diff --git a/Cargo.toml b/Cargo.toml index e813e47..6b609b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,6 +139,11 @@ bencher = "0.1" path = "libsqlite3-sys" version = "0.25.0" +# FIXME optional +[dependencies.rusqlite-macros] +path = "rusqlite-macros" +version = "0.1.0" + [[test]] name = "config_log" harness = false diff --git a/rusqlite-macros/Cargo.toml b/rusqlite-macros/Cargo.toml new file mode 100644 index 0000000..af1c040 --- /dev/null +++ b/rusqlite-macros/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rusqlite-macros" +version = "0.1.0" +authors = ["The rusqlite developers"] +edition = "2021" +description = "Private implementation detail of rusqlite crate" +repository = "https://github.com/rusqlite/rusqlite" +license = "MIT" +categories = ["database"] + +[lib] +proc-macro = true + +[dependencies] +sqlite3-parser = { version = "0.4.0", default-features = false, features = ["YYNOERRORRECOVERY"] } +fallible-iterator = "0.2" diff --git a/rusqlite-macros/src/lib.rs b/rusqlite-macros/src/lib.rs new file mode 100644 index 0000000..a09694e --- /dev/null +++ b/rusqlite-macros/src/lib.rs @@ -0,0 +1,81 @@ +//! Private implementation details of `rusqlite`. + +use proc_macro::{Delimiter, Literal, TokenStream, TokenTree}; + +use fallible_iterator::FallibleIterator; +use sqlite3_parser::lexer::sql::Parser; +use sqlite3_parser::ast::{ParameterInfo, ToTokens}; + +// https://internals.rust-lang.org/t/custom-error-diagnostics-with-procedural-macros-on-almost-stable-rust/8113 + +#[doc(hidden)] +#[proc_macro] +pub fn __bind(input: TokenStream) -> TokenStream { + try_bind(input).unwrap_or_else(|msg| parse_ts(&format!("compile_error!({:?})", msg))) +} + +type Result = std::result::Result; + +fn try_bind(input: TokenStream) -> Result { + //eprintln!("INPUT: {:#?}", input); + let (stmt, literal) = { + let mut iter = input.into_iter(); + let stmt = iter.next().unwrap(); + let _punct = iter.next().unwrap(); + let literal = iter.next().unwrap(); + assert!(iter.next().is_none()); + (stmt, literal) + }; + + let literal = match into_literal(&literal) { + Some(it) => it, + None => return Err("expected a plain string literal".to_string()), + }; + let sql = literal.to_string(); + if !sql.starts_with('"') { + return Err("expected a plain string literal".to_string()); + } + let sql = strip_matches(&sql, "\""); + //eprintln!("SQL: {}", sql); + + let mut parser = Parser::new(sql.as_bytes()); + let ast = match parser.next() { + Ok(None) => return Err("Invalid input".to_owned()), + Err(err) => { + return Err(err.to_string()); + } + Ok(Some(ast)) => ast + }; + let mut info = ParameterInfo::default(); + if let Err(err) = ast.to_tokens(&mut info) { + return Err(err.to_string()); + } + //eprintln!("ParameterInfo.count: {:#?}", info.count); + //eprintln!("ParameterInfo.names: {:#?}", info.names); + + let mut res = TokenStream::new(); + Ok(res) +} + + +fn into_literal(ts: &TokenTree) -> Option { + match ts { + TokenTree::Literal(l) => Some(l.clone()), + TokenTree::Group(g) => match g.delimiter() { + Delimiter::None => match g.stream().into_iter().collect::>().as_slice() { + [TokenTree::Literal(l)] => Some(l.clone()), + _ => None, + }, + Delimiter::Parenthesis | Delimiter::Brace | Delimiter::Bracket => None, + }, + _ => None, + } +} + +fn strip_matches<'a>(s: &'a str, pattern: &str) -> &'a str { + s.strip_prefix(pattern).unwrap_or(s).strip_suffix(pattern).unwrap_or(s) +} + +fn parse_ts(s: &str) -> TokenStream { + s.parse().unwrap() +} diff --git a/rusqlite-macros/tests/test.rs b/rusqlite-macros/tests/test.rs new file mode 100644 index 0000000..e67100f --- /dev/null +++ b/rusqlite-macros/tests/test.rs @@ -0,0 +1,22 @@ +use rusqlite_macros::__bind; + +#[test] +fn test_literal() { + let stmt = (); + __bind!(stmt, "SELECT $name"); +} + +/* FIXME +#[test] +fn test_raw_string() { + let stmt = (); + __bind!((), r#"SELECT 1"#); +} + +#[test] +fn test_const() { + const SQL: &str = "SELECT 1"; + let stmt = (); + __bind!((), SQL); +} +*/ \ No newline at end of file