ExSQL.Value (exsql v0.1.1)

Copy Markdown

SQLite's value semantics: storage classes, type affinity, comparison ordering, arithmetic, and three-valued logic.

Values map onto Elixir terms directly:

storage classElixir term
NULLnil
INTEGERinteger()
REALfloat()
TEXTbinary()
BLOB{:blob, binary}

Internally, JSON-producing functions may temporarily use {:json, binary} to model SQLite's JSON subtype. It behaves as TEXT for ordinary SQL semantics and is stripped before public result/storage boundaries.

Comparison follows SQLite's cross-class ordering: NULL < numeric values (INTEGER and REAL compare numerically with each other) < TEXT < BLOB. Boolean logic is three-valued — nil means "unknown" and propagates per the SQL standard truth tables.

Summary

Functions

Coerces value per a column's affinity before storage, following https://sqlite.org/datatype3.html §3: affinity is a preference, not a constraint — values that can't be losslessly converted are stored as-is.

Arithmetic with SQLite semantics: NULL propagates; text operands get numeric affinity applied (non-numeric text becomes 0); division and modulo by zero yield NULL.

Bitwise NOT (~) over 64 bits; NULL propagates.

Bitwise &, |, <<, >> over 64-bit integers: NULL propagates, operands convert with CAST-to-INTEGER semantics, a negative shift count shifts the other way, and results wrap to signed 64 bits.

The CAST operator (https://sqlite.org/lang_expr.html#castexpr). Unlike affinity this is a forced conversion: non-numeric text becomes 0, and numeric casts of text take the longest numeric prefix ('123abc' → 123).

Total ordering across storage classes: NULL < numeric < TEXT < BLOB.

SQL comparison operator: returns nil (unknown) if either side is NULL, otherwise a boolean per compare/2.

Applies comparison affinity to a pair of operands before comparing, per https://sqlite.org/datatype3.html §4.2: if one side has numeric affinity and the other does not, NUMERIC is applied to the other side; else if one side has TEXT affinity and the other has none, TEXT is applied. BLOB affinity counts as "no affinity". Literals compared to literals are left untouched (ticket #805).

String concatenation (||): NULL propagates, operands become text.

The GLOB operator: */? wildcards, case-sensitive.

The LIKE operator: % matches any run, _ any single character; ASCII case-insensitive, as in SQLite's default (ICU-less) build.

Returns true if an integer falls outside the 64-bit signed range.

Three-valued AND.

Three-valued NOT.

Three-valued OR.

Renders a value as TEXT, the way CAST(x AS TEXT) would.

Converts a value to SQL boolean: nil, true, or false.

Returns the storage class of a value, as typeof() reports it.

Types

t()

@type t() ::
  nil | integer() | float() | binary() | {:blob, binary()} | {:json, binary()}

Functions

apply_affinity(value, affinity)

@spec apply_affinity(t(), ExSQL.AST.ColumnDef.affinity()) :: t()

Coerces value per a column's affinity before storage, following https://sqlite.org/datatype3.html §3: affinity is a preference, not a constraint — values that can't be losslessly converted are stored as-is.

arithmetic(op, a, b)

@spec arithmetic(:add | :sub | :mul | :div | :mod, t(), t()) :: t()

Arithmetic with SQLite semantics: NULL propagates; text operands get numeric affinity applied (non-numeric text becomes 0); division and modulo by zero yield NULL.

bitnot(v)

@spec bitnot(t()) :: t()

Bitwise NOT (~) over 64 bits; NULL propagates.

bitwise(op, a, b)

@spec bitwise(:bitand | :bitor | :shl | :shr, t(), t()) :: t()

Bitwise &, |, <<, >> over 64-bit integers: NULL propagates, operands convert with CAST-to-INTEGER semantics, a negative shift count shifts the other way, and results wrap to signed 64 bits.

cast(blob, affinity)

@spec cast(t(), ExSQL.AST.ColumnDef.affinity()) :: t()

The CAST operator (https://sqlite.org/lang_expr.html#castexpr). Unlike affinity this is a forced conversion: non-numeric text becomes 0, and numeric casts of text take the longest numeric prefix ('123abc' → 123).

compare(a, b, collation \\ :binary)

@spec compare(t(), t(), atom()) :: :lt | :eq | :gt

Total ordering across storage classes: NULL < numeric < TEXT < BLOB.

Used by ORDER BY and the comparison operators (which additionally return NULL when either operand is NULL — see compare_op/3).

compare_op(op, a, b)

@spec compare_op(:eq | :ne | :lt | :le | :gt | :ge, t(), t()) :: boolean() | nil

SQL comparison operator: returns nil (unknown) if either side is NULL, otherwise a boolean per compare/2.

compare_op(op, a, b, collation)

comparison_coerce(a, affinity_a, b, affinity_b)

@spec comparison_coerce(t(), atom(), t(), atom()) :: {t(), t()}

Applies comparison affinity to a pair of operands before comparing, per https://sqlite.org/datatype3.html §4.2: if one side has numeric affinity and the other does not, NUMERIC is applied to the other side; else if one side has TEXT affinity and the other has none, TEXT is applied. BLOB affinity counts as "no affinity". Literals compared to literals are left untouched (ticket #805).

concat(a, b)

@spec concat(t(), t()) :: t()

String concatenation (||): NULL propagates, operands become text.

glob(value, pattern)

@spec glob(t(), t()) :: boolean() | nil

The GLOB operator: */? wildcards, case-sensitive.

like(value, pattern)

@spec like(t(), t()) :: boolean() | nil

The LIKE operator: % matches any run, _ any single character; ASCII case-insensitive, as in SQLite's default (ICU-less) build.

like(value, pattern, case_sensitive?)

@spec like(t(), t(), boolean()) :: boolean() | nil

like(value, pattern, escape, case_sensitive?)

@spec like(t(), t(), binary(), boolean()) :: boolean() | nil

out_of_int64_range?(n)

@spec out_of_int64_range?(integer()) :: boolean()

Returns true if an integer falls outside the 64-bit signed range.

sql_and(arg1, arg2)

@spec sql_and(boolean() | nil, boolean() | nil) :: boolean() | nil

Three-valued AND.

sql_not(b)

@spec sql_not(boolean() | nil) :: boolean() | nil

Three-valued NOT.

sql_or(arg1, arg2)

@spec sql_or(boolean() | nil, boolean() | nil) :: boolean() | nil

Three-valued OR.

to_text(v)

@spec to_text(t()) :: binary()

Renders a value as TEXT, the way CAST(x AS TEXT) would.

truthy(value)

@spec truthy(t()) :: boolean() | nil

Converts a value to SQL boolean: nil, true, or false.

type_of(v)

@spec type_of(t()) :: :null | :integer | :real | :text | :blob

Returns the storage class of a value, as typeof() reports it.