diff --git a/Cargo.toml b/Cargo.toml index 2475a0b..6746a16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,3 @@ -[workspace] -members = ["core", "macros"] - [package] name = "sqlx-conditional-queries" version = "0.3.2" @@ -14,11 +11,14 @@ categories = ["database"] [package.metadata.docs.rs] features = ["postgres"] -[dependencies] -futures-core = "0.3.31" -sqlx-conditional-queries-macros = { path = "macros", version = "0.3" } +[workspace] +members = ["core", "macros"] [features] mysql = ["sqlx-conditional-queries-macros/mysql"] postgres = ["sqlx-conditional-queries-macros/postgres"] sqlite = ["sqlx-conditional-queries-macros/sqlite"] + +[dependencies] +futures-core = "0.3.31" +sqlx-conditional-queries-macros = { path = "macros", version = "0.3" } diff --git a/core/src/codegen.rs b/core/src/codegen.rs index 3def90e..dc2c0ec 100644 --- a/core/src/codegen.rs +++ b/core/src/codegen.rs @@ -5,7 +5,10 @@ use crate::expand::ExpandedConditionalQueryAs; /// This is the final step of the macro generation pipeline. /// The match arms and the respective query fragments are now used to generate a giant match /// statement, which covers all variants of the bindings' match statements' cartesian products. -pub(crate) fn codegen(expanded: ExpandedConditionalQueryAs) -> proc_macro2::TokenStream { +pub(crate) fn codegen( + expanded: ExpandedConditionalQueryAs, + checked: bool, +) -> proc_macro2::TokenStream { let mut match_arms = Vec::new(); for (idx, arm) in expanded.match_arms.iter().enumerate() { let patterns = &arm.patterns; @@ -20,10 +23,16 @@ pub(crate) fn codegen(expanded: ExpandedConditionalQueryAs) -> proc_macro2::Toke None => quote!(#name), }); + let query = if checked { + format_ident!("query_as") + } else { + format_ident!("query_as_unchecked") + }; + match_arms.push(quote! { (#(#patterns,)*) => { ConditionalMap::#variant( - ::sqlx::query_as!( + ::sqlx::#query!( #output_type, #(#query_fragments)+*, #(#run_time_bindings),* @@ -192,10 +201,13 @@ mod tests { use super::*; #[rstest::rstest] - #[case(DatabaseType::PostgreSql)] - #[case(DatabaseType::MySql)] - #[case(DatabaseType::Sqlite)] - fn valid_syntax(#[case] database_type: DatabaseType) { + #[case(DatabaseType::PostgreSql, true)] + #[case(DatabaseType::PostgreSql, false)] + #[case(DatabaseType::MySql, true)] + #[case(DatabaseType::MySql, false)] + #[case(DatabaseType::Sqlite, true)] + #[case(DatabaseType::Sqlite, false)] + fn valid_syntax(#[case] database_type: DatabaseType, #[case] checked: bool) { let parsed = syn::parse_str::( r#" SomeType, @@ -214,14 +226,17 @@ mod tests { let analyzed = crate::analyze::analyze(parsed.clone()).unwrap(); let lowered = crate::lower::lower(analyzed); let expanded = crate::expand::expand(database_type, lowered).unwrap(); - let _codegened = codegen(expanded); + let _codegened = codegen(expanded, checked); } #[rstest::rstest] - #[case(DatabaseType::PostgreSql)] - #[case(DatabaseType::MySql)] - #[case(DatabaseType::Sqlite)] - fn type_override(#[case] database_type: DatabaseType) { + #[case(DatabaseType::PostgreSql, true)] + #[case(DatabaseType::PostgreSql, false)] + #[case(DatabaseType::MySql, true)] + #[case(DatabaseType::MySql, false)] + #[case(DatabaseType::Sqlite, true)] + #[case(DatabaseType::Sqlite, false)] + fn type_override(#[case] database_type: DatabaseType, #[case] checked: bool) { let parsed = syn::parse_str::( r#" SomeType, @@ -232,7 +247,7 @@ mod tests { let analyzed = crate::analyze::analyze(parsed.clone()).unwrap(); let lowered = crate::lower::lower(analyzed); let expanded = crate::expand::expand(database_type, lowered).unwrap(); - let codegened = codegen(expanded); + let codegened = codegen(expanded, checked); let stringified = codegened.to_string(); assert!( diff --git a/core/src/lib.rs b/core/src/lib.rs index f85121a..4560df9 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -32,12 +32,13 @@ pub enum Error { pub fn conditional_query_as( database_type: DatabaseType, input: proc_macro2::TokenStream, + checked: bool, ) -> Result { let parsed = syn::parse2::(input)?; let analyzed = analyze::analyze(parsed)?; let lowered = lower::lower(analyzed); let expanded = expand::expand(database_type, lowered)?; - let codegened = codegen::codegen(expanded); + let codegened = codegen::codegen(expanded, checked); Ok(codegened) } diff --git a/core/src/snapshot_tests.rs b/core/src/snapshot_tests.rs index be1c21d..9bc514b 100644 --- a/core/src/snapshot_tests.rs +++ b/core/src/snapshot_tests.rs @@ -23,11 +23,18 @@ fn prettyprint(ts: TokenStream) -> String { } #[rstest::rstest] -#[case::postgres(DatabaseType::PostgreSql)] -#[case::mysql(DatabaseType::MySql)] -#[case::sqlite(DatabaseType::Sqlite)] -fn only_runtime_bound_parameters(#[case] database_type: DatabaseType) { - set_snapshot_suffix!("{:?}", database_type); +#[case::postgres(DatabaseType::PostgreSql, true)] +#[case::postgres_unchecked(DatabaseType::PostgreSql, false)] +#[case::mysql(DatabaseType::MySql, true)] +#[case::mysql_unchecked(DatabaseType::MySql, false)] +#[case::sqlite(DatabaseType::Sqlite, true)] +#[case::sqlite_unchecked(DatabaseType::Sqlite, false)] +fn only_runtime_bound_parameters(#[case] database_type: DatabaseType, #[case] checked: bool) { + set_snapshot_suffix!( + "{:?}{}", + database_type, + if checked { "" } else { "_unchecked" } + ); let input = quote::quote! { OutputType, r#" @@ -36,16 +43,23 @@ fn only_runtime_bound_parameters(#[case] database_type: DatabaseType) { WHERE created_at > {created_at} "#, }; - let output = crate::conditional_query_as(database_type, input).unwrap(); + let output = crate::conditional_query_as(database_type, input, checked).unwrap(); insta::assert_snapshot!(prettyprint(output)); } #[rstest::rstest] -#[case::postgres(DatabaseType::PostgreSql)] -#[case::mysql(DatabaseType::MySql)] -#[case::sqlite(DatabaseType::Sqlite)] -fn only_compile_time_bound_parameters(#[case] database_type: DatabaseType) { - set_snapshot_suffix!("{:?}", database_type); +#[case::postgres(DatabaseType::PostgreSql, true)] +#[case::postgres_unchecked(DatabaseType::PostgreSql, false)] +#[case::mysql(DatabaseType::MySql, true)] +#[case::mysql_unchecked(DatabaseType::MySql, false)] +#[case::sqlite(DatabaseType::Sqlite, true)] +#[case::sqlite_unchecked(DatabaseType::Sqlite, false)] +fn only_compile_time_bound_parameters(#[case] database_type: DatabaseType, #[case] checked: bool) { + set_snapshot_suffix!( + "{:?}{}", + database_type, + if checked { "" } else { "_unchecked" } + ); let hash = proc_macro2::Punct::new('#', proc_macro2::Spacing::Alone); let input = quote::quote! { OutputType, @@ -58,16 +72,23 @@ fn only_compile_time_bound_parameters(#[case] database_type: DatabaseType) { _ => "value", }, }; - let output = crate::conditional_query_as(database_type, input).unwrap(); + let output = crate::conditional_query_as(database_type, input, checked).unwrap(); insta::assert_snapshot!(prettyprint(output)); } #[rstest::rstest] -#[case::postgres(DatabaseType::PostgreSql)] -#[case::mysql(DatabaseType::MySql)] -#[case::sqlite(DatabaseType::Sqlite)] -fn both_parameter_kinds(#[case] database_type: DatabaseType) { - set_snapshot_suffix!("{:?}", database_type); +#[case::postgres(DatabaseType::PostgreSql, true)] +#[case::postgres_unchecked(DatabaseType::PostgreSql, false)] +#[case::mysql(DatabaseType::MySql, true)] +#[case::mysql_unchecked(DatabaseType::MySql, false)] +#[case::sqlite(DatabaseType::Sqlite, true)] +#[case::sqlite_unchecked(DatabaseType::Sqlite, false)] +fn both_parameter_kinds(#[case] database_type: DatabaseType, #[case] checked: bool) { + set_snapshot_suffix!( + "{:?}{}", + database_type, + if checked { "" } else { "_unchecked" } + ); let hash = proc_macro2::Punct::new('#', proc_macro2::Spacing::Alone); let input = quote::quote! { OutputType, @@ -82,6 +103,6 @@ fn both_parameter_kinds(#[case] database_type: DatabaseType) { _ => "value", }, }; - let output = crate::conditional_query_as(database_type, input).unwrap(); + let output = crate::conditional_query_as(database_type, input, checked).unwrap(); insta::assert_snapshot!(prettyprint(output)); } diff --git a/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__both_parameter_kinds@MySql_unchecked.snap b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__both_parameter_kinds@MySql_unchecked.snap new file mode 100644 index 0000000..a4addf9 --- /dev/null +++ b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__both_parameter_kinds@MySql_unchecked.snap @@ -0,0 +1,112 @@ +--- +source: core/src/snapshot_tests.rs +expression: prettyprint(output) +--- +fn dummy() { + { + enum ConditionalMap<'q, DB: ::sqlx::Database, A, F0> { + Variant0(::sqlx::query::Map<'q, DB, F0, A>), + } + impl<'q, DB, A, O, F0> ConditionalMap<'q, DB, A, F0> + where + DB: ::sqlx::Database, + A: 'q + ::sqlx::IntoArguments<'q, DB> + ::std::marker::Send, + O: ::std::marker::Unpin + ::std::marker::Send, + F0: ::std::ops::FnMut(DB::Row) -> ::sqlx::Result + ::std::marker::Send, + { + /// See [`sqlx::query::Map::fetch`] + pub fn fetch<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream<'e, ::sqlx::Result> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch(executor), + } + } + /// See [`sqlx::query::Map::fetch_many`] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] + pub fn fetch_many<'e, 'c: 'e, E>( + mut self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream< + 'e, + ::sqlx::Result<::sqlx::Either>, + > + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => #[allow(deprecated)] map.fetch_many(executor), + } + } + /// See [`sqlx::query::Map::fetch_all`] + pub async fn fetch_all<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::vec::Vec> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_all(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_one`] + pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> ::sqlx::Result + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_one(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_optional`] + pub async fn fetch_optional<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::option::Option> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_optional(executor).await, + } + } + } + match (value,) { + (_,) => { + ConditionalMap::Variant0( + ::sqlx::query_as_unchecked!( + OutputType, + "\n SELECT column\n FROM table\n WHERE\n created_at > " + + "" + "?" + "\n AND value = " + "value" + + "\n ", created_at + ), + ) + } + } + } +} diff --git a/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__both_parameter_kinds@PostgreSql_unchecked.snap b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__both_parameter_kinds@PostgreSql_unchecked.snap new file mode 100644 index 0000000..b94c801 --- /dev/null +++ b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__both_parameter_kinds@PostgreSql_unchecked.snap @@ -0,0 +1,112 @@ +--- +source: core/src/snapshot_tests.rs +expression: prettyprint(output) +--- +fn dummy() { + { + enum ConditionalMap<'q, DB: ::sqlx::Database, A, F0> { + Variant0(::sqlx::query::Map<'q, DB, F0, A>), + } + impl<'q, DB, A, O, F0> ConditionalMap<'q, DB, A, F0> + where + DB: ::sqlx::Database, + A: 'q + ::sqlx::IntoArguments<'q, DB> + ::std::marker::Send, + O: ::std::marker::Unpin + ::std::marker::Send, + F0: ::std::ops::FnMut(DB::Row) -> ::sqlx::Result + ::std::marker::Send, + { + /// See [`sqlx::query::Map::fetch`] + pub fn fetch<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream<'e, ::sqlx::Result> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch(executor), + } + } + /// See [`sqlx::query::Map::fetch_many`] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] + pub fn fetch_many<'e, 'c: 'e, E>( + mut self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream< + 'e, + ::sqlx::Result<::sqlx::Either>, + > + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => #[allow(deprecated)] map.fetch_many(executor), + } + } + /// See [`sqlx::query::Map::fetch_all`] + pub async fn fetch_all<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::vec::Vec> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_all(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_one`] + pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> ::sqlx::Result + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_one(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_optional`] + pub async fn fetch_optional<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::option::Option> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_optional(executor).await, + } + } + } + match (value,) { + (_,) => { + ConditionalMap::Variant0( + ::sqlx::query_as_unchecked!( + OutputType, + "\n SELECT column\n FROM table\n WHERE\n created_at > " + + "" + "$1" + "\n AND value = " + "value" + + "\n ", created_at + ), + ) + } + } + } +} diff --git a/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__both_parameter_kinds@Sqlite_unchecked.snap b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__both_parameter_kinds@Sqlite_unchecked.snap new file mode 100644 index 0000000..a4addf9 --- /dev/null +++ b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__both_parameter_kinds@Sqlite_unchecked.snap @@ -0,0 +1,112 @@ +--- +source: core/src/snapshot_tests.rs +expression: prettyprint(output) +--- +fn dummy() { + { + enum ConditionalMap<'q, DB: ::sqlx::Database, A, F0> { + Variant0(::sqlx::query::Map<'q, DB, F0, A>), + } + impl<'q, DB, A, O, F0> ConditionalMap<'q, DB, A, F0> + where + DB: ::sqlx::Database, + A: 'q + ::sqlx::IntoArguments<'q, DB> + ::std::marker::Send, + O: ::std::marker::Unpin + ::std::marker::Send, + F0: ::std::ops::FnMut(DB::Row) -> ::sqlx::Result + ::std::marker::Send, + { + /// See [`sqlx::query::Map::fetch`] + pub fn fetch<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream<'e, ::sqlx::Result> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch(executor), + } + } + /// See [`sqlx::query::Map::fetch_many`] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] + pub fn fetch_many<'e, 'c: 'e, E>( + mut self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream< + 'e, + ::sqlx::Result<::sqlx::Either>, + > + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => #[allow(deprecated)] map.fetch_many(executor), + } + } + /// See [`sqlx::query::Map::fetch_all`] + pub async fn fetch_all<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::vec::Vec> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_all(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_one`] + pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> ::sqlx::Result + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_one(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_optional`] + pub async fn fetch_optional<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::option::Option> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_optional(executor).await, + } + } + } + match (value,) { + (_,) => { + ConditionalMap::Variant0( + ::sqlx::query_as_unchecked!( + OutputType, + "\n SELECT column\n FROM table\n WHERE\n created_at > " + + "" + "?" + "\n AND value = " + "value" + + "\n ", created_at + ), + ) + } + } + } +} diff --git a/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_compile_time_bound_parameters@MySql_unchecked.snap b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_compile_time_bound_parameters@MySql_unchecked.snap new file mode 100644 index 0000000..a2de334 --- /dev/null +++ b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_compile_time_bound_parameters@MySql_unchecked.snap @@ -0,0 +1,111 @@ +--- +source: core/src/snapshot_tests.rs +expression: prettyprint(output) +--- +fn dummy() { + { + enum ConditionalMap<'q, DB: ::sqlx::Database, A, F0> { + Variant0(::sqlx::query::Map<'q, DB, F0, A>), + } + impl<'q, DB, A, O, F0> ConditionalMap<'q, DB, A, F0> + where + DB: ::sqlx::Database, + A: 'q + ::sqlx::IntoArguments<'q, DB> + ::std::marker::Send, + O: ::std::marker::Unpin + ::std::marker::Send, + F0: ::std::ops::FnMut(DB::Row) -> ::sqlx::Result + ::std::marker::Send, + { + /// See [`sqlx::query::Map::fetch`] + pub fn fetch<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream<'e, ::sqlx::Result> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch(executor), + } + } + /// See [`sqlx::query::Map::fetch_many`] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] + pub fn fetch_many<'e, 'c: 'e, E>( + mut self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream< + 'e, + ::sqlx::Result<::sqlx::Either>, + > + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => #[allow(deprecated)] map.fetch_many(executor), + } + } + /// See [`sqlx::query::Map::fetch_all`] + pub async fn fetch_all<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::vec::Vec> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_all(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_one`] + pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> ::sqlx::Result + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_one(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_optional`] + pub async fn fetch_optional<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::option::Option> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_optional(executor).await, + } + } + } + match (value,) { + (_,) => { + ConditionalMap::Variant0( + ::sqlx::query_as_unchecked!( + OutputType, + "\n SELECT column\n FROM table\n WHERE value = " + + "value" + "\n ", + ), + ) + } + } + } +} diff --git a/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_compile_time_bound_parameters@PostgreSql_unchecked.snap b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_compile_time_bound_parameters@PostgreSql_unchecked.snap new file mode 100644 index 0000000..a2de334 --- /dev/null +++ b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_compile_time_bound_parameters@PostgreSql_unchecked.snap @@ -0,0 +1,111 @@ +--- +source: core/src/snapshot_tests.rs +expression: prettyprint(output) +--- +fn dummy() { + { + enum ConditionalMap<'q, DB: ::sqlx::Database, A, F0> { + Variant0(::sqlx::query::Map<'q, DB, F0, A>), + } + impl<'q, DB, A, O, F0> ConditionalMap<'q, DB, A, F0> + where + DB: ::sqlx::Database, + A: 'q + ::sqlx::IntoArguments<'q, DB> + ::std::marker::Send, + O: ::std::marker::Unpin + ::std::marker::Send, + F0: ::std::ops::FnMut(DB::Row) -> ::sqlx::Result + ::std::marker::Send, + { + /// See [`sqlx::query::Map::fetch`] + pub fn fetch<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream<'e, ::sqlx::Result> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch(executor), + } + } + /// See [`sqlx::query::Map::fetch_many`] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] + pub fn fetch_many<'e, 'c: 'e, E>( + mut self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream< + 'e, + ::sqlx::Result<::sqlx::Either>, + > + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => #[allow(deprecated)] map.fetch_many(executor), + } + } + /// See [`sqlx::query::Map::fetch_all`] + pub async fn fetch_all<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::vec::Vec> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_all(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_one`] + pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> ::sqlx::Result + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_one(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_optional`] + pub async fn fetch_optional<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::option::Option> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_optional(executor).await, + } + } + } + match (value,) { + (_,) => { + ConditionalMap::Variant0( + ::sqlx::query_as_unchecked!( + OutputType, + "\n SELECT column\n FROM table\n WHERE value = " + + "value" + "\n ", + ), + ) + } + } + } +} diff --git a/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_compile_time_bound_parameters@Sqlite_unchecked.snap b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_compile_time_bound_parameters@Sqlite_unchecked.snap new file mode 100644 index 0000000..a2de334 --- /dev/null +++ b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_compile_time_bound_parameters@Sqlite_unchecked.snap @@ -0,0 +1,111 @@ +--- +source: core/src/snapshot_tests.rs +expression: prettyprint(output) +--- +fn dummy() { + { + enum ConditionalMap<'q, DB: ::sqlx::Database, A, F0> { + Variant0(::sqlx::query::Map<'q, DB, F0, A>), + } + impl<'q, DB, A, O, F0> ConditionalMap<'q, DB, A, F0> + where + DB: ::sqlx::Database, + A: 'q + ::sqlx::IntoArguments<'q, DB> + ::std::marker::Send, + O: ::std::marker::Unpin + ::std::marker::Send, + F0: ::std::ops::FnMut(DB::Row) -> ::sqlx::Result + ::std::marker::Send, + { + /// See [`sqlx::query::Map::fetch`] + pub fn fetch<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream<'e, ::sqlx::Result> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch(executor), + } + } + /// See [`sqlx::query::Map::fetch_many`] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] + pub fn fetch_many<'e, 'c: 'e, E>( + mut self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream< + 'e, + ::sqlx::Result<::sqlx::Either>, + > + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => #[allow(deprecated)] map.fetch_many(executor), + } + } + /// See [`sqlx::query::Map::fetch_all`] + pub async fn fetch_all<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::vec::Vec> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_all(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_one`] + pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> ::sqlx::Result + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_one(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_optional`] + pub async fn fetch_optional<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::option::Option> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_optional(executor).await, + } + } + } + match (value,) { + (_,) => { + ConditionalMap::Variant0( + ::sqlx::query_as_unchecked!( + OutputType, + "\n SELECT column\n FROM table\n WHERE value = " + + "value" + "\n ", + ), + ) + } + } + } +} diff --git a/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_runtime_bound_parameters@MySql_unchecked.snap b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_runtime_bound_parameters@MySql_unchecked.snap new file mode 100644 index 0000000..c7e2394 --- /dev/null +++ b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_runtime_bound_parameters@MySql_unchecked.snap @@ -0,0 +1,111 @@ +--- +source: core/src/snapshot_tests.rs +expression: prettyprint(output) +--- +fn dummy() { + { + enum ConditionalMap<'q, DB: ::sqlx::Database, A, F0> { + Variant0(::sqlx::query::Map<'q, DB, F0, A>), + } + impl<'q, DB, A, O, F0> ConditionalMap<'q, DB, A, F0> + where + DB: ::sqlx::Database, + A: 'q + ::sqlx::IntoArguments<'q, DB> + ::std::marker::Send, + O: ::std::marker::Unpin + ::std::marker::Send, + F0: ::std::ops::FnMut(DB::Row) -> ::sqlx::Result + ::std::marker::Send, + { + /// See [`sqlx::query::Map::fetch`] + pub fn fetch<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream<'e, ::sqlx::Result> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch(executor), + } + } + /// See [`sqlx::query::Map::fetch_many`] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] + pub fn fetch_many<'e, 'c: 'e, E>( + mut self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream< + 'e, + ::sqlx::Result<::sqlx::Either>, + > + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => #[allow(deprecated)] map.fetch_many(executor), + } + } + /// See [`sqlx::query::Map::fetch_all`] + pub async fn fetch_all<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::vec::Vec> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_all(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_one`] + pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> ::sqlx::Result + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_one(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_optional`] + pub async fn fetch_optional<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::option::Option> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_optional(executor).await, + } + } + } + match ((),) { + ((),) => { + ConditionalMap::Variant0( + ::sqlx::query_as_unchecked!( + OutputType, + "\n SELECT column\n FROM table\n WHERE created_at > " + + "?" + "\n ", created_at + ), + ) + } + } + } +} diff --git a/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_runtime_bound_parameters@PostgreSql_unchecked.snap b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_runtime_bound_parameters@PostgreSql_unchecked.snap new file mode 100644 index 0000000..1f8cc69 --- /dev/null +++ b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_runtime_bound_parameters@PostgreSql_unchecked.snap @@ -0,0 +1,111 @@ +--- +source: core/src/snapshot_tests.rs +expression: prettyprint(output) +--- +fn dummy() { + { + enum ConditionalMap<'q, DB: ::sqlx::Database, A, F0> { + Variant0(::sqlx::query::Map<'q, DB, F0, A>), + } + impl<'q, DB, A, O, F0> ConditionalMap<'q, DB, A, F0> + where + DB: ::sqlx::Database, + A: 'q + ::sqlx::IntoArguments<'q, DB> + ::std::marker::Send, + O: ::std::marker::Unpin + ::std::marker::Send, + F0: ::std::ops::FnMut(DB::Row) -> ::sqlx::Result + ::std::marker::Send, + { + /// See [`sqlx::query::Map::fetch`] + pub fn fetch<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream<'e, ::sqlx::Result> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch(executor), + } + } + /// See [`sqlx::query::Map::fetch_many`] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] + pub fn fetch_many<'e, 'c: 'e, E>( + mut self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream< + 'e, + ::sqlx::Result<::sqlx::Either>, + > + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => #[allow(deprecated)] map.fetch_many(executor), + } + } + /// See [`sqlx::query::Map::fetch_all`] + pub async fn fetch_all<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::vec::Vec> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_all(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_one`] + pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> ::sqlx::Result + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_one(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_optional`] + pub async fn fetch_optional<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::option::Option> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_optional(executor).await, + } + } + } + match ((),) { + ((),) => { + ConditionalMap::Variant0( + ::sqlx::query_as_unchecked!( + OutputType, + "\n SELECT column\n FROM table\n WHERE created_at > " + + "$1" + "\n ", created_at + ), + ) + } + } + } +} diff --git a/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_runtime_bound_parameters@Sqlite_unchecked.snap b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_runtime_bound_parameters@Sqlite_unchecked.snap new file mode 100644 index 0000000..c7e2394 --- /dev/null +++ b/core/src/snapshots/sqlx_conditional_queries_core__snapshot_tests__only_runtime_bound_parameters@Sqlite_unchecked.snap @@ -0,0 +1,111 @@ +--- +source: core/src/snapshot_tests.rs +expression: prettyprint(output) +--- +fn dummy() { + { + enum ConditionalMap<'q, DB: ::sqlx::Database, A, F0> { + Variant0(::sqlx::query::Map<'q, DB, F0, A>), + } + impl<'q, DB, A, O, F0> ConditionalMap<'q, DB, A, F0> + where + DB: ::sqlx::Database, + A: 'q + ::sqlx::IntoArguments<'q, DB> + ::std::marker::Send, + O: ::std::marker::Unpin + ::std::marker::Send, + F0: ::std::ops::FnMut(DB::Row) -> ::sqlx::Result + ::std::marker::Send, + { + /// See [`sqlx::query::Map::fetch`] + pub fn fetch<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream<'e, ::sqlx::Result> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch(executor), + } + } + /// See [`sqlx::query::Map::fetch_many`] + #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] + pub fn fetch_many<'e, 'c: 'e, E>( + mut self, + executor: E, + ) -> ::sqlx_conditional_queries::exports::BoxStream< + 'e, + ::sqlx::Result<::sqlx::Either>, + > + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => #[allow(deprecated)] map.fetch_many(executor), + } + } + /// See [`sqlx::query::Map::fetch_all`] + pub async fn fetch_all<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::vec::Vec> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_all(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_one`] + pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> ::sqlx::Result + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_one(executor).await, + } + } + /// See [`sqlx::query::Map::fetch_optional`] + pub async fn fetch_optional<'e, 'c: 'e, E>( + self, + executor: E, + ) -> ::sqlx::Result<::std::option::Option> + where + 'q: 'e, + E: 'e + ::sqlx::Executor<'c, Database = DB>, + DB: 'e, + O: 'e, + F0: 'e, + { + match self { + Self::Variant0(map) => map.fetch_optional(executor).await, + } + } + } + match ((),) { + ((),) => { + ConditionalMap::Variant0( + ::sqlx::query_as_unchecked!( + OutputType, + "\n SELECT column\n FROM table\n WHERE created_at > " + + "?" + "\n ", created_at + ), + ) + } + } + } +} diff --git a/core/tests/regressions/issue_4.rs b/core/tests/regressions/issue_4.rs index 625c396..8525c8a 100644 --- a/core/tests/regressions/issue_4.rs +++ b/core/tests/regressions/issue_4.rs @@ -1,10 +1,13 @@ use sqlx_conditional_queries_core::DatabaseType; #[rstest::rstest] -#[case(DatabaseType::PostgreSql)] -#[case(DatabaseType::MySql)] -#[case(DatabaseType::Sqlite)] -fn regression_test_(#[case] database_type: DatabaseType) { +#[case(DatabaseType::PostgreSql, true)] +#[case(DatabaseType::PostgreSql, false)] +#[case(DatabaseType::MySql, true)] +#[case(DatabaseType::MySql, false)] +#[case(DatabaseType::Sqlite, true)] +#[case(DatabaseType::Sqlite, false)] +fn regression_test_(#[case] database_type: DatabaseType, #[case] checked: bool) { let input: proc_macro2::TokenStream = r##" SomeType, r#"{#a}______{c}"#, @@ -15,5 +18,5 @@ fn regression_test_(#[case] database_type: DatabaseType) { .parse() .unwrap(); - sqlx_conditional_queries_core::conditional_query_as(database_type, input).unwrap(); + sqlx_conditional_queries_core::conditional_query_as(database_type, input, checked).unwrap(); } diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 5e09777..befe138 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -9,12 +9,12 @@ license = "MIT OR Apache-2.0" [lib] proc-macro = true -[dependencies] -proc-macro-error2 = "2.0.1" -proc-macro2 = "1.0.92" -sqlx-conditional-queries-core = { path = "../core", version = "0.3" } - [features] mysql = [] postgres = [] sqlite = [] + +[dependencies] +proc-macro-error2 = "2.0.1" +proc-macro2 = "1.0.92" +sqlx-conditional-queries-core = { path = "../core", version = "0.3" } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 078381f..df59fca 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -17,9 +17,27 @@ const DATABASE_TYPE: DatabaseType = if cfg!(feature = "postgres") { #[proc_macro_error2::proc_macro_error] #[proc_macro] pub fn conditional_query_as(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + conditional_query_as_inner(input, true) +} + +// The public docs for this macro live in the sql-conditional-queries crate. +#[proc_macro_error2::proc_macro_error] +#[proc_macro] +pub fn conditional_query_as_unchecked(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + conditional_query_as_inner(input, false) +} + +fn conditional_query_as_inner( + input: proc_macro::TokenStream, + checked: bool, +) -> proc_macro::TokenStream { let input: proc_macro2::TokenStream = input.into(); - let ts = match sqlx_conditional_queries_core::conditional_query_as(DATABASE_TYPE, input) { + let ts = match sqlx_conditional_queries_core::conditional_query_as( + DATABASE_TYPE, + input, + checked, + ) { Ok(ts) => ts, Err(Error::SynError(err)) => { return err.to_compile_error().into(); diff --git a/src/lib.rs b/src/lib.rs index 3868e66..a5c1cfa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,9 @@ /// ``` pub use sqlx_conditional_queries_macros::conditional_query_as; +/// # Emit conditional `query_as_unchecked!` invocations +pub use sqlx_conditional_queries_macros::conditional_query_as_unchecked; + /// Do not use this module. It is only meant to be used by the generated by /// [`conditional_query_as!`] macro. #[doc(hidden)] diff --git a/testtest/Cargo.toml b/testtest/Cargo.toml new file mode 100644 index 0000000..015e08e --- /dev/null +++ b/testtest/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "testtest" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/testtest/src/main.rs b/testtest/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/testtest/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}