Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Collations

pgevolve models CREATE COLLATION as a first-class declarative IR object. A collation is a schema-qualified object (pg_collation) that names a locale-data provider plus the lc_collate / lc_ctype strings used by sort and ctype-aware comparisons. Per-column COLLATE clauses (always supported) reference collations by qname; v0.3.8 adds the ability to manage the collation objects themselves.

Source surface

Three syntactic forms are supported, mirroring Postgres:

-- Form 1: libc + locale shorthand (default provider).
CREATE COLLATION app.de_DE (locale = 'de_DE.utf8');

-- Form 2: explicit provider + locale shorthand.
CREATE COLLATION app.case_insensitive
    (provider = icu, locale = 'und', deterministic = false);

-- Form 3: explicit lc_collate + lc_ctype.
CREATE COLLATION app.mixed
    (provider = libc, lc_collate = 'C', lc_ctype = 'en_US.utf8');

The IR always stores lc_collate + lc_ctype separately. When the source used locale = 'X', the parser normalizes to lc_collate = 'X' + lc_ctype = 'X'. The renderer collapses back to the locale = '...' shorthand when the two are equal, so the round-trip is lossless and canonical.

Tests: tier-1: crates/pgevolve-core/src/parse/builder/create_collation_stmt.rs; tier-C: objects/collations/.

IR shape

Collation is a flat struct in pgevolve-core::ir::collation:

FieldTypeNotes
qnameQualifiedNameschema.collation_name
providerCollationProviderLibc | Icu | Builtin (PG 17+)
lc_collateStringFrom pg_collation.collcollate
lc_ctypeStringFrom pg_collation.collctype
deterministicboolDefault true. PG 12+; ICU only when false
versionOption<String>Read-only pg_collation.collversion. Differ ignores; ALTER COLLATION … REFRESH VERSION deferred to v0.3.9
ownerOption<Identifier>Lenient: None = unmanaged, Some(role) = differ emits ALTER COLLATION … OWNER TO
commentOption<String>COMMENT ON COLLATION qname IS '…'

Catalog::collations: Vec<Collation> — flat collection, sorted by qname after canonicalize(). The canon pass also rejects the invalid libc + nondeterministic combination (Postgres would reject at runtime; pgevolve surfaces it at canon time with a clearer error).

BUILTIN_COLLATIONS: &[&str]default, C, POSIX, und-x-icu, unicode, ucs_basic. These shortnames bypass the column-references-unmanaged-collation lint even when they have no schema qualifier, because Postgres seeds them at initdb and they are always available.

Semantics — lenient at the collation grain

pgevolve applies leniency at the whole-collation level (consistent with publications / subscriptions / statistics):

sourcecatalogdiffer action
Collation absentCollation absentno-op
Collation absentCollation presentno-op — surfaced as unmanaged-collation lint warning
Collation presentCollation absentCREATE COLLATION
Collation present, sameCollation present, sameno-op
Collation present, comment differsCollation present, differsCOMMENT ON COLLATION
Collation present, owner differsCollation present, differsALTER COLLATION … OWNER TO
Collation present, structural change (provider / lc_collate / lc_ctype / deterministic)Collation present, differsDROP COLLATION + CREATE COLLATION (ReplaceCollation; destructive)

Postgres has no in-place ALTER COLLATION for provider / locale / deterministic, so any structural change emits ReplaceCollation, which is destructive and requires intent approval.

5 StepKind variants

Step kindSQL emitted
CreateCollationCREATE COLLATION qname (provider = …, locale = …, deterministic = …)
DropCollationDROP COLLATION qname (destructive; intent required)
RenameCollationALTER COLLATION qname RENAME TO new_name
ReplaceCollationDROP COLLATION + CREATE COLLATION (structural change; destructive)
CommentOnCollationCOMMENT ON COLLATION qname IS '…'

ALTER COLLATION … OWNER TO is emitted via the standard Change::AlterObjectOwner path with OwnedObjectId::Qualified, shared with every other ownable IR kind.

5 lint rules

RuleSeverityConditionWaivable?
unmanaged-collationWarningA collation is in the catalog but not in sourceYes
column-references-unmanaged-collationWarningA column's collation references a collation outside [managed].schemas and not in BUILTIN_COLLATIONSYes
range-type-references-unmanaged-subtypeWarningA Range user type's subtype references a user type outside [managed].schemas and not a pg_catalog built-inYes
nondeterministic-collation-requires-pg-12ErrorA Collation has deterministic = false but [managed].min_pg_version < 12No
builtin-provider-requires-pg-17ErrorA Collation uses CollationProvider::Builtin but [managed].min_pg_version < 17No

Tests: tier-1: crates/pgevolve-core/src/lint/rules/unmanaged_collation.rs::tests, column_references_unmanaged_collation.rs::tests, range_type_references_unmanaged_subtype.rs::tests, nondeterministic_collation_requires_pg_12.rs::tests, builtin_provider_requires_pg_17.rs::tests; tier-C: objects/collations/.

Conformance fixture pointers

7 fixtures cover the collation surface (+1 cross-cutting scenario):

FixtureCovers
objects/collations/create-libcBasic libc + locale shorthand
objects/collations/create-icuICU provider + locale shorthand
objects/collations/create-nondeterministicICU + deterministic = false
objects/collations/dropDropCollation
objects/collations/comment-onCommentOnCollation
objects/collations/replace-on-locale-changeStructural change → ReplaceCollation
scenarios/column-references-managed-collationCross-cutting: column COLLATE references a managed Collation

Dependency edges

EdgeMeaning
Table → CollationA column with collation = Some(qname) adds an edge so the collation is created before the table that uses it

The edge is DepSource::Structural. Range → Collation is also added when a UserTypeKind::Range carries a collation: Some(qname).

Property tests (v0.3.8)

crates/pgevolve-testkit/src/ir_generator/collation.rs generates 0–2 libc collations per managed schema with a deterministic-only, safe-locale pool. ICU + nondeterministic + builtin variants are deliberately excluded from the proptest soak to avoid PG-version gating; the conformance fixtures cover those paths instead.

Out of scope (deferred to v0.3.9+)

  • CREATE COLLATION FROM existing_collation — clone syntax. Round-trip identity is ambiguous; the source declares a clone but the catalog shows the resolved provider / locale. Deferred. See the design doc.
  • ALTER COLLATION … REFRESH VERSION — read-only version field is modeled but the differ ignores it. A collation-version-drift lint and explicit REFRESH step are planned for v0.3.9. See the design doc linked above.
  • GRANT on collations — Postgres does not support object-level grants on pg_collation objects. Out of scope by PG design.