1use std::collections::{BTreeMap, HashMap};
15
16use rusqlite::Connection;
17
18use crate::database::{self, select_all};
19use crate::error::Result;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize)]
23#[non_exhaustive]
24pub enum DataType {
25 Integer,
27 Real,
29 Text,
31 Blob,
33 Unknown(i64),
35}
36
37impl DataType {
38 pub fn from_code(code: i64) -> Self {
40 match code {
41 1 => DataType::Integer,
42 2 => DataType::Real,
43 3 => DataType::Text,
44 4 => DataType::Blob,
45 other => DataType::Unknown(other),
46 }
47 }
48
49 pub fn code(&self) -> i64 {
51 match self {
52 DataType::Integer => 1,
53 DataType::Real => 2,
54 DataType::Text => 3,
55 DataType::Blob => 4,
56 DataType::Unknown(code) => *code,
57 }
58 }
59
60 pub fn sql_type(&self) -> &'static str {
62 match self {
63 DataType::Integer => "INTEGER",
64 DataType::Real => "REAL",
65 DataType::Text => "TEXT",
66 DataType::Blob => "BLOB",
67 DataType::Unknown(_) => "",
68 }
69 }
70}
71
72pub fn element_kind_name(code: i64) -> &'static str {
76 match code {
77 0 => "none",
78 1 => "project",
79 2 => "canvas",
80 4 => "layer",
81 8 => "layer_object",
82 1024 => "bank",
83 2048 => "item",
84 4096 => "track",
85 _ => "unknown",
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
91pub struct Param {
92 pub table: String,
94 pub column: String,
96 pub data_type: DataType,
98 pub flag: i64,
100 pub owner_type: i64,
102 pub lock_type: i64,
104 pub lock_specified: i64,
106 pub alternative_lock_type: Option<i64>,
108 pub alternative_lock_specified: Option<i64>,
110 pub link_table: Option<String>,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
116pub struct Elem {
117 pub table: String,
119 pub elem_type: i64,
121 pub max_index: i64,
123}
124
125#[derive(Debug, Clone, Default, serde::Serialize)]
127pub struct Schema {
128 pub params: Vec<Param>,
130 pub elems: Vec<Elem>,
132 pub external_columns: Vec<(String, String)>,
134}
135
136impl Schema {
137 pub fn read(connection: &Connection) -> Result<Self> {
139 let params = select_all(connection, "ParamScheme")?
140 .iter()
141 .map(|row| Param {
142 table: row.text("TableName").unwrap_or_default(),
143 column: row.text("LabelName").unwrap_or_default(),
144 data_type: DataType::from_code(row.int("DataType")),
145 flag: row.int("Flag"),
146 owner_type: row.int("OwnerType"),
147 lock_type: row.int("LockType"),
148 lock_specified: row.int("LockSpecified"),
149 alternative_lock_type: row.opt_int("AlternativeLockType"),
150 alternative_lock_specified: row.opt_int("AlternativeLockSpecified"),
151 link_table: row.text("LinkTable").filter(|text| !text.is_empty()),
152 })
153 .collect();
154 let elems = select_all(connection, "ElemScheme")?
155 .iter()
156 .map(|row| Elem {
157 table: row.text("TableName").unwrap_or_default(),
158 elem_type: row.int("ElemType"),
159 max_index: row.int("MaxIndex"),
160 })
161 .collect();
162 let external_columns = select_all(connection, "ExternalTableAndColumnName")?
163 .iter()
164 .map(|row| (row.text("TableName").unwrap_or_default(), row.text("ColumnName").unwrap_or_default()))
165 .collect();
166 Ok(Schema { params, elems, external_columns })
167 }
168
169 pub fn param(&self, table: &str, column: &str) -> Option<&Param> {
171 self.params.iter().find(|param| param.table == table && param.column == column)
172 }
173
174 pub fn table_params(&self, table: &str) -> impl Iterator<Item = &Param> {
176 self.params.iter().filter(move |param| param.table == table)
177 }
178
179 pub fn elem(&self, table: &str) -> Option<&Elem> {
181 self.elems.iter().find(|elem| elem.table == table)
182 }
183
184 pub fn is_external(&self, table: &str, column: &str) -> bool {
186 self.external_columns.iter().any(|(t, c)| t == table && c == column)
187 }
188
189 pub fn links(&self) -> BTreeMap<(String, String), String> {
191 self.params
192 .iter()
193 .filter_map(|param| Some(((param.table.clone(), param.column.clone()), param.link_table.clone()?)))
194 .collect()
195 }
196
197 pub fn table_names(&self) -> Vec<String> {
199 let mut seen = HashMap::new();
200 let mut names = Vec::new();
201 for param in &self.params {
202 if seen.insert(param.table.clone(), ()).is_none() {
203 names.push(param.table.clone());
204 }
205 }
206 names
207 }
208}
209
210pub fn allocate_main_id(connection: &Connection, table: &str) -> Result<i64> {
214 let recorded: i64 = connection
215 .query_row("SELECT MaxIndex FROM ElemScheme WHERE TableName = ?1", [table], |row| row.get::<_, Option<i64>>(0))
216 .ok()
217 .flatten()
218 .unwrap_or(0);
219 let used: i64 = if database::table_exists(connection, table)? {
220 connection.query_row(
221 &format!("SELECT COALESCE(MAX(MainId), 0) FROM {}", database::quote(table)),
222 [],
223 |row| row.get(0),
224 )?
225 } else {
226 0
227 };
228 let next = recorded.max(used) + 1;
229 let updated = connection.execute("UPDATE ElemScheme SET MaxIndex = ?1 WHERE TableName = ?2", (next, table))?;
230 if updated == 0 && database::table_exists(connection, "ElemScheme")? {
231 connection
232 .execute("INSERT INTO ElemScheme(TableName, ElemType, MaxIndex) VALUES (?1, 0, ?2)", (table, next))?;
233 }
234 Ok(next)
235}