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.
(cast-scalar v
type-str
{:keys [explicit? parse-timestamp resolve-regclass
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).
:parse-timestamp — fn String → java.util.Date, from expr.clj.
:resolve-regclass— fn String → oid, for ::regclass.
: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).
:parse-timestamp — fn String → java.util.Date, from expr.clj.
:resolve-regclass— fn String → oid, for `::regclass`.
: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.
(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.
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 |