Skip to main content

sqlmodel_query/
builder.rs

1//! Query builders for INSERT, UPDATE, DELETE operations.
2//!
3//! This module provides fluent builders for CRUD operations with support for:
4//! - RETURNING clause (PostgreSQL)
5//! - Bulk inserts
6//! - UPSERT (ON CONFLICT)
7//! - Explicit column SET for updates
8//! - Model-based deletes
9
10use crate::clause::Where;
11use crate::expr::{Dialect, Expr};
12use asupersync::{Cx, Outcome};
13use sqlmodel_core::{
14    Connection, FieldInfo, InheritanceStrategy, Model, Row, TransactionOps, Value,
15};
16use std::collections::HashSet;
17use std::marker::PhantomData;
18
19fn is_joined_inheritance_child<M: Model>() -> bool {
20    let inh = M::inheritance();
21    inh.strategy == InheritanceStrategy::Joined && inh.parent.is_some()
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25enum JoinedTableTarget {
26    Parent,
27    Child,
28}
29
30type JoinedSetPairs = Vec<(&'static str, Value)>;
31
32#[allow(clippy::result_large_err)]
33fn joined_parent_meta<M: Model>()
34-> Result<(&'static str, &'static [FieldInfo]), sqlmodel_core::Error> {
35    let inh = M::inheritance();
36    let Some(parent_table) = inh.parent else {
37        return Err(sqlmodel_core::Error::Custom(
38            "joined-table inheritance child missing parent table metadata".to_string(),
39        ));
40    };
41    let Some(parent_fields_fn) = inh.parent_fields_fn else {
42        return Err(sqlmodel_core::Error::Custom(
43            "joined-table inheritance child missing parent_fields_fn metadata".to_string(),
44        ));
45    };
46    Ok((parent_table, parent_fields_fn()))
47}
48
49#[allow(clippy::result_large_err)]
50fn classify_joined_column<M: Model>(
51    column: &str,
52    parent_table: &'static str,
53    parent_fields: &'static [FieldInfo],
54) -> Result<(JoinedTableTarget, &'static str), sqlmodel_core::Error> {
55    let child_fields = M::fields();
56
57    let child_lookup = |name: &str| -> Option<&'static str> {
58        child_fields
59            .iter()
60            .find(|f| f.column_name == name)
61            .map(|f| f.column_name)
62    };
63    let parent_lookup = |name: &str| -> Option<&'static str> {
64        parent_fields
65            .iter()
66            .find(|f| f.column_name == name)
67            .map(|f| f.column_name)
68    };
69
70    if let Some((table, col)) = column.split_once('.') {
71        if table == parent_table {
72            return parent_lookup(col)
73                .map(|c| (JoinedTableTarget::Parent, c))
74                .ok_or_else(|| {
75                    sqlmodel_core::Error::Custom(format!(
76                        "unknown parent column '{col}' for joined-table inheritance child"
77                    ))
78                });
79        }
80        if table == M::TABLE_NAME {
81            return child_lookup(col)
82                .map(|c| (JoinedTableTarget::Child, c))
83                .ok_or_else(|| {
84                    sqlmodel_core::Error::Custom(format!(
85                        "unknown child column '{col}' for joined-table inheritance child"
86                    ))
87                });
88        }
89        return Err(sqlmodel_core::Error::Custom(format!(
90            "unknown table qualifier '{table}' for joined-table inheritance DML; expected '{}' or '{}'",
91            parent_table,
92            M::TABLE_NAME
93        )));
94    }
95
96    let in_parent = parent_lookup(column);
97    let in_child = child_lookup(column);
98    match (in_parent, in_child) {
99        (Some(c), None) => Ok((JoinedTableTarget::Parent, c)),
100        (None, Some(c)) => Ok((JoinedTableTarget::Child, c)),
101        (Some(_), Some(_)) => Err(sqlmodel_core::Error::Custom(format!(
102            "ambiguous joined-table inheritance column '{column}' exists in both parent and child tables; qualify as '{parent_table}.{column}' or '{}.{column}'",
103            M::TABLE_NAME
104        ))),
105        (None, None) => Err(sqlmodel_core::Error::Custom(format!(
106            "unknown joined-table inheritance column '{column}'"
107        ))),
108    }
109}
110
111#[allow(clippy::result_large_err)]
112fn build_joined_pk_select_sql<M: Model>(
113    dialect: Dialect,
114    where_clause: Option<&Where>,
115    param_offset: usize,
116) -> Result<(String, Vec<Value>), sqlmodel_core::Error> {
117    let (parent_table, _parent_fields) = joined_parent_meta::<M>()?;
118    let pk_cols = M::PRIMARY_KEY;
119    if pk_cols.is_empty() {
120        return Err(sqlmodel_core::Error::Custom(
121            "joined-table inheritance DML requires a primary key".to_string(),
122        ));
123    }
124
125    let mut sql = String::new();
126    sql.push_str("SELECT ");
127    // Always select PK columns from the child table to avoid ambiguity.
128    sql.push_str(
129        &pk_cols
130            .iter()
131            .map(|c| format!("{}.{}", M::TABLE_NAME, c))
132            .collect::<Vec<_>>()
133            .join(", "),
134    );
135    sql.push_str(" FROM ");
136    sql.push_str(M::TABLE_NAME);
137    sql.push_str(" JOIN ");
138    sql.push_str(parent_table);
139    sql.push_str(" ON ");
140    sql.push_str(
141        &pk_cols
142            .iter()
143            .map(|c| format!("{}.{} = {}.{}", M::TABLE_NAME, c, parent_table, c))
144            .collect::<Vec<_>>()
145            .join(" AND "),
146    );
147
148    let mut params = Vec::new();
149    if let Some(w) = where_clause {
150        let (where_sql, where_params) = w.build_with_dialect(dialect, param_offset);
151        sql.push_str(" WHERE ");
152        sql.push_str(&where_sql);
153        params.extend(where_params);
154    }
155
156    Ok((sql, params))
157}
158
159#[allow(clippy::result_large_err)]
160fn extract_pk_values_from_rows(
161    rows: Vec<Row>,
162    pk_col_count: usize,
163) -> Result<Vec<Vec<Value>>, sqlmodel_core::Error> {
164    let mut pk_values = Vec::with_capacity(rows.len());
165    for row in rows {
166        if row.len() < pk_col_count {
167            return Err(sqlmodel_core::Error::Custom(format!(
168                "joined-table inheritance PK lookup returned {} columns; expected at least {}",
169                row.len(),
170                pk_col_count
171            )));
172        }
173        let mut vals = Vec::with_capacity(pk_col_count);
174        for i in 0..pk_col_count {
175            let Some(v) = row.get(i) else {
176                return Err(sqlmodel_core::Error::Custom(format!(
177                    "joined-table inheritance PK lookup missing column index {i}"
178                )));
179            };
180            vals.push(v.clone());
181        }
182        pk_values.push(vals);
183    }
184    Ok(pk_values)
185}
186
187async fn select_joined_pk_values_in_tx<Tx: TransactionOps, M: Model>(
188    tx: &Tx,
189    cx: &Cx,
190    dialect: Dialect,
191    where_clause: Option<&Where>,
192) -> Outcome<Vec<Vec<Value>>, sqlmodel_core::Error> {
193    let pk_cols = M::PRIMARY_KEY;
194    let (pk_sql, pk_params) = match build_joined_pk_select_sql::<M>(dialect, where_clause, 0) {
195        Ok(v) => v,
196        Err(e) => return Outcome::Err(e),
197    };
198    match tx.query(cx, &pk_sql, &pk_params).await {
199        Outcome::Ok(rows) => match extract_pk_values_from_rows(rows, pk_cols.len()) {
200            Ok(vals) => Outcome::Ok(vals),
201            Err(e) => Outcome::Err(e),
202        },
203        Outcome::Err(e) => Outcome::Err(e),
204        Outcome::Cancelled(r) => Outcome::Cancelled(r),
205        Outcome::Panicked(p) => Outcome::Panicked(p),
206    }
207}
208
209#[allow(clippy::result_large_err)]
210fn split_explicit_joined_sets<M: Model>(
211    explicit_sets: &[SetClause],
212    parent_table: &'static str,
213    parent_fields: &'static [FieldInfo],
214) -> Result<(JoinedSetPairs, JoinedSetPairs), sqlmodel_core::Error> {
215    let mut parent_sets = Vec::new();
216    let mut child_sets = Vec::new();
217
218    for set in explicit_sets {
219        let (target, col) = classify_joined_column::<M>(&set.column, parent_table, parent_fields)?;
220        if M::PRIMARY_KEY.contains(&col) {
221            return Err(sqlmodel_core::Error::Custom(format!(
222                "joined-table inheritance update does not support setting primary key column '{col}'"
223            )));
224        }
225        match target {
226            JoinedTableTarget::Parent => parent_sets.push((col, set.value.clone())),
227            JoinedTableTarget::Child => child_sets.push((col, set.value.clone())),
228        }
229    }
230
231    Ok((parent_sets, child_sets))
232}
233
234fn build_pk_in_where(
235    dialect: Dialect,
236    pk_cols: &[&'static str],
237    pk_values: &[Vec<Value>],
238    param_offset: usize,
239) -> (String, Vec<Value>) {
240    let mut params: Vec<Value> = Vec::new();
241
242    if pk_cols.is_empty() || pk_values.is_empty() {
243        return (String::new(), params);
244    }
245
246    if pk_cols.len() == 1 {
247        let col = pk_cols[0];
248        let mut placeholders = Vec::new();
249        for vals in pk_values {
250            if vals.len() != 1 {
251                continue;
252            }
253            params.push(vals[0].clone());
254            placeholders.push(dialect.placeholder(param_offset + params.len()));
255        }
256        return (format!("{col} IN ({})", placeholders.join(", ")), params);
257    }
258
259    // Composite PK: (a,b) IN ((..),(..))
260    let cols_tuple = format!("({})", pk_cols.join(", "));
261    let mut groups = Vec::new();
262    for vals in pk_values {
263        if vals.len() != pk_cols.len() {
264            continue;
265        }
266        let mut ph = Vec::new();
267        for v in vals {
268            params.push(v.clone());
269            ph.push(dialect.placeholder(param_offset + params.len()));
270        }
271        groups.push(format!("({})", ph.join(", ")));
272    }
273
274    (format!("{cols_tuple} IN ({})", groups.join(", ")), params)
275}
276
277fn build_pk_in_where_qualified(
278    dialect: Dialect,
279    table: &str,
280    pk_cols: &[&'static str],
281    pk_values: &[Vec<Value>],
282    param_offset: usize,
283) -> (String, Vec<Value>) {
284    let qualified_cols: Vec<String> = pk_cols.iter().map(|c| format!("{table}.{c}")).collect();
285
286    let mut params: Vec<Value> = Vec::new();
287    if qualified_cols.is_empty() || pk_values.is_empty() {
288        return (String::new(), params);
289    }
290
291    if qualified_cols.len() == 1 {
292        let col = &qualified_cols[0];
293        let mut placeholders = Vec::new();
294        for vals in pk_values {
295            if vals.len() != 1 {
296                continue;
297            }
298            params.push(vals[0].clone());
299            placeholders.push(dialect.placeholder(param_offset + params.len()));
300        }
301        return (format!("{col} IN ({})", placeholders.join(", ")), params);
302    }
303
304    let cols_tuple = format!("({})", qualified_cols.join(", "));
305    let mut groups = Vec::new();
306    for vals in pk_values {
307        if vals.len() != qualified_cols.len() {
308            continue;
309        }
310        let mut ph = Vec::new();
311        for v in vals {
312            params.push(v.clone());
313            ph.push(dialect.placeholder(param_offset + params.len()));
314        }
315        groups.push(format!("({})", ph.join(", ")));
316    }
317
318    (format!("{cols_tuple} IN ({})", groups.join(", ")), params)
319}
320
321fn build_update_sql_for_table_pk_in(
322    dialect: Dialect,
323    table: &str,
324    pk_cols: &[&'static str],
325    pk_values: &[Vec<Value>],
326    set_pairs: &[(&'static str, Value)],
327) -> (String, Vec<Value>) {
328    let mut params = Vec::new();
329    let mut set_clauses = Vec::new();
330    for (col, value) in set_pairs {
331        set_clauses.push(format!(
332            "{} = {}",
333            col,
334            dialect.placeholder(params.len() + 1)
335        ));
336        params.push(value.clone());
337    }
338    if set_clauses.is_empty() {
339        return (String::new(), Vec::new());
340    }
341
342    let (pk_where, pk_params) = build_pk_in_where(dialect, pk_cols, pk_values, params.len());
343    if pk_where.is_empty() {
344        return (String::new(), Vec::new());
345    }
346
347    let sql = format!(
348        "UPDATE {} SET {} WHERE {}",
349        table,
350        set_clauses.join(", "),
351        pk_where
352    );
353    params.extend(pk_params);
354    (sql, params)
355}
356
357fn build_delete_sql_for_table_pk_in(
358    dialect: Dialect,
359    table: &str,
360    pk_cols: &[&'static str],
361    pk_values: &[Vec<Value>],
362) -> (String, Vec<Value>) {
363    let (pk_where, pk_params) = build_pk_in_where(dialect, pk_cols, pk_values, 0);
364    if pk_where.is_empty() {
365        return (String::new(), Vec::new());
366    }
367    (format!("DELETE FROM {table} WHERE {pk_where}"), pk_params)
368}
369
370#[allow(clippy::result_large_err)]
371fn build_joined_child_select_sql_by_pk_in<M: Model>(
372    dialect: Dialect,
373    pk_cols: &[&'static str],
374    pk_values: &[Vec<Value>],
375) -> Result<(String, Vec<Value>), sqlmodel_core::Error> {
376    let (parent_table, parent_fields) = joined_parent_meta::<M>()?;
377    if pk_cols.is_empty() {
378        return Err(sqlmodel_core::Error::Custom(
379            "joined-table inheritance returning requires a primary key".to_string(),
380        ));
381    }
382
383    let child_cols: Vec<&'static str> = M::fields().iter().map(|f| f.column_name).collect();
384    let parent_cols: Vec<&'static str> = parent_fields.iter().map(|f| f.column_name).collect();
385
386    let mut col_parts = Vec::new();
387    for col in &child_cols {
388        col_parts.push(format!(
389            "{}.{} AS {}__{}",
390            M::TABLE_NAME,
391            col,
392            M::TABLE_NAME,
393            col
394        ));
395    }
396    for col in &parent_cols {
397        col_parts.push(format!(
398            "{}.{} AS {}__{}",
399            parent_table, col, parent_table, col
400        ));
401    }
402
403    let mut sql = String::new();
404    sql.push_str("SELECT ");
405    sql.push_str(&col_parts.join(", "));
406    sql.push_str(" FROM ");
407    sql.push_str(M::TABLE_NAME);
408    sql.push_str(" JOIN ");
409    sql.push_str(parent_table);
410    sql.push_str(" ON ");
411    sql.push_str(
412        &pk_cols
413            .iter()
414            .map(|c| format!("{}.{} = {}.{}", M::TABLE_NAME, c, parent_table, c))
415            .collect::<Vec<_>>()
416            .join(" AND "),
417    );
418
419    let (pk_where, pk_params) =
420        build_pk_in_where_qualified(dialect, M::TABLE_NAME, pk_cols, pk_values, 0);
421    if pk_where.is_empty() {
422        return Ok((String::new(), Vec::new()));
423    }
424    sql.push_str(" WHERE ");
425    sql.push_str(&pk_where);
426
427    Ok((sql, pk_params))
428}
429
430fn rewrite_insert_as_ignore(sql: &mut String) {
431    if let Some(rest) = sql.strip_prefix("INSERT INTO ") {
432        *sql = format!("INSERT IGNORE INTO {rest}");
433    }
434}
435
436fn append_on_conflict_clause(
437    dialect: Dialect,
438    sql: &mut String,
439    pk_cols: &[&'static str],
440    insert_columns: &[&'static str],
441    on_conflict: &OnConflict,
442) {
443    if dialect == Dialect::Mysql {
444        match on_conflict {
445            OnConflict::DoNothing => {
446                rewrite_insert_as_ignore(sql);
447                return;
448            }
449            OnConflict::DoUpdate { columns, .. } => {
450                let update_cols: Vec<String> = if columns.is_empty() {
451                    insert_columns
452                        .iter()
453                        .filter(|c| !pk_cols.contains(c))
454                        .map(|c| (*c).to_string())
455                        .collect()
456                } else {
457                    columns.clone()
458                };
459
460                if update_cols.is_empty() {
461                    rewrite_insert_as_ignore(sql);
462                    return;
463                }
464
465                sql.push_str(" ON DUPLICATE KEY UPDATE ");
466                sql.push_str(
467                    &update_cols
468                        .iter()
469                        .map(|c| format!("{c} = VALUES({c})"))
470                        .collect::<Vec<_>>()
471                        .join(", "),
472                );
473                return;
474            }
475        }
476    }
477
478    match on_conflict {
479        OnConflict::DoNothing => {
480            sql.push_str(" ON CONFLICT DO NOTHING");
481        }
482        OnConflict::DoUpdate { columns, target } => {
483            sql.push_str(" ON CONFLICT");
484
485            let effective_target: Vec<String> = if target.is_empty() {
486                pk_cols.iter().map(|c| (*c).to_string()).collect()
487            } else {
488                target.clone()
489            };
490
491            if effective_target.is_empty() {
492                sql.push_str(" DO NOTHING");
493                return;
494            }
495
496            sql.push_str(" (");
497            sql.push_str(&effective_target.join(", "));
498            sql.push(')');
499
500            let update_cols: Vec<String> = if columns.is_empty() {
501                insert_columns
502                    .iter()
503                    .filter(|c| !pk_cols.contains(c))
504                    .map(|c| (*c).to_string())
505                    .collect()
506            } else {
507                columns.clone()
508            };
509
510            if update_cols.is_empty() {
511                sql.push_str(" DO NOTHING");
512                return;
513            }
514
515            sql.push_str(" DO UPDATE SET ");
516            sql.push_str(
517                &update_cols
518                    .iter()
519                    .map(|c| format!("{c} = EXCLUDED.{c}"))
520                    .collect::<Vec<_>>()
521                    .join(", "),
522            );
523        }
524    }
525}
526
527fn build_insert_sql_for_table_with_columns(
528    dialect: Dialect,
529    table: &str,
530    fields: &[FieldInfo],
531    row: &[(&'static str, Value)],
532    returning: Option<&str>,
533) -> (String, Vec<Value>, Vec<&'static str>) {
534    let insert_fields: Vec<_> = row
535        .iter()
536        .map(|(name, value)| {
537            let field = fields.iter().find(|f| f.column_name == *name);
538            if let Some(f) = field
539                && f.auto_increment
540                && matches!(value, Value::Null)
541            {
542                return (*name, Value::Default);
543            }
544            (*name, value.clone())
545        })
546        .collect();
547
548    let mut columns = Vec::new();
549    let mut placeholders = Vec::new();
550    let mut params = Vec::new();
551
552    for (name, value) in insert_fields {
553        if matches!(value, Value::Default) && dialect == Dialect::Sqlite {
554            // SQLite doesn't allow DEFAULT in VALUES; omit the column to trigger defaults.
555            continue;
556        }
557
558        columns.push(name);
559
560        if matches!(value, Value::Default) {
561            placeholders.push("DEFAULT".to_string());
562        } else {
563            params.push(value);
564            placeholders.push(dialect.placeholder(params.len()));
565        }
566    }
567
568    let mut sql = if columns.is_empty() {
569        format!("INSERT INTO {} DEFAULT VALUES", table)
570    } else {
571        format!(
572            "INSERT INTO {} ({}) VALUES ({})",
573            table,
574            columns.join(", "),
575            placeholders.join(", ")
576        )
577    };
578
579    if let Some(ret) = returning {
580        sql.push_str(" RETURNING ");
581        sql.push_str(ret);
582    }
583
584    (sql, params, columns)
585}
586
587fn build_insert_sql_for_table(
588    dialect: Dialect,
589    table: &str,
590    fields: &[FieldInfo],
591    row: &[(&'static str, Value)],
592    returning: Option<&str>,
593) -> (String, Vec<Value>) {
594    let (sql, params, _cols) =
595        build_insert_sql_for_table_with_columns(dialect, table, fields, row, returning);
596    (sql, params)
597}
598
599fn build_update_sql_for_table(
600    dialect: Dialect,
601    table: &str,
602    pk_cols: &[&'static str],
603    pk_vals: &[Value],
604    set_pairs: &[(&'static str, Value)],
605) -> (String, Vec<Value>) {
606    let mut params = Vec::new();
607    let mut set_clauses = Vec::new();
608    for (col, value) in set_pairs {
609        set_clauses.push(format!(
610            "{} = {}",
611            col,
612            dialect.placeholder(params.len() + 1)
613        ));
614        params.push(value.clone());
615    }
616
617    if set_clauses.is_empty() {
618        return (String::new(), Vec::new());
619    }
620
621    let mut sql = format!("UPDATE {} SET {}", table, set_clauses.join(", "));
622    if !pk_cols.is_empty() && pk_cols.len() == pk_vals.len() {
623        let where_parts: Vec<String> = pk_cols
624            .iter()
625            .enumerate()
626            .map(|(i, col)| format!("{} = {}", col, dialect.placeholder(params.len() + i + 1)))
627            .collect();
628        sql.push_str(" WHERE ");
629        sql.push_str(&where_parts.join(" AND "));
630        params.extend_from_slice(pk_vals);
631    }
632
633    (sql, params)
634}
635
636fn extract_single_pk_i64(pk_vals: &[Value]) -> Option<i64> {
637    if pk_vals.len() != 1 {
638        return None;
639    }
640    match &pk_vals[0] {
641        Value::BigInt(v) => Some(*v),
642        Value::Int(v) => Some(i64::from(*v)),
643        _ => None,
644    }
645}
646
647async fn insert_joined_model_in_tx<Tx: TransactionOps, M: Model>(
648    tx: &Tx,
649    cx: &Cx,
650    dialect: Dialect,
651    model: &M,
652    parent_table: &'static str,
653    parent_fields: &'static [FieldInfo],
654) -> Outcome<(u64, Vec<Value>), sqlmodel_core::Error> {
655    let Some(parent_row) = model.joined_parent_row() else {
656        return Outcome::Err(sqlmodel_core::Error::Custom(
657            "joined-table inheritance child missing joined_parent_row() implementation".to_string(),
658        ));
659    };
660
661    let pk_cols = M::PRIMARY_KEY;
662    if pk_cols.is_empty() {
663        return Outcome::Err(sqlmodel_core::Error::Custom(
664            "joined-table inheritance insert requires a primary key column".to_string(),
665        ));
666    }
667    let mut effective_pk_vals = model.primary_key_value();
668    let pk_col = pk_cols.first().copied();
669    let needs_generated_id = pk_col.is_some()
670        && effective_pk_vals.len() == 1
671        && parent_fields
672            .iter()
673            .find(|f| f.column_name == pk_col.unwrap_or("") && f.primary_key)
674            .is_some_and(|f| f.auto_increment)
675        && effective_pk_vals[0].is_null();
676
677    let mut inserted_id: Option<i64> = None;
678    if dialect == Dialect::Postgres && needs_generated_id {
679        let Some(pk_col) = pk_col else {
680            return Outcome::Err(sqlmodel_core::Error::Custom(
681                "joined-table inheritance insert requires a primary key column".to_string(),
682            ));
683        };
684        let (sql, params, _cols) = build_insert_sql_for_table_with_columns(
685            dialect,
686            parent_table,
687            parent_fields,
688            &parent_row,
689            Some(pk_col),
690        );
691        match tx.query_one(cx, &sql, &params).await {
692            Outcome::Ok(Some(row)) => match row.get_as::<i64>(0) {
693                Ok(v) => inserted_id = Some(v),
694                Err(e) => return Outcome::Err(e),
695            },
696            Outcome::Ok(None) => {
697                return Outcome::Err(sqlmodel_core::Error::Custom(
698                    "base insert returned no row".to_string(),
699                ));
700            }
701            Outcome::Err(e) => return Outcome::Err(e),
702            Outcome::Cancelled(r) => return Outcome::Cancelled(r),
703            Outcome::Panicked(p) => return Outcome::Panicked(p),
704        }
705    } else {
706        let (sql, params, _cols) = build_insert_sql_for_table_with_columns(
707            dialect,
708            parent_table,
709            parent_fields,
710            &parent_row,
711            None,
712        );
713        match tx.execute(cx, &sql, &params).await {
714            Outcome::Ok(_) => {}
715            Outcome::Err(e) => return Outcome::Err(e),
716            Outcome::Cancelled(r) => return Outcome::Cancelled(r),
717            Outcome::Panicked(p) => return Outcome::Panicked(p),
718        }
719
720        if needs_generated_id {
721            let id_sql = match dialect {
722                Dialect::Sqlite => "SELECT last_insert_rowid()",
723                Dialect::Mysql => "SELECT LAST_INSERT_ID()",
724                Dialect::Postgres => unreachable!(),
725            };
726            match tx.query_one(cx, id_sql, &[]).await {
727                Outcome::Ok(Some(row)) => match row.get_as::<i64>(0) {
728                    Ok(v) => inserted_id = Some(v),
729                    Err(e) => return Outcome::Err(e),
730                },
731                Outcome::Ok(None) => {
732                    return Outcome::Err(sqlmodel_core::Error::Custom(
733                        "failed to fetch last insert id".to_string(),
734                    ));
735                }
736                Outcome::Err(e) => return Outcome::Err(e),
737                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
738                Outcome::Panicked(p) => return Outcome::Panicked(p),
739            }
740        }
741    }
742
743    let mut child_row = model.to_row();
744    if let (Some(pk_col), Some(id)) = (pk_col, inserted_id) {
745        if pk_cols.len() != 1 {
746            return Outcome::Err(sqlmodel_core::Error::Custom(
747                "joined-table inheritance auto-increment insert currently requires a single-column primary key"
748                    .to_string(),
749            ));
750        }
751        for (name, value) in &mut child_row {
752            if *name == pk_col && value.is_null() {
753                *value = Value::BigInt(id);
754            }
755        }
756        if effective_pk_vals.len() == 1 && effective_pk_vals[0].is_null() {
757            effective_pk_vals[0] = Value::BigInt(id);
758        }
759    }
760
761    let (child_sql, child_params, _cols) = build_insert_sql_for_table_with_columns(
762        dialect,
763        M::TABLE_NAME,
764        M::fields(),
765        &child_row,
766        None,
767    );
768    match tx.execute(cx, &child_sql, &child_params).await {
769        Outcome::Ok(count) => Outcome::Ok((count, effective_pk_vals)),
770        Outcome::Err(e) => Outcome::Err(e),
771        Outcome::Cancelled(r) => Outcome::Cancelled(r),
772        Outcome::Panicked(p) => Outcome::Panicked(p),
773    }
774}
775
776async fn tx_rollback_best_effort<Tx: TransactionOps>(tx: Tx, cx: &Cx) {
777    let _ = tx.rollback(cx).await;
778}
779
780/// Conflict resolution strategy for INSERT operations.
781///
782/// Used with PostgreSQL's ON CONFLICT clause for UPSERT operations.
783#[derive(Debug, Clone)]
784pub enum OnConflict {
785    /// Do nothing on conflict (INSERT ... ON CONFLICT DO NOTHING)
786    DoNothing,
787    /// Update specified columns on conflict (INSERT ... ON CONFLICT DO UPDATE SET ...)
788    DoUpdate {
789        /// The columns to update. If empty, all non-primary-key columns are updated.
790        columns: Vec<String>,
791        /// The conflict target (column names). If empty, uses primary key.
792        target: Vec<String>,
793    },
794}
795
796/// INSERT query builder.
797///
798/// # Example
799///
800/// ```ignore
801/// // Simple insert
802/// let id = insert!(hero).execute(cx, &conn).await?;
803///
804/// // Insert with RETURNING
805/// let row = insert!(hero).returning().execute_returning(cx, &conn).await?;
806///
807/// // Insert with UPSERT
808/// let id = insert!(hero)
809///     .on_conflict_do_nothing()
810///     .execute(cx, &conn).await?;
811/// ```
812#[derive(Debug)]
813pub struct InsertBuilder<'a, M: Model> {
814    model: &'a M,
815    returning: bool,
816    on_conflict: Option<OnConflict>,
817}
818
819impl<'a, M: Model> InsertBuilder<'a, M> {
820    /// Create a new INSERT builder for the given model instance.
821    pub fn new(model: &'a M) -> Self {
822        Self {
823            model,
824            returning: false,
825            on_conflict: None,
826        }
827    }
828
829    /// Add RETURNING * clause to return the inserted row.
830    ///
831    /// Use with `execute_returning()` to get the inserted row.
832    pub fn returning(mut self) -> Self {
833        self.returning = true;
834        self
835    }
836
837    /// Handle conflicts by doing nothing (PostgreSQL ON CONFLICT DO NOTHING).
838    ///
839    /// This allows the insert to silently succeed even if it would violate
840    /// a unique constraint.
841    pub fn on_conflict_do_nothing(mut self) -> Self {
842        self.on_conflict = Some(OnConflict::DoNothing);
843        self
844    }
845
846    /// Handle conflicts by updating specified columns (UPSERT).
847    ///
848    /// If `columns` is empty, all non-primary-key columns are updated.
849    /// The conflict target defaults to the primary key.
850    ///
851    /// # Example
852    ///
853    /// ```ignore
854    /// // Update name and age on conflict
855    /// insert!(hero)
856    ///     .on_conflict_do_update(&["name", "age"])
857    ///     .execute(cx, &conn).await?;
858    /// ```
859    pub fn on_conflict_do_update(mut self, columns: &[&str]) -> Self {
860        self.on_conflict = Some(OnConflict::DoUpdate {
861            columns: columns.iter().map(|s| s.to_string()).collect(),
862            target: Vec::new(), // Default to primary key
863        });
864        self
865    }
866
867    /// Handle conflicts by updating columns with a specific conflict target.
868    ///
869    /// # Arguments
870    ///
871    /// * `target` - The columns that form the unique constraint to match
872    /// * `columns` - The columns to update on conflict
873    pub fn on_conflict_target_do_update(mut self, target: &[&str], columns: &[&str]) -> Self {
874        self.on_conflict = Some(OnConflict::DoUpdate {
875            columns: columns.iter().map(|s| s.to_string()).collect(),
876            target: target.iter().map(|s| s.to_string()).collect(),
877        });
878        self
879    }
880
881    /// Build the INSERT SQL and parameters with default dialect (Postgres).
882    pub fn build(&self) -> (String, Vec<Value>) {
883        self.build_with_dialect(Dialect::default())
884    }
885
886    /// Build the INSERT SQL and parameters with specific dialect.
887    pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
888        let row = self.model.to_row();
889        let fields = M::fields();
890
891        let insert_fields: Vec<_> = row
892            .iter()
893            .map(|(name, value)| {
894                let field = fields.iter().find(|f| f.column_name == *name);
895                if let Some(f) = field
896                    && f.auto_increment
897                    && matches!(value, Value::Null)
898                {
899                    return (*name, Value::Default);
900                }
901                (*name, value.clone())
902            })
903            .collect();
904
905        let mut columns = Vec::new();
906        let mut placeholders = Vec::new();
907        let mut params = Vec::new();
908
909        for (name, value) in insert_fields {
910            if matches!(value, Value::Default) && dialect == Dialect::Sqlite {
911                // SQLite doesn't allow DEFAULT in VALUES; omit the column to trigger defaults.
912                continue;
913            }
914
915            columns.push(name);
916
917            if matches!(value, Value::Default) {
918                placeholders.push("DEFAULT".to_string());
919            } else {
920                params.push(value);
921                placeholders.push(dialect.placeholder(params.len()));
922            }
923        }
924
925        let mut sql = if columns.is_empty() {
926            format!("INSERT INTO {} DEFAULT VALUES", M::TABLE_NAME)
927        } else {
928            format!(
929                "INSERT INTO {} ({}) VALUES ({})",
930                M::TABLE_NAME,
931                columns.join(", "),
932                placeholders.join(", ")
933            )
934        };
935
936        // Add ON CONFLICT/UPSERT clause if specified
937        if let Some(on_conflict) = &self.on_conflict {
938            append_on_conflict_clause(dialect, &mut sql, M::PRIMARY_KEY, &columns, on_conflict);
939        }
940
941        // Add RETURNING clause if requested
942        if self.returning {
943            sql.push_str(" RETURNING *");
944        }
945
946        (sql, params)
947    }
948
949    /// Execute the INSERT and return the inserted ID.
950    pub async fn execute<C: Connection>(
951        self,
952        cx: &Cx,
953        conn: &C,
954    ) -> Outcome<i64, sqlmodel_core::Error> {
955        if is_joined_inheritance_child::<M>() {
956            let dialect = conn.dialect();
957            let on_conflict = self.on_conflict.clone();
958            let (parent_table, parent_fields) = match joined_parent_meta::<M>() {
959                Ok(v) => v,
960                Err(e) => return Outcome::Err(e),
961            };
962
963            let Some(parent_row) = self.model.joined_parent_row() else {
964                return Outcome::Err(sqlmodel_core::Error::Custom(
965                    "joined-table inheritance child missing joined_parent_row() implementation"
966                        .to_string(),
967                ));
968            };
969
970            let pk_vals = self.model.primary_key_value();
971            let pk_col = M::PRIMARY_KEY.first().copied();
972            let needs_generated_id = pk_col.is_some()
973                && pk_vals.len() == 1
974                && parent_fields
975                    .iter()
976                    .find(|f| f.column_name == pk_col.unwrap_or("") && f.primary_key)
977                    .is_some_and(|f| f.auto_increment)
978                && pk_vals[0].is_null();
979
980            if on_conflict.is_some() {
981                if needs_generated_id || pk_vals.iter().any(|v| v.is_null()) {
982                    return Outcome::Err(sqlmodel_core::Error::Custom(
983                        "joined-table inheritance insert ON CONFLICT requires explicit primary key values (auto-increment upsert is not supported yet)"
984                            .to_string(),
985                    ));
986                }
987                // For joined-table inheritance, we currently only support conflict targets that
988                // align across both tables (primary key).
989                if let Some(OnConflict::DoUpdate { target, .. }) = &on_conflict {
990                    let pk_target: Vec<String> =
991                        M::PRIMARY_KEY.iter().map(|c| (*c).to_string()).collect();
992                    if !target.is_empty() && target != &pk_target {
993                        return Outcome::Err(sqlmodel_core::Error::Custom(
994                            "joined-table inheritance insert ON CONFLICT currently only supports the primary key as conflict target"
995                                .to_string(),
996                        ));
997                    }
998                }
999            }
1000
1001            let parent_allowed: HashSet<&'static str> =
1002                parent_fields.iter().map(|f| f.column_name).collect();
1003            let child_allowed: HashSet<&'static str> =
1004                M::fields().iter().map(|f| f.column_name).collect();
1005
1006            let (parent_on_conflict, child_on_conflict) = match &on_conflict {
1007                None => (None, None),
1008                Some(OnConflict::DoNothing) => {
1009                    (Some(OnConflict::DoNothing), Some(OnConflict::DoNothing))
1010                }
1011                Some(OnConflict::DoUpdate { columns, target }) => {
1012                    // Column list can include either parent or child columns; each table gets its own subset.
1013                    for c in columns {
1014                        if !parent_allowed.contains(c.as_str())
1015                            && !child_allowed.contains(c.as_str())
1016                        {
1017                            return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1018                                "unknown joined-table inheritance ON CONFLICT update column '{c}'"
1019                            )));
1020                        }
1021                    }
1022
1023                    let parent_cols: Vec<String> = columns
1024                        .iter()
1025                        .filter(|c| parent_allowed.contains(c.as_str()))
1026                        .cloned()
1027                        .collect();
1028                    let child_cols: Vec<String> = columns
1029                        .iter()
1030                        .filter(|c| child_allowed.contains(c.as_str()))
1031                        .cloned()
1032                        .collect();
1033
1034                    (
1035                        Some(OnConflict::DoUpdate {
1036                            columns: parent_cols,
1037                            target: target.clone(),
1038                        }),
1039                        Some(OnConflict::DoUpdate {
1040                            columns: child_cols,
1041                            target: target.clone(),
1042                        }),
1043                    )
1044                }
1045            };
1046
1047            let tx_out = conn.begin(cx).await;
1048            let tx = match tx_out {
1049                Outcome::Ok(t) => t,
1050                Outcome::Err(e) => return Outcome::Err(e),
1051                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1052                Outcome::Panicked(p) => return Outcome::Panicked(p),
1053            };
1054
1055            // 1) Insert base row (parent table), possibly retrieving the generated PK.
1056            let mut inserted_id: Option<i64> = None;
1057            if dialect == Dialect::Postgres {
1058                let Some(pk_col) = pk_col else {
1059                    tx_rollback_best_effort(tx, cx).await;
1060                    return Outcome::Err(sqlmodel_core::Error::Custom(
1061                        "joined-table inheritance insert requires a primary key column".to_string(),
1062                    ));
1063                };
1064
1065                if needs_generated_id {
1066                    let (sql, params, _cols) = build_insert_sql_for_table_with_columns(
1067                        dialect,
1068                        parent_table,
1069                        parent_fields,
1070                        &parent_row,
1071                        Some(pk_col),
1072                    );
1073                    match tx.query_one(cx, &sql, &params).await {
1074                        Outcome::Ok(Some(row)) => match row.get_as::<i64>(0) {
1075                            Ok(v) => inserted_id = Some(v),
1076                            Err(e) => {
1077                                tx_rollback_best_effort(tx, cx).await;
1078                                return Outcome::Err(e);
1079                            }
1080                        },
1081                        Outcome::Ok(None) => {
1082                            tx_rollback_best_effort(tx, cx).await;
1083                            return Outcome::Err(sqlmodel_core::Error::Custom(
1084                                "base insert returned no row".to_string(),
1085                            ));
1086                        }
1087                        Outcome::Err(e) => {
1088                            tx_rollback_best_effort(tx, cx).await;
1089                            return Outcome::Err(e);
1090                        }
1091                        Outcome::Cancelled(r) => {
1092                            tx_rollback_best_effort(tx, cx).await;
1093                            return Outcome::Cancelled(r);
1094                        }
1095                        Outcome::Panicked(p) => {
1096                            tx_rollback_best_effort(tx, cx).await;
1097                            return Outcome::Panicked(p);
1098                        }
1099                    }
1100                } else {
1101                    let (mut sql, params, cols) = build_insert_sql_for_table_with_columns(
1102                        dialect,
1103                        parent_table,
1104                        parent_fields,
1105                        &parent_row,
1106                        None,
1107                    );
1108                    if let Some(oc) = &parent_on_conflict {
1109                        append_on_conflict_clause(dialect, &mut sql, M::PRIMARY_KEY, &cols, oc);
1110                    }
1111                    match tx.execute(cx, &sql, &params).await {
1112                        Outcome::Ok(_) => {}
1113                        Outcome::Err(e) => {
1114                            tx_rollback_best_effort(tx, cx).await;
1115                            return Outcome::Err(e);
1116                        }
1117                        Outcome::Cancelled(r) => {
1118                            tx_rollback_best_effort(tx, cx).await;
1119                            return Outcome::Cancelled(r);
1120                        }
1121                        Outcome::Panicked(p) => {
1122                            tx_rollback_best_effort(tx, cx).await;
1123                            return Outcome::Panicked(p);
1124                        }
1125                    }
1126                }
1127            } else {
1128                let (mut sql, params, cols) = build_insert_sql_for_table_with_columns(
1129                    dialect,
1130                    parent_table,
1131                    parent_fields,
1132                    &parent_row,
1133                    None,
1134                );
1135                if let Some(oc) = &parent_on_conflict {
1136                    append_on_conflict_clause(dialect, &mut sql, M::PRIMARY_KEY, &cols, oc);
1137                }
1138                match tx.execute(cx, &sql, &params).await {
1139                    Outcome::Ok(_) => {}
1140                    Outcome::Err(e) => {
1141                        tx_rollback_best_effort(tx, cx).await;
1142                        return Outcome::Err(e);
1143                    }
1144                    Outcome::Cancelled(r) => {
1145                        tx_rollback_best_effort(tx, cx).await;
1146                        return Outcome::Cancelled(r);
1147                    }
1148                    Outcome::Panicked(p) => {
1149                        tx_rollback_best_effort(tx, cx).await;
1150                        return Outcome::Panicked(p);
1151                    }
1152                }
1153
1154                if needs_generated_id {
1155                    let id_sql = match dialect {
1156                        Dialect::Sqlite => "SELECT last_insert_rowid()",
1157                        Dialect::Mysql => "SELECT LAST_INSERT_ID()",
1158                        Dialect::Postgres => unreachable!(),
1159                    };
1160                    match tx.query_one(cx, id_sql, &[]).await {
1161                        Outcome::Ok(Some(row)) => match row.get_as::<i64>(0) {
1162                            Ok(v) => inserted_id = Some(v),
1163                            Err(e) => {
1164                                tx_rollback_best_effort(tx, cx).await;
1165                                return Outcome::Err(e);
1166                            }
1167                        },
1168                        Outcome::Ok(None) => {
1169                            tx_rollback_best_effort(tx, cx).await;
1170                            return Outcome::Err(sqlmodel_core::Error::Custom(
1171                                "failed to fetch last insert id".to_string(),
1172                            ));
1173                        }
1174                        Outcome::Err(e) => {
1175                            tx_rollback_best_effort(tx, cx).await;
1176                            return Outcome::Err(e);
1177                        }
1178                        Outcome::Cancelled(r) => {
1179                            tx_rollback_best_effort(tx, cx).await;
1180                            return Outcome::Cancelled(r);
1181                        }
1182                        Outcome::Panicked(p) => {
1183                            tx_rollback_best_effort(tx, cx).await;
1184                            return Outcome::Panicked(p);
1185                        }
1186                    }
1187                }
1188            }
1189
1190            // 2) Insert child row (child table), patching PK if it was generated by base insert.
1191            let mut child_row = self.model.to_row();
1192            if let (Some(pk_col), Some(id)) = (pk_col, inserted_id) {
1193                if M::PRIMARY_KEY.len() != 1 {
1194                    tx_rollback_best_effort(tx, cx).await;
1195                    return Outcome::Err(sqlmodel_core::Error::Custom(
1196                        "joined-table inheritance auto-increment insert currently requires a single-column primary key"
1197                            .to_string(),
1198                    ));
1199                }
1200
1201                for (name, value) in &mut child_row {
1202                    if *name == pk_col && value.is_null() {
1203                        *value = Value::BigInt(id);
1204                    }
1205                }
1206            }
1207
1208            let (mut child_sql, child_params, child_cols) = build_insert_sql_for_table_with_columns(
1209                dialect,
1210                M::TABLE_NAME,
1211                M::fields(),
1212                &child_row,
1213                None,
1214            );
1215            if let Some(oc) = &child_on_conflict {
1216                append_on_conflict_clause(dialect, &mut child_sql, M::PRIMARY_KEY, &child_cols, oc);
1217            }
1218
1219            match tx.execute(cx, &child_sql, &child_params).await {
1220                Outcome::Ok(_) => {}
1221                Outcome::Err(e) => {
1222                    tx_rollback_best_effort(tx, cx).await;
1223                    return Outcome::Err(e);
1224                }
1225                Outcome::Cancelled(r) => {
1226                    tx_rollback_best_effort(tx, cx).await;
1227                    return Outcome::Cancelled(r);
1228                }
1229                Outcome::Panicked(p) => {
1230                    tx_rollback_best_effort(tx, cx).await;
1231                    return Outcome::Panicked(p);
1232                }
1233            }
1234
1235            match tx.commit(cx).await {
1236                Outcome::Ok(()) => {}
1237                Outcome::Err(e) => return Outcome::Err(e),
1238                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1239                Outcome::Panicked(p) => return Outcome::Panicked(p),
1240            }
1241
1242            let id = inserted_id
1243                .or_else(|| extract_single_pk_i64(&pk_vals))
1244                .unwrap_or(0);
1245            return Outcome::Ok(id);
1246        }
1247
1248        let (sql, params) = self.build_with_dialect(conn.dialect());
1249        conn.insert(cx, &sql, &params).await
1250    }
1251
1252    /// Execute the INSERT with RETURNING and get the inserted row.
1253    ///
1254    /// This automatically adds RETURNING * and returns the full row.
1255    pub async fn execute_returning<C: Connection>(
1256        mut self,
1257        cx: &Cx,
1258        conn: &C,
1259    ) -> Outcome<Option<Row>, sqlmodel_core::Error> {
1260        self.returning = true;
1261        if is_joined_inheritance_child::<M>() {
1262            if self.on_conflict.is_some() {
1263                return Outcome::Err(sqlmodel_core::Error::Custom(
1264                    "joined-table inheritance insert_returning does not support ON CONFLICT; use execute() for ON CONFLICT semantics"
1265                        .to_string(),
1266                ));
1267            }
1268
1269            let dialect = conn.dialect();
1270            let inh = M::inheritance();
1271            let Some(parent_table) = inh.parent else {
1272                return Outcome::Err(sqlmodel_core::Error::Custom(
1273                    "joined-table inheritance child missing parent table metadata".to_string(),
1274                ));
1275            };
1276            let Some(parent_fields_fn) = inh.parent_fields_fn else {
1277                return Outcome::Err(sqlmodel_core::Error::Custom(
1278                    "joined-table inheritance child missing parent_fields_fn metadata".to_string(),
1279                ));
1280            };
1281            let parent_fields = parent_fields_fn();
1282
1283            let Some(parent_row) = self.model.joined_parent_row() else {
1284                return Outcome::Err(sqlmodel_core::Error::Custom(
1285                    "joined-table inheritance child missing joined_parent_row() implementation"
1286                        .to_string(),
1287                ));
1288            };
1289
1290            let pk_vals = self.model.primary_key_value();
1291            let pk_col = M::PRIMARY_KEY.first().copied();
1292            let needs_generated_id = pk_col.is_some()
1293                && pk_vals.len() == 1
1294                && parent_fields
1295                    .iter()
1296                    .find(|f| f.column_name == pk_col.unwrap_or("") && f.primary_key)
1297                    .is_some_and(|f| f.auto_increment)
1298                && pk_vals[0].is_null();
1299
1300            let tx_out = conn.begin(cx).await;
1301            let tx = match tx_out {
1302                Outcome::Ok(t) => t,
1303                Outcome::Err(e) => return Outcome::Err(e),
1304                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1305                Outcome::Panicked(p) => return Outcome::Panicked(p),
1306            };
1307
1308            let mut inserted_id: Option<i64> = None;
1309            if dialect == Dialect::Postgres {
1310                let Some(pk_col) = pk_col else {
1311                    tx_rollback_best_effort(tx, cx).await;
1312                    return Outcome::Err(sqlmodel_core::Error::Custom(
1313                        "joined-table inheritance insert requires a primary key column".to_string(),
1314                    ));
1315                };
1316
1317                let (sql, params) = build_insert_sql_for_table(
1318                    dialect,
1319                    parent_table,
1320                    parent_fields,
1321                    &parent_row,
1322                    Some(pk_col),
1323                );
1324                match tx.query_one(cx, &sql, &params).await {
1325                    Outcome::Ok(Some(row)) => match row.get_as::<i64>(0) {
1326                        Ok(v) => inserted_id = Some(v),
1327                        Err(e) => {
1328                            tx_rollback_best_effort(tx, cx).await;
1329                            return Outcome::Err(e);
1330                        }
1331                    },
1332                    Outcome::Ok(None) => {
1333                        tx_rollback_best_effort(tx, cx).await;
1334                        return Outcome::Err(sqlmodel_core::Error::Custom(
1335                            "base insert returned no row".to_string(),
1336                        ));
1337                    }
1338                    Outcome::Err(e) => {
1339                        tx_rollback_best_effort(tx, cx).await;
1340                        return Outcome::Err(e);
1341                    }
1342                    Outcome::Cancelled(r) => {
1343                        tx_rollback_best_effort(tx, cx).await;
1344                        return Outcome::Cancelled(r);
1345                    }
1346                    Outcome::Panicked(p) => {
1347                        tx_rollback_best_effort(tx, cx).await;
1348                        return Outcome::Panicked(p);
1349                    }
1350                }
1351            } else {
1352                let (sql, params) = build_insert_sql_for_table(
1353                    dialect,
1354                    parent_table,
1355                    parent_fields,
1356                    &parent_row,
1357                    None,
1358                );
1359                match tx.execute(cx, &sql, &params).await {
1360                    Outcome::Ok(_) => {}
1361                    Outcome::Err(e) => {
1362                        tx_rollback_best_effort(tx, cx).await;
1363                        return Outcome::Err(e);
1364                    }
1365                    Outcome::Cancelled(r) => {
1366                        tx_rollback_best_effort(tx, cx).await;
1367                        return Outcome::Cancelled(r);
1368                    }
1369                    Outcome::Panicked(p) => {
1370                        tx_rollback_best_effort(tx, cx).await;
1371                        return Outcome::Panicked(p);
1372                    }
1373                }
1374
1375                if needs_generated_id {
1376                    let id_sql = match dialect {
1377                        Dialect::Sqlite => "SELECT last_insert_rowid()",
1378                        Dialect::Mysql => "SELECT LAST_INSERT_ID()",
1379                        Dialect::Postgres => unreachable!(),
1380                    };
1381                    match tx.query_one(cx, id_sql, &[]).await {
1382                        Outcome::Ok(Some(row)) => match row.get_as::<i64>(0) {
1383                            Ok(v) => inserted_id = Some(v),
1384                            Err(e) => {
1385                                tx_rollback_best_effort(tx, cx).await;
1386                                return Outcome::Err(e);
1387                            }
1388                        },
1389                        Outcome::Ok(None) => {
1390                            tx_rollback_best_effort(tx, cx).await;
1391                            return Outcome::Err(sqlmodel_core::Error::Custom(
1392                                "failed to fetch last insert id".to_string(),
1393                            ));
1394                        }
1395                        Outcome::Err(e) => {
1396                            tx_rollback_best_effort(tx, cx).await;
1397                            return Outcome::Err(e);
1398                        }
1399                        Outcome::Cancelled(r) => {
1400                            tx_rollback_best_effort(tx, cx).await;
1401                            return Outcome::Cancelled(r);
1402                        }
1403                        Outcome::Panicked(p) => {
1404                            tx_rollback_best_effort(tx, cx).await;
1405                            return Outcome::Panicked(p);
1406                        }
1407                    }
1408                }
1409            }
1410
1411            let mut child_row = self.model.to_row();
1412            if let (Some(pk_col), Some(id)) = (pk_col, inserted_id) {
1413                if M::PRIMARY_KEY.len() != 1 {
1414                    tx_rollback_best_effort(tx, cx).await;
1415                    return Outcome::Err(sqlmodel_core::Error::Custom(
1416                        "joined-table inheritance auto-increment insert currently requires a single-column primary key"
1417                            .to_string(),
1418                    ));
1419                }
1420
1421                for (name, value) in &mut child_row {
1422                    if *name == pk_col && value.is_null() {
1423                        *value = Value::BigInt(id);
1424                    }
1425                }
1426            }
1427
1428            let (child_sql, child_params) = build_insert_sql_for_table(
1429                dialect,
1430                M::TABLE_NAME,
1431                M::fields(),
1432                &child_row,
1433                Some("*"),
1434            );
1435            let row_out = match tx.query_one(cx, &child_sql, &child_params).await {
1436                Outcome::Ok(row) => Outcome::Ok(row),
1437                Outcome::Err(e) => {
1438                    tx_rollback_best_effort(tx, cx).await;
1439                    return Outcome::Err(e);
1440                }
1441                Outcome::Cancelled(r) => {
1442                    tx_rollback_best_effort(tx, cx).await;
1443                    return Outcome::Cancelled(r);
1444                }
1445                Outcome::Panicked(p) => {
1446                    tx_rollback_best_effort(tx, cx).await;
1447                    return Outcome::Panicked(p);
1448                }
1449            };
1450
1451            match tx.commit(cx).await {
1452                Outcome::Ok(()) => {}
1453                Outcome::Err(e) => return Outcome::Err(e),
1454                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1455                Outcome::Panicked(p) => return Outcome::Panicked(p),
1456            }
1457
1458            return row_out;
1459        }
1460
1461        let (sql, params) = self.build_with_dialect(conn.dialect());
1462        conn.query_one(cx, &sql, &params).await
1463    }
1464}
1465
1466/// Bulk INSERT query builder.
1467///
1468/// # Example
1469///
1470/// ```ignore
1471/// let heroes = vec![hero1, hero2, hero3];
1472/// let ids = insert_many!(heroes)
1473///     .execute(cx, &conn).await?;
1474/// ```
1475#[derive(Debug)]
1476pub struct InsertManyBuilder<'a, M: Model> {
1477    models: &'a [M],
1478    returning: bool,
1479    on_conflict: Option<OnConflict>,
1480}
1481
1482impl<'a, M: Model> InsertManyBuilder<'a, M> {
1483    /// Create a new bulk INSERT builder for the given model instances.
1484    pub fn new(models: &'a [M]) -> Self {
1485        Self {
1486            models,
1487            returning: false,
1488            on_conflict: None,
1489        }
1490    }
1491
1492    /// Add RETURNING * clause to return the inserted rows.
1493    pub fn returning(mut self) -> Self {
1494        self.returning = true;
1495        self
1496    }
1497
1498    /// Handle conflicts by doing nothing.
1499    pub fn on_conflict_do_nothing(mut self) -> Self {
1500        self.on_conflict = Some(OnConflict::DoNothing);
1501        self
1502    }
1503
1504    /// Handle conflicts by updating specified columns.
1505    pub fn on_conflict_do_update(mut self, columns: &[&str]) -> Self {
1506        self.on_conflict = Some(OnConflict::DoUpdate {
1507            columns: columns.iter().map(|s| s.to_string()).collect(),
1508            target: Vec::new(),
1509        });
1510        self
1511    }
1512
1513    /// Build the bulk INSERT SQL and parameters with default dialect.
1514    pub fn build(&self) -> (String, Vec<Value>) {
1515        self.build_with_dialect(Dialect::default())
1516    }
1517
1518    /// Build the bulk INSERT SQL and parameters with specific dialect.
1519    pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
1520        let batches = self.build_batches_with_dialect(dialect);
1521        match batches.len() {
1522            0 => (String::new(), Vec::new()),
1523            1 => batches.into_iter().next().unwrap(),
1524            _ => {
1525                tracing::warn!(
1526                    table = M::TABLE_NAME,
1527                    "Bulk insert requires multiple statements for this dialect. \
1528                     Use build_batches_with_dialect or execute() instead of build_with_dialect."
1529                );
1530                (String::new(), Vec::new())
1531            }
1532        }
1533    }
1534
1535    /// Build bulk INSERT statements for the given dialect.
1536    ///
1537    /// SQLite requires column omission when defaults are used, which can
1538    /// produce multiple statements to preserve correct semantics.
1539    pub fn build_batches_with_dialect(&self, dialect: Dialect) -> Vec<(String, Vec<Value>)> {
1540        enum Batch {
1541            Values {
1542                columns: Vec<&'static str>,
1543                rows: Vec<Vec<Value>>,
1544            },
1545            DefaultValues,
1546        }
1547
1548        if self.models.is_empty() {
1549            return Vec::new();
1550        }
1551
1552        if is_joined_inheritance_child::<M>() {
1553            tracing::warn!(
1554                table = M::TABLE_NAME,
1555                "build_batches_with_dialect is not available for joined-table inheritance; use execute()/execute_returning()"
1556            );
1557            return Vec::new();
1558        }
1559
1560        if dialect != Dialect::Sqlite {
1561            return vec![self.build_single_with_dialect(dialect)];
1562        }
1563
1564        let fields = M::fields();
1565        let rows: Vec<Vec<(&'static str, Value)>> =
1566            self.models.iter().map(|model| model.to_row()).collect();
1567
1568        // Determine which columns to insert (preserve field order)
1569        let insert_columns: Vec<_> = fields
1570            .iter()
1571            .filter_map(|field| {
1572                if field.auto_increment {
1573                    return Some(field.column_name);
1574                }
1575                let has_value = rows.iter().any(|row| {
1576                    row.iter()
1577                        .find(|(name, _)| name == &field.column_name)
1578                        .is_some_and(|(_, v)| !matches!(v, Value::Null))
1579                });
1580                if has_value {
1581                    Some(field.column_name)
1582                } else {
1583                    None
1584                }
1585            })
1586            .collect();
1587
1588        let mut batches: Vec<Batch> = Vec::new();
1589
1590        for row in &rows {
1591            let mut columns_for_row = Vec::new();
1592            let mut values_for_row = Vec::new();
1593
1594            for col in &insert_columns {
1595                let mut val = row
1596                    .iter()
1597                    .find(|(name, _)| name == col)
1598                    .map_or(Value::Null, |(_, v)| v.clone());
1599
1600                // Map Null auto-increment fields to DEFAULT
1601                if let Some(f) = fields.iter().find(|f| f.column_name == *col)
1602                    && f.auto_increment
1603                    && matches!(val, Value::Null)
1604                {
1605                    val = Value::Default;
1606                }
1607
1608                if matches!(val, Value::Default) {
1609                    continue;
1610                }
1611
1612                columns_for_row.push(*col);
1613                values_for_row.push(val);
1614            }
1615
1616            if columns_for_row.is_empty() {
1617                batches.push(Batch::DefaultValues);
1618                continue;
1619            }
1620
1621            match batches.last_mut() {
1622                Some(Batch::Values { columns, rows }) if *columns == columns_for_row => {
1623                    rows.push(values_for_row);
1624                }
1625                _ => batches.push(Batch::Values {
1626                    columns: columns_for_row,
1627                    rows: vec![values_for_row],
1628                }),
1629            }
1630        }
1631
1632        let mut statements = Vec::new();
1633
1634        for batch in batches {
1635            match batch {
1636                Batch::DefaultValues => {
1637                    let mut sql = format!("INSERT INTO {} DEFAULT VALUES", M::TABLE_NAME);
1638                    self.append_on_conflict(dialect, &mut sql, &[]);
1639                    self.append_returning(&mut sql);
1640                    statements.push((sql, Vec::new()));
1641                }
1642                Batch::Values { columns, rows } => {
1643                    let (sql, params) = self.build_values_batch_sql(dialect, &columns, &rows);
1644                    statements.push((sql, params));
1645                }
1646            }
1647        }
1648
1649        statements
1650    }
1651
1652    fn build_single_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
1653        let fields = M::fields();
1654        let rows: Vec<Vec<(&'static str, Value)>> =
1655            self.models.iter().map(|model| model.to_row()).collect();
1656
1657        // Determine which columns to insert
1658        // Always include auto-increment fields, include non-null values seen in any row.
1659        let insert_columns: Vec<_> = fields
1660            .iter()
1661            .filter_map(|field| {
1662                if field.auto_increment {
1663                    return Some(field.column_name);
1664                }
1665                let has_value = rows.iter().any(|row| {
1666                    row.iter()
1667                        .find(|(name, _)| name == &field.column_name)
1668                        .is_some_and(|(_, v)| !matches!(v, Value::Null))
1669                });
1670                if has_value {
1671                    Some(field.column_name)
1672                } else {
1673                    None
1674                }
1675            })
1676            .collect();
1677
1678        let mut all_values = Vec::new();
1679        let mut value_groups = Vec::new();
1680
1681        for row in &rows {
1682            let values: Vec<_> = insert_columns
1683                .iter()
1684                .map(|col| {
1685                    let val = row
1686                        .iter()
1687                        .find(|(name, _)| name == col)
1688                        .map_or(Value::Null, |(_, v)| v.clone());
1689
1690                    // Map Null auto-increment fields to DEFAULT
1691                    let field = fields.iter().find(|f| f.column_name == *col);
1692                    if let Some(f) = field
1693                        && f.auto_increment
1694                        && matches!(val, Value::Null)
1695                    {
1696                        return Value::Default;
1697                    }
1698                    val
1699                })
1700                .collect();
1701
1702            let mut placeholders = Vec::new();
1703            for v in &values {
1704                if matches!(v, Value::Default) {
1705                    placeholders.push("DEFAULT".to_string());
1706                } else {
1707                    all_values.push(v.clone());
1708                    placeholders.push(dialect.placeholder(all_values.len()));
1709                }
1710            }
1711
1712            value_groups.push(format!("({})", placeholders.join(", ")));
1713        }
1714
1715        let mut sql = format!(
1716            "INSERT INTO {} ({}) VALUES {}",
1717            M::TABLE_NAME,
1718            insert_columns.join(", "),
1719            value_groups.join(", ")
1720        );
1721
1722        self.append_on_conflict(dialect, &mut sql, &insert_columns);
1723        self.append_returning(&mut sql);
1724
1725        (sql, all_values)
1726    }
1727
1728    fn build_values_batch_sql(
1729        &self,
1730        dialect: Dialect,
1731        columns: &[&'static str],
1732        rows: &[Vec<Value>],
1733    ) -> (String, Vec<Value>) {
1734        let mut params = Vec::new();
1735        let mut value_groups = Vec::new();
1736
1737        for row in rows {
1738            let mut placeholders = Vec::new();
1739            for value in row {
1740                if matches!(value, Value::Default) {
1741                    placeholders.push("DEFAULT".to_string());
1742                } else {
1743                    params.push(value.clone());
1744                    placeholders.push(dialect.placeholder(params.len()));
1745                }
1746            }
1747            value_groups.push(format!("({})", placeholders.join(", ")));
1748        }
1749
1750        let mut sql = if columns.is_empty() {
1751            format!("INSERT INTO {} DEFAULT VALUES", M::TABLE_NAME)
1752        } else {
1753            format!(
1754                "INSERT INTO {} ({}) VALUES {}",
1755                M::TABLE_NAME,
1756                columns.join(", "),
1757                value_groups.join(", ")
1758            )
1759        };
1760
1761        self.append_on_conflict(dialect, &mut sql, columns);
1762        self.append_returning(&mut sql);
1763
1764        (sql, params)
1765    }
1766
1767    fn append_on_conflict(
1768        &self,
1769        dialect: Dialect,
1770        sql: &mut String,
1771        insert_columns: &[&'static str],
1772    ) {
1773        if let Some(on_conflict) = &self.on_conflict {
1774            append_on_conflict_clause(dialect, sql, M::PRIMARY_KEY, insert_columns, on_conflict);
1775        }
1776    }
1777
1778    fn append_returning(&self, sql: &mut String) {
1779        if self.returning {
1780            sql.push_str(" RETURNING *");
1781        }
1782    }
1783
1784    /// Execute the bulk INSERT and return rows affected.
1785    pub async fn execute<C: Connection>(
1786        self,
1787        cx: &Cx,
1788        conn: &C,
1789    ) -> Outcome<u64, sqlmodel_core::Error> {
1790        if is_joined_inheritance_child::<M>() {
1791            if self.on_conflict.is_some() {
1792                return Outcome::Err(sqlmodel_core::Error::Custom(
1793                    "joined-table inheritance bulk insert does not support ON CONFLICT yet"
1794                        .to_string(),
1795                ));
1796            }
1797
1798            let dialect = conn.dialect();
1799            let (parent_table, parent_fields) = match joined_parent_meta::<M>() {
1800                Ok(v) => v,
1801                Err(e) => return Outcome::Err(e),
1802            };
1803            let tx_out = conn.begin(cx).await;
1804            let tx = match tx_out {
1805                Outcome::Ok(t) => t,
1806                Outcome::Err(e) => return Outcome::Err(e),
1807                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1808                Outcome::Panicked(p) => return Outcome::Panicked(p),
1809            };
1810
1811            let mut total_inserted: u64 = 0;
1812            for model in self.models {
1813                match insert_joined_model_in_tx::<_, M>(
1814                    &tx,
1815                    cx,
1816                    dialect,
1817                    model,
1818                    parent_table,
1819                    parent_fields,
1820                )
1821                .await
1822                {
1823                    Outcome::Ok((count, _)) => {
1824                        total_inserted = total_inserted.saturating_add(count);
1825                    }
1826                    Outcome::Err(e) => {
1827                        tx_rollback_best_effort(tx, cx).await;
1828                        return Outcome::Err(e);
1829                    }
1830                    Outcome::Cancelled(r) => {
1831                        tx_rollback_best_effort(tx, cx).await;
1832                        return Outcome::Cancelled(r);
1833                    }
1834                    Outcome::Panicked(p) => {
1835                        tx_rollback_best_effort(tx, cx).await;
1836                        return Outcome::Panicked(p);
1837                    }
1838                }
1839            }
1840
1841            return match tx.commit(cx).await {
1842                Outcome::Ok(()) => Outcome::Ok(total_inserted),
1843                Outcome::Err(e) => Outcome::Err(e),
1844                Outcome::Cancelled(r) => Outcome::Cancelled(r),
1845                Outcome::Panicked(p) => Outcome::Panicked(p),
1846            };
1847        }
1848
1849        let batches = self.build_batches_with_dialect(conn.dialect());
1850        if batches.is_empty() {
1851            return Outcome::Ok(0);
1852        }
1853
1854        if batches.len() == 1 {
1855            let (sql, params) = &batches[0];
1856            return conn.execute(cx, sql, params).await;
1857        }
1858
1859        let outcome = conn.batch(cx, &batches).await;
1860        outcome.map(|counts| counts.into_iter().sum())
1861    }
1862
1863    /// Execute the bulk INSERT with RETURNING and get the inserted rows.
1864    pub async fn execute_returning<C: Connection>(
1865        mut self,
1866        cx: &Cx,
1867        conn: &C,
1868    ) -> Outcome<Vec<Row>, sqlmodel_core::Error> {
1869        self.returning = true;
1870        if is_joined_inheritance_child::<M>() {
1871            if self.on_conflict.is_some() {
1872                return Outcome::Err(sqlmodel_core::Error::Custom(
1873                    "joined-table inheritance bulk insert does not support ON CONFLICT yet"
1874                        .to_string(),
1875                ));
1876            }
1877
1878            let dialect = conn.dialect();
1879            let (parent_table, parent_fields) = match joined_parent_meta::<M>() {
1880                Ok(v) => v,
1881                Err(e) => return Outcome::Err(e),
1882            };
1883            let pk_cols = M::PRIMARY_KEY;
1884            if pk_cols.is_empty() {
1885                return Outcome::Err(sqlmodel_core::Error::Custom(
1886                    "joined-table inheritance returning requires a primary key".to_string(),
1887                ));
1888            }
1889
1890            let tx_out = conn.begin(cx).await;
1891            let tx = match tx_out {
1892                Outcome::Ok(t) => t,
1893                Outcome::Err(e) => return Outcome::Err(e),
1894                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1895                Outcome::Panicked(p) => return Outcome::Panicked(p),
1896            };
1897
1898            let mut inserted_pk_values: Vec<Vec<Value>> = Vec::with_capacity(self.models.len());
1899            for model in self.models {
1900                match insert_joined_model_in_tx::<_, M>(
1901                    &tx,
1902                    cx,
1903                    dialect,
1904                    model,
1905                    parent_table,
1906                    parent_fields,
1907                )
1908                .await
1909                {
1910                    Outcome::Ok((_count, pk_vals)) => {
1911                        if pk_vals.len() != pk_cols.len() || pk_vals.iter().any(Value::is_null) {
1912                            tx_rollback_best_effort(tx, cx).await;
1913                            return Outcome::Err(sqlmodel_core::Error::Custom(
1914                                "joined-table inheritance bulk insert returning requires non-null primary key values"
1915                                    .to_string(),
1916                            ));
1917                        }
1918                        inserted_pk_values.push(pk_vals);
1919                    }
1920                    Outcome::Err(e) => {
1921                        tx_rollback_best_effort(tx, cx).await;
1922                        return Outcome::Err(e);
1923                    }
1924                    Outcome::Cancelled(r) => {
1925                        tx_rollback_best_effort(tx, cx).await;
1926                        return Outcome::Cancelled(r);
1927                    }
1928                    Outcome::Panicked(p) => {
1929                        tx_rollback_best_effort(tx, cx).await;
1930                        return Outcome::Panicked(p);
1931                    }
1932                }
1933            }
1934
1935            if inserted_pk_values.is_empty() {
1936                return match tx.commit(cx).await {
1937                    Outcome::Ok(()) => Outcome::Ok(Vec::new()),
1938                    Outcome::Err(e) => Outcome::Err(e),
1939                    Outcome::Cancelled(r) => Outcome::Cancelled(r),
1940                    Outcome::Panicked(p) => Outcome::Panicked(p),
1941                };
1942            }
1943
1944            let (select_sql, select_params) = match build_joined_child_select_sql_by_pk_in::<M>(
1945                dialect,
1946                pk_cols,
1947                &inserted_pk_values,
1948            ) {
1949                Ok(v) => v,
1950                Err(e) => {
1951                    tx_rollback_best_effort(tx, cx).await;
1952                    return Outcome::Err(e);
1953                }
1954            };
1955            if select_sql.is_empty() {
1956                tx_rollback_best_effort(tx, cx).await;
1957                return Outcome::Ok(Vec::new());
1958            }
1959            let rows = match tx.query(cx, &select_sql, &select_params).await {
1960                Outcome::Ok(rows) => rows,
1961                Outcome::Err(e) => {
1962                    tx_rollback_best_effort(tx, cx).await;
1963                    return Outcome::Err(e);
1964                }
1965                Outcome::Cancelled(r) => {
1966                    tx_rollback_best_effort(tx, cx).await;
1967                    return Outcome::Cancelled(r);
1968                }
1969                Outcome::Panicked(p) => {
1970                    tx_rollback_best_effort(tx, cx).await;
1971                    return Outcome::Panicked(p);
1972                }
1973            };
1974
1975            return match tx.commit(cx).await {
1976                Outcome::Ok(()) => Outcome::Ok(rows),
1977                Outcome::Err(e) => Outcome::Err(e),
1978                Outcome::Cancelled(r) => Outcome::Cancelled(r),
1979                Outcome::Panicked(p) => Outcome::Panicked(p),
1980            };
1981        }
1982
1983        let batches = self.build_batches_with_dialect(conn.dialect());
1984        if batches.is_empty() {
1985            return Outcome::Ok(Vec::new());
1986        }
1987
1988        let mut all_rows = Vec::new();
1989        for (sql, params) in batches {
1990            match conn.query(cx, &sql, &params).await {
1991                Outcome::Ok(mut rows) => all_rows.append(&mut rows),
1992                Outcome::Err(e) => return Outcome::Err(e),
1993                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1994                Outcome::Panicked(p) => return Outcome::Panicked(p),
1995            }
1996        }
1997
1998        Outcome::Ok(all_rows)
1999    }
2000}
2001
2002/// A column-value pair for explicit UPDATE SET operations.
2003#[derive(Debug, Clone)]
2004pub struct SetClause {
2005    column: String,
2006    value: Value,
2007}
2008
2009/// UPDATE query builder.
2010///
2011/// # Example
2012///
2013/// ```ignore
2014/// // Update a model instance (uses primary key for WHERE)
2015/// update!(hero).execute(cx, &conn).await?;
2016///
2017/// // Update with explicit SET
2018/// UpdateBuilder::<Hero>::empty()
2019///     .set("age", 26)
2020///     .set("name", "New Name")
2021///     .filter(Expr::col("id").eq(42))
2022///     .execute(cx, &conn).await?;
2023///
2024/// // Update with RETURNING
2025/// let row = update!(hero).returning().execute_returning(cx, &conn).await?;
2026/// ```
2027#[derive(Debug)]
2028pub struct UpdateBuilder<'a, M: Model> {
2029    model: Option<&'a M>,
2030    where_clause: Option<Where>,
2031    set_fields: Option<Vec<&'static str>>,
2032    explicit_sets: Vec<SetClause>,
2033    returning: bool,
2034}
2035
2036impl<'a, M: Model> UpdateBuilder<'a, M> {
2037    /// Create a new UPDATE builder for the given model instance.
2038    pub fn new(model: &'a M) -> Self {
2039        Self {
2040            model: Some(model),
2041            where_clause: None,
2042            set_fields: None,
2043            explicit_sets: Vec::new(),
2044            returning: false,
2045        }
2046    }
2047
2048    /// Create an empty UPDATE builder for explicit SET operations.
2049    ///
2050    /// Use this when you want to update specific columns without a model instance.
2051    pub fn empty() -> Self {
2052        Self {
2053            model: None,
2054            where_clause: None,
2055            set_fields: None,
2056            explicit_sets: Vec::new(),
2057            returning: false,
2058        }
2059    }
2060
2061    /// Set a column to a specific value.
2062    ///
2063    /// This can be used with or without a model instance.
2064    /// When used with a model, these explicit sets override the model values.
2065    pub fn set<V: Into<Value>>(mut self, column: &str, value: V) -> Self {
2066        self.explicit_sets.push(SetClause {
2067            column: column.to_string(),
2068            value: value.into(),
2069        });
2070        self
2071    }
2072
2073    /// Only update specific fields from the model.
2074    pub fn set_only(mut self, fields: &[&'static str]) -> Self {
2075        self.set_fields = Some(fields.to_vec());
2076        self
2077    }
2078
2079    /// Add a WHERE condition (defaults to primary key match).
2080    pub fn filter(mut self, expr: Expr) -> Self {
2081        self.where_clause = Some(match self.where_clause {
2082            Some(existing) => existing.and(expr),
2083            None => Where::new(expr),
2084        });
2085        self
2086    }
2087
2088    /// Add RETURNING * clause to return the updated row(s).
2089    pub fn returning(mut self) -> Self {
2090        self.returning = true;
2091        self
2092    }
2093
2094    /// Build the UPDATE SQL and parameters with default dialect (Postgres).
2095    pub fn build(&self) -> (String, Vec<Value>) {
2096        self.build_with_dialect(Dialect::default())
2097    }
2098
2099    /// Build the UPDATE SQL and parameters with specific dialect.
2100    pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
2101        let pk = M::PRIMARY_KEY;
2102        let mut params = Vec::new();
2103        let mut set_clauses = Vec::new();
2104
2105        // First, add explicit SET clauses
2106        for set in &self.explicit_sets {
2107            set_clauses.push(format!(
2108                "{} = {}",
2109                set.column,
2110                dialect.placeholder(params.len() + 1)
2111            ));
2112            params.push(set.value.clone());
2113        }
2114
2115        // Then, add model fields if we have a model
2116        if let Some(model) = &self.model {
2117            let row = model.to_row();
2118
2119            // Determine which fields to update
2120            let update_fields: Vec<_> = row
2121                .iter()
2122                .filter(|(name, _)| {
2123                    // Skip primary key fields
2124                    if pk.contains(name) {
2125                        return false;
2126                    }
2127                    // Skip columns that have explicit sets
2128                    if self.explicit_sets.iter().any(|s| s.column == *name) {
2129                        return false;
2130                    }
2131                    // If set_only specified, only include those fields
2132                    if let Some(fields) = &self.set_fields {
2133                        return fields.contains(name);
2134                    }
2135                    true
2136                })
2137                .collect();
2138
2139            for (name, value) in update_fields {
2140                set_clauses.push(format!(
2141                    "{} = {}",
2142                    name,
2143                    dialect.placeholder(params.len() + 1)
2144                ));
2145                params.push(value.clone());
2146            }
2147        }
2148
2149        if set_clauses.is_empty() {
2150            // Nothing to update - return empty SQL
2151            return (String::new(), Vec::new());
2152        }
2153
2154        let mut sql = format!("UPDATE {} SET {}", M::TABLE_NAME, set_clauses.join(", "));
2155
2156        // Add WHERE clause
2157        if let Some(where_clause) = &self.where_clause {
2158            let (where_sql, where_params) = where_clause.build_with_dialect(dialect, params.len());
2159            sql.push_str(" WHERE ");
2160            sql.push_str(&where_sql);
2161            params.extend(where_params);
2162        } else if let Some(model) = &self.model {
2163            // Default to primary key match
2164            let pk_values = model.primary_key_value();
2165            let pk_conditions: Vec<_> = pk
2166                .iter()
2167                .zip(pk_values.iter())
2168                .enumerate()
2169                .map(|(i, (col, _))| {
2170                    format!("{} = {}", col, dialect.placeholder(params.len() + i + 1))
2171                })
2172                .collect();
2173
2174            if !pk_conditions.is_empty() {
2175                sql.push_str(" WHERE ");
2176                sql.push_str(&pk_conditions.join(" AND "));
2177                params.extend(pk_values);
2178            }
2179        }
2180
2181        // Add RETURNING clause if requested
2182        if self.returning {
2183            sql.push_str(" RETURNING *");
2184        }
2185
2186        (sql, params)
2187    }
2188
2189    /// Execute the UPDATE and return rows affected.
2190    ///
2191    /// Joined-table inheritance semantics:
2192    /// - `UpdateBuilder::empty().set(...).filter(...)` routes each `SET` column to parent or child table.
2193    /// - Unqualified ambiguous columns (e.g. shared PK names) are rejected with a clear error.
2194    /// - A single update operation may execute one UPDATE per table inside one transaction.
2195    pub async fn execute<C: Connection>(
2196        self,
2197        cx: &Cx,
2198        conn: &C,
2199    ) -> Outcome<u64, sqlmodel_core::Error> {
2200        if is_joined_inheritance_child::<M>() {
2201            if self.model.is_none() {
2202                if self.explicit_sets.is_empty() {
2203                    return Outcome::Err(sqlmodel_core::Error::Custom(
2204                        "joined-table inheritance explicit update requires at least one SET clause"
2205                            .to_string(),
2206                    ));
2207                }
2208
2209                let dialect = conn.dialect();
2210                let (parent_table, parent_fields) = match joined_parent_meta::<M>() {
2211                    Ok(v) => v,
2212                    Err(e) => return Outcome::Err(e),
2213                };
2214                let (parent_sets, child_sets) = match split_explicit_joined_sets::<M>(
2215                    &self.explicit_sets,
2216                    parent_table,
2217                    parent_fields,
2218                ) {
2219                    Ok(v) => v,
2220                    Err(e) => return Outcome::Err(e),
2221                };
2222
2223                let tx_out = conn.begin(cx).await;
2224                let tx = match tx_out {
2225                    Outcome::Ok(t) => t,
2226                    Outcome::Err(e) => return Outcome::Err(e),
2227                    Outcome::Cancelled(r) => return Outcome::Cancelled(r),
2228                    Outcome::Panicked(p) => return Outcome::Panicked(p),
2229                };
2230
2231                let pk_values = match select_joined_pk_values_in_tx::<_, M>(
2232                    &tx,
2233                    cx,
2234                    dialect,
2235                    self.where_clause.as_ref(),
2236                )
2237                .await
2238                {
2239                    Outcome::Ok(v) => v,
2240                    Outcome::Err(e) => {
2241                        tx_rollback_best_effort(tx, cx).await;
2242                        return Outcome::Err(e);
2243                    }
2244                    Outcome::Cancelled(r) => {
2245                        tx_rollback_best_effort(tx, cx).await;
2246                        return Outcome::Cancelled(r);
2247                    }
2248                    Outcome::Panicked(p) => {
2249                        tx_rollback_best_effort(tx, cx).await;
2250                        return Outcome::Panicked(p);
2251                    }
2252                };
2253
2254                if pk_values.is_empty() {
2255                    return match tx.commit(cx).await {
2256                        Outcome::Ok(()) => Outcome::Ok(0),
2257                        Outcome::Err(e) => Outcome::Err(e),
2258                        Outcome::Cancelled(r) => Outcome::Cancelled(r),
2259                        Outcome::Panicked(p) => Outcome::Panicked(p),
2260                    };
2261                }
2262
2263                let mut total = 0_u64;
2264
2265                if !parent_sets.is_empty() {
2266                    let (parent_sql, parent_params) = build_update_sql_for_table_pk_in(
2267                        dialect,
2268                        parent_table,
2269                        M::PRIMARY_KEY,
2270                        &pk_values,
2271                        &parent_sets,
2272                    );
2273                    if !parent_sql.is_empty() {
2274                        match tx.execute(cx, &parent_sql, &parent_params).await {
2275                            Outcome::Ok(n) => total = total.saturating_add(n),
2276                            Outcome::Err(e) => {
2277                                tx_rollback_best_effort(tx, cx).await;
2278                                return Outcome::Err(e);
2279                            }
2280                            Outcome::Cancelled(r) => {
2281                                tx_rollback_best_effort(tx, cx).await;
2282                                return Outcome::Cancelled(r);
2283                            }
2284                            Outcome::Panicked(p) => {
2285                                tx_rollback_best_effort(tx, cx).await;
2286                                return Outcome::Panicked(p);
2287                            }
2288                        }
2289                    }
2290                }
2291
2292                if !child_sets.is_empty() {
2293                    let (child_sql, child_params) = build_update_sql_for_table_pk_in(
2294                        dialect,
2295                        M::TABLE_NAME,
2296                        M::PRIMARY_KEY,
2297                        &pk_values,
2298                        &child_sets,
2299                    );
2300                    if !child_sql.is_empty() {
2301                        match tx.execute(cx, &child_sql, &child_params).await {
2302                            Outcome::Ok(n) => total = total.saturating_add(n),
2303                            Outcome::Err(e) => {
2304                                tx_rollback_best_effort(tx, cx).await;
2305                                return Outcome::Err(e);
2306                            }
2307                            Outcome::Cancelled(r) => {
2308                                tx_rollback_best_effort(tx, cx).await;
2309                                return Outcome::Cancelled(r);
2310                            }
2311                            Outcome::Panicked(p) => {
2312                                tx_rollback_best_effort(tx, cx).await;
2313                                return Outcome::Panicked(p);
2314                            }
2315                        }
2316                    }
2317                }
2318
2319                return match tx.commit(cx).await {
2320                    Outcome::Ok(()) => Outcome::Ok(total),
2321                    Outcome::Err(e) => Outcome::Err(e),
2322                    Outcome::Cancelled(r) => Outcome::Cancelled(r),
2323                    Outcome::Panicked(p) => Outcome::Panicked(p),
2324                };
2325            }
2326
2327            if self.where_clause.is_some() || !self.explicit_sets.is_empty() {
2328                return Outcome::Err(sqlmodel_core::Error::Custom(
2329                    "joined-table inheritance update with a model supports model-based updates only; use UpdateBuilder::empty().set(...).filter(...) for explicit WHERE/SET"
2330                        .to_string(),
2331                ));
2332            }
2333
2334            let dialect = conn.dialect();
2335            let Some(model) = self.model else {
2336                return Outcome::Err(sqlmodel_core::Error::Custom(
2337                    "update called without model".to_string(),
2338                ));
2339            };
2340            let inh = M::inheritance();
2341            let Some(parent_table) = inh.parent else {
2342                return Outcome::Err(sqlmodel_core::Error::Custom(
2343                    "joined-table inheritance child missing parent table metadata".to_string(),
2344                ));
2345            };
2346            let Some(parent_fields_fn) = inh.parent_fields_fn else {
2347                return Outcome::Err(sqlmodel_core::Error::Custom(
2348                    "joined-table inheritance child missing parent_fields_fn metadata".to_string(),
2349                ));
2350            };
2351            let parent_fields = parent_fields_fn();
2352            let Some(parent_row) = model.joined_parent_row() else {
2353                return Outcome::Err(sqlmodel_core::Error::Custom(
2354                    "joined-table inheritance child missing joined_parent_row() implementation"
2355                        .to_string(),
2356                ));
2357            };
2358
2359            let pk_cols = M::PRIMARY_KEY;
2360            let pk_vals = model.primary_key_value();
2361
2362            let tx_out = conn.begin(cx).await;
2363            let tx = match tx_out {
2364                Outcome::Ok(t) => t,
2365                Outcome::Err(e) => return Outcome::Err(e),
2366                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
2367                Outcome::Panicked(p) => return Outcome::Panicked(p),
2368            };
2369
2370            let mut total = 0_u64;
2371
2372            // Parent update (base table).
2373            let mut parent_sets: Vec<(&'static str, Value)> = Vec::new();
2374            for f in parent_fields {
2375                if f.primary_key || pk_cols.contains(&f.column_name) {
2376                    continue;
2377                }
2378                if let Some((_, v)) = parent_row.iter().find(|(k, _)| *k == f.column_name) {
2379                    parent_sets.push((f.column_name, v.clone()));
2380                }
2381            }
2382            let (parent_sql, parent_params) =
2383                build_update_sql_for_table(dialect, parent_table, pk_cols, &pk_vals, &parent_sets);
2384            if !parent_sql.is_empty() {
2385                match tx.execute(cx, &parent_sql, &parent_params).await {
2386                    Outcome::Ok(n) => total = total.saturating_add(n),
2387                    Outcome::Err(e) => {
2388                        tx_rollback_best_effort(tx, cx).await;
2389                        return Outcome::Err(e);
2390                    }
2391                    Outcome::Cancelled(r) => {
2392                        tx_rollback_best_effort(tx, cx).await;
2393                        return Outcome::Cancelled(r);
2394                    }
2395                    Outcome::Panicked(p) => {
2396                        tx_rollback_best_effort(tx, cx).await;
2397                        return Outcome::Panicked(p);
2398                    }
2399                }
2400            }
2401
2402            // Child update (child table).
2403            let row = model.to_row();
2404            let mut child_sets: Vec<(&'static str, Value)> = Vec::new();
2405            for (name, value) in row {
2406                if pk_cols.contains(&name) {
2407                    continue;
2408                }
2409                if let Some(fields) = &self.set_fields
2410                    && !fields.contains(&name)
2411                {
2412                    continue;
2413                }
2414                child_sets.push((name, value));
2415            }
2416            let (child_sql, child_params) =
2417                build_update_sql_for_table(dialect, M::TABLE_NAME, pk_cols, &pk_vals, &child_sets);
2418            if !child_sql.is_empty() {
2419                match tx.execute(cx, &child_sql, &child_params).await {
2420                    Outcome::Ok(n) => total = total.saturating_add(n),
2421                    Outcome::Err(e) => {
2422                        tx_rollback_best_effort(tx, cx).await;
2423                        return Outcome::Err(e);
2424                    }
2425                    Outcome::Cancelled(r) => {
2426                        tx_rollback_best_effort(tx, cx).await;
2427                        return Outcome::Cancelled(r);
2428                    }
2429                    Outcome::Panicked(p) => {
2430                        tx_rollback_best_effort(tx, cx).await;
2431                        return Outcome::Panicked(p);
2432                    }
2433                }
2434            }
2435
2436            match tx.commit(cx).await {
2437                Outcome::Ok(()) => Outcome::Ok(total),
2438                Outcome::Err(e) => Outcome::Err(e),
2439                Outcome::Cancelled(r) => Outcome::Cancelled(r),
2440                Outcome::Panicked(p) => Outcome::Panicked(p),
2441            }
2442        } else {
2443            let (sql, params) = self.build_with_dialect(conn.dialect());
2444            if sql.is_empty() {
2445                return Outcome::Ok(0);
2446            }
2447            conn.execute(cx, &sql, &params).await
2448        }
2449    }
2450
2451    /// Execute the UPDATE with RETURNING and get the updated rows.
2452    pub async fn execute_returning<C: Connection>(
2453        mut self,
2454        cx: &Cx,
2455        conn: &C,
2456    ) -> Outcome<Vec<Row>, sqlmodel_core::Error> {
2457        self.returning = true;
2458        if is_joined_inheritance_child::<M>() {
2459            if self.model.is_none() {
2460                if self.explicit_sets.is_empty() {
2461                    return Outcome::Err(sqlmodel_core::Error::Custom(
2462                        "joined-table inheritance explicit update_returning requires at least one SET clause"
2463                            .to_string(),
2464                    ));
2465                }
2466
2467                let dialect = conn.dialect();
2468                let (parent_table, parent_fields) = match joined_parent_meta::<M>() {
2469                    Ok(v) => v,
2470                    Err(e) => return Outcome::Err(e),
2471                };
2472                let (parent_sets, child_sets) = match split_explicit_joined_sets::<M>(
2473                    &self.explicit_sets,
2474                    parent_table,
2475                    parent_fields,
2476                ) {
2477                    Ok(v) => v,
2478                    Err(e) => return Outcome::Err(e),
2479                };
2480
2481                let tx_out = conn.begin(cx).await;
2482                let tx = match tx_out {
2483                    Outcome::Ok(t) => t,
2484                    Outcome::Err(e) => return Outcome::Err(e),
2485                    Outcome::Cancelled(r) => return Outcome::Cancelled(r),
2486                    Outcome::Panicked(p) => return Outcome::Panicked(p),
2487                };
2488
2489                let pk_values = match select_joined_pk_values_in_tx::<_, M>(
2490                    &tx,
2491                    cx,
2492                    dialect,
2493                    self.where_clause.as_ref(),
2494                )
2495                .await
2496                {
2497                    Outcome::Ok(v) => v,
2498                    Outcome::Err(e) => {
2499                        tx_rollback_best_effort(tx, cx).await;
2500                        return Outcome::Err(e);
2501                    }
2502                    Outcome::Cancelled(r) => {
2503                        tx_rollback_best_effort(tx, cx).await;
2504                        return Outcome::Cancelled(r);
2505                    }
2506                    Outcome::Panicked(p) => {
2507                        tx_rollback_best_effort(tx, cx).await;
2508                        return Outcome::Panicked(p);
2509                    }
2510                };
2511
2512                if pk_values.is_empty() {
2513                    return match tx.commit(cx).await {
2514                        Outcome::Ok(()) => Outcome::Ok(Vec::new()),
2515                        Outcome::Err(e) => Outcome::Err(e),
2516                        Outcome::Cancelled(r) => Outcome::Cancelled(r),
2517                        Outcome::Panicked(p) => Outcome::Panicked(p),
2518                    };
2519                }
2520
2521                if !parent_sets.is_empty() {
2522                    let (parent_sql, parent_params) = build_update_sql_for_table_pk_in(
2523                        dialect,
2524                        parent_table,
2525                        M::PRIMARY_KEY,
2526                        &pk_values,
2527                        &parent_sets,
2528                    );
2529                    if !parent_sql.is_empty() {
2530                        match tx.execute(cx, &parent_sql, &parent_params).await {
2531                            Outcome::Ok(_) => {}
2532                            Outcome::Err(e) => {
2533                                tx_rollback_best_effort(tx, cx).await;
2534                                return Outcome::Err(e);
2535                            }
2536                            Outcome::Cancelled(r) => {
2537                                tx_rollback_best_effort(tx, cx).await;
2538                                return Outcome::Cancelled(r);
2539                            }
2540                            Outcome::Panicked(p) => {
2541                                tx_rollback_best_effort(tx, cx).await;
2542                                return Outcome::Panicked(p);
2543                            }
2544                        }
2545                    }
2546                }
2547
2548                if !child_sets.is_empty() {
2549                    let (child_sql, child_params) = build_update_sql_for_table_pk_in(
2550                        dialect,
2551                        M::TABLE_NAME,
2552                        M::PRIMARY_KEY,
2553                        &pk_values,
2554                        &child_sets,
2555                    );
2556                    if !child_sql.is_empty() {
2557                        match tx.execute(cx, &child_sql, &child_params).await {
2558                            Outcome::Ok(_) => {}
2559                            Outcome::Err(e) => {
2560                                tx_rollback_best_effort(tx, cx).await;
2561                                return Outcome::Err(e);
2562                            }
2563                            Outcome::Cancelled(r) => {
2564                                tx_rollback_best_effort(tx, cx).await;
2565                                return Outcome::Cancelled(r);
2566                            }
2567                            Outcome::Panicked(p) => {
2568                                tx_rollback_best_effort(tx, cx).await;
2569                                return Outcome::Panicked(p);
2570                            }
2571                        }
2572                    }
2573                }
2574
2575                let (select_sql, select_params) = match build_joined_child_select_sql_by_pk_in::<M>(
2576                    dialect,
2577                    M::PRIMARY_KEY,
2578                    &pk_values,
2579                ) {
2580                    Ok(v) => v,
2581                    Err(e) => {
2582                        tx_rollback_best_effort(tx, cx).await;
2583                        return Outcome::Err(e);
2584                    }
2585                };
2586                let rows = if select_sql.is_empty() {
2587                    Vec::new()
2588                } else {
2589                    match tx.query(cx, &select_sql, &select_params).await {
2590                        Outcome::Ok(rows) => rows,
2591                        Outcome::Err(e) => {
2592                            tx_rollback_best_effort(tx, cx).await;
2593                            return Outcome::Err(e);
2594                        }
2595                        Outcome::Cancelled(r) => {
2596                            tx_rollback_best_effort(tx, cx).await;
2597                            return Outcome::Cancelled(r);
2598                        }
2599                        Outcome::Panicked(p) => {
2600                            tx_rollback_best_effort(tx, cx).await;
2601                            return Outcome::Panicked(p);
2602                        }
2603                    }
2604                };
2605
2606                return match tx.commit(cx).await {
2607                    Outcome::Ok(()) => Outcome::Ok(rows),
2608                    Outcome::Err(e) => Outcome::Err(e),
2609                    Outcome::Cancelled(r) => Outcome::Cancelled(r),
2610                    Outcome::Panicked(p) => Outcome::Panicked(p),
2611                };
2612            }
2613
2614            if self.where_clause.is_some() || !self.explicit_sets.is_empty() {
2615                return Outcome::Err(sqlmodel_core::Error::Custom(
2616                    "joined-table inheritance update_returning with a model supports model-based updates only; use UpdateBuilder::empty().set(...).filter(...) for explicit WHERE/SET"
2617                        .to_string(),
2618                ));
2619            }
2620
2621            let dialect = conn.dialect();
2622            let Some(model) = self.model else {
2623                return Outcome::Err(sqlmodel_core::Error::Custom(
2624                    "update_returning called without model".to_string(),
2625                ));
2626            };
2627            let inh = M::inheritance();
2628            let Some(parent_table) = inh.parent else {
2629                return Outcome::Err(sqlmodel_core::Error::Custom(
2630                    "joined-table inheritance child missing parent table metadata".to_string(),
2631                ));
2632            };
2633            let Some(parent_fields_fn) = inh.parent_fields_fn else {
2634                return Outcome::Err(sqlmodel_core::Error::Custom(
2635                    "joined-table inheritance child missing parent_fields_fn metadata".to_string(),
2636                ));
2637            };
2638            let parent_fields = parent_fields_fn();
2639            let Some(parent_row) = model.joined_parent_row() else {
2640                return Outcome::Err(sqlmodel_core::Error::Custom(
2641                    "joined-table inheritance child missing joined_parent_row() implementation"
2642                        .to_string(),
2643                ));
2644            };
2645
2646            let pk_cols = M::PRIMARY_KEY;
2647            let pk_vals = model.primary_key_value();
2648
2649            let tx_out = conn.begin(cx).await;
2650            let tx = match tx_out {
2651                Outcome::Ok(t) => t,
2652                Outcome::Err(e) => return Outcome::Err(e),
2653                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
2654                Outcome::Panicked(p) => return Outcome::Panicked(p),
2655            };
2656
2657            // Parent update (base table) - execute only.
2658            let mut parent_sets: Vec<(&'static str, Value)> = Vec::new();
2659            for f in parent_fields {
2660                if f.primary_key || pk_cols.contains(&f.column_name) {
2661                    continue;
2662                }
2663                if let Some((_, v)) = parent_row.iter().find(|(k, _)| *k == f.column_name) {
2664                    parent_sets.push((f.column_name, v.clone()));
2665                }
2666            }
2667            let (parent_sql, parent_params) =
2668                build_update_sql_for_table(dialect, parent_table, pk_cols, &pk_vals, &parent_sets);
2669            if !parent_sql.is_empty() {
2670                match tx.execute(cx, &parent_sql, &parent_params).await {
2671                    Outcome::Ok(_) => {}
2672                    Outcome::Err(e) => {
2673                        tx_rollback_best_effort(tx, cx).await;
2674                        return Outcome::Err(e);
2675                    }
2676                    Outcome::Cancelled(r) => {
2677                        tx_rollback_best_effort(tx, cx).await;
2678                        return Outcome::Cancelled(r);
2679                    }
2680                    Outcome::Panicked(p) => {
2681                        tx_rollback_best_effort(tx, cx).await;
2682                        return Outcome::Panicked(p);
2683                    }
2684                }
2685            }
2686
2687            // Child update with RETURNING *.
2688            let row = model.to_row();
2689            let mut child_sets: Vec<(&'static str, Value)> = Vec::new();
2690            for (name, value) in row {
2691                if pk_cols.contains(&name) {
2692                    continue;
2693                }
2694                if let Some(fields) = &self.set_fields
2695                    && !fields.contains(&name)
2696                {
2697                    continue;
2698                }
2699                child_sets.push((name, value));
2700            }
2701            let (mut child_sql, child_params) =
2702                build_update_sql_for_table(dialect, M::TABLE_NAME, pk_cols, &pk_vals, &child_sets);
2703            if child_sql.is_empty() {
2704                tx_rollback_best_effort(tx, cx).await;
2705                return Outcome::Ok(Vec::new());
2706            }
2707            child_sql.push_str(" RETURNING *");
2708
2709            let rows = match tx.query(cx, &child_sql, &child_params).await {
2710                Outcome::Ok(rows) => rows,
2711                Outcome::Err(e) => {
2712                    tx_rollback_best_effort(tx, cx).await;
2713                    return Outcome::Err(e);
2714                }
2715                Outcome::Cancelled(r) => {
2716                    tx_rollback_best_effort(tx, cx).await;
2717                    return Outcome::Cancelled(r);
2718                }
2719                Outcome::Panicked(p) => {
2720                    tx_rollback_best_effort(tx, cx).await;
2721                    return Outcome::Panicked(p);
2722                }
2723            };
2724
2725            match tx.commit(cx).await {
2726                Outcome::Ok(()) => Outcome::Ok(rows),
2727                Outcome::Err(e) => Outcome::Err(e),
2728                Outcome::Cancelled(r) => Outcome::Cancelled(r),
2729                Outcome::Panicked(p) => Outcome::Panicked(p),
2730            }
2731        } else {
2732            let (sql, params) = self.build_with_dialect(conn.dialect());
2733            if sql.is_empty() {
2734                return Outcome::Ok(Vec::new());
2735            }
2736            conn.query(cx, &sql, &params).await
2737        }
2738    }
2739}
2740
2741/// DELETE query builder.
2742///
2743/// # Example
2744///
2745/// ```ignore
2746/// // Delete by filter
2747/// delete!(Hero)
2748///     .filter(Expr::col("age").lt(18))
2749///     .execute(cx, &conn).await?;
2750///
2751/// // Delete a specific model instance
2752/// DeleteBuilder::from_model(&hero)
2753///     .execute(cx, &conn).await?;
2754///
2755/// // Delete with RETURNING
2756/// let rows = delete!(Hero)
2757///     .filter(Expr::col("status").eq("inactive"))
2758///     .returning()
2759///     .execute_returning(cx, &conn).await?;
2760/// ```
2761#[derive(Debug)]
2762pub struct DeleteBuilder<'a, M: Model> {
2763    model: Option<&'a M>,
2764    where_clause: Option<Where>,
2765    returning: bool,
2766    _marker: PhantomData<M>,
2767}
2768
2769impl<'a, M: Model> DeleteBuilder<'a, M> {
2770    /// Create a new DELETE builder for the model type.
2771    pub fn new() -> Self {
2772        Self {
2773            model: None,
2774            where_clause: None,
2775            returning: false,
2776            _marker: PhantomData,
2777        }
2778    }
2779
2780    /// Create a DELETE builder for a specific model instance.
2781    ///
2782    /// This automatically adds a WHERE clause matching the primary key.
2783    pub fn from_model(model: &'a M) -> Self {
2784        Self {
2785            model: Some(model),
2786            where_clause: None,
2787            returning: false,
2788            _marker: PhantomData,
2789        }
2790    }
2791
2792    /// Add a WHERE condition.
2793    pub fn filter(mut self, expr: Expr) -> Self {
2794        self.where_clause = Some(match self.where_clause {
2795            Some(existing) => existing.and(expr),
2796            None => Where::new(expr),
2797        });
2798        self
2799    }
2800
2801    /// Add RETURNING * clause to return the deleted row(s).
2802    pub fn returning(mut self) -> Self {
2803        self.returning = true;
2804        self
2805    }
2806
2807    /// Build the DELETE SQL and parameters with default dialect (Postgres).
2808    pub fn build(&self) -> (String, Vec<Value>) {
2809        self.build_with_dialect(Dialect::default())
2810    }
2811
2812    /// Build the DELETE SQL and parameters with specific dialect.
2813    pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
2814        let mut sql = format!("DELETE FROM {}", M::TABLE_NAME);
2815        let mut params = Vec::new();
2816
2817        if let Some(where_clause) = &self.where_clause {
2818            let (where_sql, where_params) = where_clause.build_with_dialect(dialect, 0);
2819            sql.push_str(" WHERE ");
2820            sql.push_str(&where_sql);
2821            params = where_params;
2822        } else if let Some(model) = &self.model {
2823            // Delete by primary key
2824            let pk = M::PRIMARY_KEY;
2825            let pk_values = model.primary_key_value();
2826            let pk_conditions: Vec<_> = pk
2827                .iter()
2828                .zip(pk_values.iter())
2829                .enumerate()
2830                .map(|(i, (col, _))| format!("{} = {}", col, dialect.placeholder(i + 1)))
2831                .collect();
2832
2833            if !pk_conditions.is_empty() {
2834                sql.push_str(" WHERE ");
2835                sql.push_str(&pk_conditions.join(" AND "));
2836                params.extend(pk_values);
2837            }
2838        }
2839
2840        // Add RETURNING clause if requested
2841        if self.returning {
2842            sql.push_str(" RETURNING *");
2843        }
2844
2845        (sql, params)
2846    }
2847
2848    /// Execute the DELETE and return rows affected.
2849    ///
2850    /// Joined-table inheritance semantics:
2851    /// - Filters select target child primary keys from a base+child join.
2852    /// - Deletion always removes matching child rows and their parent rows in one transaction.
2853    pub async fn execute<C: Connection>(
2854        self,
2855        cx: &Cx,
2856        conn: &C,
2857    ) -> Outcome<u64, sqlmodel_core::Error> {
2858        if is_joined_inheritance_child::<M>() {
2859            let dialect = conn.dialect();
2860            let (parent_table, _parent_fields) = match joined_parent_meta::<M>() {
2861                Ok(v) => v,
2862                Err(e) => return Outcome::Err(e),
2863            };
2864
2865            let tx_out = conn.begin(cx).await;
2866            let tx = match tx_out {
2867                Outcome::Ok(t) => t,
2868                Outcome::Err(e) => return Outcome::Err(e),
2869                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
2870                Outcome::Panicked(p) => return Outcome::Panicked(p),
2871            };
2872
2873            let pk_values = if let Some(where_clause) = self.where_clause.as_ref() {
2874                match select_joined_pk_values_in_tx::<_, M>(&tx, cx, dialect, Some(where_clause))
2875                    .await
2876                {
2877                    Outcome::Ok(v) => v,
2878                    Outcome::Err(e) => {
2879                        tx_rollback_best_effort(tx, cx).await;
2880                        return Outcome::Err(e);
2881                    }
2882                    Outcome::Cancelled(r) => {
2883                        tx_rollback_best_effort(tx, cx).await;
2884                        return Outcome::Cancelled(r);
2885                    }
2886                    Outcome::Panicked(p) => {
2887                        tx_rollback_best_effort(tx, cx).await;
2888                        return Outcome::Panicked(p);
2889                    }
2890                }
2891            } else if let Some(model) = self.model {
2892                vec![model.primary_key_value()]
2893            } else {
2894                // Explicit WHERE omitted: delete all joined-child rows (child + matching parent rows).
2895                match select_joined_pk_values_in_tx::<_, M>(&tx, cx, dialect, None).await {
2896                    Outcome::Ok(v) => v,
2897                    Outcome::Err(e) => {
2898                        tx_rollback_best_effort(tx, cx).await;
2899                        return Outcome::Err(e);
2900                    }
2901                    Outcome::Cancelled(r) => {
2902                        tx_rollback_best_effort(tx, cx).await;
2903                        return Outcome::Cancelled(r);
2904                    }
2905                    Outcome::Panicked(p) => {
2906                        tx_rollback_best_effort(tx, cx).await;
2907                        return Outcome::Panicked(p);
2908                    }
2909                }
2910            };
2911
2912            if pk_values.is_empty() {
2913                return match tx.commit(cx).await {
2914                    Outcome::Ok(()) => Outcome::Ok(0),
2915                    Outcome::Err(e) => Outcome::Err(e),
2916                    Outcome::Cancelled(r) => Outcome::Cancelled(r),
2917                    Outcome::Panicked(p) => Outcome::Panicked(p),
2918                };
2919            }
2920
2921            let (child_sql, child_params) = build_delete_sql_for_table_pk_in(
2922                dialect,
2923                M::TABLE_NAME,
2924                M::PRIMARY_KEY,
2925                &pk_values,
2926            );
2927            let (parent_sql, parent_params) =
2928                build_delete_sql_for_table_pk_in(dialect, parent_table, M::PRIMARY_KEY, &pk_values);
2929
2930            let mut total = 0_u64;
2931
2932            if !child_sql.is_empty() {
2933                match tx.execute(cx, &child_sql, &child_params).await {
2934                    Outcome::Ok(n) => total = total.saturating_add(n),
2935                    Outcome::Err(e) => {
2936                        tx_rollback_best_effort(tx, cx).await;
2937                        return Outcome::Err(e);
2938                    }
2939                    Outcome::Cancelled(r) => {
2940                        tx_rollback_best_effort(tx, cx).await;
2941                        return Outcome::Cancelled(r);
2942                    }
2943                    Outcome::Panicked(p) => {
2944                        tx_rollback_best_effort(tx, cx).await;
2945                        return Outcome::Panicked(p);
2946                    }
2947                }
2948            }
2949
2950            if !parent_sql.is_empty() {
2951                match tx.execute(cx, &parent_sql, &parent_params).await {
2952                    Outcome::Ok(n) => total = total.saturating_add(n),
2953                    Outcome::Err(e) => {
2954                        tx_rollback_best_effort(tx, cx).await;
2955                        return Outcome::Err(e);
2956                    }
2957                    Outcome::Cancelled(r) => {
2958                        tx_rollback_best_effort(tx, cx).await;
2959                        return Outcome::Cancelled(r);
2960                    }
2961                    Outcome::Panicked(p) => {
2962                        tx_rollback_best_effort(tx, cx).await;
2963                        return Outcome::Panicked(p);
2964                    }
2965                }
2966            }
2967
2968            match tx.commit(cx).await {
2969                Outcome::Ok(()) => Outcome::Ok(total),
2970                Outcome::Err(e) => Outcome::Err(e),
2971                Outcome::Cancelled(r) => Outcome::Cancelled(r),
2972                Outcome::Panicked(p) => Outcome::Panicked(p),
2973            }
2974        } else {
2975            let (sql, params) = self.build_with_dialect(conn.dialect());
2976            conn.execute(cx, &sql, &params).await
2977        }
2978    }
2979
2980    /// Execute the DELETE with RETURNING and get the deleted rows.
2981    ///
2982    /// For joined-table inheritance child models, returned rows are projected with both
2983    /// child and parent prefixes (`table__column`) before the delete is applied.
2984    pub async fn execute_returning<C: Connection>(
2985        mut self,
2986        cx: &Cx,
2987        conn: &C,
2988    ) -> Outcome<Vec<Row>, sqlmodel_core::Error> {
2989        self.returning = true;
2990        if is_joined_inheritance_child::<M>() {
2991            let dialect = conn.dialect();
2992            let (parent_table, _parent_fields) = match joined_parent_meta::<M>() {
2993                Ok(v) => v,
2994                Err(e) => return Outcome::Err(e),
2995            };
2996
2997            let tx_out = conn.begin(cx).await;
2998            let tx = match tx_out {
2999                Outcome::Ok(t) => t,
3000                Outcome::Err(e) => return Outcome::Err(e),
3001                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
3002                Outcome::Panicked(p) => return Outcome::Panicked(p),
3003            };
3004
3005            let pk_values = if let Some(where_clause) = self.where_clause.as_ref() {
3006                match select_joined_pk_values_in_tx::<_, M>(&tx, cx, dialect, Some(where_clause))
3007                    .await
3008                {
3009                    Outcome::Ok(v) => v,
3010                    Outcome::Err(e) => {
3011                        tx_rollback_best_effort(tx, cx).await;
3012                        return Outcome::Err(e);
3013                    }
3014                    Outcome::Cancelled(r) => {
3015                        tx_rollback_best_effort(tx, cx).await;
3016                        return Outcome::Cancelled(r);
3017                    }
3018                    Outcome::Panicked(p) => {
3019                        tx_rollback_best_effort(tx, cx).await;
3020                        return Outcome::Panicked(p);
3021                    }
3022                }
3023            } else if let Some(model) = self.model {
3024                vec![model.primary_key_value()]
3025            } else {
3026                match select_joined_pk_values_in_tx::<_, M>(&tx, cx, dialect, None).await {
3027                    Outcome::Ok(v) => v,
3028                    Outcome::Err(e) => {
3029                        tx_rollback_best_effort(tx, cx).await;
3030                        return Outcome::Err(e);
3031                    }
3032                    Outcome::Cancelled(r) => {
3033                        tx_rollback_best_effort(tx, cx).await;
3034                        return Outcome::Cancelled(r);
3035                    }
3036                    Outcome::Panicked(p) => {
3037                        tx_rollback_best_effort(tx, cx).await;
3038                        return Outcome::Panicked(p);
3039                    }
3040                }
3041            };
3042
3043            if pk_values.is_empty() {
3044                return match tx.commit(cx).await {
3045                    Outcome::Ok(()) => Outcome::Ok(Vec::new()),
3046                    Outcome::Err(e) => Outcome::Err(e),
3047                    Outcome::Cancelled(r) => Outcome::Cancelled(r),
3048                    Outcome::Panicked(p) => Outcome::Panicked(p),
3049                };
3050            }
3051
3052            let (select_sql, select_params) = match build_joined_child_select_sql_by_pk_in::<M>(
3053                dialect,
3054                M::PRIMARY_KEY,
3055                &pk_values,
3056            ) {
3057                Ok(v) => v,
3058                Err(e) => {
3059                    tx_rollback_best_effort(tx, cx).await;
3060                    return Outcome::Err(e);
3061                }
3062            };
3063            let rows = if select_sql.is_empty() {
3064                Vec::new()
3065            } else {
3066                match tx.query(cx, &select_sql, &select_params).await {
3067                    Outcome::Ok(rows) => rows,
3068                    Outcome::Err(e) => {
3069                        tx_rollback_best_effort(tx, cx).await;
3070                        return Outcome::Err(e);
3071                    }
3072                    Outcome::Cancelled(r) => {
3073                        tx_rollback_best_effort(tx, cx).await;
3074                        return Outcome::Cancelled(r);
3075                    }
3076                    Outcome::Panicked(p) => {
3077                        tx_rollback_best_effort(tx, cx).await;
3078                        return Outcome::Panicked(p);
3079                    }
3080                }
3081            };
3082
3083            let (child_sql, child_params) = build_delete_sql_for_table_pk_in(
3084                dialect,
3085                M::TABLE_NAME,
3086                M::PRIMARY_KEY,
3087                &pk_values,
3088            );
3089            let (parent_sql, parent_params) =
3090                build_delete_sql_for_table_pk_in(dialect, parent_table, M::PRIMARY_KEY, &pk_values);
3091
3092            if !child_sql.is_empty() {
3093                match tx.execute(cx, &child_sql, &child_params).await {
3094                    Outcome::Ok(_) => {}
3095                    Outcome::Err(e) => {
3096                        tx_rollback_best_effort(tx, cx).await;
3097                        return Outcome::Err(e);
3098                    }
3099                    Outcome::Cancelled(r) => {
3100                        tx_rollback_best_effort(tx, cx).await;
3101                        return Outcome::Cancelled(r);
3102                    }
3103                    Outcome::Panicked(p) => {
3104                        tx_rollback_best_effort(tx, cx).await;
3105                        return Outcome::Panicked(p);
3106                    }
3107                }
3108            }
3109
3110            if !parent_sql.is_empty() {
3111                match tx.execute(cx, &parent_sql, &parent_params).await {
3112                    Outcome::Ok(_) => {}
3113                    Outcome::Err(e) => {
3114                        tx_rollback_best_effort(tx, cx).await;
3115                        return Outcome::Err(e);
3116                    }
3117                    Outcome::Cancelled(r) => {
3118                        tx_rollback_best_effort(tx, cx).await;
3119                        return Outcome::Cancelled(r);
3120                    }
3121                    Outcome::Panicked(p) => {
3122                        tx_rollback_best_effort(tx, cx).await;
3123                        return Outcome::Panicked(p);
3124                    }
3125                }
3126            }
3127
3128            return match tx.commit(cx).await {
3129                Outcome::Ok(()) => Outcome::Ok(rows),
3130                Outcome::Err(e) => Outcome::Err(e),
3131                Outcome::Cancelled(r) => Outcome::Cancelled(r),
3132                Outcome::Panicked(p) => Outcome::Panicked(p),
3133            };
3134        }
3135        let (sql, params) = self.build_with_dialect(conn.dialect());
3136        conn.query(cx, &sql, &params).await
3137    }
3138}
3139
3140impl<M: Model> Default for DeleteBuilder<'_, M> {
3141    fn default() -> Self {
3142        Self::new()
3143    }
3144}
3145
3146/// Query builder for raw SQL with type-safe parameter binding.
3147#[derive(Debug)]
3148pub struct QueryBuilder {
3149    sql: String,
3150    params: Vec<Value>,
3151}
3152
3153impl QueryBuilder {
3154    /// Create a new query builder with the given SQL.
3155    pub fn new(sql: impl Into<String>) -> Self {
3156        Self {
3157            sql: sql.into(),
3158            params: Vec::new(),
3159        }
3160    }
3161
3162    /// Bind a parameter value.
3163    pub fn bind(mut self, value: impl Into<Value>) -> Self {
3164        self.params.push(value.into());
3165        self
3166    }
3167
3168    /// Bind multiple parameter values.
3169    pub fn bind_all(mut self, values: impl IntoIterator<Item = Value>) -> Self {
3170        self.params.extend(values);
3171        self
3172    }
3173
3174    /// Get the SQL and parameters.
3175    pub fn build(self) -> (String, Vec<Value>) {
3176        (self.sql, self.params)
3177    }
3178}
3179
3180#[cfg(test)]
3181mod tests {
3182    use super::*;
3183    use crate::expr::Dialect;
3184    use sqlmodel_core::field::FieldInfo;
3185    use sqlmodel_core::types::SqlType;
3186
3187    // Mock model for testing
3188    struct TestHero {
3189        id: Option<i64>,
3190        name: String,
3191        age: i32,
3192    }
3193
3194    impl Model for TestHero {
3195        const TABLE_NAME: &'static str = "heroes";
3196        const PRIMARY_KEY: &'static [&'static str] = &["id"];
3197
3198        fn fields() -> &'static [FieldInfo] {
3199            static FIELDS: &[FieldInfo] = &[
3200                FieldInfo::new("id", "id", SqlType::BigInt)
3201                    .primary_key(true)
3202                    .auto_increment(true)
3203                    .nullable(true),
3204                FieldInfo::new("name", "name", SqlType::Text),
3205                FieldInfo::new("age", "age", SqlType::Integer),
3206            ];
3207            FIELDS
3208        }
3209
3210        fn to_row(&self) -> Vec<(&'static str, Value)> {
3211            vec![
3212                ("id", self.id.map_or(Value::Null, Value::BigInt)),
3213                ("name", Value::Text(self.name.clone())),
3214                ("age", Value::Int(self.age)),
3215            ]
3216        }
3217
3218        fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
3219            Err(sqlmodel_core::Error::Custom(
3220                "from_row not used in tests".to_string(),
3221            ))
3222        }
3223
3224        fn primary_key_value(&self) -> Vec<Value> {
3225            vec![self.id.map_or(Value::Null, Value::BigInt)]
3226        }
3227
3228        fn is_new(&self) -> bool {
3229            self.id.is_none()
3230        }
3231    }
3232
3233    struct TestOnlyId {
3234        id: Option<i64>,
3235    }
3236
3237    impl Model for TestOnlyId {
3238        const TABLE_NAME: &'static str = "only_ids";
3239        const PRIMARY_KEY: &'static [&'static str] = &["id"];
3240
3241        fn fields() -> &'static [FieldInfo] {
3242            static FIELDS: &[FieldInfo] = &[FieldInfo::new("id", "id", SqlType::BigInt)
3243                .primary_key(true)
3244                .auto_increment(true)
3245                .nullable(true)];
3246            FIELDS
3247        }
3248
3249        fn to_row(&self) -> Vec<(&'static str, Value)> {
3250            vec![("id", self.id.map_or(Value::Null, Value::BigInt))]
3251        }
3252
3253        fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
3254            Err(sqlmodel_core::Error::Custom(
3255                "from_row not used in tests".to_string(),
3256            ))
3257        }
3258
3259        fn primary_key_value(&self) -> Vec<Value> {
3260            vec![self.id.map_or(Value::Null, Value::BigInt)]
3261        }
3262
3263        fn is_new(&self) -> bool {
3264            self.id.is_none()
3265        }
3266    }
3267
3268    #[test]
3269    fn test_insert_basic() {
3270        let hero = TestHero {
3271            id: None,
3272            name: "Spider-Man".to_string(),
3273            age: 25,
3274        };
3275        let (sql, params) = InsertBuilder::new(&hero).build();
3276
3277        // Auto-increment column with None gets DEFAULT, other columns get placeholders
3278        assert_eq!(
3279            sql,
3280            "INSERT INTO heroes (id, name, age) VALUES (DEFAULT, $1, $2)"
3281        );
3282        assert_eq!(params.len(), 2);
3283    }
3284
3285    #[test]
3286    fn test_insert_returning() {
3287        let hero = TestHero {
3288            id: None,
3289            name: "Spider-Man".to_string(),
3290            age: 25,
3291        };
3292        let (sql, _) = InsertBuilder::new(&hero).returning().build();
3293
3294        assert!(sql.ends_with(" RETURNING *"));
3295    }
3296
3297    #[test]
3298    fn test_insert_on_conflict_do_nothing() {
3299        let hero = TestHero {
3300            id: None,
3301            name: "Spider-Man".to_string(),
3302            age: 25,
3303        };
3304        let (sql, _) = InsertBuilder::new(&hero).on_conflict_do_nothing().build();
3305
3306        assert!(sql.contains("ON CONFLICT DO NOTHING"));
3307    }
3308
3309    #[test]
3310    fn test_insert_on_conflict_do_update() {
3311        let hero = TestHero {
3312            id: None,
3313            name: "Spider-Man".to_string(),
3314            age: 25,
3315        };
3316        let (sql, _) = InsertBuilder::new(&hero)
3317            .on_conflict_do_update(&["name", "age"])
3318            .build();
3319
3320        assert!(sql.contains("ON CONFLICT (id) DO UPDATE SET"));
3321        assert!(sql.contains("name = EXCLUDED.name"));
3322        assert!(sql.contains("age = EXCLUDED.age"));
3323    }
3324
3325    #[test]
3326    fn test_insert_mysql_on_conflict_do_nothing() {
3327        let hero = TestHero {
3328            id: None,
3329            name: "Spider-Man".to_string(),
3330            age: 25,
3331        };
3332        let (sql, _) = InsertBuilder::new(&hero)
3333            .on_conflict_do_nothing()
3334            .build_with_dialect(Dialect::Mysql);
3335
3336        assert!(sql.starts_with("INSERT IGNORE INTO heroes"));
3337        assert!(!sql.contains("ON CONFLICT"));
3338    }
3339
3340    #[test]
3341    fn test_insert_mysql_on_conflict_do_update() {
3342        let hero = TestHero {
3343            id: None,
3344            name: "Spider-Man".to_string(),
3345            age: 25,
3346        };
3347        let (sql, _) = InsertBuilder::new(&hero)
3348            .on_conflict_do_update(&["name", "age"])
3349            .build_with_dialect(Dialect::Mysql);
3350
3351        assert!(sql.contains("ON DUPLICATE KEY UPDATE"));
3352        assert!(sql.contains("name = VALUES(name)"));
3353        assert!(sql.contains("age = VALUES(age)"));
3354        assert!(!sql.contains("ON CONFLICT"));
3355    }
3356
3357    #[test]
3358    fn test_insert_many_mysql_on_conflict_do_update() {
3359        let heroes = vec![
3360            TestHero {
3361                id: None,
3362                name: "Spider-Man".to_string(),
3363                age: 25,
3364            },
3365            TestHero {
3366                id: None,
3367                name: "Iron Man".to_string(),
3368                age: 45,
3369            },
3370        ];
3371        let (sql, params) = InsertManyBuilder::new(&heroes)
3372            .on_conflict_do_update(&["name"])
3373            .build_with_dialect(Dialect::Mysql);
3374
3375        assert!(sql.contains("ON DUPLICATE KEY UPDATE"));
3376        assert!(sql.contains("name = VALUES(name)"));
3377        assert!(!sql.contains("ON CONFLICT"));
3378        assert_eq!(params.len(), 4);
3379    }
3380
3381    #[test]
3382    fn test_insert_many() {
3383        let heroes = vec![
3384            TestHero {
3385                id: None,
3386                name: "Spider-Man".to_string(),
3387                age: 25,
3388            },
3389            TestHero {
3390                id: None,
3391                name: "Iron Man".to_string(),
3392                age: 45,
3393            },
3394        ];
3395        let (sql, params) = InsertManyBuilder::new(&heroes).build();
3396
3397        // Auto-increment columns with None get DEFAULT, other columns get placeholders
3398        assert!(sql.starts_with("INSERT INTO heroes (id, name, age) VALUES"));
3399        assert!(sql.contains("(DEFAULT, $1, $2), (DEFAULT, $3, $4)"));
3400        assert_eq!(params.len(), 4);
3401    }
3402
3403    #[test]
3404    fn test_insert_sqlite_omits_default_columns() {
3405        let hero = TestHero {
3406            id: None,
3407            name: "Spider-Man".to_string(),
3408            age: 25,
3409        };
3410        let (sql, params) = InsertBuilder::new(&hero).build_with_dialect(Dialect::Sqlite);
3411
3412        assert_eq!(sql, "INSERT INTO heroes (name, age) VALUES (?1, ?2)");
3413        assert_eq!(params.len(), 2);
3414    }
3415
3416    #[test]
3417    fn test_insert_sqlite_default_values_only() {
3418        let model = TestOnlyId { id: None };
3419        let (sql, params) = InsertBuilder::new(&model).build_with_dialect(Dialect::Sqlite);
3420
3421        assert_eq!(sql, "INSERT INTO only_ids DEFAULT VALUES");
3422        assert!(params.is_empty());
3423    }
3424
3425    #[test]
3426    fn test_insert_many_sqlite_omits_auto_increment() {
3427        let heroes = vec![
3428            TestHero {
3429                id: None,
3430                name: "Spider-Man".to_string(),
3431                age: 25,
3432            },
3433            TestHero {
3434                id: None,
3435                name: "Iron Man".to_string(),
3436                age: 45,
3437            },
3438        ];
3439        let batches = InsertManyBuilder::new(&heroes).build_batches_with_dialect(Dialect::Sqlite);
3440
3441        assert_eq!(batches.len(), 1);
3442        let (sql, params) = &batches[0];
3443        assert!(sql.starts_with("INSERT INTO heroes (name, age) VALUES"));
3444        assert!(sql.contains("(?1, ?2), (?3, ?4)"));
3445        assert_eq!(params.len(), 4);
3446    }
3447
3448    #[test]
3449    fn test_insert_many_sqlite_mixed_defaults_split() {
3450        let heroes = vec![
3451            TestHero {
3452                id: Some(1),
3453                name: "Spider-Man".to_string(),
3454                age: 25,
3455            },
3456            TestHero {
3457                id: None,
3458                name: "Iron Man".to_string(),
3459                age: 45,
3460            },
3461        ];
3462        let batches = InsertManyBuilder::new(&heroes).build_batches_with_dialect(Dialect::Sqlite);
3463
3464        assert_eq!(batches.len(), 2);
3465        assert_eq!(
3466            batches[0].0,
3467            "INSERT INTO heroes (id, name, age) VALUES (?1, ?2, ?3)"
3468        );
3469        assert_eq!(
3470            batches[1].0,
3471            "INSERT INTO heroes (name, age) VALUES (?1, ?2)"
3472        );
3473        assert_eq!(batches[0].1.len(), 3);
3474        assert_eq!(batches[1].1.len(), 2);
3475    }
3476
3477    #[test]
3478    fn test_insert_many_sqlite_default_values_only() {
3479        let rows = vec![TestOnlyId { id: None }, TestOnlyId { id: None }];
3480        let batches = InsertManyBuilder::new(&rows).build_batches_with_dialect(Dialect::Sqlite);
3481
3482        assert_eq!(batches.len(), 2);
3483        assert_eq!(batches[0].0, "INSERT INTO only_ids DEFAULT VALUES");
3484        assert_eq!(batches[1].0, "INSERT INTO only_ids DEFAULT VALUES");
3485        assert!(batches[0].1.is_empty());
3486        assert!(batches[1].1.is_empty());
3487    }
3488
3489    #[test]
3490    fn test_update_basic() {
3491        let hero = TestHero {
3492            id: Some(1),
3493            name: "Spider-Man".to_string(),
3494            age: 26,
3495        };
3496        let (sql, params) = UpdateBuilder::new(&hero).build();
3497
3498        assert!(sql.starts_with("UPDATE heroes SET"));
3499        assert!(sql.contains("WHERE id = "));
3500        assert!(params.len() >= 2); // At least name, age, and id
3501    }
3502
3503    #[test]
3504    fn test_update_explicit_set() {
3505        let (sql, params) = UpdateBuilder::<TestHero>::empty()
3506            .set("age", 30)
3507            .filter(Expr::col("id").eq(1))
3508            .build_with_dialect(Dialect::Postgres);
3509
3510        assert_eq!(sql, "UPDATE heroes SET age = $1 WHERE \"id\" = $2");
3511        assert_eq!(params.len(), 2);
3512    }
3513
3514    #[test]
3515    fn test_update_returning() {
3516        let hero = TestHero {
3517            id: Some(1),
3518            name: "Spider-Man".to_string(),
3519            age: 26,
3520        };
3521        let (sql, _) = UpdateBuilder::new(&hero).returning().build();
3522
3523        assert!(sql.ends_with(" RETURNING *"));
3524    }
3525
3526    #[test]
3527    fn test_delete_basic() {
3528        let (sql, _) = DeleteBuilder::<TestHero>::new()
3529            .filter(Expr::col("age").lt(18))
3530            .build_with_dialect(Dialect::Postgres);
3531
3532        assert_eq!(sql, "DELETE FROM heroes WHERE \"age\" < $1");
3533    }
3534
3535    #[test]
3536    fn test_delete_from_model() {
3537        let hero = TestHero {
3538            id: Some(42),
3539            name: "Spider-Man".to_string(),
3540            age: 25,
3541        };
3542        let (sql, params) = DeleteBuilder::from_model(&hero).build();
3543
3544        assert!(sql.contains("WHERE id = $1"));
3545        assert_eq!(params.len(), 1);
3546    }
3547
3548    #[test]
3549    fn test_delete_returning() {
3550        let (sql, _) = DeleteBuilder::<TestHero>::new()
3551            .filter(Expr::col("status").eq("inactive"))
3552            .returning()
3553            .build_with_dialect(Dialect::Postgres);
3554
3555        assert!(sql.ends_with(" RETURNING *"));
3556    }
3557
3558    #[test]
3559    fn test_dialect_sqlite() {
3560        let hero = TestHero {
3561            id: None,
3562            name: "Spider-Man".to_string(),
3563            age: 25,
3564        };
3565        let (sql, _) = InsertBuilder::new(&hero).build_with_dialect(Dialect::Sqlite);
3566
3567        assert!(sql.contains("?1"));
3568        assert!(sql.contains("?2"));
3569    }
3570
3571    #[test]
3572    fn test_dialect_mysql() {
3573        let hero = TestHero {
3574            id: None,
3575            name: "Spider-Man".to_string(),
3576            age: 25,
3577        };
3578        let (sql, _) = InsertBuilder::new(&hero).build_with_dialect(Dialect::Mysql);
3579
3580        // MySQL uses ? without numbers
3581        assert!(sql.contains('?'));
3582        assert!(!sql.contains("$1"));
3583    }
3584}