Arcadic.Transport.Grpc (Arcadic v0.7.1)

Copy Markdown View Source

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.