diff --git a/core/lib.rs b/core/lib.rs index ee55c34ca..43e07b609 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -1100,6 +1100,7 @@ pub struct Connection { busy_timeout: RwLock, /// Whether this is an internal connection used for MVCC bootstrap is_mvcc_bootstrap_connection: AtomicBool, + /// Whether pragma foreign_keys=ON for this connection fk_pragma: AtomicBool, } diff --git a/core/schema.rs b/core/schema.rs index 6619aaaa2..29a805294 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -846,28 +846,18 @@ impl Schema { Ok(()) } - pub fn incoming_fks_to(&self, table_name: &str) -> Vec { + /// Compute all resolved FKs *referencing* `table_name` (arg: `table_name` is the parent). + /// Each item contains the child table, normalized columns/positions, and the parent lookup + /// strategy (rowid vs. UNIQUE index or PK). + pub fn resolved_fks_referencing(&self, table_name: &str) -> Vec { let target = normalize_ident(table_name); - let mut out = vec![]; + let mut out = Vec::with_capacity(4); // arbitrary estimate let parent_tbl = self .get_btree_table(&target) - .expect("incoming_fks_to: parent table must exist"); + .expect("parent table must exist"); // Precompute helper to find parent unique index, if it's not the rowid let find_parent_unique = |cols: &Vec| -> Option> { - // If matches PK exactly, we don't need a secondary index probe - let matches_pk = !parent_tbl.primary_key_columns.is_empty() - && parent_tbl.primary_key_columns.len() == cols.len() - && parent_tbl - .primary_key_columns - .iter() - .zip(cols.iter()) - .all(|((n, _ord), c)| n.eq_ignore_ascii_case(c)); - - if matches_pk { - return None; - } - self.get_indices(&parent_tbl.name) .find(|idx| { idx.unique @@ -887,16 +877,12 @@ impl Schema { }; for fk in &child.foreign_keys { - if normalize_ident(&fk.parent_table) != target { + if fk.parent_table != target { continue; } // Resolve + normalize columns - let child_cols: Vec = fk - .child_columns - .iter() - .map(|c| normalize_ident(c)) - .collect(); + let child_cols: Vec = fk.child_columns.clone(); // If no explicit parent columns were given, they were validated in add_btree_table() // to match the parent's PK. We resolve them the same way here. @@ -904,25 +890,21 @@ impl Schema { parent_tbl .primary_key_columns .iter() - .map(|(n, _)| normalize_ident(n)) + .map(|(col, _)| col) + .cloned() .collect() } else { - fk.parent_columns - .iter() - .map(|c| normalize_ident(c)) - .collect() + fk.parent_columns.clone() }; // Child positions let child_pos: Vec = child_cols .iter() .map(|cname| { - child.get_column(cname).map(|(i, _)| i).unwrap_or_else(|| { - panic!( - "incoming_fks_to: child col {}.{} missing", - child.name, cname - ) - }) + child + .get_column(cname) + .map(|(i, _)| i) + .unwrap_or_else(|| panic!("child col {}.{} missing", child.name, cname)) }) .collect(); @@ -941,10 +923,7 @@ impl Schema { } }) .unwrap_or_else(|| { - panic!( - "incoming_fks_to: parent col {}.{cname} missing", - parent_tbl.name - ) + panic!("parent col {}.{cname} missing", parent_tbl.name) }) }) .collect(); @@ -983,7 +962,8 @@ impl Schema { out } - pub fn outgoing_fks_of(&self, child_table: &str) -> Vec { + /// Compute all resolved FKs *declared by* `child_table` + pub fn resolved_fks_for_child(&self, child_table: &str) -> Vec { let child_name = normalize_ident(child_table); let Some(child) = self.get_btree_table(&child_name) else { return vec![]; @@ -992,16 +972,6 @@ impl Schema { // Helper to find the UNIQUE/index on the parent that matches the resolved parent cols let find_parent_unique = |parent_tbl: &BTreeTable, cols: &Vec| -> Option> { - let matches_pk = !parent_tbl.primary_key_columns.is_empty() - && parent_tbl.primary_key_columns.len() == cols.len() - && parent_tbl - .primary_key_columns - .iter() - .zip(cols.iter()) - .all(|((n, _), c)| n.eq_ignore_ascii_case(c)); - if matches_pk { - return None; - } self.get_indices(&parent_tbl.name) .find(|idx| { idx.unique @@ -1015,14 +985,14 @@ impl Schema { .cloned() }; - let mut out = Vec::new(); + let mut out = Vec::with_capacity(child.foreign_keys.len()); for fk in &child.foreign_keys { - let parent_name = normalize_ident(&fk.parent_table); - let Some(parent_tbl) = self.get_btree_table(&parent_name) else { + let parent_name = &fk.parent_table; + let Some(parent_tbl) = self.get_btree_table(parent_name) else { continue; }; - // Normalize columns (same rules you used in validation) + // Normalize columns let child_cols: Vec = fk .child_columns .iter() @@ -1045,7 +1015,6 @@ impl Schema { .collect() }; - // Positions let child_pos: Vec = child_cols .iter() .map(|c| child.get_column(c).expect("child col missing").0) @@ -1061,7 +1030,6 @@ impl Schema { }) .collect(); - // Parent uses rowid? let parent_uses_rowid = parent_cols.len() == 1 && { let c = parent_cols[0].as_str(); c.eq_ignore_ascii_case("rowid") @@ -1094,7 +1062,8 @@ impl Schema { out } - pub fn any_incoming_fk_to(&self, table_name: &str) -> bool { + /// Returns if any table declares a FOREIGN KEY whose parent is `table_name`. + pub fn any_resolved_fks_referencing(&self, table_name: &str) -> bool { self.tables.values().any(|t| { let Some(bt) = t.btree() else { return false; @@ -1105,36 +1074,12 @@ impl Schema { }) } - /// Returns if this table declares any outgoing FKs (is a child of some parent) + /// Returns true if `table_name` declares any FOREIGN KEYs pub fn has_child_fks(&self, table_name: &str) -> bool { self.get_table(table_name) .and_then(|t| t.btree()) .is_some_and(|t| !t.foreign_keys.is_empty()) } - - /// Return the *declared* (unresolved) FKs for a table. Callers that need - /// positions/rowid/unique info should use `incoming_fks_to` instead. - pub fn get_fks_for_table(&self, table_name: &str) -> Vec> { - self.get_table(table_name) - .and_then(|t| t.btree()) - .map(|t| t.foreign_keys.clone()) - .unwrap_or_default() - } - - /// Return pairs of (child_table_name, FK) for FKs that reference `parent_table` - pub fn get_referencing_fks(&self, parent_table: &str) -> Vec<(String, Arc)> { - let mut refs = Vec::new(); - for table in self.tables.values() { - if let Table::BTree(btree) = table.deref() { - for fk in &btree.foreign_keys { - if fk.parent_table == parent_table { - refs.push((btree.name.as_str().to_string(), fk.clone())); - } - } - } - } - refs - } } impl Clone for Schema { @@ -1524,7 +1469,6 @@ pub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> R .iter() .map(|ic| normalize_ident(ic.col_name.as_str())) .collect(); - // derive parent columns: explicit or default to parent PK let parent_table = normalize_ident(clause.tbl_name.as_str()); let parent_columns: Vec = clause @@ -1533,8 +1477,8 @@ pub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> R .map(|ic| normalize_ident(ic.col_name.as_str())) .collect(); - // arity check - if child_columns.len() != parent_columns.len() { + // Only check arity if parent columns were explicitly listed + if !parent_columns.is_empty() && child_columns.len() != parent_columns.len() { crate::bail_parse_error!( "foreign key on \"{}\" has {} child column(s) but {} parent column(s)", tbl_name, @@ -1568,17 +1512,6 @@ pub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> R } }) .unwrap_or(RefAct::NoAction), - on_insert: clause - .args - .iter() - .find_map(|a| { - if let ast::RefArg::OnInsert(x) = a { - Some(*x) - } else { - None - } - }) - .unwrap_or(RefAct::NoAction), on_update: clause .args .iter() @@ -1601,7 +1534,7 @@ pub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> R constraints, } in columns { - let name = col_name.as_str().to_string(); + let name = normalize_ident(col_name.as_str()); // Regular sqlite tables have an integer rowid that uniquely identifies a row. // Even if you create a table with a column e.g. 'id INT PRIMARY KEY', there will still // be a separate hidden rowid, and the 'id' column will have a separate index built for it. @@ -1684,11 +1617,11 @@ pub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> R defer_clause, } => { let fk = ForeignKey { - parent_table: clause.tbl_name.to_string(), + parent_table: normalize_ident(clause.tbl_name.as_str()), parent_columns: clause .columns .iter() - .map(|c| c.col_name.as_str().to_string()) + .map(|c| normalize_ident(c.col_name.as_str())) .collect(), on_delete: clause .args @@ -1701,17 +1634,6 @@ pub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> R } }) .unwrap_or(RefAct::NoAction), - on_insert: clause - .args - .iter() - .find_map(|arg| { - if let ast::RefArg::OnInsert(act) = arg { - Some(*act) - } else { - None - } - }) - .unwrap_or(RefAct::NoAction), on_update: clause .args .iter() @@ -1724,7 +1646,16 @@ pub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> R }) .unwrap_or(RefAct::NoAction), child_columns: vec![name.clone()], - deferred: defer_clause.is_some(), + deferred: match defer_clause { + Some(d) => { + d.deferrable + && matches!( + d.init_deferred, + Some(InitDeferredPred::InitiallyDeferred) + ) + } + None => false, + }, }; foreign_keys.push(Arc::new(fk)); } @@ -1742,7 +1673,7 @@ pub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> R } cols.push(Column { - name: Some(normalize_ident(&name)), + name: Some(name), ty, ty_str, primary_key, @@ -1875,7 +1806,6 @@ pub struct ForeignKey { pub parent_columns: Vec, pub on_delete: RefAct, pub on_update: RefAct, - pub on_insert: RefAct, /// DEFERRABLE INITIALLY DEFERRED pub deferred: bool, } diff --git a/core/translate/emitter.rs b/core/translate/emitter.rs index f569743be..3a1d1d017 100644 --- a/core/translate/emitter.rs +++ b/core/translate/emitter.rs @@ -440,7 +440,7 @@ fn emit_program_for_delete( .unwrap() .table .get_name(); - resolver.schema.any_incoming_fk_to(table_name) + resolver.schema.any_resolved_fks_referencing(table_name) }; // Open FK scope for the whole statement if has_parent_fks { @@ -542,7 +542,10 @@ fn emit_delete_insns( if connection.foreign_keys_enabled() && unsafe { &*table_reference }.btree().is_some() - && t_ctx.resolver.schema.any_incoming_fk_to(table_name) + && t_ctx + .resolver + .schema + .any_resolved_fks_referencing(table_name) { emit_fk_parent_existence_checks( program, @@ -1047,7 +1050,7 @@ pub fn emit_fk_parent_existence_checks( .get_btree_table(parent_table_name) .ok_or_else(|| crate::LimboError::InternalError("parent not btree".into()))?; - for fk_ref in resolver.schema.incoming_fks_to(parent_table_name) { + for fk_ref in resolver.schema.resolved_fks_referencing(parent_table_name) { // Resolve parent key columns let parent_cols: Vec = if fk_ref.fk.parent_columns.is_empty() { parent_bt @@ -1295,8 +1298,8 @@ fn emit_program_for_update( .unwrap() .table .get_name(); - let has_child_fks = fk_enabled && !resolver.schema.get_fks_for_table(table_name).is_empty(); - let has_parent_fks = fk_enabled && resolver.schema.any_incoming_fk_to(table_name); + let has_child_fks = fk_enabled && resolver.schema.has_child_fks(table_name); + let has_parent_fks = fk_enabled && resolver.schema.any_resolved_fks_referencing(table_name); // statement-level FK scope open if has_child_fks || has_parent_fks { program.emit_insn(Insn::FkCounter { @@ -1695,12 +1698,16 @@ fn emit_update_insns( // We only need to do work if the referenced key (the parent key) might change. // we detect that by comparing OLD vs NEW primary key representation // then run parent FK checks only when it actually changes. - if t_ctx.resolver.schema.any_incoming_fk_to(table_name) { + if t_ctx + .resolver + .schema + .any_resolved_fks_referencing(table_name) + { let updated_parent_positions: HashSet = plan.set_clauses.iter().map(|(i, _)| *i).collect(); // If no incoming FK’s parent key can be affected by these updates, skip the whole parent-FK block. - let incoming = t_ctx.resolver.schema.incoming_fks_to(table_name); + let incoming = t_ctx.resolver.schema.resolved_fks_referencing(table_name); let parent_tbl = &table_btree; let maybe_affects_parent_key = incoming .iter() @@ -2338,7 +2345,7 @@ pub fn emit_fk_child_existence_checks( if_zero: true, }); - for fk_ref in resolver.schema.outgoing_fks_of(table_name) { + for fk_ref in resolver.schema.resolved_fks_for_child(table_name) { // Skip when the child key is untouched (including rowid-alias special case) if !fk_ref.child_key_changed(updated_cols, table) { continue; diff --git a/core/translate/insert.rs b/core/translate/insert.rs index 01d1355a1..78c5dee5c 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -83,13 +83,6 @@ pub fn translate_insert( ); } let table_name = &tbl_name.name; - let fk_enabled = connection.foreign_keys_enabled(); - let has_child_fks = fk_enabled - && !resolver - .schema - .get_fks_for_table(table_name.as_str()) - .is_empty(); - let has_parent_fks = fk_enabled && resolver.schema.any_incoming_fk_to(table_name.as_str()); // Check if this is a system table that should be protected from direct writes if crate::schema::is_system_table(table_name.as_str()) { @@ -100,6 +93,7 @@ pub fn translate_insert( Some(table) => table, None => crate::bail_parse_error!("no such table: {}", table_name), }; + let fk_enabled = connection.foreign_keys_enabled(); // Check if this is a materialized view if resolver.schema.is_materialized_view(table_name.as_str()) { @@ -140,6 +134,7 @@ pub fn translate_insert( if !btree_table.has_rowid { crate::bail_parse_error!("INSERT into WITHOUT ROWID table is not supported"); } + let has_child_fks = fk_enabled && !btree_table.foreign_keys.is_empty(); let root_page = btree_table.root_page; @@ -243,7 +238,7 @@ pub fn translate_insert( connection, )?; - if has_child_fks || has_parent_fks { + if has_child_fks { program.emit_insn(Insn::FkCounter { increment_value: 1, check_abort: false, @@ -1044,7 +1039,7 @@ pub fn translate_insert( } } } - if has_child_fks || has_parent_fks { + if has_child_fks { emit_fk_checks_for_insert(&mut program, resolver, &insertion, table_name.as_str())?; } @@ -1909,87 +1904,56 @@ fn emit_fk_checks_for_insert( }); // Iterate child FKs declared on this table - for fk in resolver.schema.get_fks_for_table(table_name) { - let fk_ok = program.allocate_label(); + for fk_ref in resolver.schema.resolved_fks_for_child(table_name) { + let parent_tbl = resolver + .schema + .get_btree_table(&fk_ref.fk.parent_table) + .expect("parent table"); + let num_child_cols = fk_ref.child_cols.len(); - // If any child column is NULL, skip this FK - for child_col in &fk.child_columns { - let mapping = insertion - .get_col_mapping_by_name(child_col) - .ok_or_else(|| { - crate::LimboError::InternalError(format!("FK column {child_col} not found")) - })?; - let src = if mapping.column.is_rowid_alias { - insertion.key_register() - } else { - mapping.register - }; + // if any child FK value is NULL, this row doesn't reference the parent. + let fk_ok = program.allocate_label(); + for &pos_in_child in fk_ref.child_pos.iter() { + // Map INSERT image register for that column + let src = insertion + .col_mappings + .get(pos_in_child) + .expect("col must be present") + .register; program.emit_insn(Insn::IsNull { reg: src, target_pc: fk_ok, }); } - // Parent lookup: rowid path or unique-index path - let parent_tbl = resolver.schema.get_table(&fk.parent_table).ok_or_else(|| { - crate::LimboError::InternalError(format!("Parent table {} not found", fk.parent_table)) - })?; - - let uses_rowid = { - // If single parent column equals rowid or aliases rowid - fk.parent_columns.len() == 1 && { - let parent_col = fk.parent_columns[0].as_str(); - parent_col.eq_ignore_ascii_case("rowid") - || parent_tbl.columns().iter().any(|c| { - c.is_rowid_alias - && c.name - .as_ref() - .is_some_and(|n| n.eq_ignore_ascii_case(parent_col)) - }) - } - }; - - if uses_rowid { - // Simple rowid probe on parent table - let parent_bt = parent_tbl.btree().ok_or_else(|| { - crate::LimboError::InternalError("Parent table is not a BTree".into()) - })?; - let pcur = program.alloc_cursor_id(CursorType::BTreeTable(parent_bt.clone())); + if fk_ref.parent_uses_rowid { + // Parent is rowid/alias: single-reg probe + let pcur = program.alloc_cursor_id(CursorType::BTreeTable(parent_tbl.clone())); program.emit_insn(Insn::OpenRead { cursor_id: pcur, - root_page: parent_bt.root_page, + root_page: parent_tbl.root_page, db: 0, }); - - // Child value register - let cm = insertion - .get_col_mapping_by_name(&fk.child_columns[0]) - .ok_or_else(|| { - crate::LimboError::InternalError("FK child column not found".into()) - })?; - let val_reg = if cm.column.is_rowid_alias { - insertion.key_register() - } else { - cm.register - }; - + let only = 0; // n == 1 guaranteed if parent_uses_rowid + let src = insertion + .col_mappings + .get(fk_ref.child_pos[only]) + .unwrap() + .register; let violation = program.allocate_label(); - // NotExists: jump to violation if missing in parent program.emit_insn(Insn::NotExists { cursor: pcur, - rowid_reg: val_reg, + rowid_reg: src, target_pc: violation, }); - // OK program.emit_insn(Insn::Close { cursor_id: pcur }); program.emit_insn(Insn::Goto { target_pc: fk_ok }); - // Violation program.preassign_label_to_next_insn(violation); program.emit_insn(Insn::Close { cursor_id: pcur }); // Deferred vs immediate - if fk.deferred { + if fk_ref.fk.deferred { program.emit_insn(Insn::FkCounter { increment_value: 1, check_abort: false, @@ -2001,67 +1965,48 @@ fn emit_fk_checks_for_insert( description: "FOREIGN KEY constraint failed".to_string(), }); } - } else { - // Multi-column (or non-rowid) parent, we have to match a UNIQUE index with - // the exact column set and order - let parent_idx = resolver - .schema - .get_indices(&fk.parent_table) - .find(|idx| { - idx.unique - && idx.columns.len() == fk.parent_columns.len() - && idx - .columns - .iter() - .zip(fk.parent_columns.iter()) - .all(|(ic, pc)| ic.name.eq_ignore_ascii_case(pc)) - }) - .ok_or_else(|| { - crate::LimboError::InternalError(format!( - "No UNIQUE index on parent {}({:?}) for FK", - fk.parent_table, fk.parent_columns - )) - })?; - - let icur = program.alloc_cursor_id(CursorType::BTreeIndex(parent_idx.clone())); + } else if let Some(ix) = &fk_ref.parent_unique_index { + // Parent has a UNIQUE index exactly on parent_cols: use Found against that index + let icur = program.alloc_cursor_id(CursorType::BTreeIndex(ix.clone())); program.emit_insn(Insn::OpenRead { cursor_id: icur, - root_page: parent_idx.root_page, + root_page: ix.root_page, db: 0, }); - // Build packed search key registers from the *child* values - let n = fk.child_columns.len(); - let start = program.alloc_registers(n); - for (i, child_col) in fk.child_columns.iter().enumerate() { - let cm = insertion - .get_col_mapping_by_name(child_col) - .ok_or_else(|| { - crate::LimboError::InternalError(format!("Column {child_col} not found")) - })?; - let src = if cm.column.is_rowid_alias { - insertion.key_register() - } else { - cm.register - }; + // Build probe (child values order == parent index order by construction) + let probe_start = program.alloc_registers(num_child_cols); + for (i, &pos_in_child) in fk_ref.child_pos.iter().enumerate() { + let src = insertion.col_mappings.get(pos_in_child).unwrap().register; program.emit_insn(Insn::Copy { src_reg: src, - dst_reg: start + i, + dst_reg: probe_start + i, extra_amount: 0, }); } + let aff: String = ix + .columns + .iter() + .map(|c| parent_tbl.columns[c.pos_in_table].affinity().aff_mask()) + .collect(); + program.emit_insn(Insn::Affinity { + start_reg: probe_start, + count: std::num::NonZeroUsize::new(num_child_cols).unwrap(), + affinities: aff, + }); + let found = program.allocate_label(); program.emit_insn(Insn::Found { cursor_id: icur, target_pc: found, - record_reg: start, - num_regs: n, + record_reg: probe_start, + num_regs: num_child_cols, }); - // Violation path + // Not found: violation program.emit_insn(Insn::Close { cursor_id: icur }); - if fk.deferred { + if fk_ref.fk.deferred { program.emit_insn(Insn::FkCounter { increment_value: 1, check_abort: false, @@ -2074,16 +2019,14 @@ fn emit_fk_checks_for_insert( }); } program.emit_insn(Insn::Goto { target_pc: fk_ok }); - // Found OK program.preassign_label_to_next_insn(found); program.emit_insn(Insn::Close { cursor_id: icur }); } - // Done with this FK program.preassign_label_to_next_insn(fk_ok); } - program.resolve_label(after_all, program.offset()); + program.preassign_label_to_next_insn(after_all); Ok(()) } diff --git a/core/translate/pragma.rs b/core/translate/pragma.rs index 0a527a68c..601032943 100644 --- a/core/translate/pragma.rs +++ b/core/translate/pragma.rs @@ -95,6 +95,20 @@ fn update_pragma( connection: Arc, mut program: ProgramBuilder, ) -> crate::Result<(ProgramBuilder, TransactionMode)> { + let parse_pragma_enabled = |expr: &ast::Expr| -> bool { + if let Expr::Literal(Literal::Numeric(n)) = expr { + return !matches!(n.as_str(), "0"); + }; + let name_bytes = match expr { + Expr::Literal(Literal::Keyword(name)) => name.as_bytes(), + Expr::Name(name) | Expr::Id(name) => name.as_str().as_bytes(), + _ => "".as_bytes(), + }; + match_ignore_ascii_case!(match name_bytes { + b"ON" | b"TRUE" | b"YES" | b"1" => true, + _ => false, + }) + }; match pragma { PragmaName::ApplicationId => { let data = parse_signed_number(&value)?; @@ -343,38 +357,15 @@ fn update_pragma( } PragmaName::Synchronous => { use crate::SyncMode; - - let mode = match value { - Expr::Name(name) => { - let name_bytes = name.as_str().as_bytes(); - match_ignore_ascii_case!(match name_bytes { - b"OFF" | b"FALSE" | b"NO" | b"0" => SyncMode::Off, - _ => SyncMode::Full, - }) - } - Expr::Literal(Literal::Numeric(n)) => match n.as_str() { - "0" => SyncMode::Off, - _ => SyncMode::Full, - }, - _ => SyncMode::Full, + let mode = match parse_pragma_enabled(&value) { + true => SyncMode::Full, + false => SyncMode::Off, }; - connection.set_sync_mode(mode); Ok((program, TransactionMode::None)) } PragmaName::DataSyncRetry => { - let retry_enabled = match value { - Expr::Name(name) => { - let name_bytes = name.as_str().as_bytes(); - match_ignore_ascii_case!(match name_bytes { - b"ON" | b"TRUE" | b"YES" | b"1" => true, - _ => false, - }) - } - Expr::Literal(Literal::Numeric(n)) => !matches!(n.as_str(), "0"), - _ => false, - }; - + let retry_enabled = parse_pragma_enabled(&value); connection.set_data_sync_retry(retry_enabled); Ok((program, TransactionMode::None)) } @@ -388,24 +379,7 @@ fn update_pragma( Ok((program, TransactionMode::None)) } PragmaName::ForeignKeys => { - let enabled = match value { - Expr::Name(name) | Expr::Id(name) => { - let name_bytes = name.as_str().as_bytes(); - match_ignore_ascii_case!(match name_bytes { - b"ON" | b"TRUE" | b"YES" | b"1" => true, - _ => false, - }) - } - Expr::Literal(Literal::Keyword(name) | Literal::String(name)) => { - let name_bytes = name.as_bytes(); - match_ignore_ascii_case!(match name_bytes { - b"ON" | b"TRUE" | b"YES" | b"1" => true, - _ => false, - }) - } - Expr::Literal(Literal::Numeric(n)) => !matches!(n.as_str(), "0"), - _ => false, - }; + let enabled = parse_pragma_enabled(&value); connection.set_foreign_keys_enabled(enabled); Ok((program, TransactionMode::None)) } diff --git a/core/translate/upsert.rs b/core/translate/upsert.rs index f9bfd5af9..2ae07f961 100644 --- a/core/translate/upsert.rs +++ b/core/translate/upsert.rs @@ -490,11 +490,14 @@ pub fn emit_upsert( } // Parent-side checks only if any incoming FK could care - if resolver.schema.any_incoming_fk_to(table.get_name()) { + if resolver + .schema + .any_resolved_fks_referencing(table.get_name()) + { // if parent key can't change, skip let updated_parent_positions: HashSet = set_pairs.iter().map(|(i, _)| *i).collect(); - let incoming = resolver.schema.incoming_fks_to(table.get_name()); + let incoming = resolver.schema.resolved_fks_referencing(table.get_name()); let parent_key_may_change = incoming .iter() .any(|r| r.parent_key_may_change(&updated_parent_positions, &bt));