Statement-level translation: SELECT / INSERT / UPDATE / DELETE / CTE.
The top half of this namespace (translate-select + its join / HAVING / materialization machinery) maps a PlainSelect AST to a Datalog query-map plus row-formatting metadata the handler uses at execute time.
The middle half handles INSERT / UPDATE / DELETE: extract-value
lifts JSqlParser literal/expression nodes into Clojure values,
coerce-insert-value adapts them to the target column's
:db/valueType, and translate-insert / translate-update /
translate-delete produce tx-data + (for UPDATE/DELETE) an
eids-walk query. INSERT RETURNING and UPDATE RETURNING land in
extract-returning; CHECK / UPDATE expressions evaluated
per-row at handler time go through eval-check-predicate /
eval-update-expr.
The bottom half implements CTEs: translate-cte-branch
materializes one WITH-clause body against an enriched db so the
outer SELECT / DML can reference it as a virtual table;
translate-recursive-cte handles the WITH RECURSIVE form by
iterating until the CTE converges.
The three blocks live in one namespace because they are mutually recursive:
translate-select → translate-recursive-cte (WITH in SELECT) translate-recursive-cte → translate-select (CTE body) translate-insert → translate-select (INSERT ... SELECT) translate-insert → translate-recursive-cte (INSERT ... WITH) translate-select → extract-value (scalar subqueries)
Dependencies on already-extracted namespaces are all one-way:
stmt → expr (translate-expr, translate-predicate, …) stmt → ctx (make-ctx, col-var!, resolve-column, …) stmt → fns (aggregate lookups) stmt → params (ParamRef, from-bindings, parse-db) stmt → jsonb, schema, types (type coercion + jsonb ops)
Statement-level translation: SELECT / INSERT / UPDATE / DELETE / CTE. The top half of this namespace (translate-select + its join / HAVING / materialization machinery) maps a PlainSelect AST to a Datalog query-map plus row-formatting metadata the handler uses at execute time. The middle half handles INSERT / UPDATE / DELETE: `extract-value` lifts JSqlParser literal/expression nodes into Clojure values, `coerce-insert-value` adapts them to the target column's :db/valueType, and `translate-insert` / `translate-update` / `translate-delete` produce tx-data + (for UPDATE/DELETE) an eids-walk query. INSERT RETURNING and UPDATE RETURNING land in `extract-returning`; CHECK / UPDATE expressions evaluated per-row at handler time go through `eval-check-predicate` / `eval-update-expr`. The bottom half implements CTEs: `translate-cte-branch` materializes one WITH-clause body against an enriched db so the outer SELECT / DML can reference it as a virtual table; `translate-recursive-cte` handles the WITH RECURSIVE form by iterating until the CTE converges. The three blocks live in one namespace because they are mutually recursive: translate-select → translate-recursive-cte (WITH in SELECT) translate-recursive-cte → translate-select (CTE body) translate-insert → translate-select (INSERT ... SELECT) translate-insert → translate-recursive-cte (INSERT ... WITH) translate-select → extract-value (scalar subqueries) Dependencies on already-extracted namespaces are all one-way: stmt → expr (translate-expr, translate-predicate, …) stmt → ctx (make-ctx, col-var!, resolve-column, …) stmt → fns (aggregate lookups) stmt → params (ParamRef, *from-bindings*, *parse-db*) stmt → jsonb, schema, types (type coercion + jsonb ops)
(apply-sql-cast inner ce)Apply a SQL CAST to a value. Returns the value cast to the target type.
Handles nil safely (returns nil). Idempotent: if inner is already the
target type, returns it unchanged (avoids lossy round-trips through str,
e.g. Date → (str d) → unparseable string).
Apply a SQL CAST to a value. Returns the value cast to the target type. Handles nil safely (returns nil). Idempotent: if `inner` is already the target type, returns it unchanged (avoids lossy round-trips through str, e.g. Date → `(str d)` → unparseable string).
(coerce-insert-value val attr schema)Coerce a value to match the schema type for an attribute.
Coerce a value to match the schema type for an attribute.
(eval-check-predicate expr entity-map ns-str schema)Evaluate a CHECK-style JSqlParser Expression against an entity map and return a tri-state: true (satisfied), false (violation), or nil (unknown — PG treats as satisfied). This is distinct from eval-update-expr which returns the arithmetic value of the expression; predicates need comparison / logical ops that only make sense at enforcement time.
Column refs resolve via eval-update-expr so any entity-map-aware coercion (namespace-qualified lookups) stays consistent between SET-value evaluation and CHECK evaluation.
Evaluate a CHECK-style JSqlParser Expression against an entity map and return a tri-state: true (satisfied), false (violation), or nil (unknown — PG treats as satisfied). This is distinct from eval-update-expr which returns the arithmetic value of the expression; predicates need comparison / logical ops that only make sense at enforcement time. Column refs resolve via eval-update-expr so any entity-map-aware coercion (namespace-qualified lookups) stays consistent between SET-value evaluation and CHECK evaluation.
(eval-update-expr value-expr entity-map ns-str schema)Evaluate an UPDATE SET expression for a specific entity. For simple literals, returns the literal value. For expressions (col + 1, col * 2), evaluates against the entity's current values.
JdbcParameter placeholders return a ParamRef — the tx-build step runs substitute-params once bound values are available from Bind.
Evaluate an UPDATE SET expression for a specific entity. For simple literals, returns the literal value. For expressions (col + 1, col * 2), evaluates against the entity's current values. JdbcParameter placeholders return a ParamRef — the tx-build step runs substitute-params once bound values are available from Bind.
(eval-values-literal expr)Evaluate a literal JSqlParser expression from a VALUES row. Handles simple literals, casts, and parenthesis; returns the raw value. Anything else returns :unhandled — the caller decides whether to fall back.
A JdbcParameter returns a ParamRef that the wire layer resolves at Bind time, allowing prepared INSERTs / UPDATE FROM VALUES with ?/$N.
Evaluate a literal JSqlParser expression from a VALUES row. Handles simple literals, casts, and parenthesis; returns the raw value. Anything else returns :unhandled — the caller decides whether to fall back. A JdbcParameter returns a ParamRef that the wire layer resolves at Bind time, allowing prepared INSERTs / UPDATE FROM VALUES with ?/$N.
(extract-from-values update)If an UPDATE's FROM clause is (VALUES (...), (...)) AS alias(col1, col2, ...),
extract it. Returns {:alias str :cols [str] :rows [[literal ...] ...]} or nil.
If an UPDATE's FROM clause is `(VALUES (...), (...)) AS alias(col1, col2, ...)`,
extract it. Returns {:alias str :cols [str] :rows [[literal ...] ...]} or nil.(extract-returning returning-clause)Extract RETURNING clause column names from a ReturningClause. Returns nil if no RETURNING, :* for RETURNING *, or [col-name ...] for specific columns.
Extract RETURNING clause column names from a ReturningClause. Returns nil if no RETURNING, :* for RETURNING *, or [col-name ...] for specific columns.
(extract-value e)(extract-value e schema db)Extract a Clojure value from a JSqlParser expression for INSERT VALUES. Optional schema+db params enable scalar subquery evaluation.
When the expression is a JdbcParameter (prepared-statement placeholder), returns a ParamRef that the wire layer resolves at Bind time against the decoded client value.
Extract a Clojure value from a JSqlParser expression for INSERT VALUES. Optional schema+db params enable scalar subquery evaluation. When the expression is a JdbcParameter (prepared-statement placeholder), returns a ParamRef that the wire layer resolves at Bind time against the decoded client value.
(join-type join)Determine the type of a JOIN: :inner, :left, :right, :full, or :cross. LEFT JOIN and LEFT OUTER JOIN are both :left (JSqlParser may or may not set isOuter).
Determine the type of a JOIN: :inner, :left, :right, :full, or :cross. LEFT JOIN and LEFT OUTER JOIN are both :left (JSqlParser may or may not set isOuter).
(match-aggregate-index f find-elems find-aliases)Try to find the index of an aggregate function in the find-elements. For COUNT(*) → look for (count ?x), for SUM(col) → (sum ?x) or (datahike.pg.sql/filter-sum ?x). Returns the 0-based index or nil.
Matches both the raw Datalog aggregate symbol and our ns-qualified null-filtering variant (filter-sum/avg/min/max/count[-distinct]) so HAVING clauses resolve regardless of which variant the SELECT projection emitted.
Try to find the index of an aggregate function in the find-elements. For COUNT(*) → look for (count ?x), for SUM(col) → (sum ?x) or (datahike.pg.sql/filter-sum ?x). Returns the 0-based index or nil. Matches both the raw Datalog aggregate symbol and our ns-qualified null-filtering variant (filter-sum/avg/min/max/count[-distinct]) so HAVING clauses resolve regardless of which variant the SELECT projection emitted.
(materialize-derived-select! ps db schema)Given a ParenthesedSelect in FROM/JOIN position, return an enriched db that has a virtual table populated with the subquery's results.
Handles two inner shapes:
(SELECT … FROM <table-or-derived> …) — runs the inner select
against db and materializes its rows (existing behaviour).(SELECT * FROM table_function(...) [WITH ORDINALITY]) — expands
the table function directly without running a query.Returns {:db spec-db :name sub-name :alias sub-alias :aliases cols} or nil if the shape isn't recognised.
Given a ParenthesedSelect in FROM/JOIN position, return an enriched db
that has a virtual table populated with the subquery's results.
Handles two inner shapes:
1. `(SELECT … FROM <table-or-derived> …)` — runs the inner select
against `db` and materializes its rows (existing behaviour).
2. `(SELECT * FROM table_function(...) [WITH ORDINALITY])` — expands
the table function directly without running a query.
Returns {:db spec-db :name sub-name :alias sub-alias :aliases cols}
or nil if the shape isn't recognised.(materialize-table-function tf)Produce rows for a TableFunction FROM item. Currently supports
unnest(ARRAY[…]) with optional WITH ORDINALITY.
Returns {:aliases [col-names] :rows [[v1 v2 …] …] :vtypes [kw kw …]} or nil if the function isn't one we know how to expand.
For unnest(ARRAY[v1, v2, v3]) WITH ORDINALITY:
:aliases = ["unnest" "ordinality"]
:rows = [[v1 1] [v2 2] [v3 3]]
:vtypes = inferred per value
Produce rows for a `TableFunction` FROM item. Currently supports
`unnest(ARRAY[…])` with optional `WITH ORDINALITY`.
Returns {:aliases [col-names] :rows [[v1 v2 …] …] :vtypes [kw kw …]}
or nil if the function isn't one we know how to expand.
For `unnest(ARRAY[v1, v2, v3]) WITH ORDINALITY`:
:aliases = ["unnest" "ordinality"]
:rows = [[v1 1] [v2 2] [v3 3]]
:vtypes = inferred per value(parse-bytea-hex s)Decode a PostgreSQL bytea hex-format literal (\xDEADBEEF) to a byte array.
Accepts both \x... and \\x... prefixes (JDBC/psycopg2 escape variants).
Returns nil for values that don't look like hex bytea literals.
Decode a PostgreSQL bytea hex-format literal (`\xDEADBEEF`) to a byte array. Accepts both `\x...` and `\\x...` prefixes (JDBC/psycopg2 escape variants). Returns nil for values that don't look like hex bytea literals.
(select-item-alias item)(translate-cte-branch ps
cte-name
col-names
rule-vars
rule-name
schema
db
virtual-cte-schema)Translate one branch of a recursive CTE (anchor or recursive PlainSelect) into Datalog rule body clauses.
Returns a vector of clauses for use as a rule body, with the SELECT items bound to the rule output vars.
Translate one branch of a recursive CTE (anchor or recursive PlainSelect) into Datalog rule body clauses. - cte-name: the CTE's name (e.g. "__parent_store_compute") - col-names: CTE column names in order (e.g. ["id" "parent_path"]) - rule-vars: corresponding rule output vars (e.g. [?id ?parent_path]) - rule-name: the rule's name as a symbol (for self-references) - schema, db: from the outer context - virtual-cte-schema: if non-nil, the CTE is referenceable as a virtual table in this branch (for the recursive branch only). Returns a vector of clauses for use as a rule body, with the SELECT items bound to the rule output vars.
(translate-delete delete _schema)Translate a DELETE statement to Datahike retraction query + tx-data.
Translate a DELETE statement to Datahike retraction query + tx-data.
(translate-having-expr expr find-elems find-aliases)Translate a HAVING expression into a Clojure predicate fn form. The result is a map {:op symbol :col-idx int :value val} for simple cases, or a nested structure for AND/OR. The server applies this as a post-filter on result tuples.
Translate a HAVING expression into a Clojure predicate fn form.
The result is a map {:op symbol :col-idx int :value val} for simple cases,
or a nested structure for AND/OR.
The server applies this as a post-filter on result tuples.(translate-insert insert schema db)Translate an INSERT statement to Datahike transaction data. Supports single-row and multi-row VALUES, with or without column list. Handles ON CONFLICT (UPSERT) via :db.fn/call for atomic execution.
Translate an INSERT statement to Datahike transaction data. Supports single-row and multi-row VALUES, with or without column list. Handles ON CONFLICT (UPSERT) via :db.fn/call for atomic execution.
(translate-join ctx join _default-table)Add join clauses for a SQL JOIN to the context. For INNER joins with ref-based ON (a.ref_col = b.db_id), unifies the ref column variable with the target entity variable. For LEFT joins, records the ref-attr and right-table alias for later or-join wrapping in translate-select. Returns {:name str :alias str :join-type keyword :ref-attr kw :left-entity-var sym}.
Add join clauses for a SQL JOIN to the context.
For INNER joins with ref-based ON (a.ref_col = b.db_id), unifies the ref
column variable with the target entity variable.
For LEFT joins, records the ref-attr and right-table alias for later
or-join wrapping in translate-select.
Returns {:name str :alias str :join-type keyword :ref-attr kw :left-entity-var sym}.(translate-recursive-cte wi schema db)Translate a WITH RECURSIVE CTE definition into a Datalog rule. Returns: {:rule [...] :rule-name sym :col-names [...] :rule-vars [...] :in-params [...] :in-args [...]}
Translate a WITH RECURSIVE CTE definition into a Datalog rule.
Returns: {:rule [...] :rule-name sym :col-names [...] :rule-vars [...]
:in-params [...] :in-args [...]}(translate-select select schema & [db])Translate a PlainSelect into a Datalog query map + metadata. Returns {:query map :find-aliases [...] :has-aggregates? bool}
Translate a PlainSelect into a Datalog query map + metadata.
Returns {:query map :find-aliases [...] :has-aggregates? bool}(translate-update update schema db)Translate an UPDATE statement to Datahike retract+assert pairs. Handles UPDATE with WITH RECURSIVE CTE — for these, the result is {:type :update-with-recursive ...} containing the rule, columns, and target table info for the server to execute.
Translate an UPDATE statement to Datahike retract+assert pairs.
Handles UPDATE with WITH RECURSIVE CTE — for these, the result is
{:type :update-with-recursive ...} containing the rule, columns,
and target table info for the server to execute.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 |