spacetimedb_lib/db/raw_def/
v8.rs

1//! Database definitions v8, the last version before they were wrapped in an enum.
2//!
3//! Nothing to do with Chrome.
4
5use crate::db::auth::{StAccess, StTableType};
6use crate::relation::FieldName;
7use crate::{AlgebraicType, ProductType, SpacetimeType};
8use derive_more::Display;
9use spacetimedb_data_structures::map::HashSet;
10use spacetimedb_primitives::*;
11
12// TODO(1.0): move these definitions into this file,
13// along with the other structs contained in it,
14// which are currently in the crate root.
15pub use crate::ModuleDefBuilder as RawModuleDefV8Builder;
16pub use crate::RawModuleDefV8;
17
18/// The amount sequences allocate each time they over-run their allocation.
19///
20/// Note that we do not perform an initial allocation during `create_sequence` or at startup.
21/// Newly-created sequences will allocate the first time they are advanced.
22pub const SEQUENCE_ALLOCATION_STEP: i128 = 4096;
23
24/// Represents a sequence definition for a database table column.
25#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
26#[sats(crate = crate)]
27pub struct RawSequenceDefV8 {
28    /// The name of the sequence.
29    pub sequence_name: Box<str>,
30    /// The position of the column associated with this sequence.
31    pub col_pos: ColId,
32    /// The increment value for the sequence.
33    pub increment: i128,
34    /// The starting value for the sequence.
35    pub start: Option<i128>,
36    /// The minimum value for the sequence.
37    pub min_value: Option<i128>,
38    /// The maximum value for the sequence.
39    pub max_value: Option<i128>,
40    /// The number of values to preallocate for the sequence.
41    /// Deprecated, in the future this concept will no longer exist.
42    pub allocated: i128,
43}
44
45impl RawSequenceDefV8 {
46    /// Creates a new [RawSequenceDefV8] instance for a specific table and column.
47    ///
48    /// # Parameters
49    ///
50    /// * `table` - The name of the table.
51    /// * `seq_name` - The name of the sequence.
52    /// * `col_pos` - The position of the column in the `table`.
53    ///
54    /// # Example
55    ///
56    /// ```
57    /// use spacetimedb_lib::db::raw_def::*;
58    ///
59    /// let sequence_def = RawSequenceDefV8::for_column("my_table", "my_sequence", 1.into());
60    /// assert_eq!(&*sequence_def.sequence_name, "seq_my_table_my_sequence");
61    /// ```
62    pub fn for_column(table: &str, column_or_name: &str, col_pos: ColId) -> Self {
63        //removes the auto-generated suffix...
64        let seq_name = column_or_name.trim_start_matches(&format!("ct_{}_", table));
65
66        RawSequenceDefV8 {
67            sequence_name: format!("seq_{}_{}", table, seq_name).into(),
68            col_pos,
69            increment: 1,
70            start: None,
71            min_value: None,
72            max_value: None,
73            // Start with no values allocated. The first time we advance the sequence,
74            // we will allocate [`SEQUENCE_ALLOCATION_STEP`] values.
75            allocated: 0,
76        }
77    }
78}
79
80/// Which type of index to create.
81///
82/// Currently only `IndexType::BTree` is allowed.
83#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Display, SpacetimeType)]
84#[sats(crate = crate)]
85pub enum IndexType {
86    /// A BTree index.
87    BTree = 0,
88    /// A Hash index.
89    Hash = 1,
90}
91
92impl From<IndexType> for u8 {
93    fn from(value: IndexType) -> Self {
94        value as u8
95    }
96}
97
98impl TryFrom<u8> for IndexType {
99    type Error = ();
100    fn try_from(v: u8) -> Result<Self, Self::Error> {
101        match v {
102            0 => Ok(IndexType::BTree),
103            1 => Ok(IndexType::Hash),
104            _ => Err(()),
105        }
106    }
107}
108
109/// A struct representing the definition of a database index.
110#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
111#[sats(crate = crate)]
112pub struct RawIndexDefV8 {
113    /// The name of the index.
114    /// This should not be assumed to follow any particular format.
115    pub index_name: Box<str>,
116    /// Whether the index is unique.
117    pub is_unique: bool,
118    /// The type of the index.
119    pub index_type: IndexType,
120    /// List of column positions that compose the index.
121    pub columns: ColList,
122}
123
124impl RawIndexDefV8 {
125    /// Creates a new [RawIndexDefV8] with the provided parameters.
126    ///
127    /// # Parameters
128    ///
129    /// * `index_name`: The name of the index.
130    /// * `columns`: List of column positions that compose the index.
131    /// * `is_unique`: Indicates whether the index enforces uniqueness.
132    pub fn btree(index_name: Box<str>, columns: impl Into<ColList>, is_unique: bool) -> Self {
133        Self {
134            columns: columns.into(),
135            index_name,
136            is_unique,
137            index_type: IndexType::BTree,
138        }
139    }
140
141    /// Creates an [RawIndexDefV8] for a specific column of a table.
142    ///
143    /// This method generates an index name based on the table name, index name, column positions, and uniqueness constraint.
144    ///
145    /// # Example
146    ///
147    /// ```
148    /// use spacetimedb_primitives::ColList;
149    /// use spacetimedb_lib::db::raw_def::*;
150    ///
151    /// let index_def = RawIndexDefV8::for_column("my_table", "test", 1, true);
152    /// assert_eq!(&*index_def.index_name, "idx_my_table_test_unique");
153    /// ```
154    pub fn for_column(table: &str, index_or_name: &str, columns: impl Into<ColList>, is_unique: bool) -> Self {
155        let unique = if is_unique { "unique" } else { "non_unique" };
156
157        // Removes the auto-generated suffix from the index name.
158        let name = index_or_name.trim_start_matches(&format!("ct_{}_", table));
159
160        // Constructs the index name using a predefined format.
161        // No duplicate the `kind_name` that was added by an constraint
162        let name = if name.ends_with(&unique) {
163            format!("idx_{table}_{name}")
164        } else {
165            format!("idx_{table}_{name}_{unique}")
166        };
167        Self::btree(name.into(), columns, is_unique)
168    }
169}
170
171/// A struct representing the definition of a database column.
172#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
173#[sats(crate = crate)]
174pub struct RawColumnDefV8 {
175    /// The name of the column.
176    pub col_name: Box<str>,
177    /// The type of the column.
178    ///
179    /// Must satisfy [AlgebraicType::is_valid_for_client_type_use].
180    pub col_type: AlgebraicType,
181}
182
183impl RawColumnDefV8 {
184    /// Convert a product type to a list of column definitions.
185    pub fn from_product_type(value: ProductType) -> Vec<RawColumnDefV8> {
186        Vec::from(value.elements)
187            .into_iter()
188            .enumerate()
189            .map(|(pos, col)| {
190                let col_name = if let Some(name) = col.name {
191                    name
192                } else {
193                    format!("col_{pos}").into()
194                };
195
196                RawColumnDefV8 {
197                    col_name,
198                    col_type: col.algebraic_type,
199                }
200            })
201            .collect()
202    }
203}
204
205impl RawColumnDefV8 {
206    /// Creates a new [RawColumnDefV8] for a system field with the specified data type.
207    ///
208    /// This method is typically used to define system columns with predefined names and data types.
209    ///
210    /// # Parameters
211    ///
212    /// * `field_name`: The name for which to create a column definition.
213    /// * `col_type`: The [AlgebraicType] of the column.
214    ///
215    /// If `type_` is not `AlgebraicType::Builtin` or `AlgebraicType::Ref`, an error will result at validation time.
216    pub fn sys(field_name: &str, col_type: AlgebraicType) -> Self {
217        Self {
218            col_name: field_name.into(),
219            col_type,
220        }
221    }
222}
223
224/// A struct representing the definition of a database constraint.
225/// Associated with a unique `TableDef`, the one that contains it.
226#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
227#[sats(crate = crate)]
228pub struct RawConstraintDefV8 {
229    /// The name of the constraint.
230    pub constraint_name: Box<str>,
231    /// The constraints applied to the columns.
232    pub constraints: Constraints,
233    /// List of column positions associated with the constraint.
234    pub columns: ColList,
235}
236
237impl RawConstraintDefV8 {
238    /// Creates a new [RawConstraintDefV8] with the specified parameters.
239    ///
240    /// # Parameters
241    ///
242    /// * `constraint_name`: The name of the constraint.
243    /// * `constraints`: The constraints.
244    /// * `columns`: List of column positions associated with the constraint.
245    pub fn new(constraint_name: Box<str>, constraints: Constraints, columns: impl Into<ColList>) -> Self {
246        Self {
247            constraint_name,
248            constraints,
249            columns: columns.into(),
250        }
251    }
252
253    /// Creates a `ConstraintDef` for a specific column of a table.
254    ///
255    /// This method generates a constraint name based on the table name, column name, and constraint type.
256    ///
257    /// # Parameters
258    ///
259    /// * `table`: The name of the table to which the constraint belongs.
260    /// * `column_name`: The name of the column associated with the constraint.
261    /// * `constraints`: The constraints.
262    /// * `columns`: List of column positions associated with the constraint.
263    ///
264    /// # Example
265    ///
266    /// ```
267    /// use spacetimedb_primitives::{Constraints, ColList};
268    /// use spacetimedb_lib::db::raw_def::*;
269    ///
270    /// let constraint_def = RawConstraintDefV8::for_column("my_table", "test", Constraints::identity(), 1);
271    /// assert_eq!(&*constraint_def.constraint_name, "ct_my_table_test_identity");
272    /// ```
273    pub fn for_column(
274        table: &str,
275        column_or_name: &str,
276        constraints: Constraints,
277        columns: impl Into<ColList>,
278    ) -> Self {
279        //removes the auto-generated suffix...
280        let name = column_or_name.trim_start_matches(&format!("idx_{}_", table));
281
282        let kind_name = format!("{:?}", constraints.kind()).to_lowercase();
283        // No duplicate the `kind_name` that was added by an index
284        if name.ends_with(&kind_name) {
285            Self::new(format!("ct_{table}_{name}").into(), constraints, columns)
286        } else {
287            Self::new(format!("ct_{table}_{name}_{kind_name}").into(), constraints, columns)
288        }
289    }
290}
291
292/// Concatenate the column names from the `columns`
293///
294/// WARNING: If the `ColId` not exist, is skipped.
295/// TODO(Tyler): This should return an error and not allow this to be constructed
296/// if there is an invalid `ColId`
297pub fn generate_cols_name<'a>(columns: &ColList, col_name: impl Fn(ColId) -> Option<&'a str>) -> String {
298    let mut column_name = Vec::with_capacity(columns.len() as usize);
299    column_name.extend(columns.iter().filter_map(col_name));
300    column_name.join("_")
301}
302
303/// A data structure representing the definition of a database table.
304///
305/// This struct holds information about the table, including its name, columns, indexes,
306/// constraints, sequences, type, and access rights.
307#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
308#[sats(crate = crate)]
309pub struct RawTableDefV8 {
310    /// The name of the table.
311    pub table_name: Box<str>,
312    /// The columns of the table.
313    /// The ordering of the columns is significant. Columns are frequently identified by `ColId`, that is, position in this list.
314    pub columns: Vec<RawColumnDefV8>,
315    /// The indexes on the table.
316    pub indexes: Vec<RawIndexDefV8>,
317    /// The constraints on the table.
318    pub constraints: Vec<RawConstraintDefV8>,
319    /// The sequences attached to the table.
320    pub sequences: Vec<RawSequenceDefV8>,
321    /// Whether the table was created by a user or by the system.
322    pub table_type: StTableType,
323    /// The visibility of the table.
324    pub table_access: StAccess,
325    /// If this is a schedule table, the reducer it is scheduled for.
326    pub scheduled: Option<Box<str>>,
327}
328
329impl RawTableDefV8 {
330    /// Create a new `TableDef` instance with the specified `table_name` and `columns`.
331    ///
332    /// # Parameters
333    ///
334    /// - `table_name`: The name of the table.
335    /// - `columns`: A `vec` of `ColumnDef` instances representing the columns of the table.
336    ///
337    pub fn new(table_name: Box<str>, columns: Vec<RawColumnDefV8>) -> Self {
338        Self {
339            table_name,
340            columns,
341            indexes: vec![],
342            constraints: vec![],
343            sequences: vec![],
344            table_type: StTableType::User,
345            table_access: StAccess::Public,
346            scheduled: None,
347        }
348    }
349
350    #[cfg(feature = "test")]
351    pub fn new_for_tests(table_name: impl Into<Box<str>>, columns: ProductType) -> Self {
352        Self::new(table_name.into(), RawColumnDefV8::from_product_type(columns))
353    }
354
355    /// Set the type of the table and return a new `TableDef` instance with the updated type.
356    pub fn with_type(self, table_type: StTableType) -> Self {
357        let mut x = self;
358        x.table_type = table_type;
359        x
360    }
361
362    /// Set the access rights for the table and return a new `TableDef` instance with the updated access rights.
363    pub fn with_access(self, table_access: StAccess) -> Self {
364        let mut x = self;
365        x.table_access = table_access;
366        x
367    }
368
369    /// Set the constraints for the table and return a new `TableDef` instance with the updated constraints.
370    pub fn with_constraints(self, constraints: Vec<RawConstraintDefV8>) -> Self {
371        let mut x = self;
372        x.constraints = constraints;
373        x
374    }
375
376    /// Concatenate the column names from the `columns`
377    ///
378    /// WARNING: If the `ColId` not exist, is skipped.
379    /// TODO(Tyler): This should return an error and not allow this to be constructed
380    /// if there is an invalid `ColId`
381    fn generate_cols_name(&self, columns: &ColList) -> String {
382        generate_cols_name(columns, |p| self.get_column(p.idx()).map(|c| &*c.col_name))
383    }
384
385    /// Generate a [RawConstraintDefV8] using the supplied `columns`.
386    pub fn with_column_constraint(mut self, kind: Constraints, columns: impl Into<ColList>) -> Self {
387        self.constraints.push(self.gen_constraint_def(kind, columns));
388        self
389    }
390
391    fn gen_constraint_def(&self, kind: Constraints, columns: impl Into<ColList>) -> RawConstraintDefV8 {
392        let columns = columns.into();
393        RawConstraintDefV8::for_column(&self.table_name, &self.generate_cols_name(&columns), kind, columns)
394    }
395
396    /// Set the indexes for the table and return a new `TableDef` instance with the updated indexes.
397    pub fn with_indexes(self, indexes: Vec<RawIndexDefV8>) -> Self {
398        let mut x = self;
399        x.indexes = indexes;
400        x
401    }
402
403    /// Generate a [RawIndexDefV8] using the supplied `columns`.
404    pub fn with_column_index(self, columns: impl Into<ColList>, is_unique: bool) -> Self {
405        let mut x = self;
406        let columns = columns.into();
407        x.indexes.push(RawIndexDefV8::for_column(
408            &x.table_name,
409            &x.generate_cols_name(&columns),
410            columns,
411            is_unique,
412        ));
413        x
414    }
415
416    /// Set the sequences for the table and return a new `TableDef` instance with the updated sequences.
417    pub fn with_sequences(self, sequences: Vec<RawSequenceDefV8>) -> Self {
418        let mut x = self;
419        x.sequences = sequences;
420        x
421    }
422
423    /// Generate a [RawSequenceDefV8] using the supplied `columns`.
424    pub fn with_column_sequence(self, columns: ColId) -> Self {
425        let mut x = self;
426
427        x.sequences.push(RawSequenceDefV8::for_column(
428            &x.table_name,
429            &x.generate_cols_name(&ColList::new(columns)),
430            columns,
431        ));
432        x
433    }
434
435    /// Set the reducer name for scheduled tables and return updated `TableDef`.
436    pub fn with_scheduled(mut self, scheduled: Option<Box<str>>) -> Self {
437        self.scheduled = scheduled;
438        self
439    }
440
441    /// Create a `TableDef` from a product type and table name.
442    ///
443    /// NOTE: If the [ProductType.name] is `None` then it auto-generate a name like `col_{col_pos}`
444    pub fn from_product(table_name: &str, row: ProductType) -> Self {
445        Self::new(
446            table_name.into(),
447            Vec::from(row.elements)
448                .into_iter()
449                .enumerate()
450                .map(|(col_pos, e)| RawColumnDefV8 {
451                    col_name: e.name.unwrap_or_else(|| format!("col_{col_pos}").into()),
452                    col_type: e.algebraic_type,
453                })
454                .collect::<Vec<_>>(),
455        )
456    }
457
458    /// Get an iterator deriving [RawIndexDefV8]s from the constraints that require them like `UNIQUE`.
459    ///
460    /// It looks into [Self::constraints] for possible duplicates and remove them from the result
461    pub fn generated_indexes(&self) -> impl Iterator<Item = RawIndexDefV8> + '_ {
462        self.constraints
463            .iter()
464            // We are only interested in constraints implying an index.
465            .filter(|x| x.constraints.has_indexed())
466            // Create the `IndexDef`.
467            .map(|x| {
468                let is_unique = x.constraints.has_unique();
469                RawIndexDefV8::for_column(&self.table_name, &x.constraint_name, x.columns.clone(), is_unique)
470            })
471            // Only keep those we don't yet have in the list of indices (checked by name).
472            .filter(|idx| self.indexes.iter().all(|x| x.index_name != idx.index_name))
473    }
474
475    /// Get an iterator deriving [RawSequenceDefV8] from the constraints that require them like `IDENTITY`.
476    ///
477    /// It looks into [Self::constraints] for possible duplicates and remove them from the result
478    pub fn generated_sequences(&self) -> impl Iterator<Item = RawSequenceDefV8> + '_ {
479        let cols: HashSet<_> = self.sequences.iter().map(|seq| ColList::new(seq.col_pos)).collect();
480
481        self.constraints
482            .iter()
483            // We are only interested in constraints implying a sequence.
484            .filter(move |x| !cols.contains(&x.columns) && x.constraints.has_autoinc())
485            // Create the `SequenceDef`.
486            .map(|x| RawSequenceDefV8::for_column(&self.table_name, &x.constraint_name, x.columns.head().unwrap()))
487            // Only keep those we don't yet have in the list of sequences (checked by name).
488            .filter(|seq| self.sequences.iter().all(|x| x.sequence_name != seq.sequence_name))
489    }
490
491    /// Get an iterator deriving [RawConstraintDefV8] from the indexes that require them like `UNIQUE`.
492    ///
493    /// It looks into Self::constraints for possible duplicates and remove them from the result
494    pub fn generated_constraints(&self) -> impl Iterator<Item = RawConstraintDefV8> + '_ {
495        // Collect the set of all col-lists with a constraint.
496        let cols: HashSet<_> = self
497            .constraints
498            .iter()
499            .filter(|x| x.constraints.kind() != ConstraintKind::UNSET)
500            .map(|x| &x.columns)
501            .collect();
502
503        // Those indices that are not present in the constraints above
504        // have constraints generated for them.
505        // When `idx.is_unique`, a unique constraint is generated rather than an indexed one.
506        self.indexes
507            .iter()
508            .filter(move |idx| !cols.contains(&idx.columns))
509            .map(|idx| self.gen_constraint_def(Constraints::from_is_unique(idx.is_unique), idx.columns.clone()))
510    }
511
512    /// Check if the `name` of the [FieldName] exist on this [RawTableDefV8]
513    ///
514    /// Warning: It ignores the `table_id`
515    pub fn get_column_by_field(&self, field: FieldName) -> Option<&RawColumnDefV8> {
516        self.get_column(field.col.idx())
517    }
518
519    /// Get a column by its position in the table.
520    pub fn get_column(&self, pos: usize) -> Option<&RawColumnDefV8> {
521        self.columns.get(pos)
522    }
523
524    /// Check if the `col_name` exist on this [RawTableDefV8]
525    ///
526    /// Warning: It ignores the `table_name`
527    pub fn get_column_by_name(&self, col_name: &str) -> Option<&RawColumnDefV8> {
528        self.columns.iter().find(|x| &*x.col_name == col_name)
529    }
530}