First-class array values for pgwire-datahike.
Mirrors PostgreSQL's ArrayType (src/include/utils/array.h): a
value carrying element-type, the element vector (possibly nested
for multi-dim), per-dimension sizes, and per-dimension lower
bounds. Subscripting, slicing, membership, containment, concat,
PG text-format codec.
Design notes:
elem-type is a keyword matching our types/oid-* naming
(:int8, :text, :name, :bool, :float8, …) — not an OID.
Resolution to OID happens at the oid-infer / describeResult layer.elements is a Clojure vector. For 1-D arrays it's a flat
vector of element values (nil represents SQL NULL). For
multi-dim it's a nested vector — outermost level is the first
dimension, innermost is the last. Always a uniform shape; PG
rejects ragged arrays and so do we (the parser raises on a
dimension mismatch).dims is the vector of per-dimension sizes. nil is shorthand
for [(count elements)] — a 1-D array. For multi-dim,
(count dims) = ndim, (reduce * dims) = total leaf count.lbounds is the vector of per-dimension lower bounds. nil is
shorthand for [1, 1, …]. PG defaults to 1; non-1 lbounds come
from ARRAY[lo:hi]=… literals (rare). Preserved through
to-pg-text/from-pg-text round-trip when non-default.:__null__ sentinel inside
arrays. Arrays are values we own end-to-end, so element NULLs
are Clojure nil.nil (SQL NULL), NOT an error.
arr[0] and arr[-1] return nil for the default lbound=1.defrecord gives us structural equality/hash — important for
datahike/datalog round-trip (arrays flow through query results,
bind params, and DISTINCT/GROUP BY without special handling).Text codec follows PG (src/backend/utils/adt/arrayfuncs.c:
array_out / array_in):
{} for empty,; nested dimensions emit nested {…}"…" when element contains ,, ", \, {, }, or
whitespace, or is empty, or is the literal NULL (case-insensitive)\ and " with backslashNULL tokent/f (PG text format for BOOL)[lo1:hi1][lo2:hi2]…= prefixFirst-class array values for pgwire-datahike.
Mirrors PostgreSQL's `ArrayType` (src/include/utils/array.h): a
value carrying element-type, the element vector (possibly nested
for multi-dim), per-dimension sizes, and per-dimension lower
bounds. Subscripting, slicing, membership, containment, concat,
PG text-format codec.
Design notes:
- `elem-type` is a keyword matching our types/oid-* naming
(`:int8`, `:text`, `:name`, `:bool`, `:float8`, …) — not an OID.
Resolution to OID happens at the oid-infer / describeResult layer.
- `elements` is a Clojure vector. For 1-D arrays it's a flat
vector of element values (`nil` represents SQL NULL). For
multi-dim it's a nested vector — outermost level is the first
dimension, innermost is the last. Always a uniform shape; PG
rejects ragged arrays and so do we (the parser raises on a
dimension mismatch).
- `dims` is the vector of per-dimension sizes. `nil` is shorthand
for `[(count elements)]` — a 1-D array. For multi-dim,
`(count dims) = ndim`, `(reduce * dims) = total leaf count`.
- `lbounds` is the vector of per-dimension lower bounds. `nil` is
shorthand for `[1, 1, …]`. PG defaults to 1; non-1 lbounds come
from `ARRAY[lo:hi]=…` literals (rare). Preserved through
to-pg-text/from-pg-text round-trip when non-default.
- We deliberately do NOT use the `:__null__` sentinel inside
arrays. Arrays are values we own end-to-end, so element NULLs
are Clojure `nil`.
- Subscript semantics follow PG exactly: 1-indexed (offset by
lbound), out-of-range returns `nil` (SQL NULL), NOT an error.
`arr[0]` and `arr[-1]` return nil for the default lbound=1.
- `defrecord` gives us structural equality/hash — important for
datahike/datalog round-trip (arrays flow through query results,
bind params, and DISTINCT/GROUP BY without special handling).
Text codec follows PG (`src/backend/utils/adt/arrayfuncs.c`:
`array_out` / `array_in`):
- `{}` for empty
- Elements joined by `,`; nested dimensions emit nested `{…}`
- Quote `"…"` when element contains `,`, `"`, `\`, `{`, `}`, or
whitespace, or is empty, or is the literal `NULL` (case-insensitive)
- Inside quotes: escape `\` and `"` with backslash
- NULL elements emit unquoted `NULL` token
- Booleans as `t`/`f` (PG text format for BOOL)
- Non-default lbounds emit a `[lo1:hi1][lo2:hi2]…=` prefix(all-match? a pred)Predicate form: returns true if (pred leaf) returns truthy for
every leaf. Vacuously true for an empty array (PG semantics for
x = ALL(<empty>)).
Predicate form: returns true if (pred leaf) returns truthy for every leaf. Vacuously true for an empty array (PG semantics for `x = ALL(<empty>)`).
(any-match? a pred)Predicate form: returns true if (pred leaf) returns truthy for some leaf. Walks all dimensions.
Predicate form: returns true if (pred leaf) returns truthy for some leaf. Walks all dimensions.
(array elem-type elements)(array elem-type elements dims-or-lbound)(array elem-type elements dims lbounds)Construct a PgArray. Element-type is a keyword (:int8, :text,
:bool, …). Optional dims (vector of per-dim sizes) and lbounds
(vector of per-dim lower bounds, defaulting to 1 for each).
For 1-D arrays you can omit dims/lbounds — they're derived from
(count elements) and 1 respectively. For multi-dim, pass a
nested-vector elements and dims is computed automatically; or
pass dims explicitly when the elements form is something exotic.
Construct a PgArray. Element-type is a keyword (`:int8`, `:text`, `:bool`, …). Optional dims (vector of per-dim sizes) and lbounds (vector of per-dim lower bounds, defaulting to 1 for each). For 1-D arrays you can omit dims/lbounds — they're derived from `(count elements)` and `1` respectively. For multi-dim, pass a nested-vector `elements` and dims is computed automatically; or pass dims explicitly when the elements form is something exotic.
(array? v)True iff v is a PgArray instance.
True iff v is a PgArray instance.
(concat-arrs a b)PG a || b — full multi-dim semantics via cat-rejecting-mismatch.
Element-types must match; we leave compatibility checks to the
call-site (PG coerces via least common supertype).
PG `a || b` — full multi-dim semantics via `cat-rejecting-mismatch`. Element-types must match; we leave compatibility checks to the call-site (PG coerces via least common supertype).
(contains-arr? a b)PG a @> b: every non-null leaf of b is present somewhere in a.
Operates on flattened leaves — PG ignores shape for @>.
PG `a @> b`: every non-null leaf of b is present somewhere in a. Operates on flattened leaves — PG ignores shape for `@>`.
(element-type a)Element-type keyword (:text, :int8, etc.).
Element-type keyword (:text, :int8, etc.).
(flat-elements a)Walk a (possibly nested) elements vector to a flat seq of leaves —
used by member? / contains-arr? / overlap? and the binary codec
which encode element-by-element regardless of shape. Preserves nil
leaves so 3-valued logic works through them. Descends into nested
PgArray records as well as Clojure sequentials so an outer
PgArray whose :elements happen to hold inner PgArrays (the
common shape produced by expr.clj's ArrayConstructor build-fn for
ARRAY[ARRAY[…],…]) is walked correctly.
Walk a (possibly nested) elements vector to a flat seq of leaves — used by member? / contains-arr? / overlap? and the binary codec which encode element-by-element regardless of shape. Preserves nil leaves so 3-valued logic works through them. Descends into nested PgArray records as well as Clojure sequentials so an outer PgArray whose `:elements` happen to hold inner PgArrays (the common shape produced by expr.clj's ArrayConstructor build-fn for `ARRAY[ARRAY[…],…]`) is walked correctly.
(from-pg-text s elem-type)Parse PG array text format into a PgArray of the given element-type.
Inverse of to-pg-text. Handles:
{} — empty array{1,2,3} — flat 1-D{{1,2},{3,4}} — multi-dim (nested braces)[2:4]={a,b,c} — non-default lbound[1:2][1:2]={{1,0},{0,1}} — non-default multi-dim lbound,, ", \ escape sequencesNULL token → element nil
Raises on malformed input or ragged shape.Parse PG array text format into a PgArray of the given element-type.
Inverse of `to-pg-text`. Handles:
- `{}` — empty array
- `{1,2,3}` — flat 1-D
- `{{1,2},{3,4}}` — multi-dim (nested braces)
- `[2:4]={a,b,c}` — non-default lbound
- `[1:2][1:2]={{1,0},{0,1}}` — non-default multi-dim lbound
- quoted strings with `,`, `"`, `\` escape sequences
- unquoted `NULL` token → element nil
Raises on malformed input or ragged shape.(lbound a)(lbound a d)Lower bound for dimension d (1-indexed, default 1). PG: arrays
default to 1; explicit [lo:hi]= literals can shift this.
Lower bound for dimension `d` (1-indexed, default 1). PG: arrays default to 1; explicit `[lo:hi]=` literals can shift this.
(length a)Element count along the outermost (first) dimension. For 1-D arrays
that's the total leaf count. For multi-dim it's the size of dim 1.
Differs from PG's array_length — this returns 0 for an empty
array; call-sites that need PG's NULL-for-empty check explicitly.
Element count along the outermost (first) dimension. For 1-D arrays that's the total leaf count. For multi-dim it's the size of dim 1. Differs from PG's `array_length` — this returns 0 for an empty array; call-sites that need PG's NULL-for-empty check explicitly.
(length-d a d)Length along dimension d (1-indexed). PG's array_length(arr, d)
returns NULL when d is out of range — call-sites should check
(<= d (ndim a)) before relying on the result.
Length along dimension `d` (1-indexed). PG's `array_length(arr, d)` returns NULL when d is out of range — call-sites should check `(<= d (ndim a))` before relying on the result.
(match-shape a b)Predicate: does b have a shape that's compatible with being a
sub-element of a? PG accepts this when (rest dims-a) matches
dims-b exactly. Used by array_cat for the asymmetric N-D || (N-1)-D
and (N-1)-D || N-D cases (array_userfuncs.c:471–479).
Predicate: does `b` have a shape that's compatible with being a sub-element of `a`? PG accepts this when `(rest dims-a)` matches `dims-b` exactly. Used by array_cat for the asymmetric N-D || (N-1)-D and (N-1)-D || N-D cases (`array_userfuncs.c:471–479`).
(member? a x)PG x = ANY(arr) semantics with 3-valued logic. Operates on
flattened leaves so multi-dim arrays match anywhere:
ARRAY[[1,2],[3,4]] = ANY(…) is true if any leaf equals x.
PG `x = ANY(arr)` semantics with 3-valued logic. Operates on flattened leaves so multi-dim arrays match anywhere: ARRAY[[1,2],[3,4]] = ANY(…) is true if any leaf equals x. - returns true if any leaf = x - returns false if no leaf = x AND no NULL leaves - returns nil (UNKNOWN) if x isn't matched but NULL leaves exist (a NULL leaf could match any x)
(multidim? a)True if the array has more than one dimension. Several PG functions (array_append, array_prepend, array_position, array_remove) explicitly reject multi-dim arrays; callers raise the matching feature_not_supported error in that case.
True if the array has more than one dimension. Several PG functions (array_append, array_prepend, array_position, array_remove) explicitly reject multi-dim arrays; callers raise the matching feature_not_supported error in that case.
(ndim a)Number of dimensions. 1 for the 1-D default; more for nested arrays.
Number of dimensions. 1 for the 1-D default; more for nested arrays.
(overlap? a b)PG a && b: any leaf of a is a leaf of b (NULLs ignored).
Operates on flattened leaves.
PG `a && b`: any leaf of a is a leaf of b (NULLs ignored). Operates on flattened leaves.
(replace-leaves a f)Walk every leaf of a's nested elements, applying f to each
leaf value. Returns a new PgArray with the same shape, dims, and
lbounds. Used by array_replace, which PG applies to all leaves
regardless of dimensionality (arrayfuncs.c:6662). Descends into
nested PgArrays as well as Clojure sequentials.
Walk every leaf of `a`'s nested elements, applying `f` to each leaf value. Returns a new PgArray with the same shape, dims, and lbounds. Used by `array_replace`, which PG applies to all leaves regardless of dimensionality (`arrayfuncs.c:6662`). Descends into nested PgArrays as well as Clojure sequentials.
(slice a lo hi)PG array slice: arr[lo:hi]. 1-indexed, inclusive bounds, clamped
to [lbound, lbound+length-1]. Returns a PgArray (possibly empty)
preserving elem-type. nil bounds → PG default (lbound / lbound+len-1).
Slicing operates on the outermost dimension only; inner shape and lbounds are preserved. Multi-dim aware.
PG array slice: `arr[lo:hi]`. 1-indexed, inclusive bounds, clamped to `[lbound, lbound+length-1]`. Returns a PgArray (possibly empty) preserving elem-type. nil bounds → PG default (lbound / lbound+len-1). Slicing operates on the outermost dimension only; inner shape and lbounds are preserved. Multi-dim aware.
(subscript a n)PG array subscript: arr[n]. 1-indexed (offset by lbound),
out-of-range or nil index returns nil (SQL NULL) — never throws.
For multi-dim arrays, returns the nth sub-array (still a PgArray
with rank reduced by 1) so caller can chain (subscript (subscript m 1) 2) for m[1][2]. Matches PG: arr[i] on a 2-D yields a
1-D row, arr[i][j] yields a scalar.
PG array subscript: `arr[n]`. 1-indexed (offset by lbound), out-of-range or nil index returns nil (SQL NULL) — never throws. For multi-dim arrays, returns the nth sub-array (still a PgArray with rank reduced by 1) so caller can chain `(subscript (subscript m 1) 2)` for `m[1][2]`. Matches PG: `arr[i]` on a 2-D yields a 1-D row, `arr[i][j]` yields a scalar.
(to-pg-text a)Render a PgArray to PG's canonical text format: {e1,e2,...} for
1-D, nested {{…},{…}} for multi-dim. Emits [lo1:hi1][lo2:hi2]…=
prefix when any lower bound != 1.
Used by the wire layer's value->string for any PgArray value.
Render a PgArray to PG's canonical text format: `{e1,e2,...}` for
1-D, nested `{{…},{…}}` for multi-dim. Emits `[lo1:hi1][lo2:hi2]…=`
prefix when any lower bound != 1.
Used by the wire layer's value->string for any PgArray value.(ubound a d)Upper bound for dimension d (1-indexed). (lbound a d) + (length-d a d) - 1, or nil if d out of range.
Upper bound for dimension `d` (1-indexed). `(lbound a d) + (length-d a d) - 1`, or nil if d out of range.
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 |