Data — API Reference
Package: dodil.tables.v1 · Service: Tables
Two ways to move rows, and they are not equivalent:
- Typed RPCs —
Upsert,Delete,WriteStream,Commitfor writes;Query,QueryStream,GetRow,BatchGetRowsfor reads. Pass typedRowSetpayloads, no SQL text. - SQL statements through
Execute—INSERT,UPDATE,DELETE,MERGE. Everything the typed RPCs don’t cover: predicates, joins, subqueries,INSERT … SELECT.
There is no
Insert,Merge,UpdateorDeleteRowsRPC. TheTablesservice has exactly two single-shot write RPCs —UpsertandDelete— plus theWriteStreamchannel and theCommittransaction primitive. Everything else is SQL. Thedodil data table insert|merge|update|delete-rowscommands are real; each builds one SQL statement and sends it throughExecute(cli-shell/cli-k3/cmd/table.go:16-20).
Reads
| RPC | Shape | Page |
|---|---|---|
Query | SELECT, results inline. The server enforces a response-size cap; an oversized result fails and directs you to QueryStream, or spools to a ResultManifest. | Query |
QueryStream | The same request, streamed as row batches. Streaming is a transport choice, not a semantic one. | Query |
GetRow | Primary-key point read, with optional as_of_version time travel. | Query |
BatchGetRows | Many point reads in one call. Caps: 100 keys, 16 MiB. | Query |
InferParams | Parameter-type inference for a prepared statement. Read-only, no execution. | — |
Writes
| Surface | Shape | Page |
|---|---|---|
Upsert RPC | Insert-or-update by the table’s primary key (or match_columns). merge: true = partial-column merge. | Upsert |
Delete RPC | Key-only delete — keys carries just the PK columns. | Writing rows |
WriteStream | Bidirectional bulk/CDC channel: upserts and deletes interleaved, order preserved, one ack per batch. | Upsert |
Commit | Atomic multi-row commit to one table. Stateless — no BEGIN, a single-shot all-or-nothing commit. | Upsert |
INSERT SQL | Append rows, including INSERT … SELECT and INSERT OR REPLACE. | Writing rows → INSERT |
MERGE SQL | MERGE INTO … USING … ON … with WHEN MATCHED / WHEN NOT MATCHED arms. | Writing rows → MERGE |
UPDATE SQL | UPDATE … SET … WHERE …. A WHERE clause is mandatory. | Writing rows → UPDATE |
DELETE SQL | DELETE 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 Guide —
dodil data table query / insert / merge / upsert / update / delete-rows