use std::any::TypeId;
use std::collections::btree_map;
use std::collections::BTreeMap;
use std::fmt;
use spacetimedb_primitives::*;
use spacetimedb_sats::raw_identifier::RawIdentifier;
use spacetimedb_sats::typespace::TypespaceBuilder;
use spacetimedb_sats::AlgebraicType;
use spacetimedb_sats::AlgebraicTypeRef;
use spacetimedb_sats::AlgebraicValue;
use spacetimedb_sats::ProductType;
use spacetimedb_sats::ProductTypeElement;
use spacetimedb_sats::SpacetimeType;
use spacetimedb_sats::Typespace;
use crate::db::auth::StAccess;
use crate::db::auth::StTableType;
use crate::db::raw_def::v10::RawConstraintDefV10;
use crate::db::raw_def::v10::RawScopedTypeNameV10;
use crate::db::raw_def::v10::RawSequenceDefV10;
use crate::db::raw_def::v10::RawTypeDefV10;
use crate::db::view::extract_view_return_product_type_ref;
pub type RawSql = Box<str>;
#[derive(Debug, Clone, Default, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawModuleDefV9 {
pub typespace: Typespace,
pub tables: Vec<RawTableDefV9>,
pub reducers: Vec<RawReducerDefV9>,
pub types: Vec<RawTypeDefV9>,
pub misc_exports: Vec<RawMiscModuleExportV9>,
pub row_level_security: Vec<RawRowLevelSecurityDefV9>,
}
impl RawModuleDefV9 {
fn find_table_def(&self, table_name: &str) -> Option<&RawTableDefV9> {
self.tables
.iter()
.find(|table_def| table_def.name.as_ref() == table_name)
}
fn find_view_def(&self, view_name: &str) -> Option<&RawViewDefV9> {
self.misc_exports.iter().find_map(|misc_export| match misc_export {
RawMiscModuleExportV9::View(view_def) if view_def.name.as_ref() == view_name => Some(view_def),
_ => None,
})
}
fn type_ref_for_table(&self, table_name: &str) -> Option<AlgebraicTypeRef> {
self.find_table_def(table_name)
.map(|table_def| table_def.product_type_ref)
}
fn type_ref_for_view(&self, view_name: &str) -> Option<AlgebraicTypeRef> {
self.find_view_def(view_name)
.map(|view_def| &view_def.return_type)
.and_then(|return_type| extract_view_return_product_type_ref(return_type).map(|(ref_, _)| ref_))
}
pub fn type_ref_for_table_like(&self, name: &str) -> Option<AlgebraicTypeRef> {
self.type_ref_for_table(name).or_else(|| self.type_ref_for_view(name))
}
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawTableDefV9 {
pub name: RawIdentifier,
pub product_type_ref: AlgebraicTypeRef,
pub primary_key: ColList,
pub indexes: Vec<RawIndexDefV9>,
pub constraints: Vec<RawConstraintDefV9>,
pub sequences: Vec<RawSequenceDefV9>,
pub schedule: Option<RawScheduleDefV9>,
pub table_type: TableType,
pub table_access: TableAccess,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, SpacetimeType)]
#[sats(crate = crate)]
pub enum TableType {
System,
User,
}
impl From<StTableType> for TableType {
fn from(t: StTableType) -> Self {
match t {
StTableType::System => TableType::System,
StTableType::User => TableType::User,
}
}
}
impl From<TableType> for StTableType {
fn from(t: TableType) -> Self {
match t {
TableType::System => StTableType::System,
TableType::User => StTableType::User,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, SpacetimeType)]
#[sats(crate = crate)]
pub enum TableAccess {
Public,
Private,
}
impl From<StAccess> for TableAccess {
fn from(t: StAccess) -> Self {
match t {
StAccess::Public => TableAccess::Public,
StAccess::Private => TableAccess::Private,
}
}
}
impl From<TableAccess> for StAccess {
fn from(t: TableAccess) -> Self {
match t {
TableAccess::Public => StAccess::Public,
TableAccess::Private => StAccess::Private,
}
}
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawSequenceDefV9 {
pub name: Option<RawIdentifier>,
pub column: ColId,
pub start: Option<i128>,
pub min_value: Option<i128>,
pub max_value: Option<i128>,
pub increment: i128,
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawIndexDefV9 {
pub name: Option<RawIdentifier>,
pub accessor_name: Option<RawIdentifier>,
pub algorithm: RawIndexAlgorithm,
}
#[non_exhaustive]
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub enum RawIndexAlgorithm {
BTree {
columns: ColList,
},
Hash {
columns: ColList,
},
Direct {
column: ColId,
},
}
pub fn btree(cols: impl Into<ColList>) -> RawIndexAlgorithm {
RawIndexAlgorithm::BTree { columns: cols.into() }
}
pub fn hash(cols: impl Into<ColList>) -> RawIndexAlgorithm {
RawIndexAlgorithm::Hash { columns: cols.into() }
}
pub fn direct(col: impl Into<ColId>) -> RawIndexAlgorithm {
RawIndexAlgorithm::Direct { column: col.into() }
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawScheduleDefV9 {
pub name: Option<RawIdentifier>,
pub reducer_name: RawIdentifier,
pub scheduled_at_column: ColId,
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawConstraintDefV9 {
pub name: Option<RawIdentifier>,
pub data: RawConstraintDataV9,
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
#[non_exhaustive]
pub enum RawConstraintDataV9 {
Unique(RawUniqueConstraintDataV9),
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawUniqueConstraintDataV9 {
pub columns: ColList,
}
#[derive(Debug, Clone, PartialEq, Eq, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialOrd, Ord))]
pub struct RawRowLevelSecurityDefV9 {
pub sql: RawSql,
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord, derive_more::From))]
#[non_exhaustive]
pub enum RawMiscModuleExportV9 {
ColumnDefaultValue(RawColumnDefaultValueV9),
Procedure(RawProcedureDefV9),
View(RawViewDefV9),
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawColumnDefaultValueV9 {
pub table: RawIdentifier,
pub col_id: ColId,
pub value: Box<[u8]>,
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawTypeDefV9 {
pub name: RawScopedTypeNameV9,
pub ty: AlgebraicTypeRef,
pub custom_ordering: bool,
}
#[derive(Clone, SpacetimeType, PartialEq, Eq, PartialOrd, Ord)]
#[sats(crate = crate)]
pub struct RawScopedTypeNameV9 {
pub scope: Box<[RawIdentifier]>,
pub name: RawIdentifier,
}
impl fmt::Debug for RawScopedTypeNameV9 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for module in self.scope.iter() {
fmt::Debug::fmt(module, f)?;
f.write_str("::")?;
}
fmt::Debug::fmt(&self.name, f)?;
Ok(())
}
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawViewDefV9 {
pub name: RawIdentifier,
pub index: u32,
pub is_public: bool,
pub is_anonymous: bool,
pub params: ProductType,
pub return_type: AlgebraicType,
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
pub enum ViewResultHeader {
RowData,
RawSql(String),
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawReducerDefV9 {
pub name: RawIdentifier,
pub params: ProductType,
pub lifecycle: Option<Lifecycle>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SpacetimeType)]
#[cfg_attr(feature = "enum-map", derive(enum_map::Enum))]
#[sats(crate = crate)]
#[non_exhaustive]
pub enum Lifecycle {
Init,
OnConnect,
OnDisconnect,
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawProcedureDefV9 {
pub name: RawIdentifier,
pub params: ProductType,
pub return_type: AlgebraicType,
}
#[derive(Default)]
pub struct RawModuleDefV9Builder {
module: RawModuleDefV9,
type_map: BTreeMap<TypeId, AlgebraicTypeRef>,
}
impl RawModuleDefV9Builder {
pub fn new() -> Self {
Default::default()
}
pub fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType {
TypespaceBuilder::add_type::<T>(self)
}
pub fn build_table(
&mut self,
name: impl Into<RawIdentifier>,
product_type_ref: AlgebraicTypeRef,
) -> RawTableDefBuilder<'_> {
let name = name.into();
RawTableDefBuilder {
module_def: &mut self.module,
table: RawTableDefV9 {
name,
product_type_ref,
indexes: vec![],
constraints: vec![],
sequences: vec![],
schedule: None,
primary_key: ColList::empty(),
table_type: TableType::User,
table_access: TableAccess::Public,
},
}
}
pub fn build_table_with_new_type(
&mut self,
table_name: impl Into<RawIdentifier>,
product_type: impl Into<spacetimedb_sats::ProductType>,
custom_ordering: bool,
) -> RawTableDefBuilder<'_> {
let table_name = table_name.into();
let product_type_ref = self.add_algebraic_type(
[],
table_name.clone(),
AlgebraicType::from(product_type.into()),
custom_ordering,
);
self.build_table(table_name, product_type_ref)
}
pub fn build_table_with_new_type_for_tests(
&mut self,
table_name: impl Into<RawIdentifier>,
mut product_type: spacetimedb_sats::ProductType,
custom_ordering: bool,
) -> RawTableDefBuilder<'_> {
self.add_expand_product_type_for_tests(&mut 0, &mut product_type);
self.build_table_with_new_type(table_name, product_type, custom_ordering)
}
fn add_expand_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut AlgebraicType) {
if ty.is_valid_for_client_type_use() {
return;
}
match ty {
AlgebraicType::Product(prod_ty) => self.add_expand_product_type_for_tests(name_gen, prod_ty),
AlgebraicType::Sum(sum_type) => {
if let Some(wrapped) = sum_type.as_option_mut() {
self.add_expand_type_for_tests(name_gen, wrapped);
} else {
for elem in sum_type.variants.iter_mut() {
self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
}
}
}
AlgebraicType::Array(ty) => {
self.add_expand_type_for_tests(name_gen, &mut ty.elem_ty);
return;
}
_ => return,
}
let name = *name_gen;
let add_ty = core::mem::replace(ty, AlgebraicType::U8);
*ty = AlgebraicType::Ref(self.add_algebraic_type([], RawIdentifier::new(format!("gen_{name}")), add_ty, true));
*name_gen += 1;
}
fn add_expand_product_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut ProductType) {
for elem in ty.elements.iter_mut() {
self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
}
}
pub fn add_algebraic_type(
&mut self,
scope: impl IntoIterator<Item = RawIdentifier>,
name: impl Into<RawIdentifier>,
ty: spacetimedb_sats::AlgebraicType,
custom_ordering: bool,
) -> AlgebraicTypeRef {
let ty = self.module.typespace.add(ty);
let scope = scope.into_iter().collect();
let name = name.into();
self.module.types.push(RawTypeDefV9 {
name: RawScopedTypeNameV9 { name, scope },
ty,
custom_ordering,
});
ty
}
pub fn add_reducer(
&mut self,
name: impl Into<RawIdentifier>,
params: spacetimedb_sats::ProductType,
lifecycle: Option<Lifecycle>,
) {
self.module.reducers.push(RawReducerDefV9 {
name: name.into(),
params,
lifecycle,
});
}
pub fn add_procedure(
&mut self,
name: impl Into<RawIdentifier>,
params: spacetimedb_sats::ProductType,
return_type: spacetimedb_sats::AlgebraicType,
) {
self.module
.misc_exports
.push(RawMiscModuleExportV9::Procedure(RawProcedureDefV9 {
name: name.into(),
params,
return_type,
}))
}
pub fn add_view(
&mut self,
name: impl Into<RawIdentifier>,
index: usize,
is_public: bool,
is_anonymous: bool,
params: ProductType,
return_type: AlgebraicType,
) {
self.module.misc_exports.push(RawMiscModuleExportV9::View(RawViewDefV9 {
name: name.into(),
index: index as u32,
is_public,
is_anonymous,
params,
return_type,
}));
}
pub fn add_row_level_security(&mut self, sql: &str) {
self.module
.row_level_security
.push(RawRowLevelSecurityDefV9 { sql: sql.into() });
}
pub fn typespace(&self) -> &Typespace {
&self.module.typespace
}
pub fn finish(self) -> RawModuleDefV9 {
self.module
}
}
pub fn sats_name_to_scoped_name(sats_name: &str) -> RawScopedTypeNameV9 {
let mut scope: Vec<RawIdentifier> = sats_name
.split("::")
.flat_map(|s| s.split('.'))
.map(RawIdentifier::new)
.collect();
let name = scope.pop().unwrap_or_default();
RawScopedTypeNameV9 {
scope: scope.into(),
name,
}
}
impl TypespaceBuilder for RawModuleDefV9Builder {
fn add(
&mut self,
typeid: TypeId,
name: Option<&'static str>,
make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
) -> AlgebraicType {
let r = match self.type_map.entry(typeid) {
btree_map::Entry::Occupied(o) => *o.get(),
btree_map::Entry::Vacant(v) => {
let slot_ref = self.module.typespace.add(AlgebraicType::unit());
v.insert(slot_ref);
if let Some(sats_name) = name {
let name = sats_name_to_scoped_name(sats_name);
self.module.types.push(RawTypeDefV9 {
name,
ty: slot_ref,
custom_ordering: true,
});
}
let ty = make_ty(self);
self.module.typespace[slot_ref] = ty;
slot_ref
}
};
AlgebraicType::Ref(r)
}
}
pub struct RawTableDefBuilder<'a> {
module_def: &'a mut RawModuleDefV9,
table: RawTableDefV9,
}
impl RawTableDefBuilder<'_> {
pub fn with_type(mut self, table_type: TableType) -> Self {
self.table.table_type = table_type;
self
}
pub fn with_access(mut self, table_access: TableAccess) -> Self {
self.table.table_access = table_access;
self
}
pub fn with_unique_constraint(mut self, columns: impl Into<ColList>) -> Self {
let columns = columns.into();
self.table.constraints.push(RawConstraintDefV9 {
name: None,
data: RawConstraintDataV9::Unique(RawUniqueConstraintDataV9 { columns }),
});
self
}
pub fn with_primary_key(mut self, column: impl Into<ColId>) -> Self {
self.table.primary_key = ColList::new(column.into());
self
}
pub fn with_auto_inc_primary_key(self, column: impl Into<ColId>) -> Self {
let column = column.into();
self.with_primary_key(column)
.with_unique_constraint(column)
.with_column_sequence(column)
}
pub fn with_index(mut self, algorithm: RawIndexAlgorithm, accessor_name: impl Into<RawIdentifier>) -> Self {
let accessor_name = accessor_name.into();
self.table.indexes.push(RawIndexDefV9 {
name: None,
accessor_name: Some(accessor_name),
algorithm,
});
self
}
pub fn with_index_no_accessor_name(mut self, algorithm: RawIndexAlgorithm) -> Self {
self.table.indexes.push(RawIndexDefV9 {
name: None,
accessor_name: None,
algorithm,
});
self
}
pub fn with_column_sequence(mut self, column: impl Into<ColId>) -> Self {
let column = column.into();
self.table.sequences.push(RawSequenceDefV9 {
name: None,
column,
start: None,
min_value: None,
max_value: None,
increment: 1,
});
self
}
pub fn with_schedule(
mut self,
function_name: impl Into<RawIdentifier>,
scheduled_at_column: impl Into<ColId>,
) -> Self {
let reducer_name = function_name.into();
let scheduled_at_column = scheduled_at_column.into();
self.table.schedule = Some(RawScheduleDefV9 {
name: None,
reducer_name,
scheduled_at_column,
});
self
}
pub fn with_default_column_value(self, column: impl Into<ColId>, value: AlgebraicValue) -> Self {
self.module_def
.misc_exports
.push(RawMiscModuleExportV9::ColumnDefaultValue(RawColumnDefaultValueV9 {
table: self.table.name.clone(),
col_id: column.into(),
value: spacetimedb_sats::bsatn::to_vec(&value).unwrap().into(),
}));
self
}
pub fn finish(self) -> AlgebraicTypeRef {
self.table.product_type_ref
}
pub fn find_col_pos_by_name(&self, column: impl AsRef<str>) -> Option<ColId> {
let column = column.as_ref();
self.columns()?
.iter()
.position(|x| x.has_name(column.as_ref()))
.map(|x| x.into())
}
fn columns(&self) -> Option<&[ProductTypeElement]> {
self.module_def
.typespace
.get(self.table.product_type_ref)
.and_then(|ty| ty.as_product())
.map(|p| &p.elements[..])
}
}
impl Drop for RawTableDefBuilder<'_> {
fn drop(&mut self) {
self.module_def.tables.push(self.table.clone());
}
}
impl From<RawTypeDefV10> for RawTypeDefV9 {
fn from(raw: RawTypeDefV10) -> Self {
RawTypeDefV9 {
name: raw.source_name.into(),
ty: raw.ty,
custom_ordering: raw.custom_ordering,
}
}
}
impl From<RawScopedTypeNameV10> for RawScopedTypeNameV9 {
fn from(raw: RawScopedTypeNameV10) -> Self {
RawScopedTypeNameV9 {
scope: raw.scope,
name: raw.source_name,
}
}
}
impl From<RawConstraintDefV10> for RawConstraintDefV9 {
fn from(raw: RawConstraintDefV10) -> Self {
RawConstraintDefV9 {
name: raw.source_name,
data: raw.data,
}
}
}
impl From<RawSequenceDefV10> for RawSequenceDefV9 {
fn from(raw: RawSequenceDefV10) -> Self {
RawSequenceDefV9 {
name: raw.source_name,
column: raw.column,
start: raw.start,
min_value: raw.min_value,
max_value: raw.max_value,
increment: raw.increment,
}
}
}