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