One implementation of CAST(<value> AS <type>).
The same cast semantics used to be written out four times — in
sql.clj's table-free literal fast path, in expr.clj's
translate-cast, in stmt.clj's apply-sql-cast, and in
coerce.clj's INSERT path — each a case/cond over
types/cast-category that had drifted from the others. Which copy ran
depended on the SHAPE of the expression, not on its meaning, so the
same cast could behave three ways:
29::bit(4) → literal fast path → correct (-44)::bit(12) → translate-cast → passed through as -44 '101'::bit(3)::int → nested, another path → read the digits as decimal 101, not 5
Issue #12 hit exactly this for '1'::boolean, and issue #19 hit it
again for bit. Adding a branch to one copy fixes one shape.
This namespace holds the value-level semantics — (value, type) → value. Callers keep their own surrounding logic (when to fold at
translate time, how to bind a runtime var, how to read a JSqlParser
AST node); they just stop reimplementing what a cast MEANS.
parse-timestamp is injected rather than required because the parser
lives in expr.clj, which requires this namespace's dependencies —
taking it as an argument keeps this namespace a leaf and avoids a
load cycle.
One implementation of `CAST(<value> AS <type>)`.
The same cast semantics used to be written out four times — in
`sql.clj`'s table-free literal fast path, in `expr.clj`'s
`translate-cast`, in `stmt.clj`'s `apply-sql-cast`, and in
`coerce.clj`'s INSERT path — each a `case`/`cond` over
`types/cast-category` that had drifted from the others. Which copy ran
depended on the SHAPE of the expression, not on its meaning, so the
same cast could behave three ways:
29::bit(4) → literal fast path → correct
(-44)::bit(12) → translate-cast → passed through as -44
'101'::bit(3)::int → nested, another path → read the digits as
decimal 101, not 5
Issue #12 hit exactly this for `'1'::boolean`, and issue #19 hit it
again for bit. Adding a branch to one copy fixes one shape.
This namespace holds the value-level semantics — `(value, type) →
value`. Callers keep their own surrounding logic (when to fold at
translate time, how to bind a runtime var, how to read a JSqlParser
AST node); they just stop reimplementing what a cast MEANS.
`parse-timestamp` is injected rather than required because the parser
lives in `expr.clj`, which requires this namespace's dependencies —
taking it as an argument keeps this namespace a leaf and avoids a
load cycle.(apply-numeric-typmod v p s)PostgreSQL's apply_typmod (numeric.c): round to the declared scale, then reject anything whose integer part no longer fits the declared precision.
Both halves were missing. The scale is why 123.456::numeric(10,1)
answered 123.456 instead of 123.5, and the precision is why
123456::numeric(5,2) was accepted at all -- 22003 numeric field
overflow was never raised on any path.
PostgreSQL's apply_typmod (numeric.c): round to the declared scale, then reject anything whose integer part no longer fits the declared precision. Both halves were missing. The scale is why `123.456::numeric(10,1)` answered 123.456 instead of 123.5, and the precision is why `123456::numeric(5,2)` was accepted at all -- 22003 numeric field overflow was never raised on any path.
(bad-timestamp! s tz?)The timestamp counterpart of bad-date!: PostgreSQL tells a value
whose date FIELDS are impossible (22008) from text that is not a
timestamp at all (22007), and says so with the target's own name.
Public because the INSERT coercion needs the same verdict -- writing a timestamp has to refuse exactly what casting one refuses, and by the same rule.
The timestamp counterpart of `bad-date!`: PostgreSQL tells a value whose date FIELDS are impossible (22008) from text that is not a timestamp at all (22007), and says so with the target's own name. Public because the INSERT coercion needs the same verdict -- writing a timestamp has to refuse exactly what casting one refuses, and by the same rule.
(cast-scalar v
type-str
{:keys [explicit? resolve-regclass resolve-regtype
prefer-local-datetime? src-oid]
:or {explicit? true}})Apply a SQL cast of v to the target named by type-str.
Options:
:explicit? — an explicit CAST reshapes silently; an assignment
raises instead (matters for bit width coercion).
:resolve-regclass— fn String → oid, for ::regclass.
:resolve-regtype — fn String → oid, for ::regtype.
:prefer-local-datetime? — return a LocalDateTime (microsecond
precision) rather than a Date for a timestamp
cast. See the :timestamp branch.
:src-oid — the OID of the value being cast, when the caller
knows it. Only ::text uses it, to tell a date
from a timestamp: both are java.util.Date here.
Returns v unchanged for a target this doesn't classify, which is
what every call site did before and keeps unknown types passing
through rather than erroring.
Apply a SQL cast of `v` to the target named by `type-str`.
Options:
:explicit? — an explicit CAST reshapes silently; an assignment
raises instead (matters for bit width coercion).
:resolve-regclass— fn String → oid, for `::regclass`.
:resolve-regtype — fn String → oid, for `::regtype`.
:prefer-local-datetime? — return a LocalDateTime (microsecond
precision) rather than a Date for a timestamp
cast. See the :timestamp branch.
:src-oid — the OID of the value being cast, when the caller
knows it. Only `::text` uses it, to tell a date
from a timestamp: both are java.util.Date here.
Returns `v` unchanged for a target this doesn't classify, which is
what every call site did before and keeps unknown types passing
through rather than erroring.(cast-to-bit v type-str explicit?)int / text / bit → bit(n) or bit varying(n).
An integer source keeps the RIGHTMOST n bits and sign-extends on the
left (varbit.c:1550), which is why (-44)::bit(12) is
111111010100 and not the digits of -44.
int / text / bit → bit(n) or bit varying(n). An integer source keeps the RIGHTMOST n bits and sign-extends on the left (varbit.c:1550), which is why `(-44)::bit(12)` is `111111010100` and not the digits of -44.
(cast-to-float v type-str)float4 and float8. real is a DISTINCT type, not a spelling of
double precision: 1.1::real is 1.100000023841858, and a value that
does not fit is an error rather than an Infinity.
float4 and float8. `real` is a DISTINCT type, not a spelling of double precision: `1.1::real` is 1.100000023841858, and a value that does not fit is an error rather than an Infinity.
(cast-to-integer v type-str)Cast to one of PostgreSQL's three integer widths.
Two things this has to do that a plain coerce-numeric … :long does
not. It ROUNDS rather than truncates -- and the two source families
round DIFFERENTLY, which is not a detail we get to smooth over:
float -> int rint, half to EVEN (float.c dtoi4) numeric-> int half AWAY FROM ZERO (numeric.c round_var)
so 2.5::float8::int is 2 while 2.5::numeric::int is 3. And it
RANGE-CHECKS against the target width: every integer target used to
collapse to Java long, so 100000::int2 and 99999999999::int4
passed through unchanged where PostgreSQL raises 22003.
Cast to one of PostgreSQL's three integer widths. Two things this has to do that a plain `coerce-numeric … :long` does not. It ROUNDS rather than truncates -- and the two source families round DIFFERENTLY, which is not a detail we get to smooth over: float -> int rint, half to EVEN (float.c dtoi4) numeric-> int half AWAY FROM ZERO (numeric.c round_var) so `2.5::float8::int` is 2 while `2.5::numeric::int` is 3. And it RANGE-CHECKS against the target width: every integer target used to collapse to Java long, so `100000::int2` and `99999999999::int4` passed through unchanged where PostgreSQL raises 22003.
(datetime-ctx)What the ported parser reads from session state.
:now is deliberately ABSENT, so the four clock-reading tokens --
now, today, tomorrow, yesterday -- fail as unparseable.
That is what this path needs and what the server already did: their
value is the STATEMENT's, and a cast is constant-folded into a
cached plan, so folding one would freeze it for that plan's life.
Supporting them needs the deferred treatment now() gets, and the
parser is ready for it -- pass a :now and they work.
:session-zone is UTC because the server's TimeZone setting is
(expr.clj:1613). When that becomes settable, this is where it
reads from.
What the ported parser reads from session state. `:now` is deliberately ABSENT, so the four clock-reading tokens -- `now`, `today`, `tomorrow`, `yesterday` -- fail as unparseable. That is what this path needs and what the server already did: their value is the STATEMENT's, and a cast is constant-folded into a cached plan, so folding one would freeze it for that plan's life. Supporting them needs the deferred treatment `now()` gets, and the parser is ready for it -- pass a `:now` and they work. `:session-zone` is UTC because the server's TimeZone setting is (expr.clj:1613). When that becomes settable, this is where it reads from.
(numeric-typmod type-str)[precision scale] from a numeric(p[,s]) target, or nil for bare
numeric. A modifier with no scale means scale 0 -- numeric(10)
truncates to an integer, which is easy to miss.
`[precision scale]` from a `numeric(p[,s])` target, or nil for bare `numeric`. A modifier with no scale means scale 0 -- `numeric(10)` truncates to an integer, which is easy to miss.
(parse-money v)Parse PostgreSQL's money input in the C locale.
The upstream regression suite fixes lc_monetary to C. Its input
accepts a dollar sign, comma separators, and either a minus sign or
parentheses for a negative amount. PostgreSQL stores an int64 count
of cents; we retain the equivalent two-scale BigDecimal carrier.
Parse PostgreSQL's `money` input in the C locale. The upstream regression suite fixes `lc_monetary` to C. Its input accepts a dollar sign, comma separators, and either a minus sign or parentheses for a negative amount. PostgreSQL stores an int64 count of cents; we retain the equivalent two-scale BigDecimal carrier.
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 |