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