Skip to main content

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::*;
9use spacetimedb_sats::raw_identifier::RawIdentifier;
10
11// TODO(1.0): move these definitions into this file,
12// along with the other structs contained in it,
13// which are currently in the crate root.
14pub use crate::ModuleDefBuilder as RawModuleDefV8Builder;
15pub use crate::RawModuleDefV8;
16
17/// The amount sequences allocate each time they over-run their allocation.
18///
19/// Note that we do not perform an initial allocation during `create_sequence` or at startup.
20/// Newly-created sequences will allocate the first time they are advanced.
21pub const SEQUENCE_ALLOCATION_STEP: i128 = 4096;
22
23/// Represents a sequence definition for a database table column.
24#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
25#[sats(crate = crate)]
26pub struct RawSequenceDefV8 {
27    /// The name of the sequence.
28    pub sequence_name: RawIdentifier,
29    /// The position of the column associated with this sequence.
30    pub col_pos: ColId,
31    /// The increment value for the sequence.
32    pub increment: i128,
33    /// The starting value for the sequence.
34    pub start: Option<i128>,
35    /// The minimum value for the sequence.
36    pub min_value: Option<i128>,
37    /// The maximum value for the sequence.
38    pub max_value: Option<i128>,
39    /// The number of values to preallocate for the sequence.
40    /// Deprecated, in the future this concept will no longer exist.
41    pub allocated: i128,
42}
43
44impl RawSequenceDefV8 {
45    /// Creates a new [RawSequenceDefV8] instance for a specific table and column.
46    ///
47    /// # Parameters
48    ///
49    /// * `table` - The name of the table.
50    /// * `seq_name` - The name of the sequence.
51    /// * `col_pos` - The position of the column in the `table`.
52    ///
53    /// # Example
54    ///
55    /// ```
56    /// use spacetimedb_lib::db::raw_def::*;
57    ///
58    /// let sequence_def = RawSequenceDefV8::for_column("my_table", "my_sequence", 1.into());
59    /// assert_eq!(&*sequence_def.sequence_name, "seq_my_table_my_sequence");
60    /// ```
61    pub fn for_column(table: &str, column_or_name: &str, col_pos: ColId) -> Self {
62        //removes the auto-generated suffix...
63        let seq_name = column_or_name.trim_start_matches(&format!("ct_{table}_"));
64
65        RawSequenceDefV8 {
66            sequence_name: RawIdentifier::new(format!("seq_{table}_{seq_name}")),
67            col_pos,
68            increment: 1,
69            start: None,
70            min_value: None,
71            max_value: None,
72            // Start with no values allocated. The first time we advance the sequence,
73            // we will allocate [`SEQUENCE_ALLOCATION_STEP`] values.
74            allocated: 0,
75        }
76    }
77}
78
79/// Which type of index to create.
80///
81/// Currently only `IndexType::BTree` is allowed.
82#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Display, SpacetimeType)]
83#[sats(crate = crate)]
84pub enum IndexType {
85    /// A BTree index.
86    BTree = 0,
87    /// A Hash index.
88    Hash = 1,
89}
90
91impl From<IndexType> for u8 {
92    fn from(value: IndexType) -> Self {
93        value as u8
94    }
95}
96
97impl TryFrom<u8> for IndexType {
98    type Error = ();
99    fn try_from(v: u8) -> Result<Self, Self::Error> {
100        match v {
101            0 => Ok(IndexType::BTree),
102            1 => Ok(IndexType::Hash),
103            _ => Err(()),
104        }
105    }
106}
107
108/// A struct representing the definition of a database index.
109#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
110#[sats(crate = crate)]
111pub struct RawIndexDefV8 {
112    /// The name of the index.
113    /// This should not be assumed to follow any particular format.
114    pub index_name: RawIdentifier,
115    /// Whether the index is unique.
116    pub is_unique: bool,
117    /// The type of the index.
118    pub index_type: IndexType,
119    /// List of column positions that compose the index.
120    pub columns: ColList,
121}
122
123impl RawIndexDefV8 {
124    /// Creates a new [RawIndexDefV8] with the provided parameters.
125    ///
126    /// # Parameters
127    ///
128    /// * `index_name`: The name of the index.
129    /// * `columns`: List of column positions that compose the index.
130    /// * `is_unique`: Indicates whether the index enforces uniqueness.
131    pub fn btree(index_name: RawIdentifier, columns: impl Into<ColList>, is_unique: bool) -> Self {
132        Self {
133            columns: columns.into(),
134            index_name,
135            is_unique,
136            index_type: IndexType::BTree,
137        }
138    }
139
140    /// Creates an [RawIndexDefV8] for a specific column of a table.
141    ///
142    /// This method generates an index name based on the table name, index name, column positions, and uniqueness constraint.
143    ///
144    /// # Example
145    ///
146    /// ```
147    /// use spacetimedb_primitives::ColList;
148    /// use spacetimedb_lib::db::raw_def::*;
149    ///
150    /// let index_def = RawIndexDefV8::for_column("my_table", "test", 1, true);
151    /// assert_eq!(&*index_def.index_name, "idx_my_table_test_unique");
152    /// ```
153    pub fn for_column(table: &str, index_or_name: &str, columns: impl Into<ColList>, is_unique: bool) -> Self {
154        let unique = if is_unique { "unique" } else { "non_unique" };
155
156        // Removes the auto-generated suffix from the index name.
157        let name = index_or_name.trim_start_matches(&format!("ct_{table}_"));
158
159        // Constructs the index name using a predefined format.
160        // No duplicate the `kind_name` that was added by an constraint
161        let name = if name.ends_with(&unique) {
162            format!("idx_{table}_{name}")
163        } else {
164            format!("idx_{table}_{name}_{unique}")
165        };
166        Self::btree(RawIdentifier::new(name), columns, is_unique)
167    }
168}
169
170/// A struct representing the definition of a database column.
171#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
172#[sats(crate = crate)]
173pub struct RawColumnDefV8 {
174    /// The name of the column.
175    pub col_name: RawIdentifier,
176    /// The type of the column.
177    ///
178    /// Must satisfy [AlgebraicType::is_valid_for_client_type_use].
179    pub col_type: AlgebraicType,
180}
181
182impl RawColumnDefV8 {
183    /// Convert a product type to a list of column definitions.
184    pub fn from_product_type(value: ProductType) -> Vec<RawColumnDefV8> {
185        Vec::from(value.elements)
186            .into_iter()
187            .enumerate()
188            .map(|(pos, col)| {
189                let col_name = if let Some(name) = col.name {
190                    name
191                } else {
192                    RawIdentifier::new(format!("col_{pos}"))
193                };
194
195                RawColumnDefV8 {
196                    col_name,
197                    col_type: col.algebraic_type,
198                }
199            })
200            .collect()
201    }
202}
203
204impl RawColumnDefV8 {
205    /// Creates a new [RawColumnDefV8] for a system field with the specified data type.
206    ///
207    /// This method is typically used to define system columns with predefined names and data types.
208    ///
209    /// # Parameters
210    ///
211    /// * `field_name`: The name for which to create a column definition.
212    /// * `col_type`: The [AlgebraicType] of the column.
213    ///
214    /// If `type_` is not `AlgebraicType::Builtin` or `AlgebraicType::Ref`, an error will result at validation time.
215    pub fn sys(field_name: &str, col_type: AlgebraicType) -> Self {
216        Self {
217            col_name: RawIdentifier::new(field_name),
218            col_type,
219        }
220    }
221}
222
223/// A struct representing the definition of a database constraint.
224/// Associated with a unique `TableDef`, the one that contains it.
225#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
226#[sats(crate = crate)]
227pub struct RawConstraintDefV8 {
228    /// The name of the constraint.
229    pub constraint_name: RawIdentifier,
230    /// The constraints applied to the columns.
231    pub constraints: Constraints,
232    /// List of column positions associated with the constraint.
233    pub columns: ColList,
234}
235
236impl RawConstraintDefV8 {
237    /// Creates a new [RawConstraintDefV8] with the specified parameters.
238    ///
239    /// # Parameters
240    ///
241    /// * `constraint_name`: The name of the constraint.
242    /// * `constraints`: The constraints.
243    /// * `columns`: List of column positions associated with the constraint.
244    pub fn new(constraint_name: RawIdentifier, constraints: Constraints, columns: impl Into<ColList>) -> Self {
245        Self {
246            constraint_name,
247            constraints,
248            columns: columns.into(),
249        }
250    }
251
252    /// Creates a `ConstraintDef` for a specific column of a table.
253    ///
254    /// This method generates a constraint name based on the table name, column name, and constraint type.
255    ///
256    /// # Parameters
257    ///
258    /// * `table`: The name of the table to which the constraint belongs.
259    /// * `column_name`: The name of the column associated with the constraint.
260    /// * `constraints`: The constraints.
261    /// * `columns`: List of column positions associated with the constraint.
262    ///
263    /// # Example
264    ///
265    /// ```
266    /// use spacetimedb_primitives::{Constraints, ColList};
267    /// use spacetimedb_lib::db::raw_def::*;
268    ///
269    /// let constraint_def = RawConstraintDefV8::for_column("my_table", "test", Constraints::identity(), 1);
270    /// assert_eq!(&*constraint_def.constraint_name, "ct_my_table_test_identity");
271    /// ```
272    pub fn for_column(
273        table: &str,
274        column_or_name: &str,
275        constraints: Constraints,
276        columns: impl Into<ColList>,
277    ) -> Self {
278        //removes the auto-generated suffix...
279        let name = column_or_name.trim_start_matches(&format!("idx_{table}_"));
280
281        let kind_name = format!("{:?}", constraints.kind()).to_lowercase();
282        // No duplicate the `kind_name` that was added by an index
283        let name = if name.ends_with(&kind_name) {
284            format!("ct_{table}_{name}")
285        } else {
286            format!("ct_{table}_{name}_{kind_name}")
287        };
288        Self::new(RawIdentifier::new(name), constraints, columns)
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: RawIdentifier,
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<RawIdentifier>,
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: RawIdentifier, 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<RawIdentifier>, 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    pub 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<RawIdentifier>) -> 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: RawIdentifier, row: ProductType) -> Self {
445        Self::new(
446            table_name,
447            Vec::from(row.elements)
448                .into_iter()
449                .enumerate()
450                .map(|(col_pos, e)| RawColumnDefV8 {
451                    col_name: e.name.unwrap_or_else(|| RawIdentifier::new(format!("col_{col_pos}"))),
452                    col_type: e.algebraic_type,
453                })
454                .collect::<Vec<_>>(),
455        )
456    }
457
458    /// Get a column by its position in the table.
459    pub fn get_column(&self, pos: usize) -> Option<&RawColumnDefV8> {
460        self.columns.get(pos)
461    }
462
463    /// Check if the `col_name` exist on this [RawTableDefV8]
464    ///
465    /// Warning: It ignores the `table_name`
466    pub fn get_column_by_name(&self, col_name: &str) -> Option<&RawColumnDefV8> {
467        self.columns.iter().find(|x| &*x.col_name == col_name)
468    }
469}