Skip to content

Commit e515cc5

Browse files
authored
Added P.typeOf() predicate, which recognized a new set of GType enum token, String as registered in the GlobalTypeCache, as well as Class in Java/Groovy usage. (#3211)
https://issues.apache.org/jira/browse/TINKERPOP-2234
1 parent 65a438a commit e515cc5

File tree

100 files changed

+4350
-816
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+4350
-816
lines changed

CHANGELOG.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ This release also includes changes from <<release-3-7-XXX, 3.7.XXX>>.
9191
* Deprecated `ProcessLimitedStandardSuite` and `ProcessLimitedComputerSuite` in favor of `ProcessEmbeddedStandardSuite` and `ProcessEmbeddedComputerSuite` respectively.
9292
* Deprecated `ProcessStandardSuite` and the `ProcessComputerSuite` in favor of Gherkin testing and the `ProcessEmbeddedStandardSuite` and `ProcessEmbeddedComputerSuite` for testing JVM-specific Gremlin behaviors.
9393
* Removed lambda oriented Gremlin testing from Gherkin test suite.
94+
* Implemented `P.typeOf()` predicate.
95+
* Added `GType` enum to denote types.
9496
* Removed `P.getOriginalValue()` in favor of `P.getValue()`.
9597
* Simplified comparability semantics from ternary boolean logic to binary logic.
9698
* Introduced `NotP` class for negation of `P`.

docs/src/dev/provider/gremlin-semantics.asciidoc

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,35 @@ for providers to decide how to handle this in their implementation. The referenc
337337
equivalent keys to appear in a map (e.g. `1` and `1.0` can both be keys in the same map), but when comparing maps we
338338
treat pairwise entries with semantically equivalent keys as the same.
339339
340+
==== Comparability of Types
341+
342+
The `P.typeOf()` predicate enables type-based filtering in TinkerPop using inheritance-aware comparison, where a value
343+
matches if it is the specified type or any of its subtypes.
344+
345+
===== GType Enums
346+
347+
Based on the support type space, we have defined a `GType` enum, which contains a set of enumerations that is used for
348+
type casting (`asNumber()`) and comparison (`P.typeOf()`) operations.
349+
350+
* **Numeric types**: `INT`, `LONG`, `DOUBLE`, `FLOAT`, `BYTE`, `SHORT`, `BIGDECIMAL`, `BIGINT`
351+
* **General types**: `STRING`, `BOOLEAN`, `CHAR`, `UUID`, `BINARY`
352+
* **Collection types**: `LIST`, `SET`, `MAP`
353+
* **Graph types**: `VERTEX`, `EDGE`, `PROPERTY`, `VPROPERTY`, `PATH`, `TREE`, `GRAPH`
354+
* **Temporal types**: `DATETIME`, `DURATION`
355+
* **Special types**: `NULL`, `NUMBER` (supertype for all numeric types)
356+
357+
===== GlobalTypeCache
358+
359+
Providers can register custom types outside the TinkerPop type space using the `GlobalTypeCache`, making them available
360+
for `P.typeOf(String)` filtering. Unregistered string inputs will throw an `IllegalArgumentException`.
361+
362+
The `registerDataType()` method registers the class's simple name by default, or accepts a custom string name. Providers
363+
should use PascalCase following Java conventions and prefix type names to avoid conflicts
364+
(e.g., `ProviderPrefix:SimpleTypeName`).
365+
366+
Do note that the type cache only enables type recognition for filtering. Custom types still require separate
367+
serialization support - clients without custom serializers cannot deserialize these types.
368+
340369
[[gremlin-semantics-orderability]]
341370
=== Orderability
342371
@@ -776,7 +805,7 @@ link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asDate-step[reference]
776805
777806
*Description:* converts the incoming traverser to the nearest parsable type if no argument is provided, or to the desired numerical type, based on the number token (`N`) provided.
778807
779-
*Syntax:* `asNumber()` | `asNumber(N numberToken)`
808+
*Syntax:* `asNumber()` | `asNumber(GType typeToken)`
780809
781810
[width="100%",options="header"]
782811
|=========================================================
@@ -786,7 +815,7 @@ link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asDate-step[reference]
786815
787816
*Arguments:*
788817
789-
* `numberToken` - The enum `N` to denote the desired type to parse/cast to.
818+
* `typeToken` - The enum `GType` to denote the desired type to parse/cast to.
790819
791820
If no type token is provided, the incoming number remains unchanged.
792821
@@ -795,6 +824,7 @@ If no type token is provided, the incoming number remains unchanged.
795824
* If any overflow occurs during narrowing of types, then an `ArithmeticException` will be thrown.
796825
* If the incoming string cannot be parsed into a valid number format, then a `NumberFormatException` will be thrown.
797826
* If the incoming traverser is a non-String/Number (including `null`) value then an `IllegalArgumentException` will be thrown.
827+
* If the supplied type token is not a number type, then an `IllegalArgumentException` will be thrown.
798828
799829
See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStep.java[source],
800830
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asNumber-step[reference]

docs/src/reference/gremlin-variants.asciidoc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,6 +1369,42 @@ java -cp target/run-examples-shaded.jar examples.BasicGremlin
13691369
java -cp target/run-examples-shaded.jar examples.ModernTraversals
13701370
----
13711371
1372+
[[gremlin-java-differences]]
1373+
=== Differences
1374+
1375+
Gremlin-Java provides additional syntactic sugar that leverages Java's type system for the `P.typeOf()` predicate,
1376+
which accepts Java `Class` objects directly, providing a more natural way to perform type checking:
1377+
1378+
[gremlin-groovy,modern]
1379+
----
1380+
// Java-specific syntax using Class objects
1381+
g.V().values("age").is(P.typeOf(Integer.class))
1382+
g.V().values("name").is(P.typeOf(String.class))
1383+
1384+
// Further simplification with Groovy sugar syntax
1385+
g.E().has("weight", P.typeOf(Double))
1386+
----
1387+
1388+
This is equivalent to using `GType` enums. Other Gremlin language variants must use the canonical `GType` enum approach:
1389+
1390+
[gremlin-groovy,modern]
1391+
----
1392+
// Canonical syntax available in all languages
1393+
g.V().values("age").is(P.typeOf(GType.INT))
1394+
g.V().values("name").is(P.typeOf(GType.STRING))
1395+
----
1396+
1397+
Any valid Java class accepted in the Console and with embedded Java is also accepted by `P.typeOf()`, as they are not
1398+
restricted by the grammar or serialization.
1399+
[gremlin-groovy,modern]
1400+
----
1401+
// Using java.awt.Color for example
1402+
gremlin> g.inject(java.awt.Color.red)
1403+
==>java.awt.Color[r=255,g=0,b=0]
1404+
gremlin> g.inject(java.awt.Color.red, "hi", 123).is(P.typeOf(java.awt.Color))
1405+
==>java.awt.Color[r=255,g=0,b=0]
1406+
----
1407+
13721408
[[gremlin-javascript]]
13731409
== Gremlin-JavaScript
13741410

docs/src/reference/the-traversal.asciidoc

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
854854
=== AsNumber Step
855855
856856
The `asNumber()`-step (*map*) converts the incoming traverser to the nearest parsable type if no argument is provided,
857-
or to the desired numerical type, based on the number token (`N`) provided.
857+
or to the desired numerical type, based on the type token (`GType`) provided. If a type token entered isn't a numerical type, an `IllegalArgumentException` will be thrown.
858858
859859
Numerical input will pass through unless a type is specified by the number token. `ArithmeticException` will be thrown
860860
for any overflow during narrowing of types.
@@ -868,17 +868,13 @@ All other input types will result in `IllegalArgumentException`.
868868
[gremlin-groovy,modern]
869869
----
870870
g.inject(1234).asNumber() <1>
871-
g.inject(1.76).asNumber() <2>
872-
g.inject(1.76).asNumber(N.int_) <3>
873-
g.inject("1b").asNumber() <4>
874-
g.inject(33550336).asNumber(N.byte_) <5>
871+
g.inject(1.76d).asNumber() <2>
872+
g.inject(1.76d).asNumber(GType.INT) <3>
875873
----
876874
877875
<1> An int will be passed through.
878876
<2> A double will be passed through.
879877
<3> A double is converted into an int.
880-
<4> String containing any character other than numerical ones will result in `NumberFormatException`.
881-
<5> Narrowing of int to byte that overflows will throw `ArithmeticException`.
882878
883879
[NOTE, caption=Java]
884880
====
@@ -5477,12 +5473,8 @@ a comparatively slow script compilation, which makes parameterization essential
54775473
[[a-note-on-predicates]]
54785474
== A Note on Predicates
54795475
5480-
A `P` is a predicate of the form `Function<Object,Boolean>`. That is, given some object, return true or false. As of
5481-
the release of TinkerPop 3.4.0, Gremlin also supports simple text predicates, which only work on `String` values. The `TextP`
5482-
text predicates extend the `P` predicates, but are specialized in that they are of the form `Function<String,Boolean>`.
5483-
The provided predicates are outlined in the table below and are used in various steps such as <<has-step,`has()`>>-step,
5484-
<<where-step,`where()`>>-step, <<is-step,`is()`>>-step, etc. Two new additional `TextP` predicate members were added in the
5485-
TinkerPop 3.6.0 release that allow working with regular expressions. These are `TextP.regex` and `TextP.notRegex`
5476+
A `P` is a predicate of the form `Function<Object,Boolean>`. That is, given some object, return true or false. Gremlin
5477+
supports text predicates (`TextP`), which are specialized predicates that only work on String values and are of the form `Function<String,Boolean>`. Additionally, type predicate (`P.typeOf`) supports filtering traversers based on their runtime types. The provided predicates are outlined in the table below and are used in various steps such as <<has-step,`has()`>>-step, <<where-step,`where()`>>-step, <<is-step,`is()`>>-step, etc.
54865478
54875479
[width="100%",cols="3,15",options="header"]
54885480
|=========================================================
@@ -5498,6 +5490,8 @@ TinkerPop 3.6.0 release that allow working with regular expressions. These are `
54985490
| `P.between(number,number)` | Is the incoming number greater than or equal to the first provided number and less than the second?
54995491
| `P.within(objects...)` | Is the incoming object in the array of provided objects?
55005492
| `P.without(objects...)` | Is the incoming object not in the array of the provided objects?
5493+
| `P.typeOf(GType)` | Is the incoming object of the type indicated by the provided `GType` token?
5494+
| `P.typeOf(string)` | Is the incoming object of the type indicated by the provided `String`?
55015495
| `TextP.startingWith(string)` | Does the incoming `String` start with the provided `String`?
55025496
| `TextP.endingWith(string)` | Does the incoming `String` end with the provided `String`?
55035497
| `TextP.containing(string)` | Does the incoming `String` contain the provided `String`?
@@ -5555,6 +5549,69 @@ g.V().as('a').both().both().as('b').count()
55555549
g.V().as('a').both().both().as('b').where('a',neq('b')).count()
55565550
----
55575551
5552+
[[a-note-on-types]]
5553+
== A Note on Types
5554+
5555+
Gremlin steps typically operate over a handful of types that are mostly standard across graph systems. There are the
5556+
common numeric types like `Integer`, `Long`, `Double`, general types like `String`, and `Boolean`, container types like
5557+
`List`, `Set`, and `Map`, and structural types particular to graphs such as `Vertex`, `Edge`, and `Property`. During
5558+
traversal execution, it's common to encounter mixed data types, especially when extracting values from multiple
5559+
properties or when working with heterogeneous data that may have been stored inconsistently over time.
5560+
5561+
Gremlin identifies these types in the `GType` enumeration, offering a clear presentation of the standard data types one
5562+
might typically encounter with Gremlin. This enumeration is an important part of the Gremlin language in that it acts
5563+
as the argument to the `typeOf()` predicate used for filtering values based on their runtime data type.
5564+
5565+
[[gtype-enum]]
5566+
=== GType Enums
5567+
5568+
`GType` consists of the following enumerations:
5569+
5570+
* **Numeric types**: `INT`, `LONG`, `DOUBLE`, `FLOAT`, `BYTE`, `SHORT`, `BIGDECIMAL`, `BIGINT`
5571+
* **General types**: `STRING`, `BOOLEAN`, `CHAR`, `UUID`, `BINARY`
5572+
* **Collection types**: `LIST`, `SET`, `MAP`
5573+
* **Graph types**: `VERTEX`, `EDGE`, `PROPERTY`, `VPROPERTY`, `PATH`, `TREE`, `GRAPH`
5574+
* **Temporal types**: `DATETIME`, `DURATION`
5575+
* **Special types**: `NULL`, `NUMBER` (supertype for all numeric types)
5576+
5577+
As mentioned, the `typeOf()` predicate becomes particularly useful when dealing with mixed data scenarios. For example,
5578+
you would like to only return the integer values of a set of properties for further processing:
5579+
5580+
[gremlin-groovy,modern]
5581+
----
5582+
g.V().values('age','name').is(P.typeOf(GType.INT)).asNumber(GType.SHORT)
5583+
----
5584+
5585+
The `NUMBER` type allows for broader type-based filtering without needing to specify each individual numeric type:
5586+
5587+
[gremlin-groovy,modern]
5588+
----
5589+
g.union(V(), E()).values().is(P.typeOf(GType.NUMBER))
5590+
----
5591+
5592+
Type filtering is also valuable when working with traversals that return mixed graph elements. For example, when a
5593+
traversal might return both vertices and edges, you can add filter or condition based on the elements of interest:
5594+
5595+
[gremlin-groovy,modern]
5596+
----
5597+
g.V().outE().inV().path().unfold().is(P.typeOf(GType.EDGE))
5598+
g.V().outE().inV().path().unfold().choose(typeOf(VERTEX), values('name'), values('weight'))
5599+
----
5600+
5601+
[[global-type-cache]]
5602+
=== GlobalTypeCache
5603+
5604+
The `GlobalTypeCache` stores custom types registered by database providers as string-to-class mappings. These registered
5605+
type names can then be used with `P.typeOf()` for type filtering in the traversal. Consult your provider's documentation
5606+
for the correct type names when using provider-specific types.
5607+
5608+
By default, `GType` enumerations are registered using their simple class names and can be used as shown below.
5609+
5610+
[gremlin-groovy,modern]
5611+
----
5612+
gremlin> g.V().values('age','name').is(P.typeOf('Integer'))
5613+
----
5614+
55585615
[[a-note-on-maps]]
55595616
== A Note on Maps
55605617

0 commit comments

Comments
 (0)