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;
25use crate::db::raw_def::v10::RawConstraintDefV10;
26use crate::db::raw_def::v10::RawIndexDefV10;
27use crate::db::raw_def::v10::RawScopedTypeNameV10;
28use crate::db::raw_def::v10::RawSequenceDefV10;
29use crate::db::raw_def::v10::RawTypeDefV10;
30
31/// A not-yet-validated identifier.
32pub type RawIdentifier = Box<str>;
33
34/// A not-yet-validated `sql`.
35pub type RawSql = Box<str>;
36
37/// A possibly-invalid raw module definition.
38///
39/// ABI Version 9.
40///
41/// 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.
42///
43/// The module definition has a single logical global namespace, which maps `Identifier`s to:
44///
45/// - database-level objects:
46/// - logical schema objects:
47/// - tables
48/// - constraints
49/// - sequence definitions
50/// - physical schema objects:
51/// - indexes
52/// - module-level objects:
53/// - reducers
54/// - schedule definitions
55/// - binding-level objects:
56/// - type aliases
57///
58/// All of these types of objects must have unique names within the module.
59/// The exception is columns, which need unique names only within a table.
60#[derive(Debug, Clone, Default, SpacetimeType)]
61#[sats(crate = crate)]
62#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
63pub struct RawModuleDefV9 {
64 /// The `Typespace` used by the module.
65 ///
66 /// `AlgebraicTypeRef`s in the table, reducer, and type alias declarations refer to this typespace.
67 ///
68 /// The typespace must satisfy `Typespace::is_valid_for_client_code_generation`. That is, all types stored in the typespace must either:
69 /// 1. satisfy `AlgebraicType::is_valid_for_client_type_definition`
70 /// 2. and/or `AlgebraicType::is_valid_for_client_type_use`.
71 ///
72 /// Types satisfying condition 1 correspond to generated classes in client code.
73 /// (Types satisfying condition 2 are an artifact of the module bindings, and do not affect the semantics of the module definition.)
74 ///
75 /// Types satisfying condition 1 are required to have corresponding `RawTypeDefV9` declarations in the module.
76 pub typespace: Typespace,
77
78 /// The tables of the database definition used in the module.
79 ///
80 /// Each table must have a unique name.
81 pub tables: Vec<RawTableDefV9>,
82
83 /// The reducers exported by the module.
84 pub reducers: Vec<RawReducerDefV9>,
85
86 /// The types exported by the module.
87 pub types: Vec<RawTypeDefV9>,
88
89 /// Miscellaneous additional module exports.
90 ///
91 /// The enum [`RawMiscModuleExportV9`] can have new variants added
92 /// without breaking existing compiled modules.
93 /// As such, this acts as a sort of dumping ground for any exports added after we defined `RawModuleDefV9`.
94 ///
95 /// If/when we define `RawModuleDefV10`, these should be moved out of `misc_exports` and into their own fields,
96 /// and the new `misc_exports` should once again be initially empty.
97 pub misc_exports: Vec<RawMiscModuleExportV9>,
98
99 /// Row level security definitions.
100 ///
101 /// Each definition must have a unique name.
102 pub row_level_security: Vec<RawRowLevelSecurityDefV9>,
103}
104
105impl RawModuleDefV9 {
106 /// Find a [`RawTableDefV9`] by name in this raw module def
107 fn find_table_def(&self, table_name: &str) -> Option<&RawTableDefV9> {
108 self.tables
109 .iter()
110 .find(|table_def| table_def.name.as_ref() == table_name)
111 }
112
113 /// Find a [`RawViewDefV9`] by name in this raw module def
114 fn find_view_def(&self, view_name: &str) -> Option<&RawViewDefV9> {
115 self.misc_exports.iter().find_map(|misc_export| match misc_export {
116 RawMiscModuleExportV9::View(view_def) if view_def.name.as_ref() == view_name => Some(view_def),
117 _ => None,
118 })
119 }
120
121 /// Find and return the product type ref for a table in this module def
122 fn type_ref_for_table(&self, table_name: &str) -> Option<AlgebraicTypeRef> {
123 self.find_table_def(table_name)
124 .map(|table_def| table_def.product_type_ref)
125 }
126
127 /// Find and return the product type ref for a view in this module def
128 fn type_ref_for_view(&self, view_name: &str) -> Option<AlgebraicTypeRef> {
129 self.find_view_def(view_name)
130 .map(|view_def| &view_def.return_type)
131 .and_then(|return_type| {
132 return_type
133 .as_option()
134 .and_then(|inner| inner.clone().into_ref().ok())
135 .or_else(|| {
136 return_type
137 .as_array()
138 .and_then(|inner| inner.elem_ty.clone().into_ref().ok())
139 })
140 })
141 }
142
143 /// Find and return the product type ref for a table or view in this module def
144 pub fn type_ref_for_table_like(&self, name: &str) -> Option<AlgebraicTypeRef> {
145 self.type_ref_for_table(name).or_else(|| self.type_ref_for_view(name))
146 }
147}
148
149/// The definition of a database table.
150///
151/// This struct holds information about the table, including its name, columns, indexes,
152/// constraints, sequences, type, and access rights.
153///
154/// Validation rules:
155/// - The table name must be a valid `spacetimedb_schema::identifier::Identifier`.
156/// - The table's indexes, constraints, and sequences need not be sorted; they will be sorted according to their respective ordering rules.
157/// - The table's column types may refer only to types in the containing `RawModuleDefV9`'s typespace.
158/// - The table's column names must be unique.
159#[derive(Debug, Clone, SpacetimeType)]
160#[sats(crate = crate)]
161#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
162pub struct RawTableDefV9 {
163 /// The name of the table.
164 /// Unique within a module, acts as the table's identifier.
165 /// Must be a valid `spacetimedb_schema::identifier::Identifier`.
166 pub name: RawIdentifier,
167
168 /// A reference to a `ProductType` containing the columns of this table.
169 /// This is the single source of truth for the table's columns.
170 /// All elements of the `ProductType` must have names.
171 ///
172 /// Like all types in the module, this must have the [default element ordering](crate::db::default_element_ordering),
173 /// UNLESS a custom ordering is declared via a `RawTypeDefv9` for this type.
174 pub product_type_ref: AlgebraicTypeRef,
175
176 /// The primary key of the table, if present. Must refer to a valid column.
177 ///
178 /// Currently, there must be a unique constraint and an index corresponding to the primary key.
179 /// Eventually, we may remove the requirement for an index.
180 ///
181 /// The database engine does not actually care about this, but client code generation does.
182 ///
183 /// A list of length 0 means no primary key. Currently, a list of length >1 is not supported.
184 pub primary_key: ColList,
185
186 /// The indices of the table.
187 pub indexes: Vec<RawIndexDefV9>,
188
189 /// Any unique constraints on the table.
190 pub constraints: Vec<RawConstraintDefV9>,
191
192 /// The sequences for the table.
193 pub sequences: Vec<RawSequenceDefV9>,
194
195 /// The schedule for the table.
196 pub schedule: Option<RawScheduleDefV9>,
197
198 /// Whether this is a system- or user-created table.
199 pub table_type: TableType,
200
201 /// Whether this table is public or private.
202 pub table_access: TableAccess,
203}
204
205/// Whether the table was created by the system or the user.
206#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, SpacetimeType)]
207#[sats(crate = crate)]
208pub enum TableType {
209 /// Created by the system.
210 System,
211 /// Created by the user.
212 User,
213}
214impl From<StTableType> for TableType {
215 fn from(t: StTableType) -> Self {
216 match t {
217 StTableType::System => TableType::System,
218 StTableType::User => TableType::User,
219 }
220 }
221}
222impl From<TableType> for StTableType {
223 fn from(t: TableType) -> Self {
224 match t {
225 TableType::System => StTableType::System,
226 TableType::User => StTableType::User,
227 }
228 }
229}
230
231/// The visibility of the table.
232#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, SpacetimeType)]
233#[sats(crate = crate)]
234pub enum TableAccess {
235 /// Visible to all
236 Public,
237 /// Visible only to the owner
238 Private,
239}
240impl From<StAccess> for TableAccess {
241 fn from(t: StAccess) -> Self {
242 match t {
243 StAccess::Public => TableAccess::Public,
244 StAccess::Private => TableAccess::Private,
245 }
246 }
247}
248impl From<TableAccess> for StAccess {
249 fn from(t: TableAccess) -> Self {
250 match t {
251 TableAccess::Public => StAccess::Public,
252 TableAccess::Private => StAccess::Private,
253 }
254 }
255}
256
257/// A sequence definition for a database table column.
258#[derive(Debug, Clone, SpacetimeType)]
259#[sats(crate = crate)]
260#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
261pub struct RawSequenceDefV9 {
262 /// In the future, the user may FOR SOME REASON want to override this.
263 /// Even though there is ABSOLUTELY NO REASON TO.
264 /// If `None`, a nicely-formatted unique default will be chosen.
265 pub name: Option<Box<str>>,
266
267 /// The position of the column associated with this sequence.
268 /// This refers to a column in the same `RawTableDef` that contains this `RawSequenceDef`.
269 /// The column must have integral type.
270 /// This must be the unique `RawSequenceDef` for this column.
271 pub column: ColId,
272
273 /// The value to start assigning to this column.
274 /// Will be incremented by 1 for each new row.
275 /// If not present, an arbitrary start point may be selected.
276 pub start: Option<i128>,
277
278 /// The minimum allowed value in this column.
279 /// If not present, no minimum.
280 pub min_value: Option<i128>,
281
282 /// The maximum allowed value in this column.
283 /// If not present, no maximum.
284 pub max_value: Option<i128>,
285
286 /// The increment used when updating the SequenceDef.
287 pub increment: i128,
288}
289
290/// The definition of a database index.
291#[derive(Debug, Clone, SpacetimeType)]
292#[sats(crate = crate)]
293#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
294pub struct RawIndexDefV9 {
295 /// In the future, the user may FOR SOME REASON want to override this.
296 /// Even though there is ABSOLUTELY NO REASON TO.
297 pub name: Option<Box<str>>,
298
299 /// Accessor name for the index used in client codegen.
300 ///
301 /// This is set the user and should not be assumed to follow
302 /// any particular format.
303 ///
304 /// May be set to `None` if this is an auto-generated index for which the user
305 /// has not supplied a name. In this case, no client code generation for this index
306 /// will be performed.
307 ///
308 /// This name is not visible in the system tables, it is only used for client codegen.
309 pub accessor_name: Option<RawIdentifier>,
310
311 /// The algorithm parameters for the index.
312 pub algorithm: RawIndexAlgorithm,
313}
314
315/// Data specifying an index algorithm.
316/// New fields MUST be added to the END of this enum, to maintain ABI compatibility.
317#[non_exhaustive]
318#[derive(Debug, Clone, SpacetimeType)]
319#[sats(crate = crate)]
320#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
321pub enum RawIndexAlgorithm {
322 /// Implemented using a B-Tree.
323 ///
324 /// Currently, this uses a rust `std::collections::BTreeMap`.
325 BTree {
326 /// The columns to index on. These are ordered.
327 columns: ColList,
328 },
329 /// Implemented using a hashmap.
330 Hash {
331 /// The columns to index on. These are ordered.
332 columns: ColList,
333 },
334 /// Implemented using direct indexing in list(s) of `RowPointer`s.
335 /// The column this is placed on must also have a unique constraint.
336 Direct {
337 /// The column to index on.
338 /// Only one is allowed, as direct indexing with more is nonsensical.
339 column: ColId,
340 },
341}
342
343/// Returns a btree index algorithm for the columns `cols`.
344pub fn btree(cols: impl Into<ColList>) -> RawIndexAlgorithm {
345 RawIndexAlgorithm::BTree { columns: cols.into() }
346}
347
348/// Returns a hash index algorithm for the columns `cols`.
349pub fn hash(cols: impl Into<ColList>) -> RawIndexAlgorithm {
350 RawIndexAlgorithm::Hash { columns: cols.into() }
351}
352
353/// Returns a direct index algorithm for the column `col`.
354pub fn direct(col: impl Into<ColId>) -> RawIndexAlgorithm {
355 RawIndexAlgorithm::Direct { column: col.into() }
356}
357
358/// Marks a table as a timer table for a scheduled reducer or procedure.
359///
360/// The table must have columns:
361/// - `scheduled_id` of type `u64`.
362/// - `scheduled_at` of type `ScheduleAt`.
363#[derive(Debug, Clone, SpacetimeType)]
364#[sats(crate = crate)]
365#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
366pub struct RawScheduleDefV9 {
367 /// In the future, the user may FOR SOME REASON want to override this.
368 /// Even though there is ABSOLUTELY NO REASON TO.
369 pub name: Option<Box<str>>,
370
371 /// The name of the reducer or procedure to call.
372 ///
373 /// Despite the field name here, this may be either a reducer or a procedure.
374 pub reducer_name: RawIdentifier,
375
376 /// The column of the `scheduled_at` field of this scheduled table.
377 pub scheduled_at_column: ColId,
378}
379
380/// A constraint definition attached to a table.
381#[derive(Debug, Clone, SpacetimeType)]
382#[sats(crate = crate)]
383#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
384pub struct RawConstraintDefV9 {
385 /// In the future, the user may FOR SOME REASON want to override this.
386 /// Even though there is ABSOLUTELY NO REASON TO.
387 pub name: Option<Box<str>>,
388
389 /// The data for the constraint.
390 pub data: RawConstraintDataV9,
391}
392
393/// Raw data attached to a constraint.
394/// New fields MUST be added to the END of this enum, to maintain ABI compatibility.
395#[derive(Debug, Clone, SpacetimeType)]
396#[sats(crate = crate)]
397#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
398#[non_exhaustive]
399pub enum RawConstraintDataV9 {
400 Unique(RawUniqueConstraintDataV9),
401}
402
403/// Requires that the projection of the table onto these `columns` is a bijection.
404///
405/// That is, there must be a one-to-one relationship between a row and the `columns` of that row.
406#[derive(Debug, Clone, SpacetimeType)]
407#[sats(crate = crate)]
408#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
409pub struct RawUniqueConstraintDataV9 {
410 /// The columns that must be unique.
411 pub columns: ColList,
412}
413
414/// Data for the `RLS` policy on a table.
415#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)]
416#[sats(crate = crate)]
417#[cfg_attr(feature = "test", derive(PartialOrd, Ord))]
418pub struct RawRowLevelSecurityDefV9 {
419 /// The `sql` expression to use for row-level security.
420 pub sql: RawSql,
421}
422
423/// A miscellaneous module export.
424///
425/// All of the variants here were added after the format of [`RawModuleDefV9`] was already stabilized.
426/// If/when we define `RawModuleDefV10`, these should allbe moved out of `misc_exports` and into their own fields.
427#[derive(Debug, Clone, SpacetimeType)]
428#[sats(crate = crate)]
429#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord, derive_more::From))]
430#[non_exhaustive]
431pub enum RawMiscModuleExportV9 {
432 /// A default value for a column added during a supervised automigration.
433 ColumnDefaultValue(RawColumnDefaultValueV9),
434 /// A procedure definition.
435 Procedure(RawProcedureDefV9),
436 /// A view definition.
437 View(RawViewDefV9),
438}
439
440/// Marks a particular table's column as having a particular default.
441#[derive(Debug, Clone, SpacetimeType)]
442#[sats(crate = crate)]
443#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
444pub struct RawColumnDefaultValueV9 {
445 /// Identifies which table that has the default value.
446 /// This corresponds to `name` in `RawTableDefV9`.
447 pub table: RawIdentifier,
448 /// Identifies which column of `table` that has the default value.
449 pub col_id: ColId,
450 /// A BSATN-encoded [`AlgebraicValue`] valid at the table column's type.
451 /// (We cannot use `AlgebraicValue` directly as it isn't `Spacetimetype`.)
452 pub value: Box<[u8]>,
453}
454
455/// A type declaration.
456///
457/// Exactly of these must be attached to every `Product` and `Sum` type used by a module.
458#[derive(Debug, Clone, SpacetimeType)]
459#[sats(crate = crate)]
460#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
461pub struct RawTypeDefV9 {
462 /// The name of the type declaration.
463 pub name: RawScopedTypeNameV9,
464
465 /// The type to which the declaration refers.
466 /// This must point to an `AlgebraicType::Product` or an `AlgebraicType::Sum` in the module's typespace.
467 pub ty: AlgebraicTypeRef,
468
469 /// Whether this type has a custom ordering.
470 pub custom_ordering: bool,
471}
472
473/// A scoped type name, in the form `scope0::scope1::...::scopeN::name`.
474///
475/// These are the names that will be used *in client code generation*, NOT the names used for types
476/// in the module source code.
477#[derive(Clone, SpacetimeType, PartialEq, Eq, PartialOrd, Ord)]
478#[sats(crate = crate)]
479pub struct RawScopedTypeNameV9 {
480 /// The scope for this type.
481 ///
482 /// Empty unless a sats `name` attribute is used, e.g.
483 /// `#[sats(name = "namespace.name")]` in Rust.
484 pub scope: Box<[RawIdentifier]>,
485
486 /// The name of the type. This must be unique within the module.
487 ///
488 /// Eventually, we may add more information to this, such as generic arguments.
489 pub name: RawIdentifier,
490}
491
492impl fmt::Debug for RawScopedTypeNameV9 {
493 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
494 for module in self.scope.iter() {
495 fmt::Debug::fmt(module, f)?;
496 f.write_str("::")?;
497 }
498 fmt::Debug::fmt(&self.name, f)?;
499 Ok(())
500 }
501}
502
503/// A view definition.
504#[derive(Debug, Clone, SpacetimeType)]
505#[sats(crate = crate)]
506#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
507pub struct RawViewDefV9 {
508 /// The name of the view function as defined in the module
509 pub name: RawIdentifier,
510
511 /// The index of the view in the module's list of views.
512 pub index: u32,
513
514 /// Is this a public or a private view?
515 /// Currently only public views are supported.
516 /// Private views may be supported in the future.
517 pub is_public: bool,
518
519 /// Is this view anonymous?
520 /// An anonymous view does not know who called it.
521 /// Specifically, it is a view that has an `AnonymousViewContext` as its first argument.
522 /// This type does not have access to the `Identity` of the caller.
523 pub is_anonymous: bool,
524
525 /// The types and optional names of the parameters, in order.
526 /// This `ProductType` need not be registered in the typespace.
527 pub params: ProductType,
528
529 /// The return type of the view.
530 /// Either `T`, `Option<T>`, or `Vec<T>` where `T` is a `SpacetimeType`.
531 ///
532 /// More strictly `T` must be a SATS `ProductType`,
533 /// however this will be validated by the server on publish.
534 ///
535 /// This is the single source of truth for the views's columns.
536 /// All elements of the inner `ProductType` must have names.
537 /// This again will be validated by the server on publish.
538 pub return_type: AlgebraicType,
539}
540
541#[derive(Debug, Clone, SpacetimeType)]
542#[sats(crate = crate)]
543pub enum ViewResultHeader {
544 // This means the row data will follow, as a bsatn-encoded Vec<RowType>.
545 // We could make RowData contain an Vec<u8> of the bytes, but that forces us to make an extra copy when we serialize and
546 // when we deserialize.
547 RowData,
548 // This means we the view wants to return the results of the sql query.
549 RawSql(String),
550 // We can add an option for parameterized queries later,
551 // which would make it easier to cache query plans on the host side.
552}
553
554/// A reducer definition.
555#[derive(Debug, Clone, SpacetimeType)]
556#[sats(crate = crate)]
557#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
558pub struct RawReducerDefV9 {
559 /// The name of the reducer.
560 pub name: RawIdentifier,
561
562 /// The types and optional names of the parameters, in order.
563 /// This `ProductType` need not be registered in the typespace.
564 pub params: ProductType,
565
566 /// If the reducer has a special role in the module lifecycle, it should be marked here.
567 pub lifecycle: Option<Lifecycle>,
568}
569
570/// Special roles a reducer can play in the module lifecycle.
571#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SpacetimeType)]
572#[cfg_attr(feature = "enum-map", derive(enum_map::Enum))]
573#[sats(crate = crate)]
574#[non_exhaustive]
575pub enum Lifecycle {
576 /// The reducer will be invoked upon module initialization.
577 Init,
578 /// The reducer will be invoked when a client connects.
579 OnConnect,
580 /// The reducer will be invoked when a client disconnects.
581 OnDisconnect,
582}
583
584/// A procedure definition.
585///
586/// Will be wrapped in [`RawMiscModuleExportV9`] and included in the [`RawModuleDefV9`]'s `misc_exports` vec.
587#[derive(Debug, Clone, SpacetimeType)]
588#[sats(crate = crate)]
589#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
590pub struct RawProcedureDefV9 {
591 /// The name of the procedure.
592 pub name: RawIdentifier,
593
594 /// The types and optional names of the parameters, in order.
595 /// This `ProductType` need not be registered in the typespace.
596 pub params: ProductType,
597
598 /// The type of the return value.
599 ///
600 /// If this is a user-defined product or sum type,
601 /// it should be registered in the typespace and indirected through an [`AlgebraicType::Ref`].
602 pub return_type: AlgebraicType,
603}
604
605/// A builder for a [`RawModuleDefV9`].
606#[derive(Default)]
607pub struct RawModuleDefV9Builder {
608 /// The module definition.
609 module: RawModuleDefV9,
610 /// The type map from `T: 'static` Rust types to sats types.
611 type_map: BTreeMap<TypeId, AlgebraicTypeRef>,
612}
613
614impl RawModuleDefV9Builder {
615 /// Create a new, empty `RawModuleDefBuilder`.
616 pub fn new() -> Self {
617 Default::default()
618 }
619
620 /// Add a type to the in-progress module.
621 ///
622 /// The returned type must satisfy `AlgebraicType::is_valid_for_client_type_definition` or `AlgebraicType::is_valid_for_client_type_use` .
623 pub fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType {
624 TypespaceBuilder::add_type::<T>(self)
625 }
626
627 /// Create a table builder.
628 ///
629 /// Does not validate that the product_type_ref is valid; this is left to the module validation code.
630 pub fn build_table(
631 &mut self,
632 name: impl Into<RawIdentifier>,
633 product_type_ref: AlgebraicTypeRef,
634 ) -> RawTableDefBuilder<'_> {
635 let name = name.into();
636 RawTableDefBuilder {
637 module_def: &mut self.module,
638 table: RawTableDefV9 {
639 name,
640 product_type_ref,
641 indexes: vec![],
642 constraints: vec![],
643 sequences: vec![],
644 schedule: None,
645 primary_key: ColList::empty(),
646 table_type: TableType::User,
647 table_access: TableAccess::Public,
648 },
649 }
650 }
651
652 /// Build a new table with a product type.
653 /// Adds the type to the module.
654 pub fn build_table_with_new_type(
655 &mut self,
656 table_name: impl Into<RawIdentifier>,
657 product_type: impl Into<spacetimedb_sats::ProductType>,
658 custom_ordering: bool,
659 ) -> RawTableDefBuilder<'_> {
660 let table_name = table_name.into();
661
662 let product_type_ref = self.add_algebraic_type(
663 [],
664 table_name.clone(),
665 AlgebraicType::from(product_type.into()),
666 custom_ordering,
667 );
668
669 self.build_table(table_name, product_type_ref)
670 }
671
672 /// Build a new table with a product type, for testing.
673 /// Adds the type to the module.
674 pub fn build_table_with_new_type_for_tests(
675 &mut self,
676 table_name: impl Into<RawIdentifier>,
677 mut product_type: spacetimedb_sats::ProductType,
678 custom_ordering: bool,
679 ) -> RawTableDefBuilder<'_> {
680 self.add_expand_product_type_for_tests(&mut 0, &mut product_type);
681
682 self.build_table_with_new_type(table_name, product_type, custom_ordering)
683 }
684
685 fn add_expand_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut AlgebraicType) {
686 if ty.is_valid_for_client_type_use() {
687 return;
688 }
689
690 match ty {
691 AlgebraicType::Product(prod_ty) => self.add_expand_product_type_for_tests(name_gen, prod_ty),
692 AlgebraicType::Sum(sum_type) => {
693 if let Some(wrapped) = sum_type.as_option_mut() {
694 self.add_expand_type_for_tests(name_gen, wrapped);
695 } else {
696 for elem in sum_type.variants.iter_mut() {
697 self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
698 }
699 }
700 }
701 AlgebraicType::Array(ty) => {
702 self.add_expand_type_for_tests(name_gen, &mut ty.elem_ty);
703 return;
704 }
705 _ => return,
706 }
707
708 // Make the type into a ref.
709 let name = *name_gen;
710 let add_ty = core::mem::replace(ty, AlgebraicType::U8);
711 *ty = AlgebraicType::Ref(self.add_algebraic_type([], format!("gen_{name}"), add_ty, true));
712 *name_gen += 1;
713 }
714
715 fn add_expand_product_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut ProductType) {
716 for elem in ty.elements.iter_mut() {
717 self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
718 }
719 }
720
721 /// Add a type to the typespace, along with a type alias declaring its name.
722 /// This method should only be use for `AlgebraicType`s not corresponding to a Rust
723 /// type that implements `SpacetimeType`.
724 ///
725 /// Returns a reference to the newly-added type.
726 ///
727 /// NOT idempotent, calling this twice with the same name will cause errors during
728 /// validation.
729 ///
730 /// You must set `custom_ordering` if you're not using the default element ordering.
731 pub fn add_algebraic_type(
732 &mut self,
733 scope: impl IntoIterator<Item = RawIdentifier>,
734 name: impl Into<RawIdentifier>,
735 ty: spacetimedb_sats::AlgebraicType,
736 custom_ordering: bool,
737 ) -> AlgebraicTypeRef {
738 let ty = self.module.typespace.add(ty);
739 let scope = scope.into_iter().collect();
740 let name = name.into();
741 self.module.types.push(RawTypeDefV9 {
742 name: RawScopedTypeNameV9 { name, scope },
743 ty,
744 custom_ordering,
745 });
746 // 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.
747 ty
748 }
749
750 /// Add a reducer to the in-progress module.
751 /// Accepts a `ProductType` of reducer arguments for convenience.
752 /// The `ProductType` need not be registered in the typespace.
753 ///
754 /// Importantly, if the reducer's first argument is a `ReducerContext`, that
755 /// information should not be provided to this method.
756 /// That is an implementation detail handled by the module bindings and can be ignored.
757 /// As far as the module definition is concerned, the reducer's arguments
758 /// start with the first non-`ReducerContext` argument.
759 ///
760 /// (It is impossible, with the current implementation of `ReducerContext`, to
761 /// have more than one `ReducerContext` argument, at least in Rust.
762 /// This is because `SpacetimeType` is not implemented for `ReducerContext`,
763 /// so it can never act like an ordinary argument.)
764 pub fn add_reducer(
765 &mut self,
766 name: impl Into<RawIdentifier>,
767 params: spacetimedb_sats::ProductType,
768 lifecycle: Option<Lifecycle>,
769 ) {
770 self.module.reducers.push(RawReducerDefV9 {
771 name: name.into(),
772 params,
773 lifecycle,
774 });
775 }
776
777 /// Add a procedure to the in-progress module.
778 ///
779 /// Accepts a `ProductType` of arguments.
780 /// The arguments `ProductType` need not be registered in the typespace.
781 ///
782 /// Also accepts an `AlgebraicType` return type.
783 /// If this is a user-defined product or sum type,
784 /// it should be registered in the typespace and indirected through an `AlgebraicType::Ref`.
785 ///
786 /// The `&mut ProcedureContext` first argument to the procedure should not be included in the `params`.
787 pub fn add_procedure(
788 &mut self,
789 name: impl Into<RawIdentifier>,
790 params: spacetimedb_sats::ProductType,
791 return_type: spacetimedb_sats::AlgebraicType,
792 ) {
793 self.module
794 .misc_exports
795 .push(RawMiscModuleExportV9::Procedure(RawProcedureDefV9 {
796 name: name.into(),
797 params,
798 return_type,
799 }))
800 }
801
802 pub fn add_view(
803 &mut self,
804 name: impl Into<RawIdentifier>,
805 index: usize,
806 is_public: bool,
807 is_anonymous: bool,
808 params: ProductType,
809 return_type: AlgebraicType,
810 ) {
811 self.module.misc_exports.push(RawMiscModuleExportV9::View(RawViewDefV9 {
812 name: name.into(),
813 index: index as u32,
814 is_public,
815 is_anonymous,
816 params,
817 return_type,
818 }));
819 }
820
821 /// Add a row-level security policy to the module.
822 ///
823 /// The `sql` expression should be a valid SQL expression that will be used to filter rows.
824 ///
825 /// **NOTE**: The `sql` expression must be unique within the module.
826 pub fn add_row_level_security(&mut self, sql: &str) {
827 self.module
828 .row_level_security
829 .push(RawRowLevelSecurityDefV9 { sql: sql.into() });
830 }
831
832 /// Get the typespace of the module.
833 pub fn typespace(&self) -> &Typespace {
834 &self.module.typespace
835 }
836
837 /// Finish building, consuming the builder and returning the module.
838 /// The module should be validated before use.
839 pub fn finish(self) -> RawModuleDefV9 {
840 self.module
841 }
842}
843
844/// Convert a string from a sats type-name annotation like `#[sats(name = "namespace.name")]` to a `RawScopedTypeNameV9`.
845/// We split the input on the strings `"::"` and `"."` to split up module paths.
846///
847/// TODO(1.0): build namespacing directly into the bindings macros so that we don't need to do this.
848pub fn sats_name_to_scoped_name(sats_name: &str) -> RawScopedTypeNameV9 {
849 // We can't use `&[char]: Pattern` for `split` here because "::" is not a char :/
850 let mut scope: Vec<RawIdentifier> = sats_name.split("::").flat_map(|s| s.split('.')).map_into().collect();
851 // Unwrapping to "" will result in a validation error down the line, which is exactly what we want.
852 let name = scope.pop().unwrap_or_default();
853 RawScopedTypeNameV9 {
854 scope: scope.into(),
855 name,
856 }
857}
858
859impl TypespaceBuilder for RawModuleDefV9Builder {
860 fn add(
861 &mut self,
862 typeid: TypeId,
863 name: Option<&'static str>,
864 make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
865 ) -> AlgebraicType {
866 let r = match self.type_map.entry(typeid) {
867 btree_map::Entry::Occupied(o) => *o.get(),
868 btree_map::Entry::Vacant(v) => {
869 // Bind a fresh alias to the unit type.
870 let slot_ref = self.module.typespace.add(AlgebraicType::unit());
871 // Relate `typeid -> fresh alias`.
872 v.insert(slot_ref);
873
874 // Alias provided? Relate `name -> slot_ref`.
875 if let Some(sats_name) = name {
876 let name = sats_name_to_scoped_name(sats_name);
877
878 self.module.types.push(RawTypeDefV9 {
879 name,
880 ty: slot_ref,
881 // TODO(1.0): we need to update the `TypespaceBuilder` trait to include
882 // a `custom_ordering` parameter.
883 // For now, we assume all types have custom orderings, since the derive
884 // macro doesn't know about the default ordering yet.
885 custom_ordering: true,
886 });
887 }
888
889 // Borrow of `v` has ended here, so we can now convince the borrow checker.
890 let ty = make_ty(self);
891 self.module.typespace[slot_ref] = ty;
892 slot_ref
893 }
894 };
895 AlgebraicType::Ref(r)
896 }
897}
898
899/// Builder for a `RawTableDef`.
900pub struct RawTableDefBuilder<'a> {
901 module_def: &'a mut RawModuleDefV9,
902 table: RawTableDefV9,
903}
904
905impl RawTableDefBuilder<'_> {
906 /// Sets the type of the table and return it.
907 ///
908 /// This is not about column algebraic types, but about whether the table
909 /// was created by the system or the user.
910 pub fn with_type(mut self, table_type: TableType) -> Self {
911 self.table.table_type = table_type;
912 self
913 }
914
915 /// Sets the access rights for the table and return it.
916 pub fn with_access(mut self, table_access: TableAccess) -> Self {
917 self.table.table_access = table_access;
918 self
919 }
920
921 /// Generates a [RawConstraintDefV9] using the supplied `columns`.
922 pub fn with_unique_constraint(mut self, columns: impl Into<ColList>) -> Self {
923 let columns = columns.into();
924 self.table.constraints.push(RawConstraintDefV9 {
925 name: None,
926 data: RawConstraintDataV9::Unique(RawUniqueConstraintDataV9 { columns }),
927 });
928 self
929 }
930
931 /// Adds a primary key to the table.
932 /// You must also add a unique constraint on the primary key column.
933 pub fn with_primary_key(mut self, column: impl Into<ColId>) -> Self {
934 self.table.primary_key = ColList::new(column.into());
935 self
936 }
937
938 /// Adds a primary key to the table, with corresponding unique constraint and sequence definitions.
939 /// You will also need to call [`Self::with_index`] to create an index on `column`.
940 pub fn with_auto_inc_primary_key(self, column: impl Into<ColId>) -> Self {
941 let column = column.into();
942 self.with_primary_key(column)
943 .with_unique_constraint(column)
944 .with_column_sequence(column)
945 }
946
947 /// Generates a [RawIndexDefV9] using the supplied `columns`.
948 pub fn with_index(mut self, algorithm: RawIndexAlgorithm, accessor_name: impl Into<RawIdentifier>) -> Self {
949 let accessor_name = accessor_name.into();
950
951 self.table.indexes.push(RawIndexDefV9 {
952 name: None,
953 accessor_name: Some(accessor_name),
954 algorithm,
955 });
956 self
957 }
958
959 /// Generates a [RawIndexDefV9] using the supplied `columns` but with no `accessor_name`.
960 pub fn with_index_no_accessor_name(mut self, algorithm: RawIndexAlgorithm) -> Self {
961 self.table.indexes.push(RawIndexDefV9 {
962 name: None,
963 accessor_name: None,
964 algorithm,
965 });
966 self
967 }
968
969 /// Adds a [RawSequenceDefV9] on the supplied `column`.
970 pub fn with_column_sequence(mut self, column: impl Into<ColId>) -> Self {
971 let column = column.into();
972 self.table.sequences.push(RawSequenceDefV9 {
973 name: None,
974 column,
975 start: None,
976 min_value: None,
977 max_value: None,
978 increment: 1,
979 });
980
981 self
982 }
983
984 /// Adds a schedule definition to the table.
985 ///
986 /// The `function_name` should name a reducer or procedure
987 /// which accepts one argument, a row of this table.
988 ///
989 /// The table must have the appropriate columns for a scheduled table.
990 pub fn with_schedule(
991 mut self,
992 function_name: impl Into<RawIdentifier>,
993 scheduled_at_column: impl Into<ColId>,
994 ) -> Self {
995 let reducer_name = function_name.into();
996 let scheduled_at_column = scheduled_at_column.into();
997 self.table.schedule = Some(RawScheduleDefV9 {
998 name: None,
999 reducer_name,
1000 scheduled_at_column,
1001 });
1002 self
1003 }
1004
1005 /// Adds a default value for the `column`.
1006 pub fn with_default_column_value(self, column: impl Into<ColId>, value: AlgebraicValue) -> Self {
1007 // Added to `misc_exports` for backwards-compatibility reasons.
1008 self.module_def
1009 .misc_exports
1010 .push(RawMiscModuleExportV9::ColumnDefaultValue(RawColumnDefaultValueV9 {
1011 table: self.table.name.clone(),
1012 col_id: column.into(),
1013 value: spacetimedb_sats::bsatn::to_vec(&value).unwrap().into(),
1014 }));
1015 self
1016 }
1017
1018 /// Build the table and add it to the module, returning the `product_type_ref` of the table.
1019 pub fn finish(self) -> AlgebraicTypeRef {
1020 self.table.product_type_ref
1021 // self is now dropped.
1022 }
1023
1024 /// Get the column ID of the column with the specified name, if any.
1025 ///
1026 /// Returns `None` if this `TableDef` has been constructed with an invalid `ProductTypeRef`,
1027 /// or if no column exists with that name.
1028 pub fn find_col_pos_by_name(&self, column: impl AsRef<str>) -> Option<ColId> {
1029 let column = column.as_ref();
1030 self.columns()?
1031 .iter()
1032 .position(|x| x.name().is_some_and(|s| s == column))
1033 .map(|x| x.into())
1034 }
1035
1036 /// Get the columns of this type.
1037 ///
1038 /// Returns `None` if this `TableDef` has been constructed with an invalid `ProductTypeRef`.
1039 fn columns(&self) -> Option<&[ProductTypeElement]> {
1040 self.module_def
1041 .typespace
1042 .get(self.table.product_type_ref)
1043 .and_then(|ty| ty.as_product())
1044 .map(|p| &p.elements[..])
1045 }
1046}
1047
1048impl Drop for RawTableDefBuilder<'_> {
1049 fn drop(&mut self) {
1050 self.module_def.tables.push(self.table.clone());
1051 }
1052}
1053
1054impl From<RawTypeDefV10> for RawTypeDefV9 {
1055 fn from(raw: RawTypeDefV10) -> Self {
1056 RawTypeDefV9 {
1057 name: raw.source_name.into(),
1058 ty: raw.ty,
1059 custom_ordering: raw.custom_ordering,
1060 }
1061 }
1062}
1063
1064impl From<RawScopedTypeNameV10> for RawScopedTypeNameV9 {
1065 fn from(raw: RawScopedTypeNameV10) -> Self {
1066 RawScopedTypeNameV9 {
1067 scope: raw.scope,
1068 name: raw.source_name,
1069 }
1070 }
1071}
1072
1073impl From<RawIndexDefV10> for RawIndexDefV9 {
1074 fn from(raw: RawIndexDefV10) -> Self {
1075 RawIndexDefV9 {
1076 name: raw.source_name,
1077 accessor_name: raw.accessor_name,
1078 algorithm: raw.algorithm,
1079 }
1080 }
1081}
1082
1083impl From<RawConstraintDefV10> for RawConstraintDefV9 {
1084 fn from(raw: RawConstraintDefV10) -> Self {
1085 RawConstraintDefV9 {
1086 name: raw.source_name,
1087 data: raw.data,
1088 }
1089 }
1090}
1091
1092impl From<RawSequenceDefV10> for RawSequenceDefV9 {
1093 fn from(raw: RawSequenceDefV10) -> Self {
1094 RawSequenceDefV9 {
1095 name: raw.source_name,
1096 column: raw.column,
1097 start: raw.start,
1098 min_value: raw.min_value,
1099 max_value: raw.max_value,
1100 increment: raw.increment,
1101 }
1102 }
1103}