Refined sqlez, implemented 60% of workspace serialization sql

This commit is contained in:
Mikayla Maki 2022-11-04 13:22:35 -07:00
parent 6b214acbc4
commit 0186289420
11 changed files with 569 additions and 433 deletions

View file

@ -178,8 +178,29 @@ impl<T1: Column, T2: Column, T3: Column, T4: Column> Column for (T1, T2, T3, T4)
let (first, next_index) = T1::column(statement, start_index)?;
let (second, next_index) = T2::column(statement, next_index)?;
let (third, next_index) = T3::column(statement, next_index)?;
let (forth, next_index) = T4::column(statement, next_index)?;
Ok(((first, second, third, forth), next_index))
let (fourth, next_index) = T4::column(statement, next_index)?;
Ok(((first, second, third, fourth), next_index))
}
}
impl<T1: Bind, T2: Bind, T3: Bind, T4: Bind, T5: Bind> Bind for (T1, T2, T3, T4, T5) {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
let next_index = self.0.bind(statement, start_index)?;
let next_index = self.1.bind(statement, next_index)?;
let next_index = self.2.bind(statement, next_index)?;
let next_index = self.3.bind(statement, next_index)?;
self.4.bind(statement, next_index)
}
}
impl<T1: Column, T2: Column, T3: Column, T4: Column, T5: Column> Column for (T1, T2, T3, T4, T5) {
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let (first, next_index) = T1::column(statement, start_index)?;
let (second, next_index) = T2::column(statement, next_index)?;
let (third, next_index) = T3::column(statement, next_index)?;
let (fourth, next_index) = T4::column(statement, next_index)?;
let (fifth, next_index) = T5::column(statement, next_index)?;
Ok(((first, second, third, fourth, fifth), next_index))
}
}

View file

