GumpacG commented on code in PR #3433:
URL: https://github.com/apache/tinkerpop/pull/3433#discussion_r3344286458
##########
gremlin-python/src/main/python/gremlin_python/structure/graph.py:
##########
@@ -141,3 +141,114 @@ def __getitem__(self, key):
def __len__(self):
return len(self.objects)
+
+
+class ProviderDefinedType(object):
+ def __init__(self, name, properties):
+ if not name:
+ raise ValueError("name cannot be null or empty")
+ self._name = name
+ self._properties = dict(properties) if properties else {}
Review Comment:
Should we check that keys are Strings here? Other GLVs enforce String only
keys.
##########
gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java:
##########
@@ -185,21 +157,6 @@ public Builder withFallbackResolver(final
Function<Class<?>, TypeSerializer<?>>
return this;
}
- /**
- * Add {@link CustomTypeSerializer} by way of an {@link IoRegistry}.
The registry entries should be bound to
- * {@link GraphBinaryIo}.
- */
- public Builder addRegistry(final IoRegistry registry) {
- if (null == registry) throw new IllegalArgumentException("The
registry cannot be null");
-
- final List<Pair<Class, CustomTypeSerializer>> classSerializers =
registry.find(GraphBinaryIo.class, CustomTypeSerializer.class);
Review Comment:
Can you also remove GraphBinaryIo.java since all references to it are
removed now?
##########
docs/src/dev/provider/index.asciidoc:
##########
@@ -1334,6 +1334,213 @@ can be used as a reference on how these files can be
used and its
link:https://github.com/apache/tinkerpop/blob/x.y.z/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/Model.java[model]
shows the Java representation of those files.
+[[provider-defined-types]]
+=== Provider Defined Types (PDT)
+
+Provider Defined Types allow graph providers to expose custom types that
drivers can serialize and deserialize without
+manual configuration on the client side. A provider annotates a class (or
registers an adapter for a class it doesn't
+own), and the type flows through the wire protocol automatically. Clients
receive PDT values as structured objects they
+can use directly or hydrate into language-native types.
+
+==== Basic Usage
+
+Annotate a class with `@ProviderDefined` from the
`org.apache.tinkerpop.gremlin.structure.io.pdt` package:
+
+[source,java]
+----
+import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefined;
+
+@ProviderDefined(name = "mygraph:Point")
+public class Point {
+ public double x;
+ public double y;
+
+ public Point(double x, double y) {
+ this.x = x;
+ this.y = y;
+ }
+}
+----
+
+The `name` attribute is a unique identifier for the type. It is strongly
recommended to namespace type names using
+your graph's identifier as a prefix (e.g. `"mygraph:Point"`). This avoids
collisions when clients interact with
+multiple providers and makes the origin of a type immediately clear. By
default, all fields are included. Use
+`includedFields` or `excludedFields` to control which fields are serialized:
+
+[source,java]
+----
+@ProviderDefined(name = "mygraph:Point", includedFields = {"x", "y"})
+public class Point { ... }
+
+// or exclude specific fields
+@ProviderDefined(name = "mygraph:Person", excludedFields = {"internalId"})
+public class Person { ... }
+----
+
+NOTE: For annotation-based round-trip hydration (see <<round-trip-support>>),
an annotated class must expose a no-arg
+constructor and the mapped fields must be directly settable (e.g. public
fields). Classes that cannot meet these
+requirements — for example those with immutable `final` fields or no default
constructor — should instead use a
+`ProviderDefinedTypeAdapter` (see <<adapter-for-types-you-don-t-own>>), which
gives full control over construction.
+
+==== Nested Types
+
+PDT supports nested custom types. Each nested type must also be annotated:
+
+[source,java]
+----
+@ProviderDefined(name = "mygraph:Address")
+public class Address {
+ public String street;
+ public String city;
+}
+
+@ProviderDefined(name = "mygraph:Person")
+public class Person {
+ public String name;
+ public Address address;
+}
+----
+
+When serialized, the `address` field is itself encoded as a PDT value.
+
+[[adapter-for-types-you-don-t-own]]
+==== Adapter for Types You Don't Own
+
+For classes you cannot annotate (e.g. `java.awt.Color`), implement
`ProviderDefinedTypeAdapter<T>`:
+
+[source,java]
+----
+import
org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter;
+
+public class ColorAdapter implements
ProviderDefinedTypeAdapter<java.awt.Color> {
+
+ @Override
+ public String typeName() { return "mygraph:Color"; }
+
+ @Override
+ public Class<java.awt.Color> targetClass() { return java.awt.Color.class; }
+
+ @Override
+ public Map<String, Object> toProperties(java.awt.Color color) {
+ return Map.of("r", color.getRed(), "g", color.getGreen(),
+ "b", color.getBlue(), "a", color.getAlpha());
+ }
+
+ @Override
+ public java.awt.Color fromProperties(Map<String, Object> fields) {
+ return new java.awt.Color((int) fields.get("r"), (int) fields.get("g"),
+ (int) fields.get("b"), (int)
fields.get("a"));
+ }
+}
+----
+
+[[round-trip-support]]
+==== Round-Trip Support (Dehydration and Hydration)
+
+There is an important distinction between *dehydration* (serializing a type
for sending) and *hydration* (deserializing
+a received PDT back into a language-native type).
+
+*Dehydration* is handled automatically for `@ProviderDefined`-annotated
classes and adapter-registered types. When a
+user passes an annotated object into a Gremlin traversal or script, TinkerPop
converts it to a PDT on the wire
+without any extra configuration.
+
+*Hydration* — reconstructing an incoming PDT back into the original typed
object — requires the driver to know which
+class corresponds to a given PDT name. Without this mapping, the driver will
return a generic `ProviderDefinedType`
+object. To enable automatic round-trip hydration, providers must expose a
pre-configured `ProviderDefinedTypeRegistry`
+to users. How that registry is populated differs by language:
+
+===== Java
+
+Register annotated classes explicitly with the registry.
`register(Class<?>...)` inspects the `@ProviderDefined`
+annotation to derive the type name and field mapping automatically:
+
+[source,java]
+----
+ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.build();
+registry.register(Point.class, Address.class, Person.class);
+----
+
+Adapter types (for classes you don't own) are discovered automatically via
`ServiceLoader` when using
+`ProviderDefinedTypeRegistry.build()`. Register them by adding a file at:
+
+----
+META-INF/services/org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter
+----
+
+with the fully qualified class name of each adapter:
+
+----
+com.example.graph.ColorAdapter
+----
+
+===== Python
+
+Hydration is fully automatic for `@provider_defined`-decorated classes. The
decorator registers the class at
+definition time (import time), so any annotated type round-trips without any
additional setup.
+
+===== .NET
+
+`[ProviderDefined]`-annotated types are discovered automatically. Calling
`ProviderDefinedTypeRegistry.Build()`
+scans all loaded assemblies for `[ProviderDefined]`-annotated types and
registers them for hydration. No extra
+configuration is needed — providers simply annotate their types and users call
`Build()` to create the registry.
+
+===== JavaScript
+
+Register hydration adapters explicitly on a `ProviderDefinedTypeRegistry`
instance, then pass it to the connection:
+
+[source,javascript]
+----
+const registry = new ProviderDefinedTypeRegistry();
+registry.register('mygraph:Point', {
+ serialize: (obj) => ({ x: obj.x, y: obj.y }),
+ deserialize: (props) => new Point(props.x, props.y)
+}, Point);
+----
+
+===== Go
+
+Register types on a `PDTRegistry` instance. Go supports either
reflection-based registration (using `pdt` struct
+tags) or explicit function registration:
+
+[source,go]
+----
+registry := NewPDTRegistry()
+registry.RegisterType("mygraph:Point", reflect.TypeOf(Point{}))
+----
+
+===== Provider Factory Pattern
+
+Regardless of language, the recommended pattern is for providers to expose a
factory method that returns a
+pre-configured `ProviderDefinedTypeRegistry`. This shields end users from
needing to know which types exist or how
+the registry is populated:
+
+[source,java]
+----
+// In the provider's client library
+public class MyGraphTypeRegistry {
+ public static ProviderDefinedTypeRegistry build() {
+ ProviderDefinedTypeRegistry registry =
ProviderDefinedTypeRegistry.build(); // discovers ServiceLoader adapters
+ registry.register(Point.class, Address.class, Person.class);
// registers annotated types
+ return registry;
+ }
+}
+----
+
+End users configure their connection in one line:
+
+[source,java]
+----
+DriverRemoteConnection conn = DriverRemoteConnection.using(cluster);
+conn.setPdtRegistry(MyGraphTypeRegistry.build());
+GraphTraversalSource g = traversal().with(conn);
+----
+
+With this in place, `Point` objects round-trip transparently in both
directions — the annotation handles outbound
+serialization and the registry handles inbound reconstruction.
+
+For driver users consuming PDTs, see the <<gremlin-variants,Gremlin Variants>>
reference documentation for
+each language driver.
+
Review Comment:
Can you also add contraints and error behaviour section of some sort? I
think something for property map keys must be `String` and the type `name` must
be non-empty.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]