sim_platform_sqlite/driver/
introspection.rs1use super::{
2 AdoptionManifest, BaseDomain, ColumnName, Connection, IndexName, PhysicalColumn, PhysicalIndex,
3 PhysicalSchema, PhysicalTable, ProviderName, RevisionName, SchemaAttestation, SchemaName,
4 SiteError, Symbol, TableName, map_error, provider_symbol, relation_id_text, valid_source,
5};
6
7pub fn introspect_connection(
14 connection: &Connection,
15 revision: RevisionName,
16) -> Result<PhysicalSchema, SiteError> {
17 let mut tables_stmt = connection.prepare("SELECT name FROM sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__sim_%' ORDER BY name").map_err(|error| map_error(&error))?;
18 let names = tables_stmt
19 .query_map([], |row| row.get::<_, String>(0))
20 .map_err(|error| map_error(&error))?
21 .collect::<Result<Vec<_>, _>>()
22 .map_err(|error| map_error(&error))?;
23 let mut tables = Vec::new();
24 for name in names {
25 if !valid_source(&Symbol::new(name.as_str())) {
26 return Err(SiteError::Conversion);
27 }
28 let mut columns_stmt = connection
29 .prepare(&format!(
30 "PRAGMA table_info(\"{}\")",
31 name.replace('"', "\"\"")
32 ))
33 .map_err(|error| map_error(&error))?;
34 let columns = columns_stmt
35 .query_map([], |row| {
36 Ok((
37 row.get::<_, i64>(0)?,
38 row.get::<_, String>(1)?,
39 row.get::<_, String>(2)?,
40 row.get::<_, i64>(3)?,
41 ))
42 })
43 .map_err(|error| map_error(&error))?
44 .map(|result| {
45 let (ordinal, name, ty, notnull) = result.map_err(|error| map_error(&error))?;
46 let (domain, storage) = affinity(&ty);
47 Ok(PhysicalColumn {
48 name: ColumnName::new(Symbol::new(name)).map_err(|_| SiteError::Conversion)?,
49 domain: domain.id(),
50 storage,
51 nullable: notnull == 0,
52 ordinal: u32::try_from(ordinal).map_err(|_| SiteError::Conversion)?,
53 })
54 })
55 .collect::<Result<Vec<_>, SiteError>>()?;
56 let mut indexes_stmt = connection
57 .prepare(&format!(
58 "PRAGMA index_list(\"{}\")",
59 name.replace('"', "\"\"")
60 ))
61 .map_err(|error| map_error(&error))?;
62 let index_rows = indexes_stmt
63 .query_map([], |row| {
64 Ok((row.get::<_, String>(1)?, row.get::<_, i64>(2)?))
65 })
66 .map_err(|error| map_error(&error))?
67 .collect::<Result<Vec<_>, _>>()
68 .map_err(|error| map_error(&error))?;
69 let mut indexes = Vec::new();
70 for (index_name, unique) in index_rows {
71 if index_name.starts_with("sqlite_") {
72 continue;
73 }
74 let mut info = connection
75 .prepare(&format!(
76 "PRAGMA index_info(\"{}\")",
77 index_name.replace('"', "\"\"")
78 ))
79 .map_err(|error| map_error(&error))?;
80 let keys = info
81 .query_map([], |row| row.get::<_, String>(2))
82 .map_err(|error| map_error(&error))?
83 .map(|v| {
84 ColumnName::new(Symbol::new(v.map_err(|error| map_error(&error))?))
85 .map_err(|_| SiteError::Conversion)
86 })
87 .collect::<Result<Vec<_>, _>>()?;
88 indexes.push(PhysicalIndex {
89 name: IndexName::new(Symbol::new(index_name)).map_err(|_| SiteError::Conversion)?,
90 columns: keys,
91 unique: unique != 0,
92 });
93 }
94 tables.push(PhysicalTable {
95 name: TableName::new(Symbol::new(name)).map_err(|_| SiteError::Conversion)?,
96 columns,
97 indexes,
98 });
99 }
100 PhysicalSchema::normalize(
101 ProviderName::new(provider_symbol()).map_err(|_| SiteError::Conversion)?,
102 SchemaName::new(Symbol::new("main")).map_err(|_| SiteError::Conversion)?,
103 revision,
104 tables,
105 )
106 .map_err(|_| SiteError::Conversion)
107}
108
109pub fn verify_or_adopt(
120 connection: &mut Connection,
121 logical_schema: sim_relation_core::RelationId,
122 revision: sim_relation_core::RelationId,
123 revision_name: RevisionName,
124 adoption: Option<&AdoptionManifest>,
125) -> Result<SchemaAttestation, SiteError> {
126 let physical_schema = introspect_connection(connection, revision_name)?
127 .id()
128 .map_err(|_| SiteError::Provider)?;
129 let existing = connection
130 .query_row(
131 "SELECT logical_schema, physical_schema, revision FROM __sim_relation_attestation WHERE singleton=1",
132 [],
133 |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?, row.get::<_, String>(2)?)),
134 )
135 .ok();
136 if let Some((logical, physical, recorded_revision)) = existing {
137 if logical != relation_id_text(&logical_schema)
138 || physical != relation_id_text(&physical_schema)
139 || recorded_revision != relation_id_text(&revision)
140 {
141 return Err(SiteError::Drift);
142 }
143 } else {
144 adoption
145 .ok_or(SiteError::Drift)?
146 .verify(&physical_schema)
147 .map_err(|_| SiteError::Drift)?;
148 let transaction = connection
149 .transaction()
150 .map_err(|error| map_error(&error))?;
151 transaction.execute_batch("CREATE TABLE __sim_relation_attestation (singleton INTEGER PRIMARY KEY CHECK(singleton=1), logical_schema TEXT NOT NULL, physical_schema TEXT NOT NULL, revision TEXT NOT NULL)").map_err(|error| map_error(&error))?;
152 transaction
153 .execute(
154 "INSERT INTO __sim_relation_attestation VALUES (1, ?1, ?2, ?3)",
155 (
156 relation_id_text(&logical_schema),
157 relation_id_text(&physical_schema),
158 relation_id_text(&revision),
159 ),
160 )
161 .map_err(|error| map_error(&error))?;
162 transaction.commit().map_err(|error| map_error(&error))?;
163 }
164 Ok(SchemaAttestation {
165 logical_schema,
166 physical_schema,
167 revision,
168 })
169}
170fn affinity(value: &str) -> (BaseDomain, sim_relation_core::StorageRepr) {
171 let upper = value.to_ascii_uppercase();
172 if upper.contains("INT") {
173 (BaseDomain::I64, sim_relation_core::StorageRepr::I64)
174 } else if upper.contains("CHAR") || upper.contains("CLOB") || upper.contains("TEXT") {
175 (BaseDomain::Text, sim_relation_core::StorageRepr::Text)
176 } else if upper.contains("REAL") || upper.contains("FLOA") || upper.contains("DOUB") {
177 (BaseDomain::F64, sim_relation_core::StorageRepr::F64)
178 } else {
179 (BaseDomain::Bytes, sim_relation_core::StorageRepr::Bytes)
180 }
181}