TypeDB.GRPC.Decode (TypeDB.GRPC v0.1.0)

Copy Markdown View Source

Protobuf concepts into the structs typedb already defines.

Not a parallel set of types: an application that matches on %TypeDB.Concept.Attribute{} must keep matching after it switches transports, so this decodes into exactly those structs. Where the two wire formats disagree, this module is the side that moves.

They disagree about almost every value

The HTTP API sends values as JSON, which means everything TypeDB has no JSON equivalent for arrives as a string to be parsed — a decimal, a datetime, a duration. Protobuf sends them structured, so the work here is the opposite shape: assembling an Elixir term out of fields rather than parsing text.

  • a decimal is integer plus fractional, where fractional counts 1/10^19ths — TypeDB's fixed scale, not a digit count
  • a date is days since 0001-01-01, day 1 being that date
  • a datetime is seconds since the epoch plus nanoseconds
  • a duration is months, days and nanos held apart, exactly as TypeQL's grammar keeps them, which is why TypeDB.Duration can be built from it without going through text at all

That last one is a small win over the HTTP transport: this driver never has to emit or parse an ISO-8601 duration, so the negative-component problem the sibling documents cannot arise here.

Summary

Functions

A protobuf Concept into a TypeDB.Concept struct.

Any protobuf message as a tree of plain maps.

A Typedb.Protocol.ConceptRow and its column names into a TypeDB.ConceptRow.

A protobuf Value into the Elixir term it stands for.

The name TypeDB gives the value type of a Value, matching the strings the HTTP API uses so that %TypeDB.Concept.Value{value_type: ...} reads the same on both transports.

Functions

concept(concept)

@spec concept(Typedb.Protocol.Concept.t()) :: TypeDB.Concept.t() | nil

A protobuf Concept into a TypeDB.Concept struct.

nil for a Concept whose oneof is unset — which a server of a newer protocol can send, carrying a kind of concept this build has no schema for. Decoding the rest of the answer and leaving one column empty is better than failing the whole read.

message(message)

@spec message(struct()) :: map()

Any protobuf message as a tree of plain maps.

For analyze, and only for it. Every other reply in this driver decodes into a struct typedb already defines, because a caller has something to do with it; a query analysis is a description of the query, arbitrarily shaped, deep, and different in every TypeDB release. Hand-mapping it would be a hundred clauses that fall behind the protocol, so this walks the message's own reflection instead.

The rendering follows the sibling's JSON rather than protobuf convention: keys are the schema's json_name, so text_span reads as "textSpan", and a oneof becomes a "tag" alongside its inlined fields, which is exactly how the HTTP API renders the same tree. It does not follow proto3's JSON mapping, on purpose: that one omits fields holding their type's default, and in an analysis that would silently drop every reference to variable 0.

row(concept_row, columns)

@spec row(Typedb.Protocol.ConceptRow.t(), [String.t()]) :: TypeDB.ConceptRow.t()

A Typedb.Protocol.ConceptRow and its column names into a TypeDB.ConceptRow.

value(value)

@spec value(Typedb.Protocol.Value.t()) :: term()

A protobuf Value into the Elixir term it stands for.

value_type(value)

@spec value_type(Typedb.Protocol.Value.t()) :: String.t() | nil

The name TypeDB gives the value type of a Value, matching the strings the HTTP API uses so that %TypeDB.Concept.Value{value_type: ...} reads the same on both transports.