Skip to content

Commit

Permalink
[ISSUE-395] Support for Standard Match Operator (#429)
Browse files Browse the repository at this point in the history
  • Loading branch information
Leomrlin authored Dec 13, 2024
1 parent 10066a3 commit a5978f8
Show file tree
Hide file tree
Showing 37 changed files with 560 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ data: {
"IF"
"USING"
"YIELD"
"NEXT"
]

# List of additional join types. Each is a method with no arguments.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ SqlCall GQLMatchStatement() :
(
statement = SqlLetStatement(statement) (<COMMA> statement = SqlLetStatement(statement))*
[
<MATCH>
[ <NEXT> ] <MATCH>
statement = SqlMatchPattern(statement)
]
)
|
(
<MATCH>
[ <NEXT> ] <MATCH>
statement = SqlMatchPattern(statement)
)
)*
Expand Down Expand Up @@ -157,12 +157,37 @@ SqlReturnStatement SqlReturn(SqlNode from) :
}
}

SqlNodeList SqlMatchNodePropertySpecification() :
{
List<SqlNode> propertySpecificationList = null;
SqlIdentifier variable = null;
SqlNode expr = null;
Span s = Span.of();
}
{
{
propertySpecificationList = new ArrayList<SqlNode>();
}
variable = SimpleIdentifier() { propertySpecificationList.add(variable); }
<COLON>
expr = Expression(ExprContext.ACCEPT_NON_QUERY) { propertySpecificationList.add(expr);}
(
<COMMA>
variable = SimpleIdentifier() { propertySpecificationList.add(variable); }
<COLON>
expr = Expression(ExprContext.ACCEPT_NON_QUERY) { propertySpecificationList.add(expr);}
)*
{
return new SqlNodeList(propertySpecificationList, s.addAll(propertySpecificationList).pos());
}
}

SqlCall SqlMatchNode() :
{
SqlIdentifier variable = null;
SqlNodeList labels = null;
SqlIdentifier label = null;
SqlNodeList propertySpecification = null;
SqlNode condition = null;
Span s = Span.of();
}
Expand All @@ -182,16 +207,23 @@ SqlCall SqlMatchNode() :
labels = new SqlNodeList(labelList, s.addAll(labelList).pos());
}
]

[
<WHERE>
condition = Expression(ExprContext.ACCEPT_SUB_QUERY)
(
<LBRACE>
propertySpecification = SqlMatchNodePropertySpecification()
<RBRACE>
)
|
(
<WHERE>
condition = Expression(ExprContext.ACCEPT_NON_QUERY)
)
]

<RPAREN>

{
return new SqlMatchNode(s.end(this), variable, labels, condition);
return new SqlMatchNode(s.end(this), variable, labels, propertySpecification, condition);
}

}
Expand All @@ -201,6 +233,7 @@ SqlCall SqlMatchEdge() :
SqlIdentifier variable = null;
SqlNodeList labels = null;
SqlIdentifier label = null;
SqlNodeList propertySpecification = null;
SqlNode condition = null;
Span s = Span.of();
EdgeDirection direction = null;
Expand Down Expand Up @@ -229,10 +262,17 @@ SqlCall SqlMatchEdge() :
labels = new SqlNodeList(labelList, s.addAll(labelList).pos());
}
]

