Reads
Write literal table names. Joins, CTEs, derived tables and WHERE subqueries all resolve on their own — there is no lookup-table map to fill.
Query
rpc Query(QueryRequest) returns (QueryResponse);
message QueryRequest {
string db_id = 1;
string sql = 2;
map<string, Value> params = 3; // @name placeholders — never interpolate
map<string, Type> param_types = 4; // NUMERIC / TIMESTAMP / DATE / JSON binds
uint32 limit = 5; // 0 = server default
optional string min_ulid = 6; // read-your-writes watermark
ResultEncoding result_encoding = 7; // TYPED (default) | ARROW
uint32 knn_ef_search = 8; // pgvector: SET hnsw.ef_search
uint32 knn_probes = 9; // pgvector: SET ivfflat.probes
bool knn_exact_scan = 10; // pgvector: exact re-rank
bool knn_eventual = 11; // pgvector: allow an eventual index read
}
message QueryResponse {
RowSet rows = 1; // EMPTY when the result spooled — read manifest
ServedBy served_by = 2;
string max_wal_ulid = 3; // watermark this read observed
repeated string warnings = 4;
ResultManifest manifest = 5; // set above the inline size threshold
map<string, string> table_watermarks = 6; // one token per table read
}{
"db_id": "kb-prod",
"sql": "SELECT * FROM events WHERE user_id = @uid LIMIT 100",
"params": { "uid": { "string_value": "u-101" } }
}Execute’s SELECT arm and Query reach the same engine; prefer Query on read-only paths because its request cannot write. The HTTP twin is POST /v1/databases/{db}/sql/query with {"sql": "…"} only.
manifest means rows is empty
Above a size threshold the plane spools the result to object storage and returns presigned GETs (format is arrow or parquet, with ttl_seconds). Fetch the parts directly, or re-issue with a LIMIT. An empty rows alongside a set manifest is not a zero-row result.
A strong read that cannot complete
When compaction is far behind, a read that would have to merge more pending writes than the engine can hold aborts instead of returning a partial answer:
FAILED_PRECONDITION: strong read aborted: the WAL overlay truncated at N entries,
so the result would be incomplete — compaction is catching up; retry shortlyRetry after a moment; a drain is already scheduled by the time this can happen. To force it, call Compact.
QueryStream
Same QueryRequest; the response is a stream of frames.
rpc QueryStream(QueryRequest) returns (stream QueryStreamResponse);
message QueryStreamResponse {
RowSet chunk = 1;
bool last = 2; // final frame
ServedBy served_by = 3;
repeated string warnings = 4;
}Column metadata travels only in the first frame — TypedRows.columns (or the Arrow schema message) is empty on continuation frames. Streaming is a transport choice, not a semantic one; use it when a result would exceed the inline cap and you do not want to fetch spooled parts.
GetRow
A primary-key point read. Always answered at strong consistency.
rpc GetRow(GetRowRequest) returns (GetRowResponse);
message GetRowRequest {
string db_id = 1;
string table = 2;
RowSet key = 3; // the PK columns, typed, one row
repeated string columns = 4; // projection (empty = all)
optional int64 as_of_version = 5; // time travel: a pure historical snapshot
optional string min_ulid = 6;
ResultEncoding result_encoding = 7;
}
message GetRowResponse {
bool found = 1;
RowSet row = 2; // absent when not found
ServedBy served_by = 3;
string max_wal_ulid = 4;
int64 version = 5; // the table version this read observed
}With as_of_version set the read is a snapshot of that committed version and does not include writes committed since. This is the only read-side time travel on the surface.
BatchGetRows
rpc BatchGetRows(BatchGetRowsRequest) returns (BatchGetRowsResponse);
message BatchGetRowsRequest {
string db_id = 1;
string table = 2;
RowSet keys = 3; // one row per key
repeated string columns = 4;
optional int64 as_of_version = 5;
ResultEncoding result_encoding = 6;
}
message BatchGetRowsResponse {
RowSet rows = 1;
int64 version = 2;
RowSet unprocessed_keys = 3; // not attempted this call — retry exactly these
}| Rule | Detail |
|---|---|
| Caps | At most 100 keys and 16 MiB per request; over either is INVALID_ARGUMENT. Split the batch. |
| Absence | A key missing from rows and not in unprocessed_keys does not exist. Absence is not an error. |
unprocessed_keys | Keys the plane could not serve this call (a shard moved mid-fetch). Retry exactly those. |
| Column set | Every found row carries every requested column, NULL where the row has no value. A requested column is never silently absent. since 0.1.43 |
InferParams
Read-only parameter-type inference for a prepared statement. Given SQL with @pN (or $n) placeholders, returns the type the engine will bind each one as, resolved from the query and the catalog. Executes nothing. The Postgres adapter calls it at Parse so that Describe and Bind agree; you need it only if you are building a driver.
ServedBy
enum ServedBy {
SERVED_BY_UNSPECIFIED = 0;
SERVED_BY_HOT = 1; // served from a reader's RAM
SERVED_BY_WARM = 2; // served from a reader's local disk
SERVED_BY_MERGED = 3; // recent writes merged over the table snapshot
SERVED_BY_WAREHOUSE = 4; // table snapshot scan, nothing pending
SERVED_BY_WAL = 5; // pending writes only (internal)
SERVED_BY_HOT_SHARDED = 6; // sharded in-memory fan-out
}HOT / WARM / HOT_SHARDED describe residency, which you influence with LOAD TABLE / RELEASE TABLE (Performance & layout). MERGED versus WAREHOUSE tells you whether pending writes existed at read time. None of them changes what you see: every read observes every acknowledged write. How the plane decides is on How it works.
RowSet
Two encodings, chosen per request with result_encoding:
| Encoding | Shape | Use when |
|---|---|---|
TypedRows (default) | columns[] with a type code per column (INT16 / INT32 / INT64, FLOAT32 / FLOAT64, STRING, UUID, BOOL, BYTES, NUMERIC, DATE, TIMESTAMP, JSON, array / struct / vector); rows[].values[] with int64_value, string_value, … NUMERIC, TIMESTAMP, DATE and UUID ride as canonical strings, exactly. | You want typed scalars without an Arrow dependency. |
| Arrow IPC stream | One schema message then record batches. | Bulk transfer into a dataframe. |
A SMALLINT column arrives as INT16, an INTEGER/BIGINT column as INT64 (storage is 64-bit), a UUID column as UUID. The storage vocabulary and pg wire OIDs are listed on Data types.