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