From bc3832cea322ebdfdf8c6c7557b1b10c4e2139c7 Mon Sep 17 00:00:00 2001 From: yqz <2678785492@qq.com> Date: Sun, 2 Aug 2026 21:26:16 +0800 Subject: [PATCH 1/2] [ISSUE-358] Implement ISO-GQL labeled predicate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the ISO-GQL (ISO/IEC 39075 Section 19.9), which tests whether a graph element (vertex or edge) carries a given label. Following the pattern of the already-merged source/destination predicate (#366), this is implemented as built-in UDFs rather than a grammar change: - `IS_LABELED(element, label)` — TRUE if the element has the label. - `IS_NOT_LABELED(element, label)` — the negation. Both follow ISO-GQL three-valued logic: a null element or null label yields Unknown (null). The shared logic lives in `LabeledPredicateFunctions` (geaflow-dsl-common); the UDFs delegate to it and are registered in `BuildInSqlFunctionTable`. Tests: - `LabeledPredicateFunctionsTest` (geaflow-dsl-common) unit-tests the logic directly: vertex/edge labels, negation, three-valued null handling, and rejection of non-graph-element operands. - `GQLLabeledPredicateTest` (geaflow-dsl-runtime) is an end-to-end `.sql`/`.txt` case over the modern graph; expected outputs were computed by hand from the graph data. Co-Authored-By: Claude Opus 4.8 --- .../function/LabeledPredicateFunctions.java | 83 +++++++++++++++++++ .../LabeledPredicateFunctionsTest.java | 74 +++++++++++++++++ .../function/BuildInSqlFunctionTable.java | 5 ++ .../dsl/udf/table/other/IsLabeled.java | 81 ++++++++++++++++++ .../dsl/udf/table/other/IsNotLabeled.java | 82 ++++++++++++++++++ .../query/GQLLabeledPredicateTest.java | 35 ++++++++ .../expect/gql_labeled_predicate_001.txt | 6 ++ .../query/gql_labeled_predicate_001.sql | 51 ++++++++++++ 8 files changed, 417 insertions(+) create mode 100644 geaflow/geaflow-dsl/geaflow-dsl-common/src/main/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctions.java create mode 100644 geaflow/geaflow-dsl/geaflow-dsl-common/src/test/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctionsTest.java create mode 100644 geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsLabeled.java create mode 100644 geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotLabeled.java create mode 100644 geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/GQLLabeledPredicateTest.java create mode 100644 geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_labeled_predicate_001.txt create mode 100644 geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_labeled_predicate_001.sql diff --git a/geaflow/geaflow-dsl/geaflow-dsl-common/src/main/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctions.java b/geaflow/geaflow-dsl/geaflow-dsl-common/src/main/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctions.java new file mode 100644 index 000000000..7603c51b1 --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-common/src/main/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctions.java @@ -0,0 +1,83 @@ +/* + * 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 org.apache.geaflow.dsl.common.data.RowEdge; +import org.apache.geaflow.dsl.common.data.RowVertex; + +/** + * Utility class providing static methods for the ISO-GQL labeled predicate. + * + *

Implements ISO-GQL Section 19.9: <labeled predicate>, which tests whether a + * graph element (vertex or edge) has a given label. + * + *

ISO-GQL General Rules (three-valued logic): + *

+ */ +public class LabeledPredicateFunctions { + + /** + * Implements the IS LABELED predicate. + * + * @param elementValue the vertex or edge to check + * @param labelValue the label name to test for + * @return Boolean: true if the element has the given label, false if not, null if either + * operand is null + */ + public static Boolean isLabeled(Object elementValue, Object labelValue) { + // ISO-GQL Rule: If element or label is null, result is Unknown (null). + if (elementValue == null || labelValue == null) { + return null; + } + + String elementLabel = getLabel(elementValue); + return elementLabel != null && elementLabel.equals(labelValue.toString()); + } + + /** + * Implements the IS NOT LABELED predicate. + * + * @param elementValue the vertex or edge to check + * @param labelValue the label name to test for + * @return Boolean: true if the element does NOT have the given label, false if it does, null + * if either operand is null + */ + public static Boolean isNotLabeled(Object elementValue, Object labelValue) { + Boolean result = isLabeled(elementValue, labelValue); + // Three-valued logic: NOT Unknown = Unknown (null remains null). + return result == null ? null : !result; + } + + private static String getLabel(Object elementValue) { + if (elementValue instanceof RowVertex) { + return ((RowVertex) elementValue).getLabel(); + } + if (elementValue instanceof RowEdge) { + return ((RowEdge) elementValue).getLabel(); + } + throw new IllegalArgumentException( + "First operand of labeled predicate must be a vertex or an edge, got: " + + elementValue.getClass().getName()); + } +} diff --git a/geaflow/geaflow-dsl/geaflow-dsl-common/src/test/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctionsTest.java b/geaflow/geaflow-dsl/geaflow-dsl-common/src/test/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctionsTest.java new file mode 100644 index 000000000..a56d137dc --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-common/src/test/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctionsTest.java @@ -0,0 +1,74 @@ +/* + * 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 org.apache.geaflow.dsl.common.data.RowEdge; +import org.apache.geaflow.dsl.common.data.RowVertex; +import org.apache.geaflow.dsl.common.data.impl.types.ObjectEdge; +import org.apache.geaflow.dsl.common.data.impl.types.ObjectVertex; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class LabeledPredicateFunctionsTest { + + private RowVertex vertex(String label) { + ObjectVertex vertex = new ObjectVertex(1L); + vertex.setLabel(label); + return vertex; + } + + private RowEdge edge(String label) { + ObjectEdge edge = new ObjectEdge(1L, 2L); + edge.setLabel(label); + return edge; + } + + @Test + public void testIsLabeledOnVertex() { + Assert.assertTrue(LabeledPredicateFunctions.isLabeled(vertex("person"), "person")); + Assert.assertFalse(LabeledPredicateFunctions.isLabeled(vertex("person"), "software")); + } + + @Test + public void testIsLabeledOnEdge() { + Assert.assertTrue(LabeledPredicateFunctions.isLabeled(edge("knows"), "knows")); + Assert.assertFalse(LabeledPredicateFunctions.isLabeled(edge("knows"), "created")); + } + + @Test + public void testIsNotLabeled() { + Assert.assertFalse(LabeledPredicateFunctions.isNotLabeled(vertex("person"), "person")); + Assert.assertTrue(LabeledPredicateFunctions.isNotLabeled(vertex("person"), "software")); + } + + @Test + public void testThreeValuedLogicWithNullOperand() { + // Null element or null label yields Unknown (null) for both predicates. + Assert.assertNull(LabeledPredicateFunctions.isLabeled(null, "person")); + Assert.assertNull(LabeledPredicateFunctions.isLabeled(vertex("person"), null)); + Assert.assertNull(LabeledPredicateFunctions.isNotLabeled(null, "person")); + Assert.assertNull(LabeledPredicateFunctions.isNotLabeled(vertex("person"), null)); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNonGraphElementIsRejected() { + LabeledPredicateFunctions.isLabeled("not a graph element", "person"); + } +} 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 9656d9299..2c927fe46 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 @@ -97,7 +97,9 @@ 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.IsLabeled; import org.apache.geaflow.dsl.udf.table.other.IsNotDestinationOf; +import org.apache.geaflow.dsl.udf.table.other.IsNotLabeled; 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; @@ -226,6 +228,9 @@ public class BuildInSqlFunctionTable extends ListSqlOperatorTable { .add(GeaFlowFunction.of(IsNotSourceOf.class)) .add(GeaFlowFunction.of(IsDestinationOf.class)) .add(GeaFlowFunction.of(IsNotDestinationOf.class)) + // ISO-GQL labeled predicate + .add(GeaFlowFunction.of(IsLabeled.class)) + .add(GeaFlowFunction.of(IsNotLabeled.class)) // ISO-GQL property exists predicate .add(GeaFlowFunction.of(PropertyExists.class)) // UDAF diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsLabeled.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsLabeled.java new file mode 100644 index 000000000..7ba9bfaa6 --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsLabeled.java @@ -0,0 +1,81 @@ +/* + * 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.LabeledPredicateFunctions; +import org.apache.geaflow.dsl.common.function.UDF; + +/** + * UDF implementation for the ISO-GQL IS LABELED predicate. + * + *

Implements ISO-GQL Section 19.9: <labeled predicate> + * + *

Syntax:

+ *
+ *   IS_LABELED(element, label)
+ * 
+ * + *

Semantics:

+ * Returns TRUE if the vertex or edge has the given label, FALSE if not, or NULL if either + * operand is NULL. + * + *

Example:

+ *
+ * MATCH (a) -[e]-> (b)
+ * WHERE IS_LABELED(a, 'person')
+ * RETURN a, e, b
+ * 
+ */ +@Description( + name = "is_labeled", + description = "ISO-GQL Labeled Predicate: Returns TRUE if the vertex or edge has the given " + + "label, FALSE if not, NULL if either operand is NULL. Follows ISO-GQL three-valued logic." +) +public class IsLabeled extends UDF { + + /** + * Evaluates the IS LABELED predicate. + * + * @param elementValue vertex or edge to check + * @param labelValue label name to test for + * @return Boolean: true if the element has the given label, false if not, null if either + * operand is null + */ + public Boolean eval(Object elementValue, Object labelValue) { + return LabeledPredicateFunctions.isLabeled(elementValue, labelValue); + } + + /** + * Type-specific overload for vertices. + */ + public Boolean eval(RowVertex vertex, String label) { + return LabeledPredicateFunctions.isLabeled(vertex, label); + } + + /** + * Type-specific overload for edges. + */ + public Boolean eval(RowEdge edge, String label) { + return LabeledPredicateFunctions.isLabeled(edge, label); + } +} diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotLabeled.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotLabeled.java new file mode 100644 index 000000000..3804cb874 --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotLabeled.java @@ -0,0 +1,82 @@ +/* + * 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.LabeledPredicateFunctions; +import org.apache.geaflow.dsl.common.function.UDF; + +/** + * UDF implementation for the ISO-GQL IS NOT LABELED predicate. + * + *

Implements ISO-GQL Section 19.9: <labeled predicate> + * + *

Syntax:

+ *
+ *   IS_NOT_LABELED(element, label)
+ * 
+ * + *

Semantics:

+ * Returns TRUE if the vertex or edge does NOT have the given label, FALSE if it does, or NULL + * if either operand is NULL. + * + *

Example:

+ *
+ * MATCH (a) -[e]-> (b)
+ * WHERE IS_NOT_LABELED(a, 'software')
+ * RETURN a, e, b
+ * 
+ */ +@Description( + name = "is_not_labeled", + description = "ISO-GQL Labeled Predicate: Returns TRUE if the vertex or edge does NOT have " + + "the given label, FALSE if it does, NULL if either operand is NULL. Follows ISO-GQL " + + "three-valued logic." +) +public class IsNotLabeled extends UDF { + + /** + * Evaluates the IS NOT LABELED predicate. + * + * @param elementValue vertex or edge to check + * @param labelValue label name to test for + * @return Boolean: true if the element does NOT have the given label, false if it does, null + * if either operand is null + */ + public Boolean eval(Object elementValue, Object labelValue) { + return LabeledPredicateFunctions.isNotLabeled(elementValue, labelValue); + } + + /** + * Type-specific overload for vertices. + */ + public Boolean eval(RowVertex vertex, String label) { + return LabeledPredicateFunctions.isNotLabeled(vertex, label); + } + + /** + * Type-specific overload for edges. + */ + public Boolean eval(RowEdge edge, String label) { + return LabeledPredicateFunctions.isNotLabeled(edge, label); + } +} diff --git a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/GQLLabeledPredicateTest.java b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/GQLLabeledPredicateTest.java new file mode 100644 index 000000000..4b33b6838 --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/GQLLabeledPredicateTest.java @@ -0,0 +1,35 @@ +/* + * 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 GQLLabeledPredicateTest { + + @Test + public void testLabeledPredicate_001() throws Exception { + QueryTester + .build() + .withGraphDefine("/query/modern_graph.sql") + .withQueryPath("/query/gql_labeled_predicate_001.sql") + .execute() + .checkSinkResult(); + } +} diff --git a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_labeled_predicate_001.txt b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_labeled_predicate_001.txt new file mode 100644 index 000000000..6abf2432d --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/gql_labeled_predicate_001.txt @@ -0,0 +1,6 @@ +1,2,true,false,true,false +1,3,true,true,true,true +1,4,true,false,true,false +4,3,true,true,true,true +4,5,true,true,true,true +6,3,true,true,true,true diff --git a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_labeled_predicate_001.sql b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_labeled_predicate_001.sql new file mode 100644 index 000000000..4cf48d68d --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/gql_labeled_predicate_001.sql @@ -0,0 +1,51 @@ +/* + * 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: IS_LABELED / IS_NOT_LABELED predicate on vertices and edges. +-- For every matched (a)-[e]->(b) triple, evaluate the labeled predicate against +-- the known labels of the modern graph (person / software / knows / created). + +CREATE TABLE tbl_result ( + a_id bigint, + b_id bigint, + a_is_person boolean, + b_is_software boolean, + a_is_not_software boolean, + e_is_created boolean +) WITH ( + type='file', + geaflow.dsl.file.path='${target}' +); + +USE GRAPH modern; + +INSERT INTO tbl_result +SELECT + a.id, + b.id, + IS_LABELED(a, 'person') as a_is_person, + IS_LABELED(b, 'software') as b_is_software, + IS_NOT_LABELED(a, 'software') as a_is_not_software, + IS_LABELED(e, 'created') as e_is_created +FROM ( + MATCH (a) -[e]-> (b) + RETURN a, e, b +) +ORDER BY a.id, b.id +; From 5c65fc850fe202a1cea139bfd4c80f1af812fca8 Mon Sep 17 00:00:00 2001 From: yqz <2678785492@qq.com> Date: Wed, 5 Aug 2026 23:22:25 +0800 Subject: [PATCH 2/2] [ISSUE-358] Address review: inline labeled predicate logic into UDFs The geaflow-dsl-common/function package is for general-purpose interfaces, so the labeled predicate logic should live in the UDFs rather than a separate helper class there. - Inline the label-matching and three-valued-null logic (plus a private getLabel helper) directly into IsLabeled; IsNotLabeled delegates to an IsLabeled instance and negates, keeping label extraction in one place. - Remove LabeledPredicateFunctions and its test from geaflow-dsl-common. - Move the unit test to geaflow-dsl-plan as LabeledPredicateTest (same udf/table/other package as the UDFs, matching PropertyExistsTest), testing through the UDF eval methods. Co-Authored-By: Claude Opus 4.8 --- .../function/LabeledPredicateFunctions.java | 83 ------------------- .../dsl/udf/table/other/IsLabeled.java | 36 ++++++-- .../dsl/udf/table/other/IsNotLabeled.java | 18 ++-- .../table/other/LabeledPredicateTest.java} | 32 ++++--- 4 files changed, 58 insertions(+), 111 deletions(-) delete mode 100644 geaflow/geaflow-dsl/geaflow-dsl-common/src/main/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctions.java rename geaflow/geaflow-dsl/{geaflow-dsl-common/src/test/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctionsTest.java => geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/udf/table/other/LabeledPredicateTest.java} (63%) diff --git a/geaflow/geaflow-dsl/geaflow-dsl-common/src/main/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctions.java b/geaflow/geaflow-dsl/geaflow-dsl-common/src/main/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctions.java deleted file mode 100644 index 7603c51b1..000000000 --- a/geaflow/geaflow-dsl/geaflow-dsl-common/src/main/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctions.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 org.apache.geaflow.dsl.common.data.RowEdge; -import org.apache.geaflow.dsl.common.data.RowVertex; - -/** - * Utility class providing static methods for the ISO-GQL labeled predicate. - * - *

Implements ISO-GQL Section 19.9: <labeled predicate>, which tests whether a - * graph element (vertex or edge) has a given label. - * - *

ISO-GQL General Rules (three-valued logic): - *

    - *
  • If the element or the label is null, the result is Unknown (null).
  • - *
  • If the element's label equals the given label, the result is True.
  • - *
  • Otherwise, the result is False.
  • - *
- */ -public class LabeledPredicateFunctions { - - /** - * Implements the IS LABELED predicate. - * - * @param elementValue the vertex or edge to check - * @param labelValue the label name to test for - * @return Boolean: true if the element has the given label, false if not, null if either - * operand is null - */ - public static Boolean isLabeled(Object elementValue, Object labelValue) { - // ISO-GQL Rule: If element or label is null, result is Unknown (null). - if (elementValue == null || labelValue == null) { - return null; - } - - String elementLabel = getLabel(elementValue); - return elementLabel != null && elementLabel.equals(labelValue.toString()); - } - - /** - * Implements the IS NOT LABELED predicate. - * - * @param elementValue the vertex or edge to check - * @param labelValue the label name to test for - * @return Boolean: true if the element does NOT have the given label, false if it does, null - * if either operand is null - */ - public static Boolean isNotLabeled(Object elementValue, Object labelValue) { - Boolean result = isLabeled(elementValue, labelValue); - // Three-valued logic: NOT Unknown = Unknown (null remains null). - return result == null ? null : !result; - } - - private static String getLabel(Object elementValue) { - if (elementValue instanceof RowVertex) { - return ((RowVertex) elementValue).getLabel(); - } - if (elementValue instanceof RowEdge) { - return ((RowEdge) elementValue).getLabel(); - } - throw new IllegalArgumentException( - "First operand of labeled predicate must be a vertex or an edge, got: " - + elementValue.getClass().getName()); - } -} diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsLabeled.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsLabeled.java index 7ba9bfaa6..6059285a3 100644 --- a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsLabeled.java +++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsLabeled.java @@ -22,22 +22,25 @@ 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.LabeledPredicateFunctions; import org.apache.geaflow.dsl.common.function.UDF; /** * UDF implementation for the ISO-GQL IS LABELED predicate. * - *

Implements ISO-GQL Section 19.9: <labeled predicate> + *

Implements ISO-GQL Section 19.9: <labeled predicate>, which tests whether a + * graph element (vertex or edge) has a given label. * *

Syntax:

*
  *   IS_LABELED(element, label)
  * 
* - *

Semantics:

- * Returns TRUE if the vertex or edge has the given label, FALSE if not, or NULL if either - * operand is NULL. + *

Semantics (ISO-GQL three-valued logic):

+ *
    + *
  • If the element or the label is null, the result is Unknown (null).
  • + *
  • If the element's label equals the given label, the result is True.
  • + *
  • Otherwise, the result is False.
  • + *
* *

Example:

*
@@ -62,20 +65,37 @@ public class IsLabeled extends UDF {
      *         operand is null
      */
     public Boolean eval(Object elementValue, Object labelValue) {
-        return LabeledPredicateFunctions.isLabeled(elementValue, labelValue);
+        // ISO-GQL Rule: If element or label is null, result is Unknown (null).
+        if (elementValue == null || labelValue == null) {
+            return null;
+        }
+        String elementLabel = getLabel(elementValue);
+        return elementLabel != null && elementLabel.equals(labelValue.toString());
     }
 
     /**
      * Type-specific overload for vertices.
      */
     public Boolean eval(RowVertex vertex, String label) {
-        return LabeledPredicateFunctions.isLabeled(vertex, label);
+        return eval((Object) vertex, label);
     }
 
     /**
      * Type-specific overload for edges.
      */
     public Boolean eval(RowEdge edge, String label) {
-        return LabeledPredicateFunctions.isLabeled(edge, label);
+        return eval((Object) edge, label);
+    }
+
+    private static String getLabel(Object elementValue) {
+        if (elementValue instanceof RowVertex) {
+            return ((RowVertex) elementValue).getLabel();
+        }
+        if (elementValue instanceof RowEdge) {
+            return ((RowEdge) elementValue).getLabel();
+        }
+        throw new IllegalArgumentException(
+            "First operand of labeled predicate must be a vertex or an edge, got: "
+                + elementValue.getClass().getName());
     }
 }
diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotLabeled.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotLabeled.java
index 3804cb874..a1aa592c8 100644
--- a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotLabeled.java
+++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/other/IsNotLabeled.java
@@ -22,22 +22,22 @@
 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.LabeledPredicateFunctions;
 import org.apache.geaflow.dsl.common.function.UDF;
 
 /**
  * UDF implementation for the ISO-GQL IS NOT LABELED predicate.
  *
- * 

Implements ISO-GQL Section 19.9: <labeled predicate> + *

Implements ISO-GQL Section 19.9: <labeled predicate>. This is the negation of + * {@link IsLabeled} and delegates to it so the label-extraction logic lives in one place. * *

Syntax:

*
  *   IS_NOT_LABELED(element, label)
  * 
* - *

Semantics:

+ *

Semantics (ISO-GQL three-valued logic):

* Returns TRUE if the vertex or edge does NOT have the given label, FALSE if it does, or NULL - * if either operand is NULL. + * if either operand is NULL (NOT Unknown = Unknown). * *

Example:

*
@@ -54,6 +54,8 @@
 )
 public class IsNotLabeled extends UDF {
 
+    private final IsLabeled isLabeled = new IsLabeled();
+
     /**
      * Evaluates the IS NOT LABELED predicate.
      *
@@ -63,20 +65,22 @@ public class IsNotLabeled extends UDF {
      *         if either operand is null
      */
     public Boolean eval(Object elementValue, Object labelValue) {
-        return LabeledPredicateFunctions.isNotLabeled(elementValue, labelValue);
+        Boolean result = isLabeled.eval(elementValue, labelValue);
+        // Three-valued logic: NOT Unknown = Unknown (null remains null).
+        return result == null ? null : !result;
     }
 
     /**
      * Type-specific overload for vertices.
      */
     public Boolean eval(RowVertex vertex, String label) {
-        return LabeledPredicateFunctions.isNotLabeled(vertex, label);
+        return eval((Object) vertex, label);
     }
 
     /**
      * Type-specific overload for edges.
      */
     public Boolean eval(RowEdge edge, String label) {
-        return LabeledPredicateFunctions.isNotLabeled(edge, label);
+        return eval((Object) edge, label);
     }
 }
diff --git a/geaflow/geaflow-dsl/geaflow-dsl-common/src/test/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctionsTest.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/udf/table/other/LabeledPredicateTest.java
similarity index 63%
rename from geaflow/geaflow-dsl/geaflow-dsl-common/src/test/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctionsTest.java
rename to geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/udf/table/other/LabeledPredicateTest.java
index a56d137dc..9bdf02bef 100644
--- a/geaflow/geaflow-dsl/geaflow-dsl-common/src/test/java/org/apache/geaflow/dsl/common/function/LabeledPredicateFunctionsTest.java
+++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/udf/table/other/LabeledPredicateTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.geaflow.dsl.common.function;
+package org.apache.geaflow.dsl.udf.table.other;
 
 import org.apache.geaflow.dsl.common.data.RowEdge;
 import org.apache.geaflow.dsl.common.data.RowVertex;
@@ -26,7 +26,13 @@
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
-public class LabeledPredicateFunctionsTest {
+/**
+ * Unit tests for the ISO-GQL labeled predicate UDFs {@link IsLabeled} / {@link IsNotLabeled}.
+ */
+public class LabeledPredicateTest {
+
+    private final IsLabeled isLabeled = new IsLabeled();
+    private final IsNotLabeled isNotLabeled = new IsNotLabeled();
 
     private RowVertex vertex(String label) {
         ObjectVertex vertex = new ObjectVertex(1L);
@@ -42,33 +48,33 @@ private RowEdge edge(String label) {
 
     @Test
     public void testIsLabeledOnVertex() {
-        Assert.assertTrue(LabeledPredicateFunctions.isLabeled(vertex("person"), "person"));
-        Assert.assertFalse(LabeledPredicateFunctions.isLabeled(vertex("person"), "software"));
+        Assert.assertTrue(isLabeled.eval(vertex("person"), "person"));
+        Assert.assertFalse(isLabeled.eval(vertex("person"), "software"));
     }
 
     @Test
     public void testIsLabeledOnEdge() {
-        Assert.assertTrue(LabeledPredicateFunctions.isLabeled(edge("knows"), "knows"));
-        Assert.assertFalse(LabeledPredicateFunctions.isLabeled(edge("knows"), "created"));
+        Assert.assertTrue(isLabeled.eval(edge("knows"), "knows"));
+        Assert.assertFalse(isLabeled.eval(edge("knows"), "created"));
     }
 
     @Test
     public void testIsNotLabeled() {
-        Assert.assertFalse(LabeledPredicateFunctions.isNotLabeled(vertex("person"), "person"));
-        Assert.assertTrue(LabeledPredicateFunctions.isNotLabeled(vertex("person"), "software"));
+        Assert.assertFalse(isNotLabeled.eval(vertex("person"), "person"));
+        Assert.assertTrue(isNotLabeled.eval(vertex("person"), "software"));
     }
 
     @Test
     public void testThreeValuedLogicWithNullOperand() {
         // Null element or null label yields Unknown (null) for both predicates.
-        Assert.assertNull(LabeledPredicateFunctions.isLabeled(null, "person"));
-        Assert.assertNull(LabeledPredicateFunctions.isLabeled(vertex("person"), null));
-        Assert.assertNull(LabeledPredicateFunctions.isNotLabeled(null, "person"));
-        Assert.assertNull(LabeledPredicateFunctions.isNotLabeled(vertex("person"), null));
+        Assert.assertNull(isLabeled.eval((Object) null, "person"));
+        Assert.assertNull(isLabeled.eval(vertex("person"), null));
+        Assert.assertNull(isNotLabeled.eval((Object) null, "person"));
+        Assert.assertNull(isNotLabeled.eval(vertex("person"), null));
     }
 
     @Test(expectedExceptions = IllegalArgumentException.class)
     public void testNonGraphElementIsRejected() {
-        LabeledPredicateFunctions.isLabeled("not a graph element", "person");
+        isLabeled.eval("not a graph element", "person");
     }
 }