Changed SQLez migrations to be executed eagerly
Added fix for terminal working directory's sometimes getting lost co-authored-by: Kay <kay@zed.dev>
This commit is contained in:
parent
65641b1d3e
commit
e682e2dd72
5 changed files with 130 additions and 3 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -6015,6 +6015,7 @@ dependencies = [
|
||||||
"libsqlite3-sys",
|
"libsqlite3-sys",
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
"smol",
|
"smol",
|
||||||
|
"sqlez_macros",
|
||||||
"thread_local",
|
"thread_local",
|
||||||
"uuid 1.2.2",
|
"uuid 1.2.2",
|
||||||
]
|
]
|
||||||
|
|
|
@ -15,4 +15,7 @@ thread_local = "1.1.4"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
uuid = { version = "1.1.2", features = ["v4"] }
|
uuid = { version = "1.1.2", features = ["v4"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
sqlez_macros = { path = "../sqlez_macros"}
|
|
@ -4,12 +4,36 @@
|
||||||
// to creating a new db?)
|
// to creating a new db?)
|
||||||
// Otherwise any missing migrations are run on the connection
|
// Otherwise any missing migrations are run on the connection
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
use indoc::{formatdoc, indoc};
|
use indoc::{formatdoc, indoc};
|
||||||
|
use libsqlite3_sys::sqlite3_exec;
|
||||||
|
|
||||||
use crate::connection::Connection;
|
use crate::connection::Connection;
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
|
fn eager_exec(&self, sql: &str) -> anyhow::Result<()> {
|
||||||
|
let sql_str = CString::new(sql).context("Error creating cstr")?;
|
||||||
|
unsafe {
|
||||||
|
sqlite3_exec(
|
||||||
|
self.sqlite3,
|
||||||
|
sql_str.as_c_str().as_ptr(),
|
||||||
|
None,
|
||||||
|
0 as *mut _,
|
||||||
|
0 as *mut _,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.last_error()
|
||||||
|
.with_context(|| format!("Prepare call failed for query:\n{}", sql))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Migrate the database, for the given domain.
|
||||||
|
/// Note: Unlike everything else in SQLez, migrations are run eagerly, without first
|
||||||
|
/// preparing the SQL statements. This makes it possible to do multi-statement schema
|
||||||
|
/// updates in a single string without running into prepare errors.
|
||||||
pub fn migrate(&self, domain: &'static str, migrations: &[&'static str]) -> Result<()> {
|
pub fn migrate(&self, domain: &'static str, migrations: &[&'static str]) -> Result<()> {
|
||||||
self.with_savepoint("migrating", || {
|
self.with_savepoint("migrating", || {
|
||||||
// Setup the migrations table unconditionally
|
// Setup the migrations table unconditionally
|
||||||
|
@ -47,7 +71,7 @@ impl Connection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.exec(migration)?()?;
|
self.eager_exec(migration)?;
|
||||||
store_completed_migration((domain, index, *migration))?;
|
store_completed_migration((domain, index, *migration))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +83,7 @@ impl Connection {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
use sqlez_macros::sql;
|
||||||
|
|
||||||
use crate::connection::Connection;
|
use crate::connection::Connection;
|
||||||
|
|
||||||
|
@ -257,4 +282,46 @@ mod test {
|
||||||
// Verify new migration returns error when run
|
// Verify new migration returns error when run
|
||||||
assert!(second_migration_result.is_err())
|
assert!(second_migration_result.is_err())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_alter_drop() {
|
||||||
|
let connection = Connection::open_memory(Some("test_create_alter_drop"));
|
||||||
|
|
||||||
|
connection
|
||||||
|
.migrate(
|
||||||
|
"first_migration",
|
||||||
|
&[sql!( CREATE TABLE table1(a TEXT) STRICT; )],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
connection
|
||||||
|
.exec(sql!( INSERT INTO table1(a) VALUES ("test text"); ))
|
||||||
|
.unwrap()()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
connection
|
||||||
|
.migrate(
|
||||||
|
"second_migration",
|
||||||
|
&[sql!(
|
||||||
|
CREATE TABLE table2(b TEXT) STRICT;
|
||||||
|
|
||||||
|
INSERT INTO table2 (b)
|
||||||
|
SELECT a FROM table1;
|
||||||
|
|
||||||
|
DROP TABLE table1;
|
||||||
|
|
||||||
|
ALTER TABLE table2 RENAME TO table1;
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let res = &connection
|
||||||
|
.select::<String>(sql!(
|
||||||
|
SELECT b FROM table1
|
||||||
|
))
|
||||||
|
.unwrap()()
|
||||||
|
.unwrap()[0];
|
||||||
|
|
||||||
|
assert_eq!(res, "test text");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,23 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
|
/// Prepare a statement which has no bindings and returns nothing.
|
||||||
|
///
|
||||||
|
/// Note: If there are multiple statements that depend upon each other
|
||||||
|
/// (such as those which make schema changes), preparation will fail.
|
||||||
|
/// Use a true migration instead.
|
||||||
pub fn exec<'a>(&'a self, query: &str) -> Result<impl 'a + FnMut() -> Result<()>> {
|
pub fn exec<'a>(&'a self, query: &str) -> Result<impl 'a + FnMut() -> Result<()>> {
|
||||||
let mut statement = Statement::prepare(self, query)?;
|
let mut statement = Statement::prepare(self, query)?;
|
||||||
Ok(move || statement.exec())
|
Ok(move || statement.exec())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepare a statement which takes a binding, but returns nothing.
|
||||||
|
/// The bindings for a given invocation should be passed to the returned
|
||||||
|
/// closure
|
||||||
|
///
|
||||||
|
/// Note: If there are multiple statements that depend upon each other
|
||||||
|
/// (such as those which make schema changes), preparation will fail.
|
||||||
|
/// Use a true migration instead.
|
||||||
pub fn exec_bound<'a, B: Bind>(
|
pub fn exec_bound<'a, B: Bind>(
|
||||||
&'a self,
|
&'a self,
|
||||||
query: &str,
|
query: &str,
|
||||||
|
@ -20,6 +32,11 @@ impl Connection {
|
||||||
Ok(move |bindings| statement.with_bindings(bindings)?.exec())
|
Ok(move |bindings| statement.with_bindings(bindings)?.exec())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepare a statement which has no bindings and returns a `Vec<C>`.
|
||||||
|
///
|
||||||
|
/// Note: If there are multiple statements that depend upon each other
|
||||||
|
/// (such as those which make schema changes), preparation will fail.
|
||||||
|
/// Use a true migration instead.
|
||||||
pub fn select<'a, C: Column>(
|
pub fn select<'a, C: Column>(
|
||||||
&'a self,
|
&'a self,
|
||||||
query: &str,
|
query: &str,
|
||||||
|
@ -28,6 +45,11 @@ impl Connection {
|
||||||
Ok(move || statement.rows::<C>())
|
Ok(move || statement.rows::<C>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepare a statement which takes a binding and returns a `Vec<C>`.
|
||||||
|
///
|
||||||
|
/// Note: If there are multiple statements that depend upon each other
|
||||||
|
/// (such as those which make schema changes), preparation will fail.
|
||||||
|
/// Use a true migration instead.
|
||||||
pub fn select_bound<'a, B: Bind, C: Column>(
|
pub fn select_bound<'a, B: Bind, C: Column>(
|
||||||
&'a self,
|
&'a self,
|
||||||
query: &str,
|
query: &str,
|
||||||
|
@ -36,6 +58,13 @@ impl Connection {
|
||||||
Ok(move |bindings| statement.with_bindings(bindings)?.rows::<C>())
|
Ok(move |bindings| statement.with_bindings(bindings)?.rows::<C>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepare a statement that selects a single row from the database.
|
||||||
|
/// Will return none if no rows are returned and will error if more than
|
||||||
|
/// 1 row
|
||||||
|
///
|
||||||
|
/// Note: If there are multiple statements that depend upon each other
|
||||||
|
/// (such as those which make schema changes), preparation will fail.
|
||||||
|
/// Use a true migration instead.
|
||||||
pub fn select_row<'a, C: Column>(
|
pub fn select_row<'a, C: Column>(
|
||||||
&'a self,
|
&'a self,
|
||||||
query: &str,
|
query: &str,
|
||||||
|
@ -44,6 +73,13 @@ impl Connection {
|
||||||
Ok(move || statement.maybe_row::<C>())
|
Ok(move || statement.maybe_row::<C>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepare a statement which takes a binding and selects a single row
|
||||||
|
/// from the database. WIll return none if no rows are returned and will
|
||||||
|
/// error if more than 1 row is returned.
|
||||||
|
///
|
||||||
|
/// Note: If there are multiple statements that depend upon each other
|
||||||
|
/// (such as those which make schema changes), preparation will fail.
|
||||||
|
/// Use a true migration instead.
|
||||||
pub fn select_row_bound<'a, B: Bind, C: Column>(
|
pub fn select_row_bound<'a, B: Bind, C: Column>(
|
||||||
&'a self,
|
&'a self,
|
||||||
query: &str,
|
query: &str,
|
||||||
|
|
|
@ -14,6 +14,26 @@ define_connection! {
|
||||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
),
|
||||||
|
// Remove the unique constraint on the item_id table
|
||||||
|
// SQLite doesn't have a way of doing this automatically, so
|
||||||
|
// we have to do this silly copying.
|
||||||
|
sql!(
|
||||||
|
CREATE TABLE terminals2 (
|
||||||
|
workspace_id INTEGER,
|
||||||
|
item_id INTEGER,
|
||||||
|
working_directory BLOB,
|
||||||
|
PRIMARY KEY(workspace_id, item_id),
|
||||||
|
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
INSERT INTO terminals2 (workspace_id, item_id, working_directory)
|
||||||
|
SELECT workspace_id, item_id, working_directory FROM terminals;
|
||||||
|
|
||||||
|
DROP TABLE terminals;
|
||||||
|
|
||||||
|
ALTER TABLE terminals2 RENAME TO terminals;
|
||||||
)];
|
)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue