Token-driven SQL source rewrites. Normalize SQL before JSqlParser sees it by excising or injecting source-level spans — all based on positions captured by the datahike.pg.classify tokenizer.
Each rule is a pure function (tokens) -> seq of spans, where a
span is [start end replacement]. The rewriter applies all non-
overlapping spans right-to-left (so earlier offsets stay stable)
and returns the new SQL string.
Why this exists: the previous preprocess-sql was a pile of regex
str/replace calls that could false-positive on keywords inside
string literals, dollar-quotes, or comments (SELECT 'REFERENCES'
was vulnerable to the inline-REFERENCES stripper). Token-based
rules only match tokens of the right kind, so a keyword inside a
:string or :comment is invisible to them.
Callers: sql/preprocess-sql.
Token-driven SQL source rewrites. Normalize SQL before JSqlParser sees it by excising or injecting source-level spans — all based on positions captured by the datahike.pg.classify tokenizer. Each rule is a pure function `(tokens) -> seq of spans`, where a span is `[start end replacement]`. The rewriter applies all non- overlapping spans right-to-left (so earlier offsets stay stable) and returns the new SQL string. Why this exists: the previous preprocess-sql was a pile of regex `str/replace` calls that could false-positive on keywords inside string literals, dollar-quotes, or comments (`SELECT 'REFERENCES'` was vulnerable to the inline-REFERENCES stripper). Token-based rules only match tokens of the right kind, so a keyword inside a :string or :comment is invisible to them. Callers: sql/preprocess-sql.
(create-index-anonymous-rule toks)CREATE [UNIQUE] INDEX ON … → inject idx_auto_<N> between INDEX
and ON. PG allows unnamed indexes; JSqlParser doesn't. The counter
is process-wide and monotonic; collisions across handler sessions
are harmless because the name is thrown away by the :create-index
no-op handler anyway.
`CREATE [UNIQUE] INDEX ON …` → inject `idx_auto_<N>` between INDEX and ON. PG allows unnamed indexes; JSqlParser doesn't. The counter is process-wide and monotonic; collisions across handler sessions are harmless because the name is thrown away by the :create-index no-op handler anyway.
Rules replacing the most-error-prone regex replacements in the old preprocess-sql. Others (reserved-word quoting, ALTER TABLE TYPE USING stripping, complex DEFAULT paren-peel, ALTER COLUMN DROP DEFAULT) remain as regex in sql.clj for now — they're narrow enough that the regex is low-risk. Migrate them incrementally as needed.
Rules replacing the most-error-prone regex replacements in the old preprocess-sql. Others (reserved-word quoting, ALTER TABLE TYPE USING stripping, complex DEFAULT paren-peel, ALTER COLUMN DROP DEFAULT) remain as regex in sql.clj for now — they're narrow enough that the regex is low-risk. Migrate them incrementally as needed.
(inline-references-rule toks)Inline col TYPE … REFERENCES name [(cols)] [ON (DELETE|UPDATE) action].
JSqlParser doesn't accept the inline form, so we rewrite it. Two paths:
No action / RESTRICT / no action clause — just strip the
REFERENCES … span. Our existing FK plumbing only enforces
table-level FOREIGN KEY (col) REFERENCES …, and NO ACTION /
RESTRICT have no operational consequence beyond blocking the
parent delete (which we then can't enforce, but Odoo's
_auto_init flow doesn't depend on it).
CASCADE on DELETE — lift to a table-level FOREIGN KEY so
our FK plumbing tracks it and enforces cascade at runtime.
We strip the inline span AND inject a synthetic
, FOREIGN KEY (col) REFERENCES name(cols) ON DELETE CASCADE
just before the closing ) of the CREATE TABLE column list.
SET NULL / SET DEFAULT / ON UPDATE non-trivial — raise 0A000; not yet implemented at the runtime side.
Distinguishes inline from table-level by checking the previous
non-comment token: if it's ) (from FOREIGN KEY (col)), we
leave the whole REFERENCES alone so JSqlParser parses it natively
as a ForeignKeyIndex.
Inline `col TYPE … REFERENCES name [(cols)] [ON (DELETE|UPDATE) action]`. JSqlParser doesn't accept the inline form, so we rewrite it. Two paths: 1. **No action / RESTRICT / no action clause** — just strip the `REFERENCES …` span. Our existing FK plumbing only enforces table-level `FOREIGN KEY (col) REFERENCES …`, and NO ACTION / RESTRICT have no operational consequence beyond blocking the parent delete (which we then can't enforce, but Odoo's _auto_init flow doesn't depend on it). 2. **CASCADE on DELETE** — lift to a table-level `FOREIGN KEY` so our FK plumbing tracks it and enforces cascade at runtime. We strip the inline span AND inject a synthetic `, FOREIGN KEY (col) REFERENCES name(cols) ON DELETE CASCADE` just before the closing `)` of the CREATE TABLE column list. 3. **SET NULL / SET DEFAULT / ON UPDATE non-trivial** — raise 0A000; not yet implemented at the runtime side. Distinguishes inline from table-level by checking the previous non-comment token: if it's `)` (from `FOREIGN KEY (col)`), we leave the whole REFERENCES alone so JSqlParser parses it natively as a ForeignKeyIndex.
(quote-reserved-alias-rule toks)Find AS <reserved-kw> outside of CAST(... AS ...) contexts and
replace <reserved-kw> with "<reserved-kw>" so JSqlParser
accepts it as an identifier. PG already treats the two forms
equivalently (both produce the same column label).
Find `AS <reserved-kw>` outside of `CAST(... AS ...)` contexts and replace `<reserved-kw>` with `"<reserved-kw>"` so JSqlParser accepts it as an identifier. PG already treats the two forms equivalently (both produce the same column label).
(rewrite sql rules)Apply rules to sql and return the rewritten string.
Each rule is a (tokens) -> seq of [start end replacement] fn.
Throws exceptions from rules upward (callers rely on this for
unsupported-feature detection — e.g. FK ON DELETE CASCADE).
Apply rules to sql and return the rewritten string. Each rule is a `(tokens) -> seq of [start end replacement]` fn. Throws exceptions from rules upward (callers rely on this for unsupported-feature detection — e.g. FK ON DELETE CASCADE).
(select-from-rule toks)SELECT FROM … (empty projection) → SELECT 1 FROM …. PG allows
projection-less SELECT in EXISTS subqueries; JSqlParser doesn't.
`SELECT FROM …` (empty projection) → `SELECT 1 FROM …`. PG allows projection-less SELECT in EXISTS subqueries; JSqlParser doesn't.
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 |