Skip to main content

wasm_dbms/integrity/
update.rs

1// Rust guideline compliant 2026-03-01
2// X-WHERE-CLAUSE, M-CANONICAL-DOCS
3
4//! Integrity validator for update operations.
5
6use wasm_dbms_api::prelude::{
7    ColumnDef, Database as _, DbmsError, DbmsResult, Filter, Query, QueryError, TableSchema, Value,
8};
9use wasm_dbms_memory::prelude::{AccessControl, AccessControlList, MemoryProvider};
10
11use super::common;
12use crate::database::WasmDbmsDatabase;
13
14/// Integrity validator for update operations.
15///
16/// Unlike [`super::InsertIntegrityValidator`], this validator allows the
17/// primary key to remain unchanged during an update.
18pub struct UpdateIntegrityValidator<'a, T, M, A = AccessControlList>
19where
20    T: TableSchema,
21    M: MemoryProvider,
22    A: AccessControl,
23{
24    database: &'a WasmDbmsDatabase<'a, M, A>,
25    /// The current primary key value of the record being updated.
26    old_pk: Value,
27    _marker: std::marker::PhantomData<T>,
28}
29
30impl<'a, T, M, A> UpdateIntegrityValidator<'a, T, M, A>
31where
32    T: TableSchema,
33    M: MemoryProvider,
34    A: AccessControl,
35{
36    /// Creates a new update integrity validator.
37    pub fn new(dbms: &'a WasmDbmsDatabase<'a, M, A>, old_pk: Value) -> Self {
38        Self {
39            database: dbms,
40            old_pk,
41            _marker: std::marker::PhantomData,
42        }
43    }
44}
45
46impl<T, M, A> UpdateIntegrityValidator<'_, T, M, A>
47where
48    T: TableSchema,
49    M: MemoryProvider,
50    A: AccessControl,
51{
52    /// Verifies whether the given updated record values are valid.
53    pub fn validate(&self, record_values: &[(ColumnDef, Value)]) -> DbmsResult<()> {
54        for (col, value) in record_values {
55            common::check_column_validate::<T>(col, value)?;
56        }
57        self.check_primary_key_conflict(record_values)?;
58        common::check_foreign_keys::<T>(self.database, record_values)?;
59        common::check_non_nullable_fields::<T>(record_values)?;
60
61        Ok(())
62    }
63
64    /// Checks for primary key conflicts with *other* records.
65    fn check_primary_key_conflict(&self, record_values: &[(ColumnDef, Value)]) -> DbmsResult<()> {
66        let pk_name = T::primary_key();
67        let new_pk = record_values
68            .iter()
69            .find(|(col_def, _)| col_def.name == pk_name)
70            .map(|(_, value)| value.clone())
71            .ok_or(DbmsError::Query(QueryError::MissingNonNullableField(
72                pk_name.to_string(),
73            )))?;
74
75        let query = Query::builder()
76            .field(pk_name)
77            .and_where(Filter::Eq(pk_name.to_string(), new_pk.clone()))
78            .build();
79
80        let res = self.database.select::<T>(query)?;
81        match res.len() {
82            0 => Ok(()),
83            1 => {
84                if new_pk == self.old_pk {
85                    Ok(())
86                } else {
87                    Err(DbmsError::Query(QueryError::PrimaryKeyConflict))
88                }
89            }
90            _ => Err(DbmsError::Query(QueryError::PrimaryKeyConflict)),
91        }
92    }
93}