Skip to Content
We are live but in Staging 🎉
Data EnginesSQLAPI ReferenceOverview

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_id is 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 at CreateDatabase and can never be expressed again.

The RPC surface

GroupRPCs
SQLExecute
ReadsQuery · QueryStream · GetRow · BatchGetRows · InferParams
WritesUpsert · Delete · WriteStream · Commit
DDLCreateTable · AlterTable · DropTable · OptimizeTable · VacuumTable
MaintenanceCompact
Database catalogCreateDatabase · DropDatabase · ListDatabases · DescribeDatabase · UpdateDatabaseS3Credentials · GetDatabaseStats
ReservationsSetReservation · DeleteReservation — control-plane authored; you call /:bucket/tables/reservation, not these
Tenant moveReleaseDatabase · 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, History or Restore RPC. Those shapes are SQL statements over Execute. The dodil data table insert|merge|update|delete-rows|list|get|describe commands are real — they build one SQL statement and send it through Execute (cli-shell/cli-k3/cmd/table.go:16-20).

Sections

PageCovers
TablesCreateTable · AlterTable · DropTable, plus the SQL that replaces the old list/get/describe RPCs
DataQuery · QueryStream · GetRow · BatchGetRows · Upsert · Delete · WriteStream · Commit, and the DML statements
Execute (SQL)Execute — every statement shape with examples
MaintenanceOptimizeTable · VacuumTable · Compact, and RESTORE … TO VERSION AS OF
TemplatesScriptum templates filtered to warehouse-compatible ones — a control-plane surface

Retired: the old Connect RPC, 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

EndpointServes
table-rpc.uk-lon-1.dodil.io:443gRPC — the whole dodil.tables.v1 surface
https://table.uk-lon-1.dodil.ioSQL-over-HTTP — POST /v1/databases/{db}/sql/execute and /sql/query
pg.uk-lon-1.dodil.io:5432Postgres wire — sslmode=require (TLS is terminated here; don’t use prefer)
https://api.data.dodil.ioControl 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

gRPCSQL-over-HTTP (tables door)
Addressdb_id request field{db} path segment — /v1/databases/{db}/…
Request.proto field names{ "sql": "…" }
Row payloadRowSetTypedRows (default) or Arrow IPCplain JSON objects
int64native int64_value — never a lossy doubleJSON 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:

  • Postgrespsql "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, …).
  • GraphQLhttps://gql.uk-lon-1.dodil.io/graphql, per-bucket generated schema over the same tables.
  • Boltbolt+s://bolt.uk-lon-1.dodil.io:7687 for graph traversals over table-backed graphs — TLS on connect, verified against public roots; neo4j+s:// and neo4j:// both fail because routing is refused. See Graph.

Endpoints + credentials: Connect & wire adapters.

See also