mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-18 18:08:07 +03:00
Added sql! proc macro which checks syntax errors on sql code and displays them with reasonable underline locations
Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
parent
260164a711
commit
dd9d20be25
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -1570,6 +1570,7 @@ dependencies = [
|
|||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
"serde",
|
"serde",
|
||||||
"sqlez",
|
"sqlez",
|
||||||
|
"sqlez_macros",
|
||||||
"tempdir",
|
"tempdir",
|
||||||
"util",
|
"util",
|
||||||
]
|
]
|
||||||
@ -5598,6 +5599,17 @@ dependencies = [
|
|||||||
"thread_local",
|
"thread_local",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sqlez_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"sqlez",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlformat"
|
name = "sqlformat"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -45,6 +45,8 @@ members = [
|
|||||||
"crates/search",
|
"crates/search",
|
||||||
"crates/settings",
|
"crates/settings",
|
||||||
"crates/snippet",
|
"crates/snippet",
|
||||||
|
"crates/sqlez",
|
||||||
|
"crates/sqlez_macros",
|
||||||
"crates/sum_tree",
|
"crates/sum_tree",
|
||||||
"crates/terminal",
|
"crates/terminal",
|
||||||
"crates/text",
|
"crates/text",
|
||||||
|
@ -14,6 +14,7 @@ test-support = []
|
|||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
sqlez = { path = "../sqlez" }
|
sqlez = { path = "../sqlez" }
|
||||||
|
sqlez_macros = { path = "../sqlez_macros" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
anyhow = "1.0.57"
|
anyhow = "1.0.57"
|
||||||
indoc = "1.0.4"
|
indoc = "1.0.4"
|
||||||
|
@ -5,6 +5,7 @@ pub use anyhow;
|
|||||||
pub use indoc::indoc;
|
pub use indoc::indoc;
|
||||||
pub use lazy_static;
|
pub use lazy_static;
|
||||||
pub use sqlez;
|
pub use sqlez;
|
||||||
|
pub use sqlez_macros;
|
||||||
|
|
||||||
use sqlez::domain::Migrator;
|
use sqlez::domain::Migrator;
|
||||||
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||||
@ -76,273 +77,315 @@ macro_rules! connection {
|
|||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! query {
|
macro_rules! query {
|
||||||
($vis:vis fn $id:ident() -> Result<()> { $sql:expr }) => {
|
($vis:vis fn $id:ident() -> Result<()> { $($sql:tt)+ }) => {
|
||||||
$vis fn $id(&self) -> $crate::anyhow::Result<()> {
|
$vis fn $id(&self) -> $crate::anyhow::Result<()> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.exec($sql)?().context(::std::format!(
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
self.exec(sql_stmt)?().context(::std::format!(
|
||||||
"Error in {}, exec failed to execute or parse for: {}",
|
"Error in {}, exec failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis async fn $id:ident() -> Result<()> { $sql:expr }) => {
|
($vis:vis async fn $id:ident() -> Result<()> { $($sql:tt)+ }) => {
|
||||||
$vis async fn $id(&self) -> $crate::anyhow::Result<()> {
|
$vis async fn $id(&self) -> $crate::anyhow::Result<()> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
|
|
||||||
self.write(|connection| {
|
self.write(|connection| {
|
||||||
connection.exec($sql)?().context(::std::format!(
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
connection.exec(sql_stmt)?().context(::std::format!(
|
||||||
"Error in {}, exec failed to execute or parse for: {}",
|
"Error in {}, exec failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}).await
|
}).await
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $sql:expr }) => {
|
($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => {
|
||||||
$vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
|
$vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.exec_bound::<($($arg_type),+)>($sql)?(($($arg),+))
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
self.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+))
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, exec_bound failed to execute or parse for: {}",
|
"Error in {}, exec_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis async fn $id:ident($arg:ident: $arg_type:ty) -> Result<()> { $sql:expr }) => {
|
($vis:vis async fn $id:ident($arg:ident: $arg_type:ty) -> Result<()> { $($sql:tt)+ }) => {
|
||||||
$vis async fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<()> {
|
$vis async fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<()> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
|
|
||||||
self.write(move |connection| {
|
self.write(move |connection| {
|
||||||
connection.exec_bound::<$arg_type>($sql)?($arg)
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
connection.exec_bound::<$arg_type>(sql_stmt)?($arg)
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, exec_bound failed to execute or parse for: {}",
|
"Error in {}, exec_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}).await
|
}).await
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $sql:expr }) => {
|
($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => {
|
||||||
$vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
|
$vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.write(move |connection| {
|
self.write(move |connection| {
|
||||||
connection.exec_bound::<($($arg_type),+)>($sql)?(($($arg),+))
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
connection.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+))
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, exec_bound failed to execute or parse for: {}",
|
"Error in {}, exec_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}).await
|
}).await
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis fn $id:ident() -> Result<Vec<$return_type:ty>> { $sql:expr }) => {
|
($vis:vis fn $id:ident() -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
|
||||||
$vis fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
|
$vis fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.select::<$return_type>($sql)?(())
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
self.select::<$return_type>(sql_stmt)?(())
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row failed to execute or parse for: {}",
|
"Error in {}, select_row failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis async fn $id:ident() -> Result<Vec<$return_type:ty>> { $sql:expr }) => {
|
($vis:vis async fn $id:ident() -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
|
||||||
pub async fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
|
pub async fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.write(|connection| {
|
self.write(|connection| {
|
||||||
connection.select::<$return_type>($sql)?(())
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
connection.select::<$return_type>(sql_stmt)?(())
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row failed to execute or parse for: {}",
|
"Error in {}, select_row failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}).await
|
}).await
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $sql:expr }) => {
|
($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
|
||||||
$vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
|
$vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.select_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+))
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
self.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, exec_bound failed to execute or parse for: {}",
|
"Error in {}, exec_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $sql:expr }) => {
|
($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
|
||||||
$vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
|
$vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.write(|connection| {
|
self.write(|connection| {
|
||||||
connection.select_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+))
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
connection.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, exec_bound failed to execute or parse for: {}",
|
"Error in {}, exec_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}).await
|
}).await
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis fn $id:ident() -> Result<Option<$return_type:ty>> { $sql:expr }) => {
|
($vis:vis fn $id:ident() -> Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
|
||||||
$vis fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
|
$vis fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.select_row::<$return_type>($sql)?()
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
self.select_row::<$return_type>(sql_stmt)?()
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row failed to execute or parse for: {}",
|
"Error in {}, select_row failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis async fn $id:ident() -> Result<Option<$return_type:ty>> { $sql:expr }) => {
|
($vis:vis async fn $id:ident() -> Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
|
||||||
$vis async fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
|
$vis async fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.write(|connection| {
|
self.write(|connection| {
|
||||||
connection.select_row::<$return_type>($sql)?()
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
connection.select_row::<$return_type>(sql_stmt)?()
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row failed to execute or parse for: {}",
|
"Error in {}, select_row failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}).await
|
}).await
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis fn $id:ident($arg:ident: $arg_type:ty) -> Result<Option<$return_type:ty>> { $sql:expr }) => {
|
($vis:vis fn $id:ident($arg:ident: $arg_type:ty) -> Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
|
||||||
$vis fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<Option<$return_type>> {
|
$vis fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<Option<$return_type>> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.select_row_bound::<$arg_type, $return_type>($sql)?($arg)
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
self.select_row_bound::<$arg_type, $return_type>(sql_stmt)?($arg)
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Option<$return_type:ty>> { $sql:expr }) => {
|
($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
|
||||||
$vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>> {
|
$vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.select_row_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+))
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Option<$return_type:ty>> { $sql:expr }) => {
|
($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
|
||||||
$vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>> {
|
$vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
|
|
||||||
self.write(|connection| {
|
self.write(|connection| {
|
||||||
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
connection.select_row_bound::<($($arg_type),+), $return_type>(indoc! { $sql })?(($($arg),+))
|
connection.select_row_bound::<($($arg_type),+), $return_type>(indoc! { $sql })?(($($arg),+))
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}).await
|
}).await
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis fn $id:ident() -> Result<$return_type:ty> { $sql:expr }) => {
|
($vis:vis fn $id:ident() -> Result<$return_type:ty> { $($sql:tt)+ }) => {
|
||||||
$vis fn $id(&self) -> $crate::anyhow::Result<$return_type> {
|
$vis fn $id(&self) -> $crate::anyhow::Result<$return_type> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
self.select_row::<$return_type>(indoc! { $sql })?()
|
self.select_row::<$return_type>(indoc! { $sql })?()
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))?
|
))?
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound expected single row result but found none for: {}",
|
"Error in {}, select_row_bound expected single row result but found none for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis async fn $id:ident() -> Result<$return_type:ty> { $sql:expr }) => {
|
($vis:vis async fn $id:ident() -> Result<$return_type:ty> { $($sql:tt)+ }) => {
|
||||||
$vis async fn $id(&self) -> $crate::anyhow::Result<$return_type> {
|
$vis async fn $id(&self) -> $crate::anyhow::Result<$return_type> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.write(|connection| {
|
self.write(|connection| {
|
||||||
connection.select_row::<$return_type>($sql)?()
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
connection.select_row::<$return_type>(sql_stmt)?()
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))?
|
))?
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound expected single row result but found none for: {}",
|
"Error in {}, select_row_bound expected single row result but found none for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}).await
|
}).await
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis fn $id:ident($arg:ident: $arg_type:ty) -> Result<$return_type:ty> { $sql:expr }) => {
|
($vis:vis fn $id:ident($arg:ident: $arg_type:ty) -> Result<$return_type:ty> { $($sql:tt)+ }) => {
|
||||||
pub fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<$return_type> {
|
pub fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<$return_type> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.select_row_bound::<$arg_type, $return_type>($sql)?($arg)
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
self.select_row_bound::<$arg_type, $return_type>(sql_stmt)?($arg)
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))?
|
))?
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound expected single row result but found none for: {}",
|
"Error in {}, select_row_bound expected single row result but found none for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<$return_type:ty> { $sql:expr }) => {
|
($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<$return_type:ty> { $($sql:tt)+ }) => {
|
||||||
$vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<$return_type> {
|
$vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<$return_type> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
self.select_row_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+))
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))?
|
))?
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound expected single row result but found none for: {}",
|
"Error in {}, select_row_bound expected single row result but found none for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($vis:vis fn async $id:ident($($arg:ident: $arg_type:ty),+) -> Result<$return_type:ty> { $sql:expr }) => {
|
($vis:vis fn async $id:ident($($arg:ident: $arg_type:ty),+) -> Result<$return_type:ty> { $($sql:tt)+ }) => {
|
||||||
$vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<$return_type> {
|
$vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<$return_type> {
|
||||||
use $crate::anyhow::Context;
|
use $crate::anyhow::Context;
|
||||||
|
|
||||||
|
|
||||||
self.write(|connection| {
|
self.write(|connection| {
|
||||||
connection.select_row_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+))
|
let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
|
||||||
|
|
||||||
|
connection.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
"Error in {}, select_row_bound failed to execute or parse for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))?
|
))?
|
||||||
.context(::std::format!(
|
.context(::std::format!(
|
||||||
"Error in {}, select_row_bound expected single row result but found none for: {}",
|
"Error in {}, select_row_bound expected single row result but found none for: {}",
|
||||||
::std::stringify!($id),
|
::std::stringify!($id),
|
||||||
$sql,
|
sql_stmt
|
||||||
))
|
))
|
||||||
}).await
|
}).await
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use indoc::indoc;
|
|
||||||
|
|
||||||
use sqlez::{domain::Domain, thread_safe_connection::ThreadSafeConnection};
|
use sqlez::{domain::Domain, thread_safe_connection::ThreadSafeConnection};
|
||||||
|
use sqlez_macros::sql;
|
||||||
|
|
||||||
use crate::{open_file_db, open_memory_db, query};
|
use crate::{open_file_db, open_memory_db, query};
|
||||||
|
|
||||||
@ -28,31 +27,31 @@ impl Domain for KeyValueStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn migrations() -> &'static [&'static str] {
|
fn migrations() -> &'static [&'static str] {
|
||||||
&[indoc! {"
|
&[sql!(
|
||||||
CREATE TABLE kv_store(
|
CREATE TABLE kv_store(
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
value TEXT NOT NULL
|
value TEXT NOT NULL
|
||||||
) STRICT;
|
) STRICT;
|
||||||
"}]
|
)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyValueStore {
|
impl KeyValueStore {
|
||||||
query! {
|
query! {
|
||||||
pub fn read_kvp(key: &str) -> Result<Option<String>> {
|
pub fn read_kvp(key: &str) -> Result<Option<String>> {
|
||||||
"SELECT value FROM kv_store WHERE key = (?)"
|
SELECT value FROM kv_store WHERE key = (?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query! {
|
query! {
|
||||||
pub async fn write_kvp(key: String, value: String) -> Result<()> {
|
pub async fn write_kvp(key: String, value: String) -> Result<()> {
|
||||||
"INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?))"
|
INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query! {
|
query! {
|
||||||
pub async fn delete_kvp(key: String) -> Result<()> {
|
pub async fn delete_kvp(key: String) -> Result<()> {
|
||||||
"DELETE FROM kv_store WHERE key = (?)"
|
DELETE FROM kv_store WHERE key = (?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::Editor;
|
||||||
|
use db::sqlez_macros::sql;
|
||||||
use db::{connection, query};
|
use db::{connection, query};
|
||||||
use indoc::indoc;
|
|
||||||
use sqlez::domain::Domain;
|
use sqlez::domain::Domain;
|
||||||
use workspace::{ItemId, Workspace, WorkspaceId};
|
use workspace::{ItemId, Workspace, WorkspaceId};
|
||||||
|
|
||||||
use crate::Editor;
|
|
||||||
|
|
||||||
connection!(DB: EditorDb<(Workspace, Editor)>);
|
connection!(DB: EditorDb<(Workspace, Editor)>);
|
||||||
|
|
||||||
impl Domain for Editor {
|
impl Domain for Editor {
|
||||||
@ -15,7 +14,7 @@ impl Domain for Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn migrations() -> &'static [&'static str] {
|
fn migrations() -> &'static [&'static str] {
|
||||||
&[indoc! {"
|
&[sql! (
|
||||||
CREATE TABLE editors(
|
CREATE TABLE editors(
|
||||||
item_id INTEGER NOT NULL,
|
item_id INTEGER NOT NULL,
|
||||||
workspace_id INTEGER NOT NULL,
|
workspace_id INTEGER NOT NULL,
|
||||||
@ -26,26 +25,22 @@ impl Domain for Editor {
|
|||||||
ON UPDATE CASCADE
|
ON UPDATE CASCADE
|
||||||
|
|
||||||
) STRICT;
|
) STRICT;
|
||||||
"}]
|
)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorDb {
|
impl EditorDb {
|
||||||
query! {
|
query! {
|
||||||
pub fn get_path(item_id: ItemId, workspace_id: WorkspaceId) -> Result<PathBuf> {
|
pub fn get_path(item_id: ItemId, workspace_id: WorkspaceId) -> Result<PathBuf> {
|
||||||
indoc!{"
|
SELECT path FROM editors
|
||||||
SELECT path FROM editors
|
WHERE item_id = ? AND workspace_id = ?
|
||||||
WHERE item_id = ? AND workspace_id = ?
|
|
||||||
"}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query! {
|
query! {
|
||||||
pub async fn save_path(item_id: ItemId, workspace_id: WorkspaceId, path: PathBuf) -> Result<()> {
|
pub async fn save_path(item_id: ItemId, workspace_id: WorkspaceId, path: PathBuf) -> Result<()> {
|
||||||
indoc!{"
|
INSERT OR REPLACE INTO editors(item_id, workspace_id, path)
|
||||||
INSERT OR REPLACE INTO editors(item_id, workspace_id, path)
|
VALUES (?, ?, ?)
|
||||||
VALUES (?, ?, ?)
|
|
||||||
"}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,3 +12,4 @@ doctest = false
|
|||||||
syn = "1.0"
|
syn = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ use std::{
|
|||||||
ffi::{CStr, CString},
|
ffi::{CStr, CString},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
path::Path,
|
path::Path,
|
||||||
|
ptr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
@ -85,6 +86,45 @@ impl Connection {
|
|||||||
self.backup_main(&destination)
|
self.backup_main(&destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sql_has_syntax_error(&self, sql: &str) -> Option<(String, usize)> {
|
||||||
|
let sql = CString::new(sql).unwrap();
|
||||||
|
let mut remaining_sql = sql.as_c_str();
|
||||||
|
let sql_start = remaining_sql.as_ptr();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
while {
|
||||||
|
let remaining_sql_str = remaining_sql.to_str().unwrap().trim();
|
||||||
|
remaining_sql_str != ";" && !remaining_sql_str.is_empty()
|
||||||
|
} {
|
||||||
|
let mut raw_statement = 0 as *mut sqlite3_stmt;
|
||||||
|
let mut remaining_sql_ptr = ptr::null();
|
||||||
|
sqlite3_prepare_v2(
|
||||||
|
self.sqlite3,
|
||||||
|
remaining_sql.as_ptr(),
|
||||||
|
-1,
|
||||||
|
&mut raw_statement,
|
||||||
|
&mut remaining_sql_ptr,
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = sqlite3_errcode(self.sqlite3);
|
||||||
|
let offset = sqlite3_error_offset(self.sqlite3);
|
||||||
|
|
||||||
|
if res == 1 && offset >= 0 {
|
||||||
|
let message = sqlite3_errmsg(self.sqlite3);
|
||||||
|
let err_msg =
|
||||||
|
String::from_utf8_lossy(CStr::from_ptr(message as *const _).to_bytes())
|
||||||
|
.into_owned();
|
||||||
|
let sub_statement_correction =
|
||||||
|
remaining_sql.as_ptr() as usize - sql_start as usize;
|
||||||
|
|
||||||
|
return Some((err_msg, offset as usize + sub_statement_correction));
|
||||||
|
}
|
||||||
|
remaining_sql = CStr::from_ptr(remaining_sql_ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn last_error(&self) -> Result<()> {
|
pub(crate) fn last_error(&self) -> Result<()> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let code = sqlite3_errcode(self.sqlite3);
|
let code = sqlite3_errcode(self.sqlite3);
|
||||||
@ -259,10 +299,31 @@ mod test {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
connection
|
connection
|
||||||
.select_row::<usize>("SELECt * FROM test")
|
.select_row::<usize>("SELECT * FROM test")
|
||||||
.unwrap()()
|
.unwrap()()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
Some(2)
|
Some(2)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sql_has_syntax_errors() {
|
||||||
|
let connection = Connection::open_memory(Some("test_sql_has_syntax_errors"));
|
||||||
|
let first_stmt =
|
||||||
|
"CREATE TABLE kv_store(key TEXT PRIMARY KEY, value TEXT NOT NULL) STRICT ;";
|
||||||
|
let second_stmt = "SELECT FROM";
|
||||||
|
|
||||||
|
let second_offset = connection.sql_has_syntax_error(second_stmt).unwrap().1;
|
||||||
|
|
||||||
|
let res = connection
|
||||||
|
.sql_has_syntax_error(&format!("{}\n{}", first_stmt, second_stmt))
|
||||||
|
.map(|(_, offset)| offset);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Some(first_stmt.len() + second_offset + 1) // TODO: This value is wrong!
|
||||||
|
);
|
||||||
|
|
||||||
|
panic!("{:?}", res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,12 @@ pub trait Migrator {
|
|||||||
fn migrate(connection: &Connection) -> anyhow::Result<()>;
|
fn migrate(connection: &Connection) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Migrator for () {
|
||||||
|
fn migrate(_connection: &Connection) -> anyhow::Result<()> {
|
||||||
|
Ok(()) // Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<D: Domain> Migrator for D {
|
impl<D: Domain> Migrator for D {
|
||||||
fn migrate(connection: &Connection) -> anyhow::Result<()> {
|
fn migrate(connection: &Connection) -> anyhow::Result<()> {
|
||||||
connection.migrate(Self::name(), Self::migrations())
|
connection.migrate(Self::name(), Self::migrations())
|
||||||
|
@ -489,76 +489,3 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod syntax_check {
|
|
||||||
use std::{
|
|
||||||
ffi::{CStr, CString},
|
|
||||||
ptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use libsqlite3_sys::{
|
|
||||||
sqlite3_close, sqlite3_errmsg, sqlite3_error_offset, sqlite3_extended_errcode,
|
|
||||||
sqlite3_extended_result_codes, sqlite3_finalize, sqlite3_open_v2, sqlite3_prepare_v2,
|
|
||||||
sqlite3_stmt, SQLITE_OPEN_CREATE, SQLITE_OPEN_NOMUTEX, SQLITE_OPEN_READWRITE,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn syntax_errors(sql: &str) -> Option<(String, i32)> {
|
|
||||||
let mut sqlite3 = 0 as *mut _;
|
|
||||||
let mut raw_statement = 0 as *mut sqlite3_stmt;
|
|
||||||
|
|
||||||
let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_READWRITE;
|
|
||||||
unsafe {
|
|
||||||
let memory_str = CString::new(":memory:").unwrap();
|
|
||||||
sqlite3_open_v2(memory_str.as_ptr(), &mut sqlite3, flags, 0 as *const _);
|
|
||||||
|
|
||||||
let sql = CString::new(sql).unwrap();
|
|
||||||
|
|
||||||
// Turn on extended error codes
|
|
||||||
sqlite3_extended_result_codes(sqlite3, 1);
|
|
||||||
|
|
||||||
sqlite3_prepare_v2(
|
|
||||||
sqlite3,
|
|
||||||
sql.as_c_str().as_ptr(),
|
|
||||||
-1,
|
|
||||||
&mut raw_statement,
|
|
||||||
&mut ptr::null(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let res = sqlite3_extended_errcode(sqlite3);
|
|
||||||
let offset = sqlite3_error_offset(sqlite3);
|
|
||||||
|
|
||||||
if res == 1 && offset != -1 {
|
|
||||||
let message = sqlite3_errmsg(sqlite3);
|
|
||||||
let err_msg =
|
|
||||||
String::from_utf8_lossy(CStr::from_ptr(message as *const _).to_bytes())
|
|
||||||
.into_owned();
|
|
||||||
|
|
||||||
sqlite3_finalize(*&mut raw_statement);
|
|
||||||
sqlite3_close(sqlite3);
|
|
||||||
|
|
||||||
return Some((err_msg, offset));
|
|
||||||
} else {
|
|
||||||
sqlite3_finalize(*&mut raw_statement);
|
|
||||||
sqlite3_close(sqlite3);
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::syntax_errors;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_check_syntax() {
|
|
||||||
assert!(syntax_errors("SELECT FROM").is_some());
|
|
||||||
|
|
||||||
assert!(syntax_errors("SELECT col FROM table_t;").is_none());
|
|
||||||
|
|
||||||
assert!(syntax_errors("CREATE TABLE t(col TEXT,) STRICT;").is_some());
|
|
||||||
|
|
||||||
assert!(syntax_errors("CREATE TABLE t(col TEXT) STRICT;").is_none());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -17,7 +17,7 @@ lazy_static! {
|
|||||||
Default::default();
|
Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ThreadSafeConnection<M: Migrator> {
|
pub struct ThreadSafeConnection<M: Migrator = ()> {
|
||||||
uri: Arc<str>,
|
uri: Arc<str>,
|
||||||
persistent: bool,
|
persistent: bool,
|
||||||
initialize_query: Option<&'static str>,
|
initialize_query: Option<&'static str>,
|
||||||
|
16
crates/sqlez_macros/Cargo.toml
Normal file
16
crates/sqlez_macros/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "sqlez_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/sqlez_macros.rs"
|
||||||
|
proc-macro = true
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = "1.0"
|
||||||
|
quote = "1.0"
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
lazy_static = "1.4"
|
||||||
|
sqlez = { path = "../sqlez" }
|
78
crates/sqlez_macros/src/sqlez_macros.rs
Normal file
78
crates/sqlez_macros/src/sqlez_macros.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
use proc_macro::{Delimiter, Span, TokenStream, TokenTree};
|
||||||
|
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||||
|
use syn::Error;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref SQLITE: ThreadSafeConnection = ThreadSafeConnection::new(":memory:", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn sql(tokens: TokenStream) -> TokenStream {
|
||||||
|
let mut sql_tokens = vec![];
|
||||||
|
flatten_stream(tokens.clone(), &mut sql_tokens);
|
||||||
|
|
||||||
|
// Lookup of spans by offset at the end of the token
|
||||||
|
let mut spans: Vec<(usize, Span)> = Vec::new();
|
||||||
|
let mut sql = String::new();
|
||||||
|
for (token_text, span) in sql_tokens {
|
||||||
|
sql.push_str(&token_text);
|
||||||
|
spans.push((sql.len(), span));
|
||||||
|
}
|
||||||
|
|
||||||
|
let error = SQLITE.sql_has_syntax_error(sql.trim());
|
||||||
|
|
||||||
|
if let Some((error, error_offset)) = error {
|
||||||
|
let error_span = spans
|
||||||
|
.into_iter()
|
||||||
|
.skip_while(|(offset, _)| offset <= &error_offset)
|
||||||
|
.map(|(_, span)| span)
|
||||||
|
.next()
|
||||||
|
.unwrap_or(Span::call_site());
|
||||||
|
|
||||||
|
let error_text = format!("Sql Error: {}\nFor Query: {}", error, sql);
|
||||||
|
TokenStream::from(Error::new(error_span.into(), error_text).into_compile_error())
|
||||||
|
} else {
|
||||||
|
format!("r#\"{}\"#", &sql).parse().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method exists to normalize the representation of groups
|
||||||
|
/// to always include spaces between tokens. This is why we don't use the usual .to_string().
|
||||||
|
/// This allows our token search in token_at_offset to resolve
|
||||||
|
/// ambiguity of '(tokens)' vs. '( token )', due to sqlite requiring byte offsets
|
||||||
|
fn flatten_stream(tokens: TokenStream, result: &mut Vec<(String, Span)>) {
|
||||||
|
for token_tree in tokens.into_iter() {
|
||||||
|
match token_tree {
|
||||||
|
TokenTree::Group(group) => {
|
||||||
|
// push open delimiter
|
||||||
|
result.push((open_delimiter(group.delimiter()), group.span()));
|
||||||
|
// recurse
|
||||||
|
flatten_stream(group.stream(), result);
|
||||||
|
// push close delimiter
|
||||||
|
result.push((close_delimiter(group.delimiter()), group.span()));
|
||||||
|
}
|
||||||
|
TokenTree::Ident(ident) => {
|
||||||
|
result.push((format!("{} ", ident.to_string()), ident.span()));
|
||||||
|
}
|
||||||
|
leaf_tree => result.push((leaf_tree.to_string(), leaf_tree.span())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_delimiter(delimiter: Delimiter) -> String {
|
||||||
|
match delimiter {
|
||||||
|
Delimiter::Parenthesis => "(".to_string(),
|
||||||
|
Delimiter::Brace => "[".to_string(),
|
||||||
|
Delimiter::Bracket => "{".to_string(),
|
||||||
|
Delimiter::None => "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_delimiter(delimiter: Delimiter) -> String {
|
||||||
|
match delimiter {
|
||||||
|
Delimiter::Parenthesis => ")".to_string(),
|
||||||
|
Delimiter::Brace => "]".to_string(),
|
||||||
|
Delimiter::Bracket => "}".to_string(),
|
||||||
|
Delimiter::None => "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use db::{connection, indoc, query, sqlez::domain::Domain};
|
use db::{connection, query, sqlez::domain::Domain, sqlez_macros::sql};
|
||||||
|
|
||||||
use workspace::{ItemId, Workspace, WorkspaceId};
|
use workspace::{ItemId, Workspace, WorkspaceId};
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ impl Domain for Terminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn migrations() -> &'static [&'static str] {
|
fn migrations() -> &'static [&'static str] {
|
||||||
&[indoc! {"
|
&[sql!(
|
||||||
CREATE TABLE terminals (
|
CREATE TABLE terminals (
|
||||||
workspace_id INTEGER,
|
workspace_id INTEGER,
|
||||||
item_id INTEGER UNIQUE,
|
item_id INTEGER UNIQUE,
|
||||||
@ -23,7 +23,7 @@ impl Domain for Terminal {
|
|||||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
) STRICT;
|
) STRICT;
|
||||||
"}]
|
)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,11 +34,9 @@ impl TerminalDb {
|
|||||||
old_id: WorkspaceId,
|
old_id: WorkspaceId,
|
||||||
item_id: ItemId
|
item_id: ItemId
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
indoc!{"
|
UPDATE terminals
|
||||||
UPDATE terminals
|
SET workspace_id = ?
|
||||||
SET workspace_id = ?
|
WHERE workspace_id = ? AND item_id = ?
|
||||||
WHERE workspace_id = ? AND item_id = ?
|
|
||||||
"}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,20 +46,16 @@ impl TerminalDb {
|
|||||||
workspace_id: WorkspaceId,
|
workspace_id: WorkspaceId,
|
||||||
working_directory: PathBuf
|
working_directory: PathBuf
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
indoc!{"
|
INSERT OR REPLACE INTO terminals(item_id, workspace_id, working_directory)
|
||||||
INSERT OR REPLACE INTO terminals(item_id, workspace_id, working_directory)
|
VALUES (?, ?, ?)
|
||||||
VALUES (?1, ?2, ?3)
|
|
||||||
"}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query! {
|
query! {
|
||||||
pub fn get_working_directory(item_id: ItemId, workspace_id: WorkspaceId) -> Result<Option<PathBuf>> {
|
pub fn get_working_directory(item_id: ItemId, workspace_id: WorkspaceId) -> Result<Option<PathBuf>> {
|
||||||
indoc!{"
|
SELECT working_directory
|
||||||
SELECT working_directory
|
FROM terminals
|
||||||
FROM terminals
|
WHERE item_id = ? AND workspace_id = ?
|
||||||
WHERE item_id = ? AND workspace_id = ?
|
|
||||||
"}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ pub mod model;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use db::{connection, query, sqlez::connection::Connection};
|
use db::{connection, query, sqlez::connection::Connection, sqlez_macros::sql};
|
||||||
use gpui::Axis;
|
use gpui::Axis;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
|
||||||
@ -30,49 +30,49 @@ impl Domain for Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn migrations() -> &'static [&'static str] {
|
fn migrations() -> &'static [&'static str] {
|
||||||
&[indoc! {"
|
&[sql!(
|
||||||
CREATE TABLE workspaces(
|
CREATE TABLE workspaces(
|
||||||
workspace_id INTEGER PRIMARY KEY,
|
workspace_id INTEGER PRIMARY KEY,
|
||||||
workspace_location BLOB UNIQUE,
|
workspace_location BLOB UNIQUE,
|
||||||
dock_visible INTEGER, -- Boolean
|
dock_visible INTEGER, // Boolean
|
||||||
dock_anchor TEXT, -- Enum: 'Bottom' / 'Right' / 'Expanded'
|
dock_anchor TEXT, // Enum: 'Bottom' / 'Right' / 'Expanded'
|
||||||
dock_pane INTEGER, -- NULL indicates that we don't have a dock pane yet
|
dock_pane INTEGER, // NULL indicates that we don't have a dock pane yet
|
||||||
timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
FOREIGN KEY(dock_pane) REFERENCES panes(pane_id)
|
FOREIGN KEY(dock_pane) REFERENCES panes(pane_id)
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
CREATE TABLE pane_groups(
|
CREATE TABLE pane_groups(
|
||||||
group_id INTEGER PRIMARY KEY,
|
group_id INTEGER PRIMARY KEY,
|
||||||
workspace_id INTEGER NOT NULL,
|
workspace_id INTEGER NOT NULL,
|
||||||
parent_group_id INTEGER, -- NULL indicates that this is a root node
|
parent_group_id INTEGER, // NULL indicates that this is a root node
|
||||||
position INTEGER, -- NULL indicates that this is a root node
|
position INTEGER, // NULL indicates that this is a root node
|
||||||
axis TEXT NOT NULL, -- Enum: 'Vertical' / 'Horizontal'
|
axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal'
|
||||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
ON UPDATE CASCADE,
|
ON UPDATE CASCADE,
|
||||||
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
|
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
CREATE TABLE panes(
|
CREATE TABLE panes(
|
||||||
pane_id INTEGER PRIMARY KEY,
|
pane_id INTEGER PRIMARY KEY,
|
||||||
workspace_id INTEGER NOT NULL,
|
workspace_id INTEGER NOT NULL,
|
||||||
active INTEGER NOT NULL, -- Boolean
|
active INTEGER NOT NULL, // Boolean
|
||||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
ON UPDATE CASCADE
|
ON UPDATE CASCADE
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
CREATE TABLE center_panes(
|
CREATE TABLE center_panes(
|
||||||
pane_id INTEGER PRIMARY KEY,
|
pane_id INTEGER PRIMARY KEY,
|
||||||
parent_group_id INTEGER, -- NULL means that this is a root pane
|
parent_group_id INTEGER, // NULL means that this is a root pane
|
||||||
position INTEGER, -- NULL means that this is a root pane
|
position INTEGER, // NULL means that this is a root pane
|
||||||
FOREIGN KEY(pane_id) REFERENCES panes(pane_id)
|
FOREIGN KEY(pane_id) REFERENCES panes(pane_id)
|
||||||
ON DELETE CASCADE,
|
ON DELETE CASCADE,
|
||||||
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
|
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
CREATE TABLE items(
|
CREATE TABLE items(
|
||||||
item_id INTEGER NOT NULL, -- This is the item's view id, so this is not unique
|
item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique
|
||||||
workspace_id INTEGER NOT NULL,
|
workspace_id INTEGER NOT NULL,
|
||||||
pane_id INTEGER NOT NULL,
|
pane_id INTEGER NOT NULL,
|
||||||
kind TEXT NOT NULL,
|
kind TEXT NOT NULL,
|
||||||
@ -84,7 +84,7 @@ impl Domain for Workspace {
|
|||||||
ON DELETE CASCADE,
|
ON DELETE CASCADE,
|
||||||
PRIMARY KEY(item_id, workspace_id)
|
PRIMARY KEY(item_id, workspace_id)
|
||||||
) STRICT;
|
) STRICT;
|
||||||
"}]
|
)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,26 +158,22 @@ impl WorkspaceDb {
|
|||||||
.context("clearing out old locations")?;
|
.context("clearing out old locations")?;
|
||||||
|
|
||||||
// Upsert
|
// Upsert
|
||||||
conn.exec_bound(indoc! {"
|
conn.exec_bound(sql!(
|
||||||
INSERT INTO workspaces(
|
INSERT INTO workspaces(
|
||||||
workspace_id,
|
workspace_id,
|
||||||
workspace_location,
|
workspace_location,
|
||||||
dock_visible,
|
dock_visible,
|
||||||
dock_anchor,
|
dock_anchor,
|
||||||
timestamp
|
timestamp
|
||||||
)
|
)
|
||||||
VALUES (?1, ?2, ?3, ?4, CURRENT_TIMESTAMP)
|
VALUES (?1, ?2, ?3, ?4, CURRENT_TIMESTAMP)
|
||||||
ON CONFLICT DO
|
ON CONFLICT DO
|
||||||
UPDATE SET
|
UPDATE SET
|
||||||
workspace_location = ?2,
|
workspace_location = ?2,
|
||||||
dock_visible = ?3,
|
dock_visible = ?3,
|
||||||
dock_anchor = ?4,
|
dock_anchor = ?4,
|
||||||
timestamp = CURRENT_TIMESTAMP
|
timestamp = CURRENT_TIMESTAMP
|
||||||
"})?((
|
))?((workspace.id, &workspace.location, workspace.dock_position))
|
||||||
workspace.id,
|
|
||||||
&workspace.location,
|
|
||||||
workspace.dock_position,
|
|
||||||
))
|
|
||||||
.context("Updating workspace")?;
|
.context("Updating workspace")?;
|
||||||
|
|
||||||
// Save center pane group and dock pane
|
// Save center pane group and dock pane
|
||||||
@ -203,7 +199,7 @@ impl WorkspaceDb {
|
|||||||
|
|
||||||
query! {
|
query! {
|
||||||
pub async fn next_id() -> Result<WorkspaceId> {
|
pub async fn next_id() -> Result<WorkspaceId> {
|
||||||
"INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id"
|
INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user