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}
72
73#[derive(Debug, Clone)]
77pub struct EncryptedColumn {
78 pub index: usize,
79 pub declared_type: String,
80}
81
82#[derive(Debug, Clone)]
83pub struct TableSchema {
84 pub name: String,
85 pub columns: Vec<Column>,
90 pub wire_columns: Vec<Column>,
95 pub primary_key: String,
96 pub pk_index: usize,
97 pub scope_variables: Vec<ScopeVariable>,
98 pub indexes: Vec<IndexSchema>,
100 pub encrypted_columns: Vec<EncryptedColumn>,
102}
103
104impl TableSchema {
105 pub fn has_encrypted_columns(&self) -> bool {
107 !self.encrypted_columns.is_empty()
108 }
109}
110
111impl TableSchema {
112 pub fn scope_column(&self, variable: &str) -> Option<&str> {
115 self.scope_variables
116 .iter()
117 .find(|s| s.variable == variable)
118 .map(|s| s.column.as_str())
119 }
120}
121
122#[derive(Debug, Clone)]
123pub struct ClientSchema {
124 pub version: i32,
125 pub tables: Vec<TableSchema>,
126}
127
128impl ClientSchema {
129 pub fn table(&self, name: &str) -> Option<&TableSchema> {
130 self.tables.iter().find(|t| t.name == name)
131 }
132}
133
134fn parse_column_type(name: &str) -> Result<ColumnType, String> {
135 match name {
136 "string" => Ok(ColumnType::String),
137 "integer" => Ok(ColumnType::Integer),
138 "float" => Ok(ColumnType::Float),
139 "boolean" => Ok(ColumnType::Boolean),
140 "json" => Ok(ColumnType::Json),
141 "bytes" => Ok(ColumnType::Bytes),
142 "blob_ref" => Ok(ColumnType::BlobRef),
143 "crdt" => Ok(ColumnType::Crdt),
144 other => Err(format!("unknown column type {other:?}")),
145 }
146}
147
148fn parse_pattern_variable(pattern: &str) -> Result<String, String> {
150 let open = pattern
151 .find('{')
152 .ok_or_else(|| format!("scope pattern {pattern:?} has no {{variable}}"))?;
153 let close = pattern
154 .rfind('}')
155 .filter(|end| *end > open)
156 .ok_or_else(|| format!("scope pattern {pattern:?} has no closing brace"))?;
157 let variable = &pattern[open + 1..close];
158 if variable.is_empty() {
159 return Err(format!("scope pattern {pattern:?} has an empty variable"));
160 }
161 Ok(variable.to_owned())
162}
163
164pub fn compile_schema(ir: &SchemaIr) -> Result<ClientSchema, String> {
165 let mut tables = Vec::with_capacity(ir.tables.len());
166 for table in &ir.tables {
167 let mut wire_columns = Vec::with_capacity(table.columns.len());
171 let mut columns = Vec::with_capacity(table.columns.len());
172 let mut encrypted_columns = Vec::new();
173 for (index, column) in table.columns.iter().enumerate() {
174 let wire_ty = parse_column_type(&column.column_type)?;
175 wire_columns.push(Column {
176 name: column.name.clone(),
177 ty: wire_ty,
178 nullable: column.nullable,
179 });
180 if column.encrypted {
181 let declared_name = column.declared_type.clone().ok_or_else(|| {
182 format!(
183 "table {:?}: encrypted column {:?} has no declaredType (§5.11)",
184 table.name, column.name
185 )
186 })?;
187 let declared_ty = parse_column_type(&declared_name)?;
188 columns.push(Column {
189 name: column.name.clone(),
190 ty: declared_ty,
191 nullable: column.nullable,
192 });
193 encrypted_columns.push(EncryptedColumn {
194 index,
195 declared_type: declared_name,
196 });
197 } else {
198 columns.push(Column {
199 name: column.name.clone(),
200 ty: wire_ty,
201 nullable: column.nullable,
202 });
203 }
204 }
205 let pk_index = columns
206 .iter()
207 .position(|c| c.name == table.primary_key)
208 .ok_or_else(|| {
209 format!(
210 "table {:?}: primary key {:?} is not a column",
211 table.name, table.primary_key
212 )
213 })?;
214 let mut scope_variables = Vec::with_capacity(table.scopes.len());
215 for scope in &table.scopes {
216 let variable = parse_pattern_variable(&scope.pattern)?;
217 let column = scope.column.clone().unwrap_or_else(|| variable.clone());
218 scope_variables.push(ScopeVariable { variable, column });
219 }
220 let mut indexes = Vec::with_capacity(table.indexes.len());
221 for index in &table.indexes {
222 for col in &index.columns {
223 if !columns.iter().any(|c| &c.name == col) {
224 return Err(format!(
225 "table {:?}: index {:?} names unknown column {col:?}",
226 table.name, index.name
227 ));
228 }
229 }
230 indexes.push(IndexSchema {
231 name: index.name.clone(),
232 columns: index.columns.clone(),
233 unique: index.unique,
234 });
235 }
236 tables.push(TableSchema {
237 name: table.name.clone(),
238 columns,
239 wire_columns,
240 primary_key: table.primary_key.clone(),
241 pk_index,
242 scope_variables,
243 indexes,
244 encrypted_columns,
245 });
246 }
247 Ok(ClientSchema {
248 version: ir.version,
249 tables,
250 })
251}
252
253pub fn parse_schema_json(json: &serde_json::Value) -> Result<ClientSchema, String> {
254 let ir: SchemaIr =
255 serde_json::from_value(json.clone()).map_err(|e| format!("bad schema IR: {e}"))?;
256 compile_schema(&ir)
257}