spacetimedb_lib/db/raw_def/v9.rs
1//! ABI Version 9 of the raw module definitions.
2//!
3//! This is the ABI that will be used for 1.0.
4//! We are keeping around the old ABI (v8) for now, to allow ourselves to convert the codebase
5//! a component-at-a-time.
6
7use std::any::TypeId;
8use std::collections::btree_map;
9use std::collections::BTreeMap;
10use std::fmt;
11
12use itertools::Itertools;
13use spacetimedb_primitives::*;
14use spacetimedb_sats::typespace::TypespaceBuilder;
15use spacetimedb_sats::AlgebraicType;
16use spacetimedb_sats::AlgebraicTypeRef;
17use spacetimedb_sats::AlgebraicValue;
18use spacetimedb_sats::ProductType;
19use spacetimedb_sats::ProductTypeElement;
20use spacetimedb_sats::SpacetimeType;
21use spacetimedb_sats::Typespace;
22
23use crate::db::auth::StAccess;
24use crate::db::auth::StTableType;
25
26/// A not-yet-validated identifier.
27pub type RawIdentifier = Box<str>;
28
29/// A not-yet-validated `sql`.
30pub type RawSql = Box<str>;
31
32/// A possibly-invalid raw module definition.
33///
34/// ABI Version 9.
35///
36/// These "raw definitions" may contain invalid data, and are validated by the `validate` module into a proper `spacetimedb_schema::ModuleDef`, or a collection of errors.
37///
38/// The module definition has a single logical global namespace, which maps `Identifier`s to:
39///
40/// - database-level objects:
41/// - logical schema objects:
42/// - tables
43/// - constraints
44/// - sequence definitions
45/// - physical schema objects:
46/// - indexes
47/// - module-level objects:
48/// - reducers
49/// - schedule definitions
50/// - binding-level objects:
51/// - type aliases
52///
53/// All of these types of objects must have unique names within the module.
54/// The exception is columns, which need unique names only within a table.
55#[derive(Debug, Clone, Default, SpacetimeType)]
56#[sats(crate = crate)]
57#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
58pub struct RawModuleDefV9 {
59 /// The `Typespace` used by the module.
60 ///
61 /// `AlgebraicTypeRef`s in the table, reducer, and type alias declarations refer to this typespace.
62 ///
63 /// The typespace must satisfy `Typespace::is_valid_for_client_code_generation`. That is, all types stored in the typespace must either:
64 /// 1. satisfy `AlgebraicType::is_valid_for_client_type_definition`
65 /// 2. and/or `AlgebraicType::is_valid_for_client_type_use`.
66 ///
67 /// Types satisfying condition 1 correspond to generated classes in client code.
68 /// (Types satisfying condition 2 are an artifact of the module bindings, and do not affect the semantics of the module definition.)
69 ///
70 /// Types satisfying condition 1 are required to have corresponding `RawTypeDefV9` declarations in the module.
71 pub typespace: Typespace,
72
73 /// The tables of the database definition used in the module.
74 ///
75 /// Each table must have a unique name.
76 pub tables: Vec<RawTableDefV9>,
77
78 /// The reducers exported by the module.
79 pub reducers: Vec<RawReducerDefV9>,
80
81 /// The types exported by the module.
82 pub types: Vec<RawTypeDefV9>,
83
84 /// Miscellaneous additional module exports.
85 pub misc_exports: Vec<RawMiscModuleExportV9>,
86
87 /// Low level security definitions.
88 ///
89 /// Each definition must have a unique name.
90 pub row_level_security: Vec<RawRowLevelSecurityDefV9>,
91}
92
93/// The definition of a database table.
94///
95/// This struct holds information about the table, including its name, columns, indexes,
96/// constraints, sequences, type, and access rights.
97///
98/// Validation rules:
99/// - The table name must be a valid `spacetimedb_schema::identifier::Identifier`.
100/// - The table's indexes, constraints, and sequences need not be sorted; they will be sorted according to their respective ordering rules.
101/// - The table's column types may refer only to types in the containing `RawModuleDefV9`'s typespace.
102/// - The table's column names must be unique.
103#[derive(Debug, Clone, SpacetimeType)]
104#[sats(crate = crate)]
105#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
106pub struct RawTableDefV9 {
107 /// The name of the table.
108 /// Unique within a module, acts as the table's identifier.
109 /// Must be a valid `spacetimedb_schema::identifier::Identifier`.
110 pub name: RawIdentifier,
111
112 /// A reference to a `ProductType` containing the columns of this table.
113 /// This is the single source of truth for the table's columns.
114 /// All elements of the `ProductType` must have names.
115 ///
116 /// Like all types in the module, this must have the [default element ordering](crate::db::default_element_ordering),
117 /// UNLESS a custom ordering is declared via a `RawTypeDefv9` for this type.
118 pub product_type_ref: AlgebraicTypeRef,
119
120 /// The primary key of the table, if present. Must refer to a valid column.
121 ///
122 /// Currently, there must be a unique constraint and an index corresponding to the primary key.
123 /// Eventually, we may remove the requirement for an index.
124 ///
125 /// The database engine does not actually care about this, but client code generation does.
126 ///
127 /// A list of length 0 means no primary key. Currently, a list of length >1 is not supported.
128 pub primary_key: ColList,
129
130 /// The indices of the table.
131 pub indexes: Vec<RawIndexDefV9>,
132
133 /// Any unique constraints on the table.
134 pub constraints: Vec<RawConstraintDefV9>,
135
136 /// The sequences for the table.
137 pub sequences: Vec<RawSequenceDefV9>,
138
139 /// The schedule for the table.
140 pub schedule: Option<RawScheduleDefV9>,
141
142 /// Whether this is a system- or user-created table.
143 pub table_type: TableType,
144
145 /// Whether this table is public or private.
146 pub table_access: TableAccess,
147}
148
149/// Whether the table was created by the system or the user.
150#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, SpacetimeType)]
151#[sats(crate = crate)]
152pub enum TableType {
153 /// Created by the system.
154 System,
155 /// Created by the user.
156 User,
157}
158impl From<StTableType> for TableType {
159 fn from(t: StTableType) -> Self {
160 match t {
161 StTableType::System => TableType::System,
162 StTableType::User => TableType::User,
163 }
164 }
165}
166impl From<TableType> for StTableType {
167 fn from(t: TableType) -> Self {
168 match t {
169 TableType::System => StTableType::System,
170 TableType::User => StTableType::User,
171 }
172 }
173}
174
175/// The visibility of the table.
176#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, SpacetimeType)]
177#[sats(crate = crate)]
178pub enum TableAccess {
179 /// Visible to all
180 Public,
181 /// Visible only to the owner
182 Private,
183}
184impl From<StAccess> for TableAccess {
185 fn from(t: StAccess) -> Self {
186 match t {
187 StAccess::Public => TableAccess::Public,
188 StAccess::Private => TableAccess::Private,
189 }
190 }
191}
192impl From<TableAccess> for StAccess {
193 fn from(t: TableAccess) -> Self {
194 match t {
195 TableAccess::Public => StAccess::Public,
196 TableAccess::Private => StAccess::Private,
197 }
198 }
199}
200
201/// A sequence definition for a database table column.
202#[derive(Debug, Clone, SpacetimeType)]
203#[sats(crate = crate)]
204#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
205pub struct RawSequenceDefV9 {
206 /// In the future, the user may FOR SOME REASON want to override this.
207 /// Even though there is ABSOLUTELY NO REASON TO.
208 /// If `None`, a nicely-formatted unique default will be chosen.
209 pub name: Option<Box<str>>,
210
211 /// The position of the column associated with this sequence.
212 /// This refers to a column in the same `RawTableDef` that contains this `RawSequenceDef`.
213 /// The column must have integral type.
214 /// This must be the unique `RawSequenceDef` for this column.
215 pub column: ColId,
216
217 /// The value to start assigning to this column.
218 /// Will be incremented by 1 for each new row.
219 /// If not present, an arbitrary start point may be selected.
220 pub start: Option<i128>,
221
222 /// The minimum allowed value in this column.
223 /// If not present, no minimum.
224 pub min_value: Option<i128>,
225
226 /// The maximum allowed value in this column.
227 /// If not present, no maximum.
228 pub max_value: Option<i128>,
229
230 /// The increment used when updating the SequenceDef.
231 pub increment: i128,
232}
233
234/// The definition of a database index.
235#[derive(Debug, Clone, SpacetimeType)]
236#[sats(crate = crate)]
237#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
238pub struct RawIndexDefV9 {
239 /// In the future, the user may FOR SOME REASON want to override this.
240 /// Even though there is ABSOLUTELY NO REASON TO.
241 pub name: Option<Box<str>>,
242
243 /// Accessor name for the index used in client codegen.
244 ///
245 /// This is set the user and should not be assumed to follow
246 /// any particular format.
247 ///
248 /// May be set to `None` if this is an auto-generated index for which the user
249 /// has not supplied a name. In this case, no client code generation for this index
250 /// will be performed.
251 ///
252 /// This name is not visible in the system tables, it is only used for client codegen.
253 pub accessor_name: Option<RawIdentifier>,
254
255 /// The algorithm parameters for the index.
256 pub algorithm: RawIndexAlgorithm,
257}
258
259/// Data specifying an index algorithm.
260/// New fields MUST be added to the END of this enum, to maintain ABI compatibility.
261#[non_exhaustive]
262#[derive(Debug, Clone, SpacetimeType)]
263#[sats(crate = crate)]
264#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
265pub enum RawIndexAlgorithm {
266 /// Implemented using a B-Tree.
267 ///
268 /// Currently, this uses a rust `std::collections::BTreeMap`.
269 BTree {
270 /// The columns to index on. These are ordered.
271 columns: ColList,
272 },
273 /// Currently forbidden.
274 Hash {
275 /// The columns to index on. These are ordered.
276 columns: ColList,
277 },
278 /// Implemented using direct indexing in list(s) of `RowPointer`s.
279 /// The column this is placed on must also have a unique constraint.
280 Direct {
281 /// The column to index on.
282 /// Only one is allowed, as direct indexing with more is nonsensical.
283 column: ColId,
284 },
285}
286
287/// Returns a btree index algorithm for the columns `cols`.
288pub fn btree(cols: impl Into<ColList>) -> RawIndexAlgorithm {
289 RawIndexAlgorithm::BTree { columns: cols.into() }
290}
291
292/// Returns a direct index algorithm for the column `col`.
293pub fn direct(col: impl Into<ColId>) -> RawIndexAlgorithm {
294 RawIndexAlgorithm::Direct { column: col.into() }
295}
296
297/// Marks a table as a timer table for a scheduled reducer.
298///
299/// The table must have columns:
300/// - `scheduled_id` of type `u64`.
301/// - `scheduled_at` of type `ScheduleAt`.
302#[derive(Debug, Clone, SpacetimeType)]
303#[sats(crate = crate)]
304#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
305pub struct RawScheduleDefV9 {
306 /// In the future, the user may FOR SOME REASON want to override this.
307 /// Even though there is ABSOLUTELY NO REASON TO.
308 pub name: Option<Box<str>>,
309
310 /// The name of the reducer to call.
311 pub reducer_name: RawIdentifier,
312
313 /// The column of the `scheduled_at` field of this scheduled table.
314 pub scheduled_at_column: ColId,
315}
316
317/// A constraint definition attached to a table.
318#[derive(Debug, Clone, SpacetimeType)]
319#[sats(crate = crate)]
320#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
321pub struct RawConstraintDefV9 {
322 /// In the future, the user may FOR SOME REASON want to override this.
323 /// Even though there is ABSOLUTELY NO REASON TO.
324 pub name: Option<Box<str>>,
325
326 /// The data for the constraint.
327 pub data: RawConstraintDataV9,
328}
329
330/// Raw data attached to a constraint.
331/// New fields MUST be added to the END of this enum, to maintain ABI compatibility.
332#[derive(Debug, Clone, SpacetimeType)]
333#[sats(crate = crate)]
334#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
335#[non_exhaustive]
336pub enum RawConstraintDataV9 {
337 Unique(RawUniqueConstraintDataV9),
338}
339
340/// Requires that the projection of the table onto these `columns` is a bijection.
341///
342/// That is, there must be a one-to-one relationship between a row and the `columns` of that row.
343#[derive(Debug, Clone, SpacetimeType)]
344#[sats(crate = crate)]
345#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
346pub struct RawUniqueConstraintDataV9 {
347 /// The columns that must be unique.
348 pub columns: ColList,
349}
350
351/// Data for the `RLS` policy on a table.
352#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)]
353#[sats(crate = crate)]
354#[cfg_attr(feature = "test", derive(PartialOrd, Ord))]
355pub struct RawRowLevelSecurityDefV9 {
356 /// The `sql` expression to use for row-level security.
357 pub sql: RawSql,
358}
359
360/// A miscellaneous module export.
361#[derive(Debug, Clone, SpacetimeType)]
362#[sats(crate = crate)]
363#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
364#[non_exhaustive]
365pub enum RawMiscModuleExportV9 {
366 ColumnDefaultValue(RawColumnDefaultValueV9),
367}
368
369/// Marks a particular table's column as having a particular default.
370#[derive(Debug, Clone, SpacetimeType)]
371#[sats(crate = crate)]
372#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
373pub struct RawColumnDefaultValueV9 {
374 /// Identifies which table that has the default value.
375 /// This corresponds to `name` in `RawTableDefV9`.
376 pub table: RawIdentifier,
377 /// Identifies which column of `table` that has the default value.
378 pub col_id: ColId,
379 /// A BSATN-encoded [`AlgebraicValue`] valid at the table column's type.
380 /// (We cannot use `AlgebraicValue` directly as it isn't `Spacetimetype`.)
381 pub value: Box<[u8]>,
382}
383
384/// A type declaration.
385///
386/// Exactly of these must be attached to every `Product` and `Sum` type used by a module.
387#[derive(Debug, Clone, SpacetimeType)]
388#[sats(crate = crate)]
389#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
390pub struct RawTypeDefV9 {
391 /// The name of the type declaration.
392 pub name: RawScopedTypeNameV9,
393
394 /// The type to which the declaration refers.
395 /// This must point to an `AlgebraicType::Product` or an `AlgebraicType::Sum` in the module's typespace.
396 pub ty: AlgebraicTypeRef,
397
398 /// Whether this type has a custom ordering.
399 pub custom_ordering: bool,
400}
401
402/// A scoped type name, in the form `scope0::scope1::...::scopeN::name`.
403///
404/// These are the names that will be used *in client code generation*, NOT the names used for types
405/// in the module source code.
406#[derive(Clone, SpacetimeType, PartialEq, Eq, PartialOrd, Ord)]
407#[sats(crate = crate)]
408pub struct RawScopedTypeNameV9 {
409 /// The scope for this type.
410 ///
411 /// Empty unless a sats `name` attribute is used, e.g.
412 /// `#[sats(name = "namespace.name")]` in Rust.
413 pub scope: Box<[RawIdentifier]>,
414
415 /// The name of the type. This must be unique within the module.
416 ///
417 /// Eventually, we may add more information to this, such as generic arguments.
418 pub name: RawIdentifier,
419}
420
421impl fmt::Debug for RawScopedTypeNameV9 {
422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423 for module in self.scope.iter() {
424 fmt::Debug::fmt(module, f)?;
425 f.write_str("::")?;
426 }
427 fmt::Debug::fmt(&self.name, f)?;
428 Ok(())
429 }
430}
431
432/// A reducer definition.
433#[derive(Debug, Clone, SpacetimeType)]
434#[sats(crate = crate)]
435#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
436pub struct RawReducerDefV9 {
437 /// The name of the reducer.
438 pub name: RawIdentifier,
439
440 /// The types and optional names of the parameters, in order.
441 /// This `ProductType` need not be registered in the typespace.
442 pub params: ProductType,
443
444 /// If the reducer has a special role in the module lifecycle, it should be marked here.
445 pub lifecycle: Option<Lifecycle>,
446}
447
448/// Special roles a reducer can play in the module lifecycle.
449#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SpacetimeType)]
450#[cfg_attr(feature = "enum-map", derive(enum_map::Enum))]
451#[sats(crate = crate)]
452#[non_exhaustive]
453pub enum Lifecycle {
454 /// The reducer will be invoked upon module initialization.
455 Init,
456 /// The reducer will be invoked when a client connects.
457 OnConnect,
458 /// The reducer will be invoked when a client disconnects.
459 OnDisconnect,
460}
461
462/// A builder for a [`RawModuleDefV9`].
463#[derive(Default)]
464pub struct RawModuleDefV9Builder {
465 /// The module definition.
466 module: RawModuleDefV9,
467 /// The type map from `T: 'static` Rust types to sats types.
468 type_map: BTreeMap<TypeId, AlgebraicTypeRef>,
469}
470
471impl RawModuleDefV9Builder {
472 /// Create a new, empty `RawModuleDefBuilder`.
473 pub fn new() -> Self {
474 Default::default()
475 }
476
477 /// Add a type to the in-progress module.
478 ///
479 /// The returned type must satisfy `AlgebraicType::is_valid_for_client_type_definition` or `AlgebraicType::is_valid_for_client_type_use` .
480 pub fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType {
481 TypespaceBuilder::add_type::<T>(self)
482 }
483
484 /// Create a table builder.
485 ///
486 /// Does not validate that the product_type_ref is valid; this is left to the module validation code.
487 pub fn build_table(
488 &mut self,
489 name: impl Into<RawIdentifier>,
490 product_type_ref: AlgebraicTypeRef,
491 ) -> RawTableDefBuilder<'_> {
492 let name = name.into();
493 RawTableDefBuilder {
494 module_def: &mut self.module,
495 table: RawTableDefV9 {
496 name,
497 product_type_ref,
498 indexes: vec![],
499 constraints: vec![],
500 sequences: vec![],
501 schedule: None,
502 primary_key: ColList::empty(),
503 table_type: TableType::User,
504 table_access: TableAccess::Public,
505 },
506 }
507 }
508
509 /// Build a new table with a product type.
510 /// Adds the type to the module.
511 pub fn build_table_with_new_type(
512 &mut self,
513 table_name: impl Into<RawIdentifier>,
514 product_type: impl Into<spacetimedb_sats::ProductType>,
515 custom_ordering: bool,
516 ) -> RawTableDefBuilder<'_> {
517 let table_name = table_name.into();
518
519 let product_type_ref = self.add_algebraic_type(
520 [],
521 table_name.clone(),
522 AlgebraicType::from(product_type.into()),
523 custom_ordering,
524 );
525
526 self.build_table(table_name, product_type_ref)
527 }
528
529 /// Build a new table with a product type, for testing.
530 /// Adds the type to the module.
531 pub fn build_table_with_new_type_for_tests(
532 &mut self,
533 table_name: impl Into<RawIdentifier>,
534 mut product_type: spacetimedb_sats::ProductType,
535 custom_ordering: bool,
536 ) -> RawTableDefBuilder<'_> {
537 self.add_expand_product_type_for_tests(&mut 0, &mut product_type);
538
539 self.build_table_with_new_type(table_name, product_type, custom_ordering)
540 }
541
542 fn add_expand_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut AlgebraicType) {
543 if ty.is_valid_for_client_type_use() {
544 return;
545 }
546
547 match ty {
548 AlgebraicType::Product(prod_ty) => self.add_expand_product_type_for_tests(name_gen, prod_ty),
549 AlgebraicType::Sum(sum_type) => {
550 if let Some(wrapped) = sum_type.as_option_mut() {
551 self.add_expand_type_for_tests(name_gen, wrapped);
552 } else {
553 for elem in sum_type.variants.iter_mut() {
554 self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
555 }
556 }
557 }
558 AlgebraicType::Array(ty) => {
559 self.add_expand_type_for_tests(name_gen, &mut ty.elem_ty);
560 return;
561 }
562 _ => return,
563 }
564
565 // Make the type into a ref.
566 let name = *name_gen;
567 let add_ty = core::mem::replace(ty, AlgebraicType::U8);
568 *ty = AlgebraicType::Ref(self.add_algebraic_type([], format!("gen_{name}"), add_ty, true));
569 *name_gen += 1;
570 }
571
572 fn add_expand_product_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut ProductType) {
573 for elem in ty.elements.iter_mut() {
574 self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
575 }
576 }
577
578 /// Add a type to the typespace, along with a type alias declaring its name.
579 /// This method should only be use for `AlgebraicType`s not corresponding to a Rust
580 /// type that implements `SpacetimeType`.
581 ///
582 /// Returns a reference to the newly-added type.
583 ///
584 /// NOT idempotent, calling this twice with the same name will cause errors during
585 /// validation.
586 ///
587 /// You must set `custom_ordering` if you're not using the default element ordering.
588 pub fn add_algebraic_type(
589 &mut self,
590 scope: impl IntoIterator<Item = RawIdentifier>,
591 name: impl Into<RawIdentifier>,
592 ty: spacetimedb_sats::AlgebraicType,
593 custom_ordering: bool,
594 ) -> AlgebraicTypeRef {
595 let ty = self.module.typespace.add(ty);
596 let scope = scope.into_iter().collect();
597 let name = name.into();
598 self.module.types.push(RawTypeDefV9 {
599 name: RawScopedTypeNameV9 { name, scope },
600 ty,
601 custom_ordering,
602 });
603 // We don't add a `TypeId` to `self.type_map`, because there may not be a corresponding Rust type! e.g. if we are randomly generating types in proptests.
604 ty
605 }
606
607 /// Add a reducer to the in-progress module.
608 /// Accepts a `ProductType` of reducer arguments for convenience.
609 /// The `ProductType` need not be registered in the typespace.
610 ///
611 /// Importantly, if the reducer's first argument is a `ReducerContext`, that
612 /// information should not be provided to this method.
613 /// That is an implementation detail handled by the module bindings and can be ignored.
614 /// As far as the module definition is concerned, the reducer's arguments
615 /// start with the first non-`ReducerContext` argument.
616 ///
617 /// (It is impossible, with the current implementation of `ReducerContext`, to
618 /// have more than one `ReducerContext` argument, at least in Rust.
619 /// This is because `SpacetimeType` is not implemented for `ReducerContext`,
620 /// so it can never act like an ordinary argument.)
621 pub fn add_reducer(
622 &mut self,
623 name: impl Into<RawIdentifier>,
624 params: spacetimedb_sats::ProductType,
625 lifecycle: Option<Lifecycle>,
626 ) {
627 self.module.reducers.push(RawReducerDefV9 {
628 name: name.into(),
629 params,
630 lifecycle,
631 });
632 }
633
634 /// Add a row-level security policy to the module.
635 ///
636 /// The `sql` expression should be a valid SQL expression that will be used to filter rows.
637 ///
638 /// **NOTE**: The `sql` expression must be unique within the module.
639 pub fn add_row_level_security(&mut self, sql: &str) {
640 self.module
641 .row_level_security
642 .push(RawRowLevelSecurityDefV9 { sql: sql.into() });
643 }
644
645 /// Get the typespace of the module.
646 pub fn typespace(&self) -> &Typespace {
647 &self.module.typespace
648 }
649
650 /// Finish building, consuming the builder and returning the module.
651 /// The module should be validated before use.
652 pub fn finish(self) -> RawModuleDefV9 {
653 self.module
654 }
655}
656
657/// Convert a string from a sats type-name annotation like `#[sats(name = "namespace.name")]` to a `RawScopedTypeNameV9`.
658/// We split the input on the strings `"::"` and `"."` to split up module paths.
659///
660/// TODO(1.0): build namespacing directly into the bindings macros so that we don't need to do this.
661pub fn sats_name_to_scoped_name(sats_name: &str) -> RawScopedTypeNameV9 {
662 // We can't use `&[char]: Pattern` for `split` here because "::" is not a char :/
663 let mut scope: Vec<RawIdentifier> = sats_name.split("::").flat_map(|s| s.split('.')).map_into().collect();
664 // Unwrapping to "" will result in a validation error down the line, which is exactly what we want.
665 let name = scope.pop().unwrap_or_default();
666 RawScopedTypeNameV9 {
667 scope: scope.into(),
668 name,
669 }
670}
671
672impl TypespaceBuilder for RawModuleDefV9Builder {
673 fn add(
674 &mut self,
675 typeid: TypeId,
676 name: Option<&'static str>,
677 make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
678 ) -> AlgebraicType {
679 let r = match self.type_map.entry(typeid) {
680 btree_map::Entry::Occupied(o) => *o.get(),
681 btree_map::Entry::Vacant(v) => {
682 // Bind a fresh alias to the unit type.
683 let slot_ref = self.module.typespace.add(AlgebraicType::unit());
684 // Relate `typeid -> fresh alias`.
685 v.insert(slot_ref);
686
687 // Alias provided? Relate `name -> slot_ref`.
688 if let Some(sats_name) = name {
689 let name = sats_name_to_scoped_name(sats_name);
690
691 self.module.types.push(RawTypeDefV9 {
692 name,
693 ty: slot_ref,
694 // TODO(1.0): we need to update the `TypespaceBuilder` trait to include
695 // a `custom_ordering` parameter.
696 // For now, we assume all types have custom orderings, since the derive
697 // macro doesn't know about the default ordering yet.
698 custom_ordering: true,
699 });
700 }
701
702 // Borrow of `v` has ended here, so we can now convince the borrow checker.
703 let ty = make_ty(self);
704 self.module.typespace[slot_ref] = ty;
705 slot_ref
706 }
707 };
708 AlgebraicType::Ref(r)
709 }
710}
711
712/// Builder for a `RawTableDef`.
713pub struct RawTableDefBuilder<'a> {
714 module_def: &'a mut RawModuleDefV9,
715 table: RawTableDefV9,
716}
717
718impl RawTableDefBuilder<'_> {
719 /// Sets the type of the table and return it.
720 ///
721 /// This is not about column algebraic types, but about whether the table
722 /// was created by the system or the user.
723 pub fn with_type(mut self, table_type: TableType) -> Self {
724 self.table.table_type = table_type;
725 self
726 }
727
728 /// Sets the access rights for the table and return it.
729 pub fn with_access(mut self, table_access: TableAccess) -> Self {
730 self.table.table_access = table_access;
731 self
732 }
733
734 /// Generates a [RawConstraintDefV9] using the supplied `columns`.
735 pub fn with_unique_constraint(mut self, columns: impl Into<ColList>) -> Self {
736 let columns = columns.into();
737 self.table.constraints.push(RawConstraintDefV9 {
738 name: None,
739 data: RawConstraintDataV9::Unique(RawUniqueConstraintDataV9 { columns }),
740 });
741 self
742 }
743
744 /// Adds a primary key to the table.
745 /// You must also add a unique constraint on the primary key column.
746 pub fn with_primary_key(mut self, column: impl Into<ColId>) -> Self {
747 self.table.primary_key = ColList::new(column.into());
748 self
749 }
750
751 /// Adds a primary key to the table, with corresponding unique constraint and sequence definitions.
752 /// You will also need to call [`Self::with_index`] to create an index on `column`.
753 pub fn with_auto_inc_primary_key(self, column: impl Into<ColId>) -> Self {
754 let column = column.into();
755 self.with_primary_key(column)
756 .with_unique_constraint(column)
757 .with_column_sequence(column)
758 }
759
760 /// Generates a [RawIndexDefV9] using the supplied `columns`.
761 pub fn with_index(mut self, algorithm: RawIndexAlgorithm, accessor_name: impl Into<RawIdentifier>) -> Self {
762 let accessor_name = accessor_name.into();
763
764 self.table.indexes.push(RawIndexDefV9 {
765 name: None,
766 accessor_name: Some(accessor_name),
767 algorithm,
768 });
769 self
770 }
771
772 /// Generates a [RawIndexDefV9] using the supplied `columns` but with no `accessor_name`.
773 pub fn with_index_no_accessor_name(mut self, algorithm: RawIndexAlgorithm) -> Self {
774 self.table.indexes.push(RawIndexDefV9 {
775 name: None,
776 accessor_name: None,
777 algorithm,
778 });
779 self
780 }
781
782 /// Adds a [RawSequenceDefV9] on the supplied `column`.
783 pub fn with_column_sequence(mut self, column: impl Into<ColId>) -> Self {
784 let column = column.into();
785 self.table.sequences.push(RawSequenceDefV9 {
786 name: None,
787 column,
788 start: None,
789 min_value: None,
790 max_value: None,
791 increment: 1,
792 });
793
794 self
795 }
796
797 /// Adds a schedule definition to the table.
798 ///
799 /// The table must have the appropriate columns for a scheduled table.
800 pub fn with_schedule(
801 mut self,
802 reducer_name: impl Into<RawIdentifier>,
803 scheduled_at_column: impl Into<ColId>,
804 ) -> Self {
805 let reducer_name = reducer_name.into();
806 let scheduled_at_column = scheduled_at_column.into();
807 self.table.schedule = Some(RawScheduleDefV9 {
808 name: None,
809 reducer_name,
810 scheduled_at_column,
811 });
812 self
813 }
814
815 /// Adds a default value for the `column`.
816 pub fn with_default_column_value(self, column: impl Into<ColId>, value: AlgebraicValue) -> Self {
817 // Added to `misc_exports` for backwards-compatibility reasons.
818 self.module_def
819 .misc_exports
820 .push(RawMiscModuleExportV9::ColumnDefaultValue(RawColumnDefaultValueV9 {
821 table: self.table.name.clone(),
822 col_id: column.into(),
823 value: spacetimedb_sats::bsatn::to_vec(&value).unwrap().into(),
824 }));
825 self
826 }
827
828 /// Build the table and add it to the module, returning the `product_type_ref` of the table.
829 pub fn finish(self) -> AlgebraicTypeRef {
830 self.table.product_type_ref
831 // self is now dropped.
832 }
833
834 /// Get the column ID of the column with the specified name, if any.
835 ///
836 /// Returns `None` if this `TableDef` has been constructed with an invalid `ProductTypeRef`,
837 /// or if no column exists with that name.
838 pub fn find_col_pos_by_name(&self, column: impl AsRef<str>) -> Option<ColId> {
839 let column = column.as_ref();
840 self.columns()?
841 .iter()
842 .position(|x| x.name().is_some_and(|s| s == column))
843 .map(|x| x.into())
844 }
845
846 /// Get the columns of this type.
847 ///
848 /// Returns `None` if this `TableDef` has been constructed with an invalid `ProductTypeRef`.
849 fn columns(&self) -> Option<&[ProductTypeElement]> {
850 self.module_def
851 .typespace
852 .get(self.table.product_type_ref)
853 .and_then(|ty| ty.as_product())
854 .map(|p| &p.elements[..])
855 }
856}
857
858impl Drop for RawTableDefBuilder<'_> {
859 fn drop(&mut self) {
860 self.module_def.tables.push(self.table.clone());
861 }
862}