Numeric coercion helpers for SQL value paths (CAST + INSERT/UPDATE).
Both apply-sql-cast (in stmt.clj) and coerce-insert-value
(also stmt.clj) used to inline Long/parseLong /
Double/parseDouble / BigDecimal. with subtly different
null/blank/overflow rules:
NumberFormatException on parse failure
and .longValue on overflow (silently truncating).Number.longValue — .longValue of 2^63 returns
Long/MIN_VALUE, which is a wrong-value bug, not a parse bug.(try … (catch NumberFormatException _ val)) returning
the original string on failure, which Datahike then rejected
downstream with a generic schema error instead of 22P02.This namespace centralises those rules so every numeric write goes through helpers that raise the right SQLSTATE:
22003 numeric_value_out_of_range when a value can't fit
the target's range.22P02 invalid_text_representation when a string can't be
parsed as the target type.Both errors are encoded as ex-info with :sqlstate; the wire
layer's handler.clj already lifts those into ErrorResponse
messages.
Numeric coercion helpers for SQL value paths (CAST + INSERT/UPDATE).
Both `apply-sql-cast` (in stmt.clj) and `coerce-insert-value`
(also stmt.clj) used to inline `Long/parseLong` /
`Double/parseDouble` / `BigDecimal.` with subtly different
null/blank/overflow rules:
* apply-sql-cast threw `NumberFormatException` on parse failure
and `.longValue` on overflow (silently truncating).
* coerce-insert-value's BigInteger branch silently truncated
to long via `Number.longValue` — `.longValue` of `2^63` returns
`Long/MIN_VALUE`, which is a wrong-value bug, not a parse bug.
* The bigdec / float / double string branches each had their
own `(try … (catch NumberFormatException _ val))` returning
the original string on failure, which Datahike then rejected
downstream with a generic schema error instead of `22P02`.
This namespace centralises those rules so every numeric write goes
through helpers that raise the right SQLSTATE:
* `22003 numeric_value_out_of_range` when a value can't fit
the target's range.
* `22P02 invalid_text_representation` when a string can't be
parsed as the target type.
Both errors are encoded as `ex-info` with `:sqlstate`; the wire
layer's `handler.clj` already lifts those into ErrorResponse
messages.(bigint->long bi)BigInteger → primitive long, or raise 22003 numeric_value_out_of_range.
BigInteger → primitive long, or raise `22003 numeric_value_out_of_range`.
(coerce-bigint v)Coerce a numeric value to a Java long with PG-style overflow checking.
Raises 22003 numeric_value_out_of_range when v exceeds Long range.
Truncates fractional parts (matches CAST(numeric AS int8) — PG
actually rounds, but Datahike has no exact-numeric int type; this
mirrors the rest of the pipeline that uses (long …)).
Strings: parsed strictly as integers (no decimal point, no
exponent). Use coerce-numeric :long if you want decimal-string
support.
Coerce a numeric value to a Java long with PG-style overflow checking. Raises `22003 numeric_value_out_of_range` when v exceeds Long range. Truncates fractional parts (matches `CAST(numeric AS int8)` — PG actually rounds, but Datahike has no exact-numeric int type; this mirrors the rest of the pipeline that uses `(long …)`). Strings: parsed strictly as integers (no decimal point, no exponent). Use `coerce-numeric :long` if you want decimal-string support.
(coerce-numeric v target)Coerce v (number or string) to the requested numeric target.
target is one of:
:long — Java Long; raises 22003 on overflow. Strings may include
a decimal/exponent (parsed via BigDecimal then narrowed).
:double — Java Double (±Infinity allowed, mirrors PG float8).
:float — Java Float (±Infinity allowed, mirrors PG real).
:bigdec — Java BigDecimal (exact, scientific notation OK).
Numbers pass through as the right type. Strings go through
parse-decimal as the canonical intermediate. Unparseable strings
raise 22P02; out-of-range numbers (only for :long) raise
22003. nil → nil.
Coerce `v` (number or string) to the requested numeric `target`.
`target` is one of:
:long — Java Long; raises 22003 on overflow. Strings may include
a decimal/exponent (parsed via BigDecimal then narrowed).
:double — Java Double (±Infinity allowed, mirrors PG float8).
:float — Java Float (±Infinity allowed, mirrors PG real).
:bigdec — Java BigDecimal (exact, scientific notation OK).
Numbers pass through as the right type. Strings go through
`parse-decimal` as the canonical intermediate. Unparseable strings
raise `22P02`; out-of-range numbers (only for `:long`) raise
`22003`. nil → nil.(coerce-unknown s vtype)(coerce-unknown s vtype timestamp-parser)PG-style typinput dispatch: coerce an unknown-type string literal to
vtype using the type's typinput equivalent. Returns the typed
value on success, the original string on failure (so the
surrounding comparison falls through to text equality and matches
nothing — PG's outcome when an unknown literal can't resolve to
the operator's expected type).
The :db.type/instant typinput needs a parse-timestamp helper
that lives in expr.clj; instant coercion is wired separately via
coerce-comparison-operands taking an explicit timestamp parser.
PG-style typinput dispatch: coerce an unknown-type string literal to `vtype` using the type's typinput equivalent. Returns the typed value on success, the original string on failure (so the surrounding comparison falls through to text equality and matches nothing — PG's outcome when an unknown literal can't resolve to the operator's expected type). The `:db.type/instant` typinput needs a parse-timestamp helper that lives in expr.clj; instant coercion is wired separately via `coerce-comparison-operands` taking an explicit timestamp parser.
(parse-decimal s)Parse a string as BigDecimal — accepts scientific notation, trims
whitespace. Raises 22P02 on unparseable input. Returns nil for
nil input. Empty / whitespace-only strings raise 22P02.
Parse a string as BigDecimal — accepts scientific notation, trims whitespace. Raises `22P02` on unparseable input. Returns nil for nil input. Empty / whitespace-only strings raise 22P02.
(pg-error sqlstate msg)(pg-error sqlstate msg data)Build an ex-info that the wire layer renders as PG ErrorResponse.
sqlstate is the 5-char SQLSTATE; msg is the human-readable text.
Build an ex-info that the wire layer renders as PG ErrorResponse. `sqlstate` is the 5-char SQLSTATE; `msg` is the human-readable text.
{:db/valueType → (fn [^String s] typed-value-or-nil)}. Each fn is
the Datahike-side analogue of PG's typinput for the corresponding
target type — oidin/int8in → Long/parseLong, numericin →
BigDecimal., etc. A nil return means the literal is unparseable
for that type; the caller decides how to handle (typically: keep
the original string so the comparison falls through to text
equality, never matches, returns 0 rows — exactly what PG would do
if the surrounding operator-resolution failed).
Restricted to pure / immutable conversions; mutable typinputs (e.g. timestamptz with the session's TimeZone GUC) need ctx threaded through and aren't covered.
`{:db/valueType → (fn [^String s] typed-value-or-nil)}`. Each fn is
the Datahike-side analogue of PG's typinput for the corresponding
target type — `oidin`/`int8in` → `Long/parseLong`, `numericin` →
`BigDecimal.`, etc. A nil return means the literal is unparseable
for that type; the caller decides how to handle (typically: keep
the original string so the comparison falls through to text
equality, never matches, returns 0 rows — exactly what PG would do
if the surrounding operator-resolution failed).
Restricted to pure / immutable conversions; mutable typinputs
(e.g. timestamptz with the session's TimeZone GUC) need ctx
threaded through and aren't covered.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 |