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"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.3.6"
|
||||
version = "0.3.7"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
|
|
@ -1106,40 +1106,31 @@ fn generate_update_impl(
|
|||
|
||||
/// Bind all form values to query in correct order.
|
||||
/// 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>)
|
||||
-> sqlx::query::Query<'q, #db, #db_args>
|
||||
{
|
||||
#(
|
||||
// Expression takes precedence over simple value
|
||||
if let Some(expr) = self._exprs.get(#db_names) {
|
||||
let (_, expr_values) = expr.build_sql(#db_names, 1);
|
||||
for value in expr_values {
|
||||
// Use update_stmt_with_values to get properly converted values
|
||||
// This handles nested Options (Option<Option<T>>) correctly
|
||||
let (_, values) = self.update_stmt_with_values();
|
||||
for value in values {
|
||||
query = ::sqlx_record::prelude::bind_value_owned(query, value);
|
||||
}
|
||||
} else if let Some(ref value) = self.#field_idents {
|
||||
query = query.bind(value);
|
||||
}
|
||||
)*
|
||||
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().
|
||||
pub fn bind_form_values<'q>(&'q self, mut query: sqlx::query::Query<'q, #db, #db_args>)
|
||||
-> sqlx::query::Query<'q, #db, #db_args>
|
||||
{
|
||||
if self._exprs.is_empty() {
|
||||
// No expressions, use simple binding
|
||||
#(
|
||||
if let Some(ref value) = self.#field_idents {
|
||||
query = query.bind(value);
|
||||
// Always use Value-based binding to properly handle Option<T> fields
|
||||
// This ensures nested Options (Option<Option<T>>) are unwrapped correctly
|
||||
let (_, values) = self.update_stmt_with_values();
|
||||
for value in values {
|
||||
query = ::sqlx_record::prelude::bind_value_owned(query, value);
|
||||
}
|
||||
)*
|
||||
query
|
||||
} else {
|
||||
// Has expressions, use full binding
|
||||
self.bind_all_values(query)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this form uses any expressions
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@ impl UpdateExpr {
|
|||
pub type SqlValue = Value;
|
||||
|
||||
// MySQL supports unsigned integers natively
|
||||
// Note: UUID is bound as bytes for BINARY(16) column compatibility
|
||||
#[cfg(feature = "mysql")]
|
||||
macro_rules! bind_value {
|
||||
($query:expr, $value: expr) => {{
|
||||
|
|
|
|||
Loading…
Reference in New Issue