Derives virtual PostgreSQL table definitions from Datahike schemas.
Maps attribute namespace prefixes to table names and attribute local names to column names. For example: :person/name → table 'person', column 'name' :person/age → table 'person', column 'age'
Every virtual table gets an implicit 'db_id' column (the entity ID).
Derives virtual PostgreSQL table definitions from Datahike schemas. Maps attribute namespace prefixes to table names and attribute local names to column names. For example: :person/name → table 'person', column 'name' :person/age → table 'person', column 'age' Every virtual table gets an implicit 'db_id' column (the entity ID).
When non-nil, a map {:hints ihm :tables ihm} of two
IdentityHashMaps used to memoise schema-hints (keyed by db) and
derive-virtual-tables (keyed by schema, with inner keyed by
hints). Bound at the top of parse-sql's catalog enrichment block
so all catalog-data-for* calls in one query share the cache.
When non-nil, a map `{:hints ihm :tables ihm}` of two
IdentityHashMaps used to memoise `schema-hints` (keyed by db) and
`derive-virtual-tables` (keyed by schema, with inner keyed by
hints). Bound at the top of parse-sql's catalog enrichment block
so all `catalog-data-for*` calls in one query share the cache.(canonical-attr ci tname cname)The stored attribute for column cname of table tname, or nil when
the table has no such column (the caller then falls back to
(keyword tname cname), preserving NULL-for-unknown-column).
The stored attribute for column `cname` of table `tname`, or nil when the table has no such column (the caller then falls back to `(keyword tname cname)`, preserving NULL-for-unknown-column).
(canonical-table ci tname)The stored name for tname, or tname when nothing else claims it.
Returns ::ambiguous when two stored names fold together.
The stored name for `tname`, or `tname` when nothing else claims it. Returns ::ambiguous when two stored names fold together.
(ci-index schema)(ci-index schema hints)A case-folding index over the schema:
{:tables {folded-name -> real-name} :columns {real-table {folded-col -> attr}}}.
PostgreSQL folds unquoted identifiers, so after that fold a reference
carries the lower-cased name. Storage may not: a database created
before folding landed holds :MixedCase/ColA, and a Datalog-native
database — the serve-an-existing-Datahike-db case —
holds whatever its attributes were named, typically camelCase like
:person/firstName. This index is what lets a folded reference reach
either.
A folded name claimed by two different storage names maps to ::ambiguous rather than picking one; the caller raises 42702 (column) or 42P01 (table). Guessing there would silently read or write the wrong column.
Derived from the already-memoised derive-virtual-tables result, so
it costs one pass over that map and inherits its caching. Its :attrs
already has :datahike.pg/column renames applied, so hint-renamed
columns get covered for free.
A case-folding index over the schema:
`{:tables {folded-name -> real-name} :columns {real-table {folded-col -> attr}}}`.
PostgreSQL folds unquoted identifiers, so after that fold a reference
carries the lower-cased name. Storage may not: a database created
before folding landed holds `:MixedCase/ColA`, and a Datalog-native
database — the serve-an-existing-Datahike-db case —
holds whatever its attributes were named, typically camelCase like
`:person/firstName`. This index is what lets a folded reference reach
either.
A folded name claimed by two different storage names maps to
::ambiguous rather than picking one; the caller raises 42702 (column)
or 42P01 (table). Guessing there would silently read or write the
wrong column.
Derived from the already-memoised `derive-virtual-tables` result, so
it costs one pass over that map and inherits its caching. Its `:attrs`
already has `:datahike.pg/column` renames applied, so hint-renamed
columns get covered for free.(column-attnum schema table-name col-name)Return the 1-based PG attnum for a column, given the derived virtual-table map. The ordering MUST match pg_attribute row emission and RowDescription emission or pgjdbc's field-metadata JOIN (keyed on (tableOid, attnum)) won't resolve columns — so both sites call this helper.
Returns nil when the table or column isn't known.
Return the 1-based PG attnum for a column, given the derived virtual-table map. The ordering MUST match pg_attribute row emission and RowDescription emission or pgjdbc's field-metadata JOIN (keyed on (tableOid, attnum)) won't resolve columns — so both sites call this helper. Returns nil when the table or column isn't known.
(column-info schema table-name)(column-info schema table-name db)Return ordered column info for a specific table, prepending db_id. When db is provided, columns are ordered by schema entity ID (CREATE TABLE order) and :datahike.pg/* hints (column rename, hide, FK target) are applied to the derivation. Returns [{:name str :attr keyword :oid int ...} ...]
Return ordered column info for a specific table, prepending db_id.
When db is provided, columns are ordered by schema entity ID
(CREATE TABLE order) and :datahike.pg/* hints (column rename, hide,
FK target) are applied to the derivation.
Returns [{:name str :attr keyword :oid int ...} ...](column-order-from-db db table-name)Derive column creation order for a table from schema entity IDs. Returns [col-name ...] in the order attributes were transacted (CREATE TABLE order). Requires a Datahike db value. Falls back to alphabetical if db is nil.
Derive column creation order for a table from schema entity IDs. Returns [col-name ...] in the order attributes were transacted (CREATE TABLE order). Requires a Datahike db value. Falls back to alphabetical if db is nil.
(composite-types db)Read the user composite-type registry from db. Returns a vector of
{:name string :oid long :fields [{:field-name string :pg-type string
:oid long} …]} in field declaration order.
Read the user composite-type registry from `db`. Returns a vector of
{:name string :oid long :fields [{:field-name string :pg-type string
:oid long} …]} in field declaration order.(derive-ref-targets schema hints)For each :db.type/ref attribute, determine the target PK attribute
that SQL projection should dereference to. SELECT order.customer FROM order should return the customer's PK value (1, 2, 3) — what a real
PostgreSQL FK column stores — not the Datahike entity-id (10, 11, 12)
that the ref attribute physically holds.
Returns {ref-attr-ident → target-pk-attr-ident} (a subset of the
ref attrs in the schema; refs with no resolvable target are omitted
and fall back to projecting the raw entity-id).
Resolution order:
:datahike.pg/references K hint (authoritative).(name ref-attr) names a namespace, and the
:db.unique/identity attr in that namespace is the target.
Example: :order/customer (ref) → namespace customer →
:customer/id (the only :db.unique/identity in customer).
This matches the seed style most projects use, where ref attrs
are named after the table they point to.Both :db.cardinality/one AND :db.cardinality/many refs are
included. The result map's value is a target-pk-attr for the
one-cardinality case, OR a vector [target-pk-attr :many] for
many-cardinality refs — the translator branches on the shape:
get-else that derefs to a single
target-PK value (matching real PG INT FK columns)fns/pg-many-ref-array)
that boxes all target PKs into a PgArray (matching
int8[] columns)Pure schema-side derivation; for runtime data validation against the
actual entities a ref points to (polymorphism detection,
namespace-mismatch detection), see validate-ref-targets!.
Memoized — schema is value-typed and typically stable per conn, so recomputing per query is wasted work.
For each `:db.type/ref` attribute, determine the *target PK attribute*
that SQL projection should dereference to. `SELECT order.customer FROM
order` should return the customer's PK value (1, 2, 3) — what a real
PostgreSQL FK column stores — not the Datahike entity-id (10, 11, 12)
that the ref attribute physically holds.
Returns `{ref-attr-ident → target-pk-attr-ident}` (a subset of the
ref attrs in the schema; refs with no resolvable target are omitted
and fall back to projecting the raw entity-id).
Resolution order:
1. Explicit `:datahike.pg/references K` hint (authoritative).
2. Convention: `(name ref-attr)` names a namespace, and the
`:db.unique/identity` attr in that namespace is the target.
Example: `:order/customer` (ref) → namespace `customer` →
`:customer/id` (the only `:db.unique/identity` in `customer`).
This matches the seed style most projects use, where ref attrs
are named after the table they point to.
Both `:db.cardinality/one` AND `:db.cardinality/many` refs are
included. The result map's value is a `target-pk-attr` for the
one-cardinality case, OR a vector `[target-pk-attr :many]` for
many-cardinality refs — the translator branches on the shape:
- one → emit a chained `get-else` that derefs to a single
target-PK value (matching real PG INT FK columns)
- many → emit a per-row Clojure fn (`fns/pg-many-ref-array`)
that boxes all target PKs into a PgArray (matching
`int8[]` columns)
Pure schema-side derivation; for runtime data validation against the
actual entities a ref points to (polymorphism detection,
namespace-mismatch detection), see `validate-ref-targets!`.
Memoized — schema is value-typed and typically stable per conn, so
recomputing per query is wasted work.(derive-virtual-tables schema)(derive-virtual-tables schema hints)Derive virtual table definitions from a Datahike schema map.
Returns a map of table-name → {:columns [...] :attrs {...}} where each column is {:name str :attr keyword :oid int :valuetype keyword :cardinality keyword :unique keyword-or-nil :ref? bool :references keyword-or-nil} and :attrs maps column-name → attribute-ident.
Filters out internal (db.*) attributes by default.
When hints is supplied (a map {attr-ident → hint-map} typically
built by schema-hints), applies per-attribute customizations:
:datahike.pg/hidden true → attribute excluded entirely:datahike.pg/column "x" → column renamed to "x":datahike.pg/references K→ carried through on the col as :references
for the translator's FK-via-ref JOIN rewrite.Each catalog table referenced in a query calls this with the same
schema/hints. When *catalog-tx-cache* is bound, the result is
memoised across all calls in the current parse-sql so the schema
walk happens once per query (not once per catalog table).
Derive virtual table definitions from a Datahike schema map.
Returns a map of table-name → {:columns [...] :attrs {...}}
where each column is {:name str :attr keyword :oid int :valuetype keyword
:cardinality keyword :unique keyword-or-nil :ref? bool
:references keyword-or-nil}
and :attrs maps column-name → attribute-ident.
Filters out internal (db.*) attributes by default.
When `hints` is supplied (a map `{attr-ident → hint-map}` typically
built by `schema-hints`), applies per-attribute customizations:
- `:datahike.pg/hidden true` → attribute excluded entirely
- `:datahike.pg/column "x"` → column renamed to "x"
- `:datahike.pg/references K`→ carried through on the col as `:references`
for the translator's FK-via-ref JOIN rewrite.
Each catalog table referenced in a query calls this with the same
schema/hints. When `*catalog-tx-cache*` is bound, the result is
memoised across all calls in the current parse-sql so the schema
walk happens once per query (not once per catalog table).(ensure-hint-schema! conn)Idempotently install the :datahike.pg/* hint schema attrs on conn.
Safe to call multiple times. Called automatically by set-hint! and
by the server's ensure-pg-schema!. Exposed as public so callers
running under :schema-flexibility :read can prime the schema
before their first set-hint! or eager hint transaction.
Idempotently install the :datahike.pg/* hint schema attrs on `conn`. Safe to call multiple times. Called automatically by `set-hint!` and by the server's `ensure-pg-schema!`. Exposed as public so callers running under `:schema-flexibility :read` can prime the schema before their first `set-hint!` or eager hint transaction.
(field-type->oid pg-type)Map a composite/field SQL type string (e.g. "int", "text", "int[]", "numeric(10,2)") to its PG type OID. Arrays map to the element's array OID. Unknown scalars fall back to text.
Map a composite/field SQL type string (e.g. "int", "text", "int[]", "numeric(10,2)") to its PG type OID. Arrays map to the element's array OID. Unknown scalars fall back to text.
Datahike schema for the :datahike.pg/* hint entities. A hint is a dedicated entity (NOT attributes attached to the ident) that points at its target via :datahike.pg/for-ident. This sidesteps Datahike's schema-update guard, which forbids adding arbitrary attrs to an already-transacted schema entity — a necessary limit on real schema migration but one that makes ident-attached annotations impractical.
ensure-pg-schema! in server.clj installs this for every handler-
backed conn. Users can also transact it eagerly under
:schema-flexibility :read.
Datahike schema for the :datahike.pg/* hint entities. A hint is a dedicated entity (NOT attributes attached to the ident) that points at its target via :datahike.pg/for-ident. This sidesteps Datahike's schema-update guard, which forbids adding arbitrary attrs to an already-transacted schema entity — a necessary limit on real schema migration but one that makes ident-attached annotations impractical. `ensure-pg-schema!` in server.clj installs this for every handler- backed conn. Users can also transact it eagerly under `:schema-flexibility :read`.
(information-schema-columns-rows schema)(information-schema-columns-rows schema db-or-hints)Return rows for information_schema.columns query. Each row: [table_catalog table_schema table_name column_name ordinal_position data_type is_nullable column_default].
Accepts an optional db (or hints map) so :datahike.pg/column /
:datahike.pg/hidden are applied to the emitted row set. Callers that
don't have a db in scope can keep the 1-arity form.
Return rows for information_schema.columns query.
Each row: [table_catalog table_schema table_name column_name ordinal_position
data_type is_nullable column_default].
Accepts an optional `db` (or `hints` map) so :datahike.pg/column /
:datahike.pg/hidden are applied to the emitted row set. Callers that
don't have a db in scope can keep the 1-arity form.(make-catalog-tx-cache)Construct a fresh per-query cache. Caller bindings
catalog-tx-cache to the result around their unit of work.
Construct a fresh per-query cache. Caller `binding`s *catalog-tx-cache* to the result around their unit of work.
(next-composite-oid db)Next unused OID for a user composite type. Shares the user-OID space with tables (PG OIDs are global) so a composite and a table never collide.
Next unused OID for a user composite type. Shares the user-OID space with tables (PG OIDs are global) so a composite and a table never collide.
(next-table-oid db)Pick the next unused :pg/table-oid. We query via Datalog because the attribute is attached to the row-marker entity, not part of the :schema map.
Pick the next unused :pg/table-oid. We query via Datalog because the attribute is attached to the row-marker entity, not part of the :schema map.
(oid-for-valuetype vtype)Return the PostgreSQL type OID for a Datahike valueType keyword.
Return the PostgreSQL type OID for a Datahike valueType keyword.
(pg-type-name vtype)Return the PostgreSQL type name string for a Datahike valueType keyword.
Return the PostgreSQL type name string for a Datahike valueType keyword.
(ref-attrs-from-schema schema)Return the seq of :db.type/ref :db.cardinality/one attribute idents
in schema. Excludes internal namespaces.
Return the seq of `:db.type/ref :db.cardinality/one` attribute idents in `schema`. Excludes internal namespaces.
(ref-attrs-from-schema-all-cards schema)Like ref-attrs-from-schema but returns BOTH cardinality/one and
cardinality/many ref attrs as {ref-attr cardinality-keyword}.
Used by validate-ref-targets! to infer M2M projection targets
from data when the schema-only convention misses (e.g.
:account/tags → :account-tag — local name tags doesn't
match the hyphenated target namespace).
Like `ref-attrs-from-schema` but returns BOTH cardinality/one and
cardinality/many ref attrs as `{ref-attr cardinality-keyword}`.
Used by `validate-ref-targets!` to infer M2M projection targets
from data when the schema-only convention misses (e.g.
`:account/tags` → `:account-tag` — local name `tags` doesn't
match the hyphenated target namespace).(row-marker-attr table-name)Return the row-existence marker attribute for a table. E.g., :person/db-row-exists for table 'person'.
Return the row-existence marker attribute for a table. E.g., :person/db-row-exists for table 'person'.
(schema-hints db)Return {attr-ident → {:column str? :hidden bool? :references kw? :table str?}}
by scanning the db for :datahike.pg/for-ident-rooted hint entities.
Nil-safe: returns an empty map when db is nil (pure-schema call sites).
When *catalog-tx-cache* is bound (within parse-sql's catalog
enrichment), reuses the result across all catalog-data-for* calls
in the current query.
Return `{attr-ident → {:column str? :hidden bool? :references kw? :table str?}}`
by scanning the db for :datahike.pg/for-ident-rooted hint entities.
Nil-safe: returns an empty map when `db` is nil (pure-schema call sites).
When `*catalog-tx-cache*` is bound (within parse-sql's catalog
enrichment), reuses the result across all `catalog-data-for*` calls
in the current query.(set-hint! conn target hint)Convenience: transact or upsert one hint entity. target is the
attribute ident the hint applies to (e.g. :widget/full_name).
hint is a map of :column / :hidden / :references / :table.
Idempotent — :datahike.pg/for-ident is :db.unique/identity, so
repeated calls upsert the same hint entity. Ensures the hint schema
is installed first so callers needn't pre-install.
Convenience: transact or upsert one hint entity. `target` is the attribute ident the hint applies to (e.g. `:widget/full_name`). `hint` is a map of `:column` / `:hidden` / `:references` / `:table`. Idempotent — `:datahike.pg/for-ident` is :db.unique/identity, so repeated calls upsert the same hint entity. Ensures the hint schema is installed first so callers needn't pre-install.
(table-names schema)Return sorted list of virtual table names for a schema.
Return sorted list of virtual table names for a schema.
(table-oid db table-name)The :pg/table-oid attached to this table's row-marker entity, or nil
if the table doesn't exist yet or was created before we started
allocating OIDs. Datahike's (:schema db) only surfaces schema-level
attrs (:db/valueType etc.); custom attrs on the ident entity require
a Datalog lookup against the db.
The :pg/table-oid attached to this table's row-marker entity, or nil if the table doesn't exist yet or was created before we started allocating OIDs. Datahike's `(:schema db)` only surfaces schema-level attrs (:db/valueType etc.); custom attrs on the ident entity require a Datalog lookup against the db.
(validate-ref-targets! db schema ref-targets)Cross-check ref-targets (from derive-ref-targets) against the
actual data in db, AND infer targets for ref attrs that the
schema-only convention missed.
Validation drops:
:datahike.pg/references hint per polymorphic ref.:X but
named the ref attr after a different concept (:order/buyer ref
to customer). Hint should override.Inference (for ref attrs not in the input ref-targets, both card one and card many):
:db.unique/identity attr, use it as the target. This
catches plural / hyphen-named M2M cases the convention misses
(e.g. :account/tags → :account-tag/name, where local name
tags doesn't equal the target namespace account-tag).[target :many]
marker so the translator emits a PgArray projection.Each drop emits a one-shot stderr warning. Returns the validated + inferred ref-targets. Refs with no current data and no convention match warn the user to set a hint.
Cross-check `ref-targets` (from `derive-ref-targets`) against the actual data in `db`, AND infer targets for ref attrs that the schema-only convention missed. Validation drops: - polymorphic refs — entities span multiple namespaces; convention can't pick a single target safely. User must add an explicit `:datahike.pg/references` hint per polymorphic ref. - namespace-mismatched refs — convention picked target X but the data points to namespace Y. Often means the user intended `:X` but named the ref attr after a different concept (`:order/buyer` ref to `customer`). Hint should override. Inference (for ref attrs *not* in the input ref-targets, both card one and card many): - If the data points to exactly one namespace AND that namespace has a `:db.unique/identity` attr, use it as the target. This catches plural / hyphen-named M2M cases the convention misses (e.g. `:account/tags` → `:account-tag/name`, where local name `tags` doesn't equal the target namespace `account-tag`). - Cardinality is preserved: many-refs get the `[target :many]` marker so the translator emits a PgArray projection. Each drop emits a one-shot stderr warning. Returns the validated + inferred ref-targets. Refs with no current data and no convention match warn the user to set a hint.
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 |