DDL translation: CREATE TABLE, CREATE SEQUENCE, constraint extraction.
Produces Datahike tx-data that installs schema attributes (:db/ident,
:db/valueType, :db/cardinality, :db/unique, :pg/type, and the
pgwire-specific :pg/* constraint metadata).
The predicate helpers (identity-column?, column-is-primary-key?,
column-is-unique?, column-is-not-null?, specs-contain-seq?,
column-check-expr-text, column-default-spec) walk JSqlParser's
ColumnDefinition.getColumnSpecs lists, which are raw strings — so
each predicate is a case-insensitive token matcher.
Entry points:
Helpers:
DDL translation: CREATE TABLE, CREATE SEQUENCE, constraint extraction.
Produces Datahike tx-data that installs schema attributes (`:db/ident`,
`:db/valueType`, `:db/cardinality`, `:db/unique`, `:pg/type`, and the
pgwire-specific `:pg/*` constraint metadata).
The predicate helpers (`identity-column?`, `column-is-primary-key?`,
`column-is-unique?`, `column-is-not-null?`, `specs-contain-seq?`,
`column-check-expr-text`, `column-default-spec`) walk JSqlParser's
`ColumnDefinition.getColumnSpecs` lists, which are raw strings — so
each predicate is a case-insensitive token matcher.
Entry points:
- translate-create-table — main; returns {:type :ddl :tx-data [...]}
- translate-create-sequence — CREATE SEQUENCE name [...]
Helpers:
- extract-ddl-constraints — CHECK / NOT NULL / UNIQUE / FK clauses
- extract-inherits — PostgreSQL INHERITS(column-check-expr-text col)Extract the text of an inline CHECK (…) constraint from a
ColumnDefinition's ColumnSpecs, or nil if none.
JSqlParser emits inline CHECK as two tokens: CHECK and a second
token that's the entire parenthesized expression, e.g.
["CHECK" "(x > 0)"]. We keep the inner text.
Also tolerate the tokenized form PG dialect dumps sometimes
produce — CHECK followed by (, individual tokens, ) — by
paren-matching across the remaining specs.
Extract the text of an inline `CHECK (…)` constraint from a ColumnDefinition's ColumnSpecs, or nil if none. JSqlParser emits inline CHECK as two tokens: `CHECK` and a second token that's the entire parenthesized expression, e.g. `["CHECK" "(x > 0)"]`. We keep the inner text. Also tolerate the tokenized form PG dialect dumps sometimes produce — CHECK followed by `(`, individual tokens, `)` — by paren-matching across the remaining specs.
(column-default-spec col)If the column spec list carries DEFAULT <expr>, return a map
describing the default so translate-create-table can attach it to
the schema entity. Shape:
{:kind :literal :value <long|double|string|boolean|nil>} {:kind :fn :value <canonical fn name string>} {:kind :nextval :value <sequence name string>} {:kind :unsupported :raw <original SQL text>}
The literal branch handles numbers, single-quoted strings, boolean keywords, and NULL. The :fn branch covers the stateless current-* functions PG users rely on. :nextval is the only stateful default we support and it ties into our existing sequence infrastructure. Unknown expressions land in :unsupported so CREATE TABLE can raise loudly instead of silently dropping the constraint.
Walks the ColumnSpecs as whitespace-separated tokens; tolerant of
parens (e.g. DEFAULT (now())).
If the column spec list carries `DEFAULT <expr>`, return a map
describing the default so translate-create-table can attach it to
the schema entity. Shape:
{:kind :literal :value <long|double|string|boolean|nil>}
{:kind :fn :value <canonical fn name string>}
{:kind :nextval :value <sequence name string>}
{:kind :unsupported :raw <original SQL text>}
The literal branch handles numbers, single-quoted strings, boolean
keywords, and NULL. The :fn branch covers the stateless current-*
functions PG users rely on. :nextval is the only stateful default
we support and it ties into our existing sequence infrastructure.
Unknown expressions land in :unsupported so CREATE TABLE can
raise loudly instead of silently dropping the constraint.
Walks the ColumnSpecs as whitespace-separated tokens; tolerant of
parens (e.g. `DEFAULT (now())`).(column-is-not-null? col)True when the column-spec list carries NOT NULL. PK columns are
implicitly NOT NULL in PG; callers should combine this with
column-is-primary-key? to avoid double-flagging.
True when the column-spec list carries `NOT NULL`. PK columns are implicitly NOT NULL in PG; callers should combine this with column-is-primary-key? to avoid double-flagging.
(column-is-primary-key? col)True when a column has inline PRIMARY KEY in its column specs.
True when a column has inline PRIMARY KEY in its column specs.
(column-is-unique? col)True when a column has inline UNIQUE in its column specs (not part of PRIMARY KEY, which we handle separately).
True when a column has inline UNIQUE in its column specs (not part of PRIMARY KEY, which we handle separately).
(extract-ddl-constraints ct)Collect PRIMARY KEY and UNIQUE constraints from a CreateTable into a
normalized shape. Merges column-level specs (id INT PRIMARY KEY) with
table-level indexes (PRIMARY KEY (a,b), UNIQUE (c)).
Returns a map: :pk-cols — vector of column names in the PK (empty if none) :pk-name — explicit constraint name for the PK, or nil :uniques — vector of {:cols [str...] :name str-or-nil} each an independent UNIQUE constraint Column names are the raw SQL idents (unquoted), not namespaced keywords. The caller owns the :t/col namespace mapping.
Collect PRIMARY KEY and UNIQUE constraints from a CreateTable into a
normalized shape. Merges column-level specs (`id INT PRIMARY KEY`) with
table-level indexes (`PRIMARY KEY (a,b), UNIQUE (c)`).
Returns a map:
:pk-cols — vector of column names in the PK (empty if none)
:pk-name — explicit constraint name for the PK, or nil
:uniques — vector of {:cols [str...] :name str-or-nil}
each an independent UNIQUE constraint
Column names are the raw SQL idents (unquoted), not namespaced keywords.
The caller owns the :t/col namespace mapping.(extract-inherits ct)Extract parent table name from INHERITS clause in CREATE TABLE options.
Extract parent table name from INHERITS clause in CREATE TABLE options.
(identity-column? col)Check if a JSqlParser ColumnDefinition has GENERATED BY DEFAULT AS IDENTITY or uses a SERIAL type (which implies auto-increment).
Check if a JSqlParser ColumnDefinition has GENERATED BY DEFAULT AS IDENTITY or uses a SERIAL type (which implies auto-increment).
(pg-type-hint base-type array?)The :pg/type a column of SQL type base-type should record, or nil
when the storage valueType already implies the right OID.
Datahike collapses whole families onto one carrier — every integer is
:db.type/long, every temporal is :db.type/instant, jsonb and bit are
:db.type/string — so this hint is the ONLY thing that lets the
catalog report date rather than timestamp, or int4 rather than
int8. Drivers pick codecs off those OIDs.
Extracted so ALTER TABLE ... ADD COLUMN records the same hints
CREATE TABLE does. It previously recorded them only for json/jsonb,
so a column added by ALTER as date or smallint reported the
storage type forever after.
array? suppresses the scalar hints: an int[] column has base-type
"int" too and its array :pg/type ("_int4") is set elsewhere.
The `:pg/type` a column of SQL type `base-type` should record, or nil
when the storage valueType already implies the right OID.
Datahike collapses whole families onto one carrier — every integer is
:db.type/long, every temporal is :db.type/instant, jsonb and bit are
:db.type/string — so this hint is the ONLY thing that lets the
catalog report `date` rather than `timestamp`, or `int4` rather than
`int8`. Drivers pick codecs off those OIDs.
Extracted so `ALTER TABLE ... ADD COLUMN` records the same hints
CREATE TABLE does. It previously recorded them only for json/jsonb,
so a column added by ALTER as `date` or `smallint` reported the
storage type forever after.
`array?` suppresses the scalar hints: an `int[]` column has base-type
"int" too and its array `:pg/type` ("_int4") is set elsewhere.(sequence-entity seq-name
{:keys [increment minvalue maxvalue start cache cycle? type]})The stored entity for a sequence with the given resolved params.
:__seq__/value holds the last value HANDED OUT, so a fresh sequence
stores start - increment and the first advance lands exactly on
start. (PG models this as last_value + is_called=false; the offset
encoding is equivalent for a single-value-at-a-time nextval and is
what the IDENTITY path in translate-create-table already assumes.)
The stored entity for a sequence with the given resolved params. `:__seq__/value` holds the last value HANDED OUT, so a fresh sequence stores `start - increment` and the first advance lands exactly on start. (PG models this as last_value + is_called=false; the offset encoding is equivalent for a single-value-at-a-time nextval and is what the IDENTITY path in translate-create-table already assumes.)
(sequence-params opts {:keys [existing]})Resolve a classifier option list into concrete sequence parameters, applying PG's defaults and raising PG's errors.
Mirrors init_params (postgres src/backend/commands/sequence.c:1260)
including its ORDER, because a statement with more than one problem
must report the same one PG reports: duplicate detection, then AS,
INCREMENT, CYCLE, MAXVALUE, MINVALUE, the min/max crosscheck, START,
RESTART, CACHE.
existing is the current parameter map for ALTER (nil for CREATE);
options left unspecified keep their existing values on ALTER and take
the defaults on CREATE.
Returns {:type :increment :minvalue :maxvalue :start :cache :cycle? :restart :owned-by}.
Resolve a classifier option list into concrete sequence parameters,
applying PG's defaults and raising PG's errors.
Mirrors `init_params` (postgres src/backend/commands/sequence.c:1260)
including its ORDER, because a statement with more than one problem
must report the same one PG reports: duplicate detection, then AS,
INCREMENT, CYCLE, MAXVALUE, MINVALUE, the min/max crosscheck, START,
RESTART, CACHE.
`existing` is the current parameter map for ALTER (nil for CREATE);
options left unspecified keep their existing values on ALTER and take
the defaults on CREATE.
Returns {:type :increment :minvalue :maxvalue :start :cache :cycle?
:restart :owned-by}.Schema attributes backing a sequence entity. Idempotent — re-transacted on every CREATE SEQUENCE.
Schema attributes backing a sequence entity. Idempotent — re-transacted on every CREATE SEQUENCE.
(specs-contain-seq? specs tokens)True when the whitespace-split column-spec list contains the given token sequence contiguously, case-insensitive. Used to detect PRIMARY KEY (two tokens) and UNIQUE (one token) inline on a ColumnDefinition.
True when the whitespace-split column-spec list contains the given token sequence contiguously, case-insensitive. Used to detect PRIMARY KEY (two tokens) and UNIQUE (one token) inline on a ColumnDefinition.
(translate-create-sequence {:keys [seq-name seq-opts if-not-exists?]})Translate a classified CREATE SEQUENCE into schema + initial entity.
Consumes the token classifier's :seq-opts rather than JSqlParser's
AST: the grammar there covers only a subset of PG's option list, and
the values used to be recovered by regex over the re-rendered SQL
(increment\s+by\s+(\d+)), which could not see a negative increment
and silently dropped MINVALUE/MAXVALUE/CACHE/CYCLE. See issue #21.
Translate a classified CREATE SEQUENCE into schema + initial entity. Consumes the token classifier's `:seq-opts` rather than JSqlParser's AST: the grammar there covers only a subset of PG's option list, and the values used to be recovered by regex over the re-rendered SQL (`increment\s+by\s+(\d+)`), which could not see a negative increment and silently dropped MINVALUE/MAXVALUE/CACHE/CYCLE. See issue #21.
(translate-create-table ct db)Translate a CREATE TABLE statement to Datahike schema transaction data. Also returns :table-name, :column-order, :identity-cols, and :inherits.
Translate a CREATE TABLE statement to Datahike schema transaction data. Also returns :table-name, :column-order, :identity-cols, and :inherits.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |