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