spacetimedb/db/datastore/
system_tables.rs

1//! Schema definitions and accesses to the system tables,
2//! which store metadata about a SpacetimeDB database.
3//!
4//! When defining a new system table, remember to:
5//! - Define constants for its ID and name.
6//! - Name it in singular (`st_column` not `st_columns`).
7//! - Add a type `St(...)Row` to define its schema, deriving SpacetimeType.
8//!     - You will probably need to add a new ID type in `spacetimedb_primitives`,
9//!       with trait implementations in `spacetimedb_sats::{typespace, de::impl, ser::impl}`.
10//! - Add it to [`system_tables`], and define a constant for its index there.
11//! - Use [`st_fields_enum`] to define its column enum.
12//! - Register its schema in [`system_module_def`], making sure to call `validate_system_table` at the end of the function.
13
14use spacetimedb_lib::db::auth::{StAccess, StTableType};
15use spacetimedb_lib::db::raw_def::v9::{btree, RawSql};
16use spacetimedb_lib::db::raw_def::*;
17use spacetimedb_lib::de::{Deserialize, DeserializeOwned, Error};
18use spacetimedb_lib::ser::Serialize;
19use spacetimedb_lib::st_var::StVarValue;
20use spacetimedb_lib::{ConnectionId, Identity, ProductValue, SpacetimeType};
21use spacetimedb_primitives::*;
22use spacetimedb_sats::algebraic_value::ser::value_serialize;
23use spacetimedb_sats::hash::Hash;
24use spacetimedb_sats::product_value::InvalidFieldError;
25use spacetimedb_sats::{impl_deserialize, impl_serialize, impl_st, u256, AlgebraicType, AlgebraicValue, ArrayValue};
26use spacetimedb_schema::def::{
27    BTreeAlgorithm, ConstraintData, DirectAlgorithm, IndexAlgorithm, ModuleDef, UniqueConstraintData,
28};
29use spacetimedb_schema::schema::{
30    ColumnSchema, ConstraintSchema, IndexSchema, RowLevelSecuritySchema, ScheduleSchema, Schema, SequenceSchema,
31    TableSchema,
32};
33use spacetimedb_table::table::RowRef;
34use std::cell::RefCell;
35use std::str::FromStr;
36use strum::Display;
37use v9::{RawModuleDefV9Builder, TableType};
38
39use super::error::DatastoreError;
40
41/// The static ID of the table that defines tables
42pub(crate) const ST_TABLE_ID: TableId = TableId(1);
43/// The static ID of the table that defines columns
44pub(crate) const ST_COLUMN_ID: TableId = TableId(2);
45/// The static ID of the table that defines sequences
46pub(crate) const ST_SEQUENCE_ID: TableId = TableId(3);
47/// The static ID of the table that defines indexes
48pub(crate) const ST_INDEX_ID: TableId = TableId(4);
49/// The static ID of the table that defines constraints
50pub(crate) const ST_CONSTRAINT_ID: TableId = TableId(5);
51/// The static ID of the table that defines the stdb module associated with
52/// the database
53pub(crate) const ST_MODULE_ID: TableId = TableId(6);
54/// The static ID of the table that defines connected clients
55pub(crate) const ST_CLIENT_ID: TableId = TableId(7);
56/// The static ID of the table that defines system variables
57pub(crate) const ST_VAR_ID: TableId = TableId(8);
58/// The static ID of the table that defines scheduled tables
59pub(crate) const ST_SCHEDULED_ID: TableId = TableId(9);
60
61/// The static ID of the table that defines the row level security (RLS) policies
62pub(crate) const ST_ROW_LEVEL_SECURITY_ID: TableId = TableId(10);
63pub(crate) const ST_TABLE_NAME: &str = "st_table";
64pub(crate) const ST_COLUMN_NAME: &str = "st_column";
65pub(crate) const ST_SEQUENCE_NAME: &str = "st_sequence";
66pub(crate) const ST_INDEX_NAME: &str = "st_index";
67pub(crate) const ST_CONSTRAINT_NAME: &str = "st_constraint";
68pub(crate) const ST_MODULE_NAME: &str = "st_module";
69pub(crate) const ST_CLIENT_NAME: &str = "st_client";
70pub(crate) const ST_SCHEDULED_NAME: &str = "st_scheduled";
71pub(crate) const ST_VAR_NAME: &str = "st_var";
72pub(crate) const ST_ROW_LEVEL_SECURITY_NAME: &str = "st_row_level_security";
73/// Reserved range of sequence values used for system tables.
74///
75/// Ids for user-created tables will start at `ST_RESERVED_SEQUENCE_RANGE + 1`.
76///
77/// The range applies to all sequences allocated by system tables, i.e. table-,
78/// sequence-, index-, and constraint-ids.
79/// > Note that column-ids are positional indices and not based on a sequence.
80///
81/// These ids can be referred to statically even for system tables introduced
82/// after a database was created, so as long as the range is not exceeded.
83///
84/// However unlikely it may seem, it is advisable to check for overflow in the
85/// test suite when adding sequences to system tables.
86pub(crate) const ST_RESERVED_SEQUENCE_RANGE: u32 = 4096;
87
88// This help to keep the correct order when bootstrapping
89#[allow(non_camel_case_types)]
90#[derive(Debug, Display)]
91pub enum SystemTable {
92    st_table,
93    st_column,
94    st_sequence,
95    st_index,
96    st_constraint,
97    st_row_level_security,
98}
99
100pub(crate) fn system_tables() -> [TableSchema; 10] {
101    [
102        // The order should match the `id` of the system table, that start with [ST_TABLE_IDX].
103        st_table_schema(),
104        st_column_schema(),
105        st_index_schema(),
106        st_constraint_schema(),
107        st_module_schema(),
108        st_client_schema(),
109        st_var_schema(),
110        st_scheduled_schema(),
111        st_row_level_security_schema(),
112        // Is important this is always last, so the starting sequence for each
113        // system table is correct.
114        st_sequence_schema(),
115    ]
116}
117
118/// Types that represent the fields / columns of a system table.
119pub trait StFields: Copy + Sized {
120    /// Returns the column position of the system table field.
121    fn col_id(self) -> ColId;
122
123    /// Returns the column index of the system table field.
124    #[inline]
125    fn col_idx(self) -> usize {
126        self.col_id().idx()
127    }
128
129    /// Returns the column name of the system table field a static string slice.
130    fn name(self) -> &'static str;
131
132    /// Returns the column name of the system table field as a boxed slice.
133    #[inline]
134    fn col_name(self) -> Box<str> {
135        self.name().into()
136    }
137
138    /// Return all fields of this type, in order.
139    fn fields() -> &'static [Self];
140}
141
142// The following are indices into the array returned by [`system_tables`].
143pub(crate) const ST_TABLE_IDX: usize = 0;
144pub(crate) const ST_COLUMN_IDX: usize = 1;
145pub(crate) const ST_INDEX_IDX: usize = 2;
146pub(crate) const ST_CONSTRAINT_IDX: usize = 3;
147pub(crate) const ST_MODULE_IDX: usize = 4;
148pub(crate) const ST_CLIENT_IDX: usize = 5;
149pub(crate) const ST_VAR_IDX: usize = 6;
150pub(crate) const ST_SCHEDULED_IDX: usize = 7;
151pub(crate) const ST_ROW_LEVEL_SECURITY_IDX: usize = 8;
152// Must be the last index in the array.
153pub(crate) const ST_SEQUENCE_IDX: usize = 9;
154
155macro_rules! st_fields_enum {
156    ($(#[$attr:meta])* enum $ty_name:ident { $($name:expr, $var:ident = $discr:expr,)* }) => {
157        #[derive(Copy, Clone, Debug)]
158        $(#[$attr])*
159        pub enum $ty_name {
160            $($var = $discr,)*
161        }
162
163        impl StFields for $ty_name {
164            #[inline]
165            fn col_id(self) -> ColId {
166                ColId(self as _)
167            }
168
169            #[inline]
170            fn name(self) -> &'static str {
171                match self {
172                    $(Self::$var => $name,)*
173                }
174            }
175
176            fn fields() -> &'static [$ty_name] {
177                &[$($ty_name::$var,)*]
178            }
179        }
180
181        impl From<$ty_name> for ColId {
182            fn from(value: $ty_name) -> Self {
183                value.col_id()
184            }
185        }
186    }
187}
188
189// WARNING: For a stable schema, don't change the field names and discriminants.
190st_fields_enum!(enum StTableFields {
191    "table_id", TableId = 0,
192    "table_name", TableName = 1,
193    "table_type", TableType = 2,
194    "table_access", TablesAccess = 3,
195    "table_primary_key", PrimaryKey = 4,
196});
197// WARNING: For a stable schema, don't change the field names and discriminants.
198st_fields_enum!(enum StColumnFields {
199    "table_id", TableId = 0,
200    "col_pos", ColPos = 1,
201    "col_name", ColName = 2,
202    "col_type", ColType = 3,
203});
204// WARNING: For a stable schema, don't change the field names and discriminants.
205st_fields_enum!(enum StIndexFields {
206    "index_id", IndexId = 0,
207    "table_id", TableId = 1,
208    "index_name", IndexName = 2,
209    "index_algorithm", IndexAlgorithm = 3,
210});
211// WARNING: For a stable schema, don't change the field names and discriminants.
212st_fields_enum!(
213    /// The fields that define the internal table [crate::db::relational_db::ST_SEQUENCES_NAME].
214    enum StSequenceFields {
215    "sequence_id", SequenceId = 0,
216    "sequence_name", SequenceName = 1,
217    "table_id", TableId = 2,
218    "col_pos", ColPos = 3,
219    "increment", Increment = 4,
220    "start", Start = 5,
221    "min_value", MinValue = 6,
222    "max_value", MaxValue = 7,
223    "allocated", Allocated = 8,
224});
225// WARNING: For a stable schema, don't change the field names and discriminants.
226st_fields_enum!(enum StConstraintFields {
227    "constraint_id", ConstraintId = 0,
228    "constraint_name", ConstraintName = 1,
229    "table_id", TableId = 2,
230    "constraint_data", ConstraintData = 3,
231});
232// WARNING: For a stable schema, don't change the field names and discriminants.
233st_fields_enum!(enum StRowLevelSecurityFields {
234    "table_id", TableId = 0,
235    "sql", Sql = 1,
236});
237// WARNING: For a stable schema, don't change the field names and discriminants.
238st_fields_enum!(enum StModuleFields {
239    "database_identity", DatabaseIdentity = 0,
240    "owner_identity", OwnerIdentity = 1,
241    "program_kind", ProgramKind = 2,
242    "program_hash", ProgramHash = 3,
243    "program_bytes", ProgramBytes = 4,
244    "module_version", ModuleVersion = 5,
245});
246// WARNING: For a stable schema, don't change the field names and discriminants.
247st_fields_enum!(enum StClientFields {
248    "identity", Identity = 0,
249    "connection_id", ConnectionId = 1,
250});
251// WARNING: For a stable schema, don't change the field names and discriminants.
252st_fields_enum!(enum StVarFields {
253    "name", Name = 0,
254    "value", Value = 1,
255});
256
257st_fields_enum!(enum StScheduledFields {
258    "schedule_id", ScheduleId = 0,
259    "table_id", TableId = 1,
260    "reducer_name", ReducerName = 2,
261    "schedule_name", ScheduleName = 3,
262    "at_column", AtColumn = 4,
263});
264
265/// Helper method to check that a system table has the correct fields.
266/// Does not check field types since those aren't included in `StFields` types.
267/// If anything in here is not true, the system is completely broken, so it's fine to assert.
268fn validate_system_table<T: StFields + 'static>(def: &ModuleDef, table_name: &str) {
269    let table = def.table(table_name).expect("missing system table definition");
270    let fields = T::fields();
271    assert_eq!(table.columns.len(), fields.len());
272    for field in T::fields() {
273        let col = table
274            .columns
275            .get(field.col_id().idx())
276            .expect("missing system table field");
277        assert_eq!(&col.name[..], field.name());
278    }
279}
280
281/// See the comment on [`SYSTEM_MODULE_DEF`].
282fn system_module_def() -> ModuleDef {
283    let mut builder = RawModuleDefV9Builder::new();
284
285    let st_table_type = builder.add_type::<StTableRow>();
286    builder
287        .build_table(ST_TABLE_NAME, *st_table_type.as_ref().expect("should be ref"))
288        .with_type(TableType::System)
289        .with_auto_inc_primary_key(StTableFields::TableId)
290        .with_index_no_accessor_name(btree(StTableFields::TableId))
291        .with_unique_constraint(StTableFields::TableName)
292        .with_index_no_accessor_name(btree(StTableFields::TableName));
293
294    let st_raw_column_type = builder.add_type::<StColumnRow>();
295    let st_col_row_unique_cols = [StColumnFields::TableId.col_id(), StColumnFields::ColPos.col_id()];
296    builder
297        .build_table(ST_COLUMN_NAME, *st_raw_column_type.as_ref().expect("should be ref"))
298        .with_type(TableType::System)
299        .with_unique_constraint(st_col_row_unique_cols)
300        .with_index_no_accessor_name(btree(st_col_row_unique_cols));
301
302    let st_index_type = builder.add_type::<StIndexRow>();
303    builder
304        .build_table(ST_INDEX_NAME, *st_index_type.as_ref().expect("should be ref"))
305        .with_type(TableType::System)
306        .with_auto_inc_primary_key(StIndexFields::IndexId)
307        .with_index_no_accessor_name(btree(StIndexFields::IndexId));
308    // TODO(1.0): unique constraint on name?
309
310    let st_sequence_type = builder.add_type::<StSequenceRow>();
311    builder
312        .build_table(ST_SEQUENCE_NAME, *st_sequence_type.as_ref().expect("should be ref"))
313        .with_type(TableType::System)
314        .with_auto_inc_primary_key(StSequenceFields::SequenceId)
315        .with_index_no_accessor_name(btree(StSequenceFields::SequenceId));
316    // TODO(1.0): unique constraint on name?
317
318    let st_constraint_type = builder.add_type::<StConstraintRow>();
319    builder
320        .build_table(ST_CONSTRAINT_NAME, *st_constraint_type.as_ref().expect("should be ref"))
321        .with_type(TableType::System)
322        .with_auto_inc_primary_key(StConstraintFields::ConstraintId)
323        .with_index_no_accessor_name(btree(StConstraintFields::ConstraintId));
324    // TODO(1.0): unique constraint on name?
325
326    let st_row_level_security_type = builder.add_type::<StRowLevelSecurityRow>();
327    builder
328        .build_table(
329            ST_ROW_LEVEL_SECURITY_NAME,
330            *st_row_level_security_type.as_ref().expect("should be ref"),
331        )
332        .with_type(TableType::System)
333        .with_primary_key(StRowLevelSecurityFields::Sql)
334        .with_unique_constraint(StRowLevelSecurityFields::Sql)
335        .with_index_no_accessor_name(btree(StRowLevelSecurityFields::Sql))
336        .with_index_no_accessor_name(btree(StRowLevelSecurityFields::TableId));
337
338    let st_module_type = builder.add_type::<StModuleRow>();
339    builder
340        .build_table(ST_MODULE_NAME, *st_module_type.as_ref().expect("should be ref"))
341        .with_type(TableType::System);
342    // TODO: add empty unique constraint here, once we've implemented those.
343
344    let st_client_type = builder.add_type::<StClientRow>();
345    let st_client_unique_cols = [StClientFields::Identity, StClientFields::ConnectionId];
346    builder
347        .build_table(ST_CLIENT_NAME, *st_client_type.as_ref().expect("should be ref"))
348        .with_type(TableType::System)
349        .with_unique_constraint(st_client_unique_cols) // FIXME: this is a noop?
350        .with_index_no_accessor_name(btree(st_client_unique_cols));
351
352    let st_schedule_type = builder.add_type::<StScheduledRow>();
353    builder
354        .build_table(ST_SCHEDULED_NAME, *st_schedule_type.as_ref().expect("should be ref"))
355        .with_type(TableType::System)
356        .with_unique_constraint(StScheduledFields::TableId) // FIXME: this is a noop?
357        .with_index_no_accessor_name(btree(StScheduledFields::TableId))
358        .with_auto_inc_primary_key(StScheduledFields::ScheduleId) // FIXME: this is a noop?
359        .with_index_no_accessor_name(btree(StScheduledFields::ScheduleId));
360    // TODO(1.0): unique constraint on name?
361
362    let st_var_type = builder.add_type::<StVarRow>();
363    builder
364        .build_table(ST_VAR_NAME, *st_var_type.as_ref().expect("should be ref"))
365        .with_type(TableType::System)
366        .with_unique_constraint(StVarFields::Name) // FIXME: this is a noop?
367        .with_index_no_accessor_name(btree(StVarFields::Name))
368        .with_primary_key(StVarFields::Name);
369
370    let result = builder
371        .finish()
372        .try_into()
373        .expect("system table module is invalid, did you change it or add a validation rule it doesn't meet?");
374
375    validate_system_table::<StTableFields>(&result, ST_TABLE_NAME);
376    validate_system_table::<StColumnFields>(&result, ST_COLUMN_NAME);
377    validate_system_table::<StIndexFields>(&result, ST_INDEX_NAME);
378    validate_system_table::<StSequenceFields>(&result, ST_SEQUENCE_NAME);
379    validate_system_table::<StConstraintFields>(&result, ST_CONSTRAINT_NAME);
380    validate_system_table::<StRowLevelSecurityFields>(&result, ST_ROW_LEVEL_SECURITY_NAME);
381    validate_system_table::<StModuleFields>(&result, ST_MODULE_NAME);
382    validate_system_table::<StClientFields>(&result, ST_CLIENT_NAME);
383    validate_system_table::<StVarFields>(&result, ST_VAR_NAME);
384    validate_system_table::<StScheduledFields>(&result, ST_SCHEDULED_NAME);
385
386    result
387}
388
389lazy_static::lazy_static! {
390    /// The canonical definition of the system tables.
391    ///
392    /// It's important not to leak this `ModuleDef` or the `Def`s it contains outside this file.
393    /// You should only return `Schema`s from this file, not `Def`s!
394    ///
395    /// This is because `SYSTEM_MODULE_DEF` has a `Typespace` that is DISTINCT from the typespace used in the client module.
396    /// System `TableDef`s refer to this typespace, but client `TableDef`s refer to the client typespace.
397    /// This could easily result in confusing errors!
398    /// Fortunately, when converting from `TableDef` to `TableSchema`, all `AlgebraicType`s are resolved,
399    /// so that they are self-contained and do not refer to any `Typespace`.
400    static ref SYSTEM_MODULE_DEF: ModuleDef = system_module_def();
401}
402
403fn st_schema(name: &str, id: TableId) -> TableSchema {
404    let result = TableSchema::from_module_def(
405        &SYSTEM_MODULE_DEF,
406        SYSTEM_MODULE_DEF.table(name).expect("missing system table definition"),
407        (),
408        id,
409    );
410    result
411}
412
413fn st_table_schema() -> TableSchema {
414    st_schema(ST_TABLE_NAME, ST_TABLE_ID)
415}
416
417fn st_column_schema() -> TableSchema {
418    st_schema(ST_COLUMN_NAME, ST_COLUMN_ID)
419}
420
421fn st_index_schema() -> TableSchema {
422    st_schema(ST_INDEX_NAME, ST_INDEX_ID)
423}
424
425fn st_sequence_schema() -> TableSchema {
426    st_schema(ST_SEQUENCE_NAME, ST_SEQUENCE_ID)
427}
428
429fn st_constraint_schema() -> TableSchema {
430    st_schema(ST_CONSTRAINT_NAME, ST_CONSTRAINT_ID)
431}
432
433fn st_row_level_security_schema() -> TableSchema {
434    st_schema(ST_ROW_LEVEL_SECURITY_NAME, ST_ROW_LEVEL_SECURITY_ID)
435}
436
437pub(crate) fn st_module_schema() -> TableSchema {
438    st_schema(ST_MODULE_NAME, ST_MODULE_ID)
439}
440
441fn st_client_schema() -> TableSchema {
442    st_schema(ST_CLIENT_NAME, ST_CLIENT_ID)
443}
444
445fn st_scheduled_schema() -> TableSchema {
446    st_schema(ST_SCHEDULED_NAME, ST_SCHEDULED_ID)
447}
448
449pub fn st_var_schema() -> TableSchema {
450    st_schema(ST_VAR_NAME, ST_VAR_ID)
451}
452
453/// If `table_id` refers to a known system table, return its schema.
454///
455/// Used when restoring from a snapshot; system tables are reinstantiated with this schema,
456/// whereas user tables are reinstantiated with a schema computed from the snapshotted system tables.
457///
458/// This must be kept in sync with the set of system tables.
459pub(crate) fn system_table_schema(table_id: TableId) -> Option<TableSchema> {
460    match table_id {
461        ST_TABLE_ID => Some(st_table_schema()),
462        ST_COLUMN_ID => Some(st_column_schema()),
463        ST_SEQUENCE_ID => Some(st_sequence_schema()),
464        ST_INDEX_ID => Some(st_index_schema()),
465        ST_CONSTRAINT_ID => Some(st_constraint_schema()),
466        ST_ROW_LEVEL_SECURITY_ID => Some(st_row_level_security_schema()),
467        ST_MODULE_ID => Some(st_module_schema()),
468        ST_CLIENT_ID => Some(st_client_schema()),
469        ST_VAR_ID => Some(st_var_schema()),
470        ST_SCHEDULED_ID => Some(st_scheduled_schema()),
471        _ => None,
472    }
473}
474
475/// System Table [ST_TABLE_NAME]
476///
477/// | table_id | table_name  | table_type | table_access |
478/// |----------|-------------|----------- |------------- |
479/// | 4        | "customers" | "user"     | "public"     |
480#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)]
481#[sats(crate = spacetimedb_lib)]
482pub struct StTableRow {
483    pub(crate) table_id: TableId,
484    pub(crate) table_name: Box<str>,
485    pub(crate) table_type: StTableType,
486    pub(crate) table_access: StAccess,
487    /// The primary key of the table.
488    /// This is a `ColId` everywhere else, but we make it a `ColList` here
489    /// for future compatibility in case we ever have composite primary keys.
490    pub(crate) table_primary_key: Option<ColList>,
491}
492
493impl TryFrom<RowRef<'_>> for StTableRow {
494    type Error = DatastoreError;
495    fn try_from(row: RowRef<'_>) -> Result<Self, DatastoreError> {
496        read_via_bsatn(row)
497    }
498}
499
500impl From<StTableRow> for ProductValue {
501    fn from(x: StTableRow) -> Self {
502        to_product_value(&x)
503    }
504}
505
506/// A wrapper around `AlgebraicType` that acts like `AlgegbraicType::bytes()` for serialization purposes.
507#[derive(Debug, Clone, PartialEq, Eq)]
508pub(crate) struct AlgebraicTypeViaBytes(pub AlgebraicType);
509impl_st!([] AlgebraicTypeViaBytes, AlgebraicType::bytes());
510impl<'de> Deserialize<'de> for AlgebraicTypeViaBytes {
511    fn deserialize<D: spacetimedb_lib::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
512        let bytes = <&[u8]>::deserialize(deserializer)?;
513        let ty = AlgebraicType::decode(&mut &*bytes).map_err(D::Error::custom)?;
514        Ok(AlgebraicTypeViaBytes(ty))
515    }
516}
517thread_local! {
518    static ALGEBRAIC_TYPE_WRITE_BUF: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
519}
520impl_serialize!([] AlgebraicTypeViaBytes, (self, ser) => {
521    ALGEBRAIC_TYPE_WRITE_BUF.with_borrow_mut(|buf| {
522        buf.clear();
523        self.0.encode(buf);
524        buf[..].serialize(ser)
525    })
526});
527impl From<AlgebraicType> for AlgebraicTypeViaBytes {
528    fn from(ty: AlgebraicType) -> Self {
529        Self(ty)
530    }
531}
532
533/// System Table [ST_COLUMN_NAME]
534///
535/// | table_id | col_id | col_name | col_type            |
536/// |----------|---------|----------|--------------------|
537/// | 1        | 0       | "id"     | AlgebraicType::U32 |
538#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)]
539#[sats(crate = spacetimedb_lib)]
540pub struct StColumnRow {
541    pub(crate) table_id: TableId,
542    pub(crate) col_pos: ColId,
543    pub(crate) col_name: Box<str>,
544    pub(crate) col_type: AlgebraicTypeViaBytes,
545}
546
547impl TryFrom<RowRef<'_>> for StColumnRow {
548    type Error = DatastoreError;
549    fn try_from(row: RowRef<'_>) -> Result<Self, DatastoreError> {
550        read_via_bsatn(row)
551    }
552}
553
554impl From<StColumnRow> for ProductValue {
555    fn from(x: StColumnRow) -> Self {
556        to_product_value(&x)
557    }
558}
559
560impl From<StColumnRow> for ColumnSchema {
561    fn from(column: StColumnRow) -> Self {
562        Self {
563            table_id: column.table_id,
564            col_pos: column.col_pos,
565            col_name: column.col_name,
566            col_type: column.col_type.0,
567        }
568    }
569}
570
571/// System Table [ST_INDEX_NAME]
572///
573/// | index_id | table_id | index_name  | index_algorithm            |
574/// |----------|----------|-------------|----------------------------|
575/// | 1        |          | "ix_sample" | btree({"columns": [1, 2]}) |
576#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)]
577#[sats(crate = spacetimedb_lib)]
578pub struct StIndexRow {
579    pub(crate) index_id: IndexId,
580    pub(crate) table_id: TableId,
581    pub(crate) index_name: Box<str>,
582    pub(crate) index_algorithm: StIndexAlgorithm,
583}
584
585/// An index algorithm for storing in the system tables.
586///
587/// It is critical that this type never grow in layout, as it is stored in the system tables.
588/// This is checked by (TODO(1.0): add a test!)
589///
590/// It is forbidden to add data to any of the variants of this type.
591/// You have to add a NEW variant.
592#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)]
593#[sats(crate = spacetimedb_lib)]
594pub enum StIndexAlgorithm {
595    /// Unused variant to reserve space.
596    Unused(u128),
597
598    /// A BTree index.
599    BTree { columns: ColList },
600
601    /// A Direct index.
602    Direct { column: ColId },
603}
604
605impl From<IndexAlgorithm> for StIndexAlgorithm {
606    fn from(algorithm: IndexAlgorithm) -> Self {
607        match algorithm {
608            IndexAlgorithm::BTree(BTreeAlgorithm { columns }) => Self::BTree { columns },
609            IndexAlgorithm::Direct(DirectAlgorithm { column }) => Self::Direct { column },
610            algo => unreachable!("unexpected `{algo:?}`, did you add a new one?"),
611        }
612    }
613}
614
615impl From<StIndexAlgorithm> for IndexAlgorithm {
616    fn from(algorithm: StIndexAlgorithm) -> Self {
617        match algorithm {
618            StIndexAlgorithm::BTree { columns } => Self::BTree(BTreeAlgorithm { columns }),
619            StIndexAlgorithm::Direct { column } => Self::Direct(DirectAlgorithm { column }),
620            algo => unreachable!("unexpected `{algo:?}` in system table `st_indexes`"),
621        }
622    }
623}
624
625impl TryFrom<RowRef<'_>> for StIndexRow {
626    type Error = DatastoreError;
627    fn try_from(row: RowRef<'_>) -> Result<Self, DatastoreError> {
628        read_via_bsatn(row)
629    }
630}
631
632impl From<StIndexRow> for ProductValue {
633    fn from(x: StIndexRow) -> Self {
634        to_product_value(&x)
635    }
636}
637
638impl From<StIndexRow> for IndexSchema {
639    fn from(x: StIndexRow) -> Self {
640        Self {
641            index_id: x.index_id,
642            table_id: x.table_id,
643            index_name: x.index_name,
644            index_algorithm: x.index_algorithm.into(),
645        }
646    }
647}
648
649impl From<IndexSchema> for StIndexRow {
650    fn from(x: IndexSchema) -> Self {
651        Self {
652            index_id: x.index_id,
653            table_id: x.table_id,
654            index_name: x.index_name,
655            index_algorithm: x.index_algorithm.into(),
656        }
657    }
658}
659
660/// System Table [ST_SEQUENCE_NAME]
661///
662/// | sequence_id | sequence_name     | increment | start | min_value | max_value | table_id | col_pos| allocated |
663/// |-------------|-------------------|-----------|-------|-----------|-----------|----------|--------|-----------|
664/// | 1           | "seq_customer_id" | 1         | 100   | 10        | 1200      | 1        | 1      | 200       |
665#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)]
666#[sats(crate = spacetimedb_lib)]
667pub struct StSequenceRow {
668    pub(crate) sequence_id: SequenceId,
669    pub(crate) sequence_name: Box<str>,
670    pub(crate) table_id: TableId,
671    pub(crate) col_pos: ColId,
672    pub(crate) increment: i128,
673    pub(crate) start: i128,
674    pub(crate) min_value: i128,
675    pub(crate) max_value: i128,
676    pub(crate) allocated: i128,
677}
678
679impl TryFrom<RowRef<'_>> for StSequenceRow {
680    type Error = DatastoreError;
681    fn try_from(row: RowRef<'_>) -> Result<Self, DatastoreError> {
682        read_via_bsatn(row)
683    }
684}
685
686impl From<StSequenceRow> for ProductValue {
687    fn from(x: StSequenceRow) -> Self {
688        to_product_value(&x)
689    }
690}
691
692impl From<StSequenceRow> for SequenceSchema {
693    fn from(sequence: StSequenceRow) -> Self {
694        Self {
695            sequence_id: sequence.sequence_id,
696            sequence_name: sequence.sequence_name,
697            table_id: sequence.table_id,
698            col_pos: sequence.col_pos,
699            start: sequence.start,
700            increment: sequence.increment,
701            min_value: sequence.min_value,
702            max_value: sequence.max_value,
703            allocated: sequence.allocated,
704        }
705    }
706}
707
708/// System Table [ST_CONSTRAINT_NAME]
709///
710/// | constraint_id | constraint_name      | table_id    | constraint_data    -------------|
711/// |---------------|-------------------- -|-------------|---------------------------------|
712/// | 1             | "unique_customer_id" | 1           | unique({"columns": [1, 2]})     |
713#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)]
714#[sats(crate = spacetimedb_lib)]
715pub struct StConstraintRow {
716    pub(crate) constraint_id: ConstraintId,
717    pub(crate) constraint_name: Box<str>,
718    pub(crate) table_id: TableId,
719    pub(crate) constraint_data: StConstraintData,
720}
721
722/// Constraint data for storing in the system tables.
723///
724/// It is critical that this type never grow in layout, as it is stored in the system tables.
725/// This is checked by (TODO: add a check in this PR!)
726///
727/// It is forbidden to add data to any of the variants of this type.
728/// You have to add a NEW variant.
729#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)]
730#[sats(crate = spacetimedb_lib)]
731pub enum StConstraintData {
732    /// Unused variant to reserve space.
733    Unused(u128),
734
735    /// A BTree index.
736    Unique { columns: ColSet },
737}
738
739impl From<ConstraintData> for StConstraintData {
740    fn from(data: ConstraintData) -> Self {
741        match data {
742            ConstraintData::Unique(UniqueConstraintData { columns }) => StConstraintData::Unique { columns },
743            _ => unimplemented!(),
744        }
745    }
746}
747
748impl TryFrom<RowRef<'_>> for StConstraintRow {
749    type Error = DatastoreError;
750    fn try_from(row: RowRef<'_>) -> Result<Self, DatastoreError> {
751        read_via_bsatn(row)
752    }
753}
754
755impl From<StConstraintRow> for ProductValue {
756    fn from(x: StConstraintRow) -> Self {
757        to_product_value(&x)
758    }
759}
760
761impl From<StConstraintRow> for ConstraintSchema {
762    fn from(x: StConstraintRow) -> Self {
763        Self {
764            constraint_id: x.constraint_id,
765            constraint_name: x.constraint_name,
766            table_id: x.table_id,
767            data: match x.constraint_data {
768                StConstraintData::Unique { columns } => ConstraintData::Unique(UniqueConstraintData { columns }),
769                StConstraintData::Unused(_) => panic!("Someone put a forbidden variant in the system table!"),
770            },
771        }
772    }
773}
774
775/// System Table [ST_ROW_LEVEL_SECURITY_NAME]
776///
777/// | table_id | sql          |
778/// |----------|--------------|
779/// | 1        | "SELECT ..." |
780#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)]
781#[sats(crate = spacetimedb_lib)]
782pub struct StRowLevelSecurityRow {
783    pub(crate) table_id: TableId,
784    pub(crate) sql: RawSql,
785}
786
787impl TryFrom<RowRef<'_>> for StRowLevelSecurityRow {
788    type Error = DatastoreError;
789    fn try_from(row: RowRef<'_>) -> Result<Self, DatastoreError> {
790        read_via_bsatn(row)
791    }
792}
793
794impl From<StRowLevelSecurityRow> for ProductValue {
795    fn from(x: StRowLevelSecurityRow) -> Self {
796        to_product_value(&x)
797    }
798}
799
800impl From<StRowLevelSecurityRow> for RowLevelSecuritySchema {
801    fn from(x: StRowLevelSecurityRow) -> Self {
802        Self {
803            table_id: x.table_id,
804            sql: x.sql,
805        }
806    }
807}
808/// Indicates the kind of module the `program_bytes` of a [`StModuleRow`]
809/// describes.
810///
811/// More or less a placeholder to allow for future non-WASM hosts without
812/// having to change the [`st_module_schema`].
813#[derive(Clone, Copy, Debug, Eq, PartialEq)]
814pub struct ModuleKind(u8);
815
816impl ModuleKind {
817    /// The [`ModuleKind`] of WASM-based modules.
818    pub const WASM: ModuleKind = ModuleKind(0);
819}
820
821impl From<crate::messages::control_db::HostType> for ModuleKind {
822    fn from(host_type: crate::messages::control_db::HostType) -> Self {
823        match host_type {
824            crate::messages::control_db::HostType::Wasm => Self::WASM,
825        }
826    }
827}
828
829impl_serialize!([] ModuleKind, (self, ser) => self.0.serialize(ser));
830impl_deserialize!([] ModuleKind, de => u8::deserialize(de).map(Self));
831impl_st!([] ModuleKind, AlgebraicType::U8);
832
833/// A wrapper for [`ConnectionId`] that acts like [`AlgebraicType::U128`] for serialization purposes.
834#[derive(Clone, Copy, Debug, Eq, PartialEq)]
835pub struct ConnectionIdViaU128(pub ConnectionId);
836impl_serialize!([] ConnectionIdViaU128, (self, ser) => self.0.to_u128().serialize(ser));
837impl_deserialize!([] ConnectionIdViaU128, de => <u128>::deserialize(de).map(ConnectionId::from_u128).map(ConnectionIdViaU128));
838impl_st!([] ConnectionIdViaU128, AlgebraicType::U128);
839impl From<ConnectionId> for ConnectionIdViaU128 {
840    fn from(id: ConnectionId) -> Self {
841        Self(id)
842    }
843}
844
845/// A wrapper for [`Identity`] that acts like [`AlgebraicType::U256`] for serialization purposes.
846#[derive(Clone, Copy, Debug, Eq, PartialEq)]
847pub struct IdentityViaU256(pub Identity);
848impl_serialize!([] IdentityViaU256, (self, ser) => self.0.to_u256().serialize(ser));
849impl_deserialize!([] IdentityViaU256, de => <u256>::deserialize(de).map(Identity::from_u256).map(IdentityViaU256));
850impl_st!([] IdentityViaU256, AlgebraicType::U256);
851impl From<Identity> for IdentityViaU256 {
852    fn from(id: Identity) -> Self {
853        Self(id)
854    }
855}
856
857/// System table [ST_MODULE_NAME]
858/// This table holds exactly one row, describing the latest version of the
859/// SpacetimeDB module associated with the database:
860///
861/// * `database_identity` is the [`Identity`] of the database.
862/// * `owner_identity` is the [`Identity`] of the owner of the database.
863/// * `program_kind` is the [`ModuleKind`] (currently always [`ModuleKind::WASM`]).
864/// * `program_hash` is the [`Hash`] of the raw bytes of the (compiled) module.
865/// * `program_bytes` are the raw bytes of the (compiled) module.
866/// * `module_version` is the version of the module.
867///
868/// | identity | owner_identity |  program_kind | program_bytes | program_hash        | module_version |
869/// |------------------|----------------|---------------|---------------|---------------------|----------------|
870/// | <bytes>          | <bytes>        |  0            | <bytes>       | <bytes>             | <string>       |
871#[derive(Clone, Debug, Eq, PartialEq, SpacetimeType)]
872#[sats(crate = spacetimedb_lib)]
873pub struct StModuleRow {
874    pub(crate) database_identity: IdentityViaU256,
875    pub(crate) owner_identity: IdentityViaU256,
876    pub(crate) program_kind: ModuleKind,
877    pub(crate) program_hash: Hash,
878    pub(crate) program_bytes: Box<[u8]>,
879    pub(crate) module_version: Box<str>,
880}
881
882/// Read bytes directly from the column `col` in `row`.
883pub fn read_bytes_from_col(row: RowRef<'_>, col: impl StFields) -> Result<Box<[u8]>, DatastoreError> {
884    let bytes = row.read_col::<ArrayValue>(col.col_id())?;
885    if let ArrayValue::U8(bytes) = bytes {
886        Ok(bytes)
887    } else {
888        Err(InvalidFieldError {
889            name: Some(col.name()),
890            col_pos: col.col_id(),
891        }
892        .into())
893    }
894}
895
896/// Read an [`Identity`] directly from the column `col` in `row`.
897///
898/// The [`Identity`] is assumed to be stored as a flat byte array.
899pub fn read_identity_from_col(row: RowRef<'_>, col: impl StFields) -> Result<Identity, DatastoreError> {
900    Ok(Identity::from_u256(row.read_col(col.col_id())?))
901}
902
903/// Read a [`Hash`] directly from the column `col` in `row`.
904///
905/// The [`Hash`] is assumed to be stored as a flat byte array.
906pub fn read_hash_from_col(row: RowRef<'_>, col: impl StFields) -> Result<Hash, DatastoreError> {
907    Ok(Hash::from_u256(row.read_col(col.col_id())?))
908}
909
910impl TryFrom<RowRef<'_>> for StModuleRow {
911    type Error = DatastoreError;
912
913    fn try_from(row: RowRef<'_>) -> Result<Self, Self::Error> {
914        read_via_bsatn(row)
915    }
916}
917
918impl From<StModuleRow> for ProductValue {
919    fn from(row: StModuleRow) -> Self {
920        to_product_value(&row)
921    }
922}
923
924/// System table [ST_CLIENT_NAME]
925///
926/// | identity                                                           | connection_id                      |
927/// |--------------------------------------------------------------------+------------------------------------|
928/// | 0x7452047061ea2502003412941d85a42f89b0702588b823ab55fc4f12e9ea8363 | 0x6bdea3ab517f5857dc9b1b5fe99e1b14 |
929#[derive(Clone, Copy, Debug, Eq, PartialEq, SpacetimeType)]
930#[sats(crate = spacetimedb_lib)]
931pub struct StClientRow {
932    pub(crate) identity: IdentityViaU256,
933    pub(crate) connection_id: ConnectionIdViaU128,
934}
935
936impl From<StClientRow> for ProductValue {
937    fn from(var: StClientRow) -> Self {
938        to_product_value(&var)
939    }
940}
941impl From<&StClientRow> for ProductValue {
942    fn from(var: &StClientRow) -> Self {
943        to_product_value(var)
944    }
945}
946
947impl TryFrom<RowRef<'_>> for StClientRow {
948    type Error = DatastoreError;
949
950    fn try_from(row: RowRef<'_>) -> Result<Self, Self::Error> {
951        read_via_bsatn(row)
952    }
953}
954
955/// System table [ST_VAR_NAME]
956///
957/// | name        | value     |
958/// |-------------|-----------|
959/// | "row_limit" | (U64 = 5) |
960#[derive(Debug, Clone, SpacetimeType)]
961#[sats(crate = spacetimedb_lib)]
962pub struct StVarRow {
963    pub name: StVarName,
964    pub value: StVarValue,
965}
966
967impl From<StVarRow> for ProductValue {
968    fn from(var: StVarRow) -> Self {
969        to_product_value(&var)
970    }
971}
972
973impl From<StVarRow> for AlgebraicValue {
974    fn from(row: StVarRow) -> Self {
975        AlgebraicValue::Product(row.into())
976    }
977}
978
979/// A system variable that defines a row limit for queries and subscriptions.
980/// If the cardinality of a query is estimated to exceed this limit,
981/// it will be rejected before being executed.
982pub const ST_VARNAME_ROW_LIMIT: &str = "row_limit";
983/// A system variable that defines a threshold for logging slow queries.
984pub const ST_VARNAME_SLOW_QRY: &str = "slow_ad_hoc_query_ms";
985/// A system variable that defines a threshold for logging slow subscriptions.
986pub const ST_VARNAME_SLOW_SUB: &str = "slow_subscription_query_ms";
987/// A system variable that defines a threshold for logging slow tx updates.
988pub const ST_VARNAME_SLOW_INC: &str = "slow_tx_update_ms";
989
990/// The name of a system variable in `st_var`
991#[derive(Debug, Clone, Copy, PartialEq, Eq)]
992pub enum StVarName {
993    RowLimit,
994    SlowQryThreshold,
995    SlowSubThreshold,
996    SlowIncThreshold,
997}
998impl From<StVarName> for &'static str {
999    fn from(value: StVarName) -> Self {
1000        match value {
1001            StVarName::RowLimit => ST_VARNAME_ROW_LIMIT,
1002            StVarName::SlowQryThreshold => ST_VARNAME_SLOW_QRY,
1003            StVarName::SlowSubThreshold => ST_VARNAME_SLOW_SUB,
1004            StVarName::SlowIncThreshold => ST_VARNAME_SLOW_INC,
1005        }
1006    }
1007}
1008impl From<StVarName> for AlgebraicValue {
1009    fn from(value: StVarName) -> Self {
1010        let value: &'static str = value.into();
1011        AlgebraicValue::String(value.into())
1012    }
1013}
1014impl FromStr for StVarName {
1015    type Err = anyhow::Error;
1016
1017    fn from_str(s: &str) -> Result<Self, Self::Err> {
1018        match s {
1019            ST_VARNAME_ROW_LIMIT => Ok(StVarName::RowLimit),
1020            ST_VARNAME_SLOW_QRY => Ok(StVarName::SlowQryThreshold),
1021            ST_VARNAME_SLOW_SUB => Ok(StVarName::SlowSubThreshold),
1022            ST_VARNAME_SLOW_INC => Ok(StVarName::SlowIncThreshold),
1023            _ => Err(anyhow::anyhow!("Invalid system variable {}", s)),
1024        }
1025    }
1026}
1027impl_st!([] StVarName, AlgebraicType::String);
1028impl_serialize!([] StVarName, (self, ser) => <&'static str>::from(*self).serialize(ser));
1029impl<'de> Deserialize<'de> for StVarName {
1030    fn deserialize<D: spacetimedb_lib::de::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
1031        let s = <&str>::deserialize(de)?;
1032        s.parse().map_err(D::Error::custom)
1033    }
1034}
1035
1036impl StVarName {
1037    pub fn type_of(&self) -> AlgebraicType {
1038        match self {
1039            StVarName::RowLimit
1040            | StVarName::SlowQryThreshold
1041            | StVarName::SlowSubThreshold
1042            | StVarName::SlowIncThreshold => AlgebraicType::U64,
1043        }
1044    }
1045}
1046
1047impl TryFrom<RowRef<'_>> for StVarRow {
1048    type Error = DatastoreError;
1049
1050    fn try_from(row: RowRef<'_>) -> Result<Self, Self::Error> {
1051        // The position of the `value` column in `st_var`
1052        let col_pos = StVarFields::Value.col_id();
1053
1054        // An error when reading the `value` column in `st_var`
1055        let invalid_value = InvalidFieldError {
1056            col_pos,
1057            name: Some(StVarFields::Value.name()),
1058        };
1059
1060        let name = row.read_col::<Box<str>>(StVarFields::Name.col_id())?;
1061        let name = StVarName::from_str(&name)?;
1062        match row.read_col::<AlgebraicValue>(col_pos)? {
1063            AlgebraicValue::Sum(sum) => Ok(StVarRow {
1064                name,
1065                value: sum.try_into().map_err(|_| invalid_value)?,
1066            }),
1067            _ => Err(invalid_value.into()),
1068        }
1069    }
1070}
1071
1072/// System table [ST_SCHEDULED_NAME]
1073/// | schedule_id | table_id | reducer_name | schedule_name |
1074#[derive(Clone, Debug, Eq, PartialEq, SpacetimeType)]
1075#[sats(crate = spacetimedb_lib)]
1076pub struct StScheduledRow {
1077    pub(crate) schedule_id: ScheduleId,
1078    pub(crate) table_id: TableId,
1079    pub(crate) reducer_name: Box<str>,
1080    pub(crate) schedule_name: Box<str>,
1081    pub(crate) at_column: ColId,
1082}
1083
1084impl TryFrom<RowRef<'_>> for StScheduledRow {
1085    type Error = DatastoreError;
1086    fn try_from(row: RowRef<'_>) -> Result<Self, DatastoreError> {
1087        read_via_bsatn(row)
1088    }
1089}
1090
1091impl From<StScheduledRow> for ProductValue {
1092    fn from(x: StScheduledRow) -> Self {
1093        to_product_value(&x)
1094    }
1095}
1096
1097impl From<StScheduledRow> for ScheduleSchema {
1098    fn from(row: StScheduledRow) -> Self {
1099        Self {
1100            table_id: row.table_id,
1101            reducer_name: row.reducer_name,
1102            schedule_id: row.schedule_id,
1103            schedule_name: row.schedule_name,
1104            at_column: row.at_column,
1105        }
1106    }
1107}
1108
1109thread_local! {
1110    static READ_BUF: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
1111}
1112
1113/// Provides access to a buffer to which bytes can be written.
1114pub(crate) fn with_sys_table_buf<R>(run: impl FnOnce(&mut Vec<u8>) -> R) -> R {
1115    READ_BUF.with_borrow_mut(|buf| {
1116        buf.clear();
1117        run(buf)
1118    })
1119}
1120
1121/// Read a value from a system table via BSATN.
1122fn read_via_bsatn<T: DeserializeOwned>(row: RowRef<'_>) -> Result<T, DatastoreError> {
1123    with_sys_table_buf(|buf| Ok(row.read_via_bsatn::<T>(buf)?))
1124}
1125
1126/// Convert a value to a product value.
1127/// Panics if the value does not serialize to a product value.
1128/// It's fine to call this on system table types, because `validate_system_table` checks that
1129/// they are `ProductType`s.
1130///
1131/// TODO: this performs some unnecessary allocation. We may want to reimplement the conversions manually for
1132/// performance eventually.
1133fn to_product_value<T: Serialize>(value: &T) -> ProductValue {
1134    value_serialize(&value).into_product().expect("should be product")
1135}
1136
1137#[cfg(test)]
1138mod tests {
1139    use super::*;
1140
1141    #[test]
1142    fn test_sequences_within_reserved_range() {
1143        let mut num_tables = 0;
1144        let mut num_indexes = 0;
1145        let mut num_constraints = 0;
1146        let mut num_sequences = 0;
1147
1148        for table in system_tables() {
1149            num_tables += 1;
1150            num_indexes += table.indexes.len();
1151            num_constraints += table.constraints.len();
1152            num_sequences += table.sequences.len();
1153        }
1154
1155        assert!(
1156            num_tables <= ST_RESERVED_SEQUENCE_RANGE,
1157            "number of system tables exceeds reserved sequence range"
1158        );
1159        assert!(
1160            num_indexes <= ST_RESERVED_SEQUENCE_RANGE as usize,
1161            "number of system indexes exceeds reserved sequence range"
1162        );
1163        assert!(
1164            num_constraints <= ST_RESERVED_SEQUENCE_RANGE as usize,
1165            "number of system constraints exceeds reserved sequence range"
1166        );
1167        assert!(
1168            num_sequences <= ST_RESERVED_SEQUENCE_RANGE as usize,
1169            "number of system sequences exceeds reserved sequence range"
1170        );
1171    }
1172}