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? parse-timestamp 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).
:parse-timestamp — fn String → java.util.Date, from expr.clj.
: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).
:parse-timestamp — fn String → java.util.Date, from expr.clj.
: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.
(decode-numeric-date f1 f2 f3 order)Three numeric date fields to a LocalDate, per DateStyle's field
ORDER -- the half of DateStyle that says whether 8/10/2017 is
August 10th or the 8th of October.
PostgreSQL's rules (DecodeDateTime):
2017-08-10 reads the same under MDY, DMY and YMD;13/10/2017 is a month
13 under MDY and an error, while under DMY it is the 13th of
October.Returns nil when the fields do not make a date; the caller decides
whether that is 22008 or 22007. Separators do not matter here --
8/10/2017 and 10-08-2017 differ only in punctuation, and
PostgreSQL reads both by order.
Three numeric date fields to a LocalDate, per `DateStyle`'s field
ORDER -- the half of DateStyle that says whether `8/10/2017` is
August 10th or the 8th of October.
PostgreSQL's rules (DecodeDateTime):
- a leading field of FOUR or more digits is the year, whatever the
order, so `2017-08-10` reads the same under MDY, DMY and YMD;
- otherwise the order assigns the fields, and a two-digit year is
mapped to a century;
- the fields are then CHECKED, not rolled: `13/10/2017` is a month
13 under MDY and an error, while under DMY it is the 13th of
October.
Returns nil when the fields do not make a date; the caller decides
whether that is 22008 or 22007. Separators do not matter here --
`8/10/2017` and `10-08-2017` differ only in punctuation, and
PostgreSQL reads both by order.(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.
(split-trailing-zone s)[head zone-offset] for a datetime literal that carries a numeric
zone, else [s nil].
Splitting it out is the whole point: timestamp without time zone
keeps the fields and DROPS the zone (datetime.c decodes tzp and then
ignores it), while timestamptz converts by it. Reading the literal
once and letting the target decide is what keeps the two answers from
being parsed by different code.
`[head zone-offset]` for a datetime literal that carries a numeric zone, else `[s nil]`. Splitting it out is the whole point: `timestamp without time zone` keeps the fields and DROPS the zone (datetime.c decodes tzp and then ignores it), while `timestamptz` converts by it. Reading the literal once and letting the target decide is what keeps the two answers from being parsed by different code.
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 |