-
Notifications
You must be signed in to change notification settings - Fork 332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compile time query support #801
Comments
We should be able to validate query like sql!. |
Syntax checking would be a great start. I would love it if Rusqlite could also deduce the correct type from the schema too (SQLite's "it's a feature" is a feeble excuse at best, downright idiotic at worst). But I'll settle for compile time syntax checking! |
https://github.com/gwenn/lemon-rs/ contains a SQLite parser. I don't know how up to date it is though. @gwenn any opinions on integrating it vs going some other approach? One thing I'll note is: I agree that the dynamic typing is a concern, but I think as long as we don't force checking (and instead just offer a checked API) it wouldn't be a problem. Anyway, beyond basic syntax checking, if we could verify the correct parameter/result names/counts that would be huge. I do think this should all be optional though, since proc macros add quite a bit to compile time, and currently we don't use any. |
Yes I think having a live database is rather overkill. I would rather specify the schema I expect in the source code. For example in SQLite you can do this:
Which gives you a table something like this: How hard would it be to allow copy/pasting into a macro something like this?
|
I am afraid that currently So we need:
|
This would be in a proc-macro crate I think, and so we could just catch the panic, assuming it just means invalid input. (That said I still think this all would be a lot of work, and it only answers possibly the first part anyway) |
sqlx parses |
By the way, even if there isn't support for validating queries against a schema, just checking the SQL syntax would be good. I keep getting extra commas by writing queries like this:
And because SQL doesn't support trailing commas if you delete |
See https://github.com/gwenn/rusqlite/tree/rusqlite-macros/rusqlite-macros Todo:
|
Wow!!! Haven't looked yet but that's awesome!
I don't think so, since it's possible we may want to change an API in rusqlite, which also impacts the macros. |
The (easy) next step would be to to validate the number of place holders / parameters / variables (and their optional names). |
Ok, |
Oof, missed by just a bit.
sqlite3-parser seems clearer to me. That said it's totally your call. I agree that lemon-rs would be a confusing name, though. |
Ok, |
Just for reference: https://github.com/google/cpp-from-the-sky-down/tree/master/rust (full SQLite parser ?) |
Here is my build.rs which sanity checks all queries for syntax errors. use anyhow::Result;
fn sanity_check_sql_queries() -> Result<()> {
use syn::visit::{self, Visit};
struct Visitor {
db: rusqlite::Connection,
in_sql_call: bool,
errors: Vec<anyhow::Error>,
queries: u64,
}
impl<'ast> Visit<'ast> for Visitor {
fn visit_lit_str(&mut self, node: &'ast syn::LitStr) {
if self.in_sql_call {
self.queries += 1;
let query = node.token().to_string();
self.db.execute_batch(&format!("EXPLAIN QUERY PLAN {}", &query[1..query.len()-1])).unwrap_or_else(|err| {
match err {
rusqlite::Error::ExecuteReturnedResults => {},
_ => {
self.queries -= 1;
self.errors.push(anyhow::anyhow!(err))
},
}
});
}
visit::visit_lit_str(self, node);
}
fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
match node.method.to_string().as_str() {
"execute" | "execute_batch" |
"prepare" | "prepare_cached" |
"query_row" => {
self.in_sql_call = true;
for arg in &node.args {
visit::visit_expr(self, &arg);
break; // only care about first arg
}
self.in_sql_call = false;
},
_ => {},
}
visit::visit_expr_method_call(self, node);
}
}
let mut visitor = Visitor {
db: rusqlite::Connection::open_in_memory()?,
in_sql_call: false,
errors: Vec::new(),
queries: 0,
};
visitor.db.execute_batch(&String::from_utf8_lossy(include_bytes!("src/db.sql")))?;
for entry_ in walkdir::WalkDir::new("src") {
if let Ok(entry) = &entry_ {
if entry.path().file_name().unwrap().to_str().unwrap().ends_with(".rs") {
let contents = std::fs::read_to_string(entry.path())?;
let syntax_tree = syn::parse_file(&contents)?;
let last_queries = visitor.queries;
visitor.visit_file(&syntax_tree);
if visitor.queries != last_queries {
println!("cargo:warning=Found {} SQLite queries in {}", visitor.queries - last_queries, entry.path().display());
println!("cargo:rerun-if-changed={}", entry.path().display());
}
}
}
}
println!("cargo:warning=Checked {} SQLite queries from which {} failed", visitor.queries, visitor.errors.len());
if !visitor.errors.is_empty() {
let mut merged: String = "".to_string();
for err in visitor.errors {
merged += &format!("{err:?}");
}
anyhow::bail!(merged);
}
Ok(())
}
fn main() -> Result<()> {
sanity_check_sql_queries()?;
let output = std::process::Command::new("git").args(&["rev-parse", "HEAD"]).output()?;
let git_hash = String::from_utf8(output.stdout)?;
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
pkg_config::Config::new().probe("donutdb")?;
println!("cargo:rerun-if-changed=build.rs");
Ok(())
} |
@gwenn nice, if it was possible for it to also do |
As far as I know, |
It would be cool if this crate supported some of the compile time query features that SQLx has.
The text was updated successfully, but these errors were encountered: