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: &lt;source/destination predicate&gt;
+ *
+ * <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: &lt;source/destination predicate&gt;
+ *
+ * <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: &lt;source/destination predicate&gt;
+ *
+ * <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: &lt;source/destination predicate&gt;
+ *
+ * <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: &lt;source/destination predicate&gt;
+ *
+ * <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]


Reply via email to