diff --git a/rusqlite-macros/Cargo.toml b/rusqlite-macros/Cargo.toml index af1c040..7dae87a 100644 --- a/rusqlite-macros/Cargo.toml +++ b/rusqlite-macros/Cargo.toml @@ -12,5 +12,5 @@ categories = ["database"] proc-macro = true [dependencies] -sqlite3-parser = { version = "0.4.0", default-features = false, features = ["YYNOERRORRECOVERY"] } +sqlite3-parser = { version = "0.5.0", default-features = false, features = ["YYNOERRORRECOVERY"] } fallible-iterator = "0.2" diff --git a/rusqlite-macros/src/lib.rs b/rusqlite-macros/src/lib.rs index a09694e..cf41258 100644 --- a/rusqlite-macros/src/lib.rs +++ b/rusqlite-macros/src/lib.rs @@ -3,8 +3,8 @@ use proc_macro::{Delimiter, Literal, TokenStream, TokenTree}; use fallible_iterator::FallibleIterator; -use sqlite3_parser::lexer::sql::Parser; use sqlite3_parser::ast::{ParameterInfo, ToTokens}; +use sqlite3_parser::lexer::sql::Parser; // https://internals.rust-lang.org/t/custom-error-diagnostics-with-procedural-macros-on-almost-stable-rust/8113 @@ -19,7 +19,7 @@ type Result = std::result::Result; fn try_bind(input: TokenStream) -> Result { //eprintln!("INPUT: {:#?}", input); let (stmt, literal) = { - let mut iter = input.into_iter(); + let mut iter = input.clone().into_iter(); let stmt = iter.next().unwrap(); let _punct = iter.next().unwrap(); let literal = iter.next().unwrap(); @@ -44,20 +44,35 @@ fn try_bind(input: TokenStream) -> Result { Err(err) => { return Err(err.to_string()); } - Ok(Some(ast)) => ast + Ok(Some(ast)) => ast, }; let mut info = ParameterInfo::default(); if let Err(err) = ast.to_tokens(&mut info) { return Err(err.to_string()); } + if info.count == 0 { + return Ok(input); + } //eprintln!("ParameterInfo.count: {:#?}", info.count); //eprintln!("ParameterInfo.names: {:#?}", info.names); + if info.count as usize != info.names.len() { + return Err("Mixing named and numbered parameters is not supported.".to_string()); + } let mut res = TokenStream::new(); + for (i, name) in info.names.iter().enumerate() { + //eprintln!("(i: {}, name: {})", i + 1, &name[1..]); + res.extend(Some(stmt.clone())); + res.extend(parse_ts(&format!( + ".raw_bind_parameter({}, &{})?;", + i + 1, + &name[1..] + ))); + } + Ok(res) } - fn into_literal(ts: &TokenTree) -> Option { match ts { TokenTree::Literal(l) => Some(l.clone()), @@ -73,7 +88,10 @@ fn into_literal(ts: &TokenTree) -> Option { } fn strip_matches<'a>(s: &'a str, pattern: &str) -> &'a str { - s.strip_prefix(pattern).unwrap_or(s).strip_suffix(pattern).unwrap_or(s) + s.strip_prefix(pattern) + .unwrap_or(s) + .strip_suffix(pattern) + .unwrap_or(s) } fn parse_ts(s: &str) -> TokenStream { diff --git a/rusqlite-macros/tests/test.rs b/rusqlite-macros/tests/test.rs index e67100f..7a97ae4 100644 --- a/rusqlite-macros/tests/test.rs +++ b/rusqlite-macros/tests/test.rs @@ -1,9 +1,23 @@ use rusqlite_macros::__bind; +type Result = std::result::Result<(), String>; + +struct Stmt; + +impl Stmt { + pub fn raw_bind_parameter(&mut self, one_based_col_index: usize, param: &str) -> Result { + let (..) = (one_based_col_index, param); + Ok(()) + } +} + #[test] -fn test_literal() { - let stmt = (); - __bind!(stmt, "SELECT $name"); +fn test_literal() -> Result { + let first_name = "El"; + let last_name = "Barto"; + let mut stmt = Stmt; + __bind!(stmt, "SELECT $first_name, $last_name"); + Ok(()) } /* FIXME @@ -19,4 +33,4 @@ fn test_const() { let stmt = (); __bind!((), SQL); } -*/ \ No newline at end of file +*/