Skip to Content
We are live but in Staging 🎉

Create

Two creation paths, on two different planes:

WhatPlaneSurface
CreateTable — you define the schemadata planedodil.tables.v1.Tables/CreateTable, or CREATE TABLE SQL via Execute
CreateTablePipeline — a Scriptum template owns the schemacontrol planedodil.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

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.

RPCHTTP
dodil.data.pipeline.v1.PipelineService/CreateTablePipelinePOST https://api.data.dodil.io/:bucket/pipelines/table

It does not create an ingest rule. CreateTablePipeline writes 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

# 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_prefix is 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 -i pattern you pass to ingest 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