Skip to main content

inillucent_sql/
foreign_key.rs

1//! Foreign keys, as the triggers they are.
2//!
3//! Invariant: a foreign key is enforced by exactly the machinery a written
4//! trigger is enforced by. The clause is turned into `CREATE TRIGGER` text,
5//! parsed by the same parser, bound by the same binder and inlined by the same
6//! compiler - so `ON DELETE CASCADE` and the `DELETE` somebody wrote by hand
7//! cannot disagree about what a conflict clause does, what `OLD` means, or what
8//! order things happen in. SQLite makes the same choice for the same reason.
9//!
10//! Generating text rather than building bound structures is deliberate. The
11//! text is printable, so a diagnostic can show what a constraint actually does,
12//! and it is the same shape a person would have written - which means every
13//! test that covers written triggers covers this too.
14//!
15//! Four kinds of trigger come out of one clause:
16//!
17//! - the child's check, on `INSERT` and on `UPDATE OF` its own key columns,
18//!   which refuses a row whose parent is not there;
19//! - the parent's check, on `DELETE` and on `UPDATE OF` its key, which refuses
20//!   to strand a child - this is `NO ACTION` and `RESTRICT`;
21//! - the parent's `CASCADE`, which deletes or updates the children with it;
22//! - the parent's `SET NULL` and `SET DEFAULT`, which keep the children and
23//!   let go of the key.
24//!
25//! Reference: <https://sqlite.org/foreignkeys.html>.
26
27use 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
35/// The message SQLite reports for every foreign-key violation.
36pub const VIOLATION_MESSAGE: &str = "FOREIGN KEY constraint failed";
37
38/// Which write a synthesised trigger is generated for.
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub enum ForeignKeyEvent {
41    /// A row is being added to the child table.
42    ChildInsert,
43    /// A row of the child table is being changed.
44    ChildUpdate,
45    /// A row is being taken out of the parent table.
46    ParentDelete,
47    /// A row of the parent table is being changed.
48    ParentUpdate,
49}
50
51impl ForeignKeyEvent {}
52
53/// The parent columns a key refers to.
54///
55/// A clause that named none refers to the parent's primary key, and that is
56/// resolved here rather than in the catalog because the catalog reads one table
57/// at a time and the parent may not have been read yet.
58pub 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
73/// Returns the child column names of a key, in the order they were written.
74fn 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
82/// Writes an identifier the way it can be read back.
83fn 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
94/// Returns an identifier as a quoted string.
95fn quote(name: &[u8]) -> String {
96    let mut out = String::new();
97    quoted(name, &mut out);
98    out
99}
100
101/// Returns `db."table"`, so a body cannot be captured by a `temp` table of the
102/// same name.
103fn qualified(database: &[u8], table: &[u8]) -> String {
104    let mut out = quote(database);
105    out.push('.');
106    quoted(table, &mut out);
107    out
108}
109
110/// Joins the parts of a key comparison with `AND`.
111fn conjunction(parts: &[String]) -> String {
112    if parts.is_empty() {
113        return "1".to_string();
114    }
115    parts.join(" AND ")
116}
117
118/// Returns `"c1" = OLD."p1" AND ...`, which finds the children of one parent.
119fn 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
127/// Returns `"c1" = OLD."p1" COLLATE ... AND ...`, which finds the children of
128/// one parent by the parent key's collation.
129///
130/// **SQLite compares a foreign key with the parent column's collation**, not
131/// the child's. The row's values reach the generated body as literals, which
132/// carry no collation of their own, so the parent's is written out.
133///
134/// @param child - the child's key column names
135/// @param parent - the parent's key column names
136/// @param collations - the collation of each parent column
137/// @param row - `OLD` or `NEW`
138fn 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
156/// Returns the collation of one column of a table, `binary` when it has none.
157///
158/// @param table - the table the column belongs to
159/// @param column - the column's name
160fn 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
169/// Returns the name a synthesised trigger is known by.
170///
171/// It has to be unique and it has to be stable: the binder's recursion guard is
172/// a list of names, so two different constraints on the same table must not
173/// collide, and the same constraint must be recognisable when the cascade
174/// reaches it again.
175fn 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
191/// Builds the trigger that enforces one key for one event, if there is one.
192///
193/// `None` means the event needs no trigger - a `NO ACTION` parent key whose
194/// checks are deferred to the commit, for instance, or a child key whose
195/// columns this update does not touch.
196pub 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
224/// Parses generated trigger text into the form the binder consumes.
225///
226/// A generator that produced text the parser refuses would be a defect this
227/// function cannot repair, so it returns `None` and the caller enforces
228/// nothing - which is caught by the tests rather than by a user.
229fn 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
263/// Generates the child's check: a row whose key is complete must have a parent.
264///
265/// A key with a NULL in it is not checked at all. That is `MATCH SIMPLE`, which
266/// is the only match mode SQLite implements whatever the clause says, and it is
267/// why the guard is a conjunction of `IS NOT NULL` rather than a single test.
268fn 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    // **A row may be its own parent.** SQLite accepts a row of a table that
287    // references itself when the row's key columns equal its own referenced
288    // columns, which `NOT EXISTS` cannot see because the row is not written
289    // yet. SQLite compares the two values as stored, with no affinity and the
290    // binary collation, so an integer parent key does not accept the text
291    // `'1'` as itself.
292    if parent.folded == child.folded {
293        let same: Vec<String> = near
294            .iter()
295            .zip(far.iter())
296            .map(|(child_column, parent_column)| {
297                // The unary plus strips the columns' affinities.
298                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
319/// Generates what happens to the children when a parent row goes or changes.
320fn 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            // RESTRICT is not deferrable: it refuses the write where it
340            // happens, whatever the constraint's timing says. NO ACTION with a
341            // deferred constraint is checked when the transaction commits, so
342            // there is no trigger for it here.
343            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    // RESTRICT fires before the parent row is written, the rest afterwards.
390    // The difference is visible: a `BEFORE DELETE` trigger that removes the
391    // children itself satisfies NO ACTION and does not satisfy RESTRICT.
392    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    // An update that leaves the key alone is not a change to the key, and
402    // firing for it would cascade a row onto itself.
403    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
423/// Renders a comma-separated list of quoted column names.
424fn column_list(columns: &[Vec<u8>]) -> String {
425    columns
426        .iter()
427        .map(|column| quote(column))
428        .collect::<Vec<_>>()
429        .join(", ")
430}
431
432/// Builds the triggers every table's writes fire because of a foreign key.
433///
434/// It runs once per schema, over every table at once, because that is the only
435/// point at which both sides of a key are visible: a child records the key and
436/// nothing records the reverse direction, so the parent's side is found by
437/// asking every table what it points at.
438///
439/// A key that cannot be enforced - a parent that is not there, or parent
440/// columns that are not a key of the parent - produces an entry with no trigger
441/// and the message to report. That is SQLite's timing: the schema loads, and
442/// the first write that needs the constraint is what fails.
443pub 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
454/// Marks every key whose parent can lead back to its own child table.
455///
456/// The graph is small - one node per table, one edge per key - so the search is
457/// a plain walk from each key's parent looking for its child. What it answers
458/// is whether applying this key's action can fire the same key again.
459fn 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
476/// Reports whether `from` can reach `wanted` by following child-to-parent
477/// edges backwards, which is the direction an action travels.
478fn 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
498/// Returns the statement that repairs one cyclic key, or `None` when the key
499/// has nothing to repair.
500///
501/// This is the other half of a cyclic action. The trigger takes the first
502/// level - the rows that pointed directly at the row that went - and this
503/// takes what that leaves: every row whose key now has no parent. Repeating it
504/// until nothing changes reaches the leaves, however deep they are, and it
505/// terminates because every pass either changes a row or stops.
506///
507/// `NO ACTION` and `RESTRICT` are absent on purpose: they refuse rather than
508/// repair, and the trigger has already refused.
509pub 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
578/// Builds the entries for one table, both directions.
579fn 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                    // RESTRICT refuses, and is never deferred; NO ACTION
652                    // refuses and is deferred with its key; the three that
653                    // repair are not checks at all.
654                    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
666/// Returns the message SQLite reports for a key whose parent does not match.
667fn 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
675/// Returns an entry that reports a fault instead of enforcing anything.
676///
677/// @param key - the key that cannot be enforced
678/// @param message - what to report when something writes
679/// @param is_check - whether it would have refused rather than repaired
680/// @param self_referencing - whether the key's child and parent are one table
681fn 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
696/// Reports whether a key's parent columns are a key of the parent.
697///
698/// SQLite requires it: the parent columns must be the primary key or carry a
699/// UNIQUE index, because a key that could match two parent rows would make
700/// `ON DELETE CASCADE` ambiguous. A parent that does not satisfy it is a
701/// `foreign key mismatch`, reported when something writes.
702pub 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    // A single column that is the rowid alias is the table's own key.
711    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
753/// Reports whether an index orders each key column by that column's own collation.
754///
755/// **SQLite does not take an index as the parent key when it orders a column
756/// differently from the column's declaration.** `UNIQUE (code COLLATE NOCASE)`
757/// over a `code TEXT` column compares keys the way the column does not, so a
758/// foreign key onto `code` is a `foreign key mismatch`.
759///
760/// @param parent - the parent table
761/// @param index - the index being considered as the parent key
762fn 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
774/// Reports whether two column lists name the same columns, in any order.
775///
776/// Order does not matter to a key: `REFERENCES p(a, b)` is satisfied by a
777/// unique index on `(b, a)`, because either one makes the pair unique.
778fn same_set(left: &[Vec<u8>], right: &[Vec<u8>]) -> bool {
779    left.len() == right.len() && right.iter().all(|name| left.contains(name))
780}
781
782/// Returns the `SELECT` that finds every row of a child table whose key names a
783/// parent table that does not exist.
784///
785/// SQLite's `PRAGMA foreign_key_check` skips only the rows with a NULL in one
786/// of the key columns; every other row has no parent because there is no
787/// parent table to hold one.
788///
789/// @param child - the table that declares the key
790/// @param key - the key
791/// @param database - the schema the child is in
792pub 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
813/// Returns the `SELECT` that finds every row of a child table whose key has no
814/// parent, which is what `PRAGMA foreign_key_check` reports and what a deferred
815/// constraint is tested with at commit.
816///
817/// It is a query rather than a scan written by hand, so it uses the planner and
818/// the indexes an ordinary query would - a check over a million-row child with
819/// an index on its key is an index lookup per row, not a second scan.
820pub 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    // A WITHOUT ROWID table has no rowid to report, and SQLite prints NULL
848    // for it rather than refusing to check the table.
849    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    /// Builds a table with the named columns, for the generator tests.
868    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    /// Builds a key over one child column pointing at one parent column.
911    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    /// Every generated trigger has to parse. A generator that produced text the
928    /// parser refuses would enforce nothing at all, silently.
929    #[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    /// The child's check fires before the write, tests every key column for
960    /// NULL, and looks the parent up by the columns the clause named.
961    #[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    /// RESTRICT fires before the parent write and NO ACTION after it, which is
982    /// the one place the two differ.
983    #[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    /// A deferred constraint generates no check on the child and no NO ACTION
1008    /// on the parent - both wait for the commit - but RESTRICT and the cascades
1009    /// still fire where they are.
1010    #[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    /// A parent update fires only when the key actually changed, and cascades
1061    /// the new key onto the rows that carried the old one.
1062    #[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    /// An identifier with a quote in it survives the round trip, because the
1085    /// generated text is parsed again rather than merely printed.
1086    #[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    /// A composite key compares every column, in the order the clause wrote.
1106    #[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}