zed/crates/sqlez/src/connection.rs

263 lines
7.1 KiB
Rust
Raw Normal View History

2022-11-01 20:32:46 +00:00
use std::{
ffi::{CStr, CString},
marker::PhantomData,
path::Path,
2022-11-01 20:32:46 +00:00
};
use anyhow::{anyhow, Result};
use libsqlite3_sys::*;
pub struct Connection {
pub(crate) sqlite3: *mut sqlite3,
persistent: bool,
phantom: PhantomData<sqlite3>,
}
unsafe impl Send for Connection {}
impl Connection {
fn open(uri: &str, persistent: bool) -> Result<Self> {
let mut connection = Self {
sqlite3: 0 as *mut _,
persistent,
phantom: PhantomData,
};
let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_READWRITE;
unsafe {
sqlite3_open_v2(
CString::new(uri)?.as_ptr(),
&mut connection.sqlite3,
flags,
0 as *const _,
);
2022-11-02 20:26:23 +00:00
// Turn on extended error codes
sqlite3_extended_result_codes(connection.sqlite3, 1);
2022-11-01 20:32:46 +00:00
connection.last_error()?;
}
Ok(connection)
}
/// Attempts to open the database at uri. If it fails, a shared memory db will be opened
/// instead.
pub fn open_file(uri: &str) -> Self {
2022-11-18 22:20:52 +00:00
Self::open(uri, true).unwrap_or_else(|_| Self::open_memory(Some(uri)))
2022-11-01 20:32:46 +00:00
}
2022-11-18 22:20:52 +00:00
pub fn open_memory(uri: Option<&str>) -> Self {
let in_memory_path = if let Some(uri) = uri {
format!("file:{}?mode=memory&cache=shared", uri)
} else {
":memory:".to_string()
};
2022-11-01 20:32:46 +00:00
Self::open(&in_memory_path, false).expect("Could not create fallback in memory db")
}
pub fn persistent(&self) -> bool {
self.persistent
}
pub fn backup_main(&self, destination: &Connection) -> Result<()> {
unsafe {
let backup = sqlite3_backup_init(
destination.sqlite3,
CString::new("main")?.as_ptr(),
self.sqlite3,
CString::new("main")?.as_ptr(),
);
sqlite3_backup_step(backup, -1);
sqlite3_backup_finish(backup);
destination.last_error()
}
}
pub fn backup_main_to(&self, destination: impl AsRef<Path>) -> Result<()> {
let destination = Self::open_file(destination.as_ref().to_string_lossy().as_ref());
self.backup_main(&destination)
}
2022-11-01 20:32:46 +00:00
pub(crate) fn last_error(&self) -> Result<()> {
unsafe {
let code = sqlite3_errcode(self.sqlite3);
const NON_ERROR_CODES: &[i32] = &[SQLITE_OK, SQLITE_ROW];
if NON_ERROR_CODES.contains(&code) {
return Ok(());
}
let message = sqlite3_errmsg(self.sqlite3);
let message = if message.is_null() {
None
} else {
Some(
String::from_utf8_lossy(CStr::from_ptr(message as *const _).to_bytes())
.into_owned(),
)
};
Err(anyhow!(
"Sqlite call failed with code {} and message: {:?}",
code as isize,
message
))
}
2022-11-01 20:32:46 +00:00
}
}
impl Drop for Connection {
fn drop(&mut self) {
unsafe { sqlite3_close(self.sqlite3) };
}
}
#[cfg(test)]
mod test {
use anyhow::Result;
use indoc::indoc;
2022-11-07 01:00:34 +00:00
use crate::connection::Connection;
2022-11-01 20:32:46 +00:00
#[test]
fn string_round_trips() -> Result<()> {
2022-11-18 22:20:52 +00:00
let connection = Connection::open_memory(Some("string_round_trips"));
2022-11-01 20:32:46 +00:00
connection
.exec(indoc! {"
CREATE TABLE text (
text TEXT
);"})
2022-11-07 01:00:34 +00:00
.unwrap()()
.unwrap();
2022-11-01 20:32:46 +00:00
let text = "Some test text";
connection
.exec_bound("INSERT INTO text (text) VALUES (?);")
2022-11-07 01:00:34 +00:00
.unwrap()(text)
.unwrap();
2022-11-01 20:32:46 +00:00
assert_eq!(
2022-11-07 01:00:34 +00:00
connection.select_row("SELECT text FROM text;").unwrap()().unwrap(),
Some(text.to_string())
2022-11-01 20:32:46 +00:00
);
Ok(())
}
#[test]
fn tuple_round_trips() {
2022-11-18 22:20:52 +00:00
let connection = Connection::open_memory(Some("tuple_round_trips"));
2022-11-01 20:32:46 +00:00
connection
.exec(indoc! {"
CREATE TABLE test (
text TEXT,
integer INTEGER,
blob BLOB
);"})
2022-11-07 01:00:34 +00:00
.unwrap()()
.unwrap();
2022-11-01 20:32:46 +00:00
let tuple1 = ("test".to_string(), 64, vec![0, 1, 2, 4, 8, 16, 32, 64]);
let tuple2 = ("test2".to_string(), 32, vec![64, 32, 16, 8, 4, 2, 1, 0]);
let mut insert = connection
.exec_bound::<(String, usize, Vec<u8>)>(
2022-11-07 01:00:34 +00:00
"INSERT INTO test (text, integer, blob) VALUES (?, ?, ?)",
)
2022-11-01 20:32:46 +00:00
.unwrap();
2022-11-07 01:00:34 +00:00
insert(tuple1.clone()).unwrap();
insert(tuple2.clone()).unwrap();
2022-11-01 20:32:46 +00:00
assert_eq!(
connection
2022-11-07 01:00:34 +00:00
.select::<(String, usize, Vec<u8>)>("SELECT * FROM test")
.unwrap()()
.unwrap(),
2022-11-01 20:32:46 +00:00
vec![tuple1, tuple2]
);
}
#[test]
fn bool_round_trips() {
2022-11-18 22:20:52 +00:00
let connection = Connection::open_memory(Some("bool_round_trips"));
connection
.exec(indoc! {"
CREATE TABLE bools (
t INTEGER,
f INTEGER
);"})
2022-11-07 01:00:34 +00:00
.unwrap()()
.unwrap();
connection
.exec_bound("INSERT INTO bools(t, f) VALUES (?, ?)")
2022-11-07 01:00:34 +00:00
.unwrap()((true, false))
.unwrap();
assert_eq!(
2022-11-07 01:00:34 +00:00
connection
.select_row::<(bool, bool)>("SELECT * FROM bools;")
.unwrap()()
.unwrap(),
Some((true, false))
);
}
2022-11-01 20:32:46 +00:00
#[test]
fn backup_works() {
2022-11-18 22:20:52 +00:00
let connection1 = Connection::open_memory(Some("backup_works"));
2022-11-01 20:32:46 +00:00
connection1
.exec(indoc! {"
CREATE TABLE blobs (
data BLOB
);"})
2022-11-07 01:00:34 +00:00
.unwrap()()
.unwrap();
let blob = vec![0, 1, 2, 4, 8, 16, 32, 64];
connection1
.exec_bound::<Vec<u8>>("INSERT INTO blobs (data) VALUES (?);")
2022-11-07 01:00:34 +00:00
.unwrap()(blob.clone())
.unwrap();
2022-11-01 20:32:46 +00:00
// Backup connection1 to connection2
2022-11-18 22:20:52 +00:00
let connection2 = Connection::open_memory(Some("backup_works_other"));
2022-11-01 20:32:46 +00:00
connection1.backup_main(&connection2).unwrap();
// Delete the added blob and verify its deleted on the other side
let read_blobs = connection1
2022-11-07 01:00:34 +00:00
.select::<Vec<u8>>("SELECT * FROM blobs;")
.unwrap()()
.unwrap();
2022-11-01 20:32:46 +00:00
assert_eq!(read_blobs, vec![blob]);
}
2022-11-02 20:26:23 +00:00
#[test]
2022-11-07 01:00:34 +00:00
fn multi_step_statement_works() {
2022-11-18 22:20:52 +00:00
let connection = Connection::open_memory(Some("multi_step_statement_works"));
2022-11-02 20:26:23 +00:00
2022-11-07 01:00:34 +00:00
connection
.exec(indoc! {"
CREATE TABLE test (
col INTEGER
)"})
.unwrap()()
.unwrap();
2022-11-02 20:26:23 +00:00
2022-11-07 01:00:34 +00:00
connection
.exec(indoc! {"
INSERT INTO test(col) VALUES (2)"})
.unwrap()()
.unwrap();
2022-11-02 20:26:23 +00:00
2022-11-07 01:00:34 +00:00
assert_eq!(
connection
.select_row::<usize>("SELECt * FROM test")
.unwrap()()
.unwrap(),
Some(2)
);
2022-11-02 20:26:23 +00:00
}
2022-11-01 20:32:46 +00:00
}