1mod 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
33const 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
45pub struct WasmDbmsDatabase<'ctx, M>
50where
51 M: MemoryProvider,
52{
53 ctx: &'ctx DbmsContext<M>,
55 schema: Box<dyn DatabaseSchema<M> + 'ctx>,
57 transaction: Option<TransactionId>,
59}
60
61impl<'ctx, M> WasmDbmsDatabase<'ctx, M>
62where
63 M: MemoryProvider,
64{
65 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 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 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 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 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 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 fn ensure_no_drift(&self) -> DbmsResult<()> {
165 if self.drift_check()? {
166 return Err(DbmsError::Migration(MigrationError::SchemaDrift));
167 }
168 Ok(())
169 }
170
171 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 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 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 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 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 fn overlay(&self) -> DbmsResult<DatabaseOverlay> {
295 self.with_transaction(|tx| Ok(tx.overlay().clone()))
296 }
297
298 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 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 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 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 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 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 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 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 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 #[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 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 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 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 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 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 #[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 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 #[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 #[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 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 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 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 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 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 #[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 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 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 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 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 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 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
1167fn 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
1181pub 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
1198fn 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
1207fn 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
1223fn 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 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 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 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 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 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 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;