Fix MySQL placeholder issue and add missing Value types
- Remove broken static-validation feature (hardcoded $1 placeholders) - Add Value::Null variant for Option<T> support - Add From<Option<T>> impl for all Value types - Add f32, f64, NaiveTime, serde_json::Value support - Add optional decimal feature for rust_decimal::Decimal - All database backends now use runtime placeholder() function Fixes issues: - MySQL getting PostgreSQL $1 placeholders - Missing From<Option<T>> implementations - Missing base types (Decimal, JsonValue, NaiveTime, floats) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ceeecf2e5c
commit
3c0ae1983f
|
|
@ -5,7 +5,7 @@ edition.workspace = true
|
||||||
description = "Entity CRUD and change tracking for SQL databases with SQLx"
|
description = "Entity CRUD and change tracking for SQL databases with SQLx"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
@ -16,6 +16,7 @@ uuid = { version = "1", features = ["v4"] }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
paste = "1.0"
|
paste = "1.0"
|
||||||
|
rust_decimal = { version = "1", optional = true }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
|
@ -27,7 +28,7 @@ members = [
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
derive = ["dep:sqlx-record-derive"]
|
derive = ["dep:sqlx-record-derive"]
|
||||||
static-validation = ["sqlx-record-derive?/static-validation"]
|
decimal = ["dep:rust_decimal", "sqlx/rust_decimal"]
|
||||||
|
|
||||||
# Database backends - user must enable at least one
|
# Database backends - user must enable at least one
|
||||||
mysql = ["sqlx/mysql", "sqlx-record-derive?/mysql"]
|
mysql = ["sqlx/mysql", "sqlx-record-derive?/mysql"]
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ futures = "0.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
static-validation = []
|
|
||||||
mysql = []
|
mysql = []
|
||||||
postgres = []
|
postgres = []
|
||||||
sqlite = []
|
sqlite = []
|
||||||
|
|
|
||||||
|
|
@ -563,50 +563,9 @@ fn generate_get_impl(
|
||||||
quote! {}
|
quote! {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if static-validation feature is enabled at macro expansion time
|
|
||||||
let use_static_validation = cfg!(feature = "static-validation");
|
|
||||||
|
|
||||||
let get_by_impl = if use_static_validation {
|
|
||||||
let select_stmt = format!(
|
|
||||||
r#"SELECT DISTINCT {} FROM {}{}{} WHERE {} = $1"#,
|
|
||||||
select_fields.clone().collect::<Vec<_>>().join(", "),
|
|
||||||
tq, table_name, tq, pk_db_field_name
|
|
||||||
);
|
|
||||||
quote! {
|
|
||||||
pub async fn #get_by_func<'a, E>(executor: E, #pk_field: &#pk_type) -> Result<Option<Self>, sqlx::Error>
|
|
||||||
where
|
|
||||||
E: sqlx::Executor<'a, Database=#db>,
|
|
||||||
{
|
|
||||||
let result = sqlx::query_as!(
|
|
||||||
Self,
|
|
||||||
#select_stmt,
|
|
||||||
#pk_field
|
|
||||||
)
|
|
||||||
.fetch_optional(executor)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_by_primary_key<'a, E>(executor: E, #pk_field: &#pk_type) -> Result<Option<Self>, sqlx::Error>
|
|
||||||
where
|
|
||||||
E: sqlx::Executor<'a, Database=#db>,
|
|
||||||
{
|
|
||||||
let result = sqlx::query_as!(
|
|
||||||
Self,
|
|
||||||
#select_stmt,
|
|
||||||
#pk_field
|
|
||||||
)
|
|
||||||
.fetch_optional(executor)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let field_list = fields.iter().map(|f| f.db_name.clone()).collect::<Vec<_>>();
|
let field_list = fields.iter().map(|f| f.db_name.clone()).collect::<Vec<_>>();
|
||||||
|
|
||||||
quote! {
|
let get_by_impl = quote! {
|
||||||
pub async fn #get_by_func<'a, E>(executor: E, #pk_field: &#pk_type) -> Result<Option<Self>, sqlx::Error>
|
pub async fn #get_by_func<'a, E>(executor: E, #pk_field: &#pk_type) -> Result<Option<Self>, sqlx::Error>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database=#db>,
|
E: sqlx::Executor<'a, Database=#db>,
|
||||||
|
|
@ -642,7 +601,6 @@ fn generate_get_impl(
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
|
|
|
||||||
103
src/value.rs
103
src/value.rs
|
|
@ -1,5 +1,5 @@
|
||||||
use sqlx::query::{Query, QueryAs, QueryScalar};
|
use sqlx::query::{Query, QueryAs, QueryScalar};
|
||||||
use sqlx::types::chrono::{NaiveDate, NaiveDateTime};
|
use sqlx::types::chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||||
use crate::filter::placeholder;
|
use crate::filter::placeholder;
|
||||||
|
|
||||||
// Database type alias based on enabled feature
|
// Database type alias based on enabled feature
|
||||||
|
|
@ -34,6 +34,7 @@ pub type Arguments_<'q> = sqlx::postgres::PgArguments;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
|
Null,
|
||||||
Int8(i8),
|
Int8(i8),
|
||||||
Uint8(u8),
|
Uint8(u8),
|
||||||
Int16(i16),
|
Int16(i16),
|
||||||
|
|
@ -42,12 +43,18 @@ pub enum Value {
|
||||||
Uint32(u32),
|
Uint32(u32),
|
||||||
Int64(i64),
|
Int64(i64),
|
||||||
Uint64(u64),
|
Uint64(u64),
|
||||||
|
Float32(f32),
|
||||||
|
Float64(f64),
|
||||||
VecU8(Vec<u8>),
|
VecU8(Vec<u8>),
|
||||||
String(String),
|
String(String),
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
Uuid(uuid::Uuid),
|
Uuid(uuid::Uuid),
|
||||||
NaiveDate(NaiveDate),
|
NaiveDate(NaiveDate),
|
||||||
NaiveDateTime(NaiveDateTime),
|
NaiveDateTime(NaiveDateTime),
|
||||||
|
NaiveTime(NaiveTime),
|
||||||
|
Json(serde_json::Value),
|
||||||
|
#[cfg(feature = "decimal")]
|
||||||
|
Decimal(rust_decimal::Decimal),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expression for column updates beyond simple value assignment.
|
/// Expression for column updates beyond simple value assignment.
|
||||||
|
|
@ -253,6 +260,7 @@ pub type SqlValue = Value;
|
||||||
macro_rules! bind_value {
|
macro_rules! bind_value {
|
||||||
($query:expr, $value: expr) => {{
|
($query:expr, $value: expr) => {{
|
||||||
let query = match $value {
|
let query = match $value {
|
||||||
|
Value::Null => $query.bind(None::<String>),
|
||||||
Value::Int8(v) => $query.bind(v),
|
Value::Int8(v) => $query.bind(v),
|
||||||
Value::Uint8(v) => $query.bind(v),
|
Value::Uint8(v) => $query.bind(v),
|
||||||
Value::Int16(v) => $query.bind(v),
|
Value::Int16(v) => $query.bind(v),
|
||||||
|
|
@ -261,12 +269,18 @@ macro_rules! bind_value {
|
||||||
Value::Uint32(v) => $query.bind(v),
|
Value::Uint32(v) => $query.bind(v),
|
||||||
Value::Int64(v) => $query.bind(v),
|
Value::Int64(v) => $query.bind(v),
|
||||||
Value::Uint64(v) => $query.bind(v),
|
Value::Uint64(v) => $query.bind(v),
|
||||||
|
Value::Float32(v) => $query.bind(v),
|
||||||
|
Value::Float64(v) => $query.bind(v),
|
||||||
Value::VecU8(v) => $query.bind(v),
|
Value::VecU8(v) => $query.bind(v),
|
||||||
Value::String(v) => $query.bind(v),
|
Value::String(v) => $query.bind(v),
|
||||||
Value::Bool(v) => $query.bind(v),
|
Value::Bool(v) => $query.bind(v),
|
||||||
Value::Uuid(v) => $query.bind(v),
|
Value::Uuid(v) => $query.bind(v),
|
||||||
Value::NaiveDate(v) => $query.bind(v),
|
Value::NaiveDate(v) => $query.bind(v),
|
||||||
Value::NaiveDateTime(v) => $query.bind(v),
|
Value::NaiveDateTime(v) => $query.bind(v),
|
||||||
|
Value::NaiveTime(v) => $query.bind(v),
|
||||||
|
Value::Json(v) => $query.bind(v),
|
||||||
|
#[cfg(feature = "decimal")]
|
||||||
|
Value::Decimal(v) => $query.bind(v),
|
||||||
};
|
};
|
||||||
query
|
query
|
||||||
}};
|
}};
|
||||||
|
|
@ -277,6 +291,7 @@ macro_rules! bind_value {
|
||||||
macro_rules! bind_value {
|
macro_rules! bind_value {
|
||||||
($query:expr, $value: expr) => {{
|
($query:expr, $value: expr) => {{
|
||||||
let query = match $value {
|
let query = match $value {
|
||||||
|
Value::Null => $query.bind(None::<String>),
|
||||||
Value::Int8(v) => $query.bind(v),
|
Value::Int8(v) => $query.bind(v),
|
||||||
Value::Uint8(v) => $query.bind(*v as i16),
|
Value::Uint8(v) => $query.bind(*v as i16),
|
||||||
Value::Int16(v) => $query.bind(v),
|
Value::Int16(v) => $query.bind(v),
|
||||||
|
|
@ -285,12 +300,18 @@ macro_rules! bind_value {
|
||||||
Value::Uint32(v) => $query.bind(*v as i64),
|
Value::Uint32(v) => $query.bind(*v as i64),
|
||||||
Value::Int64(v) => $query.bind(v),
|
Value::Int64(v) => $query.bind(v),
|
||||||
Value::Uint64(v) => $query.bind(*v as i64),
|
Value::Uint64(v) => $query.bind(*v as i64),
|
||||||
|
Value::Float32(v) => $query.bind(v),
|
||||||
|
Value::Float64(v) => $query.bind(v),
|
||||||
Value::VecU8(v) => $query.bind(v),
|
Value::VecU8(v) => $query.bind(v),
|
||||||
Value::String(v) => $query.bind(v),
|
Value::String(v) => $query.bind(v),
|
||||||
Value::Bool(v) => $query.bind(v),
|
Value::Bool(v) => $query.bind(v),
|
||||||
Value::Uuid(v) => $query.bind(v),
|
Value::Uuid(v) => $query.bind(v),
|
||||||
Value::NaiveDate(v) => $query.bind(v),
|
Value::NaiveDate(v) => $query.bind(v),
|
||||||
Value::NaiveDateTime(v) => $query.bind(v),
|
Value::NaiveDateTime(v) => $query.bind(v),
|
||||||
|
Value::NaiveTime(v) => $query.bind(v),
|
||||||
|
Value::Json(v) => $query.bind(v),
|
||||||
|
#[cfg(feature = "decimal")]
|
||||||
|
Value::Decimal(v) => $query.bind(v),
|
||||||
};
|
};
|
||||||
query
|
query
|
||||||
}};
|
}};
|
||||||
|
|
@ -309,10 +330,13 @@ pub fn bind_values<'q>(query: Query<'q, DB, Arguments_<'q>>, values: &'q [Value]
|
||||||
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
|
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
|
||||||
pub fn bind_value_owned<'q>(query: Query<'q, DB, Arguments_<'q>>, value: Value) -> Query<'q, DB, Arguments_<'q>> {
|
pub fn bind_value_owned<'q>(query: Query<'q, DB, Arguments_<'q>>, value: Value) -> Query<'q, DB, Arguments_<'q>> {
|
||||||
match value {
|
match value {
|
||||||
|
Value::Null => query.bind(None::<String>),
|
||||||
Value::Int8(v) => query.bind(v),
|
Value::Int8(v) => query.bind(v),
|
||||||
Value::Int16(v) => query.bind(v),
|
Value::Int16(v) => query.bind(v),
|
||||||
Value::Int32(v) => query.bind(v),
|
Value::Int32(v) => query.bind(v),
|
||||||
Value::Int64(v) => query.bind(v),
|
Value::Int64(v) => query.bind(v),
|
||||||
|
Value::Float32(v) => query.bind(v),
|
||||||
|
Value::Float64(v) => query.bind(v),
|
||||||
#[cfg(feature = "mysql")]
|
#[cfg(feature = "mysql")]
|
||||||
Value::Uint8(v) => query.bind(v),
|
Value::Uint8(v) => query.bind(v),
|
||||||
#[cfg(feature = "mysql")]
|
#[cfg(feature = "mysql")]
|
||||||
|
|
@ -335,6 +359,10 @@ pub fn bind_value_owned<'q>(query: Query<'q, DB, Arguments_<'q>>, value: Value)
|
||||||
Value::Uuid(v) => query.bind(v),
|
Value::Uuid(v) => query.bind(v),
|
||||||
Value::NaiveDate(v) => query.bind(v),
|
Value::NaiveDate(v) => query.bind(v),
|
||||||
Value::NaiveDateTime(v) => query.bind(v),
|
Value::NaiveDateTime(v) => query.bind(v),
|
||||||
|
Value::NaiveTime(v) => query.bind(v),
|
||||||
|
Value::Json(v) => query.bind(v),
|
||||||
|
#[cfg(feature = "decimal")]
|
||||||
|
Value::Decimal(v) => query.bind(v),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -530,6 +558,79 @@ impl From<&NaiveDateTime> for Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New type implementations
|
||||||
|
impl From<f32> for Value {
|
||||||
|
fn from(value: f32) -> Self {
|
||||||
|
Value::Float32(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&f32> for Value {
|
||||||
|
fn from(value: &f32) -> Self {
|
||||||
|
Value::Float32(*value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f64> for Value {
|
||||||
|
fn from(value: f64) -> Self {
|
||||||
|
Value::Float64(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&f64> for Value {
|
||||||
|
fn from(value: &f64) -> Self {
|
||||||
|
Value::Float64(*value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NaiveTime> for Value {
|
||||||
|
fn from(value: NaiveTime) -> Self {
|
||||||
|
Value::NaiveTime(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&NaiveTime> for Value {
|
||||||
|
fn from(value: &NaiveTime) -> Self {
|
||||||
|
Value::NaiveTime(*value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Value> for Value {
|
||||||
|
fn from(value: serde_json::Value) -> Self {
|
||||||
|
Value::Json(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&serde_json::Value> for Value {
|
||||||
|
fn from(value: &serde_json::Value) -> Self {
|
||||||
|
Value::Json(value.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "decimal")]
|
||||||
|
impl From<rust_decimal::Decimal> for Value {
|
||||||
|
fn from(value: rust_decimal::Decimal) -> Self {
|
||||||
|
Value::Decimal(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "decimal")]
|
||||||
|
impl From<&rust_decimal::Decimal> for Value {
|
||||||
|
fn from(value: &rust_decimal::Decimal) -> Self {
|
||||||
|
Value::Decimal(*value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option<T> implementations - convert None to Value::Null
|
||||||
|
impl<T: Into<Value>> From<Option<T>> for Value {
|
||||||
|
fn from(value: Option<T>) -> Self {
|
||||||
|
match value {
|
||||||
|
Some(v) => v.into(),
|
||||||
|
None => Value::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait BindValues<'q> {
|
pub trait BindValues<'q> {
|
||||||
type Output;
|
type Output;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue