Create
Two creation paths, on two different planes:
| What | Plane | Surface |
|---|---|---|
CreateTable — you define the schema | data plane | dodil.tables.v1.Tables/CreateTable, or CREATE TABLE SQL via Execute |
CreateTablePipeline — a Scriptum template owns the schema | control plane | dodil.data.pipeline.v1.PipelineService/CreateTablePipeline · POST /:bucket/pipelines/table |
See the Tables hub for the full RPC list.
CreateTable
Caller defines columns / partition columns / primary key; the plane creates the Delta table eagerly. CreateTableRequest carries a oneof ddl — a typed TableSpec or a raw sql string. Both normalize to the same executor, so CREATE TABLE SQL through Execute is equivalent.
Request
SQL
CREATE TABLE events (
id BIGINT NOT NULL,
user_id VARCHAR NOT NULL,
occurred_at TIMESTAMP NOT NULL,
event_type VARCHAR NOT NULL,
payload JSON,
PRIMARY KEY (id, user_id)
)
PARTITIONED BY (event_type);CREATE TABLE IF NOT EXISTS, CREATE OR REPLACE TABLE and CREATE TABLE … AS SELECT are all accepted — they set if_not_exists / or_replace / the CTAS source on the same plan.
Response
message CreateTableResponse {
string table_name = 1;
string location = 2; // s3://… path
int64 version = 3;
repeated string partition_columns = 4;
repeated string pk_columns = 5;
uint32 column_count = 6; // TOTAL columns
bool already_existed = 7; // IF NOT EXISTS hit an existing table
bool replaced = 8; // OR REPLACE dropped and rebuilt
}{
"table_name": "events",
"location": "s3://…/events",
"version": 0,
"partition_columns": ["event_type"],
"pk_columns": ["id", "user_id"],
"column_count": 5,
"already_existed": false,
"replaced": false
}Through Execute, the same information arrives as ExecuteResponse.ddl (a DdlResult, which adds columns_added, objects_deleted and rows_backfilled).
Defaults via ColumnDef.default_expr — BigQuery-aligned SQL applied at row commit when the column is absent: GENERATE_UUID(), ULID(), CURRENT_TIMESTAMP(), 42, 'pending'. The expression is validated against the column type at DDL time, not on first insert. Note GENERATE_UUID() and ULID() return strings — declare those columns VARCHAR. See Core Concepts → Column.
Keyless tables: a table created without a primary key gets a hidden _rowid VARCHAR DEFAULT generate_ulid() PK so bag semantics and Postgres parity hold. It is hidden from SELECT * and from the pg wire, but it means keyed DML addresses _rowid, not your columns.
CreateTablePipeline
Pipeline-generated mode — the schema is owned by a Scriptum analytics template and materializes lazily on first ingest. This is a control-plane RPC, on dodil.data.pipeline.v1.PipelineService, not on Tables. It returns a Pipeline, not a table.
| RPC | HTTP |
|---|---|
dodil.data.pipeline.v1.PipelineService/CreateTablePipeline | POST https://api.data.dodil.io/:bucket/pipelines/table |
It does not create an ingest rule.
CreateTablePipelinewrites two rows — the warehouse destination and the pipeline binding — and stops. Nothing will ever fire until you add a rule yourself (dodil-k3/bin/api/src/services/pipeline/create_table.rs:12-15, whose comment says so verbatim). This is the single most common reason a pipeline table stays empty.
Request
dodil data
# 1. create the pipeline + its warehouse destination
dodil data table pipeline create entities -b kb-prod \
--template entity_pii_extraction \
--description "Auto-extracted entities + PII"
# 2. MANDATORY — scope an ingest rule at it, or nothing ever runs
dodil data ingest add entities-intake -b kb-prod \
-c "$PIPELINE_ID" -i 'intake/**/*.pdf'Browse templates with dodil data table templates (flags: --search, --label).
Response
A Pipeline — see Pipelines. Its id is what you pass to dodil data ingest add -c.
folder_prefixis not a glob. It writes a 0-byte S3 folder marker so the prefix shows up in the object explorer before the first upload. It never scopes a rule and can never match a glob — rule scope is the-ipattern you pass toingest add.
Precondition: the template must declare @accepts_extension. Without it the create is refused (dodil-k3/bin/api/src/services/pipeline/facets.rs:112-124) — the derived globs have nothing to derive from.
The schema is template-defined and learned on first ingest. K3 deliberately does not derive columns up front (create_table.rs:74-79), so DESCRIBE returns nothing useful until the first document lands.
See also
- Lifecycle —
SHOW TABLES·DESCRIBE·DropTable·TRUNCATE - Schema —
AlterTable: ADD / DROP COLUMN, widening retype - Execute → CREATE TABLE — the SQL side, including CTAS
- Core Concepts → Table — type signature + both creation modes
- SQL recipes → Pipeline-generated table — the full six-step walkthrough
- CLI Guide —
dodil data table create/dodil data table pipeline create