fix: use Value-based binding in UpdateForm for proper Option<T> handling
When UpdateForm wraps fields that are already Option<T>, it creates nested Options (Option<Option<T>>). The old bind_form_values method bound these directly as &Option<T>, which caused MySQL "malform packet" errors for Uuid -> BINARY(16) conversions. Now both bind_form_values and bind_all_values use update_stmt_with_values() which properly converts values through the Value enum: - Some(None) -> Value::Null - Some(Some(v)) -> Value::T(v) This preserves the three-state semantics: - None: don't include field in UPDATE - Some(None): SET column = NULL - Some(Some(v)): SET column = value Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a1464d3f7c
commit
6ed2401be1
|
|
@ -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.6"
|
version = "0.3.7"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
|
|
@ -1106,40 +1106,31 @@ fn generate_update_impl(
|
||||||
|
|
||||||
/// Bind all form values to query in correct order.
|
/// Bind all form values to query in correct order.
|
||||||
/// Handles both simple values and expression values, respecting expression precedence.
|
/// Handles both simple values and expression values, respecting expression precedence.
|
||||||
|
/// Uses Value enum for proper type handling of Option<T> fields.
|
||||||
pub fn bind_all_values<'q>(&'q self, mut query: sqlx::query::Query<'q, #db, #db_args>)
|
pub fn bind_all_values<'q>(&'q self, mut query: sqlx::query::Query<'q, #db, #db_args>)
|
||||||
-> sqlx::query::Query<'q, #db, #db_args>
|
-> sqlx::query::Query<'q, #db, #db_args>
|
||||||
{
|
{
|
||||||
#(
|
// Use update_stmt_with_values to get properly converted values
|
||||||
// Expression takes precedence over simple value
|
// This handles nested Options (Option<Option<T>>) correctly
|
||||||
if let Some(expr) = self._exprs.get(#db_names) {
|
let (_, values) = self.update_stmt_with_values();
|
||||||
let (_, expr_values) = expr.build_sql(#db_names, 1);
|
for value in values {
|
||||||
for value in expr_values {
|
|
||||||
query = ::sqlx_record::prelude::bind_value_owned(query, value);
|
query = ::sqlx_record::prelude::bind_value_owned(query, value);
|
||||||
}
|
}
|
||||||
} else if let Some(ref value) = self.#field_idents {
|
|
||||||
query = query.bind(value);
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
query
|
query
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Legacy binding method - only binds simple Option values (ignores expressions).
|
/// Legacy binding method - binds values through the Value enum for proper type handling.
|
||||||
/// For backward compatibility. New code should use bind_all_values().
|
/// For backward compatibility. New code should use bind_all_values().
|
||||||
pub fn bind_form_values<'q>(&'q self, mut query: sqlx::query::Query<'q, #db, #db_args>)
|
pub fn bind_form_values<'q>(&'q self, mut query: sqlx::query::Query<'q, #db, #db_args>)
|
||||||
-> sqlx::query::Query<'q, #db, #db_args>
|
-> sqlx::query::Query<'q, #db, #db_args>
|
||||||
{
|
{
|
||||||
if self._exprs.is_empty() {
|
// Always use Value-based binding to properly handle Option<T> fields
|
||||||
// No expressions, use simple binding
|
// This ensures nested Options (Option<Option<T>>) are unwrapped correctly
|
||||||
#(
|
let (_, values) = self.update_stmt_with_values();
|
||||||
if let Some(ref value) = self.#field_idents {
|
for value in values {
|
||||||
query = query.bind(value);
|
query = ::sqlx_record::prelude::bind_value_owned(query, value);
|
||||||
}
|
}
|
||||||
)*
|
|
||||||
query
|
query
|
||||||
} else {
|
|
||||||
// Has expressions, use full binding
|
|
||||||
self.bind_all_values(query)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if this form uses any expressions
|
/// Check if this form uses any expressions
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,7 @@ impl UpdateExpr {
|
||||||
pub type SqlValue = Value;
|
pub type SqlValue = Value;
|
||||||
|
|
||||||
// MySQL supports unsigned integers natively
|
// MySQL supports unsigned integers natively
|
||||||
|
// Note: UUID is bound as bytes for BINARY(16) column compatibility
|
||||||
#[cfg(feature = "mysql")]
|
#[cfg(feature = "mysql")]
|
||||||
macro_rules! bind_value {
|
macro_rules! bind_value {
|
||||||
($query:expr, $value: expr) => {{
|
($query:expr, $value: expr) => {{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue