Skip to content

Commit

Permalink
Add schema::type_for_generate, update validation to use it, fixing a
Browse files Browse the repository at this point in the history
minor bug in the tests in the process

Add utilities to ModuleDef for easier ues for codegen (#1678)

Update crates/sats/src/proptest.rs

Co-authored-by: Mazdak Farrokhzad <[email protected]>
Signed-off-by: james gilles <[email protected]>

Move constraints around for easier future ABI evolution

WIP: Allow cyclic AlgebraicTypes.

Final fixes, address review comments

Final comments addressed & copy editing

Remove outdated comment
  • Loading branch information
kazimuth committed Sep 10, 2024
1 parent c8279e1 commit f88641d
Show file tree
Hide file tree
Showing 15 changed files with 1,375 additions and 428 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 38 additions & 5 deletions crates/lib/src/db/raw_def/v9.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ pub struct RawTableDefV9 {
pub indexes: Vec<RawIndexDefV9>,

/// Any unique constraints on the table.
pub unique_constraints: Vec<RawUniqueConstraintDefV9>,
pub constraints: Vec<RawConstraintDefV9>,

/// The sequences for the table.
pub sequences: Vec<RawSequenceDefV9>,
Expand Down Expand Up @@ -296,6 +296,37 @@ pub struct RawScheduleDefV9 {
pub reducer_name: RawIdentifier,
}

/// A constraint definition attached to a table.
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawConstraintDefV9 {
/// The name of the constraint. Must be unique within the containing `RawDatabaseDef`.
pub name: RawIdentifier,

/// The data for the constraint.
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),
}

/// Requires that the projection of the table onto these `columns` is a bijection.
///
/// That is, there must be a one-to-one relationship between a row and the `columns` of that row.
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
pub struct RawUniqueConstraintDataV9 {
/// The columns that must be unique.
pub columns: ColList,
}

/// A miscellaneous module export.
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
Expand All @@ -314,6 +345,7 @@ pub struct RawTypeDefV9 {
pub name: RawScopedTypeNameV9,

/// The type to which the declaration refers.
/// This must point to an `AlgebraicType::Product` or an `AlgebraicType::Sum` in the module's typespace.
pub ty: AlgebraicTypeRef,

/// Whether this type has a custom ordering.
Expand Down Expand Up @@ -411,7 +443,7 @@ impl RawModuleDefV9Builder {
name,
product_type_ref,
indexes: vec![],
unique_constraints: vec![],
constraints: vec![],
sequences: vec![],
schedule: None,
primary_key: None,
Expand Down Expand Up @@ -583,9 +615,10 @@ impl<'a> RawTableDefBuilder<'a> {
/// Generates a [UniqueConstraintDef] using the supplied `columns`.
pub fn with_unique_constraint(mut self, columns: ColList, name: Option<RawIdentifier>) -> Self {
let name = name.unwrap_or_else(|| self.generate_unique_constraint_name(&columns));
self.table
.unique_constraints
.push(RawUniqueConstraintDefV9 { name, columns });
self.table.constraints.push(RawConstraintDefV9 {
name,
data: RawConstraintDataV9::Unique(RawUniqueConstraintDataV9 { columns }),
});
self
}

Expand Down
16 changes: 16 additions & 0 deletions crates/sats/src/algebraic_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,22 @@ impl AlgebraicType {
matches!(self, Self::Sum(p) if p.is_schedule_at())
}

/// Returns whether this type is a unit type.
pub fn is_unit(&self) -> bool {
matches!(self, Self::Product(p) if p.is_unit())
}

/// Returns whether this type is a never type.
pub fn is_never(&self) -> bool {
matches!(self, Self::Sum(p) if p.is_empty())
}

/// If this type is the standard option type, returns the type of the `some` variant.
/// Otherwise, returns `None`.
pub fn as_option(&self) -> Option<&AlgebraicType> {
self.as_sum().and_then(SumType::as_option)
}

/// Returns whether this type is scalar or a string type.
pub fn is_scalar_or_string(&self) -> bool {
self.is_scalar() || self.is_string()
Expand Down
21 changes: 18 additions & 3 deletions crates/sats/src/proptest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//!
//! This notably excludes `Ref` types.
use crate::{i256, u256};
use crate::{i256, u256, ProductTypeElement, SumTypeVariant};
use crate::{
AlgebraicType, AlgebraicTypeRef, AlgebraicValue, ArrayValue, MapType, MapValue, ProductType, ProductValue, SumType,
SumValue, Typespace, F32, F64,
Expand Down Expand Up @@ -54,15 +54,30 @@ fn generate_algebraic_type_from_leaves(
prop_oneof![
gen_element.clone().prop_map(AlgebraicType::array),
(gen_element.clone(), gen_element.clone()).prop_map(|(key, val)| AlgebraicType::map(key, val)),
// No need for field or variant names.

// No need to generate units here;
// we already generate them in `generate_non_compound_algebraic_type`.
vec(gen_element.clone().prop_map_into(), 1..=SIZE)
.prop_map(|vec| vec
.into_iter()
.enumerate()
.map(|(i, ty)| ProductTypeElement {
// Generate names because the validation code in the `schema` crate requires them.
name: Some(format!("field_{i}").into()),
algebraic_type: ty
})
.collect())
.prop_map(Vec::into_boxed_slice)
.prop_map(AlgebraicType::product),
// Do not generate nevers here; we can't store never in a page.
vec(gen_element.clone().prop_map_into(), 1..=SIZE)
.prop_map(|vec| vec
.into_iter()
.enumerate()
.map(|(i, ty)| SumTypeVariant {
name: Some(format!("variant_{i}").into()),
algebraic_type: ty
})
.collect::<Vec<_>>())
.prop_map(Vec::into_boxed_slice)
.prop_map(AlgebraicType::sum),
]
Expand Down
8 changes: 8 additions & 0 deletions crates/sats/src/typespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ impl Typespace {
Ok(())
}

/// Iterate over types in the typespace with their references.
pub fn refs_with_types(&self) -> impl Iterator<Item = (AlgebraicTypeRef, &AlgebraicType)> {
self.types
.iter()
.enumerate()
.map(|(idx, ty)| (AlgebraicTypeRef(idx as _), ty))
}

/// Check that the entire typespace is valid for generating a `SpacetimeDB` client module.
/// See also the `spacetimedb_schema` crate, which layers additional validation on top
/// of these checks.
Expand Down
1 change: 1 addition & 0 deletions crates/schema/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ unicode-normalization.workspace = true
serde_json.workspace = true
smallvec.workspace = true
hashbrown.workspace = true
enum-as-inner.workspace = true

[dev-dependencies]
spacetimedb-lib = { workspace = true, features = ["test"] }
Expand Down
7 changes: 7 additions & 0 deletions crates/schema/proptest-regressions/type_for_generate.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 50cf163ac81228385b27f96ba1801355e39bc722a937a0cf6ec0d4b27d23ef14 # shrinks to t = Typespace { types: [Bool, Bool, Bool, Product(ProductType { elements: [ProductTypeElement { name: None, algebraic_type: Bool }] }), Bool] }
Loading

0 comments on commit f88641d

Please sign in to comment.