1use std::collections::HashMap;
8use std::sync::Arc;
9
10use rusqlite::types::Value;
11use rusqlite::{Connection, OpenFlags, Params};
12
13use crate::error::Result;
14
15pub use rusqlite::types::Value as SqlValue;
16
17pub fn open_bytes(image: &[u8]) -> Result<Connection> {
19 let mut connection = Connection::open_in_memory_with_flags(OpenFlags::SQLITE_OPEN_READ_WRITE)?;
20 connection.deserialize_read_exact("main", image, image.len(), false)?;
21 Ok(connection)
22}
23
24pub fn serialize(connection: &Connection) -> Result<Vec<u8>> {
26 Ok(connection.serialize("main")?.to_vec())
27}
28
29#[derive(Debug, Clone, Default, PartialEq)]
31pub struct Row {
32 columns: Arc<Vec<String>>,
33 index: Arc<HashMap<String, usize>>,
34 values: Vec<Value>,
35}
36
37impl Row {
38 pub fn new(columns: Vec<String>, values: Vec<Value>) -> Self {
40 let index = columns.iter().enumerate().map(|(i, name)| (name.clone(), i)).collect();
41 Row { columns: Arc::new(columns), index: Arc::new(index), values }
42 }
43
44 pub fn columns(&self) -> &[String] {
46 &self.columns
47 }
48
49 pub fn values(&self) -> &[Value] {
51 &self.values
52 }
53
54 pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
56 self.columns.iter().map(String::as_str).zip(self.values.iter())
57 }
58
59 pub fn get(&self, column: &str) -> Option<&Value> {
61 self.index.get(column).map(|i| &self.values[*i])
62 }
63
64 pub fn has(&self, column: &str) -> bool {
66 self.index.contains_key(column)
67 }
68
69 pub fn is_set(&self, column: &str) -> bool {
71 !matches!(self.get(column), None | Some(Value::Null))
72 }
73
74 pub fn int(&self, column: &str) -> i64 {
76 self.opt_int(column).unwrap_or(0)
77 }
78
79 pub fn opt_int(&self, column: &str) -> Option<i64> {
81 match self.get(column)? {
82 Value::Integer(value) => Some(*value),
83 Value::Real(value) => Some(*value as i64),
84 _ => None,
85 }
86 }
87
88 pub fn real(&self, column: &str) -> f64 {
90 self.opt_real(column).unwrap_or(0.0)
91 }
92
93 pub fn opt_real(&self, column: &str) -> Option<f64> {
95 match self.get(column)? {
96 Value::Integer(value) => Some(*value as f64),
97 Value::Real(value) => Some(*value),
98 _ => None,
99 }
100 }
101
102 pub fn text(&self, column: &str) -> Option<String> {
104 match self.get(column)? {
105 Value::Text(value) => Some(value.clone()),
106 Value::Blob(value) => Some(String::from_utf8_lossy(value).into_owned()),
107 _ => None,
108 }
109 }
110
111 pub fn blob(&self, column: &str) -> Option<&[u8]> {
113 match self.get(column)? {
114 Value::Blob(value) => Some(value),
115 Value::Text(value) => Some(value.as_bytes()),
116 _ => None,
117 }
118 }
119
120 pub fn truthy(&self, column: &str) -> bool {
122 match self.get(column) {
123 Some(Value::Integer(value)) => *value != 0,
124 Some(Value::Real(value)) => *value != 0.0,
125 Some(Value::Text(value)) => !value.is_empty(),
126 Some(Value::Blob(value)) => !value.is_empty(),
127 _ => false,
128 }
129 }
130
131 pub fn set(&mut self, column: &str, value: Value) -> bool {
133 match self.index.get(column) {
134 Some(i) => {
135 self.values[*i] = value;
136 true
137 }
138 None => false,
139 }
140 }
141}
142
143pub fn select(connection: &Connection, sql: &str, params: impl Params) -> Result<Vec<Row>> {
145 let mut statement = connection.prepare(sql)?;
146 let columns: Vec<String> = statement.column_names().into_iter().map(str::to_owned).collect();
147 let index: Arc<HashMap<String, usize>> =
148 Arc::new(columns.iter().enumerate().map(|(i, name)| (name.clone(), i)).collect());
149 let columns = Arc::new(columns);
150 let count = columns.len();
151 let rows = statement.query_map(params, |row| {
152 let mut values = Vec::with_capacity(count);
153 for i in 0..count {
154 values.push(row.get::<_, Value>(i)?);
155 }
156 Ok(values)
157 })?;
158 let mut result = Vec::new();
159 for values in rows {
160 result.push(Row { columns: columns.clone(), index: index.clone(), values: values? });
161 }
162 Ok(result)
163}
164
165pub fn select_all(connection: &Connection, table: &str) -> Result<Vec<Row>> {
167 if !table_exists(connection, table)? {
168 return Ok(Vec::new());
169 }
170 select(connection, &format!("SELECT * FROM {} ORDER BY rowid", quote(table)), [])
171}
172
173pub fn table_exists(connection: &Connection, table: &str) -> Result<bool> {
175 let count: i64 = connection.query_row(
176 "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1",
177 [table],
178 |row| row.get(0),
179 )?;
180 Ok(count > 0)
181}
182
183pub fn tables(connection: &Connection) -> Result<Vec<String>> {
185 let mut statement = connection.prepare("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY rowid")?;
186 let names = statement.query_map([], |row| row.get::<_, String>(0))?;
187 Ok(names.collect::<std::result::Result<_, _>>()?)
188}
189
190pub fn columns(connection: &Connection, table: &str) -> Result<Vec<(String, String)>> {
192 let mut statement = connection.prepare(&format!("PRAGMA table_info({})", quote(table)))?;
193 let names = statement.query_map([], |row| Ok((row.get::<_, String>(1)?, row.get::<_, String>(2)?)))?;
194 Ok(names.collect::<std::result::Result<_, _>>()?)
195}
196
197pub fn has_column(connection: &Connection, table: &str, column: &str) -> Result<bool> {
199 Ok(columns(connection, table)?.iter().any(|(name, _)| name == column))
200}
201
202pub fn has_table(connection: &Connection, table: &str) -> Result<bool> {
204 let count: i64 = connection.query_row(
205 "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1",
206 [table],
207 |row| row.get(0),
208 )?;
209 Ok(count > 0)
210}
211
212pub fn quote(identifier: &str) -> String {
214 format!("\"{}\"", identifier.replace('"', "\"\""))
215}
216
217pub fn value_as_text(value: &Value) -> Option<String> {
219 match value {
220 Value::Text(text) => Some(text.clone()),
221 Value::Blob(blob) => Some(String::from_utf8_lossy(blob).into_owned()),
222 _ => None,
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn rows_look_up_columns_by_name() {
232 let connection = Connection::open_in_memory().unwrap();
233 connection
234 .execute_batch(
235 "CREATE TABLE T(_PW_ID INTEGER PRIMARY KEY AUTOINCREMENT, A INTEGER, B REAL, C TEXT, D BLOB);
236 INSERT INTO T(A, B, C, D) VALUES (5, 1.5, 'x', x'6869');",
237 )
238 .unwrap();
239 let image = serialize(&connection).unwrap();
240 let reopened = open_bytes(&image).unwrap();
241 let rows = select_all(&reopened, "T").unwrap();
242 assert_eq!(rows.len(), 1);
243 let row = &rows[0];
244 assert_eq!(row.int("A"), 5);
245 assert_eq!(row.real("B"), 1.5);
246 assert_eq!(row.text("C").as_deref(), Some("x"));
247 assert_eq!(row.text("D").as_deref(), Some("hi"));
248 assert!(!row.has("Missing"));
249 assert_eq!(row.int("Missing"), 0);
250 assert!(select_all(&reopened, "Nope").unwrap().is_empty());
251 assert_eq!(columns(&reopened, "T").unwrap()[1], ("A".to_string(), "INTEGER".to_string()));
252 }
253}