This is an automated email from the ASF dual-hosted git repository.
loogn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/geaflow.git
The following commit(s) were added to refs/heads/master by this push:
new 4ea648fee feat(dsl): 实现ISO-GQL源/目标谓词函数 (#675)
4ea648fee is described below
commit 4ea648fee0e7d8e91bc0d9f78407cb50a482ea78
Author: SeasonPilot <[email protected]>
AuthorDate: Tue Dec 2 09:35:10 2025 +0800
feat(dsl): 实现ISO-GQL源/目标谓词函数 (#675)
- 新增SourceDestinationFunctions工具类,实现ISO-GQL标准的源/目标谓词逻辑
- 添加四个新的UDF函数:IsSourceOf、IsNotSourceOf、IsDestinationOf、IsNotDestinationOf
- 在BuildInSqlFunctionTable中注册新的源/目标谓词函数
- 实现完整的三值逻辑处理,包括空值和无向边情况- 添加五个测试用例验证源/目标谓词的正确性- 提供详细的JavaDoc说明和ISO-GQL规则实现注释
---
.../function/SourceDestinationFunctions.java | 154 +++++++++++++++++++++
.../schema/function/BuildInSqlFunctionTable.java | 9 ++
.../dsl/udf/table/other/IsDestinationOf.java | 80 +++++++++++
.../dsl/udf/table/other/IsNotDestinationOf.java | 78 +++++++++++
.../geaflow/dsl/udf/table/other/IsNotSourceOf.java | 78 +++++++++++
.../geaflow/dsl/udf/table/other/IsSourceOf.java | 80 +++++++++++
.../runtime/query/GQLSourceDestinationTest.java | 75 ++++++++++
.../expect/gql_source_destination_001.txt | 2 +
.../expect/gql_source_destination_002.txt | 2 +
.../expect/gql_source_destination_003.txt | 2 +
.../expect/gql_source_destination_004.txt | 2 +
.../expect/gql_source_destination_005.txt | 2 +
.../resources/query/gql_source_destination_001.sql | 50 +++++++
.../resources/query/gql_source_destination_002.sql | 50 +++++++
.../resources/query/gql_source_destination_003.sql | 47 +++++++
.../resources/query/gql_source_destination_004.sql | 47 +++++++
.../resources/query/gql_source_destination_005.sql | 47 +++++++
17 files changed, 805 insertions(+)
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-common/src/main/java/org/apache/geaflow/dsl/common/function/SourceDestinationFunctions.java
b/geaflow/geaflow-dsl/geaflow-dsl-common/src/main/java/org/apache/geaflow/dsl/common/function/SourceDestinationFunctions.java
new file mode 100644
index 000000000..631792ced
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-common/src/main/java/org/apache/geaflow/dsl/common/function/SourceDestinationFunctions.java
@@ -0,0 +1,154 @@
+/*
+ * 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.common.function;
+
+import java.util.Objects;
+import org.apache.geaflow.dsl.common.data.RowEdge;
+import org.apache.geaflow.dsl.common.data.RowVertex;
+
+/**
+ * Utility class providing static methods for ISO-GQL source/destination
predicates.
+ *
+ * <p>Implements ISO-GQL Section 19.10: <source/destination predicate>
+ *
+ * <p>These static methods are called via reflection by the corresponding
runtime
+ * Expression classes (IsSourceOfExpression, IsDestinationOfExpression) for
better
+ * distributed execution safety.
+ *
+ * <p>ISO-GQL General Rules:
+ * <ul>
+ * <li>If node or edge is null, result is Unknown (null)</li>
+ * <li>If edge is undirected, result is False</li>
+ * <li>If node matches edge endpoint (source/destination), result is
True</li>
+ * <li>Otherwise, result is False</li>
+ * </ul>
+ */
+public class SourceDestinationFunctions {
+
+ /**
+ * Implements IS_SOURCE_OF predicate.
+ *
+ * @param nodeValue vertex/node object to check
+ * @param edgeValue edge object to check
+ * @return Boolean: true if node is source of edge, false if not, null if
either is null
+ */
+ public static Boolean isSourceOf(Object nodeValue, Object edgeValue) {
+ // ISO-GQL Rule 1: If node or edge is null, result is Unknown (null)
+ if (nodeValue == null || edgeValue == null) {
+ return null; // Three-valued logic: Unknown
+ }
+
+ // Validate types
+ if (!(nodeValue instanceof RowVertex)) {
+ throw new IllegalArgumentException(
+ "First operand of IS_SOURCE_OF must be a vertex/node, got: "
+ + nodeValue.getClass().getName());
+ }
+ if (!(edgeValue instanceof RowEdge)) {
+ throw new IllegalArgumentException(
+ "Second operand of IS_SOURCE_OF must be an edge, got: "
+ + edgeValue.getClass().getName());
+ }
+
+ RowVertex node = (RowVertex) nodeValue;
+ RowEdge edge = (RowEdge) edgeValue;
+
+ // ISO-GQL Rule 2: If edge is undirected, result is False
+ // Note: In GeaFlow, BOTH direction means undirected
+ if (edge.getDirect() ==
org.apache.geaflow.model.graph.edge.EdgeDirection.BOTH) {
+ return false;
+ }
+
+ // ISO-GQL Rule 3: Check if node is source of edge
+ // Compare node ID with edge source ID
+ Object nodeId = node.getId();
+ Object edgeSrcId = edge.getSrcId();
+
+ return Objects.equals(nodeId, edgeSrcId);
+ }
+
+ /**
+ * Implements IS_NOT_SOURCE_OF predicate.
+ *
+ * @param nodeValue vertex/node object to check
+ * @param edgeValue edge object to check
+ * @return Boolean: true if node is NOT source of edge, false if it is,
null if either is null
+ */
+ public static Boolean isNotSourceOf(Object nodeValue, Object edgeValue) {
+ Boolean result = isSourceOf(nodeValue, edgeValue);
+ // Three-valued logic: NOT Unknown = Unknown (null remains null)
+ return result == null ? null : !result;
+ }
+
+ /**
+ * Implements IS_DESTINATION_OF predicate.
+ *
+ * @param nodeValue vertex/node object to check
+ * @param edgeValue edge object to check
+ * @return Boolean: true if node is destination of edge, false if not,
null if either is null
+ */
+ public static Boolean isDestinationOf(Object nodeValue, Object edgeValue) {
+ // ISO-GQL Rule 1: If node or edge is null, result is Unknown (null)
+ if (nodeValue == null || edgeValue == null) {
+ return null; // Three-valued logic: Unknown
+ }
+
+ // Validate types
+ if (!(nodeValue instanceof RowVertex)) {
+ throw new IllegalArgumentException(
+ "First operand of IS_DESTINATION_OF must be a vertex/node,
got: "
+ + nodeValue.getClass().getName());
+ }
+ if (!(edgeValue instanceof RowEdge)) {
+ throw new IllegalArgumentException(
+ "Second operand of IS_DESTINATION_OF must be an edge, got: "
+ + edgeValue.getClass().getName());
+ }
+
+ RowVertex node = (RowVertex) nodeValue;
+ RowEdge edge = (RowEdge) edgeValue;
+
+ // ISO-GQL Rule 2: If edge is undirected, result is False
+ // Note: In GeaFlow, BOTH direction means undirected
+ if (edge.getDirect() ==
org.apache.geaflow.model.graph.edge.EdgeDirection.BOTH) {
+ return false;
+ }
+
+ // ISO-GQL Rule 3: Check if node is destination of edge
+ // Compare node ID with edge target ID
+ Object nodeId = node.getId();
+ Object edgeTargetId = edge.getTargetId();
+
+ return Objects.equals(nodeId, edgeTargetId);
+ }
+
+ /**
+ * Implements IS_NOT_DESTINATION_OF predicate.
+ *
+ * @param nodeValue vertex/node object to check
+ * @param edgeValue edge object to check
+ * @return Boolean: true if node is NOT destination of edge, false if it
is, null if either is null
+ */
+ public static Boolean isNotDestinationOf(Object nodeValue, Object
edgeValue) {
+ Boolean result = isDestinationOf(nodeValue, edgeValue);
+ // Three-valued logic: NOT Unknown = Unknown (null remains null)
+ return result == null ? null : !result;
+ }
+}
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/BuildInSqlFunctionTable.java
b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/BuildInSqlFunctionTable.java
index 7c3d72fb9..5e75e5d92 100644
---
a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/BuildInSqlFunctionTable.java
+++
b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/BuildInSqlFunctionTable.java
@@ -90,6 +90,10 @@ import org.apache.geaflow.dsl.udf.table.other.EdgeTargetId;
import org.apache.geaflow.dsl.udf.table.other.EdgeTimestamp;
import org.apache.geaflow.dsl.udf.table.other.If;
import org.apache.geaflow.dsl.udf.table.other.IsDecimal;
+import org.apache.geaflow.dsl.udf.table.other.IsDestinationOf;
+import org.apache.geaflow.dsl.udf.table.other.IsNotDestinationOf;
+import org.apache.geaflow.dsl.udf.table.other.IsNotSourceOf;
+import org.apache.geaflow.dsl.udf.table.other.IsSourceOf;
import org.apache.geaflow.dsl.udf.table.other.Label;
import org.apache.geaflow.dsl.udf.table.other.VertexId;
import org.apache.geaflow.dsl.udf.table.string.Ascii2String;
@@ -205,6 +209,11 @@ public class BuildInSqlFunctionTable extends
ListSqlOperatorTable {
.add(GeaFlowFunction.of(EdgeTargetId.class))
.add(GeaFlowFunction.of(EdgeTimestamp.class))
.add(GeaFlowFunction.of(IsDecimal.class))
+ // ISO-GQL source/destination predicates
+ .add(GeaFlowFunction.of(IsSourceOf.class))
+ .add(GeaFlowFunction.of(IsNotSourceOf.class))
+ .add(GeaFlowFunction.of(IsDestinationOf.class))
+ .add(GeaFlowFunction.of(IsNotDestinationOf.class))
// UDAF
.add(GeaFlowFunction.of(PercentileLong.class))
.add(GeaFlowFunction.of(PercentileInteger.class))
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsDestinationOf.java
b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsDestinationOf.java
new file mode 100644
index 000000000..3beeb7a3d
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsDestinationOf.java
@@ -0,0 +1,80 @@
+/*
+ * 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.udf.table.other;
+
+import org.apache.geaflow.dsl.common.data.RowEdge;
+import org.apache.geaflow.dsl.common.data.RowVertex;
+import org.apache.geaflow.dsl.common.function.Description;
+import org.apache.geaflow.dsl.common.function.SourceDestinationFunctions;
+import org.apache.geaflow.dsl.common.function.UDF;
+
+/**
+ * UDF implementation for ISO-GQL IS DESTINATION OF predicate.
+ *
+ * <p>Implements ISO-GQL Section 19.10: <source/destination predicate>
+ *
+ * <p><b>Syntax:</b></p>
+ * <pre>
+ * IS_DESTINATION_OF(node, edge)
+ * </pre>
+ *
+ * <p><b>Semantics:</b></p>
+ * Returns TRUE if the node is the destination of the edge, FALSE otherwise,
or NULL if either operand is NULL.
+ *
+ * <p><b>ISO-GQL Rules:</b></p>
+ * <ul>
+ * <li>If node or edge is null, result is Unknown (null)</li>
+ * <li>If edge is undirected, result is False</li>
+ * <li>If node.id equals edge.targetId, result is True</li>
+ * <li>Otherwise, result is False</li>
+ * </ul>
+ *
+ * <p><b>Example:</b></p>
+ * <pre>
+ * MATCH (a) -[e]-> (b)
+ * WHERE IS_DESTINATION_OF(b, e)
+ * RETURN a, e, b
+ * </pre>
+ */
+@Description(
+ name = "is_destination_of",
+ description = "ISO-GQL Destination Predicate: Returns TRUE if node is the
destination of edge, "
+ + "FALSE if not, NULL if either operand is NULL. Follows ISO-GQL
three-valued logic."
+)
+public class IsDestinationOf extends UDF {
+
+ /**
+ * Evaluates IS DESTINATION OF predicate.
+ *
+ * @param nodeValue vertex/node to check (should be RowVertex)
+ * @param edgeValue edge to check (should be RowEdge)
+ * @return Boolean: true if node is destination of edge, false if not,
null if either is null
+ */
+ public Boolean eval(Object nodeValue, Object edgeValue) {
+ return SourceDestinationFunctions.isDestinationOf(nodeValue,
edgeValue);
+ }
+
+ /**
+ * Type-specific overload for better type checking.
+ */
+ public Boolean eval(RowVertex node, RowEdge edge) {
+ return SourceDestinationFunctions.isDestinationOf(node, edge);
+ }
+}
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotDestinationOf.java
b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotDestinationOf.java
new file mode 100644
index 000000000..b3cf5c44e
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotDestinationOf.java
@@ -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.udf.table.other;
+
+import org.apache.geaflow.dsl.common.data.RowEdge;
+import org.apache.geaflow.dsl.common.data.RowVertex;
+import org.apache.geaflow.dsl.common.function.Description;
+import org.apache.geaflow.dsl.common.function.SourceDestinationFunctions;
+import org.apache.geaflow.dsl.common.function.UDF;
+
+/**
+ * UDF implementation for ISO-GQL IS NOT DESTINATION OF predicate.
+ *
+ * <p>Implements ISO-GQL Section 19.10: <source/destination predicate>
+ *
+ * <p><b>Syntax:</b></p>
+ * <pre>
+ * IS_NOT_DESTINATION_OF(node, edge)
+ * </pre>
+ *
+ * <p><b>Semantics:</b></p>
+ * Returns TRUE if the node is NOT the destination of the edge, FALSE if it
is, or NULL if either operand is NULL.
+ *
+ * <p><b>ISO-GQL Rules:</b></p>
+ * <ul>
+ * <li>If node or edge is null, result is Unknown (null)</li>
+ * <li>Otherwise, returns the negation of IS_DESTINATION_OF result</li>
+ * </ul>
+ *
+ * <p><b>Example:</b></p>
+ * <pre>
+ * MATCH (a) -[e]-> (b)
+ * WHERE IS_NOT_DESTINATION_OF(a, e) -- Always TRUE for this pattern
+ * RETURN a, e, b
+ * </pre>
+ */
+@Description(
+ name = "is_not_destination_of",
+ description = "ISO-GQL Destination Predicate: Returns TRUE if node is NOT
the destination of edge, "
+ + "FALSE if it is, NULL if either operand is NULL. Follows ISO-GQL
three-valued logic."
+)
+public class IsNotDestinationOf extends UDF {
+
+ /**
+ * Evaluates IS NOT DESTINATION OF predicate.
+ *
+ * @param nodeValue vertex/node to check (should be RowVertex)
+ * @param edgeValue edge to check (should be RowEdge)
+ * @return Boolean: true if node is NOT destination of edge, false if it
is, null if either is null
+ */
+ public Boolean eval(Object nodeValue, Object edgeValue) {
+ return SourceDestinationFunctions.isNotDestinationOf(nodeValue,
edgeValue);
+ }
+
+ /**
+ * Type-specific overload for better type checking.
+ */
+ public Boolean eval(RowVertex node, RowEdge edge) {
+ return SourceDestinationFunctions.isNotDestinationOf(node, edge);
+ }
+}
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotSourceOf.java
b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotSourceOf.java
new file mode 100644
index 000000000..c95c616c6
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotSourceOf.java
@@ -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.udf.table.other;
+
+import org.apache.geaflow.dsl.common.data.RowEdge;
+import org.apache.geaflow.dsl.common.data.RowVertex;
+import org.apache.geaflow.dsl.common.function.Description;
+import org.apache.geaflow.dsl.common.function.SourceDestinationFunctions;
+import org.apache.geaflow.dsl.common.function.UDF;
+
+/**
+ * UDF implementation for ISO-GQL IS NOT SOURCE OF predicate.
+ *
+ * <p>Implements ISO-GQL Section 19.10: <source/destination predicate>
+ *
+ * <p><b>Syntax:</b></p>
+ * <pre>
+ * IS_NOT_SOURCE_OF(node, edge)
+ * </pre>
+ *
+ * <p><b>Semantics:</b></p>
+ * Returns TRUE if the node is NOT the source of the edge, FALSE if it is, or
NULL if either operand is NULL.
+ *
+ * <p><b>ISO-GQL Rules:</b></p>
+ * <ul>
+ * <li>If node or edge is null, result is Unknown (null)</li>
+ * <li>Otherwise, returns the negation of IS_SOURCE_OF result</li>
+ * </ul>
+ *
+ * <p><b>Example:</b></p>
+ * <pre>
+ * MATCH (a) -[e]-> (b)
+ * WHERE IS_NOT_SOURCE_OF(a, e) -- Always FALSE for this pattern
+ * RETURN a, e, b
+ * </pre>
+ */
+@Description(
+ name = "is_not_source_of",
+ description = "ISO-GQL Source Predicate: Returns TRUE if node is NOT the
source of edge, "
+ + "FALSE if it is, NULL if either operand is NULL. Follows ISO-GQL
three-valued logic."
+)
+public class IsNotSourceOf extends UDF {
+
+ /**
+ * Evaluates IS NOT SOURCE OF predicate.
+ *
+ * @param nodeValue vertex/node to check (should be RowVertex)
+ * @param edgeValue edge to check (should be RowEdge)
+ * @return Boolean: true if node is NOT source of edge, false if it is,
null if either is null
+ */
+ public Boolean eval(Object nodeValue, Object edgeValue) {
+ return SourceDestinationFunctions.isNotSourceOf(nodeValue, edgeValue);
+ }
+
+ /**
+ * Type-specific overload for better type checking.
+ */
+ public Boolean eval(RowVertex node, RowEdge edge) {
+ return SourceDestinationFunctions.isNotSourceOf(node, edge);
+ }
+}
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsSourceOf.java
b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsSourceOf.java
new file mode 100644
index 000000000..c6d1cccde
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsSourceOf.java
@@ -0,0 +1,80 @@
+/*
+ * 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.udf.table.other;
+
+import org.apache.geaflow.dsl.common.data.RowEdge;
+import org.apache.geaflow.dsl.common.data.RowVertex;
+import org.apache.geaflow.dsl.common.function.Description;
+import org.apache.geaflow.dsl.common.function.SourceDestinationFunctions;
+import org.apache.geaflow.dsl.common.function.UDF;
+
+/**
+ * UDF implementation for ISO-GQL IS SOURCE OF predicate.
+ *
+ * <p>Implements ISO-GQL Section 19.10: <source/destination predicate>
+ *
+ * <p><b>Syntax:</b></p>
+ * <pre>
+ * IS_SOURCE_OF(node, edge)
+ * </pre>
+ *
+ * <p><b>Semantics:</b></p>
+ * Returns TRUE if the node is the source of the edge, FALSE otherwise, or
NULL if either operand is NULL.
+ *
+ * <p><b>ISO-GQL Rules:</b></p>
+ * <ul>
+ * <li>If node or edge is null, result is Unknown (null)</li>
+ * <li>If edge is undirected, result is False</li>
+ * <li>If node.id equals edge.srcId, result is True</li>
+ * <li>Otherwise, result is False</li>
+ * </ul>
+ *
+ * <p><b>Example:</b></p>
+ * <pre>
+ * MATCH (a) -[e]-> (b)
+ * WHERE IS_SOURCE_OF(a, e)
+ * RETURN a, e, b
+ * </pre>
+ */
+@Description(
+ name = "is_source_of",
+ description = "ISO-GQL Source Predicate: Returns TRUE if node is the
source of edge, "
+ + "FALSE if not, NULL if either operand is NULL. Follows ISO-GQL
three-valued logic."
+)
+public class IsSourceOf extends UDF {
+
+ /**
+ * Evaluates IS SOURCE OF predicate.
+ *
+ * @param nodeValue vertex/node to check (should be RowVertex)
+ * @param edgeValue edge to check (should be RowEdge)
+ * @return Boolean: true if node is source of edge, false if not, null if
either is null
+ */
+ public Boolean eval(Object nodeValue, Object edgeValue) {
+ return SourceDestinationFunctions.isSourceOf(nodeValue, edgeValue);
+ }
+
+ /**
+ * Type-specific overload for better type checking.
+ */
+ public Boolean eval(RowVertex node, RowEdge edge) {
+ return SourceDestinationFunctions.isSourceOf(node, edge);
+ }
+}
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/GQLSourceDestinationTest.java
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/GQLSourceDestinationTest.java
new file mode 100644
index 000000000..fee0fd1cc
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/GQLSourceDestinationTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.runtime.query;
+
+import org.testng.annotations.Test;
+
+public class GQLSourceDestinationTest {
+
+ @Test
+ public void testSourceDestination_001() throws Exception {
+ QueryTester
+ .build()
+ .withGraphDefine("/query/modern_graph.sql")
+ .withQueryPath("/query/gql_source_destination_001.sql")
+ .execute()
+ .checkSinkResult();
+ }
+
+ @Test
+ public void testSourceDestination_002() throws Exception {
+ QueryTester
+ .build()
+ .withGraphDefine("/query/modern_graph.sql")
+ .withQueryPath("/query/gql_source_destination_002.sql")
+ .execute()
+ .checkSinkResult();
+ }
+
+ @Test
+ public void testSourceDestination_003() throws Exception {
+ QueryTester
+ .build()
+ .withGraphDefine("/query/modern_graph.sql")
+ .withQueryPath("/query/gql_source_destination_003.sql")
+ .execute()
+ .checkSinkResult();
+ }
+
+ @Test
+ public void testSourceDestination_004() throws Exception {
+ QueryTester
+ .build()
+ .withGraphDefine("/query/modern_graph.sql")
+ .withQueryPath("/query/gql_source_destination_004.sql")
+ .execute()
+ .checkSinkResult();
+ }
+
+ @Test
+ public void testSourceDestination_005() throws Exception {
+ QueryTester
+ .build()
+ .withGraphDefine("/query/modern_graph.sql")
+ .withQueryPath("/query/gql_source_destination_005.sql")
+ .execute()
+ .checkSinkResult();
+ }
+}
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_001.txt
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_001.txt
new file mode 100644
index 000000000..f998ff784
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_001.txt
@@ -0,0 +1,2 @@
+1,marko,0.5,2,vadas,true
+1,marko,1.0,4,josh,true
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_002.txt
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_002.txt
new file mode 100644
index 000000000..f998ff784
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_002.txt
@@ -0,0 +1,2 @@
+1,marko,0.5,2,vadas,true
+1,marko,1.0,4,josh,true
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_003.txt
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_003.txt
new file mode 100644
index 000000000..bd9f37bb1
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_003.txt
@@ -0,0 +1,2 @@
+1,marko,2,vadas
+1,marko,4,josh
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_004.txt
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_004.txt
new file mode 100644
index 000000000..bd9f37bb1
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_004.txt
@@ -0,0 +1,2 @@
+1,marko,2,vadas
+1,marko,4,josh
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_005.txt
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_005.txt
new file mode 100644
index 000000000..bd9f37bb1
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_source_destination_005.txt
@@ -0,0 +1,2 @@
+1,marko,2,vadas
+1,marko,4,josh
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_001.sql
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_001.sql
new file mode 100644
index 000000000..fedc8ccd3
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_001.sql
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+-- Test Case 1: Basic IS SOURCE OF predicate
+-- Tests that a node is correctly identified as the source of an edge
+
+CREATE TABLE tbl_result (
+ a_id bigint,
+ a_name varchar,
+ e_weight double,
+ b_id bigint,
+ b_name varchar,
+ is_source boolean
+) WITH (
+ type='file',
+ geaflow.dsl.file.path='${target}'
+);
+
+USE GRAPH modern;
+
+INSERT INTO tbl_result
+SELECT
+ a.id,
+ a.name,
+ e.weight,
+ b.id,
+ b.name,
+ IS_SOURCE_OF(a, e) as is_source
+FROM (
+ MATCH (a:person) -[e:knows]-> (b:person)
+ RETURN a, e, b
+)
+ORDER BY a.id, b.id
+;
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_002.sql
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_002.sql
new file mode 100644
index 000000000..b4d2e1094
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_002.sql
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+-- Test Case 2: Basic IS DESTINATION OF predicate
+-- Tests that a node is correctly identified as the destination of an edge
+
+CREATE TABLE tbl_result (
+ a_id bigint,
+ a_name varchar,
+ e_weight double,
+ b_id bigint,
+ b_name varchar,
+ is_destination boolean
+) WITH (
+ type='file',
+ geaflow.dsl.file.path='${target}'
+);
+
+USE GRAPH modern;
+
+INSERT INTO tbl_result
+SELECT
+ a.id,
+ a.name,
+ e.weight,
+ b.id,
+ b.name,
+ IS_DESTINATION_OF(b, e) as is_destination
+FROM (
+ MATCH (a:person) -[e:knows]-> (b:person)
+ RETURN a, e, b
+)
+ORDER BY a.id, b.id
+;
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_003.sql
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_003.sql
new file mode 100644
index 000000000..65ddf5649
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_003.sql
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+-- Test Case 3: IS NOT SOURCE OF predicate (negative test)
+-- Tests that IS NOT SOURCE OF correctly filters out source nodes
+
+CREATE TABLE tbl_result (
+ a_id bigint,
+ a_name varchar,
+ b_id bigint,
+ b_name varchar
+) WITH (
+ type='file',
+ geaflow.dsl.file.path='${target}'
+);
+
+USE GRAPH modern;
+
+INSERT INTO tbl_result
+SELECT
+ a.id,
+ a.name,
+ b.id,
+ b.name
+FROM (
+ MATCH (a:person) -[e:knows]-> (b:person)
+ WHERE IS_NOT_SOURCE_OF(b, e)
+ RETURN a, b
+)
+ORDER BY a.id, b.id
+;
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_004.sql
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_004.sql
new file mode 100644
index 000000000..828692db5
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_004.sql
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+-- Test Case 4: IS NOT DESTINATION OF predicate (negative test)
+-- Tests that IS NOT DESTINATION OF correctly filters out destination nodes
+
+CREATE TABLE tbl_result (
+ a_id bigint,
+ a_name varchar,
+ b_id bigint,
+ b_name varchar
+) WITH (
+ type='file',
+ geaflow.dsl.file.path='${target}'
+);
+
+USE GRAPH modern;
+
+INSERT INTO tbl_result
+SELECT
+ a.id,
+ a.name,
+ b.id,
+ b.name
+FROM (
+ MATCH (a:person) -[e:knows]-> (b:person)
+ WHERE IS_NOT_DESTINATION_OF(a, e)
+ RETURN a, b
+)
+ORDER BY a.id, b.id
+;
diff --git
a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_005.sql
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_005.sql
new file mode 100644
index 000000000..d52982d38
--- /dev/null
+++
b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_source_destination_005.sql
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+-- Test Case 5: Combined IS SOURCE OF and IS DESTINATION OF
+-- Tests using both predicates together in WHERE clause
+
+CREATE TABLE tbl_result (
+ a_id bigint,
+ a_name varchar,
+ b_id bigint,
+ b_name varchar
+) WITH (
+ type='file',
+ geaflow.dsl.file.path='${target}'
+);
+
+USE GRAPH modern;
+
+INSERT INTO tbl_result
+SELECT
+ a.id,
+ a.name,
+ b.id,
+ b.name
+FROM (
+ MATCH (a:person) -[e:knows]-> (b:person)
+ WHERE IS_SOURCE_OF(a, e) AND IS_DESTINATION_OF(b, e)
+ RETURN a, b
+)
+ORDER BY a.id, b.id
+;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]