Skip to content

Commit

Permalink
Merge pull request #1087 from kuzudb/unlabeled-rel
Browse files Browse the repository at this point in the history
Unlabeled rel
  • Loading branch information
andyfengHKU committed Dec 2, 2022
2 parents 7a64f4c + 0d73d01 commit a81da23
Show file tree
Hide file tree
Showing 41 changed files with 2,739 additions and 2,247 deletions.
11 changes: 7 additions & 4 deletions src/antlr4/Cypher.g4
Original file line number Diff line number Diff line change
Expand Up @@ -259,28 +259,31 @@ oC_RelationshipPattern
;

oC_RelationshipDetail
: '[' SP? ( oC_Variable SP? )? ( oC_RelTypeName SP? )? ( oC_RangeLiteral SP? ) ? ( kU_Properties SP? ) ? ']' ;
: '[' SP? ( oC_Variable SP? )? ( oC_RelationshipTypes SP? )? ( oC_RangeLiteral SP? ) ? ( kU_Properties SP? ) ? ']' ;

// The original oC_Properties definition is oC_MapLiteral | oC_Parameter.
// We choose to not support parameter as properties which will be the decision for a long time.
// We then substitute with oC_MapLiteral definition. We create oC_MapLiteral only when we decide to add MAP type.
kU_Properties
: '{' SP? ( oC_PropertyKeyName SP? ':' SP? oC_Expression SP? ( ',' SP? oC_PropertyKeyName SP? ':' SP? oC_Expression SP? )* )? '}';

oC_RelationshipTypes
: ':' SP? oC_RelTypeName ( SP? '|' ':'? SP? oC_RelTypeName )* ;

oC_NodeLabels
: oC_NodeLabel ( SP? oC_NodeLabel )* ;
: oC_NodeLabel ( SP? oC_NodeLabel )* ;

oC_NodeLabel
: ':' SP? oC_LabelName ;

oC_RangeLiteral
: '*' SP? oC_IntegerLiteral SP? '..' SP? oC_IntegerLiteral ;
: '*' SP? oC_IntegerLiteral SP? '..' SP? oC_IntegerLiteral ;

oC_LabelName
: oC_SchemaName ;

oC_RelTypeName
: ':' SP? oC_SchemaName ;
: oC_SchemaName ;

oC_Expression
: oC_OrExpression ;
Expand Down
66 changes: 37 additions & 29 deletions src/binder/bind/bind_graph_pattern.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <set>

#include "binder/binder.h"

