diff --git a/sqlx-core/src/type_checking.rs b/sqlx-core/src/type_checking.rs index d9690d14..4d0ef55f 100644 --- a/sqlx-core/src/type_checking.rs +++ b/sqlx-core/src/type_checking.rs @@ -60,6 +60,10 @@ pub enum Error { DateTimeCrateFeatureNotEnabled, #[error("Cargo feature for configured `macros.preferred-crates.numeric` not enabled")] NumericCrateFeatureNotEnabled, + #[error("multiple date-time types are possible; falling back to `{fallback}`")] + AmbiguousDateTimeType { fallback: &'static str }, + #[error("multiple numeric types are possible; falling back to `{fallback}`")] + AmbiguousNumericType { fallback: &'static str }, } /// An adapter for [`Value`] which attempts to decode the value and format it when printed using [`Debug`]. @@ -195,12 +199,24 @@ macro_rules! impl_type_checking { if matches!(preferred_crates.date_time, DateTimeCrate::Time | DateTimeCrate::Inferred) { $( if <$time_ty as sqlx_core::types::Type<$database>>::type_info() == *info { + if cfg!(feature = "chrono") { + return Err($crate::type_checking::Error::AmbiguousDateTimeType { + fallback: $crate::select_input_type!($time_ty $(, $time_input)?), + }); + } + return Ok($crate::select_input_type!($time_ty $(, $time_input)?)); } )* $( if <$time_ty as sqlx_core::types::Type<$database>>::compatible(info) { + if cfg!(feature = "chrono") { + return Err($crate::type_checking::Error::AmbiguousDateTimeType { + fallback: $crate::select_input_type!($time_ty $(, $time_input)?), + }); + } + return Ok($crate::select_input_type!($time_ty $(, $time_input)?)); } )* @@ -240,12 +256,24 @@ macro_rules! impl_type_checking { if matches!(preferred_crates.numeric, NumericCrate::BigDecimal | NumericCrate::Inferred) { $( if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::type_info() == *info { + if cfg!(feature = "rust_decimal") { + return Err($crate::type_checking::Error::AmbiguousNumericType { + fallback: $crate::select_input_type!($bigdecimal_ty $(, $bigdecimal_input)?), + }); + } + return Ok($crate::select_input_type!($bigdecimal_ty $(, $bigdecimal_input)?)); } )* $( if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::compatible(info) { + if cfg!(feature = "rust_decimal") { + return Err($crate::type_checking::Error::AmbiguousNumericType { + fallback: $crate::select_input_type!($bigdecimal_ty $(, $bigdecimal_input)?), + }); + } + return Ok($crate::select_input_type!($bigdecimal_ty $(, $bigdecimal_input)?)); } )* @@ -311,12 +339,24 @@ macro_rules! impl_type_checking { if matches!(preferred_crates.date_time, DateTimeCrate::Time | DateTimeCrate::Inferred) { $( if <$time_ty as sqlx_core::types::Type<$database>>::type_info() == *info { + if cfg!(feature = "chrono") { + return Err($crate::type_checking::Error::AmbiguousDateTimeType { + fallback: stringify!($time_ty), + }); + } + return Ok(stringify!($time_ty)); } )* $( if <$time_ty as sqlx_core::types::Type<$database>>::compatible(info) { + if cfg!(feature = "chrono") { + return Err($crate::type_checking::Error::AmbiguousDateTimeType { + fallback: stringify!($time_ty), + }); + } + return Ok(stringify!($time_ty)); } )* @@ -356,12 +396,24 @@ macro_rules! impl_type_checking { if matches!(preferred_crates.numeric, NumericCrate::BigDecimal | NumericCrate::Inferred) { $( if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::type_info() == *info { + if cfg!(feature = "rust_decimal") { + return Err($crate::type_checking::Error::AmbiguousDateTimeType { + fallback: stringify!($bigdecimal_ty), + }); + } + return Ok(stringify!($bigdecimal_ty)); } )* $( if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::compatible(info) { + if cfg!(feature = "rust_decimal") { + return Err($crate::type_checking::Error::AmbiguousDateTimeType { + fallback: stringify!($bigdecimal_ty), + }); + } + return Ok(stringify!($bigdecimal_ty)); } )* @@ -438,6 +490,24 @@ macro_rules! impl_type_checking { )* } + #[cfg(feature = "bigdecimal")] + { + $( + if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::compatible(&info) { + return $crate::type_checking::FmtValue::debug::<$bigdecimal_ty>(value); + } + )* + } + + #[cfg(feature = "rust_decimal")] + { + $( + if <$rust_decimal_ty as sqlx_core::types::Type<$database>>::compatible(&info) { + return $crate::type_checking::FmtValue::debug::<$rust_decimal_ty>(value); + } + )* + } + $( $(#[$meta])? if <$ty as sqlx_core::types::Type<$database>>::compatible(&info) { diff --git a/sqlx-macros-core/src/query/args.rs b/sqlx-macros-core/src/query/args.rs index 1ddc5e98..6195ee6b 100644 --- a/sqlx-macros-core/src/query/args.rs +++ b/sqlx-macros-core/src/query/args.rs @@ -1,11 +1,12 @@ use crate::database::DatabaseExt; -use crate::query::QueryMacroInput; +use crate::query::{QueryMacroInput, Warnings}; use either::Either; use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned}; use sqlx_core::config::Config; use sqlx_core::describe::Describe; use sqlx_core::type_checking; +use sqlx_core::type_checking::Error; use sqlx_core::type_info::TypeInfo; use syn::spanned::Spanned; use syn::{Expr, ExprCast, ExprGroup, Type}; @@ -15,6 +16,7 @@ use syn::{Expr, ExprCast, ExprGroup, Type}; pub fn quote_args( input: &QueryMacroInput, config: &Config, + warnings: &mut Warnings, info: &Describe, ) -> crate::Result { let db_path = DB::db_path(); @@ -59,7 +61,7 @@ pub fn quote_args( return Ok(quote!()); } - let param_ty = get_param_type::(param_ty, config, i)?; + let param_ty = get_param_type::(param_ty, config, warnings, i)?; Ok(quote_spanned!(expr.span() => // this shouldn't actually run @@ -107,6 +109,7 @@ pub fn quote_args( fn get_param_type( param_ty: &DB::TypeInfo, config: &Config, + warnings: &mut Warnings, i: usize, ) -> crate::Result { if let Some(type_override) = config.macros.type_override(param_ty.name()) { @@ -156,6 +159,15 @@ fn get_param_type( (configured by `macros.preferred-crates.numeric` in sqlx.toml)", ) } + + Error::AmbiguousDateTimeType { fallback } => { + warnings.ambiguous_datetime = true; + return Ok(fallback.parse()?); + } + Error::AmbiguousNumericType { fallback } => { + warnings.ambiguous_numeric = true; + return Ok(fallback.parse()?); + } }; Err(message.into()) diff --git a/sqlx-macros-core/src/query/mod.rs b/sqlx-macros-core/src/query/mod.rs index 37592d4f..e87449d3 100644 --- a/sqlx-macros-core/src/query/mod.rs +++ b/sqlx-macros-core/src/query/mod.rs @@ -241,6 +241,12 @@ impl DescribeExt for Describe where { } +#[derive(Default)] +struct Warnings { + ambiguous_datetime: bool, + ambiguous_numeric: bool, +} + fn expand_with_data( input: QueryMacroInput, data: QueryData, @@ -267,7 +273,9 @@ where } } - let args_tokens = args::quote_args(&input, config, &data.describe)?; + let mut warnings = Warnings::default(); + + let args_tokens = args::quote_args(&input, config, &mut warnings, &data.describe)?; let query_args = format_ident!("query_args"); @@ -286,7 +294,7 @@ where } else { match input.record_type { RecordType::Generated => { - let columns = output::columns_to_rust::(&data.describe, config)?; + let columns = output::columns_to_rust::(&data.describe, config, &mut warnings)?; let record_name: Type = syn::parse_str("Record").unwrap(); @@ -322,28 +330,32 @@ where record_tokens } RecordType::Given(ref out_ty) => { - let columns = output::columns_to_rust::(&data.describe, config)?; + let columns = output::columns_to_rust::(&data.describe, config, &mut warnings)?; output::quote_query_as::(&input, out_ty, &query_args, &columns) } - RecordType::Scalar => { - output::quote_query_scalar::(&input, config, &query_args, &data.describe)? - } + RecordType::Scalar => output::quote_query_scalar::( + &input, + config, + &mut warnings, + &query_args, + &data.describe, + )?, } }; - let mut warnings = TokenStream::new(); + let mut warnings_out = TokenStream::new(); - if config.macros.preferred_crates.date_time.is_inferred() { + if warnings.ambiguous_datetime { // Warns if the date-time crate is inferred but both `chrono` and `time` are enabled - warnings.extend(quote! { + warnings_out.extend(quote! { ::sqlx::warn_on_ambiguous_inferred_date_time_crate(); }); } - if config.macros.preferred_crates.numeric.is_inferred() { + if warnings.ambiguous_numeric { // Warns if the numeric crate is inferred but both `bigdecimal` and `rust_decimal` are enabled - warnings.extend(quote! { + warnings_out.extend(quote! { ::sqlx::warn_on_ambiguous_inferred_numeric_crate(); }); } @@ -354,7 +366,7 @@ where { use ::sqlx::Arguments as _; - #warnings + #warnings_out #args_tokens diff --git a/sqlx-macros-core/src/query/output.rs b/sqlx-macros-core/src/query/output.rs index 1a145e3a..987dcaa3 100644 --- a/sqlx-macros-core/src/query/output.rs +++ b/sqlx-macros-core/src/query/output.rs @@ -7,7 +7,7 @@ use sqlx_core::describe::Describe; use crate::database::DatabaseExt; -use crate::query::QueryMacroInput; +use crate::query::{QueryMacroInput, Warnings}; use sqlx_core::config::Config; use sqlx_core::type_checking; use sqlx_core::type_checking::TypeChecking; @@ -82,15 +82,17 @@ impl Display for DisplayColumn<'_> { pub fn columns_to_rust( describe: &Describe, config: &Config, + warnings: &mut Warnings, ) -> crate::Result> { (0..describe.columns().len()) - .map(|i| column_to_rust(describe, config, i)) + .map(|i| column_to_rust(describe, config, warnings, i)) .collect::>>() } fn column_to_rust( describe: &Describe, config: &Config, + warnings: &mut Warnings, i: usize, ) -> crate::Result { let column = &describe.columns()[i]; @@ -116,7 +118,7 @@ fn column_to_rust( (ColumnTypeOverride::Wildcard, true) => ColumnType::OptWildcard, (ColumnTypeOverride::None, _) => { - let type_ = get_column_type::(config, i, column); + let type_ = get_column_type::(config, warnings, i, column); if !nullable { ColumnType::Exact(type_) } else { @@ -204,6 +206,7 @@ pub fn quote_query_as( pub fn quote_query_scalar( input: &QueryMacroInput, config: &Config, + warnings: &mut Warnings, bind_args: &Ident, describe: &Describe, ) -> crate::Result { @@ -218,10 +221,10 @@ pub fn quote_query_scalar( } // attempt to parse a column override, otherwise fall back to the inferred type of the column - let ty = if let Ok(rust_col) = column_to_rust(describe, config, 0) { + let ty = if let Ok(rust_col) = column_to_rust(describe, config, warnings, 0) { rust_col.type_.to_token_stream() } else if input.checked { - let ty = get_column_type::(config, 0, &columns[0]); + let ty = get_column_type::(config, warnings, 0, &columns[0]); if describe.nullable(0).unwrap_or(true) { quote! { ::std::option::Option<#ty> } } else { @@ -239,7 +242,12 @@ pub fn quote_query_scalar( }) } -fn get_column_type(config: &Config, i: usize, column: &DB::Column) -> TokenStream { +fn get_column_type( + config: &Config, + warnings: &mut Warnings, + i: usize, + column: &DB::Column, +) -> TokenStream { if let ColumnOrigin::Table(origin) = column.origin() { if let Some(column_override) = config.macros.column_override(&origin.table, &origin.name) { return column_override.parse().unwrap(); @@ -322,6 +330,14 @@ fn get_column_type(config: &Config, i: usize, column: &DB::Colu } ) } + type_checking::Error::AmbiguousDateTimeType { fallback } => { + warnings.ambiguous_datetime = true; + return fallback.parse().unwrap(); + } + type_checking::Error::AmbiguousNumericType { fallback } => { + warnings.ambiguous_numeric = true; + return fallback.parse().unwrap(); + } }; syn::Error::new(Span::call_site(), message).to_compile_error()