wasm-dbms-api 0.9.0

Runtime-agnostic API types and traits for the wasm-dbms DBMS engine.
Documentation
pub mod snapshot;

use xxhash_rust::xxh3::xxh3_64;

pub use self::snapshot::{
    ColumnSnapshot, CustomDataTypeSnapshot, DataTypeSnapshot, ForeignKeySnapshot, IndexSnapshot,
    OnDeleteSnapshot, TableSchemaSnapshot, WireSize,
};
use crate::dbms::foreign_fetcher::ForeignFetcher;
use crate::dbms::table::column_def::{ColumnDef, IndexDef};
use crate::dbms::table::{InsertRecord, TableRecord, UpdateRecord};
use crate::dbms::types::DataTypeKind;
use crate::memory::Encode;
use crate::prelude::{Sanitize, Validate};

/// A type representing a unique fingerprint for a table schema.
pub type TableFingerprint = u64;

/// Maps a runtime [`DataTypeKind`] into its stable [`DataTypeSnapshot`] counterpart.
fn data_type_to_snapshot(kind: &DataTypeKind) -> DataTypeSnapshot {
    match kind {
        DataTypeKind::Blob => DataTypeSnapshot::Blob,
        DataTypeKind::Boolean => DataTypeSnapshot::Boolean,
        DataTypeKind::Date => DataTypeSnapshot::Date,
        DataTypeKind::DateTime => DataTypeSnapshot::Datetime,
        DataTypeKind::Decimal => DataTypeSnapshot::Decimal,
        DataTypeKind::Int8 => DataTypeSnapshot::Int8,
        DataTypeKind::Int16 => DataTypeSnapshot::Int16,
        DataTypeKind::Int32 => DataTypeSnapshot::Int32,
        DataTypeKind::Int64 => DataTypeSnapshot::Int64,
        DataTypeKind::Json => DataTypeSnapshot::Json,
        DataTypeKind::Text => DataTypeSnapshot::Text,
        DataTypeKind::Uint8 => DataTypeSnapshot::Uint8,
        DataTypeKind::Uint16 => DataTypeSnapshot::Uint16,
        DataTypeKind::Uint32 => DataTypeSnapshot::Uint32,
        DataTypeKind::Uint64 => DataTypeSnapshot::Uint64,
        DataTypeKind::Uuid => DataTypeSnapshot::Uuid,
        DataTypeKind::Custom { tag, wire_size } => {
            DataTypeSnapshot::Custom(Box::new(CustomDataTypeSnapshot {
                tag: (*tag).to_string(),
                wire_size: *wire_size,
            }))
        }
    }
}

/// Table schema representation.
///
/// It is used to define the structure of a database table.
pub trait TableSchema
where
    Self: Encode + 'static,
{
    /// The [`TableRecord`] type associated with this table schema;
    /// which is the data returned by a query.
    type Record: TableRecord<Schema = Self>;
    /// The [`InsertRecord`] type associated with this table schema.
    type Insert: InsertRecord<Schema = Self>;
    /// The [`UpdateRecord`] type associated with this table schema.
    type Update: UpdateRecord<Schema = Self>;
    /// The [`ForeignFetcher`] type associated with this table schema.
    type ForeignFetcher: ForeignFetcher;

    /// Returns the name of the table.
    fn table_name() -> &'static str;

    /// Returns the column definitions of the table.
    fn columns() -> &'static [ColumnDef];

    /// Returns the name of the primary key column.
    fn primary_key() -> &'static str;

    /// Returns the list of indexes defined on the table, where each index
    /// is represented by the list of column names it includes.
    fn indexes() -> &'static [IndexDef] {
        &[]
    }

    /// Converts itself into a vector of column-value pairs.
    fn to_values(self) -> Vec<(ColumnDef, crate::dbms::value::Value)>;

    /// Returns the [`Sanitize`] implementation for the given column name, if any.
    fn sanitizer(column_name: &'static str) -> Option<Box<dyn Sanitize>>;

    /// Returns the [`Validate`] implementation for the given column name, if any.
    fn validator(column_name: &'static str) -> Option<Box<dyn Validate>>;

    /// Returns an instance of the [`ForeignFetcher`] for this table schema.
    fn foreign_fetcher() -> Self::ForeignFetcher {
        Default::default()
    }

    /// Builds a self-describing [`TableSchemaSnapshot`] from the compile-time schema definition.
    ///
    /// The snapshot captures the structural shape of the table — name, primary key, alignment,
    /// columns and indexes — in a stable, encodable form so it can be persisted to stable memory
    /// and later diffed against the snapshot of a previous version to derive the migration steps
    /// required to bring the on-disk layout up to date.
    ///
    /// The default implementation assembles the snapshot from [`Self::table_name`],
    /// [`Self::primary_key`], [`Self::columns`], [`Self::indexes`] and the [`Encode::ALIGNMENT`]
    /// constant. It is sufficient for every schema generated by `#[derive(Table)]`; implementors
    /// that carry metadata not exposed through [`ColumnDef`] (e.g. column defaults or non-default
    /// foreign-key `ON DELETE` actions) should override it.
    fn schema_snapshot() -> TableSchemaSnapshot {
        let columns = Self::columns()
            .iter()
            .map(|c| ColumnSnapshot {
                name: c.name.to_string(),
                data_type: data_type_to_snapshot(&c.data_type),
                nullable: c.nullable,
                auto_increment: c.auto_increment,
                unique: c.unique,
                primary_key: c.primary_key,
                foreign_key: c.foreign_key.as_ref().map(|fk| ForeignKeySnapshot {
                    table: fk.foreign_table.to_string(),
                    column: fk.foreign_column.to_string(),
                    on_delete: OnDeleteSnapshot::Restrict,
                }),
                default: c.default.map(|f| f()),
            })
            .collect();

        let indexes = Self::indexes()
            .iter()
            .map(|idx| {
                let cols = idx.columns();
                let unique = match cols {
                    [single] => Self::columns()
                        .iter()
                        .find(|c| c.name == *single)
                        .is_some_and(|c| c.unique || c.primary_key),
                    _ => false,
                };
                IndexSnapshot {
                    columns: cols.iter().map(|c| (*c).to_string()).collect(),
                    unique,
                }
            })
            .collect();

        TableSchemaSnapshot {
            version: TableSchemaSnapshot::latest_version(),
            name: Self::table_name().to_string(),
            primary_key: Self::primary_key().to_string(),
            alignment: <Self as Encode>::ALIGNMENT as u32,
            columns,
            indexes,
        }
    }

    /// Returns the fingerprint of the table schema.
    ///
    /// The fingerprint is computed as a hash of [`Self::table_name`] so that the same table keeps
    /// the same identity across rebuilds (where [`std::any::TypeId`] is not stable) and across
    /// schema evolution. Two distinct types declaring the same `table_name` are intentionally
    /// considered the same logical table.
    fn fingerprint() -> TableFingerprint {
        fingerprint_for_name(Self::table_name())
    }
}

/// Computes the [`TableFingerprint`] for an arbitrary table name.
///
/// Uses `xxh3_64` (deterministic, stable across processes and architectures).
/// Used by the migration engine to derive the registry key for tables it
/// knows only by name (e.g. when applying a `MigrationOp::CreateTable` from a
/// snapshot, which does not carry compile-time `TypeId` information).
pub fn fingerprint_for_name(name: &str) -> TableFingerprint {
    xxh3_64(name.as_bytes())
}