From cc23f9cdd76fb926cb3853245cf16cfe9f91fb5e Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 20 Nov 2022 18:07:17 +0100 Subject: [PATCH 01/10] Captured identifiers in SQL strings Initial draft --- Cargo.toml | 5 +++ rusqlite-macros/Cargo.toml | 16 +++++++ rusqlite-macros/src/lib.rs | 81 +++++++++++++++++++++++++++++++++++ rusqlite-macros/tests/test.rs | 22 ++++++++++ 4 files changed, 124 insertions(+) create mode 100644 rusqlite-macros/Cargo.toml create mode 100644 rusqlite-macros/src/lib.rs create mode 100644 rusqlite-macros/tests/test.rs 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 From 5b20201423ed4578c0c1ede25617d3b369a9a88a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 4 Dec 2022 11:25:01 +0100 Subject: [PATCH 02/10] Captured identifiers in SQL strings Use `raw_bind_parameter` --- rusqlite-macros/Cargo.toml | 2 +- rusqlite-macros/src/lib.rs | 28 +++++++++++++++++++++++----- rusqlite-macros/tests/test.rs | 22 ++++++++++++++++++---- 3 files changed, 42 insertions(+), 10 deletions(-) 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 +*/ From 78b7c521054d57b95a3fec4d0531ebb54ffd22fc Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 26 Dec 2022 20:00:59 +0100 Subject: [PATCH 03/10] Captured identifiers in SQL strings Introduce macro_rules `prepare_and_bind` and `prepare_cached_and_bind` --- src/lib.rs | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index da07327..f0ba603 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,8 @@ pub use crate::statement::{Statement, StatementStatus}; pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior}; pub use crate::types::ToSql; pub use crate::version::*; +#[doc(hidden)] +pub use rusqlite_macros::__bind; mod error; @@ -219,6 +221,30 @@ macro_rules! named_params { }; } +/// Captured identifiers in SQL +#[macro_export] +macro_rules! prepare_and_bind { + ($conn:expr, $sql:literal) => {{ + #[cfg(trick_rust_analyzer_into_highlighting_interpolated_bits)] + format_args!($sql); + let mut stmt = $conn.prepare($sql)?; + $crate::__bind!(stmt, $sql); + stmt + }}; +} + +/// Captured identifiers in SQL +#[macro_export] +macro_rules! prepare_cached_and_bind { + ($conn:expr, $sql:literal) => {{ + #[cfg(trick_rust_analyzer_into_highlighting_interpolated_bits)] + format_args!($sql); + let mut stmt = $conn.prepare_cached($sql)?; + $crate::__bind!(stmt, $sql); + stmt + }}; +} + /// A typedef of the result returned by many methods. pub type Result = result::Result; @@ -2088,9 +2114,20 @@ mod test { } #[test] - pub fn db_readonly() -> Result<()> { + fn db_readonly() -> Result<()> { let db = Connection::open_in_memory()?; assert!(!db.is_readonly(MAIN_DB)?); Ok(()) } + + #[test] + fn prepare_and_bind() -> Result<()> { + let db = Connection::open_in_memory()?; + let name = "Lisa"; + let age = 8; + let mut stmt = prepare_and_bind!(db, "SELECT $name, $age;"); + let (v1, v2) = stmt.raw_query().get_expected_row().and_then(|r| Ok((r.get::<_,String>(0)?, r.get::<_,i64>(1)?)))?; + assert_eq!((v1.as_str(), v2), (name, age)); + Ok(()) + } } From b59b0ddf2e3906716abe3756f09217d57d3589ee Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 16 Apr 2023 16:17:36 +0200 Subject: [PATCH 04/10] Bump sqlite3-parser version --- libsqlite3-sys/build.rs | 1 + rusqlite-macros/Cargo.toml | 2 +- rusqlite-macros/src/lib.rs | 1 - rusqlite-macros/tests/test.rs | 6 +++--- src/lib.rs | 7 ++++--- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/libsqlite3-sys/build.rs b/libsqlite3-sys/build.rs index e3c065a..da785e4 100644 --- a/libsqlite3-sys/build.rs +++ b/libsqlite3-sys/build.rs @@ -497,6 +497,7 @@ mod bindings { None } } + fn item_name(&self, original_item_name: &str) -> Option { original_item_name .strip_prefix("sqlite3_index_info_") diff --git a/rusqlite-macros/Cargo.toml b/rusqlite-macros/Cargo.toml index 7dae87a..5c5db10 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.5.0", default-features = false, features = ["YYNOERRORRECOVERY"] } +sqlite3-parser = { version = "0.7.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 cf41258..2369639 100644 --- a/rusqlite-macros/src/lib.rs +++ b/rusqlite-macros/src/lib.rs @@ -21,7 +21,6 @@ fn try_bind(input: TokenStream) -> Result { let (stmt, literal) = { let mut iter = input.clone().into_iter(); let stmt = iter.next().unwrap(); - let _punct = iter.next().unwrap(); let literal = iter.next().unwrap(); assert!(iter.next().is_none()); (stmt, literal) diff --git a/rusqlite-macros/tests/test.rs b/rusqlite-macros/tests/test.rs index 7a97ae4..785ca9b 100644 --- a/rusqlite-macros/tests/test.rs +++ b/rusqlite-macros/tests/test.rs @@ -16,7 +16,7 @@ fn test_literal() -> Result { let first_name = "El"; let last_name = "Barto"; let mut stmt = Stmt; - __bind!(stmt, "SELECT $first_name, $last_name"); + __bind!(stmt "SELECT $first_name, $last_name"); Ok(()) } @@ -24,13 +24,13 @@ fn test_literal() -> Result { #[test] fn test_raw_string() { let stmt = (); - __bind!((), r#"SELECT 1"#); + __bind!(stmt r#"SELECT 1"#); } #[test] fn test_const() { const SQL: &str = "SELECT 1"; let stmt = (); - __bind!((), SQL); + __bind!(stmt SQL); } */ diff --git a/src/lib.rs b/src/lib.rs index 7f95735..1b3b17e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -221,7 +221,7 @@ macro_rules! prepare_and_bind { #[cfg(trick_rust_analyzer_into_highlighting_interpolated_bits)] format_args!($sql); let mut stmt = $conn.prepare($sql)?; - $crate::__bind!(stmt, $sql); + $crate::__bind!(stmt $sql); stmt }}; } @@ -233,7 +233,7 @@ macro_rules! prepare_cached_and_bind { #[cfg(trick_rust_analyzer_into_highlighting_interpolated_bits)] format_args!($sql); let mut stmt = $conn.prepare_cached($sql)?; - $crate::__bind!(stmt, $sql); + $crate::__bind!(stmt $sql); stmt }}; } @@ -923,7 +923,8 @@ impl Connection { /// /// This function is unsafe because improper use may impact the Connection. /// In particular, it should only be called on connections created - /// and owned by the caller, e.g. as a result of calling ffi::sqlite3_open(). + /// and owned by the caller, e.g. as a result of calling + /// ffi::sqlite3_open(). #[inline] pub unsafe fn from_handle_owned(db: *mut ffi::sqlite3) -> Result { let db = InnerConnection::new(db, true); From 0e369ba878c4e5915049fdfed931c6ff56af876d Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 16 Apr 2023 18:09:15 +0200 Subject: [PATCH 05/10] Misc --- src/busy.rs | 2 +- src/lib.rs | 6 +++++- src/vtab/vtablog.rs | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/busy.rs b/src/busy.rs index b9a5d40..18fa7e2 100644 --- a/src/busy.rs +++ b/src/busy.rs @@ -1,4 +1,4 @@ -///! Busy handler (when the database is locked) +//! Busy handler (when the database is locked) use std::convert::TryInto; use std::mem; use std::os::raw::{c_int, c_void}; diff --git a/src/lib.rs b/src/lib.rs index 1b3b17e..49ab436 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2120,7 +2120,11 @@ mod test { let name = "Lisa"; let age = 8; let mut stmt = prepare_and_bind!(db, "SELECT $name, $age;"); - let (v1, v2) = stmt.raw_query().get_expected_row().and_then(|r| Ok((r.get::<_,String>(0)?, r.get::<_,i64>(1)?)))?; + let (v1, v2) = stmt + .raw_query() + .next() + .and_then(|o| o.ok_or(Error::QueryReturnedNoRows)) + .and_then(|r| Ok((r.get::<_, String>(0)?, r.get::<_, i64>(1)?)))?; assert_eq!((v1.as_str(), v2), (name, age)); Ok(()) } diff --git a/src/vtab/vtablog.rs b/src/vtab/vtablog.rs index 1b3e1b8..f7aa1b1 100644 --- a/src/vtab/vtablog.rs +++ b/src/vtab/vtablog.rs @@ -1,4 +1,4 @@ -///! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c) +//! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c) use std::default::Default; use std::marker::PhantomData; use std::os::raw::c_int; From f0670ccaddf3fa2fc0c8ba6a6c7e3c1b015f3908 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 10 Jun 2023 10:55:52 +0200 Subject: [PATCH 06/10] Fix macro hygiene issue --- rusqlite-macros/src/lib.rs | 34 ++++++++++++++++++++++++++++------ src/lib.rs | 25 +++++++++++++++++++++---- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/rusqlite-macros/src/lib.rs b/rusqlite-macros/src/lib.rs index 2369639..7bfda9d 100644 --- a/rusqlite-macros/src/lib.rs +++ b/rusqlite-macros/src/lib.rs @@ -1,6 +1,6 @@ //! Private implementation details of `rusqlite`. -use proc_macro::{Delimiter, Literal, TokenStream, TokenTree}; +use proc_macro::{Delimiter, Group, Literal, Span, TokenStream, TokenTree}; use fallible_iterator::FallibleIterator; use sqlite3_parser::ast::{ParameterInfo, ToTokens}; @@ -58,15 +58,19 @@ fn try_bind(input: TokenStream) -> Result { return Err("Mixing named and numbered parameters is not supported.".to_string()); } + let call_site = literal.span(); 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..] - ))); + res.extend(respan( + parse_ts(&format!( + ".raw_bind_parameter({}, &{})?;", + i + 1, + &name[1..] + )), + call_site, + )); } Ok(res) @@ -93,6 +97,24 @@ fn strip_matches<'a>(s: &'a str, pattern: &str) -> &'a str { .unwrap_or(s) } +fn respan(ts: TokenStream, span: Span) -> TokenStream { + let mut res = TokenStream::new(); + for tt in ts { + let tt = match tt { + TokenTree::Ident(mut ident) => { + ident.set_span(ident.span().resolved_at(span).located_at(span)); + TokenTree::Ident(ident) + } + TokenTree::Group(group) => { + TokenTree::Group(Group::new(group.delimiter(), respan(group.stream(), span))) + } + _ => tt, + }; + res.extend(Some(tt)) + } + res +} + fn parse_ts(s: &str) -> TokenStream { s.parse().unwrap() } diff --git a/src/lib.rs b/src/lib.rs index 49ab436..3f6f1c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -215,11 +215,26 @@ macro_rules! named_params { } /// Captured identifiers in SQL +/// +/// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not +/// work). +/// * `$x.y` expression does not work. +/// +/// # Example +/// +/// ```rust, no_run +/// # use rusqlite::{prepare_and_bind, Connection, Result, Statement}; +/// +/// fn misc(db: &Connection) -> Result { +/// let name = "Lisa"; +/// let age = 8; +/// let smart = true; +/// Ok(prepare_and_bind!(db, "SELECT $name, @age, :smart;")) +/// } +/// ``` #[macro_export] macro_rules! prepare_and_bind { ($conn:expr, $sql:literal) => {{ - #[cfg(trick_rust_analyzer_into_highlighting_interpolated_bits)] - format_args!($sql); let mut stmt = $conn.prepare($sql)?; $crate::__bind!(stmt $sql); stmt @@ -227,11 +242,13 @@ macro_rules! prepare_and_bind { } /// Captured identifiers in SQL +/// +/// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not +/// work). +/// * `$x.y` expression does not work. #[macro_export] macro_rules! prepare_cached_and_bind { ($conn:expr, $sql:literal) => {{ - #[cfg(trick_rust_analyzer_into_highlighting_interpolated_bits)] - format_args!($sql); let mut stmt = $conn.prepare_cached($sql)?; $crate::__bind!(stmt $sql); stmt From 759471172194cd9952f338ef3a6eaac636700972 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 10 Jun 2023 12:05:55 +0200 Subject: [PATCH 07/10] Make rusqlite-macros optional --- Cargo.toml | 7 ++----- rusqlite-macros/Cargo.toml | 4 ++-- rusqlite-macros/src/lib.rs | 5 ----- src/lib.rs | 5 +++++ 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8c163c9..eca3e34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,6 +122,7 @@ fallible-iterator = "0.2" fallible-streaming-iterator = "0.1" uuid = { version = "1.0", optional = true } smallvec = "1.6.1" +rusqlite-macros = { path = "rusqlite-macros", version = "0.1.0", optional = true } [dev-dependencies] doc-comment = "0.3" @@ -133,16 +134,12 @@ unicase = "2.6.0" # Use `bencher` over criterion because it builds much faster and we don't have # many benchmarks bencher = "0.1" +rusqlite-macros = { path = "rusqlite-macros", version = "0.1.0" } [dependencies.libsqlite3-sys] path = "libsqlite3-sys" version = "0.26.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 index 5c5db10..3d40775 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.7.0", default-features = false, features = ["YYNOERRORRECOVERY"] } -fallible-iterator = "0.2" +sqlite3-parser = { version = "0.9", default-features = false, features = ["YYNOERRORRECOVERY"] } +fallible-iterator = "0.3" diff --git a/rusqlite-macros/src/lib.rs b/rusqlite-macros/src/lib.rs index 7bfda9d..8dda22c 100644 --- a/rusqlite-macros/src/lib.rs +++ b/rusqlite-macros/src/lib.rs @@ -17,7 +17,6 @@ pub fn __bind(input: TokenStream) -> TokenStream { type Result = std::result::Result; fn try_bind(input: TokenStream) -> Result { - //eprintln!("INPUT: {:#?}", input); let (stmt, literal) = { let mut iter = input.clone().into_iter(); let stmt = iter.next().unwrap(); @@ -35,7 +34,6 @@ fn try_bind(input: TokenStream) -> Result { 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() { @@ -52,8 +50,6 @@ fn try_bind(input: TokenStream) -> Result { 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()); } @@ -61,7 +57,6 @@ fn try_bind(input: TokenStream) -> Result { let call_site = literal.span(); 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(respan( parse_ts(&format!( diff --git a/src/lib.rs b/src/lib.rs index 3f6f1c4..12a5d31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ pub use crate::statement::{Statement, StatementStatus}; pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior}; pub use crate::types::ToSql; pub use crate::version::*; +#[cfg(feature = "rusqlite-macros")] #[doc(hidden)] pub use rusqlite_macros::__bind; @@ -232,6 +233,8 @@ macro_rules! named_params { /// Ok(prepare_and_bind!(db, "SELECT $name, @age, :smart;")) /// } /// ``` +#[cfg(feature = "rusqlite-macros")] +#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite-macros")))] #[macro_export] macro_rules! prepare_and_bind { ($conn:expr, $sql:literal) => {{ @@ -246,6 +249,8 @@ macro_rules! prepare_and_bind { /// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not /// work). /// * `$x.y` expression does not work. +#[cfg(feature = "rusqlite-macros")] +#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite-macros")))] #[macro_export] macro_rules! prepare_cached_and_bind { ($conn:expr, $sql:literal) => {{ From 048a442bc6886a9f561dd8058aea9b3ed738dab0 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 10 Jun 2023 12:14:41 +0200 Subject: [PATCH 08/10] Fix test build error --- Cargo.toml | 3 +-- src/lib.rs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eca3e34..fba0ccd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,7 +134,6 @@ unicase = "2.6.0" # Use `bencher` over criterion because it builds much faster and we don't have # many benchmarks bencher = "0.1" -rusqlite-macros = { path = "rusqlite-macros", version = "0.1.0" } [dependencies.libsqlite3-sys] path = "libsqlite3-sys" @@ -159,7 +158,7 @@ name = "exec" harness = false [package.metadata.docs.rs] -features = ["modern-full"] +features = ["modern-full", "rusqlite-macros"] all-features = false no-default-features = true default-target = "x86_64-unknown-linux-gnu" diff --git a/src/lib.rs b/src/lib.rs index 12a5d31..b390a42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2137,6 +2137,7 @@ mod test { } #[test] + #[cfg(feature = "rusqlite-macros")] fn prepare_and_bind() -> Result<()> { let db = Connection::open_in_memory()?; let name = "Lisa"; From b86d9321b5538c0977bcc7f0fd12393f346b4e7f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 20 Aug 2023 10:35:26 +0200 Subject: [PATCH 09/10] Support Rust expression like `{x.y}` in SQL strings --- rusqlite-macros/Cargo.toml | 2 +- rusqlite-macros/src/lib.rs | 6 +++++- rusqlite-macros/tests/test.rs | 25 ++++++++++++++++++++++++- src/lib.rs | 8 ++++---- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/rusqlite-macros/Cargo.toml b/rusqlite-macros/Cargo.toml index 3d40775..c50ebcf 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.9", default-features = false, features = ["YYNOERRORRECOVERY"] } +sqlite3-parser = { version = "0.10.0", default-features = false, features = ["YYNOERRORRECOVERY", "rust_variable"] } fallible-iterator = "0.3" diff --git a/rusqlite-macros/src/lib.rs b/rusqlite-macros/src/lib.rs index 8dda22c..583f839 100644 --- a/rusqlite-macros/src/lib.rs +++ b/rusqlite-macros/src/lib.rs @@ -58,11 +58,15 @@ fn try_bind(input: TokenStream) -> Result { let mut res = TokenStream::new(); for (i, name) in info.names.iter().enumerate() { res.extend(Some(stmt.clone())); + let offset = match name.as_bytes()[0] { + b'$' | b'@' | b'#' | b':' => 1, + _ => 0, // captured identifier: {...} + }; res.extend(respan( parse_ts(&format!( ".raw_bind_parameter({}, &{})?;", i + 1, - &name[1..] + &name[offset..] )), call_site, )); diff --git a/rusqlite-macros/tests/test.rs b/rusqlite-macros/tests/test.rs index 785ca9b..1499718 100644 --- a/rusqlite-macros/tests/test.rs +++ b/rusqlite-macros/tests/test.rs @@ -16,7 +16,30 @@ fn test_literal() -> Result { let first_name = "El"; let last_name = "Barto"; let mut stmt = Stmt; - __bind!(stmt "SELECT $first_name, $last_name"); + __bind!(stmt "SELECT $first_name, {last_name}"); + Ok(()) +} + +#[test] +fn test_tuple() -> Result { + let names = ("El", "Barto"); + let mut stmt = Stmt; + __bind!(stmt "SELECT {names.0}, {names.1}"); + Ok(()) +} + +#[test] +fn test_struct() -> Result { + struct Person<'s> { + first_name: &'s str, + last_name: &'s str, + } + let p = Person { + first_name: "El", + last_name: "Barto", + }; + let mut stmt = Stmt; + __bind!(stmt "SELECT {p.first_name}, {p.last_name}"); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 4106838..a9a3af4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,9 +220,9 @@ macro_rules! named_params { /// Captured identifiers in SQL /// -/// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not -/// work). +/// * SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not work). /// * `$x.y` expression does not work. +/// * `{x}` and `{x.y}` work /// /// # Example /// @@ -249,9 +249,9 @@ macro_rules! prepare_and_bind { /// Captured identifiers in SQL /// -/// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not -/// work). +/// * SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not work). /// * `$x.y` expression does not work. +/// * `{x}` and `{x.y}` work #[cfg(feature = "rusqlite-macros")] #[cfg_attr(docsrs, doc(cfg(feature = "rusqlite-macros")))] #[macro_export] From bbb570aabd20bae03b3d510acdcfece0e5e7cd6f Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 21 Aug 2023 19:40:37 +0200 Subject: [PATCH 10/10] Revert "Support Rust expression like `{x.y}` in SQL strings" This reverts commit b86d9321b5538c0977bcc7f0fd12393f346b4e7f. --- rusqlite-macros/Cargo.toml | 2 +- rusqlite-macros/src/lib.rs | 6 +----- rusqlite-macros/tests/test.rs | 25 +------------------------ src/lib.rs | 8 ++++---- 4 files changed, 7 insertions(+), 34 deletions(-) diff --git a/rusqlite-macros/Cargo.toml b/rusqlite-macros/Cargo.toml index c50ebcf..2fad35b 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.10.0", default-features = false, features = ["YYNOERRORRECOVERY", "rust_variable"] } +sqlite3-parser = { version = "0.11", default-features = false, features = ["YYNOERRORRECOVERY"] } fallible-iterator = "0.3" diff --git a/rusqlite-macros/src/lib.rs b/rusqlite-macros/src/lib.rs index 583f839..8dda22c 100644 --- a/rusqlite-macros/src/lib.rs +++ b/rusqlite-macros/src/lib.rs @@ -58,15 +58,11 @@ fn try_bind(input: TokenStream) -> Result { let mut res = TokenStream::new(); for (i, name) in info.names.iter().enumerate() { res.extend(Some(stmt.clone())); - let offset = match name.as_bytes()[0] { - b'$' | b'@' | b'#' | b':' => 1, - _ => 0, // captured identifier: {...} - }; res.extend(respan( parse_ts(&format!( ".raw_bind_parameter({}, &{})?;", i + 1, - &name[offset..] + &name[1..] )), call_site, )); diff --git a/rusqlite-macros/tests/test.rs b/rusqlite-macros/tests/test.rs index 1499718..785ca9b 100644 --- a/rusqlite-macros/tests/test.rs +++ b/rusqlite-macros/tests/test.rs @@ -16,30 +16,7 @@ fn test_literal() -> Result { let first_name = "El"; let last_name = "Barto"; let mut stmt = Stmt; - __bind!(stmt "SELECT $first_name, {last_name}"); - Ok(()) -} - -#[test] -fn test_tuple() -> Result { - let names = ("El", "Barto"); - let mut stmt = Stmt; - __bind!(stmt "SELECT {names.0}, {names.1}"); - Ok(()) -} - -#[test] -fn test_struct() -> Result { - struct Person<'s> { - first_name: &'s str, - last_name: &'s str, - } - let p = Person { - first_name: "El", - last_name: "Barto", - }; - let mut stmt = Stmt; - __bind!(stmt "SELECT {p.first_name}, {p.last_name}"); + __bind!(stmt "SELECT $first_name, $last_name"); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index a9a3af4..4106838 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,9 +220,9 @@ macro_rules! named_params { /// Captured identifiers in SQL /// -/// * SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not work). +/// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not +/// work). /// * `$x.y` expression does not work. -/// * `{x}` and `{x.y}` work /// /// # Example /// @@ -249,9 +249,9 @@ macro_rules! prepare_and_bind { /// Captured identifiers in SQL /// -/// * SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not work). +/// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not +/// work). /// * `$x.y` expression does not work. -/// * `{x}` and `{x.y}` work #[cfg(feature = "rusqlite-macros")] #[cfg_attr(docsrs, doc(cfg(feature = "rusqlite-macros")))] #[macro_export]