1use inillucent_base::limits::Limits;
28
29use crate::ast::{ReferentialAction, TriggerTime};
30use crate::catalog_view::{
31 ForeignKeyInfo, ForeignKeyTrigger, TableInfo, TableKind, TriggerEventInfo, TriggerInfo,
32};
33use crate::parser::parse_next_statement;
34
35pub const VIOLATION_MESSAGE: &str = "FOREIGN KEY constraint failed";
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub enum ForeignKeyEvent {
41 ChildInsert,
43 ChildUpdate,
45 ParentDelete,
47 ParentUpdate,
49}
50
51impl ForeignKeyEvent {}
52
53pub fn parent_columns(key: &ForeignKeyInfo, parent: &TableInfo) -> Option<Vec<Vec<u8>>> {
59 if !key.parent_columns.is_empty() {
60 return Some(key.parent_columns.clone());
61 }
62 let primary = parent.primary_key();
63 if primary.is_empty() {
64 return None;
65 }
66 let mut names = Vec::with_capacity(primary.len());
67 for position in primary {
68 names.push(parent.columns.get(usize::from(position))?.name.clone());
69 }
70 Some(names)
71}
72
73fn child_columns(key: &ForeignKeyInfo, child: &TableInfo) -> Option<Vec<Vec<u8>>> {
75 let mut names = Vec::with_capacity(key.columns.len());
76 for position in &key.columns {
77 names.push(child.columns.get(usize::from(*position))?.name.clone());
78 }
79 Some(names)
80}
81
82fn quoted(name: &[u8], out: &mut String) {
84 out.push('"');
85 for byte in name {
86 if *byte == b'"' {
87 out.push('"');
88 }
89 out.push(char::from(*byte));
90 }
91 out.push('"');
92}
93
94fn quote(name: &[u8]) -> String {
96 let mut out = String::new();
97 quoted(name, &mut out);
98 out
99}
100
101fn qualified(database: &[u8], table: &[u8]) -> String {
104 let mut out = quote(database);
105 out.push('.');
106 quoted(table, &mut out);
107 out
108}
109
110fn conjunction(parts: &[String]) -> String {
112 if parts.is_empty() {
113 return "1".to_string();
114 }
115 parts.join(" AND ")
116}
117
118fn children_of(child: &[Vec<u8>], parent: &[Vec<u8>], row: &str) -> String {
120 let mut parts = Vec::with_capacity(child.len());
121 for (near, far) in child.iter().zip(parent.iter()) {
122 parts.push(format!("{} = {row}.{}", quote(near), quote(far)));
123 }
124 conjunction(&parts)
125}
126
127fn children_matching(
139 child: &[Vec<u8>],
140 parent: &[Vec<u8>],
141 collations: &[Vec<u8>],
142 row: &str,
143) -> String {
144 let mut parts = Vec::with_capacity(child.len());
145 for ((near, far), collation) in child.iter().zip(parent.iter()).zip(collations.iter()) {
146 parts.push(format!(
147 "{} = {row}.{} COLLATE {}",
148 quote(near),
149 quote(far),
150 quote(collation)
151 ));
152 }
153 conjunction(&parts)
154}
155
156fn collation_of(table: &TableInfo, column: &[u8]) -> Vec<u8> {
161 table
162 .column_position(&column.to_ascii_lowercase())
163 .and_then(|position| table.column(position))
164 .map(|held| held.collation.clone())
165 .filter(|name| !name.is_empty())
166 .unwrap_or_else(|| b"binary".to_vec())
167}
168
169fn trigger_name(child: &TableInfo, key: &ForeignKeyInfo, event: ForeignKeyEvent) -> Vec<u8> {
176 let suffix = match event {
177 ForeignKeyEvent::ChildInsert => "ci",
178 ForeignKeyEvent::ChildUpdate => "cu",
179 ForeignKeyEvent::ParentDelete => "pd",
180 ForeignKeyEvent::ParentUpdate => "pu",
181 };
182 let mut name = b"sqlite_fk_".to_vec();
183 name.extend_from_slice(&child.folded);
184 name.push(b'_');
185 name.extend_from_slice(key.id.to_string().as_bytes());
186 name.push(b'_');
187 name.extend_from_slice(suffix.as_bytes());
188 name
189}
190
191pub fn trigger_for(
197 child: &TableInfo,
198 parent: &TableInfo,
199 key: &ForeignKeyInfo,
200 event: ForeignKeyEvent,
201 database: &[u8],
202 deferred: bool,
203 limits: &Limits,
204) -> Option<TriggerInfo> {
205 let near = child_columns(key, child)?;
206 let far = parent_columns(key, parent)?;
207 if near.len() != far.len() || near.is_empty() {
208 return None;
209 }
210 let sql = match event {
211 ForeignKeyEvent::ChildInsert | ForeignKeyEvent::ChildUpdate => {
212 if deferred {
213 return None;
214 }
215 child_check(child, parent, key, event, database, &near, &far)
216 }
217 ForeignKeyEvent::ParentDelete | ForeignKeyEvent::ParentUpdate => {
218 parent_action(child, parent, key, event, database, &near, &far, deferred)?
219 }
220 };
221 build(&sql, trigger_name(child, key, event), limits)
222}
223
224fn build(sql: &str, name: Vec<u8>, limits: &Limits) -> Option<TriggerInfo> {
230 let parsed = parse_next_statement(sql.as_bytes(), 0, limits).ok()?;
231 let crate::ast::Statement::CreateTrigger {
232 time,
233 event,
234 when,
235 body,
236 ..
237 } = &parsed.statement
238 else {
239 return None;
240 };
241 let event = match event {
242 crate::ast::TriggerEvent::Insert => TriggerEventInfo::Insert,
243 crate::ast::TriggerEvent::Delete => TriggerEventInfo::Delete,
244 crate::ast::TriggerEvent::Update(columns) => TriggerEventInfo::Update(
245 columns
246 .iter()
247 .map(|column| parsed.ast.folded(*column).to_vec())
248 .collect(),
249 ),
250 };
251 Some(TriggerInfo {
252 folded: name.to_ascii_lowercase(),
253 name,
254 time: time.unwrap_or(TriggerTime::Before),
255 event,
256 when: *when,
257 body: body.clone(),
258 ast: parsed.ast,
259 table_database: None,
260 })
261}
262
263fn child_check(
269 child: &TableInfo,
270 parent: &TableInfo,
271 key: &ForeignKeyInfo,
272 event: ForeignKeyEvent,
273 database: &[u8],
274 near: &[Vec<u8>],
275 far: &[Vec<u8>],
276) -> String {
277 let mut guards: Vec<String> = near
278 .iter()
279 .map(|column| format!("NEW.{} IS NOT NULL", quote(column)))
280 .collect();
281 let lookup = children_of(far, near, "NEW");
282 guards.push(format!(
283 "NOT EXISTS (SELECT 1 FROM {} WHERE {lookup})",
284 qualified(database, &parent.name)
285 ));
286 if parent.folded == child.folded {
293 let same: Vec<String> = near
294 .iter()
295 .zip(far.iter())
296 .map(|(child_column, parent_column)| {
297 format!(
299 "+NEW.{} IS +NEW.{}",
300 quote(parent_column),
301 quote(child_column)
302 )
303 })
304 .collect();
305 guards.push(format!("NOT ({})", conjunction(&same)));
306 }
307 let fires = match event {
308 ForeignKeyEvent::ChildUpdate => format!("BEFORE UPDATE OF {} ON", column_list(near)),
309 _ => "BEFORE INSERT ON".to_string(),
310 };
311 format!(
312 "CREATE TRIGGER {} {fires} {} BEGIN SELECT RAISE(ABORT, '{VIOLATION_MESSAGE}') WHERE {}; END",
313 quote(&trigger_name(child, key, event)),
314 quote(&child.name),
315 conjunction(&guards)
316 )
317}
318
319fn parent_action(
321 child: &TableInfo,
322 parent: &TableInfo,
323 key: &ForeignKeyInfo,
324 event: ForeignKeyEvent,
325 database: &[u8],
326 near: &[Vec<u8>],
327 far: &[Vec<u8>],
328 deferred: bool,
329) -> Option<String> {
330 let action = match event {
331 ForeignKeyEvent::ParentDelete => key.on_delete,
332 _ => key.on_update,
333 };
334 let collations: Vec<Vec<u8>> = far.iter().map(|name| collation_of(parent, name)).collect();
335 let matching = children_matching(near, far, &collations, "OLD");
336 let target = qualified(database, &child.name);
337 let body = match action {
338 ReferentialAction::NoAction | ReferentialAction::Restrict => {
339 if deferred && action == ReferentialAction::NoAction {
344 return None;
345 }
346 format!(
347 "SELECT RAISE(ABORT, '{VIOLATION_MESSAGE}') WHERE EXISTS (SELECT 1 FROM {target} WHERE {matching});"
348 )
349 }
350 ReferentialAction::Cascade => match event {
351 ForeignKeyEvent::ParentDelete => {
352 format!("DELETE FROM {target} WHERE {matching};")
353 }
354 _ => {
355 let sets: Vec<String> = near
356 .iter()
357 .zip(far.iter())
358 .map(|(child_column, parent_column)| {
359 format!("{} = NEW.{}", quote(child_column), quote(parent_column))
360 })
361 .collect();
362 format!("UPDATE {target} SET {} WHERE {matching};", sets.join(", "))
363 }
364 },
365 ReferentialAction::SetNull => {
366 let sets: Vec<String> = near
367 .iter()
368 .map(|column| format!("{} = NULL", quote(column)))
369 .collect();
370 format!("UPDATE {target} SET {} WHERE {matching};", sets.join(", "))
371 }
372 ReferentialAction::SetDefault => {
373 let mut sets = Vec::with_capacity(near.len());
374 for (position, column) in key.columns.iter().zip(near.iter()) {
375 let default = child
376 .columns
377 .get(usize::from(*position))
378 .and_then(|info| info.default_sql.clone())
379 .unwrap_or_else(|| b"NULL".to_vec());
380 sets.push(format!(
381 "{} = ({})",
382 quote(column),
383 String::from_utf8_lossy(&default)
384 ));
385 }
386 format!("UPDATE {target} SET {} WHERE {matching};", sets.join(", "))
387 }
388 };
389 let time = if action == ReferentialAction::Restrict {
393 "BEFORE"
394 } else {
395 "AFTER"
396 };
397 let fires = match event {
398 ForeignKeyEvent::ParentDelete => format!("{time} DELETE ON"),
399 _ => format!("{time} UPDATE OF {} ON", column_list(far)),
400 };
401 let guard = match event {
404 ForeignKeyEvent::ParentUpdate => {
405 let changed: Vec<String> = far
406 .iter()
407 .map(|column| {
408 let name = quote(column);
409 format!("OLD.{name} IS NOT NEW.{name}")
410 })
411 .collect();
412 format!(" WHEN {}", changed.join(" OR "))
413 }
414 _ => String::new(),
415 };
416 Some(format!(
417 "CREATE TRIGGER {} {fires} {}{guard} BEGIN {body} END",
418 quote(&trigger_name(child, key, event)),
419 quote(&parent.name)
420 ))
421}
422
423fn column_list(columns: &[Vec<u8>]) -> String {
425 columns
426 .iter()
427 .map(|column| quote(column))
428 .collect::<Vec<_>>()
429 .join(", ")
430}
431
432pub fn plan_schema(tables: &mut [TableInfo], database: &[u8], limits: &Limits) {
444 mark_cycles(tables);
445 let snapshot: Vec<TableInfo> = tables.to_vec();
446 for table in tables.iter_mut() {
447 if table.kind != TableKind::Table {
448 continue;
449 }
450 table.foreign_key_triggers = plan_table(table, &snapshot, database, limits);
451 }
452}
453
454fn mark_cycles(tables: &mut [TableInfo]) {
460 let edges: Vec<(Vec<u8>, Vec<u8>)> = tables
461 .iter()
462 .flat_map(|table| {
463 table
464 .foreign_keys
465 .iter()
466 .map(|key| (table.folded.clone(), key.parent_folded.clone()))
467 })
468 .collect();
469 for table in tables.iter_mut() {
470 for key in &mut table.foreign_keys {
471 key.cyclic = reaches(&edges, &key.parent_folded, &table.folded);
472 }
473 }
474}
475
476fn reaches(edges: &[(Vec<u8>, Vec<u8>)], from: &[u8], wanted: &[u8]) -> bool {
479 let mut seen: Vec<Vec<u8>> = Vec::new();
480 let mut pending: Vec<Vec<u8>> = vec![from.to_vec()];
481 while let Some(table) = pending.pop() {
482 if table == wanted {
483 return true;
484 }
485 if seen.contains(&table) {
486 continue;
487 }
488 seen.push(table.clone());
489 for (child, parent) in edges {
490 if *child == table {
491 pending.push(parent.clone());
492 }
493 }
494 }
495 false
496}
497
498pub fn sweep_statement(
510 child: &TableInfo,
511 parent: &TableInfo,
512 key: &ForeignKeyInfo,
513 database: &[u8],
514) -> Option<String> {
515 let near = child_columns(key, child)?;
516 let far = parent_columns(key, parent)?;
517 if near.len() != far.len() || near.is_empty() {
518 return None;
519 }
520 let outer = quote(&child.name);
521 let mut guards: Vec<String> = near
522 .iter()
523 .map(|column| format!("{outer}.{} IS NOT NULL", quote(column)))
524 .collect();
525 let lookup: Vec<String> = far
526 .iter()
527 .zip(near.iter())
528 .map(|(parent_column, child_column)| {
529 format!(
530 "p.{} = {outer}.{}",
531 quote(parent_column),
532 quote(child_column)
533 )
534 })
535 .collect();
536 guards.push(format!(
537 "NOT EXISTS (SELECT 1 FROM {} AS p WHERE {})",
538 qualified(database, &parent.name),
539 conjunction(&lookup)
540 ));
541 let target = qualified(database, &child.name);
542 let where_clause = conjunction(&guards);
543 match key.on_delete {
544 ReferentialAction::Cascade => Some(format!("DELETE FROM {target} WHERE {where_clause}")),
545 ReferentialAction::SetNull => {
546 let sets: Vec<String> = near
547 .iter()
548 .map(|column| format!("{} = NULL", quote(column)))
549 .collect();
550 Some(format!(
551 "UPDATE {target} SET {} WHERE {where_clause}",
552 sets.join(", ")
553 ))
554 }
555 ReferentialAction::SetDefault => {
556 let mut sets = Vec::with_capacity(near.len());
557 for (position, column) in key.columns.iter().zip(near.iter()) {
558 let default = child
559 .columns
560 .get(usize::from(*position))
561 .and_then(|info| info.default_sql.clone())
562 .unwrap_or_else(|| b"NULL".to_vec());
563 sets.push(format!(
564 "{} = ({})",
565 quote(column),
566 String::from_utf8_lossy(&default)
567 ));
568 }
569 Some(format!(
570 "UPDATE {target} SET {} WHERE {where_clause}",
571 sets.join(", ")
572 ))
573 }
574 ReferentialAction::NoAction | ReferentialAction::Restrict => None,
575 }
576}
577
578fn plan_table(
580 table: &TableInfo,
581 tables: &[TableInfo],
582 database: &[u8],
583 limits: &Limits,
584) -> Vec<ForeignKeyTrigger> {
585 let mut planned = Vec::new();
586 for key in &table.foreign_keys {
587 let parent = tables
588 .iter()
589 .find(|candidate| candidate.folded == key.parent_folded);
590 let Some(parent) = parent else {
591 planned.push(unusable(
592 key,
593 format!(
594 "no such table: {}.{}",
595 String::from_utf8_lossy(database),
596 String::from_utf8_lossy(&key.parent)
597 ),
598 true,
599 key.parent_folded == table.folded,
600 ));
601 continue;
602 };
603 if !parent_key_is_unique(parent, key) {
604 planned.push(unusable(
605 key,
606 mismatch(table, parent),
607 true,
608 parent.folded == table.folded,
609 ));
610 continue;
611 }
612 for event in [ForeignKeyEvent::ChildInsert, ForeignKeyEvent::ChildUpdate] {
613 if let Some(trigger) = trigger_for(table, parent, key, event, database, false, limits) {
614 planned.push(ForeignKeyTrigger {
615 is_check: true,
616 deferred: key.is_deferred(),
617 trigger: Some(trigger),
618 fault: Vec::new(),
619 self_referencing: parent.folded == table.folded,
620 });
621 }
622 }
623 }
624 for child in tables {
625 if child.kind != TableKind::Table {
626 continue;
627 }
628 for key in &child.foreign_keys {
629 if key.parent_folded != table.folded {
630 continue;
631 }
632 if !parent_key_is_unique(table, key) {
633 planned.push(unusable(
634 key,
635 mismatch(child, table),
636 false,
637 child.folded == table.folded,
638 ));
639 continue;
640 }
641 for event in [ForeignKeyEvent::ParentDelete, ForeignKeyEvent::ParentUpdate] {
642 let Some(trigger) = trigger_for(child, table, key, event, database, false, limits)
643 else {
644 continue;
645 };
646 let action = match event {
647 ForeignKeyEvent::ParentDelete => key.on_delete,
648 _ => key.on_update,
649 };
650 planned.push(ForeignKeyTrigger {
651 is_check: action == ReferentialAction::NoAction,
655 deferred: key.is_deferred(),
656 trigger: Some(trigger),
657 fault: Vec::new(),
658 self_referencing: child.folded == table.folded,
659 });
660 }
661 }
662 }
663 planned
664}
665
666fn mismatch(child: &TableInfo, parent: &TableInfo) -> String {
668 format!(
669 "foreign key mismatch - \"{}\" referencing \"{}\"",
670 String::from_utf8_lossy(&child.name),
671 String::from_utf8_lossy(&parent.name)
672 )
673}
674
675fn unusable(
682 key: &ForeignKeyInfo,
683 message: String,
684 is_check: bool,
685 self_referencing: bool,
686) -> ForeignKeyTrigger {
687 ForeignKeyTrigger {
688 is_check,
689 deferred: key.is_deferred(),
690 trigger: None,
691 fault: message.into_bytes(),
692 self_referencing,
693 }
694}
695
696pub fn parent_key_is_unique(parent: &TableInfo, key: &ForeignKeyInfo) -> bool {
703 let Some(wanted) = parent_columns(key, parent) else {
704 return false;
705 };
706 let folded: Vec<Vec<u8>> = wanted
707 .iter()
708 .map(|name| name.to_ascii_lowercase())
709 .collect();
710 if folded.len() == 1 {
712 if let Some(alias) = parent.rowid_alias {
713 if let Some(column) = parent.columns.get(usize::from(alias)) {
714 if folded.first() == Some(&column.folded) {
715 return true;
716 }
717 }
718 }
719 }
720 let primary = parent.primary_key();
721 let primary_collates = parent
722 .indexes
723 .iter()
724 .filter(|index| index.origin == crate::catalog_view::IndexOrigin::PrimaryKey)
725 .all(|index| index_uses_column_collations(parent, index));
726 if !primary.is_empty() && primary.len() == folded.len() && primary_collates {
727 let names: Vec<Vec<u8>> = primary
728 .iter()
729 .filter_map(|position| parent.columns.get(usize::from(*position)))
730 .map(|column| column.folded.clone())
731 .collect();
732 if same_set(&names, &folded) {
733 return true;
734 }
735 }
736 parent.indexes.iter().any(|index| {
737 index.unique
738 && index.columns.len() == folded.len()
739 && index_uses_column_collations(parent, index)
740 && {
741 let names: Vec<Vec<u8>> = index
742 .columns
743 .iter()
744 .filter_map(|key| key.column)
745 .filter_map(|position| parent.columns.get(usize::from(position)))
746 .map(|column| column.folded.clone())
747 .collect();
748 same_set(&names, &folded)
749 }
750 })
751}
752
753fn index_uses_column_collations(
763 parent: &TableInfo,
764 index: &crate::catalog_view::IndexInfo,
765) -> bool {
766 index.columns.iter().all(|key| {
767 let Some(column) = key.column.and_then(|position| parent.column(position)) else {
768 return true;
769 };
770 key.collation.eq_ignore_ascii_case(&column.collation)
771 })
772}
773
774fn same_set(left: &[Vec<u8>], right: &[Vec<u8>]) -> bool {
779 left.len() == right.len() && right.iter().all(|name| left.contains(name))
780}
781
782pub fn orphan_query(child: &TableInfo, key: &ForeignKeyInfo, database: &[u8]) -> Option<String> {
793 let near = child_columns(key, child)?;
794 if near.is_empty() {
795 return None;
796 }
797 let guards: Vec<String> = near
798 .iter()
799 .map(|column| format!("c.{} IS NOT NULL", quote(column)))
800 .collect();
801 let identity = if child.without_rowid {
802 "NULL"
803 } else {
804 "c.rowid"
805 };
806 Some(format!(
807 "SELECT {identity} FROM {} AS c WHERE {}",
808 qualified(database, &child.name),
809 conjunction(&guards)
810 ))
811}
812
813pub fn violation_query(
821 child: &TableInfo,
822 parent: &TableInfo,
823 key: &ForeignKeyInfo,
824 database: &[u8],
825) -> Option<String> {
826 let near = child_columns(key, child)?;
827 let far = parent_columns(key, parent)?;
828 if near.len() != far.len() || near.is_empty() {
829 return None;
830 }
831 let mut guards: Vec<String> = near
832 .iter()
833 .map(|column| format!("c.{} IS NOT NULL", quote(column)))
834 .collect();
835 let lookup: Vec<String> = far
836 .iter()
837 .zip(near.iter())
838 .map(|(parent_column, child_column)| {
839 format!("p.{} = c.{}", quote(parent_column), quote(child_column))
840 })
841 .collect();
842 guards.push(format!(
843 "NOT EXISTS (SELECT 1 FROM {} AS p WHERE {})",
844 qualified(database, &parent.name),
845 conjunction(&lookup)
846 ));
847 let identity = if child.without_rowid {
850 "NULL"
851 } else {
852 "c.rowid"
853 };
854 Some(format!(
855 "SELECT {identity} FROM {} AS c WHERE {}",
856 qualified(database, &child.name),
857 conjunction(&guards)
858 ))
859}
860
861#[cfg(test)]
862mod tests {
863 use super::*;
864 use crate::catalog_view::{ColumnInfo, TableKind};
865 use inillucent_value::Affinity;
866
867 fn table(name: &[u8], columns: &[&[u8]]) -> TableInfo {
869 TableInfo {
870 name: name.to_vec(),
871 folded: name.to_ascii_lowercase(),
872 database: 0,
873 root: 2,
874 columns: columns
875 .iter()
876 .map(|column| ColumnInfo {
877 name: column.to_vec(),
878 folded: column.to_ascii_lowercase(),
879 declared_type: Vec::new(),
880 affinity: Affinity::Blob,
881 collation: b"binary".to_vec(),
882 not_null: false,
883 not_null_conflict: None,
884 primary_key_conflict: None,
885 default_sql: None,
886 primary_key_position: None,
887 hidden: false,
888 generated: false,
889 stored: false,
890 generated_sql: None,
891 })
892 .collect(),
893 rowid_alias: None,
894 without_rowid: false,
895 strict: false,
896 autoincrement: false,
897 kind: TableKind::Table,
898 create_sql: Vec::new(),
899 indexes: Vec::new(),
900 view: None,
901 triggers: Vec::new(),
902 analysed_rows: None,
903 checks: Vec::new(),
904 foreign_keys: Vec::new(),
905 foreign_key_triggers: Vec::new(),
906 module: None,
907 }
908 }
909
910 fn key(on_delete: ReferentialAction, on_update: ReferentialAction) -> ForeignKeyInfo {
912 ForeignKeyInfo {
913 id: 0,
914 columns: vec![1],
915 parent: b"p".to_vec(),
916 parent_folded: b"p".to_vec(),
917 parent_columns: vec![b"id".to_vec()],
918 on_delete,
919 on_update,
920 match_clause: Vec::new(),
921 deferrable: false,
922 initially_deferred: false,
923 cyclic: false,
924 }
925 }
926
927 #[test]
930 fn every_generated_trigger_parses() {
931 let child = table(b"c", &[b"id", b"pid"]);
932 let parent = table(b"p", &[b"id"]);
933 let limits = Limits::default();
934 let actions = [
935 ReferentialAction::NoAction,
936 ReferentialAction::Restrict,
937 ReferentialAction::Cascade,
938 ReferentialAction::SetNull,
939 ReferentialAction::SetDefault,
940 ];
941 let events = [
942 ForeignKeyEvent::ChildInsert,
943 ForeignKeyEvent::ChildUpdate,
944 ForeignKeyEvent::ParentDelete,
945 ForeignKeyEvent::ParentUpdate,
946 ];
947 for action in actions {
948 let key = key(action, action);
949 for event in events {
950 let built = trigger_for(&child, &parent, &key, event, b"main", false, &limits);
951 assert!(
952 built.is_some(),
953 "{action:?} on {event:?} produced no trigger"
954 );
955 }
956 }
957 }
958
959 #[test]
962 fn the_child_check_reads_as_it_should() {
963 let child = table(b"c", &[b"id", b"pid"]);
964 let parent = table(b"p", &[b"id"]);
965 let key = key(ReferentialAction::NoAction, ReferentialAction::NoAction);
966 let sql = child_check(
967 &child,
968 &parent,
969 &key,
970 ForeignKeyEvent::ChildInsert,
971 b"main",
972 &[b"pid".to_vec()],
973 &[b"id".to_vec()],
974 );
975 assert!(sql.contains("BEFORE INSERT ON \"c\""), "{sql}");
976 assert!(sql.contains("NEW.\"pid\" IS NOT NULL"), "{sql}");
977 assert!(sql.contains("NOT EXISTS"), "{sql}");
978 assert!(sql.contains("FOREIGN KEY constraint failed"), "{sql}");
979 }
980
981 #[test]
984 fn restrict_fires_before_and_no_action_after() {
985 let child = table(b"c", &[b"id", b"pid"]);
986 let parent = table(b"p", &[b"id"]);
987 let limits = Limits::default();
988 for (action, expected) in [
989 (ReferentialAction::Restrict, TriggerTime::Before),
990 (ReferentialAction::NoAction, TriggerTime::After),
991 ] {
992 let key = key(action, action);
993 let built = trigger_for(
994 &child,
995 &parent,
996 &key,
997 ForeignKeyEvent::ParentDelete,
998 b"main",
999 false,
1000 &limits,
1001 )
1002 .expect("the trigger is generated");
1003 assert_eq!(built.time, expected, "{action:?}");
1004 }
1005 }
1006
1007 #[test]
1011 fn a_deferred_key_defers_only_its_checks() {
1012 let child = table(b"c", &[b"id", b"pid"]);
1013 let parent = table(b"p", &[b"id"]);
1014 let limits = Limits::default();
1015 let deferred = key(ReferentialAction::NoAction, ReferentialAction::NoAction);
1016 assert!(trigger_for(
1017 &child,
1018 &parent,
1019 &deferred,
1020 ForeignKeyEvent::ChildInsert,
1021 b"main",
1022 true,
1023 &limits
1024 )
1025 .is_none());
1026 assert!(trigger_for(
1027 &child,
1028 &parent,
1029 &deferred,
1030 ForeignKeyEvent::ParentDelete,
1031 b"main",
1032 true,
1033 &limits
1034 )
1035 .is_none());
1036 let restrict = key(ReferentialAction::Restrict, ReferentialAction::Restrict);
1037 assert!(trigger_for(
1038 &child,
1039 &parent,
1040 &restrict,
1041 ForeignKeyEvent::ParentDelete,
1042 b"main",
1043 true,
1044 &limits
1045 )
1046 .is_some());
1047 let cascade = key(ReferentialAction::Cascade, ReferentialAction::Cascade);
1048 assert!(trigger_for(
1049 &child,
1050 &parent,
1051 &cascade,
1052 ForeignKeyEvent::ParentDelete,
1053 b"main",
1054 true,
1055 &limits
1056 )
1057 .is_some());
1058 }
1059
1060 #[test]
1063 fn a_parent_update_guards_on_the_key_changing() {
1064 let child = table(b"c", &[b"id", b"pid"]);
1065 let parent = table(b"p", &[b"id"]);
1066 let key = key(ReferentialAction::Cascade, ReferentialAction::Cascade);
1067 let sql = parent_action(
1068 &child,
1069 &parent,
1070 &key,
1071 ForeignKeyEvent::ParentUpdate,
1072 b"main",
1073 &[b"pid".to_vec()],
1074 &[b"id".to_vec()],
1075 false,
1076 )
1077 .expect("the trigger is generated");
1078 assert!(sql.contains("AFTER UPDATE OF \"id\""), "{sql}");
1079 assert!(sql.contains("WHEN OLD.\"id\" IS NOT NEW.\"id\""), "{sql}");
1080 assert!(sql.contains("SET \"pid\" = NEW.\"id\""), "{sql}");
1081 assert!(sql.contains("WHERE \"pid\" = OLD.\"id\""), "{sql}");
1082 }
1083
1084 #[test]
1087 fn an_awkward_identifier_is_quoted() {
1088 assert_eq!(quote(b"we\"ird"), "\"we\"\"ird\"");
1089 let child = table(b"we\"ird", &[b"id", b"pid"]);
1090 let parent = table(b"p", &[b"id"]);
1091 let key = key(ReferentialAction::Cascade, ReferentialAction::Cascade);
1092 let limits = Limits::default();
1093 assert!(trigger_for(
1094 &child,
1095 &parent,
1096 &key,
1097 ForeignKeyEvent::ChildInsert,
1098 b"main",
1099 false,
1100 &limits
1101 )
1102 .is_some());
1103 }
1104
1105 #[test]
1107 fn a_composite_key_compares_every_column() {
1108 let matching = children_of(
1109 &[b"a".to_vec(), b"b".to_vec()],
1110 &[b"x".to_vec(), b"y".to_vec()],
1111 "OLD",
1112 );
1113 assert_eq!(matching, "\"a\" = OLD.\"x\" AND \"b\" = OLD.\"y\"");
1114 }
1115}