Cast from Postgres to Elixir types
Implementation inspired by Cainophile, Supabase Realtime and Sequin.
Vendored from walex 4.8.0 (WalEx.Casting.Types).
Raise-sites (decode-boundary contract)
cast_record/2 is lenient by default: every clause that parses a scalar
wraps the parse in a case/Integer.parse/Float.parse and falls back to
the original string on failure, and the catch-all clause returns the value
unchanged for unknown types. Parse-failures therefore never raise.
Three clauses can still raise on truly-malformed input, because they call a bang/raising function with no local rescue:
Decimal.new/1(the"numeric","decimal","money"scalar clauses and the"_numeric"/"_decimal"/"_money"array clauses) raisesDecimal.Erroron a malformed numeric string.Base.decode16!/2(the"bytea"scalar clause and the"_bytea"array clause) raisesArgumentErroron non-hex bytea payloads.DateTime.from_naive!/2(the"timestamp"scalar clause and the"_timestamp"array clause) raises on an invalid naive datetime; in practice the precedingNaiveDateTime.from_iso8601/1guards it, but the bang call is retained verbatim from upstream.
Jason.decode/1 (the "jsonb"/"json" scalar and "_jsonb"/"_json"
array clauses) does NOT raise — it returns {:error, _}, which the
surrounding case collapses to the lenient fallback.
Because malformed numeric/bytea inputs raise through cast_record/2, the
Assembler (Task 13) MUST invoke cast_record/2 inside a decode boundary —
either Replicant.Decoder.decode/1 (Task 8) or its own try/rescue — so a
single malformed cell scrubs to a boundary error rather than crashing the
pipeline. Do NOT widen cast_record/2 to swallow these: the lenient
fallback already covers ordinary parse-failures; only genuinely-malformed
input reaches the raising path, and the boundary is the correct place to
scrub it.
Summary
Functions
Casts a PostgreSQL string value to its appropriate Elixir type.
Functions
Casts a PostgreSQL string value to its appropriate Elixir type.
Examples
iex> cast_record("t", "bool")
true
iex> cast_record("123", "int4")
123
iex> cast_record("123.45", "numeric")
#Decimal<123.45>
iex> cast_record("{1,2,3}", "_int4")
[1, 2, 3]
iex> cast_record("2024-01-15T10:30:00Z", "timestamptz")
#DateTime<2024-01-15 10:30:00Z>Special values like NaN and Infinity are handled:
iex> cast_record("NaN", "float8")
:nanReturns the original value if casting fails.