Skip to main content

sqlite_diff_rs/schema/
simple_table.rs

1//! Simple table schema for SQL-based operations.
2//!
3//! [`SimpleTable`] is a schema type that can be created from SQL
4//! `CREATE TABLE` statements and used to generate SQL INSERT, UPDATE, and
5//! DELETE statements.
6
7use alloc::string::String;
8use alloc::vec;
9use alloc::vec::Vec;
10use core::hash::{Hash, Hasher};
11
12use crate::parser::TableSchema;
13use crate::{encoding::Value, schema::dyn_table::IndexableValues};
14
15use super::{DynTable, SchemaWithPK};
16
17/// A simple table schema with column names for SQL generation.
18///
19/// This type wraps [`TableSchema`] and adds column names, allowing it to be
20/// used for both binary encoding/decoding and SQL statement digestion.
21///
22/// # Example
23///
24/// ```rust
25/// use sqlite_diff_rs::SimpleTable;
26/// use sqlite_diff_rs::{PatchSet, DiffSetBuilder};
27///
28/// let table = SimpleTable::new("users", &["id", "name"], &[0]);
29/// let mut patchset = PatchSet::<SimpleTable, String, Vec<u8>>::new();
30/// patchset.add_table(&table);
31/// patchset.digest_sql("INSERT INTO users (id, name) VALUES (1, 'Alice')").unwrap();
32/// ```
33#[derive(Debug, Clone, Eq)]
34pub struct SimpleTable {
35    /// The underlying table schema (for binary encoding).
36    schema: TableSchema<String>,
37    /// Column names in order.
38    columns: Vec<String>,
39}
40
41impl SimpleTable {
42    /// Create a new simple table schema.
43    ///
44    /// # Arguments
45    ///
46    /// * `name` - the table name.
47    /// * `columns` - column names in order.
48    /// * `pk_indices` - indices of primary key columns (in PK order).
49    ///
50    /// # Panics
51    ///
52    /// Panics if any `pk_indices` value is out of bounds.
53    #[must_use]
54    pub fn new(name: impl Into<String>, columns: &[&str], pk_indices: &[usize]) -> Self {
55        let name = name.into();
56        let columns: Vec<String> = columns.iter().map(|&c| String::from(c)).collect();
57        let column_count = columns.len();
58
59        // Convert pk_indices to pk_flags
60        let mut pk_flags = vec![0u8; column_count];
61        for (pk_ordinal, &col_idx) in pk_indices.iter().enumerate() {
62            assert!(col_idx < column_count, "PK index out of bounds");
63            pk_flags[col_idx] = u8::try_from(pk_ordinal + 1).expect("Too many PK columns");
64        }
65
66        Self {
67            schema: TableSchema::new(name, column_count, pk_flags),
68            columns,
69        }
70    }
71
72    /// Get the column names.
73    #[must_use]
74    pub fn column_names(&self) -> &[String] {
75        &self.columns
76    }
77
78    /// Get a column name by index.
79    #[must_use]
80    pub fn column_name(&self, index: usize) -> Option<&str> {
81        self.columns.get(index).map(String::as_str)
82    }
83
84    /// Get the column index by name.
85    #[must_use]
86    pub fn column_index(&self, name: &str) -> Option<usize> {
87        self.columns.iter().position(|c| c == name)
88    }
89
90    /// Get the indices of primary key columns, in PK order.
91    #[must_use]
92    pub fn pk_indices(&self) -> Vec<usize> {
93        self.schema.pk_indices()
94    }
95
96    /// Get the inner `TableSchema`.
97    #[must_use]
98    pub fn inner(&self) -> &TableSchema<String> {
99        &self.schema
100    }
101}
102
103impl PartialEq for SimpleTable {
104    fn eq(&self, other: &Self) -> bool {
105        self.schema == other.schema && self.columns == other.columns
106    }
107}
108
109impl Hash for SimpleTable {
110    fn hash<H: Hasher>(&self, state: &mut H) {
111        self.schema.hash(state);
112        self.columns.hash(state);
113    }
114}
115
116impl DynTable for SimpleTable {
117    #[inline]
118    fn name(&self) -> &str {
119        self.schema.name()
120    }
121
122    #[inline]
123    fn number_of_columns(&self) -> usize {
124        self.schema.number_of_columns()
125    }
126
127    #[inline]
128    fn write_pk_flags(&self, buf: &mut [u8]) {
129        self.schema.write_pk_flags(buf);
130    }
131}
132
133impl SchemaWithPK for SimpleTable {
134    fn number_of_primary_keys(&self) -> usize {
135        self.schema.number_of_primary_keys()
136    }
137
138    fn primary_key_index(&self, col_idx: usize) -> Option<usize> {
139        self.schema.primary_key_index(col_idx)
140    }
141
142    fn extract_pk<S, B>(
143        &self,
144        values: &impl IndexableValues<Text = S, Binary = B>,
145    ) -> alloc::vec::Vec<Value<S, B>>
146    where
147        S: Clone,
148        B: Clone,
149    {
150        self.schema.extract_pk(values)
151    }
152}
153
154/// Defines a schema in which the mapping of column names to
155/// column positions is known at runtime.
156pub trait NamedColumns: SchemaWithPK {
157    /// Get the column index for a given column name.
158    ///
159    /// Returns `Some(index)` if the column exists, or `None` if it doesn't.
160    fn column_index(&self, column_name: &str) -> Option<usize>;
161}
162
163impl NamedColumns for SimpleTable {
164    #[inline]
165    fn column_index(&self, column_name: &str) -> Option<usize> {
166        self.column_index(column_name)
167    }
168}
169
170impl<T: NamedColumns> NamedColumns for &T {
171    #[inline]
172    fn column_index(&self, column_name: &str) -> Option<usize> {
173        T::column_index(self, column_name)
174    }
175}