Skip to main content

wasm_dbms/
database.rs

1//! Core DBMS database struct providing CRUD and transaction operations.
2
3mod access_planner;
4mod aggregate;
5mod filter_analyzer;
6mod index_reader;
7mod migration;
8mod plan_executor;
9
10use std::cmp::Ordering;
11use std::collections::{BTreeSet, HashSet};
12use std::ops::ControlFlow;
13
14use wasm_dbms_api::prelude::{
15    AggregateFunction, AggregatedRow, ColumnDef, Database, DbmsError, DbmsResult, DeleteBehavior,
16    Filter, ForeignFetcher, ForeignKeyDef, InsertRecord, JoinResultSet, MigrationError,
17    MigrationOp, MigrationPolicy, OrderDirection, Query, QueryError, TableColumns, TableError,
18    TableRecord, TableSchema, TransactionError, TransactionId, UpdateRecord, Value, ValuesSource,
19};
20use wasm_dbms_memory::RecordAddress;
21use wasm_dbms_memory::prelude::{MemoryAccess, MemoryProvider, NextRecord, TableRegistry};
22
23pub(crate) use self::access_planner::MAX_ALTERNATIVES;
24use self::access_planner::{AccessPath, collect_filter_columns, is_fallible, plan_filter};
25use self::index_reader::{IndexReader, IndexScan};
26use self::plan_executor::{Budget, materialize, plan_access};
27use crate::context::{AccessKind, DbmsContext};
28use crate::database::migration::snapshots;
29use crate::schema::DatabaseSchema;
30use crate::transaction::journal::{Journal, JournaledWriter};
31use crate::transaction::{DatabaseOverlay, TableOverlay, Transaction, TransactionOp};
32
33/// Default capacity for SELECT queries.
34const DEFAULT_SELECT_CAPACITY: usize = 128;
35
36fn prime_drift_cache<M>(ctx: &DbmsContext<M>, schema: &dyn DatabaseSchema<M>)
37where
38    M: MemoryProvider,
39{
40    let compiled_hash = snapshots::compute_hash(schema.compiled_snapshots_dyn());
41    let drifted = ctx.schema_registry.borrow().schema_hash() != compiled_hash;
42    ctx.set_drift(compiled_hash, drifted);
43}
44
45/// The main DBMS database struct, generic over `MemoryProvider`.
46///
47/// This struct borrows from a [`DbmsContext`] and provides all CRUD
48/// operations, transaction management, and query execution.
49pub struct WasmDbmsDatabase<'ctx, M>
50where
51    M: MemoryProvider,
52{
53    /// Reference to the DBMS context owning all state.
54    ctx: &'ctx DbmsContext<M>,
55    /// Schema for dynamic dispatch of table operations.
56    schema: Box<dyn DatabaseSchema<M> + 'ctx>,
57    /// Active transaction ID, if any.
58    transaction: Option<TransactionId>,
59}
60
61impl<'ctx, M> WasmDbmsDatabase<'ctx, M>
62where
63    M: MemoryProvider,
64{
65    /// Creates a one-shot (non-transactional) database instance.
66    pub fn oneshot(ctx: &'ctx DbmsContext<M>, schema: impl DatabaseSchema<M> + 'ctx) -> Self {
67        let schema = Box::new(schema);
68        prime_drift_cache(ctx, schema.as_ref());
69        Self {
70            ctx,
71            schema,
72            transaction: None,
73        }
74    }
75
76    /// Creates a transactional database instance.
77    pub fn from_transaction(
78        ctx: &'ctx DbmsContext<M>,
79        schema: impl DatabaseSchema<M> + 'ctx,
80        transaction_id: TransactionId,
81    ) -> Self {
82        let schema = Box::new(schema);
83        prime_drift_cache(ctx, schema.as_ref());
84        Self {
85            ctx,
86            schema,
87            transaction: Some(transaction_id),
88        }
89    }
90
91    /// Executes a closure with a mutable reference to the current transaction.
92    fn with_transaction_mut<F, R>(&self, f: F) -> DbmsResult<R>
93    where
94        F: FnOnce(&mut Transaction) -> DbmsResult<R>,
95    {
96        let txid = self.transaction.as_ref().ok_or(DbmsError::Transaction(
97            TransactionError::NoActiveTransaction,
98        ))?;
99
100        let mut ts = self.ctx.transaction_session.borrow_mut();
101        let tx = ts.get_transaction_mut(txid)?;
102        f(tx)
103    }
104
105    /// Executes a closure with a reference to the current transaction.
106    fn with_transaction<F, R>(&self, f: F) -> DbmsResult<R>
107    where
108        F: FnOnce(&Transaction) -> DbmsResult<R>,
109    {
110        let txid = self.transaction.as_ref().ok_or(DbmsError::Transaction(
111            TransactionError::NoActiveTransaction,
112        ))?;
113
114        let ts = self.ctx.transaction_session.borrow();
115        let tx = ts.get_transaction(txid)?;
116        f(tx)
117    }
118
119    /// Validates captured row versions and combines them into one commit filter.
120    fn validated_transaction_filter(
121        &self,
122        table: &'static str,
123        filters: Vec<Filter>,
124    ) -> DbmsResult<Option<Filter>> {
125        for filter in &filters {
126            let rows = self.schema.select(
127                self,
128                table,
129                Query::builder().all().filter(Some(filter.clone())).build(),
130            )?;
131            if rows.len() != 1 {
132                return Err(DbmsError::Query(QueryError::ConstraintViolation(format!(
133                    "transaction conflict on table '{table}'"
134                ))));
135            }
136        }
137
138        Ok(filters.into_iter().reduce(Filter::or))
139    }
140
141    /// Returns the cached drift flag, computing and caching it on first call.
142    ///
143    /// `O(tables × snapshot bytes)` on the first invocation; `O(1)` thereafter.
144    /// Cleared by [`Self::migrate`] on success so subsequent CRUD calls
145    /// recompute against the new state.
146    fn drift_check(&self) -> DbmsResult<bool> {
147        if self.ctx.is_migrating() {
148            return Ok(false);
149        }
150        let compiled_hash = snapshots::compute_hash(self.schema.compiled_snapshots_dyn());
151        if let Some(cached) = self.ctx.cached_drift_for(compiled_hash) {
152            return Ok(cached);
153        }
154        let drifted = self.ctx.schema_registry.borrow().schema_hash() != compiled_hash;
155        self.ctx.set_drift(compiled_hash, drifted);
156        Ok(drifted)
157    }
158
159    /// Returns `Err(SchemaDrift)` when the cached drift flag is set.
160    ///
161    /// Called as the first line of every CRUD entry point on the
162    /// [`Database`] trait so reads and writes refuse to proceed against a
163    /// schema the storage layer cannot interpret.
164    fn ensure_no_drift(&self) -> DbmsResult<()> {
165        if self.drift_check()? {
166            return Err(DbmsError::Migration(MigrationError::SchemaDrift));
167        }
168        Ok(())
169    }
170
171    /// Executes a closure atomically using a write-ahead journal.
172    ///
173    /// All writes performed inside `f` are recorded. On success the journal
174    /// is committed (entries discarded). On error the journal is rolled back,
175    /// restoring every modified byte to its pre-call state.
176    ///
177    /// When a journal is already active (e.g., inside [`Database::commit`]),
178    /// this method delegates to the outer journal and does not manage its own.
179    ///
180    /// # Panics
181    ///
182    /// Panics if the rollback itself fails, because a failed rollback leaves
183    /// memory in an irrecoverably corrupt state (M-PANIC-ON-BUG).
184    fn atomic<F, R>(&self, f: F) -> DbmsResult<R>
185    where
186        F: FnOnce(&WasmDbmsDatabase<'ctx, M>) -> DbmsResult<R>,
187    {
188        let nested = self.ctx.journal.borrow().is_some();
189        if !nested {
190            *self.ctx.journal.borrow_mut() = Some(Journal::new());
191        }
192        match f(self) {
193            Ok(res) => {
194                if !nested && let Some(journal) = self.ctx.journal.borrow_mut().take() {
195                    journal.commit();
196                }
197                Ok(res)
198            }
199            Err(err) => {
200                if !nested && let Some(journal) = self.ctx.journal.borrow_mut().take() {
201                    journal
202                        .rollback(&mut self.ctx.mm.borrow_mut())
203                        .expect("critical: failed to rollback journal");
204                }
205                Err(err)
206            }
207        }
208    }
209
210    /// Checks whether any foreign key references exist for the given record.
211    ///
212    /// Returns `true` if at least one referencing row exists in any table.
213    fn has_foreign_key_references<T>(
214        &self,
215        record_values: &[(ColumnDef, Value)],
216    ) -> DbmsResult<bool>
217    where
218        T: TableSchema,
219    {
220        for (table, column, value) in self.referencing_columns::<T>(record_values)? {
221            let filter = Filter::eq(column, value);
222            let query = Query::builder().field(column).filter(Some(filter)).build();
223            let rows = self.schema.select(self, table, query)?;
224            if !rows.is_empty() {
225                return Ok(true);
226            }
227        }
228        Ok(false)
229    }
230
231    /// Deletes foreign key related records recursively for cascade deletes.
232    fn delete_foreign_keys_cascade<T>(
233        &self,
234        record_values: &[(ColumnDef, Value)],
235    ) -> DbmsResult<u64>
236    where
237        T: TableSchema,
238    {
239        let mut count = 0;
240        for (table, column, value) in self.referencing_columns::<T>(record_values)? {
241            let filter = Filter::eq(column, value);
242            count += self
243                .schema
244                .delete(self, table, DeleteBehavior::Cascade, Some(filter))?;
245        }
246        Ok(count)
247    }
248
249    /// Lists the foreign key columns referencing the given record of `T`.
250    ///
251    /// Each entry is the referencing table, its foreign key column and the value of the
252    /// referenced column of the record, i.e. the value a referencing row stores.
253    fn referencing_columns<T>(
254        &self,
255        record_values: &[(ColumnDef, Value)],
256    ) -> DbmsResult<Vec<(&'static str, &'static str, Value)>>
257    where
258        T: TableSchema,
259    {
260        let mut referencing = Vec::new();
261        for (table, columns) in self.schema.referenced_tables(T::table_name()) {
262            for column in columns {
263                let referenced_column = self.referenced_column(table, column)?;
264                let value = record_values
265                    .iter()
266                    .find(|(col_def, _)| col_def.name == referenced_column)
267                    .map(|(_, value)| value.clone())
268                    .ok_or_else(|| {
269                        DbmsError::Query(QueryError::UnknownColumn(referenced_column.to_string()))
270                    })?;
271                referencing.push((table, column, value));
272            }
273        }
274        Ok(referencing)
275    }
276
277    /// Returns the column of `T` referenced by the foreign key `column` of `table`.
278    ///
279    /// # Errors
280    ///
281    /// Returns an error if the schema does not know `table`, or if `column` is not a foreign key
282    /// of `table`.
283    fn referenced_column(&self, table: &str, column: &str) -> DbmsResult<&'static str> {
284        self.schema
285            .table_columns(table)?
286            .iter()
287            .find(|col_def| col_def.name == column)
288            .and_then(|col_def| col_def.foreign_key.as_ref())
289            .map(|fk| fk.foreign_column)
290            .ok_or_else(|| DbmsError::Query(QueryError::UnknownColumn(column.to_string())))
291    }
292
293    /// Retrieves the current overlay from the active transaction.
294    fn overlay(&self) -> DbmsResult<DatabaseOverlay> {
295        self.with_transaction(|tx| Ok(tx.overlay().clone()))
296    }
297
298    /// Returns whether the record matches the provided filter.
299    fn record_matches_filter(
300        &self,
301        record_values: &[(ColumnDef, Value)],
302        filter: &Filter,
303    ) -> DbmsResult<bool> {
304        filter.matches(record_values).map_err(DbmsError::from)
305    }
306
307    /// Removes duplicate records based on the values of the given columns.
308    ///
309    /// Keeps the first record encountered for each distinct combination of the
310    /// values of the specified columns. Columns missing from a record are
311    /// treated as [`Value::Null`]. When `distinct_by` is empty, no records are
312    /// removed.
313    fn apply_distinct(&self, results: &mut Vec<TableColumns>, distinct_by: &[String]) {
314        if distinct_by.is_empty() {
315            return;
316        }
317        let mut seen: HashSet<Vec<Value>> = HashSet::new();
318        results.retain(|record| {
319            let key: Vec<Value> = distinct_by
320                .iter()
321                .map(|col| {
322                    Self::this_columns(record)
323                        .and_then(|cols| {
324                            cols.iter()
325                                .find(|(cd, _)| cd.name == col.as_str())
326                                .map(|(_, v)| v.clone())
327                        })
328                        .unwrap_or(Value::Null)
329                })
330                .collect();
331            seen.insert(key)
332        });
333    }
334
335    /// Filters record columns down to only the selected fields.
336    fn apply_column_selection<T>(&self, results: &mut [TableColumns], query: &Query)
337    where
338        T: TableSchema,
339    {
340        if query.all_selected() {
341            return;
342        }
343        let selected_columns = query.columns::<T>();
344        results
345            .iter_mut()
346            .flat_map(|record| record.iter_mut())
347            .filter(|(source, _)| *source == ValuesSource::This)
348            .for_each(|(_, cols)| {
349                cols.retain(|(col_def, _)| selected_columns.contains(&col_def.name.to_string()));
350            });
351    }
352
353    /// Batch-fetches eager relations for collected results.
354    fn batch_load_eager_relations<T>(
355        &self,
356        results: &mut [TableColumns],
357        query: &Query,
358    ) -> DbmsResult<()>
359    where
360        T: TableSchema,
361    {
362        if query.eager_relations.is_empty() {
363            return Ok(());
364        }
365
366        let fetcher = T::foreign_fetcher();
367
368        for relation in &query.eager_relations {
369            let fk_columns = Self::collect_fk_values::<T>(results, relation)?;
370
371            for (local_column, pk_values) in &fk_columns {
372                let batch_map = fetcher.fetch_batch(self, relation, local_column, pk_values)?;
373
374                Self::verify_fk_batch(&batch_map, pk_values, relation)?;
375                Self::attach_foreign_data(results, &batch_map, relation, local_column);
376            }
377        }
378
379        Ok(())
380    }
381
382    /// Collects distinct FK values across all records for a given relation.
383    fn collect_fk_values<T>(
384        results: &[TableColumns],
385        relation: &str,
386    ) -> DbmsResult<Vec<(&'static str, Vec<Value>)>>
387    where
388        T: TableSchema,
389    {
390        let mut fk_columns: Vec<(&'static str, HashSet<Value>)> = vec![];
391
392        for record_columns in results {
393            let Some(cols) = Self::this_columns(record_columns) else {
394                continue;
395            };
396
397            let mut found_fk = false;
398            for (col_def, value) in cols {
399                let Some(fk) = &col_def.foreign_key else {
400                    continue;
401                };
402                if *fk.foreign_table != *relation {
403                    continue;
404                }
405
406                found_fk = true;
407                // a null foreign key references no record, so there is nothing to fetch
408                if value.is_null() {
409                    continue;
410                }
411                match fk_columns.iter_mut().find(|(lc, _)| *lc == fk.local_column) {
412                    Some((_, values)) => {
413                        values.insert(value.clone());
414                    }
415                    None => {
416                        let mut set = HashSet::new();
417                        set.insert(value.clone());
418                        fk_columns.push((fk.local_column, set));
419                    }
420                }
421            }
422
423            if !found_fk {
424                return Err(DbmsError::Query(QueryError::InvalidQuery(format!(
425                    "Cannot load relation '{relation}' for table '{}': no foreign key found",
426                    T::table_name()
427                ))));
428            }
429        }
430
431        Ok(fk_columns
432            .into_iter()
433            .map(|(col, set)| (col, set.into_iter().collect()))
434            .collect())
435    }
436
437    /// Verifies all FK values were found in the batch result.
438    fn verify_fk_batch(
439        batch_map: &std::collections::HashMap<Value, Vec<(ColumnDef, Value)>>,
440        pk_values: &[Value],
441        relation: &str,
442    ) -> DbmsResult<()> {
443        if let Some(missing) = pk_values.iter().find(|v| !batch_map.contains_key(v)) {
444            return Err(DbmsError::Query(QueryError::BrokenForeignKeyReference {
445                table: relation.to_string(),
446                key: missing.clone(),
447            }));
448        }
449        Ok(())
450    }
451
452    /// Attaches batch-fetched foreign data to each record.
453    fn attach_foreign_data(
454        results: &mut [TableColumns],
455        batch_map: &std::collections::HashMap<Value, Vec<(ColumnDef, Value)>>,
456        relation: &str,
457        local_column: &str,
458    ) {
459        for record_columns in results.iter_mut() {
460            let fk_value = Self::this_columns(record_columns).and_then(|cols| {
461                cols.iter().find_map(|(col_def, value)| {
462                    let fk = col_def.foreign_key.as_ref()?;
463                    (fk.foreign_table == relation && fk.local_column == local_column)
464                        .then(|| value.clone())
465                })
466            });
467
468            let Some(fk_val) = fk_value else { continue };
469            let Some(foreign_values) = batch_map.get(&fk_val) else {
470                continue;
471            };
472
473            record_columns.push((
474                ValuesSource::Foreign {
475                    table: relation.to_string(),
476                    column: local_column.to_string(),
477                },
478                foreign_values.clone(),
479            ));
480        }
481    }
482
483    /// Extracts the `ValuesSource::This` columns from a record.
484    fn this_columns(
485        record: &[(ValuesSource, Vec<(ColumnDef, Value)>)],
486    ) -> Option<&Vec<(ColumnDef, Value)>> {
487        record
488            .iter()
489            .find(|(src, _)| *src == ValuesSource::This)
490            .map(|(_, cols)| cols)
491    }
492
493    /// Retrieves existing rows matching a filter, returning `(primary_key, full_row)` pairs.
494    #[expect(
495        clippy::type_complexity,
496        reason = "complex return type is necessary for returning both PK and full row data"
497    )]
498    fn existing_rows_for_filter<T>(
499        &self,
500        filter: Option<Filter>,
501    ) -> DbmsResult<Vec<(Value, Vec<(ColumnDef, Value)>)>>
502    where
503        T: TableSchema,
504    {
505        let pk = T::primary_key();
506        let query = Query::builder().filter(filter).build();
507        let records = self.select::<T>(query)?;
508        let rows = records
509            .into_iter()
510            .map(|record| {
511                let values = record.to_values();
512                let pk_value = values
513                    .iter()
514                    .find(|(col_def, _)| col_def.name == pk)
515                    .expect("primary key not found")
516                    .1
517                    .clone();
518                (pk_value, values)
519            })
520            .collect();
521
522        Ok(rows)
523    }
524
525    /// Loads the table registry for a given table schema.
526    fn load_table_registry<T>(&self) -> DbmsResult<TableRegistry>
527    where
528        T: TableSchema,
529    {
530        let sr = self.ctx.schema_registry.borrow();
531        let registry_pages = sr
532            .table_registry_page::<T>()
533            .ok_or(DbmsError::Table(TableError::TableNotFound))?;
534
535        let mut mm = self.ctx.mm.borrow_mut();
536        TableRegistry::load(registry_pages, &mut *mm).map_err(DbmsError::from)
537    }
538
539    /// Sorts query results by a column.
540    fn sort_query_results(
541        &self,
542        results: &mut [TableColumns],
543        column: &str,
544        direction: OrderDirection,
545    ) {
546        results.sort_by(|a, b| {
547            fn get_value<'a>(
548                values: &'a [(ValuesSource, Vec<(ColumnDef, Value)>)],
549                column: &str,
550            ) -> Option<&'a Value> {
551                values
552                    .iter()
553                    .find(|(source, _)| *source == ValuesSource::This)
554                    .and_then(|(_, cols)| {
555                        cols.iter()
556                            .find(|(col_def, _)| col_def.name == column)
557                            .map(|(_, value)| value)
558                    })
559            }
560
561            let a_value = get_value(a, column);
562            let b_value = get_value(b, column);
563
564            sort_values_with_direction(a_value, b_value, direction)
565        });
566    }
567
568    /// Applies the transaction `changes` to a stored row, then checks it against `filter`.
569    ///
570    /// Returns [`None`] when the transaction removed the row or the patched row does not match.
571    fn patch_and_filter(
572        &self,
573        changes: Option<&TableOverlay>,
574        values: Vec<(ColumnDef, Value)>,
575        filter: Option<&Filter>,
576    ) -> DbmsResult<Option<Vec<(ColumnDef, Value)>>> {
577        let values = match changes {
578            Some(overlay) => match overlay.patch_row(values) {
579                Some(patched_values) => patched_values,
580                None => return Ok(None),
581            },
582            None => values,
583        };
584
585        if let Some(filter) = filter
586            && !self.record_matches_filter(&values, filter)?
587        {
588            return Ok(None);
589        }
590
591        Ok(Some(values))
592    }
593
594    /// Returns the persistent addresses an access path selects, plus every
595    /// stored row the transaction updated, or `None` when the path exceeds
596    /// its budget and the caller must scan.
597    fn candidate_addresses<T, MA>(
598        &self,
599        reader: &IndexReader<'_>,
600        path: &AccessPath,
601        changes: Option<&TableOverlay>,
602        mm: &mut MA,
603    ) -> DbmsResult<Option<Vec<RecordAddress>>>
604    where
605        T: TableSchema,
606        MA: MemoryAccess,
607    {
608        let mut budget = Budget::default_limits();
609        let addresses = materialize(reader, path, &mut budget, mm)?;
610        self.ctx
611            .count_access(AccessKind::IndexEntry, budget.entries_read());
612        let Some(mut addresses) = addresses else {
613            return Ok(None);
614        };
615
616        if let Some(changes) = changes {
617            // Updated rows may have moved into the predicate from keys the
618            // path never read; fetch them by primary key and re-check them.
619            let pk_columns = [T::primary_key()];
620            let mut columns = BTreeSet::from([T::primary_key()]);
621            path.collect_constrained_columns(&mut columns);
622            for pk in changes.updated_pks(&columns) {
623                let found = reader.exact(&pk_columns, &[pk], mm)?;
624                self.ctx
625                    .count_access(AccessKind::IndexEntry, found.len() as u64);
626                addresses.extend(found);
627            }
628            addresses.sort_unstable();
629            addresses.dedup();
630        }
631        Ok(Some(addresses))
632    }
633
634    /// Serves a read from index keys alone when one index holds every column
635    /// the query needs, or returns `None`.
636    ///
637    /// Eligible reads are non-transactional, load no relations, and use a
638    /// single index range: the filter's plan when it is one range, or a full
639    /// scan of the first covering index for an unfiltered projection.
640    /// Distinct, ordering, and pagination run in the usual pipeline.
641    #[expect(
642        clippy::type_complexity,
643        reason = "database rows retain their schema and value pairs"
644    )]
645    fn try_covering_select<T>(
646        &self,
647        query: &Query,
648        table_registry: &TableRegistry,
649    ) -> DbmsResult<Option<Vec<Vec<(ColumnDef, Value)>>>>
650    where
651        T: TableSchema,
652    {
653        if self.transaction.is_some() || !query.eager_relations.is_empty() {
654            return Ok(None);
655        }
656        let filter = query.filter.as_ref();
657        if filter.is_some_and(is_fallible) {
658            return Ok(None);
659        }
660        let mut needed: BTreeSet<String> = query.columns::<T>().into_iter().collect();
661        needed.extend(query.order_by.iter().map(|(column, _)| column.clone()));
662        needed.extend(query.distinct_by.iter().cloned());
663        if let Some(filter) = filter {
664            collect_filter_columns(filter, &mut needed);
665        }
666        let covers = |columns: &[&str]| {
667            needed
668                .iter()
669                .all(|column| columns.contains(&column.as_str()))
670        };
671
672        let scan = match filter {
673            Some(filter) => match plan_filter(filter, T::indexes(), T::columns()) {
674                Some(AccessPath::Scan(scan)) => scan,
675                _ => return Ok(None),
676            },
677            None => match T::indexes().iter().find(|index| covers(index.columns())) {
678                Some(index) => IndexScan::prefix(index.columns(), Vec::new()),
679                None => return Ok(None),
680            },
681        };
682        if !covers(scan.columns) {
683            return Ok(None);
684        }
685
686        if query.limit == Some(0) {
687            return Ok(Some(Vec::new()));
688        }
689        let stop_after = if query.order_by.is_empty() && query.distinct_by.is_empty() {
690            query
691                .limit
692                .map(|limit| query.offset.unwrap_or_default().saturating_add(limit))
693        } else {
694            None
695        };
696
697        // Key positions of the covered columns, in table column order.
698        let layout: Vec<(usize, ColumnDef)> = T::columns()
699            .iter()
700            .filter_map(|column| {
701                scan.columns
702                    .iter()
703                    .position(|name| *name == column.name)
704                    .map(|position| (position, *column))
705            })
706            .collect();
707        let mut rows = Vec::new();
708        let mut entries = 0u64;
709        let mut mm = self.ctx.mm.borrow_mut();
710        IndexReader::new(table_registry.index_ledger()).for_each_entry(
711            &scan,
712            &mut *mm,
713            |key, _| {
714                entries += 1;
715                let values: Vec<(ColumnDef, Value)> = layout
716                    .iter()
717                    .map(|(position, column)| (*column, key[*position].clone()))
718                    .collect();
719                if let Some(filter) = &query.filter
720                    && !self.record_matches_filter(&values, filter)?
721                {
722                    return Ok(ControlFlow::Continue(()));
723                }
724                rows.push(values);
725                Ok::<_, DbmsError>(if stop_after.is_some_and(|limit| rows.len() >= limit) {
726                    ControlFlow::Break(())
727                } else {
728                    ControlFlow::Continue(())
729                })
730            },
731        )?;
732        self.ctx.count_access(AccessKind::IndexEntry, entries);
733        Ok(Some(rows))
734    }
735
736    /// Selects rows through an index access path, or returns `None` when no
737    /// index helps or the path exceeds its budget.
738    ///
739    /// Stored rows are patched with the transaction's changes, inserted rows
740    /// are added, and each visible primary key is returned once. Infallible
741    /// filters are checked in full. Fallible filters first check the visible
742    /// index key, then evaluate the legacy residual to preserve error behavior.
743    #[expect(
744        clippy::type_complexity,
745        reason = "database rows retain their schema and value pairs"
746    )]
747    fn try_index_select<T>(
748        &self,
749        query: &Query,
750        table_registry: &TableRegistry,
751        table_overlay: &DatabaseOverlay,
752    ) -> DbmsResult<Option<Vec<Vec<(ColumnDef, Value)>>>>
753    where
754        T: TableSchema,
755    {
756        let Some(filter) = &query.filter else {
757            return Ok(None);
758        };
759        let Some(planned) = plan_access(filter, T::indexes(), T::columns()) else {
760            return Ok(None);
761        };
762        let changes = table_overlay.table_overlay(T::table_name());
763        let mut mm = self.ctx.mm.borrow_mut();
764        let reader = IndexReader::new(table_registry.index_ledger());
765        let Some(addresses) =
766            self.candidate_addresses::<T, _>(&reader, &planned.path, changes, &mut *mm)?
767        else {
768            return Ok(None);
769        };
770
771        let legacy = is_fallible(filter);
772        let mut rows = Vec::new();
773        let mut visible_pks = HashSet::new();
774        for address in addresses {
775            let record: T = table_registry
776                .read_at(address, &mut *mm)
777                .map_err(DbmsError::from)?;
778            self.ctx.count_access(AccessKind::RecordFetch, 1);
779            let Some(values) = self.patch_and_filter(changes, record.to_values(), None)? else {
780                continue;
781            };
782            if legacy && !planned.path.matches_row(&values) {
783                continue;
784            }
785            if let Some(verify) = &planned.verify
786                && !self.record_matches_filter(&values, verify)?
787            {
788                continue;
789            }
790            if changes.is_none() || visible_pks.insert(primary_key_value::<T>(&values)) {
791                rows.push(values);
792            }
793        }
794
795        if let Some(changes) = changes {
796            for row in changes.iter_inserted() {
797                if legacy && !planned.path.matches_row(&row) {
798                    continue;
799                }
800                if let Some(verify) = &planned.verify
801                    && !self.record_matches_filter(&row, verify)?
802                {
803                    continue;
804                }
805                if visible_pks.insert(primary_key_value::<T>(&row)) {
806                    rows.push(row);
807                }
808            }
809        }
810
811        Ok(Some(rows))
812    }
813
814    /// Core select logic returning intermediate `TableColumns`.
815    #[doc(hidden)]
816    pub fn select_columns<T>(&self, query: Query) -> DbmsResult<Vec<TableColumns>>
817    where
818        T: TableSchema,
819    {
820        reject_aggregate_clauses(&query)?;
821        let table_registry = self.load_table_registry::<T>()?;
822        let mut table_overlay = if self.transaction.is_some() {
823            self.overlay()?
824        } else {
825            DatabaseOverlay::default()
826        };
827
828        let initial_capacity = query
829            .limit
830            .unwrap_or(DEFAULT_SELECT_CAPACITY)
831            .min(DEFAULT_SELECT_CAPACITY);
832        let mut results = Vec::with_capacity(initial_capacity);
833        // When ORDER BY or DISTINCT is present, LIMIT and OFFSET must be applied after
834        // sorting and deduplication to comply with standard SQL semantics
835        // (WHERE -> DISTINCT -> ORDER BY -> OFFSET -> LIMIT).
836        let has_order_by = !query.order_by.is_empty();
837        let has_distinct = !query.distinct_by.is_empty();
838        let defer_pagination = has_order_by || has_distinct;
839        let mut count = 0;
840
841        let indexed_rows = match self.try_covering_select::<T>(&query, &table_registry)? {
842            Some(rows) => Some(rows),
843            None => self.try_index_select::<T>(&query, &table_registry, &table_overlay)?,
844        };
845        if let Some(indexed_rows) = indexed_rows {
846            for values in indexed_rows {
847                if !defer_pagination {
848                    count += 1;
849                    if query.offset.is_some_and(|offset| count <= offset) {
850                        continue;
851                    }
852                }
853                if !defer_pagination && query.limit.is_some_and(|limit| results.len() >= limit) {
854                    break;
855                }
856                results.push(vec![(ValuesSource::This, values)]);
857                if !defer_pagination && query.limit.is_some_and(|limit| results.len() >= limit) {
858                    break;
859                }
860            }
861        } else {
862            let mut mm = self.ctx.mm.borrow_mut();
863            let table_reader = table_registry.read::<T, _>(&mut *mm);
864            let mut table_reader = table_overlay.reader(table_reader);
865
866            while let Some(values) = table_reader.try_next()? {
867                self.ctx.count_access(AccessKind::ScannedRow, 1);
868                if let Some(filter) = &query.filter
869                    && !self.record_matches_filter(&values, filter)?
870                {
871                    continue;
872                }
873                if !defer_pagination {
874                    count += 1;
875                    if query.offset.is_some_and(|offset| count <= offset) {
876                        continue;
877                    }
878                }
879                if !defer_pagination && query.limit.is_some_and(|limit| results.len() >= limit) {
880                    break;
881                }
882                results.push(vec![(ValuesSource::This, values)]);
883                if !defer_pagination && query.limit.is_some_and(|limit| results.len() >= limit) {
884                    break;
885                }
886            }
887        }
888
889        self.apply_distinct(&mut results, &query.distinct_by);
890        self.batch_load_eager_relations::<T>(&mut results, &query)?;
891        self.apply_column_selection::<T>(&mut results, &query);
892
893        for (column, direction) in query.order_by.into_iter().rev() {
894            self.sort_query_results(&mut results, &column, direction);
895        }
896
897        // Apply OFFSET and LIMIT after sorting/deduplication when deferred
898        if defer_pagination {
899            let offset = query.offset.unwrap_or_default();
900            if offset > 0 {
901                if offset >= results.len() {
902                    results.clear();
903                } else {
904                    results.drain(..offset);
905                }
906            }
907            if let Some(limit) = query.limit {
908                results.truncate(limit);
909            }
910        }
911
912        Ok(results)
913    }
914
915    /// Executes a join query.
916    fn select_join_inner(&self, table: &str, query: Query) -> DbmsResult<JoinResultSet> {
917        reject_aggregate_clauses(&query)?;
918        self.schema.select_join(self, table, query)
919    }
920
921    /// Rewrites the foreign key values of tables referencing a record of `T` whose referenced
922    /// column changed.
923    ///
924    /// Every foreign key is matched against the column it declares: the primary key or any other
925    /// column of `T`. Foreign keys whose referenced column keeps its value are left untouched.
926    fn update_references_to_updated_record<T>(
927        &self,
928        old_values: &[(ColumnDef, Value)],
929        new_values: &[(ColumnDef, Value)],
930    ) -> DbmsResult<u64>
931    where
932        T: TableSchema,
933    {
934        let mut count = 0;
935        for (ref_table, ref_columns) in self.schema.referenced_tables(T::table_name()) {
936            for ref_col in ref_columns {
937                let referenced_column = self.referenced_column(ref_table, ref_col)?;
938                let find = |values: &[(ColumnDef, Value)]| {
939                    values
940                        .iter()
941                        .find(|(col_def, _)| col_def.name == referenced_column)
942                        .map(|(col_def, value)| (*col_def, value.clone()))
943                };
944                let (Some((col_def, old_value)), Some((_, new_value))) =
945                    (find(old_values), find(new_values))
946                else {
947                    continue;
948                };
949                if old_value == new_value {
950                    continue;
951                }
952
953                let ref_patch_value = (
954                    ColumnDef {
955                        name: ref_col,
956                        data_type: col_def.data_type,
957                        auto_increment: false,
958                        nullable: false,
959                        primary_key: false,
960                        unique: false,
961                        foreign_key: Some(ForeignKeyDef {
962                            foreign_table: T::table_name(),
963                            foreign_column: referenced_column,
964                            local_column: ref_col,
965                        }),
966                        default: None,
967                        renamed_from: &[],
968                    },
969                    new_value,
970                );
971                let filter = Filter::eq(ref_col, old_value);
972
973                count += self
974                    .schema
975                    .update(self, ref_table, &[ref_patch_value], Some(filter))?;
976            }
977        }
978
979        Ok(count)
980    }
981
982    /// Sanitizes values using the table schema's sanitizers.
983    fn sanitize_values<T>(
984        &self,
985        values: Vec<(ColumnDef, Value)>,
986    ) -> DbmsResult<Vec<(ColumnDef, Value)>>
987    where
988        T: TableSchema,
989    {
990        let mut sanitized_values = Vec::with_capacity(values.len());
991        for (col_def, value) in values.into_iter() {
992            let value = match T::sanitizer(col_def.name) {
993                Some(sanitizer) => sanitizer.sanitize(value)?,
994                None => value,
995            };
996            sanitized_values.push((col_def, value));
997        }
998        Ok(sanitized_values)
999    }
1000
1001    /// Collects all records matching a filter from the table registry.
1002    #[allow(clippy::type_complexity)]
1003    fn collect_matching_records<T>(
1004        &self,
1005        table_registry: &TableRegistry,
1006        filter: &Option<Filter>,
1007    ) -> DbmsResult<Vec<(NextRecord<T>, Vec<(ColumnDef, Value)>)>>
1008    where
1009        T: TableSchema,
1010    {
1011        let mut mm = self.ctx.mm.borrow_mut();
1012
1013        // `collect_matching_records` is only used by the non-transactional update/delete paths.
1014        // Transactional mutations first resolve rows via `existing_rows_for_filter`, which reads
1015        // through `select()` and therefore includes the overlay. Using `overlay = None` here is
1016        // intentional because the atomic write path is operating on committed storage only.
1017        if let Some(filter) = filter
1018            && let Some(planned) = plan_access(filter, T::indexes(), T::columns())
1019        {
1020            let reader = IndexReader::new(table_registry.index_ledger());
1021            if let Some(addresses) =
1022                self.candidate_addresses::<T, _>(&reader, &planned.path, None, &mut *mm)?
1023            {
1024                let mut records = Vec::new();
1025                for address in addresses {
1026                    let record: T = table_registry
1027                        .read_at(address, &mut *mm)
1028                        .map_err(DbmsError::from)?;
1029                    self.ctx.count_access(AccessKind::RecordFetch, 1);
1030                    let record_values = record.clone().to_values();
1031                    if let Some(verify) = &planned.verify
1032                        && !self.record_matches_filter(&record_values, verify)?
1033                    {
1034                        continue;
1035                    }
1036                    records.push((
1037                        NextRecord {
1038                            record,
1039                            page: address.page,
1040                            offset: address.offset,
1041                        },
1042                        record_values,
1043                    ));
1044                }
1045                return Ok(records);
1046            }
1047        }
1048
1049        let mut table_reader = table_registry.read::<T, _>(&mut *mm);
1050        let mut records = vec![];
1051        while let Some(values) = table_reader.try_next()? {
1052            self.ctx.count_access(AccessKind::ScannedRow, 1);
1053            let record_values = values.record.clone().to_values();
1054            if let Some(filter) = filter
1055                && !self.record_matches_filter(&record_values, filter)?
1056            {
1057                continue;
1058            }
1059            records.push((values, record_values));
1060        }
1061        Ok(records)
1062    }
1063
1064    /// For each indexed column for the table, inserts the index for the given record address.
1065    fn insert_index<T>(
1066        &self,
1067        table_registry: &mut TableRegistry,
1068        record_address: RecordAddress,
1069        values: &[(ColumnDef, Value)],
1070        mm: &mut impl wasm_dbms_memory::MemoryAccess,
1071    ) -> DbmsResult<()>
1072    where
1073        T: TableSchema,
1074    {
1075        let index_ledger = table_registry.index_ledger_mut();
1076        for columns in T::indexes().iter().map(|index| index.columns()) {
1077            let key = index_key(columns, values);
1078            index_ledger.insert(columns, key, record_address, mm)?;
1079        }
1080
1081        Ok(())
1082    }
1083
1084    /// For each indexed column for the table, deletes the index for the given record address.
1085    fn delete_index<T>(
1086        &self,
1087        table_registry: &mut TableRegistry,
1088        record_address: RecordAddress,
1089        values: &[(ColumnDef, Value)],
1090        mm: &mut impl wasm_dbms_memory::MemoryAccess,
1091    ) -> DbmsResult<()>
1092    where
1093        T: TableSchema,
1094    {
1095        let index_ledger = table_registry.index_ledger_mut();
1096        for columns in T::indexes().iter().map(|index| index.columns()) {
1097            let key = index_key(columns, values);
1098            index_ledger.delete(columns, &key, record_address, mm)?;
1099        }
1100        Ok(())
1101    }
1102
1103    /// For each indexed column for the table, updates the index for the given record address.
1104    ///
1105    /// When an indexed column's value changed, the old key is deleted and the new key is inserted.
1106    /// When only the record address moved (same key), the pointer is updated in place.
1107    fn update_index<T>(
1108        &self,
1109        table_registry: &mut TableRegistry,
1110        old_record_address: RecordAddress,
1111        new_record_address: RecordAddress,
1112        old_values: &[(ColumnDef, Value)],
1113        new_values: &[(ColumnDef, Value)],
1114        mm: &mut impl wasm_dbms_memory::MemoryAccess,
1115    ) -> DbmsResult<()>
1116    where
1117        T: TableSchema,
1118    {
1119        let index_ledger = table_registry.index_ledger_mut();
1120        for columns in T::indexes().iter().map(|index| index.columns()) {
1121            let old_key = index_key(columns, old_values);
1122            let new_key = index_key(columns, new_values);
1123            if old_key == new_key {
1124                index_ledger.update(
1125                    columns,
1126                    &new_key,
1127                    old_record_address,
1128                    new_record_address,
1129                    mm,
1130                )?;
1131            } else {
1132                index_ledger.delete(columns, &old_key, old_record_address, mm)?;
1133                index_ledger.insert(columns, new_key, new_record_address, mm)?;
1134            }
1135        }
1136        Ok(())
1137    }
1138
1139    /// Fills in auto-increment values for columns that are missing from the input.
1140    fn fill_auto_increment_values<T>(
1141        &self,
1142        table_registry: &mut TableRegistry,
1143        mut values: Vec<(ColumnDef, Value)>,
1144    ) -> DbmsResult<Vec<(ColumnDef, Value)>>
1145    where
1146        T: TableSchema,
1147    {
1148        let mut mm = self.ctx.mm.borrow_mut();
1149        // iter over auto-increment columns, for each of them check if the value is provided, if not get the next auto-increment value.
1150        for auto_increment_column in T::columns().iter().filter(|col| col.auto_increment) {
1151            if values
1152                .iter()
1153                .any(|(col_def, _)| col_def.name == auto_increment_column.name)
1154            {
1155                continue;
1156            }
1157            let next_value = table_registry
1158                .next_autoincrement(auto_increment_column.name, &mut *mm)?
1159                .ok_or(DbmsError::Table(TableError::SchemaMismatch))?;
1160            values.push((*auto_increment_column, next_value));
1161        }
1162
1163        Ok(values)
1164    }
1165}
1166
1167/// Rejects queries that carry `GROUP BY` or `HAVING` on a non-aggregate path.
1168///
1169/// `GROUP BY` and `HAVING` only have meaning under
1170/// [`Database::aggregate`](wasm_dbms_api::prelude::Database::aggregate). When
1171/// they appear on a regular `select`, `select_raw`, or `select_join`, returning
1172/// rows would silently drop the user's grouping intent — better to fail loudly
1173/// and steer the caller to the right method.
1174fn reject_aggregate_clauses(query: &Query) -> DbmsResult<()> {
1175    if !query.group_by.is_empty() || query.having.is_some() {
1176        return Err(DbmsError::Query(QueryError::AggregateClauseInSelect));
1177    }
1178    Ok(())
1179}
1180
1181/// Provides ordering for two optional values by direction.
1182pub fn sort_values_with_direction(
1183    a: Option<&Value>,
1184    b: Option<&Value>,
1185    direction: OrderDirection,
1186) -> Ordering {
1187    match (a, b) {
1188        (Some(a_val), Some(b_val)) => match direction {
1189            OrderDirection::Ascending => a_val.cmp(b_val),
1190            OrderDirection::Descending => b_val.cmp(a_val),
1191        },
1192        (Some(_), None) => std::cmp::Ordering::Greater,
1193        (None, Some(_)) => std::cmp::Ordering::Less,
1194        (None, None) => std::cmp::Ordering::Equal,
1195    }
1196}
1197
1198/// Converts column-value pairs to a schema entity.
1199fn values_to_schema_entity<T>(values: Vec<(ColumnDef, Value)>) -> DbmsResult<T>
1200where
1201    T: TableSchema,
1202{
1203    let record = T::Insert::from_values(&values)?.into_record();
1204    Ok(record)
1205}
1206
1207/// Builds the index key for the given columns by extracting values from the record.
1208///
1209/// Columns not found in `values` default to [`Value::Null`].
1210fn index_key(columns: &[&str], values: &[(ColumnDef, Value)]) -> Vec<Value> {
1211    columns
1212        .iter()
1213        .map(|col| {
1214            values
1215                .iter()
1216                .find(|(cd, _)| cd.name == *col)
1217                .map(|(_, v)| v.clone())
1218                .unwrap_or(Value::Null)
1219        })
1220        .collect()
1221}
1222
1223/// Returns the primary-key value of a row of `T`, or [`Value::Null`] when absent.
1224fn primary_key_value<T>(values: &[(ColumnDef, Value)]) -> Value
1225where
1226    T: TableSchema,
1227{
1228    values
1229        .iter()
1230        .find(|(column, _)| column.name == T::primary_key())
1231        .map(|(_, value)| value.clone())
1232        .unwrap_or(Value::Null)
1233}
1234
1235impl<M> Database for WasmDbmsDatabase<'_, M>
1236where
1237    M: MemoryProvider,
1238{
1239    fn select<T>(&self, query: Query) -> DbmsResult<Vec<T::Record>>
1240    where
1241        T: TableSchema,
1242    {
1243        self.ensure_no_drift()?;
1244        if !query.joins.is_empty() {
1245            return Err(DbmsError::Query(QueryError::JoinInsideTypedSelect));
1246        }
1247        let results = self.select_columns::<T>(query)?;
1248        Ok(results.into_iter().map(T::Record::from_values).collect())
1249    }
1250
1251    fn select_raw(&self, table: &str, query: Query) -> DbmsResult<Vec<Vec<(ColumnDef, Value)>>> {
1252        self.ensure_no_drift()?;
1253        self.schema.select(self, table, query)
1254    }
1255
1256    fn select_join(&self, table: &str, query: Query) -> DbmsResult<JoinResultSet> {
1257        self.ensure_no_drift()?;
1258        self.select_join_inner(table, query)
1259    }
1260
1261    fn aggregate<T>(
1262        &self,
1263        query: Query,
1264        aggregates: &[AggregateFunction],
1265    ) -> DbmsResult<Vec<AggregatedRow>>
1266    where
1267        T: TableSchema,
1268    {
1269        self.ensure_no_drift()?;
1270        aggregate::run_aggregate::<T, _>(self, query, aggregates)
1271    }
1272
1273    fn insert<T>(&self, record: T::Insert) -> DbmsResult<()>
1274    where
1275        T: TableSchema,
1276        T::Insert: InsertRecord<Schema = T>,
1277    {
1278        self.ensure_no_drift()?;
1279        let mut table_registry = self.load_table_registry::<T>()?;
1280        let record_values = record.clone().into_values();
1281        let record_values =
1282            self.fill_auto_increment_values::<T>(&mut table_registry, record_values)?;
1283        let sanitized_values = self.sanitize_values::<T>(record_values)?;
1284        self.schema
1285            .validate_insert(self, T::table_name(), &sanitized_values)?;
1286        if self.transaction.is_some() {
1287            self.with_transaction_mut(|tx| tx.insert::<T>(sanitized_values))?;
1288        } else {
1289            self.atomic(|db| {
1290                let record = T::Insert::from_values(&sanitized_values)?;
1291                let mut mm = db.ctx.mm.borrow_mut();
1292                // update journal with the insert operation before mutating memory
1293                let mut journal_ref = db.ctx.journal.borrow_mut();
1294                let journal = journal_ref
1295                    .as_mut()
1296                    .expect("journal must be active inside atomic");
1297                let mut writer = JournaledWriter::new(&mut *mm, journal);
1298                // insert the record in the table registry, and eventually update the indexes
1299                let record_address = table_registry
1300                    .insert(record.into_record(), &mut writer)
1301                    .map_err(DbmsError::from)?;
1302                self.insert_index::<T>(
1303                    &mut table_registry,
1304                    record_address,
1305                    &sanitized_values,
1306                    &mut writer,
1307                )?;
1308                Ok(())
1309            })?;
1310        }
1311
1312        Ok(())
1313    }
1314
1315    fn update<T>(&self, patch: T::Update) -> DbmsResult<u64>
1316    where
1317        T: TableSchema,
1318        T::Update: UpdateRecord<Schema = T>,
1319    {
1320        self.ensure_no_drift()?;
1321        let filter = patch.where_clause().clone();
1322        if self.transaction.is_some() {
1323            let rows = self.existing_rows_for_filter::<T>(filter.clone())?;
1324            let count = rows.len() as u64;
1325            self.with_transaction_mut(|tx| tx.update::<T>(patch, rows))?;
1326
1327            return Ok(count);
1328        }
1329
1330        let patch = patch.update_values();
1331
1332        self.atomic(|db| {
1333            let mut count = 0;
1334
1335            let mut table_registry = db.load_table_registry::<T>()?;
1336            let records = db.collect_matching_records::<T>(&table_registry, &filter)?;
1337
1338            for (record, record_values) in records {
1339                let current_pk_value = record_values
1340                    .iter()
1341                    .find(|(col_def, _)| col_def.primary_key)
1342                    .expect("primary key not found")
1343                    .1
1344                    .clone();
1345
1346                let previous_record = values_to_schema_entity::<T>(record_values.clone())?;
1347                let old_values_for_index = record_values.clone();
1348                let mut record_values = record_values;
1349
1350                for (patch_col_def, patch_value) in &patch {
1351                    if let Some((_, record_value)) = record_values
1352                        .iter_mut()
1353                        .find(|(record_col_def, _)| record_col_def.name == patch_col_def.name)
1354                    {
1355                        *record_value = patch_value.clone();
1356                    }
1357                }
1358                let record_values = db.sanitize_values::<T>(record_values)?;
1359                db.schema.validate_update(
1360                    db,
1361                    T::table_name(),
1362                    &record_values,
1363                    current_pk_value.clone(),
1364                )?;
1365                let updated_record = values_to_schema_entity::<T>(record_values.clone())?;
1366                {
1367                    let mut mm = db.ctx.mm.borrow_mut();
1368                    // update journal with the update operation before mutating memory
1369                    let mut journal_ref = db.ctx.journal.borrow_mut();
1370                    let journal = journal_ref
1371                        .as_mut()
1372                        .expect("journal must be active inside atomic");
1373                    let mut writer = JournaledWriter::new(&mut *mm, journal);
1374                    // update table registry
1375                    let old_address = RecordAddress::new(record.page, record.offset);
1376                    let new_address = table_registry
1377                        .update(updated_record, previous_record, old_address, &mut writer)
1378                        .map_err(DbmsError::from)?;
1379                    // update indexes if needed
1380                    self.update_index::<T>(
1381                        &mut table_registry,
1382                        old_address,
1383                        new_address,
1384                        &old_values_for_index,
1385                        &record_values,
1386                        &mut writer,
1387                    )?;
1388                }
1389                count += 1;
1390
1391                count += db.update_references_to_updated_record::<T>(
1392                    &old_values_for_index,
1393                    &record_values,
1394                )?;
1395            }
1396
1397            Ok(count)
1398        })
1399    }
1400
1401    fn delete<T>(&self, behaviour: DeleteBehavior, filter: Option<Filter>) -> DbmsResult<u64>
1402    where
1403        T: TableSchema,
1404    {
1405        self.ensure_no_drift()?;
1406        if self.transaction.is_some() {
1407            let rows = self.existing_rows_for_filter::<T>(filter.clone())?;
1408            let count = rows.len() as u64;
1409
1410            self.with_transaction_mut(|tx| tx.delete::<T>(behaviour, rows))?;
1411
1412            return Ok(count);
1413        }
1414
1415        self.atomic(|db| {
1416            let mut table_registry = db.load_table_registry::<T>()?;
1417            let records = db.collect_matching_records::<T>(&table_registry, &filter)?;
1418            let mut count = records.len() as u64;
1419            for (record, record_values) in records {
1420                match behaviour {
1421                    DeleteBehavior::Cascade => {
1422                        count += db.delete_foreign_keys_cascade::<T>(&record_values)?;
1423                    }
1424                    DeleteBehavior::Restrict => {
1425                        if db.has_foreign_key_references::<T>(&record_values)? {
1426                            return Err(DbmsError::Query(
1427                                QueryError::ForeignKeyConstraintViolation {
1428                                    referencing_table: T::table_name().to_string(),
1429                                    field: T::primary_key().to_string(),
1430                                },
1431                            ));
1432                        }
1433                    }
1434                }
1435                let mut mm = db.ctx.mm.borrow_mut();
1436                let mut journal_ref = db.ctx.journal.borrow_mut();
1437                let journal = journal_ref
1438                    .as_mut()
1439                    .expect("journal must be active inside atomic");
1440                // write table and index deletions to the journal before mutating memory
1441                let mut writer = JournaledWriter::new(&mut *mm, journal);
1442                let address = RecordAddress::new(record.page, record.offset);
1443                table_registry
1444                    .delete(record.record, address, &mut writer)
1445                    .map_err(DbmsError::from)?;
1446                self.delete_index::<T>(&mut table_registry, address, &record_values, &mut writer)?;
1447            }
1448
1449            Ok(count)
1450        })
1451    }
1452
1453    fn commit(&mut self) -> DbmsResult<()> {
1454        self.ensure_no_drift()?;
1455        let Some(txid) = self.transaction.take() else {
1456            return Err(DbmsError::Transaction(
1457                TransactionError::NoActiveTransaction,
1458            ));
1459        };
1460        let transaction = {
1461            let mut ts = self.ctx.transaction_session.borrow_mut();
1462            ts.take_transaction(&txid)?
1463        };
1464
1465        *self.ctx.journal.borrow_mut() = Some(Journal::new());
1466
1467        for op in transaction.operations {
1468            let result = match op {
1469                TransactionOp::Insert { table, values } => self
1470                    .schema
1471                    .validate_insert(self, table, &values)
1472                    .and_then(|()| self.schema.insert(self, table, &values)),
1473                TransactionOp::Delete {
1474                    table,
1475                    behaviour,
1476                    filters,
1477                } => self
1478                    .validated_transaction_filter(table, filters)
1479                    .and_then(|filter| match filter {
1480                        Some(filter) => self
1481                            .schema
1482                            .delete(self, table, behaviour, Some(filter))
1483                            .map(|_| ()),
1484                        None => Ok(()),
1485                    }),
1486                TransactionOp::Update {
1487                    table,
1488                    patch,
1489                    filters,
1490                } => self
1491                    .validated_transaction_filter(table, filters)
1492                    .and_then(|filter| match filter {
1493                        Some(filter) => self
1494                            .schema
1495                            .update(self, table, &patch, Some(filter))
1496                            .map(|_| ()),
1497                        None => Ok(()),
1498                    }),
1499            };
1500
1501            if let Err(err) = result {
1502                if let Some(journal) = self.ctx.journal.borrow_mut().take() {
1503                    journal
1504                        .rollback(&mut self.ctx.mm.borrow_mut())
1505                        .expect("critical: failed to rollback journal");
1506                }
1507                return Err(err);
1508            }
1509        }
1510
1511        if let Some(journal) = self.ctx.journal.borrow_mut().take() {
1512            journal.commit();
1513        }
1514        Ok(())
1515    }
1516
1517    fn rollback(&mut self) -> DbmsResult<()> {
1518        let Some(txid) = self.transaction.take() else {
1519            return Err(DbmsError::Transaction(
1520                TransactionError::NoActiveTransaction,
1521            ));
1522        };
1523
1524        let mut ts = self.ctx.transaction_session.borrow_mut();
1525        ts.close_transaction(&txid);
1526        Ok(())
1527    }
1528
1529    fn has_drift(&self) -> DbmsResult<bool> {
1530        self.drift_check()
1531    }
1532
1533    fn pending_migrations(&self) -> DbmsResult<Vec<MigrationOp>> {
1534        let stored = {
1535            let sr = self.ctx.schema_registry.borrow();
1536            let mut mm = self.ctx.mm.borrow_mut();
1537            sr.stored_snapshots(&mut *mm)?
1538        };
1539        let compiled = self.schema.compiled_snapshots_dyn();
1540        migration::diff::diff(&stored, &compiled, self.schema.as_ref())
1541    }
1542
1543    fn migrate(&mut self, policy: MigrationPolicy) -> DbmsResult<()> {
1544        let mut ops = self.pending_migrations()?;
1545        migration::plan::validate(&ops, policy)?;
1546        migration::plan::order_ops(&mut ops);
1547        migration::apply::apply(self, ops)
1548    }
1549}
1550
1551#[cfg(test)]
1552mod tests;