1use serde::Deserialize;
7use ssp2::segment::{Column, ColumnType};
8
9#[derive(Debug, Clone, Deserialize)]
10pub struct SchemaIr {
11 pub version: i32,
12 pub tables: Vec<TableIr>,
13}
14
15#[derive(Debug, Clone, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct TableIr {
18 pub name: String,
19 pub columns: Vec<ColumnIr>,
20 pub primary_key: String,
21 pub scopes: Vec<ScopePatternIr>,
22 #[serde(default)]
25 pub indexes: Vec<IndexIr>,
26}
27
28#[derive(Debug, Clone, Deserialize)]
29pub struct ColumnIr {
30 pub name: String,
31 #[serde(rename = "type")]
32 pub column_type: String,
33 pub nullable: bool,
34 #[serde(default)]
37 pub encrypted: bool,
38 #[serde(default, rename = "declaredType")]
40 pub declared_type: Option<String>,
41}
42
43#[derive(Debug, Clone, Deserialize)]
45pub struct IndexIr {
46 pub name: String,
47 pub columns: Vec<String>,
48 pub unique: bool,
49}
50
51#[derive(Debug, Clone)]
53pub struct IndexSchema {
54 pub name: String,
55 pub columns: Vec<String>,
56 pub unique: bool,
57}
58
59#[derive(Debug, Clone, Deserialize)]
60pub struct ScopePatternIr {
61 pub pattern: String,
62 #[serde(default)]
63 pub column: Option<String>,
64}
65
66#[derive(Debug, Clone)]
68pub struct ScopeVariable {
69 pub variable: String,
70 pub column: String,
71 pub prefix: String,
72}
73
74#[derive(Debug, Clone)]
78pub struct EncryptedColumn {
79 pub index: usize,
80 pub declared_type: String,
81}
82
83#[derive(Debug, Clone)]
84pub struct TableSchema {
85 pub name: String,
86 pub columns: Vec<Column>,
91 pub wire_columns: Vec<Column>,
96 pub primary_key: String,
97 pub pk_index: usize,
98 pub scope_variables: Vec<ScopeVariable>,
99 pub indexes: Vec<IndexSchema>,
101 pub encrypted_columns: Vec<EncryptedColumn>,
103}
104
105impl TableSchema {
106 pub fn has_encrypted_columns(&self) -> bool {
108 !self.encrypted_columns.is_empty()
109 }
110}
111
112impl TableSchema {
113 pub fn scope_column(&self, variable: &str) -> Option<&str> {
116 self.scope_variables
117 .iter()
118 .find(|s| s.variable == variable)
119 .map(|s| s.column.as_str())
120 }
121}
122
123#[derive(Debug, Clone)]
124pub struct ClientSchema {
125 pub version: i32,
126 pub tables: Vec<TableSchema>,
127}
128
129impl ClientSchema {
130 pub fn table(&self, name: &str) -> Option<&TableSchema> {
131 self.tables.iter().find(|t| t.name == name)
132 }
133}
134
135fn parse_column_type(name: &str) -> Result<ColumnType, String> {
136 match name {
137 "string" => Ok(ColumnType::String),
138 "integer" => Ok(ColumnType::Integer),
139 "float" => Ok(ColumnType::Float),
140 "boolean" => Ok(ColumnType::Boolean),
141 "json" => Ok(ColumnType::Json),
142 "bytes" => Ok(ColumnType::Bytes),
143 "blob_ref" => Ok(ColumnType::BlobRef),
144 "crdt" => Ok(ColumnType::Crdt),
145 other => Err(format!("unknown column type {other:?}")),
146 }
147}
148
149fn parse_pattern_variable(pattern: &str) -> Result<String, String> {
151 let open = pattern
152 .find('{')
153 .ok_or_else(|| format!("scope pattern {pattern:?} has no {{variable}}"))?;
154 let close = pattern
155 .rfind('}')
156 .filter(|end| *end > open)
157 .ok_or_else(|| format!("scope pattern {pattern:?} has no closing brace"))?;
158 let variable = &pattern[open + 1..close];
159 if variable.is_empty() {
160 return Err(format!("scope pattern {pattern:?} has an empty variable"));
161 }
162 Ok(variable.to_owned())
163}
164
165pub fn compile_schema(ir: &SchemaIr) -> Result<ClientSchema, String> {
166 let mut tables = Vec::with_capacity(ir.tables.len());
167 for table in &ir.tables {
168 let mut wire_columns = Vec::with_capacity(table.columns.len());
172 let mut columns = Vec::with_capacity(table.columns.len());
173 let mut encrypted_columns = Vec::new();
174 for (index, column) in table.columns.iter().enumerate() {
175 let wire_ty = parse_column_type(&column.column_type)?;
176 wire_columns.push(Column {
177 name: column.name.clone(),
178 ty: wire_ty,
179 nullable: column.nullable,
180 });
181 if column.encrypted {
182 let declared_name = column.declared_type.clone().ok_or_else(|| {
183 format!(
184 "table {:?}: encrypted column {:?} has no declaredType (§5.11)",
185 table.name, column.name
186 )
187 })?;
188 let declared_ty = parse_column_type(&declared_name)?;
189 columns.push(Column {
190 name: column.name.clone(),
191 ty: declared_ty,
192 nullable: column.nullable,
193 });
194 encrypted_columns.push(EncryptedColumn {
195 index,
196 declared_type: declared_name,
197 });
198 } else {
199 columns.push(Column {
200 name: column.name.clone(),
201 ty: wire_ty,
202 nullable: column.nullable,
203 });
204 }
205 }
206 let pk_index = columns
207 .iter()
208 .position(|c| c.name == table.primary_key)
209 .ok_or_else(|| {
210 format!(
211 "table {:?}: primary key {:?} is not a column",
212 table.name, table.primary_key
213 )
214 })?;
215 let mut scope_variables = Vec::with_capacity(table.scopes.len());
216 for scope in &table.scopes {
217 let variable = parse_pattern_variable(&scope.pattern)?;
218 let column = scope.column.clone().unwrap_or_else(|| variable.clone());
219 let open = scope.pattern.find('{').expect("validated scope pattern");
220 let prefix = scope.pattern[..open]
221 .strip_suffix(':')
222 .unwrap_or(&scope.pattern[..open])
223 .to_owned();
224 scope_variables.push(ScopeVariable {
225 variable,
226 column,
227 prefix,
228 });
229 }
230 let mut indexes = Vec::with_capacity(table.indexes.len());
231 for index in &table.indexes {
232 for col in &index.columns {
233 if !columns.iter().any(|c| &c.name == col) {
234 return Err(format!(
235 "table {:?}: index {:?} names unknown column {col:?}",
236 table.name, index.name
237 ));
238 }
239 }
240 indexes.push(IndexSchema {
241 name: index.name.clone(),
242 columns: index.columns.clone(),
243 unique: index.unique,
244 });
245 }
246 tables.push(TableSchema {
247 name: table.name.clone(),
248 columns,
249 wire_columns,
250 primary_key: table.primary_key.clone(),
251 pk_index,
252 scope_variables,
253 indexes,
254 encrypted_columns,
255 });
256 }
257 Ok(ClientSchema {
258 version: ir.version,
259 tables,
260 })
261}
262
263pub fn parse_schema_json(json: &serde_json::Value) -> Result<ClientSchema, String> {
264 let ir: SchemaIr =
265 serde_json::from_value(json.clone()).map_err(|e| format!("bad schema IR: {e}"))?;
266 compile_schema(&ir)
267}