Tables — API Reference
Package: dodil.tables.v1 · Service: Tables · Proto: dodil-tables/proto/api/tables.proto
The tables data plane is served by tabled, not by the K3 control plane. One service, Tables, carries every table operation. Two entry points matter up front:
Execute— send one DuckDB-flavored SQL statement. Every DDL / DML / SELECT / SHOW / DESCRIBE / EXPLAIN shape goes here, and the plane routes it. This is the canonical surface.Query/QueryStream— the read-optimized path for SELECT.Execute’s SELECT arm delegates to the same engine, so prefer these when you only read.
Addressing.
db_idis the sole identity on every request — an opaque string the plane never decomposes. Through the gateway a bucket’s db id is{org}--{bucket}(or the bare bucket when there is no org). Storage location is registered once atCreateDatabaseand can never be expressed again.
The RPC surface
| Group | RPCs |
|---|---|
| SQL | Execute |
| Reads | Query · QueryStream · GetRow · BatchGetRows · InferParams |
| Writes | Upsert · Delete · WriteStream · Commit |
| DDL | CreateTable · AlterTable · DropTable · OptimizeTable · VacuumTable |
| Maintenance | Compact |
| Database catalog | CreateDatabase · DropDatabase · ListDatabases · DescribeDatabase · UpdateDatabaseS3Credentials · GetDatabaseStats |
| Reservations | SetReservation · DeleteReservation — control-plane authored; you call /:bucket/tables/reservation, not these |
| Tenant move | ReleaseDatabase · AdoptDatabase — control-plane orchestrated |
Sibling services in the same package: TablesResidency (Load / Release / GetLoadState), TablesObserve (NodeStatus / Capacity / Health), TablesVector, TablesGraph.
There is no
Insert,Merge,Update,DeleteRows,ListTables,GetTable,DescribeTable,ListPartitions,Materialize,HistoryorRestoreRPC. Those shapes are SQL statements overExecute. Thedodil data table insert|merge|update|delete-rows|list|get|describecommands are real — they build one SQL statement and send it throughExecute(cli-shell/cli-k3/cmd/table.go:16-20).
Sections
| Page | Covers |
|---|---|
| Tables | CreateTable · AlterTable · DropTable, plus the SQL that replaces the old list/get/describe RPCs |
| Data | Query · QueryStream · GetRow · BatchGetRows · Upsert · Delete · WriteStream · Commit, and the DML statements |
| Execute (SQL) | Execute — every statement shape with examples |
| Maintenance | OptimizeTable · VacuumTable · Compact, and RESTORE … TO VERSION AS OF |
| Templates | Scriptum templates filtered to warehouse-compatible ones — a control-plane surface |
Retired: the old
ConnectRPC, which minted per-bucket endpoints and scoped tokens, is gone — endpoints are stable per region and derived client-side; your credential works directly on every door. The engine plane (EnableEngine/GetEngine/DisableEngine) is gone too: tables are implicit per bucket and capacity is governed by reservations. See Connect & wire adapters.
Endpoint roots
| Endpoint | Serves |
|---|---|
table-rpc.uk-lon-1.dodil.io:443 | gRPC — the whole dodil.tables.v1 surface |
https://table.uk-lon-1.dodil.io | SQL-over-HTTP — POST /v1/databases/{db}/sql/execute and /sql/query |
pg.uk-lon-1.dodil.io:5432 | Postgres wire — sslmode=require (TLS is terminated here; don’t use prefer) |
https://api.data.dodil.io | Control plane only — buckets, pipelines, ingest, reservations. It serves no table data, DDL or SQL. |
Auth: bearer JWT, dk_ API key, or a service account — the same credential works on every door. See Conventions and Connect & wire adapters.
Wire conventions
| gRPC | SQL-over-HTTP (tables door) | |
|---|---|---|
| Address | db_id request field | {db} path segment — /v1/databases/{db}/… |
| Request | .proto field names | { "sql": "…" } |
| Row payload | RowSet — TypedRows (default) or Arrow IPC | plain JSON objects |
int64 | native int64_value — never a lossy double | JSON number |
The /v1/sql/execute + X-DB-Id header form still works; source describes it as the legacy path-prefix style (dodil-k3/crates/tables-gateway/src/http.rs:1-17). Prefer the canonical path form.
Execute replies with one of three arms — rows (SELECT/SHOW/DESCRIBE/EXPLAIN), rows_affected (INSERT/UPSERT/UPDATE/DELETE/MERGE), or ddl — plus statement_kind, served_by, warnings, and max_wal_ulid. Over HTTP that surfaces as {"kind":"query"|"write"|"ddl", …}.
Read-your-writes
The plane holds no session — the watermark is the session. Every write returns a ULID (WriteAck.wal_ulid, WriteStreamAck.max_ulid, ExecuteResponse.max_wal_ulid); pass it as the next read’s min_ulid on Query / GetRow / Execute and the read is guaranteed to observe at least that write, or fail over to the S3 WAL — never a silently-stale overlay.
You rarely need it. Freshness is not a client knob: the plane defaults every read to STRONG and only takes the analytical fast path when the writer proves the db’s WAL backlog is empty, at which point eventual ≡ strong (dodil-tables/crates/coordinator/src/service.rs:1692-1795). A Freshness field on a request is ignored.
Other protocols
Same tables, the wire you already speak:
- Postgres —
psql "host=pg.uk-lon-1.dodil.io port=5432 dbname=<db_id> user=dk_… password=<secret> sslmode=require"— full DuckDB-dialect SQL, pgvector KNN operators, prepared statements,COPY FROM STDIN, cursors, savepoints. Works with ORMs (SQLAlchemy, Prisma, …). - GraphQL —
https://gql.uk-lon-1.dodil.io/graphql, per-bucket generated schema over the same tables. - Bolt —
bolt+s://bolt.uk-lon-1.dodil.io:7687for graph traversals over table-backed graphs — TLS on connect, verified against public roots;neo4j+s://andneo4j://both fail because routing is refused. See Graph.
Endpoints + credentials: Connect & wire adapters.
See also
- Core Concepts — every type signature
- Quickstart — end-to-end in 5 minutes
- SQL Compatibility — DuckDB dialect + statement-shape reference
- CLI Guide —
dodil data table/dodil data sql