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 trigger_name(child: &TableInfo, key: &ForeignKeyInfo, event: ForeignKeyEvent) -> Vec<u8> {
134 let suffix = match event {
135 ForeignKeyEvent::ChildInsert => "ci",
136 ForeignKeyEvent::ChildUpdate => "cu",
137 ForeignKeyEvent::ParentDelete => "pd",
138 ForeignKeyEvent::ParentUpdate => "pu",
139 };
140 let mut name = b"sqlite_fk_".to_vec();
141 name.extend_from_slice(&child.folded);
142 name.push(b'_');
143 name.extend_from_slice(key.id.to_string().as_bytes());
144 name.push(b'_');
145 name.extend_from_slice(suffix.as_bytes());
146 name
147}
148
149pub fn trigger_for(
155 child: &TableInfo,
156 parent: &TableInfo,
157 key: &ForeignKeyInfo,
158 event: ForeignKeyEvent,
159 database: &[u8],
160 deferred: bool,
161 limits: &Limits,
162) -> Option<TriggerInfo> {
163 let near = child_columns(key, child)?;
164 let far = parent_columns(key, parent)?;
165 if near.len() != far.len() || near.is_empty() {
166 return None;
167 }
168 let sql = match event {
169 ForeignKeyEvent::ChildInsert | ForeignKeyEvent::ChildUpdate => {
170 if deferred {
171 return None;
172 }
173 child_check(child, parent, key, event, database, &near, &far)
174 }
175 ForeignKeyEvent::ParentDelete | ForeignKeyEvent::ParentUpdate => {
176 parent_action(child, parent, key, event, database, &near, &far, deferred)?
177 }
178 };
179 build(&sql, trigger_name(child, key, event), limits)
180}
181
182fn build(sql: &str, name: Vec<u8>, limits: &Limits) -> Option<TriggerInfo> {
188 let parsed = parse_next_statement(sql.as_bytes(), 0, limits).ok()?;
189 let crate::ast::Statement::CreateTrigger {
190 time,
191 event,
192 when,
193 body,
194 ..
195 } = &parsed.statement
196 else {
197 return None;
198 };
199 let event = match event {
200 crate::ast::TriggerEvent::Insert => TriggerEventInfo::Insert,
201 crate::ast::TriggerEvent::Delete => TriggerEventInfo::Delete,
202 crate::ast::TriggerEvent::Update(columns) => TriggerEventInfo::Update(
203 columns
204 .iter()
205 .map(|column| parsed.ast.folded(*column).to_vec())
206 .collect(),
207 ),
208 };
209 Some(TriggerInfo {
210 folded: name.to_ascii_lowercase(),
211 name,
212 time: time.unwrap_or(TriggerTime::Before),
213 event,
214 when: *when,
215 body: body.clone(),
216 ast: parsed.ast,
217 })
218}
219
220fn child_check(
226 child: &TableInfo,
227 parent: &TableInfo,
228 key: &ForeignKeyInfo,
229 event: ForeignKeyEvent,
230 database: &[u8],
231 near: &[Vec<u8>],
232 far: &[Vec<u8>],
233) -> String {
234 let mut guards: Vec<String> = near
235 .iter()
236 .map(|column| format!("NEW.{} IS NOT NULL", quote(column)))
237 .collect();
238 let lookup = children_of(far, near, "NEW");
239 guards.push(format!(
240 "NOT EXISTS (SELECT 1 FROM {} WHERE {lookup})",
241 qualified(database, &parent.name)
242 ));
243 let fires = match event {
244 ForeignKeyEvent::ChildUpdate => format!("BEFORE UPDATE OF {} ON", column_list(near)),
245 _ => "BEFORE INSERT ON".to_string(),
246 };
247 format!(
248 "CREATE TRIGGER {} {fires} {} BEGIN SELECT RAISE(ABORT, '{VIOLATION_MESSAGE}') WHERE {}; END",
249 quote(&trigger_name(child, key, event)),
250 quote(&child.name),
251 conjunction(&guards)
252 )
253}
254
255fn parent_action(
257 child: &TableInfo,
258 parent: &TableInfo,
259 key: &ForeignKeyInfo,
260 event: ForeignKeyEvent,
261 database: &[u8],
262 near: &[Vec<u8>],
263 far: &[Vec<u8>],
264 deferred: bool,
265) -> Option<String> {
266 let action = match event {
267 ForeignKeyEvent::ParentDelete => key.on_delete,
268 _ => key.on_update,
269 };
270 let matching = children_of(near, far, "OLD");
271 let target = qualified(database, &child.name);
272 let body = match action {
273 ReferentialAction::NoAction | ReferentialAction::Restrict => {
274 if deferred && action == ReferentialAction::NoAction {
279 return None;
280 }
281 format!(
282 "SELECT RAISE(ABORT, '{VIOLATION_MESSAGE}') WHERE EXISTS (SELECT 1 FROM {target} WHERE {matching});"
283 )
284 }
285 ReferentialAction::Cascade => match event {
286 ForeignKeyEvent::ParentDelete => {
287 format!("DELETE FROM {target} WHERE {matching};")
288 }
289 _ => {
290 let sets: Vec<String> = near
291 .iter()
292 .zip(far.iter())
293 .map(|(child_column, parent_column)| {
294 format!("{} = NEW.{}", quote(child_column), quote(parent_column))
295 })
296 .collect();
297 format!("UPDATE {target} SET {} WHERE {matching};", sets.join(", "))
298 }
299 },
300 ReferentialAction::SetNull => {
301 let sets: Vec<String> = near
302 .iter()
303 .map(|column| format!("{} = NULL", quote(column)))
304 .collect();
305 format!("UPDATE {target} SET {} WHERE {matching};", sets.join(", "))
306 }
307 ReferentialAction::SetDefault => {
308 let mut sets = Vec::with_capacity(near.len());
309 for (position, column) in key.columns.iter().zip(near.iter()) {
310 let default = child
311 .columns
312 .get(usize::from(*position))
313 .and_then(|info| info.default_sql.clone())
314 .unwrap_or_else(|| b"NULL".to_vec());
315 sets.push(format!(
316 "{} = ({})",
317 quote(column),
318 String::from_utf8_lossy(&default)
319 ));
320 }
321 format!("UPDATE {target} SET {} WHERE {matching};", sets.join(", "))
322 }
323 };
324 let time = if action == ReferentialAction::Restrict {
328 "BEFORE"
329 } else {
330 "AFTER"
331 };
332 let fires = match event {
333 ForeignKeyEvent::ParentDelete => format!("{time} DELETE ON"),
334 _ => format!("{time} UPDATE OF {} ON", column_list(far)),
335 };
336 let guard = match event {
339 ForeignKeyEvent::ParentUpdate => {
340 let changed: Vec<String> = far
341 .iter()
342 .map(|column| {
343 let name = quote(column);
344 format!("OLD.{name} IS NOT NEW.{name}")
345 })
346 .collect();
347 format!(" WHEN {}", changed.join(" OR "))
348 }
349 _ => String::new(),
350 };
351 Some(format!(
352 "CREATE TRIGGER {} {fires} {}{guard} BEGIN {body} END",
353 quote(&trigger_name(child, key, event)),
354 quote(&parent.name)
355 ))
356}
357
358fn column_list(columns: &[Vec<u8>]) -> String {
360 columns
361 .iter()
362 .map(|column| quote(column))
363 .collect::<Vec<_>>()
364 .join(", ")
365}
366
367pub fn plan_schema(tables: &mut [TableInfo], database: &[u8], limits: &Limits) {
379 mark_cycles(tables);
380 let snapshot: Vec<TableInfo> = tables.to_vec();
381 for table in tables.iter_mut() {
382 if table.kind != TableKind::Table {
383 continue;
384 }
385 table.foreign_key_triggers = plan_table(table, &snapshot, database, limits);
386 }
387}
388
389fn mark_cycles(tables: &mut [TableInfo]) {
395 let edges: Vec<(Vec<u8>, Vec<u8>)> = tables
396 .iter()
397 .flat_map(|table| {
398 table
399 .foreign_keys
400 .iter()
401 .map(|key| (table.folded.clone(), key.parent_folded.clone()))
402 })
403 .collect();
404 for table in tables.iter_mut() {
405 for key in &mut table.foreign_keys {
406 key.cyclic = reaches(&edges, &key.parent_folded, &table.folded);
407 }
408 }
409}
410
411fn reaches(edges: &[(Vec<u8>, Vec<u8>)], from: &[u8], wanted: &[u8]) -> bool {
414 let mut seen: Vec<Vec<u8>> = Vec::new();
415 let mut pending: Vec<Vec<u8>> = vec![from.to_vec()];
416 while let Some(table) = pending.pop() {
417 if table == wanted {
418 return true;
419 }
420 if seen.contains(&table) {
421 continue;
422 }
423 seen.push(table.clone());
424 for (child, parent) in edges {
425 if *child == table {
426 pending.push(parent.clone());
427 }
428 }
429 }
430 false
431}
432
433pub fn sweep_statement(
445 child: &TableInfo,
446 parent: &TableInfo,
447 key: &ForeignKeyInfo,
448 database: &[u8],
449) -> Option<String> {
450 let near = child_columns(key, child)?;
451 let far = parent_columns(key, parent)?;
452 if near.len() != far.len() || near.is_empty() {
453 return None;
454 }
455 let outer = quote(&child.name);
456 let mut guards: Vec<String> = near
457 .iter()
458 .map(|column| format!("{outer}.{} IS NOT NULL", quote(column)))
459 .collect();
460 let lookup: Vec<String> = far
461 .iter()
462 .zip(near.iter())
463 .map(|(parent_column, child_column)| {
464 format!(
465 "p.{} = {outer}.{}",
466 quote(parent_column),
467 quote(child_column)
468 )
469 })
470 .collect();
471 guards.push(format!(
472 "NOT EXISTS (SELECT 1 FROM {} AS p WHERE {})",
473 qualified(database, &parent.name),
474 conjunction(&lookup)
475 ));
476 let target = qualified(database, &child.name);
477 let where_clause = conjunction(&guards);
478 match key.on_delete {
479 ReferentialAction::Cascade => Some(format!("DELETE FROM {target} WHERE {where_clause}")),
480 ReferentialAction::SetNull => {
481 let sets: Vec<String> = near
482 .iter()
483 .map(|column| format!("{} = NULL", quote(column)))
484 .collect();
485 Some(format!(
486 "UPDATE {target} SET {} WHERE {where_clause}",
487 sets.join(", ")
488 ))
489 }
490 ReferentialAction::SetDefault => {
491 let mut sets = Vec::with_capacity(near.len());
492 for (position, column) in key.columns.iter().zip(near.iter()) {
493 let default = child
494 .columns
495 .get(usize::from(*position))
496 .and_then(|info| info.default_sql.clone())
497 .unwrap_or_else(|| b"NULL".to_vec());
498 sets.push(format!(
499 "{} = ({})",
500 quote(column),
501 String::from_utf8_lossy(&default)
502 ));
503 }
504 Some(format!(
505 "UPDATE {target} SET {} WHERE {where_clause}",
506 sets.join(", ")
507 ))
508 }
509 ReferentialAction::NoAction | ReferentialAction::Restrict => None,
510 }
511}
512
513fn plan_table(
515 table: &TableInfo,
516 tables: &[TableInfo],
517 database: &[u8],
518 limits: &Limits,
519) -> Vec<ForeignKeyTrigger> {
520 let mut planned = Vec::new();
521 for key in &table.foreign_keys {
522 let parent = tables
523 .iter()
524 .find(|candidate| candidate.folded == key.parent_folded);
525 let Some(parent) = parent else {
526 planned.push(unusable(
527 key,
528 format!(
529 "no such table: {}.{}",
530 String::from_utf8_lossy(database),
531 String::from_utf8_lossy(&key.parent)
532 ),
533 true,
534 key.parent_folded == table.folded,
535 ));
536 continue;
537 };
538 if !parent_key_is_unique(parent, key) {
539 planned.push(unusable(
540 key,
541 mismatch(table, parent),
542 true,
543 parent.folded == table.folded,
544 ));
545 continue;
546 }
547 for event in [ForeignKeyEvent::ChildInsert, ForeignKeyEvent::ChildUpdate] {
548 if let Some(trigger) = trigger_for(table, parent, key, event, database, false, limits) {
549 planned.push(ForeignKeyTrigger {
550 is_check: true,
551 deferred: key.is_deferred(),
552 trigger: Some(trigger),
553 fault: Vec::new(),
554 self_referencing: parent.folded == table.folded,
555 });
556 }
557 }
558 }
559 for child in tables {
560 if child.kind != TableKind::Table {
561 continue;
562 }
563 for key in &child.foreign_keys {
564 if key.parent_folded != table.folded {
565 continue;
566 }
567 if !parent_key_is_unique(table, key) {
568 planned.push(unusable(
569 key,
570 mismatch(child, table),
571 false,
572 child.folded == table.folded,
573 ));
574 continue;
575 }
576 for event in [ForeignKeyEvent::ParentDelete, ForeignKeyEvent::ParentUpdate] {
577 let Some(trigger) = trigger_for(child, table, key, event, database, false, limits)
578 else {
579 continue;
580 };
581 let action = match event {
582 ForeignKeyEvent::ParentDelete => key.on_delete,
583 _ => key.on_update,
584 };
585 planned.push(ForeignKeyTrigger {
586 is_check: action == ReferentialAction::NoAction,
590 deferred: key.is_deferred(),
591 trigger: Some(trigger),
592 fault: Vec::new(),
593 self_referencing: child.folded == table.folded,
594 });
595 }
596 }
597 }
598 planned
599}
600
601fn mismatch(child: &TableInfo, parent: &TableInfo) -> String {
603 format!(
604 "foreign key mismatch - \"{}\" referencing \"{}\"",
605 String::from_utf8_lossy(&child.name),
606 String::from_utf8_lossy(&parent.name)
607 )
608}
609
610fn unusable(
617 key: &ForeignKeyInfo,
618 message: String,
619 is_check: bool,
620 self_referencing: bool,
621) -> ForeignKeyTrigger {
622 ForeignKeyTrigger {
623 is_check,
624 deferred: key.is_deferred(),
625 trigger: None,
626 fault: message.into_bytes(),
627 self_referencing,
628 }
629}
630
631pub fn parent_key_is_unique(parent: &TableInfo, key: &ForeignKeyInfo) -> bool {
638 let Some(wanted) = parent_columns(key, parent) else {
639 return false;
640 };
641 let folded: Vec<Vec<u8>> = wanted
642 .iter()
643 .map(|name| name.to_ascii_lowercase())
644 .collect();
645 if folded.len() == 1 {
647 if let Some(alias) = parent.rowid_alias {
648 if let Some(column) = parent.columns.get(usize::from(alias)) {
649 if folded.first() == Some(&column.folded) {
650 return true;
651 }
652 }
653 }
654 }
655 let primary = parent.primary_key();
656 if !primary.is_empty() && primary.len() == folded.len() {
657 let names: Vec<Vec<u8>> = primary
658 .iter()
659 .filter_map(|position| parent.columns.get(usize::from(*position)))
660 .map(|column| column.folded.clone())
661 .collect();
662 if same_set(&names, &folded) {
663 return true;
664 }
665 }
666 parent.indexes.iter().any(|index| {
667 index.unique && index.columns.len() == folded.len() && {
668 let names: Vec<Vec<u8>> = index
669 .columns
670 .iter()
671 .filter_map(|key| key.column)
672 .filter_map(|position| parent.columns.get(usize::from(position)))
673 .map(|column| column.folded.clone())
674 .collect();
675 same_set(&names, &folded)
676 }
677 })
678}
679
680fn same_set(left: &[Vec<u8>], right: &[Vec<u8>]) -> bool {
685 left.len() == right.len() && right.iter().all(|name| left.contains(name))
686}
687
688pub fn violation_query(
696 child: &TableInfo,
697 parent: &TableInfo,
698 key: &ForeignKeyInfo,
699 database: &[u8],
700) -> Option<String> {
701 let near = child_columns(key, child)?;
702 let far = parent_columns(key, parent)?;
703 if near.len() != far.len() || near.is_empty() {
704 return None;
705 }
706 let mut guards: Vec<String> = near
707 .iter()
708 .map(|column| format!("c.{} IS NOT NULL", quote(column)))
709 .collect();
710 let lookup: Vec<String> = far
711 .iter()
712 .zip(near.iter())
713 .map(|(parent_column, child_column)| {
714 format!("p.{} = c.{}", quote(parent_column), quote(child_column))
715 })
716 .collect();
717 guards.push(format!(
718 "NOT EXISTS (SELECT 1 FROM {} AS p WHERE {})",
719 qualified(database, &parent.name),
720 conjunction(&lookup)
721 ));
722 let identity = if child.without_rowid {
725 "NULL"
726 } else {
727 "c.rowid"
728 };
729 Some(format!(
730 "SELECT {identity} FROM {} AS c WHERE {}",
731 qualified(database, &child.name),
732 conjunction(&guards)
733 ))
734}
735
736#[cfg(test)]
737mod tests {
738 use super::*;
739 use crate::catalog_view::{ColumnInfo, TableKind};
740 use inillucent_value::Affinity;
741
742 fn table(name: &[u8], columns: &[&[u8]]) -> TableInfo {
744 TableInfo {
745 name: name.to_vec(),
746 folded: name.to_ascii_lowercase(),
747 database: 0,
748 root: 2,
749 columns: columns
750 .iter()
751 .map(|column| ColumnInfo {
752 name: column.to_vec(),
753 folded: column.to_ascii_lowercase(),
754 declared_type: Vec::new(),
755 affinity: Affinity::Blob,
756 collation: b"binary".to_vec(),
757 not_null: false,
758 not_null_conflict: None,
759 primary_key_conflict: None,
760 default_sql: None,
761 primary_key_position: None,
762 hidden: false,
763 generated: false,
764 stored: false,
765 generated_sql: None,
766 })
767 .collect(),
768 rowid_alias: None,
769 without_rowid: false,
770 strict: false,
771 autoincrement: false,
772 kind: TableKind::Table,
773 create_sql: Vec::new(),
774 indexes: Vec::new(),
775 view: None,
776 triggers: Vec::new(),
777 analysed_rows: None,
778 checks: Vec::new(),
779 foreign_keys: Vec::new(),
780 foreign_key_triggers: Vec::new(),
781 module: None,
782 }
783 }
784
785 fn key(on_delete: ReferentialAction, on_update: ReferentialAction) -> ForeignKeyInfo {
787 ForeignKeyInfo {
788 id: 0,
789 columns: vec![1],
790 parent: b"p".to_vec(),
791 parent_folded: b"p".to_vec(),
792 parent_columns: vec![b"id".to_vec()],
793 on_delete,
794 on_update,
795 match_clause: Vec::new(),
796 deferrable: false,
797 initially_deferred: false,
798 cyclic: false,
799 }
800 }
801
802 #[test]
805 fn every_generated_trigger_parses() {
806 let child = table(b"c", &[b"id", b"pid"]);
807 let parent = table(b"p", &[b"id"]);
808 let limits = Limits::default();
809 let actions = [
810 ReferentialAction::NoAction,
811 ReferentialAction::Restrict,
812 ReferentialAction::Cascade,
813 ReferentialAction::SetNull,
814 ReferentialAction::SetDefault,
815 ];
816 let events = [
817 ForeignKeyEvent::ChildInsert,
818 ForeignKeyEvent::ChildUpdate,
819 ForeignKeyEvent::ParentDelete,
820 ForeignKeyEvent::ParentUpdate,
821 ];
822 for action in actions {
823 let key = key(action, action);
824 for event in events {
825 let built = trigger_for(&child, &parent, &key, event, b"main", false, &limits);
826 assert!(
827 built.is_some(),
828 "{action:?} on {event:?} produced no trigger"
829 );
830 }
831 }
832 }
833
834 #[test]
837 fn the_child_check_reads_as_it_should() {
838 let child = table(b"c", &[b"id", b"pid"]);
839 let parent = table(b"p", &[b"id"]);
840 let key = key(ReferentialAction::NoAction, ReferentialAction::NoAction);
841 let sql = child_check(
842 &child,
843 &parent,
844 &key,
845 ForeignKeyEvent::ChildInsert,
846 b"main",
847 &[b"pid".to_vec()],
848 &[b"id".to_vec()],
849 );
850 assert!(sql.contains("BEFORE INSERT ON \"c\""), "{sql}");
851 assert!(sql.contains("NEW.\"pid\" IS NOT NULL"), "{sql}");
852 assert!(sql.contains("NOT EXISTS"), "{sql}");
853 assert!(sql.contains("FOREIGN KEY constraint failed"), "{sql}");
854 }
855
856 #[test]
859 fn restrict_fires_before_and_no_action_after() {
860 let child = table(b"c", &[b"id", b"pid"]);
861 let parent = table(b"p", &[b"id"]);
862 let limits = Limits::default();
863 for (action, expected) in [
864 (ReferentialAction::Restrict, TriggerTime::Before),
865 (ReferentialAction::NoAction, TriggerTime::After),
866 ] {
867 let key = key(action, action);
868 let built = trigger_for(
869 &child,
870 &parent,
871 &key,
872 ForeignKeyEvent::ParentDelete,
873 b"main",
874 false,
875 &limits,
876 )
877 .expect("the trigger is generated");
878 assert_eq!(built.time, expected, "{action:?}");
879 }
880 }
881
882 #[test]
886 fn a_deferred_key_defers_only_its_checks() {
887 let child = table(b"c", &[b"id", b"pid"]);
888 let parent = table(b"p", &[b"id"]);
889 let limits = Limits::default();
890 let deferred = key(ReferentialAction::NoAction, ReferentialAction::NoAction);
891 assert!(trigger_for(
892 &child,
893 &parent,
894 &deferred,
895 ForeignKeyEvent::ChildInsert,
896 b"main",
897 true,
898 &limits
899 )
900 .is_none());
901 assert!(trigger_for(
902 &child,
903 &parent,
904 &deferred,
905 ForeignKeyEvent::ParentDelete,
906 b"main",
907 true,
908 &limits
909 )
910 .is_none());
911 let restrict = key(ReferentialAction::Restrict, ReferentialAction::Restrict);
912 assert!(trigger_for(
913 &child,
914 &parent,
915 &restrict,
916 ForeignKeyEvent::ParentDelete,
917 b"main",
918 true,
919 &limits
920 )
921 .is_some());
922 let cascade = key(ReferentialAction::Cascade, ReferentialAction::Cascade);
923 assert!(trigger_for(
924 &child,
925 &parent,
926 &cascade,
927 ForeignKeyEvent::ParentDelete,
928 b"main",
929 true,
930 &limits
931 )
932 .is_some());
933 }
934
935 #[test]
938 fn a_parent_update_guards_on_the_key_changing() {
939 let child = table(b"c", &[b"id", b"pid"]);
940 let parent = table(b"p", &[b"id"]);
941 let key = key(ReferentialAction::Cascade, ReferentialAction::Cascade);
942 let sql = parent_action(
943 &child,
944 &parent,
945 &key,
946 ForeignKeyEvent::ParentUpdate,
947 b"main",
948 &[b"pid".to_vec()],
949 &[b"id".to_vec()],
950 false,
951 )
952 .expect("the trigger is generated");
953 assert!(sql.contains("AFTER UPDATE OF \"id\""), "{sql}");
954 assert!(sql.contains("WHEN OLD.\"id\" IS NOT NEW.\"id\""), "{sql}");
955 assert!(sql.contains("SET \"pid\" = NEW.\"id\""), "{sql}");
956 assert!(sql.contains("WHERE \"pid\" = OLD.\"id\""), "{sql}");
957 }
958
959 #[test]
962 fn an_awkward_identifier_is_quoted() {
963 assert_eq!(quote(b"we\"ird"), "\"we\"\"ird\"");
964 let child = table(b"we\"ird", &[b"id", b"pid"]);
965 let parent = table(b"p", &[b"id"]);
966 let key = key(ReferentialAction::Cascade, ReferentialAction::Cascade);
967 let limits = Limits::default();
968 assert!(trigger_for(
969 &child,
970 &parent,
971 &key,
972 ForeignKeyEvent::ChildInsert,
973 b"main",
974 false,
975 &limits
976 )
977 .is_some());
978 }
979
980 #[test]
982 fn a_composite_key_compares_every_column() {
983 let matching = children_of(
984 &[b"a".to_vec(), b"b".to_vec()],
985 &[b"x".to_vec(), b"y".to_vec()],
986 "OLD",
987 );
988 assert_eq!(matching, "\"a\" = OLD.\"x\" AND \"b\" = OLD.\"y\"");
989 }
990}