This is an automated email from the ASF dual-hosted git repository. xiazcy pushed a commit to branch type-predicate-poc in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit f9f18479fe9830b2ea0d86cd5c11eb4d22f4137d Author: xiazcy <[email protected]> AuthorDate: Fri Jun 6 12:25:51 2025 -0700 asbool() & type predicate proposal drafts --- .../src/dev/future/proposal-asbool-step-7.asciidoc | 75 +++++++++++++++ .../dev/future/proposal-type-predicate-8.asciidoc | 105 +++++++++++++++++++++ .../gremlin/process/traversal/Compare.java | 40 ++++++++ .../tinkerpop/gremlin/process/traversal/P.java | 21 +++++ .../gremlin/util/GremlinValueComparator.java | 11 +++ .../tinkerpop/gremlin/process/traversal/PTest.java | 3 + 6 files changed, 255 insertions(+) diff --git a/docs/src/dev/future/proposal-asbool-step-7.asciidoc b/docs/src/dev/future/proposal-asbool-step-7.asciidoc new file mode 100644 index 0000000000..901fddd141 --- /dev/null +++ b/docs/src/dev/future/proposal-asbool-step-7.asciidoc @@ -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. +//// + +image::apache-tinkerpop-logo.png[width=500,link="https://tinkerpop.apache.org"] + +*x.y.z - Proposal 7* + +== asBool() Step + +=== Motivation + +Given the additions of the `asString()` and `asDate()` steps in the 3.7 line, this proposal seeks to bridge another gap in language functionality, which is boolean parsing. + +=== Definition + +The `asBool()` step will convert the incoming traverser into a Boolean value. + +Note that `null` value will be considered `false`. + +The incoming traverser can be of type: + +*Number* - All non-zero values are considered `true`, zero, `NaN` and `null` values are considered `false`, for example: +[cols=",",options="header",] +|=== +|Numerical Value |Boolean Value +|1 |true +|0 |false +|3.14 |true +|-1 |true +|-0.0 |false +|0.0 |false +|NaN |false +|null| false +|=== + +*String* - Strings will not be convertable into bool, except boolean strings: +[cols=",",options="header",] +|=== +|Sting Value |Boolean Value +|"true" |true +|"false" |false +|"True" |true +|"False" |false +|"TRUE" |true +|"FALSE" |false +|"trUE" |true +|"faLSe" |false +|"null"|Parsing Exception +|"1" |Parsing Exception +|"hello" |Parsing Exception +|=== + +*All other types* - Throws parsing exception. + +=== Alternative semantics + +Depending on how we want `asBool()` to be used, a potential alternative is to make `asBool()` an eval function and follow the `Groovy Truth` definitions. + +* For strings, all non-empty strings are considered `true`, and empty strings are considered `false`. Note in this case, `"false"` will be considered `true`, which could be confusing. + +* For collection, all non-empty collections are considered `true`, and empty collections are considered `false`. \ No newline at end of file diff --git a/docs/src/dev/future/proposal-type-predicate-8.asciidoc b/docs/src/dev/future/proposal-type-predicate-8.asciidoc new file mode 100644 index 0000000000..b9eb0771f3 --- /dev/null +++ b/docs/src/dev/future/proposal-type-predicate-8.asciidoc @@ -0,0 +1,105 @@ +//// +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. +//// + +image::apache-tinkerpop-logo.png[width=500,link="https://tinkerpop.apache.org"] + +*x.y.z - Proposal 7* + +== Type Predicate + +=== Motivation + +Following up on link:https://issues.apache.org/jira/browse/TINKERPOP-2234[TINKERPOP-2234], filtering of traversers based on types would be a very convenient addition to data manipulation in Gremlin. For example, when we +perform union of traversals and only want vertices or edges, currently there is no simple way to assert the type and pass it through. + +=== Definitions + +For defining what types to assert, we'll be using a set of tokens. A proposed set of type tokens are listed below based on GraphBinary. + +* The `token` could be a defined set of `Type` enums, e.g. `Type.String`, `Type.Number`, `Type.Date`, `Type.Vertex`, `Type.Edge`, `Type.Property`. +** Note that the `GremlinValueComparator` class contains a set of `Type` enums, which we could extract and promote to use. +* In addition, could also recognize the `Number` type tokens used by the `asNumber()` step, i.e. `N.Int`, `N.Double`. + +The range of tokens are: +[cols=",",options="header",] +|=== +|Token |Gremlin Type Reference +|N.Int |GraphBinary 4.0, GType +|N.Long |GraphBinary 4.0, GType +|N.Double |GraphBinary 4.0, GType +|N.Float |GraphBinary 4.0, GType +|N.BigDecimal |GraphBinary 4.0, GType +|N.BigInteger |GraphBinary 4.0, GType +|N.Byte |GraphBinary 4.0, GType +|N.Short |GraphBinary 4.0, GType +|Type.Number | Token for Java Number type +|Type.String |GraphBinary 4.0, GType +|Type.DateTime |GraphBinary 4.0, GType +|Type.List |GraphBinary 4.0, GType +|Type.Set |GraphBinary 4.0, GType +|Type.Map |GraphBinary 4.0, GType +|Type.UUID |GraphBinary, 4.0 GType +|Type.Edge |GraphBinary 4.0 +|Type.Path |GraphBinary 4.0 +|Type.Property |GraphBinary 4.0 +|Type.Graph |GraphBinary 4.0 +|Type.Vertex |GraphBinary 4.0, GType +|Type.VP |GraphBinary 4.0(VertexProperty) +|Type.Direction |GraphBinary 4.0 +|Type.T |GraphBinary 4.0 +|Type.Binary |GraphBinary 4.0 +|Type.Boolean |GraphBinary 4.0 +|Type.Tree |GraphBinary 4.0 +|Type.Char |GraphBinary 4.0 +|Type.Duration |GraphBinary 4.0 +|Type.Marker |GraphBinary 4.0 +|Type.PDT(type)* |GraphBinary 4.0(CompositePDT & PrimitivePDT) +|Type.Null |GraphBinary 4.0(Unspecified Null Object), GValue(UNKNOWN) +|=== + +*Providers are to register their custom types which can be string arguments to the token, for the Grammar to recognize. + +==== Addition of `P.of(token)` + +Given `P` is widely used by filter steps and has existing evaluation structure, we would also introduce a new predicate condition, which should be quite straight forward. + +* `P.of(token)` +* `P.nof(token)` - negation "not of type" + +A special convenience for embedded use is to take Java class as overload for syntactic sugar. + +* `P.of(class)` +* `P.nof(class)` - negation "not of type" + +[source] +---- +// embedded - recognizes Java classes +gremlin> g.V().is(P.of(Vertex)).fold() +==> [v[1],v[2],[v3],[v4],[v5],[v6]] +gremlin> g.inject(1.0,2,3,'hello',false).is(P.of(Number)).fold() +==> [1.0,2,3] + +// remote - uses the token +gremlin> g.V().is(P.of(Type.Vertex)).fold() +==> [v[1],v[2],[v3],[v4],[v5],[v6]] +gremlin> g.inject(1.0,2,3,'hello',false).is(P.of(Type.number)).fold() +==> [1.0,2,3] + +gremlin> g.inject(1.0,2,3,'hello',false).is(P.nof(Type.number)).fold() +==> ['hello',false] +---- + diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java index 09387614f8..1f1be62777 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java @@ -51,6 +51,26 @@ public enum Compare implements PBiPredicate<Object, Object> { } }, + /** + * Evaluates if the first object is equal to the second per Gremlin Comparison semantics. + * + * @since 3.8.0 + */ + of { + @Override + public boolean test(final Object first, final Object second) { + return GremlinValueComparator.COMPARABILITY.of(first, second); + } + + /** + * The negative of {@code eq} is {@link #neq}. + */ + @Override + public Compare negate() { + return nof; + } + }, + /** * Evaluates if the first object is not equal to the second per Gremlin Comparison semantics. * @@ -71,6 +91,26 @@ public enum Compare implements PBiPredicate<Object, Object> { } }, + /** + * Evaluates if the first object is not equal to the second per Gremlin Comparison semantics. + * + * @since 3.8.0 + */ + nof { + @Override + public boolean test(final Object first, final Object second) { + return !of.test(first, second); + } + + /** + * The negative of {@code neq} is {@link #eq} + */ + @Override + public Compare negate() { + return of; + } + }, + /** * Evaluates if the first object is greater than the second per Gremlin Comparison semantics. * diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java index 917a13891d..edd18f5265 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java @@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.process.traversal; import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; import org.apache.tinkerpop.gremlin.process.traversal.util.OrP; +import org.apache.tinkerpop.gremlin.util.GremlinValueComparator; import java.io.Serializable; import java.util.Arrays; @@ -145,6 +146,26 @@ public class P<V> implements Predicate<V>, Serializable, Cloneable { return new P(Compare.neq, value); } + /** + * Determines if values are of a type. + * + * @since 3.8.0-incubating + */ + // restricting parameter type to Class for PoC + public static <V> P<V> of(final Class<?> value) { + return new P(Compare.of, value); + } + + /** + * Determines if values are not of a type. + * + * @since 3.8.0-incubating + */ + // restricting parameter type to Class for PoC + public static <V> P<V> nof(final Class<?> value) { + return new P(Compare.nof, value); + } + /** * Determines if a value is less than another. * diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java index f99c191b30..5593a11401 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java @@ -80,6 +80,10 @@ public abstract class GremlinValueComparator implements Comparator<Object> { public boolean equals(final Object f, final Object s) { return compare(f, s) == 0; } + + public boolean of(final Object f, final Object s) { + return ((Class<?>)s).isAssignableFrom(f.getClass()); + } }; /** @@ -160,6 +164,11 @@ public abstract class GremlinValueComparator implements Comparator<Object> { } } + @Override + public boolean of(final Object f, final Object s) { + return ((Class<?>)s).isAssignableFrom(f.getClass()); + } + private boolean containersOfDifferentSize(final Object f, final Object s) { if (f instanceof Collection && s instanceof Collection) if (((Collection) f).size() != (((Collection) s).size())) @@ -377,4 +386,6 @@ public abstract class GremlinValueComparator implements Comparator<Object> { */ public abstract boolean equals(final Object f, final Object s); + public abstract boolean of(final Object f, final Object s); + } diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java index 15855e0108..c6689f76d2 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java @@ -21,6 +21,7 @@ package org.apache.tinkerpop.gremlin.process.traversal; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; import org.apache.tinkerpop.gremlin.process.traversal.util.OrP; +import org.apache.tinkerpop.gremlin.util.GremlinValueComparator; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -55,6 +56,8 @@ public class PTest { @Parameterized.Parameters(name = "{0}.test({1}) = {2}") public static Iterable<Object[]> data() { return new ArrayList<>(Arrays.asList(new Object[][]{ + {P.of(Number.class), 1, true}, + {P.eq(0), 0, true}, {P.eq(0), -0, true}, {P.eq(0), +0, true},
