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}