gRPC API
CamusDB exposes a client-facing gRPC API in addition to the REST/JSON API. The gRPC endpoint uses HTTP/2 on a dedicated port and reaches the same SQL, transaction, and row-operation engine as the HTTP endpoints.
It is enabled by default. Configure or disable it in config.yml:
grpc_enabled: true
grpc_port: 5096
If raft_certificate is configured, CamusDB reuses it for TLS on the gRPC
listener. Otherwise the gRPC listener uses plaintext HTTP/2, which is suitable
for local development and private test networks.
When authentication is enabled, use the CamusAuth service to exchange a
username and password for a bearer token. gRPC clients then send that token in
request metadata:
authorization: Bearer camus_<id>.<secret>
Authentication failures return UNAUTHENTICATED; privilege failures return
PERMISSION_DENIED. See
Authentication And Authorization.
The Protobuf contract lives in the CamusDB source tree at
CamusDB.Grpc.Contracts/Protos/camus_sql.proto. Generate client bindings from
that file with the standard gRPC toolchain for your language.
Services
The protocol defines three services:
| Service | Use it for |
|---|---|
CamusSql | SQL queries, DML, DDL, explicit transactions, ping, and duplex batching. |
CamusRows | Typed row CRUD without building SQL text. |
CamusAuth | Login and logout for authenticated gRPC clients. |
Use CamusSql for most application and ORM work. Use CamusRows when a client
already has structured row values, filters, and ordering and wants to avoid
constructing SQL strings.
Auth Service
CamusAuth provides the credential exchange used by gRPC-only deployments:
| RPC | Purpose |
|---|---|
Login | Accepts LoginRequest { user, password } and returns LoginReply { token, expires_at_unix_ms, expires_in_seconds }. |
Logout | Revokes the token supplied in authorization metadata. Missing or already-revoked tokens still produce the desired end state. |
expires_at_unix_ms and expires_in_seconds describe the same deadline. Renew
before that deadline instead of assuming a fixed token lifetime.
SQL Service
CamusSql provides:
| RPC | Shape | Purpose |
|---|---|---|
ExecuteQuery | server stream | Execute SELECT and SHOW; emits schema first, then rows. |
ExecuteNonQuery | unary | Execute INSERT, UPDATE, or DELETE; returns affected rows. |
ExecuteDdl | unary | Execute database, table, index, and schema statements. |
StartTransaction | unary | Start an explicit transaction and receive a TxnHandle. |
CommitTransaction | unary | Commit an explicit transaction by handle. |
RollbackTransaction | unary | Roll back an explicit transaction by handle. |
BatchExecute | bidirectional stream | Pipeline SQL operations, transaction lifecycle messages, and prepared-statement lifecycle messages on one stream. |
Ping | unary | Check liveness and round-trip connectivity. |
SQL parameters are sent as a map<string, Value>. Prefer parameters over SQL
string interpolation so typed values such as DATE, DATETIME, BYTES, and
UUID cross the wire without loss.
Row Service
CamusRows provides typed CRUD operations:
| RPC | Purpose |
|---|---|
InsertRow | Insert one row from a column-value map. |
Query | Query rows by table, optional index name, filters, and ordering. |
QueryById | Fetch one row by primary-key value. |
UpdateRows | Update matching rows. |
UpdateById | Update one row by primary-key value. |
DeleteRows | Delete matching rows. |
DeleteById | Delete one row by primary-key value. |
Filters use QueryFilter { column_name, op, value }, where op is a string
such as "=", ">", ">=", "<", "<=", or "LIKE". Ordering uses
OrderBy { column_name, direction }, with ascending or descending direction.
QueryById, UpdateById, and DeleteById take a string key value. The server
resolves the real primary-key column from the table schema, so the primary key
does not need to be named id.
Value Encoding
All parameters, row values, filters, and result cells use the Protobuf Value
message. It is a typed oneof that mirrors CamusDB's column types:
| Column type | Wire field | Encoding |
|---|---|---|
NULL | null_value | Explicit typed NULL sentinel. |
ID / OID | id_value | 24 lowercase ObjectId hex characters. |
INT64 | int64_value | Signed 64-bit integer. |
STRING | string_value | UTF-8 string. |
BOOL | bool_value | Boolean. |
FLOAT64 | float64_value | IEEE-754 double. |
FLOAT32 | float32_value | IEEE-754 float. |
BYTES | bytes_value | Raw bytes. |
DATE | date_value | UTC .NET ticks, truncated to midnight. |
DATETIME | datetime_value | UTC .NET ticks. |
ARRAY | array_value | Element type plus nested Value items. |
UUID / GUID | uuid_value | Exactly 16 bytes in canonical big-endian order. |
Important rules for client implementers:
- Send ObjectIds in
id_value, notstring_value. - Send UUIDs as 16 bytes, not as strings.
- Send
DATEandDATETIMEas ticks, not ISO strings or Unix timestamps. - Preserve
FLOAT32andFLOAT64as separate wire fields. - Include the array element type even when the array is empty.
- Treat an unset
Valueand explicitnull_valueas NULL when decoding.
To convert Unix milliseconds to ticks:
ticks = 621355968000000000 + unix_millis * 10000
Query Streams
ExecuteQuery, CamusRows.Query, and CamusRows.QueryById are
server-streaming calls with a schema-first contract:
QueryStreamMessage(schema)
QueryStreamMessage(row)
QueryStreamMessage(row)
...
QueryStreamMessage(cache_metadata)
The schema message is always first and appears exactly once, even when the
result set is empty. Rows are positional: row.values[i] belongs to
schema.columns[i]. Clients should take the column type from the schema, not
from the first non-NULL row value.
A query with a {cache=...} hint may append one trailing cache_metadata
message after the last row. Its absence means the statement carried no cache
hint.
Transactions
Every SQL and row operation can run in autocommit mode or inside an explicit transaction.
Autocommit requests omit txn_handle. The server starts a short transaction,
runs the operation, and commits it.
Explicit transactions use CamusSql.StartTransaction:
StartTransaction -> TxnHandle
ExecuteQuery / ExecuteNonQuery / ExecuteDdl with txn_handle
CommitTransaction(txn_handle)
StartTxnRequest and autocommit SqlRequest can set:
isolation_level:READ_COMMITTEDorSERIALIZABLEtransaction_mode:READ_WRITEorREAD_ONLYlocking:PESSIMISTICorOPTIMISTIC
When a request resumes an existing txn_handle, these fields are ignored
because the transaction properties were fixed when the transaction started.
Causal Tokens
Replies that advance transaction state include a causal token with three HLC components:
causal_token_ncausal_token_lcausal_token_c
Carry all three values into the next request in the same client session. The
N component is part of HLC ordering and must not be dropped. Threading the
token preserves read-your-writes behavior when a client talks to a cluster.
For explicit transactions, keep the latest causal token in the TxnHandle when
resuming, committing, or rolling back the transaction.
Duplex Batching
CamusSql.BatchExecute lets a client pipeline many operations over one
bidirectional stream. This is useful for drivers and ORMs that would otherwise
pay one unary round trip per statement.
Each request contains:
request_id: client-assigned id echoed by every response for that operationkind:QUERY,NON_QUERY,START,COMMIT,ROLLBACK,PREPARE, orCLOSErequest: the sameSqlRequestshape used by unary SQL calls
Responses for different request_id values may interleave and arrive out of
order. Clients must demultiplex by request_id.
A batched query emits:
schema
row...
query_complete
query_complete is the terminal message for that request and carries the row
count plus causal token. For a {cache=...} hinted query, query_complete
also carries the cache verdict. Non-query, start, commit, and rollback
operations each emit one terminal success response. Failed operations emit one terminal
BatchError { code, message }.
Operations that share the same transaction handle are ordered per batch stream. If a client uses multiple batch streams, pin all operations for a transaction to the same stream. Autocommit operations can use any stream.
grpc_batch_max_in_flight controls how many operations one batch stream may
execute concurrently before the server applies backpressure.
Prepared Statements
Prepared statements are supported on CamusSql.BatchExecute.
Send a PREPARE operation with the target database and SQL text. The terminal
PrepareReply returns:
statement_id: an integer handle scoped to that batch streamparameter_names: the positional binding order for placeholders
Then send QUERY or NON_QUERY operations with statement_id and
positional_parameters. When statement_id is set, do not also send SQL text,
database name, or named parameter maps.
Use CLOSE to release a prepared statement id on the stream. CLOSE is
idempotent.
Handles are stream-local and disappear when the BatchExecute stream closes or
is rebuilt. If an execution fails with CADB0520 UnknownPreparedStatement,
prepare again on the current stream and replay the operation once. Await the
PrepareReply before executing with its id; batch requests may otherwise run
concurrently and the execution can reach the server before registration.
Unary gRPC calls do not accept prepared handles because they have no stream scope. See Prepared Statements for supported statement types, binding rules, and configuration limits.
Errors And Retries
Unary and server-streaming RPCs surface domain errors as gRPC status errors with trailing metadata:
| Trailer | Meaning |
|---|---|
camus-error-code | CamusDB CADBxxxx error code. |
camus-error-message | Human-readable error message. |
Batched operations use in-band BatchError messages because trailers are
per-call, not per operation.
Retry by camus-error-code, not by message text:
| Code | Retry rule |
|---|---|
CADB0502 TransactionConflict | Replay the whole transaction from a fresh BEGIN. |
CADB0504 TransactionMustRetry | Replay the whole transaction from a fresh BEGIN. |
CADB0505 TransactionLifetimeExceeded | Replay the whole transaction from a fresh BEGIN. |
CADB0509 TransactionFinalizeUnresolved | Retry the same COMMIT or ROLLBACK on the same transaction handle. |
CADB0520 UnknownPreparedStatement | Prepare again on the current node or stream, then replay the execution once. |
For streaming queries, only replay automatically if no rows have been surfaced to the caller yet. Once rows have been emitted, surface the error to the caller instead of silently replaying the query.