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}