From 0c1e5a4a1193d6afb7f03ddc9d56f11e28a5dfbe Mon Sep 17 00:00:00 2001 From: Sagar Rao Date: Sun, 30 Nov 2025 18:36:00 +0530 Subject: [PATCH] Adding parser changes for OPTIONAL MATCH keyword --- .../src/main/codegen/config.fmpp | 5 +++- .../src/main/codegen/includes/gqlQuery.ftl | 10 ++++---- .../dsl/operator/SqlMatchPatternOperator.java | 18 +++++++-------- .../geaflow/dsl/sqlnode/SqlMatchPattern.java | 12 +++++++--- .../geaflow/dsl/MatchReturnSyntaxTest.java | 7 ++++++ .../src/test/resources/GQLOptionalMatch.sql | 23 +++++++++++++++++++ 6 files changed, 58 insertions(+), 17 deletions(-) create mode 100644 geaflow/geaflow-dsl/geaflow-dsl-parser/src/test/resources/GQLOptionalMatch.sql diff --git a/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/codegen/config.fmpp b/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/codegen/config.fmpp index 08a2d84d7..d81a8434b 100644 --- a/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/codegen/config.fmpp +++ b/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/codegen/config.fmpp @@ -72,7 +72,8 @@ data: { "DIFFERENT", "PARTITIONED", "YIELD", - "IF" + "IF", + "OPTIONAL" ] # let noReservedKeywords can be a identifier @@ -435,6 +436,8 @@ data: { "USING" "YIELD" "NEXT" + "OPTIONAL" + ] # List of additional join types. Each is a method with no arguments. diff --git a/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/codegen/includes/gqlQuery.ftl b/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/codegen/includes/gqlQuery.ftl index e006b9a16..08602758c 100644 --- a/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/codegen/includes/gqlQuery.ftl +++ b/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/codegen/includes/gqlQuery.ftl @@ -3,18 +3,18 @@ SqlCall GQLMatchStatement() : SqlCall statement = null; } { - statement = SqlMatchPattern(statement) + statement = SqlMatchPattern(statement) ( ( statement = SqlLetStatement(statement) ( statement = SqlLetStatement(statement))* [ - [ ] + [ ] statement = SqlMatchPattern(statement) ] ) | ( - [ ] + [ ] statement = SqlMatchPattern(statement) ) )* @@ -366,8 +366,10 @@ SqlMatchPattern SqlMatchPattern(SqlNode preMatch) : SqlNode condition = null; SqlNodeList orderBy = null; SqlNode count = null; + boolean optional = false; } { + [ { optional = true; } ] pathPattern = SqlUnionPathPattern() { pathList.add(pathPattern); } ( pathPattern = SqlUnionPathPattern() { pathList.add(pathPattern); } )* @@ -387,7 +389,7 @@ SqlMatchPattern SqlMatchPattern(SqlNode preMatch) : ] { graphPattern = new SqlNodeList(pathList, s.addAll(pathList).pos()); - return new SqlMatchPattern(s.end(this), preMatch, graphPattern, condition, orderBy, count); + return new SqlMatchPattern(s.end(this), preMatch, graphPattern, condition, orderBy, count, optional); } } diff --git a/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/java/org/apache/geaflow/dsl/operator/SqlMatchPatternOperator.java b/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/java/org/apache/geaflow/dsl/operator/SqlMatchPatternOperator.java index fd5b6ebd8..7c388c2b7 100644 --- a/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/java/org/apache/geaflow/dsl/operator/SqlMatchPatternOperator.java +++ b/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/java/org/apache/geaflow/dsl/operator/SqlMatchPatternOperator.java @@ -30,16 +30,16 @@ public class SqlMatchPatternOperator extends SqlOperator { private SqlMatchPatternOperator() { super("MatchPattern", SqlKind.OTHER, 2, true, - ReturnTypes.SCOPE, null, null); + ReturnTypes.SCOPE, null, null); } @Override public SqlCall createCall( - SqlLiteral functionQualifier, - SqlParserPos pos, - SqlNode... operands) { + SqlLiteral functionQualifier, + SqlParserPos pos, + SqlNode... operands) { return new SqlMatchPattern(pos, operands[0], (SqlNodeList) operands[1], operands[2], - (SqlNodeList) operands[3], operands[4]); + (SqlNodeList) operands[3], operands[4], false); } @Override @@ -49,10 +49,10 @@ public SqlSyntax getSyntax() { @Override public void unparse( - SqlWriter writer, - SqlCall call, - int leftPrec, - int rightPrec) { + SqlWriter writer, + SqlCall call, + int leftPrec, + int rightPrec) { call.unparse(writer, leftPrec, rightPrec); } } diff --git a/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/java/org/apache/geaflow/dsl/sqlnode/SqlMatchPattern.java b/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/java/org/apache/geaflow/dsl/sqlnode/SqlMatchPattern.java index a864b1334..18a426b97 100644 --- a/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/java/org/apache/geaflow/dsl/sqlnode/SqlMatchPattern.java +++ b/geaflow/geaflow-dsl/geaflow-dsl-parser/src/main/java/org/apache/geaflow/dsl/sqlnode/SqlMatchPattern.java @@ -39,14 +39,17 @@ public class SqlMatchPattern extends SqlCall { private SqlNode limit; + private boolean optional; + public SqlMatchPattern(SqlParserPos pos, SqlNode from, SqlNodeList pathPatterns, - SqlNode where, SqlNodeList orderBy, SqlNode limit) { + SqlNode where, SqlNodeList orderBy, SqlNode limit, boolean optional) { super(pos); this.from = from; this.pathPatterns = pathPatterns; this.where = where; this.orderBy = orderBy; this.limit = limit; + this.optional = optional; } @Override @@ -57,7 +60,7 @@ public SqlOperator getOperator() { @Override public List getOperandList() { return ImmutableNullableList.of(getFrom(), getPathPatterns(), getWhere(), - getOrderBy(), getLimit()); + getOrderBy(), getLimit()); } @Override @@ -82,7 +85,6 @@ public SqlNodeList getOrderBy() { return orderBy; } - public SqlNode getLimit() { return limit; } @@ -170,4 +172,8 @@ public void setWhere(SqlNode where) { public boolean isSinglePattern() { return pathPatterns.size() == 1 && pathPatterns.get(0) instanceof SqlPathPattern; } + + public boolean isOptional() { + return optional; + } } diff --git a/geaflow/geaflow-dsl/geaflow-dsl-parser/src/test/java/org/apache/geaflow/dsl/MatchReturnSyntaxTest.java b/geaflow/geaflow-dsl/geaflow-dsl-parser/src/test/java/org/apache/geaflow/dsl/MatchReturnSyntaxTest.java index 86f0094bf..69e69fb10 100644 --- a/geaflow/geaflow-dsl/geaflow-dsl-parser/src/test/java/org/apache/geaflow/dsl/MatchReturnSyntaxTest.java +++ b/geaflow/geaflow-dsl/geaflow-dsl-parser/src/test/java/org/apache/geaflow/dsl/MatchReturnSyntaxTest.java @@ -80,4 +80,11 @@ public void testGQLMatchOrder() throws Exception { String unParseStmts = parseStmtsAndUnParse(parseStmtsAndUnParse(unParseSql)); Assert.assertEquals(unParseStmts, unParseSql); } + + @Test + public void testGQLOptionalMatch() throws Exception { + String unParseSql = parseSqlAndUnParse("GQLOptionalMatch.sql"); + String unParseStmts = parseStmtsAndUnParse(parseStmtsAndUnParse(unParseSql)); + Assert.assertEquals(unParseStmts, unParseSql); + } } diff --git a/geaflow/geaflow-dsl/geaflow-dsl-parser/src/test/resources/GQLOptionalMatch.sql b/geaflow/geaflow-dsl/geaflow-dsl-parser/src/test/resources/GQLOptionalMatch.sql new file mode 100644 index 000000000..d06c04205 --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-parser/src/test/resources/GQLOptionalMatch.sql @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +OPTIONAL MATCH (a:Person) RETURN a.name; +MATCH (a:Person) OPTIONAL MATCH (a)-[:Knows]->(b:Person) RETURN a.name, b.name; +OPTIONAL MATCH (a:Person) WHERE a.age > 20 RETURN a.name; +MATCH (a:Person) OPTIONAL MATCH (a)-[:Knows]->(b:Person) OPTIONAL MATCH (b)-[:Knows]->(c:Person) RETURN a.name, b.name, c.name;