Skip to Content
We are live but in Staging 🎉

Data — API Reference

Package: dodil.tables.v1 · Service: Tables

Two ways to move rows, and they are not equivalent:

  1. Typed RPCsUpsert, Delete, WriteStream, Commit for writes; Query, QueryStream, GetRow, BatchGetRows for reads. Pass typed RowSet payloads, no SQL text.
  2. SQL statements through ExecuteINSERT, UPDATE, DELETE, MERGE. Everything the typed RPCs don’t cover: predicates, joins, subqueries, INSERT … SELECT.

There is no Insert, Merge, Update or DeleteRows RPC. The Tables service has exactly two single-shot write RPCs — Upsert and Delete — plus the WriteStream channel and the Commit transaction primitive. Everything else is SQL. The dodil data table insert|merge|update|delete-rows commands are real; each builds one SQL statement and sends it through Execute (cli-shell/cli-k3/cmd/table.go:16-20).

Reads

RPCShapePage
QuerySELECT, results inline. The server enforces a response-size cap; an oversized result fails and directs you to QueryStream, or spools to a ResultManifest.Query
QueryStreamThe same request, streamed as row batches. Streaming is a transport choice, not a semantic one.Query
GetRowPrimary-key point read, with optional as_of_version time travel.Query
BatchGetRowsMany point reads in one call. Caps: 100 keys, 16 MiB.Query
InferParamsParameter-type inference for a prepared statement. Read-only, no execution.

Writes

SurfaceShapePage
Upsert RPCInsert-or-update by the table’s primary key (or match_columns). merge: true = partial-column merge.Upsert
Delete RPCKey-only delete — keys carries just the PK columns.Writing rows
WriteStreamBidirectional bulk/CDC channel: upserts and deletes interleaved, order preserved, one ack per batch.Upsert
CommitAtomic multi-row commit to one table. Stateless — no BEGIN, a single-shot all-or-nothing commit.Upsert
INSERT SQLAppend rows, including INSERT … SELECT and INSERT OR REPLACE.Writing rows → INSERT
MERGE SQLMERGE INTO … USING … ON … with WHEN MATCHED / WHEN NOT MATCHED arms.Writing rows → MERGE
UPDATE SQLUPDATE … SET … WHERE …. A WHERE clause is mandatory.Writing rows → UPDATE
DELETE SQLDELETE FROM … WHERE …. A WHERE clause is mandatory — use TRUNCATE TABLE to clear a table.Writing rows → DELETE

Read-your-writes

The plane is stateless — it holds no session, and the watermark is the session. Every write returns a ULID (WriteAck.wal_ulid, WriteStreamAck.max_ulid, CommitResponse.max_wal_ulid, ExecuteResponse.max_wal_ulid). Pass it as the next read’s min_ulid and the read observes at least that write, or fails over to the S3 WAL — never a silently-stale overlay.

You usually do not need it. Freshness is not a client knob: STRONG is the default for every shape, and the analytical fast path is earned by a frontier check — only when the writer proves the db’s WAL backlog is empty does the plane take it, because then eventual ≡ strong (dodil-tables/crates/coordinator/src/service.rs:1692-1795). Any Freshness field a client sends is ignored, and dodil data table query --freshness is a deprecated no-op.

See also

  • Execute — full DuckDB SQL surface; the canonical DML/DDL reference
  • Tables — table lifecycle
  • SQL Compatibility — DuckDB dialect, statement shapes, the honest unsupported list
  • CLI Guidedodil data table query / insert / merge / upsert / update / delete-rows