namespace kuzu {
Expand Down Expand Up @@ -37,29 +39,25 @@ unique_ptr<QueryGraph> Binder::bindPatternElement(
return queryGraph;
}

// TODO(Xiyang): remove this validation when we support full multi-labeled query
static void validateNodeRelConnectivity(table_id_t srcTableID, table_id_t dstTableID,
table_id_t relTableID, const CatalogContent& catalogContent) {
for (auto& [srcTableID_, dstTableID_] :
catalogContent.getRelTableSchema(relTableID)->srcDstTableIDs) {
if (srcTableID_ == srcTableID && dstTableID_ == dstTableID) {
return;
}
}
throw BinderException("Node table " + catalogContent.getNodeTableName(srcTableID) +
" doesn't connect to " + catalogContent.getNodeTableName(dstTableID) +
" through rel table " + catalogContent.getRelTableName(relTableID) + ".");
}

// E.g. MATCH (:person)-[:studyAt]->(:person) ...
static void validateNodeRelConnectivity(const Catalog& catalog_, const RelExpression& rel,
const NodeExpression& srcNode, const NodeExpression& dstNode) {
set<pair<table_id_t, table_id_t>> srcDstTableIDs;
for (auto relTableID : rel.getTableIDs()) {
for (auto [srcTableID, dstTableID] :
catalog_.getReadOnlyVersion()->getRelTableSchema(relTableID)->srcDstTableIDs) {
srcDstTableIDs.insert({srcTableID, dstTableID});
}
}
for (auto srcTableID : srcNode.getTableIDs()) {
for (auto dstTableID : dstNode.getTableIDs()) {
validateNodeRelConnectivity(
srcTableID, dstTableID, rel.getTableID(), *catalog_.getReadOnlyVersion());
if (srcDstTableIDs.contains(make_pair(srcTableID, dstTableID))) {
return;
}
}
}
throw BinderException("Nodes " + srcNode.getRawName() + " and " + dstNode.getRawName() +
" are not connected through rel " + rel.getRawName() + ".");
}

void Binder::bindQueryRel(const RelPattern& relPattern, const shared_ptr<NodeExpression>& leftNode,
Expand All @@ -72,11 +70,7 @@ void Binder::bindQueryRel(const RelPattern& relPattern, const shared_ptr<NodeExp
throw BinderException("Bind relationship " + parsedName +
" to relationship with same name is not supported.");
}
auto tableID = bindRelTable(relPattern.getTableName());
if (ANY_TABLE_ID == tableID) {
throw BinderException(
"Any-table is not supported. " + parsedName + " does not have a table.");
}
auto tableIDs = bindRelTableIDs(relPattern.getTableNames());
// bind node to rel
auto isLeftNodeSrc = RIGHT == relPattern.getDirection();
auto srcNode = isLeftNodeSrc ? leftNode : rightNode;
Expand All @@ -96,16 +90,16 @@ void Binder::bindQueryRel(const RelPattern& relPattern, const shared_ptr<NodeExp
throw BinderException("Lower bound of rel " + parsedName + " is greater than upperBound.");
}
auto queryRel = make_shared<RelExpression>(
getUniqueExpressionName(parsedName), tableID, srcNode, dstNode, lowerBound, upperBound);
getUniqueExpressionName(parsedName), tableIDs, srcNode, dstNode, lowerBound, upperBound);
if (!queryRel->isVariableLength()) {
queryRel->setInternalIDProperty(expressionBinder.bindInternalIDExpression(queryRel));
}
queryRel->setAlias(parsedName);
queryRel->setRawName(parsedName);
validateNodeRelConnectivity(catalog, *queryRel, *srcNode, *dstNode);
if (!parsedName.empty()) {
variablesInScope.insert({parsedName, queryRel});
}
validateNodeRelConnectivity(catalog, *queryRel, *srcNode, *dstNode);
for (auto i = 0u; i < relPattern.getNumPropertyKeyValPairs(); ++i) {
auto [propertyName, rhs] = relPattern.getProperty(i);
auto boundLhs = expressionBinder.bindRelPropertyExpression(queryRel, propertyName);
Expand Down Expand Up @@ -153,14 +147,28 @@ shared_ptr<NodeExpression> Binder::createQueryNode(const NodePattern& nodePatter
return queryNode;
}

unordered_set<table_id_t> Binder::bindNodeTableIDs(const vector<string>& nodeTableNames) {
unordered_set<table_id_t> Binder::bindTableIDs(
const vector<string>& tableNames, DataTypeID nodeOrRelType) {
unordered_set<table_id_t> result;
for (auto& nodeTableName : nodeTableNames) {
auto nodeTableID = bindNodeTableID(nodeTableName);
if (nodeTableID == ANY_TABLE_ID) {
throw BinderException("Any-table is not supported");
switch (nodeOrRelType) {
case NODE: {
for (auto& tableName : tableNames) {
result.insert(bindNodeTableID(tableName));
}
} break;
case REL: {
for (auto& tableName : tableNames) {
result.insert(bindRelTableID(tableName));
}
} break;
default:
throw NotImplementedException(
"bindTableIDs(" + Types::dataTypeToString(nodeOrRelType) + ").");
}
for (auto& tableID : result) {
if (tableID == ANY_TABLE_ID) {
throw BinderException("Any-table is not supported.");
}
result.insert(nodeTableID);
}
return result;
}
Expand Down
75 changes: 42 additions & 33 deletions src/binder/bind/bind_projection_clause.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ expression_vector Binder::rewriteProjectionExpressions(const expression_vector&
expression_vector result;
for (auto& expression : expressions) {
if (expression->dataType.typeID == NODE) {
for (auto& property : rewriteNodeAsAllProperties(expression)) {
for (auto& property : rewriteAsAllProperties(expression, NODE)) {
result.push_back(property);
}
} else if (expression->dataType.typeID == REL) {
for (auto& property : rewriteRelAsAllProperties(expression)) {
for (auto& property : rewriteAsAllProperties(expression, REL)) {
result.push_back(property);
}
} else {
Expand All @@ -72,43 +72,52 @@ expression_vector Binder::rewriteProjectionExpressions(const expression_vector&
return result;
}

expression_vector Binder::rewriteNodeAsAllProperties(const shared_ptr<Expression>& expression) {
auto& node = (NodeExpression&)*expression;
auto numColumns = 0u;
// Make sure columns are in the same order as specified in catalog.
unordered_map<uint32_t, string> colIdxToName;
unordered_map<string, vector<Property>> nameToProperties;
for (auto tableID : node.getTableIDs()) {
for (auto& property : catalog.getReadOnlyVersion()->getAllNodeProperties(tableID)) {
if (!nameToProperties.contains(property.name)) {
nameToProperties.insert({property.name, vector<Property>{}});
colIdxToName.insert({numColumns, property.name});
numColumns++;
expression_vector Binder::rewriteAsAllProperties(
const shared_ptr<Expression>& expression, DataTypeID nodeOrRelType) {
vector<Property> properties;
switch (nodeOrRelType) {
case NODE: {
auto& node = (NodeExpression&)*expression;
for (auto tableID : node.getTableIDs()) {
for (auto& property : catalog.getReadOnlyVersion()->getAllNodeProperties(tableID)) {
properties.push_back(property);
}
nameToProperties.at(property.name).push_back(property);
}
} break;
case REL: {
auto& rel = (RelExpression&)*expression;
for (auto tableID : rel.getTableIDs()) {
for (auto& property : catalog.getReadOnlyVersion()->getRelProperties(tableID)) {
properties.push_back(property);
}
}
} break;
default:
throw NotImplementedException(
"Cannot rewrite type " + Types::dataTypeToString(nodeOrRelType));
}
expression_vector result;
for (auto i = 0u; i < numColumns; ++i) {
auto name = colIdxToName.at(i);
auto properties = nameToProperties.at(name);
auto propertyExpression =
expressionBinder.bindNodePropertyExpression(expression, properties);
propertyExpression->setRawName(expression->getRawName() + "." + name);
result.emplace_back(propertyExpression);
}
return result;
}

expression_vector Binder::rewriteRelAsAllProperties(const shared_ptr<Expression>& expression) {
auto& rel = (RelExpression&)*expression;
expression_vector result;
for (auto& property : catalog.getReadOnlyVersion()->getRelProperties(rel.getTableID())) {
// Make sure columns are in the same order as specified in catalog.
vector<string> columnNames;
unordered_set<string> existingColumnNames;
for (auto& property : properties) {
if (TableSchema::isReservedPropertyName(property.name)) {
continue;
}
auto propertyExpression = expressionBinder.bindRelPropertyExpression(expression, property);
propertyExpression->setRawName(expression->getRawName() + "." + property.name);
if (!existingColumnNames.contains(property.name)) {
columnNames.push_back(property.name);
existingColumnNames.insert(property.name);
}
}
expression_vector result;
for (auto& columnName : columnNames) {
shared_ptr<Expression> propertyExpression;
if (nodeOrRelType == NODE) {
propertyExpression =
expressionBinder.bindNodePropertyExpression(expression, columnName);
} else {
propertyExpression = expressionBinder.bindRelPropertyExpression(expression, columnName);
}
propertyExpression->setRawName(expression->getRawName() + "." + columnName);
result.emplace_back(propertyExpression);
}
return result;
Expand Down
7 changes: 6 additions & 1 deletion src/binder/bind/bind_updating_clause.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ unique_ptr<BoundCreateNode> Binder::bindCreateNode(

unique_ptr<BoundCreateRel> Binder::bindCreateRel(
shared_ptr<RelExpression> rel, const PropertyKeyValCollection& collection) {
if (rel->getNumTableIDs() > 1) {
throw BinderException(
"Create multi-labeled rel " + rel->getRawName() + "is not supported.");
}
auto catalogContent = catalog.getReadOnlyVersion();
// CreateRel requires all properties in schema as input. So we rewrite set property to
// null if user does not specify a property in the query.
Expand All @@ -86,7 +90,8 @@ unique_ptr<BoundCreateRel> Binder::bindCreateRel(
if (collection.hasPropertyKeyValPair(*rel, property.name)) {
setItems.push_back(collection.getPropertyKeyValPair(*rel, property.name));
} else {
auto propertyExpression = expressionBinder.bindRelPropertyExpression(rel, property);
auto propertyExpression =
expressionBinder.bindRelPropertyExpression(rel, property.name);
shared_ptr<Expression> nullExpression =
LiteralExpression::createNullLiteralExpression(getUniqueExpressionName("NULL"));
nullExpression = ExpressionBinder::implicitCastIfNecessary(
Expand Down
2 changes: 1 addition & 1 deletion src/binder/binder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ shared_ptr<Expression> Binder::bindWhereExpression(const ParsedExpression& parse
return whereExpression;
}

table_id_t Binder::bindRelTable(const string& tableName) const {
table_id_t Binder::bindRelTableID(const string& tableName) const {
if (tableName.empty()) {
return ANY_TABLE_ID;
}
Expand Down
Loading

0 comments on commit a81da23

Please sign in to comment.