Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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.
*/

package org.apache.geaflow.dsl.operator;

import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlFunction;
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.geaflow.dsl.sqlnode.SqlSameCall;

/**
* SqlOperator for the ISO-GQL SAME predicate function.
*
* <p>This operator represents the SAME function which checks element identity.
*
* <p>Syntax: SAME(element1, element2, ...)
*
* <p>Returns: BOOLEAN - TRUE if all element references point to the same element,
* FALSE otherwise.
*
* <p>Implements ISO/IEC 39075:2024 Section 19.12.
*/
public class SqlSameOperator extends SqlFunction {

public static final SqlSameOperator INSTANCE = new SqlSameOperator();

private SqlSameOperator() {
super(
"SAME",
SqlKind.OTHER_FUNCTION,
ReturnTypes.BOOLEAN,
null,
// At least 2 operands, all must be of comparable types
OperandTypes.VARIADIC,
SqlFunctionCategory.USER_DEFINED_FUNCTION
);
}

@Override
public SqlCall createCall(
SqlLiteral functionQualifier,
SqlParserPos pos,
SqlNode... operands) {
return new SqlSameCall(pos, java.util.Arrays.asList(operands));
}

@Override
public void unparse(
SqlWriter writer,
SqlCall call,
int leftPrec,
int rightPrec) {
call.unparse(writer, leftPrec, rightPrec);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* 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.
*/

package org.apache.geaflow.dsl.sqlnode;

import java.util.List;
import java.util.Objects;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.geaflow.dsl.operator.SqlSameOperator;

/**
* SqlNode representing the ISO-GQL SAME predicate function.
*
* <p>The SAME predicate checks if multiple element references point to the same
* graph element (identity check, not value equality).
*
* <p>Syntax: SAME(element_ref1, element_ref2 [, element_ref3, ...])
*
* <p>Example:
* <pre>
* MATCH (a:Person)-[:KNOWS]->(b), (b)-[:KNOWS]->(c)
* WHERE SAME(a, c)
* RETURN a.name, b.name;
* </pre>
*
* <p>This returns triangular paths where the start and end vertices are the same element.
*
* <p>Implements ISO/IEC 39075:2024 Section 19.12 (SAME predicate).
*/
public class SqlSameCall extends SqlCall {

private final List<SqlNode> operands;

/**
* Creates a SqlSameCall.
*
* @param pos Parser position
* @param operands List of element reference expressions (must be 2 or more)
*/
public SqlSameCall(SqlParserPos pos, List<SqlNode> operands) {
super(pos);
this.operands = Objects.requireNonNull(operands, "operands");

// ISO-GQL requires at least 2 arguments
if (operands.size() < 2) {
throw new IllegalArgumentException(
"SAME predicate requires at least 2 arguments, got: " + operands.size());
}
}

@Override
public SqlOperator getOperator() {
return SqlSameOperator.INSTANCE;
}

@Override
public List<SqlNode> getOperandList() {
return operands;
}

@Override
public void validate(SqlValidator validator, SqlValidatorScope scope) {
// Validation will be handled by GQLSameValidator
// This just validates the syntax is correct
for (SqlNode operand : operands) {
operand.validate(validator, scope);
}
}

@Override
public void setOperand(int i, SqlNode operand) {
if (i < 0 || i >= operands.size()) {
throw new IllegalArgumentException("Invalid operand index: " + i);
}
operands.set(i, operand);
}

@Override
public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
writer.print("SAME");
final SqlWriter.Frame frame =
writer.startList(SqlWriter.FrameTypeEnum.FUN_CALL, "(", ")");

for (int i = 0; i < operands.size(); i++) {
if (i > 0) {
writer.sep(",");
}
operands.get(i).unparse(writer, 0, 0);
}

writer.endList(frame);
}

/**
* Returns the number of operands (element references) in this SAME call.
*/
public int getOperandCount() {
return operands.size();
}

/**
* Returns the operand at the specified index.
*/
public SqlNode getOperand(int index) {
return operands.get(index);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,11 @@ public void testIsoGQLMatch() throws Exception {
String unParseStmts = parseStmtsAndUnParse(parseStmtsAndUnParse(unParseSql));
Assert.assertEquals(unParseStmts, unParseSql);
}

@Test
public void testIsoGQLSamePredicate() throws Exception {
String unParseSql = parseSqlAndUnParse("IsoGQLSame.sql");
String unParseStmts = parseStmtsAndUnParse(parseStmtsAndUnParse(unParseSql));
Assert.assertEquals(unParseStmts, unParseSql);
}
}
Original file line number Diff line number Diff line change
@@ -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.
*/

MATCH (a:person)-[:know]->(b:person), (b)-[:know]->(c:person) WHERE SAME(a, c) RETURN a.name, b.name;
MATCH (a:person)-[:know]->(b:person), (c:person)-[:know]->(d:person) WHERE SAME(a, c) RETURN a.id, b.id, c.id, d.id;
MATCH (a:person {id: 1})-[e1:know]->(b:person), (c:person)-[e2:know]->(d:person) WHERE SAME(a, b, c) RETURN a.id, b.id;
MATCH (a:person)-[e1:know]->(b:person)-[e2:know]->(c:person) WHERE SAME(a, c) RETURN a.name, b.name, c.name;
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.util.ReflectiveSqlOperatorTable;
import org.apache.geaflow.dsl.operator.SqlSameOperator;

public class BuildInSqlOperatorTable extends ReflectiveSqlOperatorTable {

Expand Down Expand Up @@ -173,7 +174,9 @@ public class BuildInSqlOperatorTable extends ReflectiveSqlOperatorTable {
SqlStdOperatorTable.CUME_DIST,
SqlStdOperatorTable.ROW_NUMBER,
SqlStdOperatorTable.LAG,
SqlStdOperatorTable.LEAD
SqlStdOperatorTable.LEAD,
// ISO-GQL SAME predicate
SqlSameOperator.INSTANCE
};

public BuildInSqlOperatorTable() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Objects;
import java.util.Random;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.geaflow.common.binary.BinaryString;
import org.apache.geaflow.dsl.common.data.RowEdge;
import org.apache.geaflow.dsl.common.data.RowVertex;

public final class GeaFlowBuiltinFunctions {

Expand Down Expand Up @@ -1428,6 +1431,41 @@ public static Boolean equal(Object a, Object b) {
return a.equals(b);
}

/**
* ISO-GQL SAME predicate function.
* Checks if two graph elements refer to the same element by comparing their identities.
* For vertices, compares vertex IDs.
* For edges, compares both source and target IDs.
*
* @param a first element (vertex or edge)
* @param b second element (vertex or edge)
* @return true if elements have the same identity, false otherwise, null if either is null
*/
public static Boolean same(Object a, Object b) {
if (a == null || b == null) {
return null;
}
try {
// Handle vertex-vertex comparison
if (a instanceof RowVertex && b instanceof RowVertex) {
Object idA = ((RowVertex) a).getId();
Object idB = ((RowVertex) b).getId();
return Objects.equals(idA, idB);
}
// Handle edge-edge comparison
if (a instanceof RowEdge && b instanceof RowEdge) {
RowEdge edgeA = (RowEdge) a;
RowEdge edgeB = (RowEdge) b;
return Objects.equals(edgeA.getSrcId(), edgeB.getSrcId())
&& Objects.equals(edgeA.getTargetId(), edgeB.getTargetId());
}
// Different types cannot be the same
return false;
} catch (ClassCastException ex) {
return false;
}
}

public static Boolean unequal(Long a, Long b) {
if (a == null || b == null) {
return null;
Expand Down
Loading