feat: make macros aware of macros.preferred-crates

This commit is contained in:
Austin Bonander 2024-09-19 22:54:48 -07:00
parent 8604b51ae3
commit 13f6ef0ab0
24 changed files with 814 additions and 378 deletions

View File

@ -23,15 +23,17 @@ pub trait Column: 'static + Send + Sync + Debug {
fn type_info(&self) -> &<Self::Database as Database>::TypeInfo;
/// If this column comes from a table, return the table and original column name.
///
///
/// Returns [`ColumnOrigin::Expression`] if the column is the result of an expression
/// or else the source table could not be determined.
///
///
/// Returns [`ColumnOrigin::Unknown`] if the database driver does not have that information,
/// or has not overridden this method.
// This method returns an owned value instead of a reference,
// This method returns an owned value instead of a reference,
// to give the implementor more flexibility.
fn origin(&self) -> ColumnOrigin { ColumnOrigin::Unknown }
fn origin(&self) -> ColumnOrigin {
ColumnOrigin::Unknown
}
}
/// A [`Column`] that originates from a table.
@ -44,20 +46,20 @@ pub struct TableColumn {
pub name: Arc<str>,
}
/// The possible statuses for our knowledge of the origin of a [`Column`].
/// The possible statuses for our knowledge of the origin of a [`Column`].
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))]
pub enum ColumnOrigin {
/// The column is known to originate from a table.
///
/// Included is the table name and original column name.
/// The column is known to originate from a table.
///
/// Included is the table name and original column name.
Table(TableColumn),
/// The column originates from an expression, or else its origin could not be determined.
Expression,
/// The database driver does not know the column origin at this time.
///
///
/// This may happen if:
/// * The connection is in the middle of executing a query,
/// * The connection is in the middle of executing a query,
/// and cannot query the catalog to fetch this information.
/// * The connection does not have access to the database catalog.
/// * The implementation of [`Column`] did not override [`Column::origin()`].

View File

@ -44,6 +44,6 @@ pub struct Config {
impl Config {
pub fn database_url_var(&self) -> &str {
self.database_url_var.as_deref().unwrap_or("DATABASE_URL")
self.database_url_var.as_deref().unwrap_or("DATABASE_URL")
}
}
}

View File

@ -3,13 +3,13 @@ use std::collections::BTreeMap;
/// Configuration for the `query!()` family of macros.
#[derive(Debug, Default)]
#[cfg_attr(
feature = "sqlx-toml",
derive(serde::Deserialize),
feature = "sqlx-toml",
derive(serde::Deserialize),
serde(default, rename_all = "kebab-case")
)]
pub struct Config {
/// Specify which crates' types to use when types from multiple crates apply.
///
///
/// See [`PreferredCrates`] for details.
pub preferred_crates: PreferredCrates,
@ -18,6 +18,12 @@ pub struct Config {
/// Default type mappings are defined by the database driver.
/// Refer to the `sqlx::types` module for details.
///
/// ## Note: Case-Sensitive
/// Currently, the case of the type name MUST match the name SQLx knows it by.
/// Built-in types are spelled in all-uppercase to match SQL convention.
///
/// However, user-created types in Postgres are all-lowercase unless quoted.
///
/// ## Note: Orthogonal to Nullability
/// These overrides do not affect whether `query!()` decides to wrap a column in `Option<_>`
/// or not. They only override the inner type used.
@ -63,7 +69,7 @@ pub struct Config {
/// ```toml
/// [macros.type-overrides]
/// # Override a built-in type
/// 'uuid' = "crate::types::MyUuid"
/// 'UUID' = "crate::types::MyUuid"
///
/// # Support an external or custom wrapper type (e.g. from the `isn` Postgres extension)
/// # (NOTE: FOR DOCUMENTATION PURPOSES ONLY; THIS CRATE/TYPE DOES NOT EXIST AS OF WRITING)
@ -132,6 +138,8 @@ pub struct Config {
/// ```
///
/// (See `Note` section above for details.)
// TODO: allow specifying different types for input vs output
// e.g. to accept `&[T]` on input but output `Vec<T>`
pub type_overrides: BTreeMap<SqlType, RustType>,
/// Specify per-table and per-column overrides for mapping SQL types to Rust types.
@ -221,7 +229,7 @@ pub struct Config {
#[cfg_attr(
feature = "sqlx-toml",
derive(serde::Deserialize),
serde(rename_all = "kebab-case")
serde(default, rename_all = "kebab-case")
)]
pub struct PreferredCrates {
/// Specify the crate to use for mapping date/time types to Rust.
@ -360,6 +368,7 @@ pub type RustType = Box<str>;
impl Config {
/// Get the override for a given type name (optionally schema-qualified).
pub fn type_override(&self, type_name: &str) -> Option<&str> {
// TODO: make this case-insensitive
self.type_overrides.get(type_name).map(|s| &**s)
}
@ -378,6 +387,15 @@ impl DateTimeCrate {
pub fn is_inferred(&self) -> bool {
*self == Self::Inferred
}
#[inline(always)]
pub fn crate_name(&self) -> Option<&str> {
match self {
Self::Inferred => None,
Self::Chrono => Some("chrono"),
Self::Time => Some("time"),
}
}
}
impl NumericCrate {
@ -386,4 +404,13 @@ impl NumericCrate {
pub fn is_inferred(&self) -> bool {
*self == Self::Inferred
}
}
#[inline(always)]
pub fn crate_name(&self) -> Option<&str> {
match self {
Self::Inferred => None,
Self::BigDecimal => Some("bigdecimal"),
Self::RustDecimal => Some("rust_decimal"),
}
}
}

View File

@ -14,8 +14,8 @@ use std::collections::BTreeSet;
/// Be sure you know what you are doing and that you read all relevant documentation _thoroughly_.
#[derive(Debug, Default)]
#[cfg_attr(
feature = "sqlx-toml",
derive(serde::Deserialize),
feature = "sqlx-toml",
derive(serde::Deserialize),
serde(default, rename_all = "kebab-case")
)]
pub struct Config {

View File

@ -86,9 +86,7 @@ pub enum ConfigError {
/// No configuration file was found. Not necessarily fatal.
#[error("config file {path:?} not found")]
NotFound {
path: PathBuf,
},
NotFound { path: PathBuf },
/// An I/O error occurred while attempting to read the config file at `path`.
///
@ -103,7 +101,7 @@ pub enum ConfigError {
/// An error in the TOML was encountered while parsing the config file at `path`.
///
/// The error gives line numbers and context when printed with `Display`/`ToString`.
///
///
/// Only returned if the `sqlx-toml` feature is enabled.
#[error("error parsing config file {path:?}")]
Parse {
@ -115,14 +113,12 @@ pub enum ConfigError {
/// A `sqlx.toml` file was found or specified, but the `sqlx-toml` feature is not enabled.
#[error("SQLx found config file at {path:?} but the `sqlx-toml` feature was not enabled")]
ParseDisabled {
path: PathBuf
},
ParseDisabled { path: PathBuf },
}
impl ConfigError {
/// Create a [`ConfigError`] from a [`std::io::Error`].
///
///
/// Maps to either `NotFound` or `Io`.
pub fn from_io(path: PathBuf, error: io::Error) -> Self {
if error.kind() == io::ErrorKind::NotFound {
@ -131,7 +127,7 @@ impl ConfigError {
Self::Io { path, error }
}
}
/// If this error means the file was not found, return the path that was attempted.
pub fn not_found_path(&self) -> Option<&Path> {
if let Self::NotFound { path } = self {
@ -227,15 +223,18 @@ impl Config {
// Motivation: https://github.com/toml-rs/toml/issues/761
tracing::debug!("read config TOML from {path:?}:\n{toml_s}");
toml::from_str(&toml_s).map_err(|error| ConfigError::Parse { path, error: Box::new(error) })
toml::from_str(&toml_s).map_err(|error| ConfigError::Parse {
path,
error: Box::new(error),
})
}
#[cfg(not(feature = "sqlx-toml"))]
fn read_from(path: PathBuf) -> Result<Self, ConfigError> {
match path.try_exists() {
Ok(true) => Err(ConfigError::ParseDisabled { path }),
Ok(false) => Err(ConfigError::NotFound { path }),
Err(e) => Err(ConfigError::from_io(path, e))
Err(e) => Err(ConfigError::from_io(path, e)),
}
}
}

View File

@ -30,7 +30,14 @@ date-time = "chrono"
# in case new date/time crates are added in the future:
# date-time = "time"
# Force the macros to use the `rust_decimal` crate for `NUMERIC`, even if `bigdecimal` is enabled.
#
# Defaults to "inferred": use whichever crate is enabled (`bigdecimal` takes precedence over `rust_decimal`).
numeric = "rust_decimal"
# Or, ensure the macros always prefer `bigdecimal`
# in case new decimal crates are added in the future:
# numeric = "bigdecimal"
# Set global overrides for mapping SQL types to Rust types.
#
@ -44,7 +51,9 @@ date-time = "chrono"
# or not. They only override the inner type used.
[macros.type-overrides]
# Override a built-in type (map all `UUID` columns to `crate::types::MyUuid`)
'uuid' = "crate::types::MyUuid"
# Note: currently, the case of the type name MUST match.
# Built-in types are spelled in all-uppercase to match SQL convention.
'UUID' = "crate::types::MyUuid"
# Support an external or custom wrapper type (e.g. from the `isn` Postgres extension)
# (NOTE: FOR DOCUMENTATION PURPOSES ONLY; THIS CRATE/TYPE DOES NOT EXIST AS OF WRITING)

View File

@ -20,9 +20,12 @@ fn assert_macros_config(config: &config::macros::Config) {
use config::macros::*;
assert_eq!(config.preferred_crates.date_time, DateTimeCrate::Chrono);
assert_eq!(config.preferred_crates.numeric, NumericCrate::RustDecimal);
// Type overrides
// Don't need to cover everything, just some important canaries.
assert_eq!(config.type_override("UUID"), Some("crate::types::MyUuid"));
assert_eq!(config.type_override("foo"), Some("crate::types::Foo"));
assert_eq!(config.type_override(r#""Bar""#), Some("crate::types::Bar"),);
@ -79,6 +82,12 @@ fn assert_migrate_config(config: &config::migrate::Config) {
assert_eq!(config.ignored_chars, ignored_chars);
assert_eq!(config.defaults.migration_type, DefaultMigrationType::Reversible);
assert_eq!(config.defaults.migration_versioning, DefaultVersioning::Sequential);
assert_eq!(
config.defaults.migration_type,
DefaultMigrationType::Reversible
);
assert_eq!(
config.defaults.migration_versioning,
DefaultVersioning::Sequential
);
}

View File

@ -1,3 +1,4 @@
use crate::config::macros::PreferredCrates;
use crate::database::Database;
use crate::decode::Decode;
use crate::type_info::TypeInfo;
@ -26,12 +27,18 @@ pub trait TypeChecking: Database {
///
/// If the type has a borrowed equivalent suitable for query parameters,
/// this is that borrowed type.
fn param_type_for_id(id: &Self::TypeInfo) -> Option<&'static str>;
fn param_type_for_id(
id: &Self::TypeInfo,
preferred_crates: &PreferredCrates,
) -> Result<&'static str, Error>;
/// Get the full path of the Rust type that corresponds to the given `TypeInfo`, if applicable.
///
/// Always returns the owned version of the type, suitable for decoding from `Row`.
fn return_type_for_id(id: &Self::TypeInfo) -> Option<&'static str>;
fn return_type_for_id(
id: &Self::TypeInfo,
preferred_crates: &PreferredCrates,
) -> Result<&'static str, Error>;
/// Get the name of the Cargo feature gate that must be enabled to process the given `TypeInfo`,
/// if applicable.
@ -43,6 +50,18 @@ pub trait TypeChecking: Database {
fn fmt_value_debug(value: &<Self as Database>::Value) -> FmtValue<'_, Self>;
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("no built-in mapping found for SQL type; a type override may be required")]
NoMappingFound,
#[error("Cargo feature for configured `macros.preferred-crates.date-time` not enabled")]
DateTimeCrateFeatureNotEnabled,
#[error("Cargo feature for configured `macros.preferred-crates.numeric` not enabled")]
NumericCrateFeatureNotEnabled,
}
/// An adapter for [`Value`] which attempts to decode the value and format it when printed using [`Debug`].
pub struct FmtValue<'v, DB>
where
@ -134,36 +153,256 @@ macro_rules! impl_type_checking {
},
ParamChecking::$param_checking:ident,
feature-types: $ty_info:ident => $get_gate:expr,
datetime-types: {
chrono: {
$($chrono_ty:ty $(| $chrono_input:ty)?),*$(,)?
},
time: {
$($time_ty:ty $(| $time_input:ty)?),*$(,)?
},
},
numeric-types: {
bigdecimal: {
$($bigdecimal_ty:ty $(| $bigdecimal_input:ty)?),*$(,)?
},
rust_decimal: {
$($rust_decimal_ty:ty $(| $rust_decimal_input:ty)?),*$(,)?
},
},
) => {
impl $crate::type_checking::TypeChecking for $database {
const PARAM_CHECKING: $crate::type_checking::ParamChecking = $crate::type_checking::ParamChecking::$param_checking;
fn param_type_for_id(info: &Self::TypeInfo) -> Option<&'static str> {
match () {
fn param_type_for_id(
info: &Self::TypeInfo,
preferred_crates: &$crate::config::macros::PreferredCrates,
) -> Result<&'static str, $crate::type_checking::Error> {
use $crate::config::macros::{DateTimeCrate, NumericCrate};
use $crate::type_checking::Error;
// Check `macros.preferred-crates.date-time`
//
// Due to legacy reasons, `time` takes precedent over `chrono` if both are enabled.
// Any crates added later should be _lower_ priority than `chrono` to avoid breakages.
// ----------------------------------------
#[cfg(feature = "time")]
if matches!(preferred_crates.date_time, DateTimeCrate::Time | DateTimeCrate::Inferred) {
$(
$(#[$meta])?
_ if <$ty as sqlx_core::types::Type<$database>>::type_info() == *info => Some($crate::select_input_type!($ty $(, $input)?)),
if <$time_ty as sqlx_core::types::Type<$database>>::type_info() == *info {
return Ok($crate::select_input_type!($time_ty $(, $time_input)?));
}
)*
$(
$(#[$meta])?
_ if <$ty as sqlx_core::types::Type<$database>>::compatible(info) => Some($crate::select_input_type!($ty $(, $input)?)),
if <$time_ty as sqlx_core::types::Type<$database>>::compatible(info) {
return Ok($crate::select_input_type!($time_ty $(, $time_input)?));
}
)*
_ => None
}
#[cfg(not(feature = "time"))]
if preferred_crates.date_time == DateTimeCrate::Time {
return Err(Error::DateTimeCrateFeatureNotEnabled);
}
#[cfg(feature = "chrono")]
if matches!(preferred_crates.date_time, DateTimeCrate::Chrono | DateTimeCrate::Inferred) {
$(
if <$chrono_ty as sqlx_core::types::Type<$database>>::type_info() == *info {
return Ok($crate::select_input_type!($chrono_ty $(, $chrono_input)?));
}
)*
$(
if <$chrono_ty as sqlx_core::types::Type<$database>>::compatible(info) {
return Ok($crate::select_input_type!($chrono_ty $(, $chrono_input)?));
}
)*
}
#[cfg(not(feature = "chrono"))]
if preferred_crates.date_time == DateTimeCrate::Chrono {
return Err(Error::DateTimeCrateFeatureNotEnabled);
}
// Check `macros.preferred-crates.numeric`
//
// Due to legacy reasons, `bigdecimal` takes precedent over `rust_decimal` if
// both are enabled.
// ----------------------------------------
#[cfg(feature = "bigdecimal")]
if matches!(preferred_crates.numeric, NumericCrate::BigDecimal | NumericCrate::Inferred) {
$(
if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::type_info() == *info {
return Ok($crate::select_input_type!($bigdecimal_ty $(, $bigdecimal_input)?));
}
)*
$(
if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::compatible(info) {
return Ok($crate::select_input_type!($bigdecimal_ty $(, $bigdecimal_input)?));
}
)*
}
#[cfg(not(feature = "bigdecimal"))]
if preferred_crates.numeric == NumericCrate::BigDecimal {
return Err(Error::NumericCrateFeatureNotEnabled);
}
#[cfg(feature = "rust_decimal")]
if matches!(preferred_crates.numeric, NumericCrate::RustDecimal | NumericCrate::Inferred) {
$(
if <$rust_decimal_ty as sqlx_core::types::Type<$database>>::type_info() == *info {
return Ok($crate::select_input_type!($rust_decimal_ty $(, $rust_decimal_input)?));
}
)*
$(
if <$rust_decimal_ty as sqlx_core::types::Type<$database>>::compatible(info) {
return Ok($crate::select_input_type!($rust_decimal_ty $(, $rust_decimal_input)?));
}
)*
}
#[cfg(not(feature = "rust_decimal"))]
if preferred_crates.numeric == NumericCrate::RustDecimal {
return Err(Error::NumericCrateFeatureNotEnabled);
}
// Check all other types
// ---------------------
$(
$(#[$meta])?
if <$ty as sqlx_core::types::Type<$database>>::type_info() == *info {
return Ok($crate::select_input_type!($ty $(, $input)?));
}
)*
$(
$(#[$meta])?
if <$ty as sqlx_core::types::Type<$database>>::compatible(info) {
return Ok($crate::select_input_type!($ty $(, $input)?));
}
)*
Err(Error::NoMappingFound)
}
fn return_type_for_id(info: &Self::TypeInfo) -> Option<&'static str> {
match () {
fn return_type_for_id(
info: &Self::TypeInfo,
preferred_crates: &$crate::config::macros::PreferredCrates,
) -> Result<&'static str, $crate::type_checking::Error> {
use $crate::config::macros::{DateTimeCrate, NumericCrate};
use $crate::type_checking::Error;
// Check `macros.preferred-crates.date-time`
//
// Due to legacy reasons, `time` takes precedent over `chrono` if both are enabled.
// Any crates added later should be _lower_ priority than `chrono` to avoid breakages.
// ----------------------------------------
#[cfg(feature = "time")]
if matches!(preferred_crates.date_time, DateTimeCrate::Time | DateTimeCrate::Inferred) {
$(
$(#[$meta])?
_ if <$ty as sqlx_core::types::Type<$database>>::type_info() == *info => Some(stringify!($ty)),
if <$time_ty as sqlx_core::types::Type<$database>>::type_info() == *info {
return Ok(stringify!($time_ty));
}
)*
$(
$(#[$meta])?
_ if <$ty as sqlx_core::types::Type<$database>>::compatible(info) => Some(stringify!($ty)),
if <$time_ty as sqlx_core::types::Type<$database>>::compatible(info) {
return Ok(stringify!($time_ty));
}
)*
_ => None
}
#[cfg(not(feature = "time"))]
if preferred_crates.date_time == DateTimeCrate::Time {
return Err(Error::DateTimeCrateFeatureNotEnabled);
}
#[cfg(feature = "chrono")]
if matches!(preferred_crates.date_time, DateTimeCrate::Chrono | DateTimeCrate::Inferred) {
$(
if <$chrono_ty as sqlx_core::types::Type<$database>>::type_info() == *info {
return Ok(stringify!($chrono_ty));
}
)*
$(
if <$chrono_ty as sqlx_core::types::Type<$database>>::compatible(info) {
return Ok(stringify!($chrono_ty));
}
)*
}
#[cfg(not(feature = "chrono"))]
if preferred_crates.date_time == DateTimeCrate::Chrono {
return Err(Error::DateTimeCrateFeatureNotEnabled);
}
// Check `macros.preferred-crates.numeric`
//
// Due to legacy reasons, `bigdecimal` takes precedent over `rust_decimal` if
// both are enabled.
// ----------------------------------------
#[cfg(feature = "bigdecimal")]
if matches!(preferred_crates.numeric, NumericCrate::BigDecimal | NumericCrate::Inferred) {
$(
if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::type_info() == *info {
return Ok(stringify!($bigdecimal_ty));
}
)*
$(
if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::compatible(info) {
return Ok(stringify!($bigdecimal_ty));
}
)*
}
#[cfg(not(feature = "bigdecimal"))]
if preferred_crates.numeric == NumericCrate::BigDecimal {
return Err(Error::NumericCrateFeatureNotEnabled);
}
#[cfg(feature = "rust_decimal")]
if matches!(preferred_crates.numeric, NumericCrate::RustDecimal | NumericCrate::Inferred) {
$(
if <$rust_decimal_ty as sqlx_core::types::Type<$database>>::type_info() == *info {
return Ok($crate::select_input_type!($rust_decimal_ty $(, $rust_decimal_input)?));
}
)*
$(
if <$rust_decimal_ty as sqlx_core::types::Type<$database>>::compatible(info) {
return Ok($crate::select_input_type!($rust_decimal_ty $(, $rust_decimal_input)?));
}
)*
}
#[cfg(not(feature = "rust_decimal"))]
if preferred_crates.numeric == NumericCrate::RustDecimal {
return Err(Error::NumericCrateFeatureNotEnabled);
}
// Check all other types
// ---------------------
$(
$(#[$meta])?
if <$ty as sqlx_core::types::Type<$database>>::type_info() == *info {
return Ok(stringify!($ty));
}
)*
$(
$(#[$meta])?
if <$ty as sqlx_core::types::Type<$database>>::compatible(info) {
return Ok(stringify!($ty));
}
)*
Err(Error::NoMappingFound)
}
fn get_feature_gate($ty_info: &Self::TypeInfo) -> Option<&'static str> {
@ -175,13 +414,32 @@ macro_rules! impl_type_checking {
let info = value.type_info();
match () {
#[cfg(feature = "time")]
{
$(
$(#[$meta])?
_ if <$ty as sqlx_core::types::Type<$database>>::compatible(&info) => $crate::type_checking::FmtValue::debug::<$ty>(value),
if <$time_ty as sqlx_core::types::Type<$database>>::compatible(&info) {
return $crate::type_checking::FmtValue::debug::<$time_ty>(value);
}
)*
_ => $crate::type_checking::FmtValue::unknown(value),
}
#[cfg(feature = "chrono")]
{
$(
if <$chrono_ty as sqlx_core::types::Type<$database>>::compatible(&info) {
return $crate::type_checking::FmtValue::debug::<$chrono_ty>(value);
}
)*
}
$(
$(#[$meta])?
if <$ty as sqlx_core::types::Type<$database>>::compatible(&info) {
return $crate::type_checking::FmtValue::debug::<$ty>(value);
}
)*
$crate::type_checking::FmtValue::unknown(value)
}
}
};

View File

@ -3,7 +3,10 @@ use crate::query::QueryMacroInput;
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_info::TypeInfo;
use syn::spanned::Spanned;
use syn::{Expr, ExprCast, ExprGroup, Type};
@ -11,6 +14,7 @@ use syn::{Expr, ExprCast, ExprGroup, Type};
/// and binds them to `DB::Arguments` with the ident `query_args`.
pub fn quote_args<DB: DatabaseExt>(
input: &QueryMacroInput,
config: &Config,
info: &Describe<DB>,
) -> crate::Result<TokenStream> {
let db_path = DB::db_path();
@ -55,22 +59,7 @@ pub fn quote_args<DB: DatabaseExt>(
return Ok(quote!());
}
let param_ty =
DB::param_type_for_id(param_ty)
.ok_or_else(|| {
if let Some(feature_gate) = DB::get_feature_gate(param_ty) {
format!(
"optional sqlx feature `{}` required for type {} of param #{}",
feature_gate,
param_ty,
i + 1,
)
} else {
format!("unsupported type {} for param #{}", param_ty, i + 1)
}
})?
.parse::<TokenStream>()
.map_err(|_| format!("Rust type mapping for {param_ty} not parsable"))?;
let param_ty = get_param_type::<DB>(param_ty, config, i)?;
Ok(quote_spanned!(expr.span() =>
// this shouldn't actually run
@ -115,6 +104,63 @@ pub fn quote_args<DB: DatabaseExt>(
})
}
fn get_param_type<DB: DatabaseExt>(
param_ty: &DB::TypeInfo,
config: &Config,
i: usize,
) -> crate::Result<TokenStream> {
if let Some(type_override) = config.macros.type_override(param_ty.name()) {
return Ok(type_override.parse()?);
}
let err = match DB::param_type_for_id(param_ty, &config.macros.preferred_crates) {
Ok(t) => return Ok(t.parse()?),
Err(e) => e,
};
let param_num = i + 1;
let message = match err {
type_checking::Error::NoMappingFound => {
if let Some(feature_gate) = DB::get_feature_gate(param_ty) {
format!(
"optional sqlx feature `{feature_gate}` required for type {param_ty} of param #{param_num}",
)
} else {
format!("unsupported type {param_ty} for param #{param_num}")
}
}
type_checking::Error::DateTimeCrateFeatureNotEnabled => {
let feature_gate = config
.macros
.preferred_crates
.date_time
.crate_name()
.expect("BUG: got feature-not-enabled error for DateTimeCrate::Inferred");
format!(
"SQLx feature `{feature_gate}` required for type {param_ty} of param #{param_num} \
(configured by `macros.preferred-crates.date-time` in sqlx.toml)",
)
}
type_checking::Error::NumericCrateFeatureNotEnabled => {
let feature_gate = config
.macros
.preferred_crates
.numeric
.crate_name()
.expect("BUG: got feature-not-enabled error for NumericCrate::Inferred");
format!(
"SQLx feature `{feature_gate}` required for type {param_ty} of param #{param_num} \
(configured by `macros.preferred-crates.numeric` in sqlx.toml)",
)
}
};
Err(message.into())
}
fn get_type_override(expr: &Expr) -> Option<&Type> {
match expr {
Expr::Group(group) => get_type_override(&group.expr),

View File

@ -15,8 +15,8 @@ use crate::database::DatabaseExt;
use crate::query::data::{hash_string, DynQueryData, QueryData};
use crate::query::input::RecordType;
use either::Either;
use url::Url;
use sqlx_core::config::Config;
use url::Url;
mod args;
mod data;
@ -139,11 +139,9 @@ static METADATA: Lazy<Metadata> = Lazy::new(|| {
let offline = env("SQLX_OFFLINE")
.map(|s| s.eq_ignore_ascii_case("true") || s == "1")
.unwrap_or(false);
let var_name = Config::from_crate()
.common
.database_url_var();
let var_name = Config::from_crate().common.database_url_var();
let database_url = env(var_name).ok();
Metadata {
@ -251,6 +249,8 @@ fn expand_with_data<DB: DatabaseExt>(
where
Describe<DB>: DescribeExt,
{
let config = Config::from_crate();
// validate at the minimum that our args match the query's input parameters
let num_parameters = match data.describe.parameters() {
Some(Either::Left(params)) => Some(params.len()),
@ -267,7 +267,7 @@ where
}
}
let args_tokens = args::quote_args(&input, &data.describe)?;
let args_tokens = args::quote_args(&input, config, &data.describe)?;
let query_args = format_ident!("query_args");
@ -286,7 +286,7 @@ where
} else {
match input.record_type {
RecordType::Generated => {
let columns = output::columns_to_rust::<DB>(&data.describe)?;
let columns = output::columns_to_rust::<DB>(&data.describe, config)?;
let record_name: Type = syn::parse_str("Record").unwrap();
@ -322,22 +322,40 @@ where
record_tokens
}
RecordType::Given(ref out_ty) => {
let columns = output::columns_to_rust::<DB>(&data.describe)?;
let columns = output::columns_to_rust::<DB>(&data.describe, config)?;
output::quote_query_as::<DB>(&input, out_ty, &query_args, &columns)
}
RecordType::Scalar => {
output::quote_query_scalar::<DB>(&input, &query_args, &data.describe)?
output::quote_query_scalar::<DB>(&input, config, &query_args, &data.describe)?
}
}
};
let mut warnings = TokenStream::new();
if config.macros.preferred_crates.date_time.is_inferred() {
// Warns if the date-time crate is inferred but both `chrono` and `time` are enabled
warnings.extend(quote! {
::sqlx::warn_on_ambiguous_inferred_date_time_crate();
});
}
if config.macros.preferred_crates.numeric.is_inferred() {
// Warns if the numeric crate is inferred but both `bigdecimal` and `rust_decimal` are enabled
warnings.extend(quote! {
::sqlx::warn_on_ambiguous_inferred_numeric_crate();
});
}
let ret_tokens = quote! {
{
#[allow(clippy::all)]
{
use ::sqlx::Arguments as _;
#warnings
#args_tokens
#output

View File

@ -8,12 +8,13 @@ use sqlx_core::describe::Describe;
use crate::database::DatabaseExt;
use crate::query::QueryMacroInput;
use sqlx_core::config::Config;
use sqlx_core::type_checking;
use sqlx_core::type_checking::TypeChecking;
use sqlx_core::type_info::TypeInfo;
use std::fmt::{self, Display, Formatter};
use syn::parse::{Parse, ParseStream};
use syn::Token;
use sqlx_core::config::Config;
use sqlx_core::type_info::TypeInfo;
pub struct RustColumn {
pub(super) ident: Ident,
@ -78,13 +79,20 @@ impl Display for DisplayColumn<'_> {
}
}
pub fn columns_to_rust<DB: DatabaseExt>(describe: &Describe<DB>) -> crate::Result<Vec<RustColumn>> {
pub fn columns_to_rust<DB: DatabaseExt>(
describe: &Describe<DB>,
config: &Config,
) -> crate::Result<Vec<RustColumn>> {
(0..describe.columns().len())
.map(|i| column_to_rust(describe, i))
.map(|i| column_to_rust(describe, config, i))
.collect::<crate::Result<Vec<_>>>()
}
fn column_to_rust<DB: DatabaseExt>(describe: &Describe<DB>, i: usize) -> crate::Result<RustColumn> {
fn column_to_rust<DB: DatabaseExt>(
describe: &Describe<DB>,
config: &Config,
i: usize,
) -> crate::Result<RustColumn> {
let column = &describe.columns()[i];
// add raw prefix to all identifiers
@ -108,7 +116,7 @@ fn column_to_rust<DB: DatabaseExt>(describe: &Describe<DB>, i: usize) -> crate::
(ColumnTypeOverride::Wildcard, true) => ColumnType::OptWildcard,
(ColumnTypeOverride::None, _) => {
let type_ = get_column_type::<DB>(i, column);
let type_ = get_column_type::<DB>(config, i, column);
if !nullable {
ColumnType::Exact(type_)
} else {
@ -195,6 +203,7 @@ pub fn quote_query_as<DB: DatabaseExt>(
pub fn quote_query_scalar<DB: DatabaseExt>(
input: &QueryMacroInput,
config: &Config,
bind_args: &Ident,
describe: &Describe<DB>,
) -> crate::Result<TokenStream> {
@ -209,10 +218,10 @@ pub fn quote_query_scalar<DB: DatabaseExt>(
}
// 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, 0) {
let ty = if let Ok(rust_col) = column_to_rust(describe, config, 0) {
rust_col.type_.to_token_stream()
} else if input.checked {
let ty = get_column_type::<DB>(0, &columns[0]);
let ty = get_column_type::<DB>(config, 0, &columns[0]);
if describe.nullable(0).unwrap_or(true) {
quote! { ::std::option::Option<#ty> }
} else {
@ -230,52 +239,92 @@ pub fn quote_query_scalar<DB: DatabaseExt>(
})
}
fn get_column_type<DB: DatabaseExt>(i: usize, column: &DB::Column) -> TokenStream {
fn get_column_type<DB: DatabaseExt>(config: &Config, i: usize, column: &DB::Column) -> TokenStream {
if let ColumnOrigin::Table(origin) = column.origin() {
if let Some(column_override) = Config::from_crate()
.macros
.column_override(&origin.table, &origin.name)
{
if let Some(column_override) = config.macros.column_override(&origin.table, &origin.name) {
return column_override.parse().unwrap();
}
}
let type_info = column.type_info();
if let Some(type_override) = Config::from_crate()
.macros
.type_override(type_info.name())
{
return type_override.parse().unwrap();
if let Some(type_override) = config.macros.type_override(type_info.name()) {
return type_override.parse().unwrap();
}
<DB as TypeChecking>::return_type_for_id(type_info).map_or_else(
|| {
let message =
if let Some(feature_gate) = <DB as TypeChecking>::get_feature_gate(type_info) {
format!(
"optional sqlx feature `{feat}` required for type {ty} of {col}",
ty = &type_info,
feat = feature_gate,
col = DisplayColumn {
idx: i,
name: column.name()
}
)
} else {
format!(
"unsupported type {ty} of {col}",
ty = type_info,
col = DisplayColumn {
idx: i,
name: column.name()
}
)
};
syn::Error::new(Span::call_site(), message).to_compile_error()
},
|t| t.parse().unwrap(),
)
let err = match <DB as TypeChecking>::return_type_for_id(
type_info,
&config.macros.preferred_crates,
) {
Ok(t) => return t.parse().unwrap(),
Err(e) => e,
};
let message = match err {
type_checking::Error::NoMappingFound => {
if let Some(feature_gate) = <DB as TypeChecking>::get_feature_gate(type_info) {
format!(
"SQLx feature `{feat}` required for type {ty} of {col}",
ty = &type_info,
feat = feature_gate,
col = DisplayColumn {
idx: i,
name: column.name()
}
)
} else {
format!(
"no built-in mapping found for type {ty} of {col}; \
a type override may be required, see documentation for details",
ty = type_info,
col = DisplayColumn {
idx: i,
name: column.name()
}
)
}
}
type_checking::Error::DateTimeCrateFeatureNotEnabled => {
let feature_gate = config
.macros
.preferred_crates
.date_time
.crate_name()
.expect("BUG: got feature-not-enabled error for DateTimeCrate::Inferred");
format!(
"SQLx feature `{feat}` required for type {ty} of {col} \
(configured by `macros.preferred-crates.date-time` in sqlx.toml)",
ty = &type_info,
feat = feature_gate,
col = DisplayColumn {
idx: i,
name: column.name()
}
)
}
type_checking::Error::NumericCrateFeatureNotEnabled => {
let feature_gate = config
.macros
.preferred_crates
.numeric
.crate_name()
.expect("BUG: got feature-not-enabled error for NumericCrate::Inferred");
format!(
"SQLx feature `{feat}` required for type {ty} of {col} \
(configured by `macros.preferred-crates.numeric` in sqlx.toml)",
ty = &type_info,
feat = feature_gate,
col = DisplayColumn {
idx: i,
name: column.name()
}
)
}
};
syn::Error::new(Span::call_site(), message).to_compile_error()
}
impl ColumnDecl {

View File

@ -22,8 +22,8 @@ use futures_core::future::BoxFuture;
use futures_core::stream::BoxStream;
use futures_core::Stream;
use futures_util::{pin_mut, TryStreamExt};
use std::{borrow::Cow, sync::Arc};
use sqlx_core::column::{ColumnOrigin, TableColumn};
use std::{borrow::Cow, sync::Arc};
impl MySqlConnection {
async fn prepare_statement<'c>(
@ -391,7 +391,7 @@ fn recv_next_result_column(def: &ColumnDefinition, ordinal: usize) -> Result<MyS
};
let table = def.table()?;
let origin = if table.is_empty() {
ColumnOrigin::Expression
} else {
@ -406,7 +406,7 @@ fn recv_next_result_column(def: &ColumnDefinition, ordinal: usize) -> Result<MyS
name: column_name.into(),
})
};
let type_info = MySqlTypeInfo::from_column(def);
Ok(MySqlColumn {

View File

@ -130,7 +130,7 @@ impl ColumnDefinition {
pub(crate) fn table(&self) -> Result<&str, Error> {
str::from_utf8(&self.table).map_err(Error::protocol)
}
pub(crate) fn name(&self) -> Result<&str, Error> {
str::from_utf8(&self.name).map_err(Error::protocol)
}

View File

@ -25,41 +25,39 @@ impl_type_checking!(
// BINARY, VAR_BINARY, BLOB
Vec<u8>,
// Types from third-party crates need to be referenced at a known path
// for the macros to work, but we don't want to require the user to add extra dependencies.
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::NaiveTime,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::NaiveDate,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::NaiveDateTime,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>,
#[cfg(feature = "time")]
sqlx::types::time::Time,
#[cfg(feature = "time")]
sqlx::types::time::Date,
#[cfg(feature = "time")]
sqlx::types::time::PrimitiveDateTime,
#[cfg(feature = "time")]
sqlx::types::time::OffsetDateTime,
#[cfg(feature = "bigdecimal")]
sqlx::types::BigDecimal,
#[cfg(feature = "rust_decimal")]
sqlx::types::Decimal,
#[cfg(feature = "json")]
sqlx::types::JsonValue,
},
ParamChecking::Weak,
feature-types: info => info.__type_feature_gate(),
// The expansion of the macro automatically applies the correct feature name
// and checks `[macros.preferred-crates]`
datetime-types: {
chrono: {
sqlx::types::chrono::NaiveTime,
sqlx::types::chrono::NaiveDate,
sqlx::types::chrono::NaiveDateTime,
sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>,
},
time: {
sqlx::types::time::Time,
sqlx::types::time::Date,
sqlx::types::time::PrimitiveDateTime,
sqlx::types::time::OffsetDateTime,
},
},
numeric-types: {
bigdecimal: {
sqlx::types::BigDecimal,
},
rust_decimal: {
sqlx::types::Decimal,
},
},
);

View File

@ -1,8 +1,8 @@
use crate::ext::ustr::UStr;
use crate::{PgTypeInfo, Postgres};
pub(crate) use sqlx_core::column::{Column, ColumnIndex};
use sqlx_core::column::ColumnOrigin;
pub(crate) use sqlx_core::column::{Column, ColumnIndex};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))]
@ -13,7 +13,7 @@ pub struct PgColumn {
#[cfg_attr(feature = "offline", serde(default))]
pub(crate) origin: ColumnOrigin,
#[cfg_attr(feature = "offline", serde(skip))]
pub(crate) relation_id: Option<crate::types::Oid>,
#[cfg_attr(feature = "offline", serde(skip))]

View File

@ -1,4 +1,4 @@
use std::collections::btree_map;
use crate::connection::TableColumns;
use crate::error::Error;
use crate::ext::ustr::UStr;
use crate::io::StatementId;
@ -12,11 +12,9 @@ use crate::types::Oid;
use crate::HashMap;
use crate::{PgColumn, PgConnection, PgTypeInfo};
use smallvec::SmallVec;
use sqlx_core::column::{ColumnOrigin, TableColumn};
use sqlx_core::query_builder::QueryBuilder;
use std::sync::Arc;
use sqlx_core::column::{ColumnOrigin, TableColumn};
use sqlx_core::hash_map;
use crate::connection::TableColumns;
/// Describes the type of the `pg_type.typtype` column
///
@ -125,9 +123,12 @@ impl PgConnection {
let type_info = self
.maybe_fetch_type_info_by_oid(field.data_type_id, should_fetch)
.await?;
let origin = if let (Some(relation_oid), Some(attribute_no)) = (field.relation_id, field.relation_attribute_no) {
self.maybe_fetch_column_origin(relation_oid, attribute_no, should_fetch).await?
let origin = if let (Some(relation_oid), Some(attribute_no)) =
(field.relation_id, field.relation_attribute_no)
{
self.maybe_fetch_column_origin(relation_oid, attribute_no, should_fetch)
.await?
} else {
ColumnOrigin::Expression
};
@ -200,52 +201,65 @@ impl PgConnection {
Ok(PgTypeInfo(PgType::DeclareWithOid(oid)))
}
}
async fn maybe_fetch_column_origin(
&mut self,
relation_id: Oid,
&mut self,
relation_id: Oid,
attribute_no: i16,
should_fetch: bool,
) -> Result<ColumnOrigin, Error> {
let mut table_columns = match self.cache_table_to_column_names.entry(relation_id) {
hash_map::Entry::Occupied(table_columns) => {
table_columns.into_mut()
},
hash_map::Entry::Vacant(vacant) => {
if !should_fetch { return Ok(ColumnOrigin::Unknown); }
let table_name: String = query_scalar("SELECT $1::oid::regclass::text")
.bind(relation_id)
.fetch_one(&mut *self)
.await?;
vacant.insert(TableColumns {
table_name: table_name.into(),
columns: Default::default(),
if let Some(origin) =
self.cache_table_to_column_names
.get(&relation_id)
.and_then(|table_columns| {
let column_name = table_columns.columns.get(&attribute_no).cloned()?;
Some(ColumnOrigin::Table(TableColumn {
table: table_columns.table_name.clone(),
name: column_name,
}))
})
}
{
return Ok(origin);
}
if !should_fetch {
return Ok(ColumnOrigin::Unknown);
}
// Looking up the table name _may_ end up being redundant,
// but the round-trip to the server is by far the most expensive part anyway.
let Some((table_name, column_name)): Option<(String, String)> = query_as(
// language=PostgreSQL
"SELECT $1::oid::regclass::text, attname \
FROM pg_catalog.pg_attribute \
WHERE attrelid = $1 AND attnum = $2",
)
.bind(relation_id)
.bind(attribute_no)
.fetch_optional(&mut *self)
.await?
else {
// The column/table doesn't exist anymore for whatever reason.
return Ok(ColumnOrigin::Unknown);
};
let column_name = match table_columns.columns.entry(attribute_no) {
btree_map::Entry::Occupied(occupied) => Arc::clone(occupied.get()),
btree_map::Entry::Vacant(vacant) => {
if !should_fetch { return Ok(ColumnOrigin::Unknown); }
let column_name: String = query_scalar(
"SELECT attname FROM pg_attribute WHERE attrelid = $1 AND attnum = $2"
)
.bind(relation_id)
.bind(attribute_no)
.fetch_one(&mut *self)
.await?;
Arc::clone(vacant.insert(column_name.into()))
}
};
let table_columns = self
.cache_table_to_column_names
.entry(relation_id)
.or_insert_with(|| TableColumns {
table_name: table_name.into(),
columns: Default::default(),
});
let column_name = table_columns
.columns
.entry(attribute_no)
.or_insert(column_name.into());
Ok(ColumnOrigin::Table(TableColumn {
table: table_columns.table_name.clone(),
name: column_name
name: Arc::clone(column_name),
}))
}

View File

@ -148,8 +148,8 @@ impl PgConnection {
cache_type_oid: HashMap::new(),
cache_type_info: HashMap::new(),
cache_elem_type_to_array: HashMap::new(),
log_settings: options.log_settings.clone(),
}),
cache_table_to_column_names: HashMap::new(),
log_settings: options.log_settings.clone(),}),
})
}
}

View File

@ -39,42 +39,6 @@ impl_type_checking!(
#[cfg(feature = "uuid")]
sqlx::types::Uuid,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::NaiveTime,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::NaiveDate,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::NaiveDateTime,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc> | sqlx::types::chrono::DateTime<_>,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::postgres::types::PgTimeTz<sqlx::types::chrono::NaiveTime, sqlx::types::chrono::FixedOffset>,
#[cfg(feature = "time")]
sqlx::types::time::Time,
#[cfg(feature = "time")]
sqlx::types::time::Date,
#[cfg(feature = "time")]
sqlx::types::time::PrimitiveDateTime,
#[cfg(feature = "time")]
sqlx::types::time::OffsetDateTime,
#[cfg(feature = "time")]
sqlx::postgres::types::PgTimeTz<sqlx::types::time::Time, sqlx::types::time::UtcOffset>,
#[cfg(feature = "bigdecimal")]
sqlx::types::BigDecimal,
#[cfg(feature = "rust_decimal")]
sqlx::types::Decimal,
#[cfg(feature = "ipnetwork")]
sqlx::types::ipnetwork::IpNetwork,
@ -106,36 +70,6 @@ impl_type_checking!(
#[cfg(feature = "uuid")]
Vec<sqlx::types::Uuid> | &[sqlx::types::Uuid],
#[cfg(all(feature = "chrono", not(feature = "time")))]
Vec<sqlx::types::chrono::NaiveTime> | &[sqlx::types::chrono::NaiveTime],
#[cfg(all(feature = "chrono", not(feature = "time")))]
Vec<sqlx::types::chrono::NaiveDate> | &[sqlx::types::chrono::NaiveDate],
#[cfg(all(feature = "chrono", not(feature = "time")))]
Vec<sqlx::types::chrono::NaiveDateTime> | &[sqlx::types::chrono::NaiveDateTime],
#[cfg(all(feature = "chrono", not(feature = "time")))]
Vec<sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>> | &[sqlx::types::chrono::DateTime<_>],
#[cfg(feature = "time")]
Vec<sqlx::types::time::Time> | &[sqlx::types::time::Time],
#[cfg(feature = "time")]
Vec<sqlx::types::time::Date> | &[sqlx::types::time::Date],
#[cfg(feature = "time")]
Vec<sqlx::types::time::PrimitiveDateTime> | &[sqlx::types::time::PrimitiveDateTime],
#[cfg(feature = "time")]
Vec<sqlx::types::time::OffsetDateTime> | &[sqlx::types::time::OffsetDateTime],
#[cfg(feature = "bigdecimal")]
Vec<sqlx::types::BigDecimal> | &[sqlx::types::BigDecimal],
#[cfg(feature = "rust_decimal")]
Vec<sqlx::types::Decimal> | &[sqlx::types::Decimal],
#[cfg(feature = "ipnetwork")]
Vec<sqlx::types::ipnetwork::IpNetwork> | &[sqlx::types::ipnetwork::IpNetwork],
@ -152,72 +86,114 @@ impl_type_checking!(
sqlx::postgres::types::PgRange<i32>,
sqlx::postgres::types::PgRange<i64>,
#[cfg(feature = "bigdecimal")]
sqlx::postgres::types::PgRange<sqlx::types::BigDecimal>,
#[cfg(feature = "rust_decimal")]
sqlx::postgres::types::PgRange<sqlx::types::Decimal>,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::postgres::types::PgRange<sqlx::types::chrono::NaiveDate>,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::postgres::types::PgRange<sqlx::types::chrono::NaiveDateTime>,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::postgres::types::PgRange<sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>> |
sqlx::postgres::types::PgRange<sqlx::types::chrono::DateTime<_>>,
#[cfg(feature = "time")]
sqlx::postgres::types::PgRange<sqlx::types::time::Date>,
#[cfg(feature = "time")]
sqlx::postgres::types::PgRange<sqlx::types::time::PrimitiveDateTime>,
#[cfg(feature = "time")]
sqlx::postgres::types::PgRange<sqlx::types::time::OffsetDateTime>,
// Range arrays
Vec<sqlx::postgres::types::PgRange<i32>> | &[sqlx::postgres::types::PgRange<i32>],
Vec<sqlx::postgres::types::PgRange<i64>> | &[sqlx::postgres::types::PgRange<i64>],
#[cfg(feature = "bigdecimal")]
Vec<sqlx::postgres::types::PgRange<sqlx::types::BigDecimal>> |
&[sqlx::postgres::types::PgRange<sqlx::types::BigDecimal>],
#[cfg(feature = "rust_decimal")]
Vec<sqlx::postgres::types::PgRange<sqlx::types::Decimal>> |
&[sqlx::postgres::types::PgRange<sqlx::types::Decimal>],
#[cfg(all(feature = "chrono", not(feature = "time")))]
Vec<sqlx::postgres::types::PgRange<sqlx::types::chrono::NaiveDate>> |
&[sqlx::postgres::types::PgRange<sqlx::types::chrono::NaiveDate>],
#[cfg(all(feature = "chrono", not(feature = "time")))]
Vec<sqlx::postgres::types::PgRange<sqlx::types::chrono::NaiveDateTime>> |
&[sqlx::postgres::types::PgRange<sqlx::types::chrono::NaiveDateTime>],
#[cfg(all(feature = "chrono", not(feature = "time")))]
Vec<sqlx::postgres::types::PgRange<sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>>> |
&[sqlx::postgres::types::PgRange<sqlx::types::chrono::DateTime<_>>],
#[cfg(all(feature = "chrono", not(feature = "time")))]
Vec<sqlx::postgres::types::PgRange<sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>>> |
&[sqlx::postgres::types::PgRange<sqlx::types::chrono::DateTime<_>>],
#[cfg(feature = "time")]
Vec<sqlx::postgres::types::PgRange<sqlx::types::time::Date>> |
&[sqlx::postgres::types::PgRange<sqlx::types::time::Date>],
#[cfg(feature = "time")]
Vec<sqlx::postgres::types::PgRange<sqlx::types::time::PrimitiveDateTime>> |
&[sqlx::postgres::types::PgRange<sqlx::types::time::PrimitiveDateTime>],
#[cfg(feature = "time")]
Vec<sqlx::postgres::types::PgRange<sqlx::types::time::OffsetDateTime>> |
&[sqlx::postgres::types::PgRange<sqlx::types::time::OffsetDateTime>],
},
ParamChecking::Strong,
feature-types: info => info.__type_feature_gate(),
// The expansion of the macro automatically applies the correct feature name
// and checks `[macros.preferred-crates]`
datetime-types: {
chrono: {
// Scalar types
sqlx::types::chrono::NaiveTime,
sqlx::types::chrono::NaiveDate,
sqlx::types::chrono::NaiveDateTime,
sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc> | sqlx::types::chrono::DateTime<_>,
sqlx::postgres::types::PgTimeTz<sqlx::types::chrono::NaiveTime, sqlx::types::chrono::FixedOffset>,
// Array types
Vec<sqlx::types::chrono::NaiveTime> | &[sqlx::types::chrono::NaiveTime],
Vec<sqlx::types::chrono::NaiveDate> | &[sqlx::types::chrono::NaiveDate],
Vec<sqlx::types::chrono::NaiveDateTime> | &[sqlx::types::chrono::NaiveDateTime],
Vec<sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>> | &[sqlx::types::chrono::DateTime<_>],
// Range types
sqlx::postgres::types::PgRange<sqlx::types::chrono::NaiveDate>,
sqlx::postgres::types::PgRange<sqlx::types::chrono::NaiveDateTime>,
sqlx::postgres::types::PgRange<sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>> |
sqlx::postgres::types::PgRange<sqlx::types::chrono::DateTime<_>>,
// Arrays of ranges
Vec<sqlx::postgres::types::PgRange<sqlx::types::chrono::NaiveDate>> |
&[sqlx::postgres::types::PgRange<sqlx::types::chrono::NaiveDate>],
Vec<sqlx::postgres::types::PgRange<sqlx::types::chrono::NaiveDateTime>> |
&[sqlx::postgres::types::PgRange<sqlx::types::chrono::NaiveDateTime>],
Vec<sqlx::postgres::types::PgRange<sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>>> |
&[sqlx::postgres::types::PgRange<sqlx::types::chrono::DateTime<_>>],
},
time: {
// Scalar types
sqlx::types::time::Time,
sqlx::types::time::Date,
sqlx::types::time::PrimitiveDateTime,
sqlx::types::time::OffsetDateTime,
sqlx::postgres::types::PgTimeTz<sqlx::types::time::Time, sqlx::types::time::UtcOffset>,
// Array types
Vec<sqlx::types::time::Time> | &[sqlx::types::time::Time],
Vec<sqlx::types::time::Date> | &[sqlx::types::time::Date],
Vec<sqlx::types::time::PrimitiveDateTime> | &[sqlx::types::time::PrimitiveDateTime],
Vec<sqlx::types::time::OffsetDateTime> | &[sqlx::types::time::OffsetDateTime],
// Range types
sqlx::postgres::types::PgRange<sqlx::types::time::Date>,
sqlx::postgres::types::PgRange<sqlx::types::time::PrimitiveDateTime>,
sqlx::postgres::types::PgRange<sqlx::types::time::OffsetDateTime>,
// Arrays of ranges
Vec<sqlx::postgres::types::PgRange<sqlx::types::time::Date>> |
&[sqlx::postgres::types::PgRange<sqlx::types::time::Date>],
Vec<sqlx::postgres::types::PgRange<sqlx::types::time::PrimitiveDateTime>> |
&[sqlx::postgres::types::PgRange<sqlx::types::time::PrimitiveDateTime>],
Vec<sqlx::postgres::types::PgRange<sqlx::types::time::OffsetDateTime>> |
&[sqlx::postgres::types::PgRange<sqlx::types::time::OffsetDateTime>],
},
},
numeric-types: {
bigdecimal: {
sqlx::types::BigDecimal,
Vec<sqlx::types::BigDecimal> | &[sqlx::types::BigDecimal],
sqlx::postgres::types::PgRange<sqlx::types::BigDecimal>,
Vec<sqlx::postgres::types::PgRange<sqlx::types::BigDecimal>> |
&[sqlx::postgres::types::PgRange<sqlx::types::BigDecimal>],
},
rust_decimal: {
sqlx::types::Decimal,
Vec<sqlx::types::Decimal> | &[sqlx::types::Decimal],
sqlx::postgres::types::PgRange<sqlx::types::Decimal>,
Vec<sqlx::postgres::types::PgRange<sqlx::types::Decimal>> |
&[sqlx::postgres::types::PgRange<sqlx::types::Decimal>],
},
},
);

View File

@ -11,7 +11,7 @@ pub struct SqliteColumn {
pub(crate) type_info: SqliteTypeInfo,
#[cfg_attr(feature = "offline", serde(default))]
pub(crate) origin: ColumnOrigin
pub(crate) origin: ColumnOrigin,
}
impl Column for SqliteColumn {

View File

@ -49,7 +49,7 @@ pub(crate) fn describe(conn: &mut ConnectionState, query: &str) -> Result<Descri
for col in 0..num {
let name = stmt.handle.column_name(col).to_owned();
let origin = stmt.handle.column_origin(col);
let type_info = if let Some(ty) = stmt.handle.column_decltype(col) {

View File

@ -1,12 +1,9 @@
use std::ffi::c_void;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::ptr;
use std::ptr::NonNull;
use std::slice::from_raw_parts;
use std::str::{from_utf8, from_utf8_unchecked};
use std::sync::Arc;
use crate::error::{BoxDynError, Error};
use crate::type_info::DataType;
use crate::{SqliteError, SqliteTypeInfo};
use libsqlite3_sys::{
sqlite3, sqlite3_bind_blob64, sqlite3_bind_double, sqlite3_bind_int, sqlite3_bind_int64,
sqlite3_bind_null, sqlite3_bind_parameter_count, sqlite3_bind_parameter_name,
@ -20,9 +17,12 @@ use libsqlite3_sys::{
SQLITE_TRANSIENT, SQLITE_UTF8,
};
use sqlx_core::column::{ColumnOrigin, TableColumn};
use crate::error::{BoxDynError, Error};
use crate::type_info::DataType;
use crate::{SqliteError, SqliteTypeInfo};
use std::os::raw::{c_char, c_int};
use std::ptr;
use std::ptr::NonNull;
use std::slice::from_raw_parts;
use std::str::{from_utf8, from_utf8_unchecked};
use std::sync::Arc;
use super::unlock_notify;
@ -114,8 +114,9 @@ impl StatementHandle {
}
pub(crate) fn column_origin(&self, index: usize) -> ColumnOrigin {
if let Some((table, name)) =
self.column_table_name(index).zip(self.column_origin_name(index))
if let Some((table, name)) = self
.column_table_name(index)
.zip(self.column_origin_name(index))
{
let table: Arc<str> = self
.column_db_name(index)
@ -125,20 +126,20 @@ impl StatementHandle {
// TODO: check that SQLite returns the names properly quoted if necessary
|db| format!("{db}.{table}").into(),
);
ColumnOrigin::Table(TableColumn {
table,
name: name.into()
name: name.into(),
})
} else {
ColumnOrigin::Expression
}
}
fn column_db_name(&self, index: usize) -> Option<&str> {
unsafe {
let db_name = sqlite3_column_database_name(self.0.as_ptr(), check_col_idx!(index));
if !db_name.is_null() {
Some(from_utf8_unchecked(CStr::from_ptr(db_name).to_bytes()))
} else {
@ -170,7 +171,7 @@ impl StatementHandle {
}
}
}
pub(crate) fn column_type_info(&self, index: usize) -> SqliteTypeInfo {
SqliteTypeInfo(DataType::from_code(self.column_type(index)))
}

View File

@ -104,6 +104,7 @@ impl VirtualStatement {
ordinal: i,
name: name.clone(),
type_info,
origin: statement.column_origin(i),
});
column_names.insert(name, i);

View File

@ -1,8 +1,7 @@
use crate::Sqlite;
#[allow(unused_imports)]
use sqlx_core as sqlx;
use crate::Sqlite;
// f32 is not included below as REAL represents a floating point value
// stored as an 8-byte IEEE floating point number (i.e. an f64)
// For more info see: https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
@ -20,24 +19,6 @@ impl_type_checking!(
String,
Vec<u8>,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::NaiveDate,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::NaiveDateTime,
#[cfg(all(feature = "chrono", not(feature = "time")))]
sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc> | sqlx::types::chrono::DateTime<_>,
#[cfg(feature = "time")]
sqlx::types::time::OffsetDateTime,
#[cfg(feature = "time")]
sqlx::types::time::PrimitiveDateTime,
#[cfg(feature = "time")]
sqlx::types::time::Date,
#[cfg(feature = "uuid")]
sqlx::types::Uuid,
},
@ -48,4 +29,28 @@ impl_type_checking!(
// The type integrations simply allow the user to skip some intermediate representation,
// which is usually TEXT.
feature-types: _info => None,
// The expansion of the macro automatically applies the correct feature name
// and checks `[macros.preferred-crates]`
datetime-types: {
chrono: {
sqlx::types::chrono::NaiveDate,
sqlx::types::chrono::NaiveDateTime,
sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>
| sqlx::types::chrono::DateTime<_>,
},
time: {
sqlx::types::time::OffsetDateTime,
sqlx::types::time::PrimitiveDateTime,
sqlx::types::time::Date,
},
},
numeric-types: {
bigdecimal: { },
rust_decimal: { },
},
);

View File

@ -168,3 +168,27 @@ pub mod prelude {
#[cfg(feature = "_unstable-doc")]
pub use sqlx_core::config;
#[doc(hidden)]
#[cfg_attr(
all(feature = "chrono", feature = "time"),
deprecated = "SQLx has both `chrono` and `time` features enabled, \
which presents an ambiguity when the `query!()` macros are mapping date/time types. \
The `query!()` macros prefer types from `time` by default, \
but this behavior should not be relied upon; \
to resolve the ambiguity, we recommend specifying the preferred crate in a `sqlx.toml` file: \
https://docs.rs/sqlx/latest/sqlx/config/macros/PreferredCrates.html#field.date_time"
)]
pub fn warn_on_ambiguous_inferred_date_time_crate() {}
#[doc(hidden)]
#[cfg_attr(
all(feature = "bigdecimal", feature = "rust_decimal"),
deprecated = "SQLx has both `bigdecimal` and `rust_decimal` features enabled, \
which presents an ambiguity when the `query!()` macros are mapping `NUMERIC`. \
The `query!()` macros prefer `bigdecimal::BigDecimal` by default, \
but this behavior should not be relied upon; \
to resolve the ambiguity, we recommend specifying the preferred crate in a `sqlx.toml` file: \
https://docs.rs/sqlx/latest/sqlx/config/macros/PreferredCrates.html#field.numeric"
)]
pub fn warn_on_ambiguous_inferred_numeric_crate() {}