[
<WHERE>
condition = Expression(ExprContext.ACCEPT_NON_QUERY)
(
<LBRACE>
propertySpecification = SqlMatchNodePropertySpecification()
<RBRACE>
)
|
(
<WHERE>
condition = Expression(ExprContext.ACCEPT_NON_QUERY)
)
]
<RBRACKET>
<MINUS>
Expand All @@ -242,12 +282,17 @@ SqlCall SqlMatchEdge() :
]
[
<LBRACE> { minHop = -1; maxHop = -1; }
<UNSIGNED_INTEGER_LITERAL> { minHop = Integer.parseInt(token.image); }
<COMMA> [ <UNSIGNED_INTEGER_LITERAL> { maxHop = Integer.parseInt(token.image); } ]
<UNSIGNED_INTEGER_LITERAL> { minHop = Integer.parseInt(token.image); maxHop = minHop;}
[
(<COMMA> <UNSIGNED_INTEGER_LITERAL>) { maxHop = Integer.parseInt(token.image); }
|
(<COMMA>) { maxHop = -1; }
]
<RBRACE>
]
{
return new SqlMatchEdge(s.end(this), variable, labels, condition, direction, minHop, maxHop);
return new SqlMatchEdge(s.end(this), variable, labels, propertySpecification, condition,
direction, minHop, maxHop);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public SqlCall createCall(
SqlNode... operands) {
String directionName = operands[3].toString();

return new SqlMatchEdge(pos, (SqlIdentifier) operands[0], (SqlNodeList) operands[1], operands[2],
return new SqlMatchEdge(pos, (SqlIdentifier) operands[0], (SqlNodeList) operands[1],
(SqlNodeList) operands[2], operands[3],
EdgeDirection.of(directionName), 1, 1);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public SqlCall createCall(
SqlLiteral functionQualifier,
SqlParserPos pos,
SqlNode... operands) {
return new SqlMatchNode(pos, (SqlIdentifier) operands[0], (SqlNodeList) operands[1], operands[2]);
return new SqlMatchNode(pos, (SqlIdentifier) operands[0], (SqlNodeList) operands[1],
(SqlNodeList) operands[2], operands[3]);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ public class SqlMatchEdge extends SqlMatchNode {
private final int maxHop;

public SqlMatchEdge(SqlParserPos pos, SqlIdentifier name,
SqlNodeList labels, SqlNode where,
SqlNodeList labels, SqlNodeList propertySpecification, SqlNode where,
EdgeDirection direction,
int minHop, int maxHop) {
super(pos, name, labels, where);
super(pos, name, labels, propertySpecification, where);
this.direction = direction;
this.minHop = minHop;
this.maxHop = maxHop;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.calcite.sql.SqlBasicCall;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
Expand All @@ -38,11 +40,18 @@ public class SqlMatchNode extends SqlCall {

private SqlNode where;

public SqlMatchNode(SqlParserPos pos, SqlIdentifier name, SqlNodeList labels, SqlNode where) {
private SqlNode combineWhere;

private SqlNodeList propertySpecification;

public SqlMatchNode(SqlParserPos pos, SqlIdentifier name, SqlNodeList labels,
SqlNodeList propertySpecification, SqlNode where) {
super(pos);
this.name = name;
this.labels = labels;
this.propertySpecification = propertySpecification;
this.where = where;
this.combineWhere = getInnerWhere(propertySpecification, where);
}

@Override
Expand All @@ -57,7 +66,7 @@ public SqlKind getKind() {

@Override
public List<SqlNode> getOperandList() {
return ImmutableNullableList.of(name, labels, where);
return ImmutableNullableList.of(name, labels, propertySpecification, where);
}

@Override
Expand All @@ -70,6 +79,9 @@ public void setOperand(int i, SqlNode operand) {
this.labels = (SqlNodeList) operand;
break;
case 2:
this.propertySpecification = (SqlNodeList) operand;
break;
case 3:
this.where = operand;
break;
default:
Expand Down Expand Up @@ -103,6 +115,20 @@ protected void unparseNode(SqlWriter writer) {
writer.keyword("where");
where.unparse(writer, 0, 0);
}
if (propertySpecification != null && propertySpecification.size() > 0) {
writer.keyword("{");
int idx = 0;
for (SqlNode node : propertySpecification.getList()) {
if (idx % 2 != 0) {
writer.keyword(":");
} else if (idx > 0) {
writer.keyword(",");
}
node.unparse(writer, 0, 0);
idx++;
}
writer.keyword("}");
}
}

public SqlIdentifier getNameId() {
Expand Down Expand Up @@ -139,11 +165,45 @@ public String getName() {
}

public SqlNode getWhere() {
return combineWhere;
}

public static SqlNode getInnerWhere(SqlNodeList propertySpecification, SqlNode where) {
//If where is null but property specification is not null,
// construct where SqlNode use the property specification.
if (propertySpecification != null && propertySpecification.size() >= 2) {
List<SqlNode> propertyEqualsConditions = new ArrayList<>();

for (int idx = 0; idx < propertySpecification.size(); idx += 2) {
SqlNode left = propertySpecification.get(idx);
SqlNode right = propertySpecification.get(idx + 1);
SqlNode equalsCondition = makeEqualsSqlNode(left, right);
propertyEqualsConditions.add(equalsCondition);
}
if (where != null) {
propertyEqualsConditions.add(where);
}
return makeAndSqlNode(propertyEqualsConditions);
}
return where;
}

private static SqlNode makeEqualsSqlNode(SqlNode leftNode, SqlNode rightNode) {
return new SqlBasicCall(SqlStdOperatorTable.EQUALS, new SqlNode[]{leftNode, rightNode},
leftNode.getParserPosition());
}

private static SqlNode makeAndSqlNode(List<SqlNode> conditions) {
if (conditions.size() == 1) {
return conditions.get(0); // 只有一个条件时直接返回
} else {
return new SqlBasicCall(SqlStdOperatorTable.AND, conditions.toArray(new SqlNode[0]),
conditions.get(0).getParserPosition());
}
}

public void setWhere(SqlNode where) {
this.where = where;
this.combineWhere = where;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2023 AntGroup CO., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/

package com.antgroup.geaflow.dsl;

import org.testng.Assert;
import org.testng.annotations.Test;

@Test(groups = "SyntaxTest")
public class IsoGqlSyntaxTest extends BaseDslTest {

@Test
public void testIsoGQLMatch() throws Exception {
String unParseSql = parseSqlAndUnParse("IsoGQLMatch.sql");
String unParseStmts = parseStmtsAndUnParse(parseStmtsAndUnParse(unParseSql));
Assert.assertEquals(unParseStmts, unParseSql);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
MATCH (n:person{id: 1}) where n.born != 1000 RETURN n.name, n.born, n.id;
MATCH (a:person WHERE a.id = 1)-[e:know]->{6}(b:person) return e;
MATCH (n:person{id: 1, name: 'c'}) where n.born != 1000 RETURN n.name, n.born, n.id;
MATCH (n:person{id: 1}) where n.born < 1950 AND n.born > 1900 RETURN n.name, n.born, n.id;
MATCH (a:person {id: 1})-[e1:know]->(b:person)-[e2:acted_in]->(c:movie)
RETURN a.id, e1.weight, b.id, b.name, e2.role, c.title LIMIT 10;
MATCH (a:person {id: 1})-[e1:know]->(b:person)-[e2:acted_in]->(c:movie)
RETURN a.id, e1.weight, b.id, b.name, e2.role, c.title LIMIT 2;
MATCH (a:person {id: 1})-[e1:know]->{2}(b:person) return a.id, b.id, b.name;
MATCH (a:person WHERE a.id = 1)-[e:know]->{6}(b:person) return e;
MATCH p = (a:person WHERE a.id = 1) -[e:know]->{1, 6} (b:person) RETURN p;
MATCH (n:person{id: 1}) RETURN n.name, n.born, n.id;
MATCH (a:person {id: 3})-[b:acted_in]->(c:movie) RETURN b.role, b.act_year;
MATCH (a:person {id: 1})-[e1:know]->(b:person) return a.id, a.born, b.id, b.name, b.born;
MATCH (a:person {id: 1})-[e1:know]->{2}(b:person) return a.id, b.id, b.name, b.born;
MATCH (a:person {id: 1})-[e:know]->(b:person) return e.weight + b.born - b.id;
MATCH (n:person WHERE n.id = 1) -[e:know]->{2}(m:person) RETURN m.id;
MATCH (n:person{id:5})-[:know]->(m:person)<-[:know]-(k:person)-[:know]->(t:person)
RETURN n.id, m.id, k.id, t.id;
MATCH (n:person{id:1})-[:know]->(m:person)<-[:know]-(k:person) RETURN n.id, m.id, k.id;
MATCH (n:person{id:1}) RETURN n, n.name;
MATCH (a:person {id: 3})-[b:acted_in]->(c:movie) RETURN a,c;
MATCH (a:person {id: 1})-[e:know]->(c:person {id:3}) RETURN e;
MATCH (a:person {id: 3})-[e:acted_in]->(c:movie) RETURN e;
MATCH p=(a:person{id:1})-[e:know]->(b:person) RETURN a.id, a.name, b.id, b.name, p;
MATCH p=(a:person{id:1})-[e:know]->(b:person)-[e2:acted_in]->(c:movie) RETURN a.id, b.id, c.id, p;
MATCH (a:person {id: 1}) return a.born + a.born;
MATCH (a:person {id: 1})-[e:know]->(b:person) return a.born + b.born - b.id;
MATCH (n:person{id: 1}) RETURN n.name, n.born, n.id;
MATCH (a:person {id: 3})-[b:acted_in]->(c:movie) RETURN a.id, b.role, c.id LIMIT 10;
MATCH (a:person {id: 3})-[b:acted_in]->(c:movie)
RETURN a.name, a.id, b.role, c.id, c.title LIMIT 10;
MATCH (a:person {id: 3})-[b:acted_in]->(c:movie) WHERE c.id = 102
RETURN a.id, b.role, c.id LIMIT 10;
MATCH (a:person {id: 10000})-[b:acted_in]->(c:movie)
RETURN a.name, a.id, b.role, c.id, c.title LIMIT 10;
MATCH (a:person {id: 3})<-[b:know]-(c:person) RETURN a.name, a.id, b.weight, c.id, c.name;
MATCH (n:person{id: '1'}) RETURN n.name, n.born, n.id;
MATCH (a:person {id: '3'})-[b:acted_in]->(c:movie) RETURN a.id, b.role, c.id LIMIT 10;
MATCH (a:person {id: '3'})-[b:acted_in]->(c:movie)
RETURN a.name, a.id, b.role, c.id, c.title LIMIT 10;
MATCH (a:person {id: '3'})-[b:acted_in]->(c:movie) WHERE c.id = '102'
RETURN a.id, b.role, c.id LIMIT 10;
Loading

0 comments on commit a5978f8

Please sign in to comment.