Implement PrimitivePDT#3485
Open
Cole-Greer wants to merge 17 commits into
Open
Conversation
Fixes the CompositePDT (0xF0) response-path gap where a provider type registered via a ProviderDefinedTypeAdapter but not annotated with @ProviderDefined could not be serialized when returned to the client. GraphBinaryWriter now accepts an optional ProviderDefinedTypeRegistry (parallel to GraphBinaryReader). When a class has no direct serializer and is not @ProviderDefined-annotated, the writer consults the registry by class and dehydrates via the adapter (adapter.toFields) into a ProviderDefinedType. Annotation-based auto-conversion is unchanged; adapter lookup is an additional resolution path. The GraphSON write path gains the same capability via PdtGraphSONSerializerProviderV4, which returns an adapter-based serializer for registry-registered classes. The registry is threaded through GraphBinaryMessageSerializerV4 and AbstractGraphSONMessageSerializerV4. The prior gap-validation test (which asserted the failure) is replaced by GraphBinaryWriterPdtTest#shouldDehydrateRegisteredButUnannotatedTypeViaAdapterOnWritePath, asserting a successful adapter round-trip. tinkerpop-lka Assisted-by: Kiro:claude-opus-4.8
…r to CompositePDTAdapter Behavior-preserving refactor that prepares the PDT adapter SPI for the upcoming PrimitivePDT (0xF1) work by introducing a common supertype. - ProviderDefinedTypeAdapter<T> is now a thin common supertype exposing only typeName() and targetClass(). - New CompositePDTAdapter<T> extends it with the composite-specific toFields(T)/fromFields(Map) methods. - ProviderDefinedTypeRegistry stores composite adapters as CompositePDTAdapter; register(...) accepts the supertype and routes composite adapters via instanceof; create() discovers adapters via ServiceLoader on the supertype so a single service file can list any adapter kind. AnnotatedTypeAdapter now implements CompositePDTAdapter. - Updated composite toFields call sites in GremlinLang and the lka write-path code (GraphBinaryWriter, PdtGraphSONSerializersV4) to use CompositePDTAdapter. - Updated test fixtures (TestPointAdapter and others) to implement CompositePDTAdapter. No primitive (0xF1) logic is introduced here. Composite behavior is unchanged; all composite PDT tests pass. tinkerpop-2gy.1 Assisted-by: Kiro:claude-opus-4.8
Introduces the core value type for PrimitivePDT and its GraphBinary
serialization, mirroring the composite ProviderDefinedType.
- PrimitiveProviderDefinedType: immutable (String name, String value).
name non-null/non-empty; value non-null with empty string allowed and
null rejected; equals/hashCode on (name, value); toString pdt[name](value);
transient withHydrated/getHydrated for symmetry. The value is opaque and
is never parsed or normalized.
- PrimitiveProviderDefinedTypeSerializer: SimpleTypeSerializer over
DataType.PRIMITIVE_PDT writing/reading two fully-qualified Strings
({name}{value}).
- DataType: add PRIMITIVE_PDT(0xF1).
- TypeSerializerRegistry: register PrimitiveProviderDefinedType ->
PrimitiveProviderDefinedTypeSerializer.
Adapter/registry hydration, reader/writer dehydration dispatch, GraphSON,
grammar, and GLVs are deferred to later beads.
Tests: PrimitiveProviderDefinedTypeTest (validation, equals/hashCode/toString)
and PrimitiveProviderDefinedTypeSerializerTest (round-trip incl. opaque-value
fidelity: leading zeros, large and non-numeric values).
tinkerpop-2gy.2
Assisted-by: Kiro:claude-opus-4.8
… dispatch Wires PrimitivePDT into the registry and the GraphBinary read/write paths. - New PrimitivePDTAdapter<T> extends ProviderDefinedTypeAdapter<T> with toValue(T)/fromValue(String). - ProviderDefinedTypeRegistry: parallel primitiveAdaptersByName/ByClass; register(...) routes PrimitivePDTAdapter into the primitive maps; getPrimitiveAdapterByName/ByClass added. Registering a class already registered under the other kind (composite vs primitive) throws (fail-fast, bidirectional). ServiceLoader discovery on the supertype picks up primitive adapters automatically. - hydratePrimitive(PrimitiveProviderDefinedType): adapter lookup by name + fromValue, with graceful degradation (log + return raw) on missing or throwing adapter. The composite hydrate() recursion now also hydrates a PrimitiveProviderDefinedType nested inside a composite's fields. - GraphBinaryReader.read(): hydrate a deserialized PrimitiveProviderDefinedType via the registry. - GraphBinaryWriter: dehydrate a raw object whose class has a registered PrimitivePDTAdapter into a PrimitiveProviderDefinedType (parallel to the composite adapter path), resolving the PRIMITIVE_PDT serializer. GraphSON, grammar, server fixtures, and GLVs remain for later beads. Tests: registry primitive register/lookup, hydratePrimitive success and graceful degradation, dual-registration throws, primitive-nested-in-composite hydration; writer adapter round-trip for an unannotated primitive type. tinkerpop-2gy.3 Assisted-by: Kiro:claude-opus-4.8
Adds GraphSON V4 support for PrimitiveProviderDefinedType under the
g:PrimitivePdt type tag.
- PrimitiveProviderDefinedTypeJacksonSerializer emits
{"type": <name>, "value": <value>} with value as an untyped JSON string
(per the GraphSON spec; the value is the opaque stringified primitive).
- PrimitiveProviderDefinedTypeJacksonDeserializer parses type/value and
hydrates via the ProviderDefinedTypeRegistry when set.
- GraphSONModule (V4) maps PrimitiveProviderDefinedType -> "PrimitivePdt",
registers the ser/deser, and threads the registry to the primitive
deserializer via setPdtRegistry.
- Write-side adapter fallback (PdtGraphSONSerializerProviderV4 /
GraphSONTypeIdResolver) extended so a raw object with a registered
PrimitivePDTAdapter serializes as g:PrimitivePdt.
Response-only in T4; both directions implemented for round-trip tests.
Tests: PdtGraphSONSerializersV4Test extended with g:PrimitivePdt
serialize/deserialize, registry hydration, and primitive-nested-in-composite.
tinkerpop-2gy.4
Assisted-by: Kiro:claude-opus-4.8
…ng support
Adds the gremlin-lang text form for PrimitivePDT as an overload of the
existing PDT(...) literal: PDT("name", "value").
- Gremlin.g4: pdtLiteral gains a second unlabeled alternative
(K_PDT LPAREN stringLiteral COMMA stringLiteral RPAREN). Unambiguous with
the composite map form ('[' vs quote on the 2nd arg). Reuses K_PDT.
- GenericLiteralVisitor.visitPdtLiteral branches on genericMapLiteral != null;
the primitive form builds PrimitiveProviderDefinedType(name, value).
- GremlinLang.argAsString emits PDT(<name>,<value>) for
PrimitiveProviderDefinedType and auto-dehydrates classes with a registered
PrimitivePDTAdapter.
- All translator visitors (Java, Groovy, Python, Javascript, Go, DotNet,
Anonymized) emit the language-native primitive construction; the base
TranslateVisitor passthrough covers both forms. Composite branches updated
from stringLiteral() to stringLiteral(0) for the new list accessor.
GLV runtime libraries are handled in later beads.
Tests: GeneralLiteralVisitorTest (parse primitive + composite still works),
GremlinLangTest (round-trip + auto-dehydration), GremlinTranslatorTest
(per-language primitive emission).
tinkerpop-2gy.5
Assisted-by: Kiro:claude-opus-4.8
PrimitivePDT reuses the same ProviderDefinedTypeRegistry that composite already threads through the Java serialization stack, so no production wiring changes were required. Adds a driver-level test proving end-to-end behavior at the serializer/registry level (no live server): - raw adapter-registered object round-trips through the GraphBinary message serializer (request dehydration + response hydration); - a raw PrimitiveProviderDefinedType round-trips over GraphBinary; - GraphSON response path hydrates an adapter-registered primitive; - full GraphBinary request/response cycle hydrates back to the typed object. tinkerpop-2gy.6 Assisted-by: Kiro:claude-opus-4.8
Adds gremlin-server test fixtures and end-to-end integration coverage for
PrimitivePDT, mirroring the composite fixtures.
- Uint32 (long-backed unsigned 32-bit) + Uint32Adapter (PrimitivePDTAdapter;
toValue=Long.toUnsignedString, fromValue parses with 0..4294967295 range
validation).
- TinkerId (String-backed, non-numeric) + TinkerIdAdapter, proving the
adapter generalizes beyond numbers.
- Measurement: a @ProviderDefined composite fixture containing a Uint32
field, exercising primitive-nested-in-composite.
- Primitive adapters registered via the unified
META-INF/services/...ProviderDefinedTypeAdapter file so the server/test
registry discovers them through ServiceLoader.
- GremlinServerPrimitivePdtIntegrateTest: injects PDT("Uint32","..."),
PDT("TinkerId","..."), a Measurement containing a nested Uint32, and a
collection, asserting correct round-trip/hydration.
tinkerpop-2gy.7
Assisted-by: Kiro:claude-opus-4.8
Implements PrimitivePDT in the Python GLV, mirroring the composite support.
- structure/graph.py: PrimitiveProviderDefinedType(name, value) and registry
support for primitive adapters (register + hydrate_primitive with graceful
degradation); reuses the existing pdt_registry threading.
- structure/io/graphbinaryV4.py: DataType.primitive_pdt=0xf1 and
PrimitiveProviderDefinedTypeIO (writes/reads two fully-qualified Strings);
reader hydration dispatch for PrimitiveProviderDefinedType, including
primitive-nested-in-composite.
- driver/serializer.py: primitive registry threaded through the same
pdt_registry path as composite.
GraphSON read support is intentionally omitted: the gremlin-python driver is
GraphBinary-only for V4 (no GraphSON V4 deserializer exists), so there is no
g:PrimitivePdt read path to add. Clients send PrimitivePDT as the gremlin-lang
PDT("name","value") literal and receive it via GraphBinary.
Tests: 40 passing (GraphBinary round-trip incl. opaque-value fidelity,
registry hydration, primitive-nested-in-composite), 3 pre-existing
entry_points skips.
tinkerpop-2gy.8
Assisted-by: Kiro:claude-opus-4.8
…upport, registry naming, and integration coverage
Outcome of the post-implementation review pass over the PrimitivePDT work.
Touches several already-merged beads; details and departures below.
Production fixes:
- GraphBinaryWriter.dehydrateToPdt now prefers a registered adapter over the
@ProviderDefined annotation, matching the documented precedence in
GremlinLang.argAsString ("a registered adapter takes priority"). Previously
the binary write path preferred the annotation, diverging from the request
path. (refs tinkerpop-lka, tinkerpop-2gy.3)
- gremlin-python: GremlinLang._arg_as_string did not handle
PrimitiveProviderDefinedType, raising TypeError when a primitive PDT was used
as a traversal argument. Added the PDT("name","value") text emission and
primitive-adapter auto-dehydration (primitive checked before composite),
mirroring the Java side. This gap shipped in the Python GLV bead and was
caught by the new integration tests. (refs tinkerpop-2gy.8)
- Renamed the composite adapter accessors/maps to be explicit about "composite"
in both Java (getCompositeAdapterByName/ByClass, compositeAdaptersBy*) and
Python (get_composite_adapter_by_class, _composite_adapters_by_*), now that a
parallel primitive set exists. (refs tinkerpop-2gy.1, tinkerpop-2gy.3)
Test coverage added (these gaps allowed the above bugs to ship):
- GraphBinaryWriterPdtTest: precedence regression test asserting a registered
adapter wins over @ProviderDefined on the binary write path.
- GremlinDriverIntegrateTest: PrimitivePDT traversal-API integration tests
covering the unregistered base case, registered auto de/hydration, and the
registered nested (composite-containing-primitive) case. Reuses the
server-side Uint32/Uint32Adapter/Measurement fixtures rather than
duplicating them. (refs tinkerpop-2gy.6, tinkerpop-2gy.7)
- gremlin-python: traversal-API integration tests (raw/unregistered, registered
hydration, in-collection, nested-in-composite) mirroring the composite suite.
(refs tinkerpop-2gy.8)
Departure note: the gremlin-python GLV (tinkerpop-2gy.8) does not add a GraphSON
g:PrimitivePdt read path because the Python driver is GraphBinary-only for V4;
an orphaned GraphSON reader added during implementation was removed.
Assisted-by: Kiro:claude-opus-4.8
Implements PrimitivePDT in the JavaScript GLV, mirroring composite support
and applying the review lessons from the Python GLV.
- PrimitiveProviderDefinedType (name, value) in structure/graph.ts.
- PrimitivePDTSerializer replaces the prior StubSerializer for
DataType.PRIMITIVEPDT (0xf1): writes/reads two fully-qualified Strings.
- ProviderDefinedTypeRegistry gains an explicit primitive adapter path
(registerPrimitive / getPrimitiveAdapterByClass / hydratePrimitive),
mirroring the composite/primitive split used in Java/Python.
- gremlin-lang text serialization emits PDT("name","value") for a
PrimitiveProviderDefinedType and auto-dehydrates classes with a registered
primitive adapter (primitive checked before composite). This is the
client-side text path that was the Python gap; covered by unit tests here.
- Client/connection reuse the existing pdtRegistry option.
No GraphSON g:PrimitivePdt read path is added (consistent with the JS driver's
GraphBinary-based V4 response handling; nothing fabricated).
Tests: unit tests (serializer round-trip incl. opaque-value fidelity, registry
hydration, gremlin-lang PDT text emission) — full unit suite passing
(21082 tests). Integration tests (raw/unregistered, registered de/hydration,
nested-in-composite) pass against the test server: 4/4.
tinkerpop-2gy.9
Assisted-by: Kiro:claude-opus-4.8
Implements PrimitivePDT in the Go GLV, mirroring composite support and
applying the review lessons from the Python GLV.
- PrimitiveProviderDefinedType struct {Name, Value} in providerDefinedType.go.
- primitivePDTType (0xf1): type resolution for *PrimitiveProviderDefinedType,
primitivePdtWriter (two fully-qualified Strings), writer-map entry, and
readPrimitivePDT + deserializer switch case.
- PDTRegistry gains an explicit primitive adapter path
(RegisterPrimitiveFuncs[WithType], HydratePrimitive) alongside composite.
- gremlin-lang text translation (gremlinlang.go) emits PDT("name","value") for
*PrimitiveProviderDefinedType and auto-dehydrates values whose type has a
registered primitive adapter (primitive checked before composite) — the
client-side text path that was the Python gap, unit-tested here.
- Client wiring reuses the existing PDTRegistry path.
No GraphSON g:PrimitivePdt read path added (consistent with the Go driver's
GraphBinary-based V4 response handling).
Tests: unit tests for serializer/deserializer round-trip (incl. opaque-value
fidelity: leading zeros, large/non-numeric/empty), gremlin-lang text emission,
adapter dehydration with primitive-over-composite precedence, and registry
hydration (no-adapter raw, error->raw, nil, nested-in-composite) — all passing.
Integration tests (unregistered, registered de/hydration, nested, in-collection)
pass against the test server: PASS.
tinkerpop-2gy.10
Assisted-by: Kiro:claude-opus-4.8
Implements PrimitivePDT in the .NET GLV, mirroring composite support and
applying the review lessons from the Python GLV.
- PrimitiveProviderDefinedType (Name, Value) + IPrimitivePdtAdapter<T>
(TypeName/FromString/ToString) in Structure/.
- DataType.PrimitivePDT (0xF1) enabled; PrimitivePDTSerializer writes/reads
two fully-qualified Strings; registered in TypeSerializerRegistry.
- ProviderDefinedTypeRegistry gains an explicit primitive adapter path
(register + GetPrimitiveAdapterByType + HydratePrimitive), mirroring the
composite/primitive naming split used across the other GLVs.
- GraphBinaryReader hydrates PrimitiveProviderDefinedType via the registry.
- GremlinLang text translation emits PDT("name","value") for a
PrimitiveProviderDefinedType and auto-dehydrates registered types — the
client-side text path that was the Python gap.
- ADAPTER-OVER-ATTRIBUTE precedence: a registered adapter takes priority over
the [ProviderDefined] attribute on dehydration (matching the Java/Python fix
in ef194e3), applied in the text path.
- Client wiring reuses the existing SetPdtRegistry / GremlinClient /
DriverRemoteConnection path.
No GraphSON g:PrimitivePdt read path added (consistent with the .NET driver's
GraphBinary-based V4 response handling).
Tests: 57 unit tests pass (serializer round-trip incl. opaque-value fidelity,
registry hydration, gremlin-lang text emission, adapter-over-attribute
precedence). Integration tests (raw, opaque value, in-collection,
nested-in-composite, registered) pass against the test server: 6/6.
tinkerpop-2gy.11
Assisted-by: Kiro:claude-opus-4.8
…note, CHANGELOG
Documentation for the PrimitivePDT (0xF1 / g:PrimitivePdt) feature.
- docs/src/dev/provider/index.asciidoc: new "Primitive Provider Defined Types"
section (anchor primitive-provider-defined-types) — when to use a primitive
PDT (a single opaque stringified value for types with no native TinkerPop
representation, e.g. unsigned integers), per-GLV adapter registration
(Java PrimitivePDTAdapter, Python register_primitive, JS registerPrimitive,
Go RegisterPrimitiveFuncs, .NET IPrimitivePdtAdapter), and the
adapter-over-annotation/attribute precedence.
- docs/src/reference/gremlin-variants.asciidoc: document both PDT literal forms
— composite PDT("name",[map]) and primitive PDT("name","value").
- docs/src/upgrade/release-4.x.x.asciidoc: PrimitivePDT upgrade note.
- CHANGELOG.asciidoc: entry in the current unreleased section.
The GraphBinary (0xf1, {type}{value} two fully-qualified Strings) and GraphSON
(g:PrimitivePdt, untyped string value) spec sections already matched the
implementation and required no changes.
tinkerpop-2gy.13
Assisted-by: Kiro:claude-opus-4.8
…nsient, clarify JS PDT quoting Minor cleanups from the tinkerpop-2gy code review (no behavioral change): - PrimitiveProviderDefinedType: document why hydrated is excluded from equals/hashCode (mirroring ProviderDefinedType) and drop the no-op `transient` modifier (the type is not Serializable), aligning with the composite POJO's field declaration. - gremlin-javascript gremlin-lang: comment that PDT literals deliberately use double-quoted strings (consistent with the composite PDT form), with JSON.stringify handling escaping. Verified: gremlin-core PrimitiveProviderDefinedTypeTest 10/0/0; gremlin-javascript unit (gremlin-lang + pdt-registry) 228 passing. tinkerpop-2gy Assisted-by: Kiro:claude-opus-4.8
Go natively serializes the built-in uint32 (emitted as a Gremlin Long), and that native type-switch in argAsString runs before the PDT registry lookup, so a bare uint32 never reaches a primitive adapter. The nested integration test therefore defines a distinct named type (type myUint32 uint32) to opt into PDT handling. Added a comment explaining this and contrasting with .NET (where System.UInt32 is not natively serialized and can be registered directly). Filed tinkerpop-kof to decide whether a registered adapter should take precedence over native type serialization across all GLVs. tinkerpop-2gy Assisted-by: Kiro:claude-opus-4.8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements PrimitivePDT, the provider-defined primitive type reserved in the
GraphBinary (
0xF1) and GraphSON (g:PrimitivePdt) V4 specs, completing theProvider Defined Types feature begun with CompositePDT in #3433.
A PrimitivePDT represents a value as a single opaque stringified value plus a
provider type name — for types that have no native TinkerPop representation but can
be expressed as one string (e.g. an unsigned 32-bit integer, a WKT geometry, a
provider-specific identifier). It complements CompositePDT (a type name + a map of
fields) for structured types.
Wire/text forms:
0xF1:{type}{value}— two fully-qualified Strings; the value isopaque and never parsed by TinkerPop.
g:PrimitivePdt:{"type": "...", "value": "..."}with an untypedstring value (response-only in V4).
PDT("name", "value")— an overload of the compositePDT("name", [map])literal (the second argument's type selects the variant; thisis unambiguous to the parser).
Provider integration is adapter-based: a provider registers a primitive adapter
(
toValue/fromValue) so the driver auto-dehydrates a native object on send andauto-hydrates it on receive, with no per-call configuration. A registered adapter
takes precedence over the
@ProviderDefinedannotation /[ProviderDefined]attribute on dehydration, consistent across GLVs.
What's included
gremlin-core / gremlin-util (Java)
PrimitiveProviderDefinedTypevalue type andPrimitiveProviderDefinedTypeSerializer(
DataType.PRIMITIVE_PDT0xF1).PrimitivePDTAdapter; extracted a commonProviderDefinedTypeAdaptersupertypewith
CompositePDTAdapter/PrimitivePDTAdapterimplementations and explicitcomposite/primitive registry accessors.
dispatch, and GraphSON
g:PrimitivePdtser/deser.GremlinLangtext emission.type registered via an adapter (but not annotated) can be serialized on the
server→client response path — a pre-existing CompositePDT gap.
GLVs — full support in Python, JavaScript, Go, .NET (and the Java driver):
value type,
0xF1serializer, registry primitive path, gremlin-langPDT("name","value")emission + adapter auto-dehydration, and client wiring (reusing the existing PDT
registry plumbing).
Server — test fixtures (
Uint32,TinkerId, and a compositeMeasurementcontaining a primitive) and end-to-end integration coverage.
Docs — provider guide section, gremlin-lang literal reference, upgrade note, and
CHANGELOG entry. The GraphBinary/GraphSON spec sections already matched the
implementation.
Key design decisions
adapter supertype unifies discovery while keeping composite/primitive distinct.
PDT(...)rather than a new keyword, following the existingliteral-alternation convention.
(leading zeros, large/non-numeric values), with the provider adapter owning
parse/format.
and response paths.
Testing
fidelity, registry hydration with graceful degradation, dual-registration rejection,
gremlin-lang text emission, adapter-over-annotation/attribute precedence).
unregistered/raw round-trip, registered auto de/hydration, and
primitive-nested-in-composite: Java, Python, JavaScript, Go, .NET.
VOTE +1