Skip to main content

wasm_dbms/
database.rs

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