use crate::def::{validate::Result, ModuleDef};
use crate::error::{RawColumnName, ValidationError, ValidationErrors};
use spacetimedb_data_structures::map::HashSet;
use spacetimedb_lib::db::raw_def::{v8::*, v9::*};
use spacetimedb_lib::{
AlgebraicType,
MiscModuleExport as RawMiscModuleExportV8,
ProductType,
ReducerDef as RawReducerDefV8,
TableDesc as RawTableDescV8,
TypeAlias as RawTypeAliasV8,
};
use spacetimedb_primitives::{ColId, ColList, ConstraintKind, Constraints};
use spacetimedb_sats::raw_identifier::RawIdentifier;
use spacetimedb_sats::{AlgebraicTypeRef, Typespace, WithTypespace};
const INIT_NAME: &str = "__init__";
const IDENTITY_CONNECTED_NAME: &str = "__identity_connected__";
const IDENTITY_DISCONNECTED_NAME: &str = "__identity_disconnected__";
pub fn validate(def: RawModuleDefV8) -> Result<ModuleDef> {
let mut extra_errors = vec![];
let v9 = upgrade_module(def, &mut extra_errors);
let result: Result<ModuleDef> = crate::def::validate::v9::validate(v9);
ValidationErrors::add_extra_errors(result, extra_errors).map_err(ValidationErrors::sort_deduplicate)
}
fn upgrade_module(def: RawModuleDefV8, extra_errors: &mut Vec<ValidationError>) -> RawModuleDefV9 {
let RawModuleDefV8 {
typespace,
tables,
reducers,
misc_exports,
} = def;
let tables = convert_all(tables, |table| upgrade_table(table, &typespace, extra_errors));
let reducers = convert_all(reducers, upgrade_reducer);
let types = misc_exports.into_iter().map(upgrade_misc_export_to_type).collect();
RawModuleDefV9 {
typespace,
tables,
reducers,
types,
misc_exports: Default::default(),
row_level_security: vec![], }
}
pub fn generated_indexes(table: &RawTableDefV8) -> impl Iterator<Item = RawIndexDefV8> + '_ {
table
.constraints
.iter()
.filter(|x| x.constraints.has_indexed())
.map(|x| {
let is_unique = x.constraints.has_unique();
RawIndexDefV8::for_column(&table.table_name, &x.constraint_name, x.columns.clone(), is_unique)
})
.filter(|idx| table.indexes.iter().all(|x| x.index_name != idx.index_name))
}
pub fn generated_sequences(table: &RawTableDefV8) -> impl Iterator<Item = RawSequenceDefV8> + '_ {
let cols: HashSet<_> = table.sequences.iter().map(|seq| ColList::new(seq.col_pos)).collect();
table
.constraints
.iter()
.filter(move |x| !cols.contains(&x.columns) && x.constraints.has_autoinc())
.map(|x| RawSequenceDefV8::for_column(&table.table_name, &x.constraint_name, x.columns.head().unwrap()))
.filter(|seq| table.sequences.iter().all(|x| x.sequence_name != seq.sequence_name))
}
pub fn generated_constraints(table: &RawTableDefV8) -> impl Iterator<Item = RawConstraintDefV8> + '_ {
let cols: HashSet<_> = table
.constraints
.iter()
.filter(|x| x.constraints.kind() != ConstraintKind::UNSET)
.map(|x| &x.columns)
.collect();
table
.indexes
.iter()
.filter(move |idx: &&RawIndexDefV8| !cols.contains(&idx.columns))
.map(|idx| table.gen_constraint_def(Constraints::from_is_unique(idx.is_unique), idx.columns.clone()))
}
fn upgrade_table(
table: RawTableDescV8,
typespace: &Typespace,
extra_errors: &mut Vec<ValidationError>,
) -> RawTableDefV9 {
let generated_constraints = generated_constraints(&table.schema).collect::<Vec<_>>();
let generated_sequences = generated_sequences(&table.schema).collect::<Vec<_>>();
let generated_indexes = generated_indexes(&table.schema).collect::<Vec<_>>();
let RawTableDescV8 {
schema:
RawTableDefV8 {
table_name,
columns,
indexes,
constraints,
sequences,
table_type,
table_access,
scheduled,
},
data: product_type_ref,
} = table;
let scheduled_at_col = columns
.iter()
.position(|x| &*x.col_name == "scheduled_at")
.map(|i| i as u16);
check_all_column_defs(product_type_ref, columns, &table_name, typespace, extra_errors);
let indexes = convert_all(indexes.into_iter().chain(generated_indexes), upgrade_index);
let sequences = convert_all(sequences.into_iter().chain(generated_sequences), upgrade_sequence);
let schedule = upgrade_schedule(scheduled, scheduled_at_col);
let mut primary_key = None;
let unique_constraints = constraints
.into_iter()
.chain(generated_constraints)
.filter_map(|constraint| upgrade_constraint(constraint, &table_name, &mut primary_key, extra_errors))
.collect();
let table_type = table_type.into();
let table_access = table_access.into();
RawTableDefV9 {
name: table_name,
product_type_ref,
primary_key: ColList::from_iter(primary_key),
indexes,
constraints: unique_constraints,
sequences,
schedule,
table_type,
table_access,
}
}
fn check_all_column_defs(
product_type_ref: AlgebraicTypeRef,
columns: Vec<RawColumnDefV8>,
table_name: &RawIdentifier,
typespace: &Typespace,
extra_errors: &mut Vec<ValidationError>,
) {
let resolved_product_type = typespace
.get(product_type_ref)
.and_then(AlgebraicType::as_product)
.map(|product_type| WithTypespace::new(typespace, product_type).resolve_refs());
match resolved_product_type {
Some(Ok(resolved_product_type)) => {
for (i, column) in columns.into_iter().enumerate() {
check_column(
i.into(),
column,
&resolved_product_type,
table_name,
typespace,
extra_errors,
);
}
}
_ => {
extra_errors.push(ValidationError::InvalidProductTypeRef {
table: table_name.clone(),
ref_: product_type_ref,
});
}
}
}
fn check_column(
id: ColId,
column: RawColumnDefV8,
resolved_product_type: &ProductType,
table_name: &RawIdentifier,
typespace: &Typespace,
extra_errors: &mut Vec<ValidationError>,
) {
let RawColumnDefV8 { col_name, col_type } = column;
let element = resolved_product_type.elements.get(id.idx());
let resolved_col_ty = WithTypespace::new(typespace, &col_type).resolve_refs();
match (element, resolved_col_ty) {
(Some(element), Ok(resolved_col_ty)) => {
if !element.has_name(&col_name) || element.algebraic_type != resolved_col_ty {
extra_errors.push(ValidationError::ColumnDefMalformed {
column: RawColumnName::new(table_name.clone(), col_name),
ty: resolved_col_ty.into(),
pos: id,
product_type: resolved_product_type.clone().into(),
});
}
}
_ => extra_errors.push(ValidationError::ColumnDefMalformed {
column: RawColumnName::new(table_name.clone(), col_name),
ty: col_type.into(),
pos: id,
product_type: resolved_product_type.clone().into(),
}),
}
}
fn upgrade_index(index: RawIndexDefV8) -> RawIndexDefV9 {
let RawIndexDefV8 {
index_name,
is_unique: _, index_type,
columns,
} = index;
let algorithm = match index_type {
IndexType::BTree => RawIndexAlgorithm::BTree { columns },
IndexType::Hash => RawIndexAlgorithm::Hash { columns },
};
let accessor_name = Some(index_name.clone());
RawIndexDefV9 {
name: Some(index_name),
accessor_name,
algorithm,
}
}
fn upgrade_constraint(
constraint: RawConstraintDefV8,
table_name: &RawIdentifier,
primary_key: &mut Option<ColId>,
extra_errors: &mut Vec<ValidationError>,
) -> Option<RawConstraintDefV9> {
let RawConstraintDefV8 {
constraint_name, constraints,
columns,
} = constraint;
if constraints.has_primary_key() {
if let Some(col) = columns.as_singleton() {
let replaced = primary_key.replace(col);
if replaced.is_some() {
extra_errors.push(ValidationError::RepeatedPrimaryKey {
table: table_name.clone(),
});
}
} else {
extra_errors.push(ValidationError::RepeatedPrimaryKey {
table: table_name.clone(),
});
}
}
if constraints.has_unique() {
Some(RawConstraintDefV9 {
name: Some(constraint_name),
data: RawConstraintDataV9::Unique(RawUniqueConstraintDataV9 { columns }),
})
} else {
None
}
}
fn upgrade_schedule(schedule: Option<RawIdentifier>, scheduled_at_col: Option<u16>) -> Option<RawScheduleDefV9> {
let scheduled_at_col = scheduled_at_col?;
schedule.map(|reducer_name| RawScheduleDefV9 {
name: None,
reducer_name,
scheduled_at_column: scheduled_at_col.into(),
})
}
fn upgrade_sequence(sequence: RawSequenceDefV8) -> RawSequenceDefV9 {
let RawSequenceDefV8 {
sequence_name,
col_pos,
increment,
start,
min_value,
max_value,
allocated: _, } = sequence;
RawSequenceDefV9 {
name: Some(sequence_name),
column: col_pos,
start,
increment,
min_value,
max_value,
}
}
fn upgrade_reducer(reducer: RawReducerDefV8) -> RawReducerDefV9 {
let RawReducerDefV8 { name, args } = reducer;
let lifecycle = match &name[..] {
INIT_NAME => Some(Lifecycle::Init),
IDENTITY_CONNECTED_NAME => Some(Lifecycle::OnConnect),
IDENTITY_DISCONNECTED_NAME => Some(Lifecycle::OnDisconnect),
_ => None,
};
RawReducerDefV9 {
name,
params: ProductType::from_iter(args),
lifecycle,
}
}
fn upgrade_misc_export_to_type(misc_export: RawMiscModuleExportV8) -> RawTypeDefV9 {
let RawMiscModuleExportV8::TypeAlias(RawTypeAliasV8 { name, ty }) = misc_export;
let name = sats_name_to_scoped_name(&name);
RawTypeDefV9 {
name,
ty,
custom_ordering: true,
}
}
fn convert_all<T, U>(input: impl IntoIterator<Item = T>, f: impl FnMut(T) -> U) -> Vec<U> {
input.into_iter().map(f).collect()
}
#[cfg(test)]
mod tests {
use crate::def::validate::tests::{check_product_type, expect_identifier, expect_type_name};
use crate::def::validate::v8::{IDENTITY_CONNECTED_NAME, IDENTITY_DISCONNECTED_NAME, INIT_NAME};
use crate::def::IndexAlgorithm;
use crate::def::{validate::Result, ModuleDef};
use crate::error::*;
use crate::type_for_generate::ClientCodegenError;
use spacetimedb_data_structures::expect_error_matching;
use spacetimedb_lib::db::raw_def::*;
use spacetimedb_lib::{ScheduleAt, TableDesc};
use spacetimedb_primitives::{ColId, ColList, Constraints};
use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef, ProductType};
use v8::RawModuleDefV8Builder;
use v9::Lifecycle;
#[test]
fn valid_definition() {
let mut builder = RawModuleDefV8Builder::default();
let product_type = AlgebraicType::product([("a", AlgebraicType::U64), ("b", AlgebraicType::String)]);
let product_type_ref = builder.add_type_for_tests("scope1.scope2.ReferencedProduct", product_type.clone());
let sum_type = AlgebraicType::simple_enum(["Gala", "GrannySmith", "RedDelicious"].into_iter());
let sum_type_ref = builder.add_type_for_tests("ReferencedSum", sum_type.clone());
let schedule_at_type = builder.add_type::<ScheduleAt>();
builder.add_table_for_tests(RawTableDefV8::new_for_tests(
"Apples",
ProductType::from([
("id", AlgebraicType::U64),
("name", AlgebraicType::String),
("count", AlgebraicType::U16),
("type", sum_type_ref.into()),
]),
));
builder.add_table_for_tests(
RawTableDefV8::new_for_tests(
"Bananas",
ProductType::from([
("count", AlgebraicType::U16),
("id", AlgebraicType::U64),
("name", AlgebraicType::String),
(
"optional_product_column",
AlgebraicType::option(product_type_ref.into()),
),
]),
)
.with_column_constraint(Constraints::primary_key_auto(), 0)
.with_column_index([0, 1, 2], false),
);
let deliveries_product_type = builder.add_table_for_tests(
RawTableDefV8::new_for_tests(
"Deliveries",
ProductType::from([
("id", AlgebraicType::U64),
("scheduled_at", schedule_at_type.clone()),
("scheduled_id", AlgebraicType::U64),
]),
)
.with_column_constraint(Constraints::primary_key_auto(), 2)
.with_scheduled(Some("check_deliveries".into())),
);
builder.add_reducer_for_tests(INIT_NAME, ProductType::unit());
builder.add_reducer_for_tests(IDENTITY_CONNECTED_NAME, ProductType::unit());
builder.add_reducer_for_tests(IDENTITY_DISCONNECTED_NAME, ProductType::unit());
builder.add_reducer_for_tests(
"check_deliveries",
ProductType::from([("a", deliveries_product_type.into())]),
);
builder.add_reducer_for_tests("extra_reducer", ProductType::from([("a", AlgebraicType::U64)]));
let def: ModuleDef = builder.finish().try_into().unwrap();
let apples = expect_identifier("Apples");
let bananas = expect_identifier("Bananas");
let deliveries = expect_identifier("Deliveries");
assert_eq!(def.tables.len(), 3);
let apples_def = &def.tables[&apples];
assert_eq!(&apples_def.name, &apples);
assert_eq!(apples_def.columns.len(), 4);
assert_eq!(apples_def.columns[0].name, expect_identifier("id"));
assert_eq!(apples_def.columns[0].ty, AlgebraicType::U64);
assert_eq!(apples_def.columns[1].name, expect_identifier("name"));
assert_eq!(apples_def.columns[1].ty, AlgebraicType::String);
assert_eq!(apples_def.columns[2].name, expect_identifier("count"));
assert_eq!(apples_def.columns[2].ty, AlgebraicType::U16);
assert_eq!(apples_def.columns[3].name, expect_identifier("type"));
assert_eq!(apples_def.columns[3].ty, sum_type_ref.into());
assert_eq!(apples_def.primary_key, None);
let bananas_def = &def.tables[&bananas];
assert_eq!(&bananas_def.name, &bananas);
assert_eq!(bananas_def.columns.len(), 4);
assert_eq!(bananas_def.columns[0].name, expect_identifier("count"));
assert_eq!(bananas_def.columns[0].ty, AlgebraicType::U16);
assert_eq!(bananas_def.columns[1].name, expect_identifier("id"));
assert_eq!(bananas_def.columns[1].ty, AlgebraicType::U64);
assert_eq!(bananas_def.columns[2].name, expect_identifier("name"));
assert_eq!(bananas_def.columns[2].ty, AlgebraicType::String);
assert_eq!(
bananas_def.columns[3].name,
expect_identifier("optional_product_column")
);
assert_eq!(
bananas_def.columns[3].ty,
AlgebraicType::option(product_type_ref.into())
);
assert_eq!(bananas_def.primary_key, Some(0.into()));
let delivery_def = &def.tables[&deliveries];
assert_eq!(&delivery_def.name, &deliveries);
assert_eq!(delivery_def.columns.len(), 3);
assert_eq!(delivery_def.columns[0].name, expect_identifier("id"));
assert_eq!(delivery_def.columns[0].ty, AlgebraicType::U64);
assert_eq!(delivery_def.columns[1].name, expect_identifier("scheduled_at"));
assert_eq!(delivery_def.columns[1].ty, schedule_at_type);
assert_eq!(delivery_def.columns[2].name, expect_identifier("scheduled_id"));
assert_eq!(delivery_def.columns[2].ty, AlgebraicType::U64);
assert_eq!(delivery_def.schedule.as_ref().unwrap().at_column, 1.into());
assert_eq!(
&delivery_def.schedule.as_ref().unwrap().function_name[..],
"check_deliveries"
);
assert_eq!(delivery_def.primary_key, Some(ColId(2)));
assert_eq!(def.typespace.get(product_type_ref), Some(&product_type));
assert_eq!(def.typespace.get(sum_type_ref), Some(&sum_type));
check_product_type(&def, apples_def);
check_product_type(&def, bananas_def);
check_product_type(&def, delivery_def);
let product_type_name = expect_type_name("scope1::scope2::ReferencedProduct");
let sum_type_name = expect_type_name("ReferencedSum");
let apples_type_name = expect_type_name("Apples");
let bananas_type_name = expect_type_name("Bananas");
let deliveries_type_name = expect_type_name("Deliveries");
assert_eq!(def.types[&product_type_name].ty, product_type_ref);
assert_eq!(def.types[&sum_type_name].ty, sum_type_ref);
assert_eq!(def.types[&apples_type_name].ty, apples_def.product_type_ref);
assert_eq!(def.types[&bananas_type_name].ty, bananas_def.product_type_ref);
assert_eq!(def.types[&deliveries_type_name].ty, delivery_def.product_type_ref);
let init_name = expect_identifier(INIT_NAME);
assert_eq!(&*def.reducers[&init_name].name, &*init_name);
assert_eq!(def.reducers[&init_name].lifecycle, Some(Lifecycle::Init));
let identity_connected_name = expect_identifier(IDENTITY_CONNECTED_NAME);
assert_eq!(&*def.reducers[&identity_connected_name].name, &*identity_connected_name);
assert_eq!(
def.reducers[&identity_connected_name].lifecycle,
Some(Lifecycle::OnConnect)
);
let identity_disconnected_name = expect_identifier(IDENTITY_DISCONNECTED_NAME);
assert_eq!(
&*def.reducers[&identity_disconnected_name].name,
&*identity_disconnected_name
);
assert_eq!(
def.reducers[&identity_disconnected_name].lifecycle,
Some(Lifecycle::OnDisconnect)
);
let extra_reducer_name = expect_identifier("extra_reducer");
assert_eq!(&*def.reducers[&extra_reducer_name].name, &*extra_reducer_name);
assert_eq!(def.reducers[&extra_reducer_name].lifecycle, None);
assert_eq!(
def.reducers[&extra_reducer_name].params,
ProductType::from([("a", AlgebraicType::U64)])
);
let check_deliveries_name = expect_identifier("check_deliveries");
assert_eq!(&*def.reducers[&check_deliveries_name].name, &*check_deliveries_name);
assert_eq!(def.reducers[&check_deliveries_name].lifecycle, None);
assert_eq!(
def.reducers[&check_deliveries_name].params,
ProductType::from([("a", deliveries_product_type.into())])
);
}
#[test]
fn invalid_product_type_ref() {
let mut builder = RawModuleDefV8Builder::default();
builder.add_table(TableDesc {
schema: RawTableDefV8::new_for_tests("Bananas", ProductType::from([("count", AlgebraicType::U32)])),
data: AlgebraicTypeRef(1337),
});
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(result, ValidationError::InvalidProductTypeRef { table, ref_ } => {
&table[..] == "Bananas" && ref_ == &AlgebraicTypeRef(1337)
});
}
#[test]
fn invalid_table_name() {
let mut builder = RawModuleDefV8Builder::default();
builder.add_table_for_tests(RawTableDefV8::new_for_tests(
"",
ProductType::from([("b", AlgebraicType::U16), ("a", AlgebraicType::U64)]),
));
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(result, ValidationError::IdentifierError { error } => {
error == &IdentifierError::Empty {}
});
}
#[test]
fn invalid_column_name() {
let mut builder = RawModuleDefV8Builder::default();
builder.add_table_for_tests(RawTableDefV8::new_for_tests(
"",
ProductType::from([("b", AlgebraicType::U16), ("a", AlgebraicType::U64)]),
));
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(result, ValidationError::IdentifierError { error } => {
error == &IdentifierError::Empty {}
});
}
#[test]
fn invalid_index_column_ref() {
let mut builder = RawModuleDefV8Builder::default();
builder.add_table_for_tests(
RawTableDefV8::new_for_tests(
"Bananas",
ProductType::from([("b", AlgebraicType::U16), ("a", AlgebraicType::U64)]),
)
.with_column_index(ColList::from_iter([0, 55]), false),
);
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(result, ValidationError::ColumnNotFound { table, column, .. } => {
&table[..] == "Bananas" &&
column == &55.into()
});
}
#[test]
fn invalid_unique_constraint_column_ref() {
let mut builder = RawModuleDefV8Builder::default();
builder.add_table_for_tests(
RawTableDefV8::new_for_tests(
"Bananas",
ProductType::from([("b", AlgebraicType::U16), ("a", AlgebraicType::U64)]),
)
.with_column_constraint(Constraints::unique(), ColList::from_iter([55])),
);
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(result, ValidationError::ColumnNotFound { table, column, .. } => {
&table[..] == "Bananas" &&
column == &55.into()
});
}
#[test]
fn invalid_sequence_column_ref() {
let mut builder = RawModuleDefV8Builder::default();
builder.add_table_for_tests(
RawTableDefV8::new_for_tests(
"Bananas",
ProductType::from([("b", AlgebraicType::U16), ("a", AlgebraicType::U64)]),
)
.with_column_sequence(ColId(55)),
);
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(result, ValidationError::ColumnNotFound { table, column, .. } => {
&table[..] == "Bananas" &&
column == &55.into()
});
let mut builder = RawModuleDefV8Builder::default();
builder.add_table_for_tests(
RawTableDefV8::new_for_tests(
"Bananas",
ProductType::from([("b", AlgebraicType::U16), ("a", AlgebraicType::String)]),
)
.with_column_sequence(ColId(1)),
);
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(result, ValidationError::InvalidSequenceColumnType { column, column_type, .. } => {
column == &RawColumnName::new("Bananas", "a") &&
column_type.0 == AlgebraicType::String
});
}
#[test]
fn invalid_index_column_duplicates() {
let mut builder = RawModuleDefV8Builder::default();
builder.add_table_for_tests(
RawTableDefV8::new_for_tests(
"Bananas",
ProductType::from([("b", AlgebraicType::U16), ("a", AlgebraicType::U64)]),
)
.with_column_index(ColList::from_iter([0, 0]), false),
);
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(result, ValidationError::DuplicateColumns{ columns, .. } => {
columns == &ColList::from_iter([0, 0])
});
}
#[test]
fn invalid_unique_constraint_column_duplicates() {
let mut builder = RawModuleDefV8Builder::default();
builder.add_table_for_tests(
RawTableDefV8::new_for_tests(
"Bananas",
ProductType::from([("b", AlgebraicType::U16), ("a", AlgebraicType::U64)]),
)
.with_column_constraint(Constraints::unique(), ColList::from_iter([1, 1])),
);
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(result, ValidationError::DuplicateColumns{ columns, .. } => {
columns == &ColList::from_iter([1, 1])
});
}
#[test]
fn recursive_ref() {
let recursive_type = AlgebraicType::product([("a", AlgebraicTypeRef(0).into())]);
let mut builder = RawModuleDefV8Builder::default();
let ref_ = builder.add_type_for_tests("Recursive", recursive_type.clone());
builder.add_reducer_for_tests("silly", ProductType::from([("a", ref_.into())]));
let result: ModuleDef = builder.finish().try_into().unwrap();
assert!(result.typespace_for_generate[ref_].is_recursive());
}
#[test]
fn out_of_bounds_ref() {
let invalid_type_1 = AlgebraicType::product([("a", AlgebraicTypeRef(31).into())]);
let mut builder = RawModuleDefV8Builder::default();
let ref_ = builder.add_type_for_tests("Invalid", invalid_type_1.clone());
builder.add_reducer_for_tests("silly", ProductType::from([("a", ref_.into())]));
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(result, ValidationError::ClientCodegenError { location, error: ClientCodegenError::TypeRefError(_) } => {
location == &TypeLocation::InTypespace { ref_: AlgebraicTypeRef(0) }
});
}
#[test]
fn invalid_use() {
let inner_type_invalid_for_use = AlgebraicType::product([("b", AlgebraicType::U32)]);
let invalid_type = AlgebraicType::product([("a", inner_type_invalid_for_use.clone())]);
let mut builder = RawModuleDefV8Builder::default();
let ref_ = builder.add_type_for_tests("Invalid", invalid_type.clone());
builder.add_reducer_for_tests("silly", ProductType::from([("a", ref_.into())]));
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(
result,
ValidationError::ClientCodegenError {
location,
error: ClientCodegenError::NonSpecialTypeNotAUse { ty }
} => {
location == &TypeLocation::InTypespace { ref_: AlgebraicTypeRef(0) } &&
ty.0 == inner_type_invalid_for_use
}
);
}
#[test]
fn allows_hash_indexes() {
let mut builder = RawModuleDefV8Builder::default();
builder.add_table_for_tests(
RawTableDefV8::new_for_tests(
"Bananas",
ProductType::from([("b", AlgebraicType::U16), ("a", AlgebraicType::U64)]),
)
.with_indexes(vec![RawIndexDefV8 {
columns: [0].into(),
is_unique: false,
index_name: "Bananas_index".into(),
index_type: IndexType::Hash,
}]),
);
let def: ModuleDef = builder.finish().try_into().unwrap();
let indexes = def.indexes().collect::<Vec<_>>();
assert_eq!(indexes.len(), 1);
assert_eq!(indexes[0].algorithm, IndexAlgorithm::Hash(0.into()));
}
#[test]
fn invalid_primary_key() {
let mut builder = RawModuleDefV8Builder::default();
builder.add_table_for_tests(
RawTableDefV8::new_for_tests(
"Bananas",
ProductType::from([("b", AlgebraicType::U16), ("a", AlgebraicType::U64)]),
)
.with_column_constraint(Constraints::primary_key(), ColList::from_iter([44])),
);
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(result, ValidationError::ColumnNotFound { table, column, .. } => {
&table[..] == "Bananas" &&
column == &44.into()
});
}
#[test]
fn duplicate_type_name() {
let mut builder = RawModuleDefV8Builder::default();
builder.add_type_for_tests("scope1.scope2.Duplicate", AlgebraicType::U64);
builder.add_type_for_tests("scope1::scope2::Duplicate", AlgebraicType::U32);
let result: Result<ModuleDef> = builder.finish().try_into();
expect_error_matching!(result, ValidationError::DuplicateTypeName { name } => {
name == &expect_type_name("scope1::scope2::Duplicate")
});
}
}