1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//! `CREATE COLLATION` IR — first-class managed collation kind.
//!
//! Source `lc_collate` and `lc_ctype` are always stored separately, even
//! when the user wrote `locale = 'X'` shorthand. The renderer collapses
//! back to `locale = '...'` when the two are equal.
use serde::{Deserialize, Serialize};
use crate::identifier::{Identifier, QualifiedName};
/// A user-defined collation managed by pgevolve.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Collation {
/// Schema-qualified collation name.
pub qname: QualifiedName,
/// libc / icu / PG 17+ builtin.
pub provider: CollationProvider,
/// `lc_collate` from `pg_collation.collcollate`.
pub lc_collate: String,
/// `lc_ctype` from `pg_collation.collctype`.
pub lc_ctype: String,
/// `deterministic` toggle — default `true`. PG 12+, ICU only when false.
pub deterministic: bool,
/// Read-only `pg_collation.collversion`. Source declares as `None`;
/// the differ ignores this field. REFRESH VERSION management deferred
/// to v0.3.9.
pub version: Option<String>,
/// Lenient owner field (per v0.3.1 cross-cutting state pattern).
pub owner: Option<Identifier>,
/// `COMMENT ON COLLATION qname IS '...'`.
pub comment: Option<String>,
}
/// Locale-data provider — controls which OS / library produces the
/// sort + ctype tables.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CollationProvider {
/// `pg_collation.collprovider = 'c'`.
Libc,
/// `pg_collation.collprovider = 'i'`.
Icu,
/// `pg_collation.collprovider = 'b'` — PG 17+ only.
Builtin,
}
/// Collation shortnames that bypass `column-references-unmanaged-collation`
/// even when they have no schema qualifier. PG seeds these at initdb.
pub const BUILTIN_COLLATIONS: &[&str] =
&["default", "C", "POSIX", "und-x-icu", "unicode", "ucs_basic"];
impl CollationProvider {
/// SQL keyword used in `CREATE COLLATION … (provider = …)`.
#[must_use]
pub const fn sql_keyword(self) -> &'static str {
match self {
Self::Libc => "libc",
Self::Icu => "icu",
Self::Builtin => "builtin",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn id(s: &str) -> Identifier {
Identifier::from_unquoted(s).unwrap()
}
fn qn(s: &str, n: &str) -> QualifiedName {
QualifiedName::new(id(s), id(n))
}
#[test]
fn collation_serde_round_trip() {
let c = Collation {
qname: qn("app", "case_insensitive"),
provider: CollationProvider::Icu,
lc_collate: "und".into(),
lc_ctype: "und".into(),
deterministic: false,
version: None,
owner: None,
comment: Some("CI collation".into()),
};
let json = serde_json::to_string(&c).unwrap();
let back: Collation = serde_json::from_str(&json).unwrap();
assert_eq!(c, back);
}
#[test]
fn provider_sql_keywords() {
assert_eq!(CollationProvider::Libc.sql_keyword(), "libc");
assert_eq!(CollationProvider::Icu.sql_keyword(), "icu");
assert_eq!(CollationProvider::Builtin.sql_keyword(), "builtin");
}
}