supercode_interchange/world/codec/
sqlite.rs1use std::path::Path;
7
8use rusqlite::types::ValueRef;
9use rusqlite::{Connection, OpenFlags};
10use serde_json::{Map, Value};
11
12use crate::Result;
13
14fn other(message: String) -> crate::Error {
15 crate::Error::Other(message)
16}
17
18fn open_read_only(path: &Path) -> Result<Connection> {
19 Connection::open_with_flags(
20 path,
21 OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX,
22 )
23 .map_err(|e| other(format!("{}: {e}", path.display())))
24}
25
26fn cell(value: ValueRef<'_>) -> Value {
27 match value {
28 ValueRef::Null => Value::Null,
29 ValueRef::Integer(i) => Value::from(i),
30 ValueRef::Real(f) => serde_json::Number::from_f64(f)
31 .map(Value::Number)
32 .unwrap_or(Value::Null),
33 ValueRef::Text(t) => Value::String(String::from_utf8_lossy(t).into_owned()),
34 ValueRef::Blob(b) => Value::String(String::from_utf8_lossy(b).into_owned()),
35 }
36}
37
38pub fn table_exists(path: &Path, table: &str) -> bool {
40 if !path.is_file() {
41 return false;
42 }
43 let Ok(conn) = open_read_only(path) else {
44 return false;
45 };
46 conn.query_row(
47 "select name from sqlite_master where type='table' and name=?1",
48 [table],
49 |_| Ok(()),
50 )
51 .is_ok()
52}
53
54pub fn read_rows(
56 path: &Path,
57 sql: &str,
58 params: &[&dyn rusqlite::ToSql],
59) -> Result<Option<Vec<Map<String, Value>>>> {
60 if !path.is_file() {
61 return Ok(None);
62 }
63 let conn = open_read_only(path)?;
64 let mut statement = conn
65 .prepare(sql)
66 .map_err(|e| other(format!("{}: {e}", path.display())))?;
67 let names: Vec<String> = statement
68 .column_names()
69 .iter()
70 .map(|s| s.to_string())
71 .collect();
72 let rows = statement
73 .query_map(params, |row| {
74 let mut object = Map::new();
75 for (index, name) in names.iter().enumerate() {
76 object.insert(name.clone(), cell(row.get_ref(index)?));
77 }
78 Ok(object)
79 })
80 .map_err(|e| other(format!("{}: {e}", path.display())))?
81 .collect::<rusqlite::Result<Vec<_>>>()
82 .map_err(|e| other(format!("{}: {e}", path.display())))?;
83 Ok(Some(rows))
84}
85
86#[derive(Debug, Clone)]
88pub enum Param {
89 Null,
91 Int(i64),
93 Real(f64),
95 Text(String),
97}
98
99impl From<&Value> for Param {
100 fn from(value: &Value) -> Self {
101 match value {
102 Value::Null => Self::Null,
103 Value::Bool(b) => Self::Int(i64::from(*b)),
104 Value::Number(n) => n
105 .as_i64()
106 .map(Self::Int)
107 .or_else(|| n.as_f64().map(Self::Real))
108 .unwrap_or(Self::Null),
109 Value::String(s) => Self::Text(s.clone()),
110 other => Self::Text(other.to_string()),
111 }
112 }
113}
114
115impl rusqlite::ToSql for Param {
116 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
117 use rusqlite::types::{ToSqlOutput, Value as SqlValue};
118 Ok(ToSqlOutput::Owned(match self {
119 Self::Null => SqlValue::Null,
120 Self::Int(i) => SqlValue::Integer(*i),
121 Self::Real(f) => SqlValue::Real(*f),
122 Self::Text(t) => SqlValue::Text(t.clone()),
123 }))
124 }
125}
126
127pub fn write_table(path: &Path, ddl: &str, insert: &str, rows: &[Vec<Param>]) -> Result<()> {
129 let conn = Connection::open(path).map_err(|e| other(format!("{}: {e}", path.display())))?;
130 if !ddl.trim().is_empty() {
131 conn.execute_batch(ddl)
132 .map_err(|e| other(format!("{}: {e}", path.display())))?;
133 }
134 conn.execute_batch("begin")
135 .map_err(|e| other(e.to_string()))?;
136 {
137 let mut statement = conn
138 .prepare(insert)
139 .map_err(|e| other(format!("{}: {e}", path.display())))?;
140 for row in rows {
141 let params: Vec<&dyn rusqlite::ToSql> =
142 row.iter().map(|p| p as &dyn rusqlite::ToSql).collect();
143 statement
144 .execute(params.as_slice())
145 .map_err(|e| other(format!("{}: {e}", path.display())))?;
146 }
147 }
148 conn.execute_batch("commit")
149 .map_err(|e| other(e.to_string()))?;
150 Ok(())
151}