mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-29 21:00:54 +00:00
feat: make macros aware of macros.preferred-crates
This commit is contained in:
parent
8604b51ae3
commit
13f6ef0ab0
@ -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()`].
|
||||
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@ -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))]
|
||||
|
||||
@ -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),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@ -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(),}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)))
|
||||
}
|
||||
|
||||
@ -104,6 +104,7 @@ impl VirtualStatement {
|
||||
ordinal: i,
|
||||
name: name.clone(),
|
||||
type_info,
|
||||
origin: statement.column_origin(i),
|
||||
});
|
||||
|
||||
column_names.insert(name, i);
|
||||
|
||||
@ -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: { },
|
||||
},
|
||||
);
|
||||
|
||||
24
src/lib.rs
24
src/lib.rs
@ -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() {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user