diff --git a/sqlx-core/src/arguments.rs b/sqlx-core/src/arguments.rs index fdb5dc14..5a486954 100644 --- a/sqlx-core/src/arguments.rs +++ b/sqlx-core/src/arguments.rs @@ -1,5 +1,7 @@ +use std::any; + use crate::database::HasOutput; -use crate::{encode, Database, TypeEncode}; +use crate::{encode, Database, TypeEncode, TypeInfo}; /// A collection of arguments to be applied to a prepared statement. /// @@ -17,11 +19,33 @@ pub struct Arguments<'a, Db: Database> { pub struct Argument<'a, Db: Database> { unchecked: bool, + // preserved from `T::type_id()` + type_id: Db::TypeId, + + // preserved from `T::compatible` + type_compatible: fn(&Db::TypeInfo) -> bool, + + // preserved from `any::type_name::` + // used in error messages + rust_type_name: &'static str, + // TODO: we might want to allow binding to Box> // this would allow an Owned storage of values value: &'a dyn TypeEncode, } +impl<'a, Db: Database> Argument<'a, Db> { + fn new>(value: &'a T, unchecked: bool) -> Self { + Self { + value, + unchecked, + type_id: T::type_id(), + type_compatible: T::compatible, + rust_type_name: any::type_name::(), + } + } +} + impl Default for Arguments<'_, Db> { fn default() -> Self { Self { named: Vec::new(), positional: Vec::new() } @@ -43,7 +67,7 @@ impl<'a, Db: Database> Arguments<'a, Db> { /// and you attempt to bind a `&str` in Rust, an incompatible type error will be raised. /// pub fn add>(&mut self, value: &'a T) { - self.positional.push(Argument { value, unchecked: false }); + self.positional.push(Argument::new(value, false)); } /// Add an unchecked value to the end of the arguments collection. @@ -53,17 +77,17 @@ impl<'a, Db: Database> Arguments<'a, Db> { /// will not be hinted when preparing the statement. /// pub fn add_unchecked>(&mut self, value: &'a T) { - self.positional.push(Argument { value, unchecked: true }); + self.positional.push(Argument::new(value, true)); } /// Add a named value to the argument collection. pub fn add_as>(&mut self, name: &'a str, value: &'a T) { - self.named.push((name, Argument { value, unchecked: false })); + self.named.push((name, Argument::new(value, false))); } /// Add an unchecked, named value to the arguments collection. pub fn add_unchecked_as>(&mut self, name: &'a str, value: &'a T) { - self.named.push((name, Argument { value, unchecked: true })); + self.named.push((name, Argument::new(value, true))); } } @@ -120,25 +144,14 @@ impl<'a, Db: Database> Arguments<'a, Db> { } impl<'a, Db: Database> Argument<'a, Db> { - /// Returns `true` if the argument is unchecked. - #[must_use] - pub fn unchecked(&self) -> bool { - self.unchecked - } - /// Returns the SQL type identifier of the argument. - /// - /// When the statement is prepared, the database will often infer the type - /// of the incoming argument. This method takes that (`ty`) along with the value of - /// the argument to determine the actual type identifier that will be sent when - /// the statement is executed. - /// #[must_use] - pub fn type_id(&self, ty: &Db::TypeInfo) -> Db::TypeId { - self.value.type_id(ty) + pub fn type_id(&self) -> Db::TypeId { + self.type_id } - /// Encode this argument into the output buffer, for use in executing the prepared statement. + /// Encode this argument into the output buffer, for use in executing + /// the prepared statement. /// /// When the statement is prepared, the database will often infer the type /// of the incoming argument. This method takes that (`ty`) along with the value of @@ -149,6 +162,13 @@ impl<'a, Db: Database> Argument<'a, Db> { ty: &Db::TypeInfo, out: &mut >::Output, ) -> encode::Result<()> { + if !self.unchecked && !(self.type_compatible)(ty) { + return Err(encode::Error::TypeNotCompatible { + rust_type_name: self.rust_type_name, + sql_type_name: ty.name(), + }); + } + self.value.encode(ty, out) } } diff --git a/sqlx-core/src/type.rs b/sqlx-core/src/type.rs index 3300f080..fe3ce004 100644 --- a/sqlx-core/src/type.rs +++ b/sqlx-core/src/type.rs @@ -1,8 +1,5 @@ -use crate::{Database, Decode, Encode, TypeInfo}; - -// NOTE: The interface here is not final. There are some special considerations -// for MSSQL and Postgres (Arrays and Ranges) that need careful handling -// to ensure we correctly cover them. +use crate::database::{HasOutput, HasRawValue}; +use crate::{decode, encode, Database, Decode, Encode, RawValue, TypeInfo}; /// Indicates that a SQL type is supported for a database. pub trait Type { @@ -42,36 +39,12 @@ impl> Type for &'_ T { } #[allow(clippy::module_name_repetitions)] -pub trait TypeEncode: Type + Encode { - /// Returns the canonical SQL type identifier for this Rust type. - #[allow(unused_variables)] - fn type_id(&self, ty: &Db::TypeInfo) -> Db::TypeId; +pub trait TypeEncode: Type + Encode {} - /// Determines if this Rust type is compatible with the specified SQL type. - /// - /// To be compatible, the Rust type must support encoding _and_ decoding - /// from the specified SQL type. - /// - fn compatible(&self, ty: &Db::TypeInfo) -> bool { - ty.id() == self.type_id(ty) - } - - /// Returns the Rust type name of this. - #[doc(hidden)] - #[inline] - fn __rust_type_name_of(&self) -> &'static str { - std::any::type_name::() - } -} - -impl + Encode> TypeEncode for T { - fn type_id(&self, _ty: &Db::TypeInfo) -> Db::TypeId { - Self::type_id() - } -} +impl + Encode, Db: Database> TypeEncode for T {} #[allow(clippy::module_name_repetitions)] -pub trait TypeDecode<'r, Db: Database>: Type + Decode<'r, Db> {} +pub trait TypeDecode<'r, Db: Database>: Sized + Type + Decode<'r, Db> {} impl<'r, T: Type + Decode<'r, Db>, Db: Database> TypeDecode<'r, Db> for T {}