Parse-time type inference for SELECT-item expressions.
Mirrors PostgreSQL's exprType (src/backend/nodes/nodeFuncs.c): each
node in the expression tree has a declared result OID, computed by
walking the AST during parse/analyze. PG looks up operator and
function signatures in pg_operator / pg_proc; we hardcode the
equivalent rules for the set of SQL we handle.
Consumed by translate-select to populate :select-item-oids on the
parsed map, which describeResult reads to emit correct
RowDescription OIDs via the Extended Query protocol — before Execute
runs, so value-based inference isn't available.
Return values:
nil when we can't determine it (unknown function, unresolvable
column, subquery, etc.) — callers fall back to TEXT (OID 25),
matching the pre-existing behavior.Parse-time type inference for SELECT-item expressions.
Mirrors PostgreSQL's `exprType` (src/backend/nodes/nodeFuncs.c): each
node in the expression tree has a declared result OID, computed by
walking the AST during parse/analyze. PG looks up operator and
function signatures in `pg_operator` / `pg_proc`; we hardcode the
equivalent rules for the set of SQL we handle.
Consumed by `translate-select` to populate `:select-item-oids` on the
parsed map, which `describeResult` reads to emit correct
RowDescription OIDs via the Extended Query protocol — before Execute
runs, so value-based inference isn't available.
Return values:
- A positive integer OID when the type is determined.
- `nil` when we can't determine it (unknown function, unresolvable
column, subquery, etc.) — callers fall back to TEXT (OID 25),
matching the pre-existing behavior.(expr-oid expr env)Return the inferred PG OID for a JSqlParser expression, or nil if we can't determine it.
env carries the live db and schema:
{:db <datahike db>
:schema <schema map>
:table-aliases {alias → real-table-name}
:default-table <string or nil>
:hints <schema-hint map or nil>}
Return the inferred PG OID for a JSqlParser expression, or nil if we
can't determine it.
`env` carries the live db and schema:
{:db <datahike db>
:schema <schema map>
:table-aliases {alias → real-table-name}
:default-table <string or nil>
:hints <schema-hint map or nil>}(param-placeholder-index expr)The 1-based $N index when expr is a bare parameter placeholder,
else nil.
Only a bare placeholder qualifies. $1::int4 is a CastExpression
whose type cast-oid already resolves, and $1 + 1 gets its type
from the arithmetic rule — in both cases PG takes the type from the
expression, not from the parameter declaration, so reporting an index
there would let a declared OID override a correctly inferred one.
Parenthesised placeholders are unwrapped: PG's exprType sees through
parens, so SELECT ($1) types the same as SELECT $1. JSqlParser
spells a parenthesised expression two ways — Parenthesis, and
ParenthesedExpressionList (a one-element list) for a select item —
so both are unwrapped, repeatedly for SELECT (($1)).
The 1-based `$N` index when `expr` is a bare parameter placeholder, else nil. Only a *bare* placeholder qualifies. `$1::int4` is a CastExpression whose type `cast-oid` already resolves, and `$1 + 1` gets its type from the arithmetic rule — in both cases PG takes the type from the expression, not from the parameter declaration, so reporting an index there would let a declared OID override a correctly inferred one. Parenthesised placeholders are unwrapped: PG's exprType sees through parens, so `SELECT ($1)` types the same as `SELECT $1`. JSqlParser spells a parenthesised expression two ways — `Parenthesis`, and `ParenthesedExpressionList` (a one-element list) for a select item — so both are unwrapped, repeatedly for `SELECT (($1))`.
(resolve-aggregate-result-oid agg-name input-oid)Given an aggregate name and the OID of its first argument, return
the result OID per sql-aggregate->return-oid. Returns nil for
unknown aggregates or unresolvable rules (caller falls back).
Public so the translator can derive the runtime fn variant from the same rule the OID inference uses — avoids drift between describeResult's reported type and the actual runtime.
Given an aggregate name and the OID of its first argument, return the result OID per `sql-aggregate->return-oid`. Returns nil for unknown aggregates or unresolvable rules (caller falls back). Public so the translator can derive the runtime fn variant from the same rule the OID inference uses — avoids drift between describeResult's reported type and the actual runtime.
Aggregate function → return-OID rule. Three rule shapes:
integer (an OID): always returns this OID, regardless of input. Used for COUNT (always INT8) and the variance/correlation family (always FLOAT8).
:arg-type: returns the OID of the first argument. Used for
MIN/MAX where the result-type tracks the input.
map {<input-oid> <output-oid> … :default <fallback>}:
resolved by looking up the first argument's OID. Used for SUM/AVG
to match PG's pg_proc.dat per-input-type return rules:
AVG(int2|int4|int8|numeric) → numeric (PG) AVG(float4|float8) → float8 SUM(int2|int4) → int8 SUM(int8|numeric) → numeric (overflow-safe) SUM(float4) → float4 SUM(float8) → float8
PG promotes integer SUMs to int8/numeric to prevent overflow, and AVG(int) to numeric to preserve fractional precision. Without these per-input rules, AVG(total_cents) renders as a truncated INT8 in Metabase even though the runtime returns a Double, and SUM(int8) silently overflows when the sum exceeds Long/MAX_VALUE.
Aggregate function → return-OID rule. Three rule shapes:
- integer (an OID): always returns this OID, regardless of input.
Used for COUNT (always INT8) and the variance/correlation family
(always FLOAT8).
- `:arg-type`: returns the OID of the first argument. Used for
MIN/MAX where the result-type tracks the input.
- map `{<input-oid> <output-oid> … :default <fallback>}`:
resolved by looking up the first argument's OID. Used for SUM/AVG
to match PG's `pg_proc.dat` per-input-type return rules:
AVG(int2|int4|int8|numeric) → numeric (PG)
AVG(float4|float8) → float8
SUM(int2|int4) → int8
SUM(int8|numeric) → numeric (overflow-safe)
SUM(float4) → float4
SUM(float8) → float8
PG promotes integer SUMs to int8/numeric to prevent overflow,
and AVG(int) to numeric to preserve fractional precision. Without
these per-input rules, AVG(total_cents) renders as a truncated
INT8 in Metabase even though the runtime returns a Double, and
SUM(int8) silently overflows when the sum exceeds Long/MAX_VALUE.Scalar function → return OID. Entries either a fixed OID or a keyword
dispatched in function-oid for arg-dependent return types.
Scalar function → return OID. Entries either a fixed OID or a keyword dispatched in `function-oid` for arg-dependent return types.
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 |