Optional gRPC transport over ArcadeDB's gRPC plugin (GrpcServerPlugin).
Its reason to exist is query_stream/3: ArcadeDB's StreamQuery in CURSOR mode is a
real server cursor — the server runs the query once and streams batch_size-sized
batches as the client iterates (O(n), server-paced), where the HTTP transport can only
offset-page (O(n²) in the general case) and Bolt streams Cypher only. execute/4 covers
the unary read/write path, and begin/commit/rollback run gRPC session transactions
(tx-scoped reads/writes carry the transaction_id). The remaining HTTP-shaped admin/server
surface (server settings, users, login/logout) stays :not_supported — use an HTTP Conn.
Selecting it
conn =
Arcadic.connect("grpc://localhost:50051", "mydb",
transport: Arcadic.Transport.Grpc,
auth: {"root", System.fetch_env!("PW")}
)Requires the optional deps {:grpc, "~> 0.11"} and {:protobuf, "~> 0.17"}. The endpoint host
and port come from Conn.base_url; credentials from Conn.auth ({user, pass} only — a bearer
conn is rejected at construction, as with Bolt). Admin RPCs (Ping) authenticate by the request
credentials field; data RPCs by x-arcade-user/-password/-database metadata.
Connection pooling
By default each call opens a fresh channel. For channel reuse, add the caller-supervised
{Arcadic.Transport.Grpc.ChannelPool, []} to your supervision tree — the transport then reuses one
long-lived, HTTP/2-multiplexed channel per {host, port, tls?} endpoint across all calls (a gRPC
channel multiplexes concurrent streams). Absent the pool, the per-call connect is used (no behavior
change). Tenant-blind — the pool keys on the endpoint only.
TLS
Enabled by a secure URL scheme (grpcs:// / grpc+tls:// / https://) or
transport_options: [tls: true]; plaintext otherwise. Because credentials travel on the wire,
prefer a secure scheme in production. When TLS is on it ALWAYS verifies the server certificate
against the OS trust store (verify_peer + :public_key.cacerts_get/0) — never verify_none;
an untrusted cert fails the handshake.
Redaction & value handling
A gRPC RPCError.message can echo the offending statement or value. This transport maps every
failure to an atom-only Arcadic.Error/Arcadic.TransportError reason and NEVER surfaces the
raw wire message — the value-free contract the HTTP/Bolt transports honor. Caveat: the
underlying :grpc library emits [:grpc, :client, :rpc, *] telemetry whose metadata carries the
full request (bound params, AND for batch_ingest/ingest the whole record batch) — a consumer
that attaches a handler and logs that metadata would surface those values OUTSIDE arcadic's
redaction boundary. Values are encoded value-free: any value with no gRPC representation (an
integer outside int64, a non-UTF-8 binary) yields {:error, :invalid_params | :invalid_record},
never a Protobuf.EncodeError that echoes it — so gRPC rejects a few large-integer values HTTP
accepts as JSON text (a documented, mechanically-forced divergence). DECIMAL columns decode to a
float (lossy for large scales; an out-of-range scale decodes to nil rather than crashing the
row), since arcadic takes no arbitrary-decimal dependency.
batch_ingest (graph vertex/edge bulk) maps to the gRPC GraphBatchLoad stream — the twin of
HTTP POST /api/v1/batch. Because GraphBatchChunk carries no transaction field, a batch CANNOT
join an open transaction/3; called on a session-bearing conn it fails closed
(:transaction_unsupported) rather than silently auto-commit outside the caller's tx (on HTTP the
same batch rides the session). Use the document-ingest arm or an UNWIND $rows statement in a tx.