Evaluates CHECK expression data against a row the way PostgreSQL does: nil is NULL, comparisons and functions yield nil when an operand is nil, and a CHECK passes unless its result is false. AND, OR and COALESCE stop at the first decisive operand, left to right. An expression PostgreSQL would fail on (division by zero, a cast that does not parse) fails the row, since the database would reject it too. Rows hold the registry's types (java.time, UUID, jsonb as maps and vectors).
Only the vocabulary that occurs in CHECK constraints is implemented; supported? tells whether an expression is inside it, and render leaves the others unrendered.
Evaluates CHECK expression data against a row the way PostgreSQL does: nil is NULL, comparisons and functions yield nil when an operand is nil, and a CHECK passes unless its result is false. AND, OR and COALESCE stop at the first decisive operand, left to right. An expression PostgreSQL would fail on (division by zero, a cast that does not parse) fails the row, since the database would reject it too. Rows hold the registry's types (java.time, UUID, jsonb as maps and vectors). Only the vocabulary that occurs in CHECK constraints is implemented; supported? tells whether an expression is inside it, and render leaves the others unrendered.
Turns expressions as printed by PostgreSQL's deparser (pg_get_constraintdef, pg_get_expr) into HoneySQL-style data. Only that output format is supported, not arbitrary SQL.
Covered: comparison, logical, arithmetic, string, array, jsonb and regex operators (see edn-safe-ops); IS [NOT] NULL/TRUE/FALSE; IS [NOT] DISTINCT FROM; [NOT] IN; ANY/ALL (ARRAY[...]); [NOT] BETWEEN [SYMMETRIC]; [NOT] LIKE/ILIKE; ::casts; function calls; TRIM(BOTH FROM x); CASE; CURRENT_TIMESTAMP and friends; AT TIME ZONE; (x).field; x[i]; quoted identifiers; string, number, boolean and NULL literals.
Operators whose symbol is not a readable EDN keyword (~, @>, ...) get a named keyword (see edn-safe-ops); ->honeysql maps them back when SQL has to be generated.
Turns expressions as printed by PostgreSQL's deparser (pg_get_constraintdef, pg_get_expr) into HoneySQL-style data. Only that output format is supported, not arbitrary SQL. Covered: comparison, logical, arithmetic, string, array, jsonb and regex operators (see edn-safe-ops); IS [NOT] NULL/TRUE/FALSE; IS [NOT] DISTINCT FROM; [NOT] IN; ANY/ALL (ARRAY[...]); [NOT] BETWEEN [SYMMETRIC]; [NOT] LIKE/ILIKE; ::casts; function calls; TRIM(BOTH FROM x); CASE; CURRENT_TIMESTAMP and friends; AT TIME ZONE; (x).field; x[i]; quoted identifiers; string, number, boolean and NULL literals. Operators whose symbol is not a readable EDN keyword (~, @>, ...) get a named keyword (see edn-safe-ops); ->honeysql maps them back when SQL has to be generated.
Config -> generated EDN files, one per schema.
Config: {:schemas ["public"] ; default ["public"] :out-dir "resources/pgmalli" ; default; files are <out-dir>/<schema>.edn :overrides {constraint-name schema-or-{:skip reason}} :db {:host :port :db :user :password :sslmode :psql :dir}} ; optional; psql's environment otherwise
File contents: {:schema "public" :database-version "PostgreSQL 17.6" :registry {name schema} :unrendered [fact ...] :skipped [fact ...]}
Output is deterministic (sorted maps, pprint). Overrides that are not EDN (functions) are rejected at generation time.
Config -> generated EDN files, one per schema.
Config:
{:schemas ["public"] ; default ["public"]
:out-dir "resources/pgmalli" ; default; files are <out-dir>/<schema>.edn
:overrides {constraint-name schema-or-{:skip reason}}
:db {:host :port :db :user :password :sslmode :psql :dir}} ; optional; psql's environment otherwise
File contents:
{:schema "public" :database-version "PostgreSQL 17.6"
:registry {name schema} :unrendered [fact ...] :skipped [fact ...]}
Output is deterministic (sorted maps, pprint). Overrides that are not EDN (functions)
are rejected at generation time.Reads the structure of one schema from pg_catalog through psql.
Connection settings are psql's own (PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD, PGSSLMODE, ~/.pgpass); entries in the db map override them for the call.
Result shape: {:name "public" :database_version "PostgreSQL 17.6" :tables {"users" {:name "users" :columns [{:name :position :data_type :type_schema :is_nullable :default_value :generated_expr :identity :max_length :precision :scale}] :constraints {"users_age_check" {:name :type :columns :check_clause :is_valid :nulls_not_distinct :references}}}} :types {"mood" {:kind "ENUM" :enum_values [...]} "email" {:kind "DOMAIN" :base_type :not_null :default :constraints [{:name :definition}]}}} :type is one of "CHECK", "PRIMARY KEY", "UNIQUE", "FOREIGN KEY"; :references is {:match :schema :table :columns} for foreign keys, :match one of "SIMPLE", "FULL", "PARTIAL"; :nulls_not_distinct is set for UNIQUE constraints. Maps keyed by object name (:tables, :constraints, :types) keep string keys; everything else is keywordized. Expressions are the strings PostgreSQL's deparser produces.
Reads the structure of one schema from pg_catalog through psql.
Connection settings are psql's own (PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD,
PGSSLMODE, ~/.pgpass); entries in the db map override them for the call.
Result shape:
{:name "public" :database_version "PostgreSQL 17.6"
:tables {"users" {:name "users"
:columns [{:name :position :data_type :type_schema :is_nullable
:default_value :generated_expr :identity :max_length :precision :scale}]
:constraints {"users_age_check" {:name :type :columns :check_clause :is_valid :nulls_not_distinct :references}}}}
:types {"mood" {:kind "ENUM" :enum_values [...]}
"email" {:kind "DOMAIN" :base_type :not_null :default :constraints [{:name :definition}]}}}
:type is one of "CHECK", "PRIMARY KEY", "UNIQUE", "FOREIGN KEY"; :references is
{:match :schema :table :columns} for foreign keys, :match one of "SIMPLE", "FULL", "PARTIAL";
:nulls_not_distinct is set for UNIQUE constraints. Maps keyed by object name (:tables, :constraints,
:types) keep string keys; everything else is keywordized. Expressions are the strings
PostgreSQL's deparser produces.JSON text -> data, with whichever parser the runtime has: babashka ships cheshire and cannot load data.json; the JVM uses data.json.
JSON text -> data, with whichever parser the runtime has: babashka ships cheshire and cannot load data.json; the JVM uses data.json.
Facts about a schema, derived from the structure read by pgmalli.impl.ir.
A fact is a map with :fact (the kind), :schema, and :table / :column / :constraint where they apply. CHECK constraints are matched against a fixed set of patterns (column patterns, then :branch-check and :or-check); anything else is kept as :table-check with the expression data, never dropped.
Kinds: :enum-type an enum type of the schema {:type-name :values} :column a column {:type :position :nullable? :default :identity :generated} :identity is :always, :default or :serial (a nextval default); :generated is the expression :enum column of an enum type {:type-name :values} :domain-ref column of a domain type {:type-name :base} :unknown-type type outside the mapping table (rendered as :any) {:type} :max-length varchar(n) {:max} :numeric numeric(p,s) {:precision :scale} :in-set col IN (...) / col = v {:values} :not-in-set col NOT IN (...) / col <> v {:values} :range col >= a AND col <= b, BETWEEN, ... {:min :max :min-exclusive? :max-exclusive?} :non-blank length(trim(col)) > 0 / col <> '' {:trim?} :length length(col) <= n, cardinality(col) > 0 {:fn :min :max :exact} :json-type jsonb_typeof(col) = 'object' {:json-type} :regex col ~ 're' {:re :case-insensitive?} :like col LIKE 'p' {:pattern :case-insensitive?} :not-null col IS NOT NULL :null col IS NULL :when-present col IS NULL OR <column pattern> {:fact-when-present} :primary-key table primary key {:columns} :unique UNIQUE constraint {:columns :nulls-distinct? (false for NULLS NOT DISTINCT)} :references FOREIGN KEY {:columns :match (:simple or :full) :to {:schema :table :columns}} :domain domain type {:type-name :base :not-null? :default :facts (column patterns over VALUE)} :domain-check CHECK of a domain that matched no pattern {:type-name :constraint :expr :valid?} :branch-check CHECK of the form col = v AND ... OR col = w AND ... {:dispatch :branches [{:values :facts} or {:null true :facts}] :default} :or-check CHECK whose OR alternatives are each an AND of column patterns {:alternatives [[fact ...] ...]} :table-check CHECK that matched no pattern {:expr :columns :valid?} :unparsed expression that could not be read {:input :error}
One CHECK can yield several facts (column patterns joined by AND); every fact from a CHECK carries the whole expression as :expr. NOT VALID constraints are never matched, since existing rows may violate them.
Facts about a schema, derived from the structure read by pgmalli.impl.ir.
A fact is a map with :fact (the kind), :schema, and :table / :column / :constraint where they
apply. CHECK constraints are matched against a fixed set of patterns (column patterns, then
:branch-check and :or-check); anything else is kept as :table-check with the expression data,
never dropped.
Kinds:
:enum-type an enum type of the schema {:type-name :values}
:column a column {:type :position :nullable? :default :identity :generated}
:identity is :always, :default or :serial (a nextval default); :generated is the expression
:enum column of an enum type {:type-name :values}
:domain-ref column of a domain type {:type-name :base}
:unknown-type type outside the mapping table (rendered as :any) {:type}
:max-length varchar(n) {:max}
:numeric numeric(p,s) {:precision :scale}
:in-set col IN (...) / col = v {:values}
:not-in-set col NOT IN (...) / col <> v {:values}
:range col >= a AND col <= b, BETWEEN, ... {:min :max :min-exclusive? :max-exclusive?}
:non-blank length(trim(col)) > 0 / col <> '' {:trim?}
:length length(col) <= n, cardinality(col) > 0 {:fn :min :max :exact}
:json-type jsonb_typeof(col) = 'object' {:json-type}
:regex col ~ 're' {:re :case-insensitive?}
:like col LIKE 'p' {:pattern :case-insensitive?}
:not-null col IS NOT NULL
:null col IS NULL
:when-present col IS NULL OR <column pattern> {:fact-when-present}
:primary-key table primary key {:columns}
:unique UNIQUE constraint {:columns :nulls-distinct? (false for NULLS NOT DISTINCT)}
:references FOREIGN KEY {:columns :match (:simple or :full) :to {:schema :table :columns}}
:domain domain type {:type-name :base :not-null? :default :facts (column patterns over VALUE)}
:domain-check CHECK of a domain that matched no pattern {:type-name :constraint :expr :valid?}
:branch-check CHECK of the form col = v AND ... OR col = w AND ...
{:dispatch :branches [{:values :facts} or {:null true :facts}] :default}
:or-check CHECK whose OR alternatives are each an AND of column patterns
{:alternatives [[fact ...] ...]}
:table-check CHECK that matched no pattern {:expr :columns :valid?}
:unparsed expression that could not be read {:input :error}
One CHECK can yield several facts (column patterns joined by AND); every fact from a CHECK
carries the whole expression as :expr. NOT VALID constraints are never matched, since
existing rows may violate them.Facts -> malli schemas.
registry returns {:registry {name schema} :unrendered [fact] :skipped [fact]} with :pg.<schema>/<type> enum types and domains (a domain CHECK outside the patterns is [:pg/check-value expr], evaluated over the value as :VALUE) :pg.<schema>/<table> a valid row as read from the database A row schema is [:map ...] alone, or [:and [:map ...] checks...] when the table has constraints across columns: [:multi ...] for branches on one column's value, [:or ...] of map fragments, and [:pg/check expr] for everything else pgmalli.impl.eval can evaluate; bytea columns with a length CHECK are [:pg/bytes {:min :max}]. Insert schemas are derived from row schemas when a registry is loaded (pgmalli.impl.runtime).
Properties: on the map :pg/table ("schema.table"), :pg/primary-key, :pg/unique ({:columns} each, :nulls-distinct false for NULLS NOT DISTINCT) and :pg/foreign-keys ({:columns :table :to} each, tables schema-qualified, :match :full for MATCH FULL); on columns :pg/type :pg/default :pg/identity :pg/generated :pg/constraint, and malli's :default for literal defaults.
Identifiers that are not plain names become string keys, keeping the output readable EDN. overrides is {constraint-name schema-or-{:skip reason}}.
Facts -> malli schemas.
registry returns {:registry {name schema} :unrendered [fact] :skipped [fact]} with
:pg.<schema>/<type> enum types and domains (a domain CHECK outside the patterns is
[:pg/check-value expr], evaluated over the value as :VALUE)
:pg.<schema>/<table> a valid row as read from the database
A row schema is [:map ...] alone, or [:and [:map ...] checks...] when the table has constraints
across columns: [:multi ...] for branches on one column's value, [:or ...] of map fragments,
and [:pg/check expr] for everything else pgmalli.impl.eval can evaluate; bytea columns with a
length CHECK are [:pg/bytes {:min :max}]. Insert schemas are
derived from row schemas when a registry is loaded (pgmalli.impl.runtime).
Properties: on the map :pg/table ("schema.table"), :pg/primary-key, :pg/unique
({:columns} each, :nulls-distinct false for NULLS NOT DISTINCT) and :pg/foreign-keys
({:columns :table :to} each, tables schema-qualified, :match :full for MATCH FULL); on columns :pg/type
:pg/default :pg/identity :pg/generated :pg/constraint, and malli's :default for literal defaults.
Identifiers that are not plain names become string keys, keeping the output readable EDN.
overrides is {constraint-name schema-or-{:skip reason}}.The application side: generated files as malli registries, plus helpers that need malli. Generated files are read from the classpath as pgmalli/<schema>.edn.
The application side: generated files as malli registries, plus helpers that need malli. Generated files are read from the classpath as pgmalli/<schema>.edn.
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 |