Skip to main content

wasm_dbms_api/dbms/table/
schema.rs

1pub mod snapshot;
2
3use xxhash_rust::xxh3::xxh3_64;
4
5pub use self::snapshot::{
6    ColumnSnapshot, CustomDataTypeSnapshot, DataTypeSnapshot, ForeignKeySnapshot, IndexSnapshot,
7    OnDeleteSnapshot, TableSchemaSnapshot, WireSize,
8};
9use crate::dbms::foreign_fetcher::ForeignFetcher;
10use crate::dbms::table::column_def::{ColumnDef, IndexDef};
11use crate::dbms::table::{InsertRecord, TableRecord, UpdateRecord};
12use crate::dbms::types::DataTypeKind;
13use crate::memory::Encode;
14use crate::prelude::{Sanitize, Validate};
15
16/// A type representing a unique fingerprint for a table schema.
17pub type TableFingerprint = u64;
18
19/// Maps a runtime [`DataTypeKind`] into its stable [`DataTypeSnapshot`] counterpart.
20fn data_type_to_snapshot(kind: &DataTypeKind) -> DataTypeSnapshot {
21    match kind {
22        DataTypeKind::Blob => DataTypeSnapshot::Blob,
23        DataTypeKind::Boolean => DataTypeSnapshot::Boolean,
24        DataTypeKind::Date => DataTypeSnapshot::Date,
25        DataTypeKind::DateTime => DataTypeSnapshot::Datetime,
26        DataTypeKind::Decimal => DataTypeSnapshot::Decimal,
27        DataTypeKind::Int8 => DataTypeSnapshot::Int8,
28        DataTypeKind::Int16 => DataTypeSnapshot::Int16,
29        DataTypeKind::Int32 => DataTypeSnapshot::Int32,
30        DataTypeKind::Int64 => DataTypeSnapshot::Int64,
31        DataTypeKind::Json => DataTypeSnapshot::Json,
32        DataTypeKind::Text => DataTypeSnapshot::Text,
33        DataTypeKind::Uint8 => DataTypeSnapshot::Uint8,
34        DataTypeKind::Uint16 => DataTypeSnapshot::Uint16,
35        DataTypeKind::Uint32 => DataTypeSnapshot::Uint32,
36        DataTypeKind::Uint64 => DataTypeSnapshot::Uint64,
37        DataTypeKind::Uuid => DataTypeSnapshot::Uuid,
38        DataTypeKind::Custom { tag, wire_size } => {
39            DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
40                tag: (*tag).to_string(),
41                wire_size: *wire_size,
42            }))
43        }
44    }
45}
46
47/// Table schema representation.
48///
49/// It is used to define the structure of a database table.
50pub trait TableSchema
51where
52    Self: Encode + 'static,
53{
54    /// The [`TableRecord`] type associated with this table schema;
55    /// which is the data returned by a query.
56    type Record: TableRecord<Schema = Self>;
57    /// The [`InsertRecord`] type associated with this table schema.
58    type Insert: InsertRecord<Schema = Self>;
59    /// The [`UpdateRecord`] type associated with this table schema.
60    type Update: UpdateRecord<Schema = Self>;
61    /// The [`ForeignFetcher`] type associated with this table schema.
62    type ForeignFetcher: ForeignFetcher;
63
64    /// Returns the name of the table.
65    fn table_name() -> &'static str;
66
67    /// Returns the column definitions of the table.
68    fn columns() -> &'static [ColumnDef];
69
70    /// Returns the name of the primary key column.
71    fn primary_key() -> &'static str;
72
73    /// Returns the list of indexes defined on the table, where each index
74    /// is represented by the list of column names it includes.
75    fn indexes() -> &'static [IndexDef] {
76        &[]
77    }
78
79    /// Converts itself into a vector of column-value pairs.
80    fn to_values(self) -> Vec<(ColumnDef, crate::dbms::value::Value)>;
81
82    /// Returns the [`Sanitize`] implementation for the given column name, if any.
83    fn sanitizer(column_name: &'static str) -> Option<Box<dyn Sanitize>>;
84
85    /// Returns the [`Validate`] implementation for the given column name, if any.
86    fn validator(column_name: &'static str) -> Option<Box<dyn Validate>>;
87
88    /// Returns an instance of the [`ForeignFetcher`] for this table schema.
89    fn foreign_fetcher() -> Self::ForeignFetcher {
90        Default::default()
91    }
92
93    /// Builds a self-describing [`TableSchemaSnapshot`] from the compile-time schema definition.
94    ///
95    /// The snapshot captures the structural shape of the table — name, primary key, alignment,
96    /// columns and indexes — in a stable, encodable form so it can be persisted to stable memory
97    /// and later diffed against the snapshot of a previous version to derive the migration steps
98    /// required to bring the on-disk layout up to date.
99    ///
100    /// The default implementation assembles the snapshot from [`Self::table_name`],
101    /// [`Self::primary_key`], [`Self::columns`], [`Self::indexes`] and the [`Encode::ALIGNMENT`]
102    /// constant. It is sufficient for every schema generated by `#[derive(Table)]`; implementors
103    /// that carry metadata not exposed through [`ColumnDef`] (e.g. column defaults or non-default
104    /// foreign-key `ON DELETE` actions) should override it.
105    fn schema_snapshot() -> TableSchemaSnapshot {
106        let columns = Self::columns()
107            .iter()
108            .map(|c| ColumnSnapshot {
109                name: c.name.to_string(),
110                data_type: data_type_to_snapshot(&c.data_type),
111                nullable: c.nullable,
112                auto_increment: c.auto_increment,
113                unique: c.unique,
114                primary_key: c.primary_key,
115                foreign_key: c.foreign_key.as_ref().map(|fk| ForeignKeySnapshot {
116                    table: fk.foreign_table.to_string(),
117                    column: fk.foreign_column.to_string(),
118                    on_delete: OnDeleteSnapshot::Restrict,
119                }),
120                default: c.default.map(|f| f()),
121            })
122            .collect();
123
124        let indexes = Self::indexes()
125            .iter()
126            .map(|idx| {
127                let cols = idx.columns();
128                let unique = match cols {
129                    [single] => Self::columns()
130                        .iter()
131                        .find(|c| c.name == *single)
132                        .is_some_and(|c| c.unique || c.primary_key),
133                    _ => false,
134                };
135                IndexSnapshot {
136                    columns: cols.iter().map(|c| (*c).to_string()).collect(),
137                    unique,
138                }
139            })
140            .collect();
141
142        TableSchemaSnapshot {
143            version: TableSchemaSnapshot::latest_version(),
144            name: Self::table_name().to_string(),
145            primary_key: Self::primary_key().to_string(),
146            alignment: <Self as Encode>::ALIGNMENT as u32,
147            columns,
148            indexes,
149        }
150    }
151
152    /// Returns the fingerprint of the table schema.
153    ///
154    /// The fingerprint is computed as a hash of [`Self::table_name`] so that the same table keeps
155    /// the same identity across rebuilds (where [`std::any::TypeId`] is not stable) and across
156    /// schema evolution. Two distinct types declaring the same `table_name` are intentionally
157    /// considered the same logical table.
158    fn fingerprint() -> TableFingerprint {
159        fingerprint_for_name(Self::table_name())
160    }
161}
162
163/// Computes the [`TableFingerprint`] for an arbitrary table name.
164///
165/// Uses `xxh3_64` (deterministic, stable across processes and architectures).
166/// Used by the migration engine to derive the registry key for tables it
167/// knows only by name (e.g. when applying a `MigrationOp::CreateTable` from a
168/// snapshot, which does not carry compile-time `TypeId` information).
169pub fn fingerprint_for_name(name: &str) -> TableFingerprint {
170    xxh3_64(name.as_bytes())
171}