Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unlabeled rel in match patterns #1087

Merged
merged 1 commit into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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