sim_relation_schema/physical.rs
1use sim_kernel::{Datum, Symbol};
2use sim_relation_core::{
3 ColumnName, DomainId, IndexName, ProviderName, RelationId, RevisionName, SchemaName,
4 StorageRepr, TableName, ToRelationDatum,
5};
6use std::collections::BTreeSet;
7
8/// A normalized provider-observed column.
9#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
10pub struct PhysicalColumn {
11 /// Observed column name.
12 pub name: ColumnName,
13 /// Normalized logical domain.
14 pub domain: DomainId,
15 /// Exact provider-boundary representation.
16 pub storage: StorageRepr,
17 /// Observed nullability.
18 pub nullable: bool,
19 /// Provider ordinal preserving semantic column order.
20 pub ordinal: u32,
21}
22/// A normalized provider-observed index.
23#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
24pub struct PhysicalIndex {
25 /// Observed index name.
26 pub name: IndexName,
27 /// Observed key columns in order.
28 pub columns: Vec<ColumnName>,
29 /// Observed uniqueness.
30 pub unique: bool,
31}
32/// A normalized provider-observed table.
33#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
34pub struct PhysicalTable {
35 /// Observed table name.
36 pub name: TableName,
37 /// Observed columns.
38 pub columns: Vec<PhysicalColumn>,
39 /// Observed indexes.
40 pub indexes: Vec<PhysicalIndex>,
41}
42/// Immutable normalized evidence observed from a live provider catalog.
43#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct PhysicalSchema {
45 provider: ProviderName,
46 schema: SchemaName,
47 revision: RevisionName,
48 tables: Vec<PhysicalTable>,
49}
50impl PhysicalSchema {
51 /// Normalizes an observed catalog. Tables/indexes are unordered; columns are sorted by provider ordinal.
52 pub fn normalize(
53 provider: ProviderName,
54 schema: SchemaName,
55 revision: RevisionName,
56 mut tables: Vec<PhysicalTable>,
57 ) -> Result<Self, &'static str> {
58 let mut table_names = BTreeSet::new();
59 for table in &mut tables {
60 if !table_names.insert(table.name.clone()) {
61 return Err("duplicate physical table");
62 }
63 table.columns.sort_by_key(|v| v.ordinal);
64 if table
65 .columns
66 .windows(2)
67 .any(|v| v[0].ordinal == v[1].ordinal)
68 {
69 return Err("duplicate physical ordinal");
70 }
71 let mut names = BTreeSet::new();
72 if table.columns.iter().any(|v| !names.insert(v.name.clone())) {
73 return Err("duplicate physical column");
74 }
75 table.indexes.sort_by(|a, b| a.name.cmp(&b.name));
76 }
77 tables.sort_by(|a, b| a.name.cmp(&b.name));
78 Ok(Self {
79 provider,
80 schema,
81 revision,
82 tables,
83 })
84 }
85 /// Returns normalized tables.
86 pub fn tables(&self) -> &[PhysicalTable] {
87 &self.tables
88 }
89 /// Returns the distinct physical identity.
90 pub fn id(&self) -> Result<RelationId, sim_kernel::Error> {
91 RelationId::of(self)
92 }
93}
94fn storage(v: StorageRepr) -> Symbol {
95 Symbol::new(match v {
96 StorageRepr::Bool => "bool",
97 StorageRepr::I64 => "i64",
98 StorageRepr::F64 => "f64",
99 StorageRepr::Text => "text",
100 StorageRepr::Bytes => "bytes",
101 })
102}
103impl ToRelationDatum for PhysicalSchema {
104 fn to_datum(&self) -> Datum {
105 Datum::Node {
106 tag: Symbol::qualified("relation-schema", "physical-schema"),
107 fields: vec![
108 (
109 Symbol::new("provider"),
110 Datum::Symbol(self.provider.symbol().clone()),
111 ),
112 (
113 Symbol::new("schema"),
114 Datum::Symbol(self.schema.symbol().clone()),
115 ),
116 (
117 Symbol::new("revision"),
118 Datum::Symbol(self.revision.symbol().clone()),
119 ),
120 (
121 Symbol::new("tables"),
122 Datum::Vector(
123 self.tables
124 .iter()
125 .map(|t| Datum::Node {
126 tag: Symbol::qualified("relation-schema", "physical-table"),
127 fields: vec![
128 (Symbol::new("name"), Datum::Symbol(t.name.symbol().clone())),
129 (
130 Symbol::new("columns"),
131 Datum::Vector(
132 t.columns
133 .iter()
134 .map(|c| Datum::Node {
135 tag: Symbol::qualified(
136 "relation-schema",
137 "physical-column",
138 ),
139 fields: vec![
140 (
141 Symbol::new("name"),
142 Datum::Symbol(c.name.symbol().clone()),
143 ),
144 (
145 Symbol::new("domain"),
146 Datum::Symbol(
147 c.domain.symbol().clone(),
148 ),
149 ),
150 (
151 Symbol::new("storage"),
152 Datum::Symbol(storage(c.storage)),
153 ),
154 (
155 Symbol::new("nullable"),
156 Datum::Bool(c.nullable),
157 ),
158 (
159 Symbol::new("ordinal"),
160 Datum::Number(
161 sim_kernel::NumberLiteral {
162 domain: Symbol::qualified(
163 "core", "u32",
164 ),
165 canonical: c
166 .ordinal
167 .to_string(),
168 },
169 ),
170 ),
171 ],
172 })
173 .collect(),
174 ),
175 ),
176 (
177 Symbol::new("indexes"),
178 Datum::Vector(
179 t.indexes
180 .iter()
181 .map(|i| Datum::Node {
182 tag: Symbol::qualified(
183 "relation-schema",
184 "physical-index",
185 ),
186 fields: vec![
187 (
188 Symbol::new("name"),
189 Datum::Symbol(i.name.symbol().clone()),
190 ),
191 (
192 Symbol::new("columns"),
193 Datum::Vector(
194 i.columns
195 .iter()
196 .map(|n| {
197 Datum::Symbol(
198 n.symbol().clone(),
199 )
200 })
201 .collect(),
202 ),
203 ),
204 (
205 Symbol::new("unique"),
206 Datum::Bool(i.unique),
207 ),
208 ],
209 })
210 .collect(),
211 ),
212 ),
213 ],
214 })
215 .collect(),
216 ),
217 ),
218 ],
219 }
220 }
221}