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 +;