@ -99,7 +99,29 @@ impl Connection {
}
pub(crate) fn last_error(&self) -> Result<()> {
unsafe { error_to_result(sqlite3_errcode(self.sqlite3)) }
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
))
}
}
}
@ -109,31 +131,6 @@ impl Drop for Connection {
}
}
pub(crate) fn error_to_result(code: std::os::raw::c_int) -> Result<()> {
const NON_ERROR_CODES: &[i32] = &[SQLITE_OK, SQLITE_ROW];
unsafe {
if NON_ERROR_CODES.contains(&code) {
return Ok(());
}
let message = sqlite3_errstr(code);
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
))
}
}
#[cfg(test)]
mod test {
use anyhow::Result;
@ -213,6 +210,35 @@ mod test {
);
}
#[test]
fn bool_round_trips() {
let connection = Connection::open_memory("bool_round_trips");
connection
.exec(indoc! {"
CREATE TABLE bools (
t INTEGER,
f INTEGER
);"})
.unwrap();
connection
.prepare("INSERT INTO bools(t, f) VALUES (?, ?);")
.unwrap()
.with_bindings((true, false))
.unwrap()
.exec()
.unwrap();
assert_eq!(
&connection
.prepare("SELECT * FROM bools;")
.unwrap()
.row::<(bool, bool)>()
.unwrap(),
&(true, false)
);
}
#[test]
fn backup_works() {
let connection1 = Connection::open_memory("backup_works");

View file

@ -8,11 +8,11 @@ impl Connection {
// point is released.
pub fn with_savepoint<R, F>(&self, name: impl AsRef<str>, f: F) -> Result<R>
where
F: FnOnce(&Connection) -> Result<R>,
F: FnOnce() -> Result<R>,
{
let name = name.as_ref().to_owned();
self.exec(format!("SAVEPOINT {}", &name))?;
let result = f(self);
let result = f();
match result {
Ok(_) => {
self.exec(format!("RELEASE {}", name))?;
@ -30,11 +30,11 @@ impl Connection {
// point is released.
pub fn with_savepoint_rollback<R, F>(&self, name: impl AsRef<str>, f: F) -> Result<Option<R>>
where
F: FnOnce(&Connection) -> Result<Option<R>>,
F: FnOnce() -> Result<Option<R>>,
{
let name = name.as_ref().to_owned();
self.exec(format!("SAVEPOINT {}", &name))?;
let result = f(self);
let result = f();
match result {
Ok(Some(_)) => {
self.exec(format!("RELEASE {}", name))?;
@ -69,21 +69,21 @@ mod tests {
let save1_text = "test save1";
let save2_text = "test save2";
connection.with_savepoint("first", |save1| {
save1
connection.with_savepoint("first", || {
connection
.prepare("INSERT INTO text(text, idx) VALUES (?, ?)")?
.with_bindings((save1_text, 1))?
.exec()?;
assert!(save1
.with_savepoint("second", |save2| -> Result<Option<()>, anyhow::Error> {
save2
assert!(connection
.with_savepoint("second", || -> Result<Option<()>, anyhow::Error> {
connection
.prepare("INSERT INTO text(text, idx) VALUES (?, ?)")?
.with_bindings((save2_text, 2))?
.exec()?;
assert_eq!(
save2
connection
.prepare("SELECT text FROM text ORDER BY text.idx ASC")?
.rows::<String>()?,
vec![save1_text, save2_text],
@ -95,20 +95,20 @@ mod tests {
.is_some());
assert_eq!(
save1
connection
.prepare("SELECT text FROM text ORDER BY text.idx ASC")?
.rows::<String>()?,
vec![save1_text],
);
save1.with_savepoint_rollback::<(), _>("second", |save2| {
save2
connection.with_savepoint_rollback::<(), _>("second", || {
connection
.prepare("INSERT INTO text(text, idx) VALUES (?, ?)")?
.with_bindings((save2_text, 2))?
.exec()?;
assert_eq!(
save2
connection
.prepare("SELECT text FROM text ORDER BY text.idx ASC")?
.rows::<String>()?,
vec![save1_text, save2_text],
@ -118,20 +118,20 @@ mod tests {
})?;
assert_eq!(
save1
connection
.prepare("SELECT text FROM text ORDER BY text.idx ASC")?
.rows::<String>()?,
vec![save1_text],
);
save1.with_savepoint_rollback("second", |save2| {
save2
connection.with_savepoint_rollback("second", || {
connection
.prepare("INSERT INTO text(text, idx) VALUES (?, ?)")?
.with_bindings((save2_text, 2))?
.exec()?;
assert_eq!(
save2
connection
.prepare("SELECT text FROM text ORDER BY text.idx ASC")?
.rows::<String>()?,
vec![save1_text, save2_text],
@ -141,7 +141,7 @@ mod tests {
})?;
assert_eq!(
save1
connection
.prepare("SELECT text FROM text ORDER BY text.idx ASC")?
.rows::<String>()?,
vec![save1_text, save2_text],

View file

@ -6,7 +6,7 @@ use anyhow::{anyhow, Context, Result};
use libsqlite3_sys::*;
use crate::bindable::{Bind, Column};
use crate::connection::{error_to_result, Connection};
use crate::connection::Connection;
pub struct Statement<'a> {
raw_statement: *mut sqlite3_stmt,
@ -48,7 +48,9 @@ impl<'a> Statement<'a> {
0 as *mut _,
);
connection.last_error().context("Prepare call failed.")?;
connection
.last_error()
.with_context(|| format!("Prepare call failed for query:\n{}", query.as_ref()))?;
}
Ok(statement)
@ -309,10 +311,7 @@ impl<'a> Statement<'a> {
impl<'a> Drop for Statement<'a> {
fn drop(&mut self) {
unsafe {
let error = sqlite3_finalize(self.raw_statement);
error_to_result(error).expect("failed error");
};
unsafe { sqlite3_finalize(self.raw_statement) };
}
}
@ -327,9 +326,9 @@ mod test {
let connection1 = Connection::open_memory("blob_round_trips");
connection1
.exec(indoc! {"
CREATE TABLE blobs (
data BLOB
);"})
CREATE TABLE blobs (
data BLOB
);"})
.unwrap();
let blob = &[0, 1, 2, 4, 8, 16, 32, 64];
@ -352,4 +351,41 @@ mod test {
let mut read = connection1.prepare("SELECT * FROM blobs;").unwrap();
assert_eq!(read.step().unwrap(), StepResult::Done);
}
#[test]
pub fn maybe_returns_options() {
let connection = Connection::open_memory("maybe_returns_options");
connection
.exec(indoc! {"
CREATE TABLE texts (
text TEXT
);"})
.unwrap();
assert!(connection
.prepare("SELECT text FROM texts")
.unwrap()
.maybe_row::<String>()
.unwrap()
.is_none());
let text_to_insert = "This is a test";
connection
.prepare("INSERT INTO texts VALUES (?)")
.unwrap()
.with_bindings(text_to_insert)
.unwrap()
.exec()
.unwrap();
assert_eq!(
connection
.prepare("SELECT text FROM texts")
.unwrap()
.maybe_row::<String>()
.unwrap(),
Some(text_to_insert.to_string())
);
}
}