spacetimedsl-0.7.2 has been yanked.
SpacetimeDSL
SpacetimeDSL provides you a high-level Domain Specific Language (DSL) to interact in an ergonomic and type-safe way with the data in your SpacetimeDB instance.
Limitations
- The
#[spacetimedsl::dsl] attribute macro must be above the #[spacetimedb::table] attribute macro.
The following things aren't considered during code generation yet:
The following SpacetimeDB features can't be used:
Features
The documentation is currently outdated due to major refactorings, e. g. it doesn't contain the actual generated code, except for the Unique Multi Column Index and Foreign Key Referential Integrity which are the newest and biggest features of SpacetimeDSL.
If features contain snippets of generated code, they relate to the following input code:
pub mod entity {
use spacetimedb::Timestamp;
use spacetimedb::table;
use spacetimedsl::dsl;
#[dsl(plural_name = entities)]
#[table(name = entity, public)]
pub struct Entity {
#[primary_key]
#[auto_inc]
#[wrap]
#[referenced_by(path = crate::component::identifier, table = identifier)]
#[referenced_by(path = crate::component::position, table = position)]
#[referenced_by(path = crate::component::position, table = unique_position)]
id: u128,
created_at: Timestamp,
}
}
pub mod component {
pub mod identifier {
use spacetimedb::{Timestamp, table};
use spacetimedsl::dsl;
#[dsl(plural_name = identifiers)]
#[table(name = identifier, public)]
pub struct Identifier {
#[primary_key]
#[auto_inc]
#[wrap]
id: u128,
#[unique]
#[wrapped(path = crate::entity::EntityId)]
#[foreign_key(table = entity, on_delete = Cascade)]
entity_id: u128,
#[unique]
pub value: String,
created_at: Timestamp,
modified_at: Timestamp,
}
}
pub mod position {
use spacetimedb::{Timestamp, table};
use spacetimedsl::dsl;
#[spacetimedsl::dsl(plural_name = positions)]
#[spacetimedb::table(name = position, public, index(name = x_y_z, btree(columns = [x, y, z])))]
pub struct Position {
#[primary_key]
#[auto_inc]
#[wrap]
id: u128,
#[unique]
#[wrapped(path = crate::entity::EntityId)]
#[foreign_key(table = entity, on_delete = Cascade)]
entity_id: u128,
pub x: i128,
pub y: i128,
pub z: i128,
created_at: Timestamp,
modified_at: Timestamp,
}
#[dsl(plural_name = unique_positions, unique_index(name = x_y_z))]
#[table(name = unique_position, public, index(name = x_y_z, btree(columns = [x, y, z])))]
pub struct UniquePosition {
#[primary_key]
#[auto_inc]
#[wrap]
id: u128,
#[unique]
#[wrapped(path = crate::entity::EntityId)]
#[foreign_key(table = entity, on_delete = Cascade)]
entity_id: u128,
pub x: i128,
pub y: i128,
pub z: i128,
created_at: Timestamp,
modified_at: Timestamp,
}
}
}
Which produces the following DSL methods:

Which can for example be used like so:

🔥🥵 Unique multi-column indices
SpacetimeDSL has implemented unique multi-column indices before SpacetimeDB.
Here is an example:
#[dsl(plural_name = entity_relationships, unique_index(name = parent_child_entity_id))]
#[table(name = entity_relationship, public, index(name = parent_child_entity_id, btree(columns = [parent_entity_id, child_entity_id])))]
pub struct EntityRelationship {
#[primary_key]
#[auto_inc]
id: u128,
parent_entity_id: u128,
child_entity_id: u128,
}
As you can see, you just need to add , unique_index(name = parent_child_entity_id) to your #[spacetimedsl::dsl(plural_name = entity_relationships)] attribute macro (and have a multi-column index on your #[spacetimedb::table] with the same name) and you'll get DSL methods which return an Option<EntityRelationship> instead of an Vec<EntityRelationship> (Get / Update / Delete One Row instead of Get / Update / Delete Multiple Rows)
Keep in mind that the referential integrity which SpacetimeDSL provides is only enforced if you never call db state mutating methods on the &spacetimedb::ReducerContext yourself (insert, update, delete) - so only use the DSL methods.
This feature is unstable and will be removed if SpacetimeDB has implemented it's own unique multi column index feature.
🔥🥵 Foreign Keys / Referential Integrity
You can add #[foreign_key] and #[referenced_by] to your table columns to enforce referential integrity and apply on delete strategies.
Here is a example which is using both:
pub mod entity {
#[dsl(plural_name = entities)]
#[table(name = entity, public)]
pub struct Entity {
#[primary_key]
#[auto_inc]
#[wrap]
#[referenced_by(path = crate::entity::relationship, table = entity_relationship)]
#[referenced_by(path = crate::identifier, table = identifier)]
id: u128,
created_at: Timestamp,
}
pub mod relationship {
#[dsl(plural_name = entity_relationships, unique_index(name = parent_child_entity_id))]
#[table(name = entity_relationship, public, index(name = parent_child_entity_id, btree(columns = [parent_entity_id, child_entity_id])))]
pub struct EntityRelationship {
#[primary_key]
#[auto_inc]
#[wrap]
id: u128,
#[index(btree)]
#[wrapped(name = crate::entity::EntityId)]
#[foreign_key(path = crate::entity, table = entity, on_delete = Error)]
parent_entity_id: u128,
#[index(btree)]
#[wrapped(name = crate::entity::EntityId)]
#[foreign_key(path = crate::entity, table = entity, on_delete = Delete)]
child_entity_id: u128,
created_at: Timestamp,
}
}
}
pub mod identifier {
#[dsl(plural_name = identifiers)]
#[table(name = identifier, public)]
pub struct Identifier {
#[primary_key]
#[auto_inc]
#[wrap]
id: u128
#[unique]
#[wrapped(path = crate::entity::EntityId)]
#[foreign_key(path = crate::entity, table = entity, on_delete = Delete)]
entity_id: u128
#[unique]
pub value: String
created_at: Timestamp
modified_at: Timestamp,
}
}
The #[referenced_by] attribute needs values for the path and the table field and is only allowed on #[primary_key] columns (which require #[wrap]/#[wrapped]).
You can add multiple #[referenced_by]'s to the same primary key column.
You need one for each table which has a #[foreign_key] which is referencing the table (see the pk column of the entity table).
#[referenced_by]'s are responsible for calling the OnDeleteStrategy's of tables which reference them though a #[foreign_key], that means it's influencing the DeleteOne / DeleteMany DSL methods. Here is the changed DeleteOne DSL method of the Entity table:
pub trait DeleteEntityRowById: spacetimedsl::DSLContext {
fn delete_entity_by_id(&self, id: impl Into<EntityId>) -> Result<bool, ()> {
use spacetimedsl::Wrapper;
use spacetimedb::{DbContext, Table};
let id = id.into().value();
let row_to_delete = self.ctx().db().entity().id().find(id);
let primary_key_value_of_row_to_delete;
match row_to_delete {
None => {
return Ok(false);
}
Some(row) => {
primary_key_value_of_row_to_delete = row.id;
}
};
spacetimedsl::internal::DSLInternals::execute_on_delete_strategies_of_referencing_tables_after_one_row_of_the_entity_table_was_deleted(
self.ctx(),
spacetimedsl::OnDeleteStrategy::Error,
&primary_key_value_of_row_to_delete,
)?;
spacetimedsl::internal::DSLInternals::execute_on_delete_strategies_of_referencing_tables_after_one_row_of_the_entity_table_was_deleted(
self.ctx(),
spacetimedsl::OnDeleteStrategy::Delete,
&primary_key_value_of_row_to_delete,
)?;
spacetimedsl::internal::DSLInternals::execute_on_delete_strategies_of_referencing_tables_after_one_row_of_the_entity_table_was_deleted(
self.ctx(),
spacetimedsl::OnDeleteStrategy::SetZero,
&primary_key_value_of_row_to_delete,
)?;
Ok(self.ctx().db().entity().id().delete(id))
}
}
impl DeleteEntityRowById for spacetimedsl::DSL<'_> {}
The delete DSL methods call internal functions now, which are generated by tables with #[referenced_by]'s.
They are called more than one time to ensure that OnDeleteStrategy::Error is always processed first.
There is one implementation which is called if
- one row of the referenced table should be deleted and
one which is called if
- multiple rows of the referenced table should be deleted.
Let's have a look at the functions:
pub trait ExecuteOnDeleteStrategiesOfReferencingTablesAfterOneRowOfTheEntityTableWasDeleted {
fn execute_on_delete_strategies_of_referencing_tables_after_one_row_of_the_entity_table_was_deleted(
ctx: &spacetimedb::ReducerContext,
strategy: spacetimedsl::OnDeleteStrategy,
primary_key_value_of_row_to_delete: &u128,
) -> Result<(), ()> {
use spacetimedsl::Wrapper;
use spacetimedb::{DbContext, Table};
use crate::entity::relationship::ExecuteOnDeleteStrategiesOfTheEntityRelationshipTableAfterOneRowOfTheEntityTableWasDeleted;
spacetimedsl::internal::DSLInternals::execute_on_delete_strategies_of_the_entity_relationship_table_after_one_row_of_the_entity_table_was_deleted(
ctx,
&strategy,
primary_key_value_of_row_to_delete,
)?;
use crate::identifier::ExecuteOnDeleteStrategiesOfTheIdentifierTableAfterOneRowOfTheEntityTableWasDeleted;
spacetimedsl::internal::DSLInternals::execute_on_delete_strategies_of_the_identifier_table_after_one_row_of_the_entity_table_was_deleted(
ctx,
&strategy,
primary_key_value_of_row_to_delete,
)?;
Ok(())
}
}
impl ExecuteOnDeleteStrategiesOfReferencingTablesAfterOneRowOfTheEntityTableWasDeleted for spacetimedsl::internal::DSLInternals {}
pub trait ExecuteOnDeleteStrategiesOfReferencingTablesAfterMultipleRowsOfTheEntityTableWereDeleted {
fn execute_on_delete_strategies_of_referencing_tables_after_multiple_rows_of_the_entity_table_were_deleted(
ctx: &spacetimedb::ReducerContext,
strategy: spacetimedsl::OnDeleteStrategy,
primary_key_values_of_rows_to_delete: &Vec<u128>,
) -> Result<(), ()> {
use spacetimedsl::Wrapper;
use spacetimedb::{DbContext, Table};
use crate::entity::relationship::ExecuteOnDeleteStrategiesOfTheEntityRelationshipTableAfterMultipleRowsOfTheEntityTableWereDeleted;
spacetimedsl::internal::DSLInternals::execute_on_delete_strategies_of_the_entity_relationship_table_after_multiple_rows_of_the_entity_table_were_deleted(
ctx,
&strategy,
primary_key_values_of_rows_to_delete,
)?;
use crate::identifier::ExecuteOnDeleteStrategiesOfTheIdentifierTableAfterMultipleRowsOfTheEntityTableWereDeleted;
spacetimedsl::internal::DSLInternals::execute_on_delete_strategies_of_the_identifier_table_after_multiple_rows_of_the_entity_table_were_deleted(
ctx,
&strategy,
primary_key_values_of_rows_to_delete,
)?;
Ok(())
}
}
impl ExecuteOnDeleteStrategiesOfReferencingTablesAfterMultipleRowsOfTheEntityTableWereDeleted
for spacetimedsl::internal::DSLInternals {}
(They're the same except that the below has a &Vec<PrimaryKeyType> instead of a &PrimaryKeyType parameter).
As you can see they are calling other internal functions, which are generated by tables with #[foreign_key] attributes.
#[foreign_key]'s are only allowed on columns with #[primary_key], #[index] or #[unique].
They also require the #[wrapped] attribute as the #[primary_key] columns does.
They need a value for the path, table and on_delete fields. I think the first two are self-explanatory, the latter not:
on_delete requires you to supply a OnDeleteStrategy - at the moment there are three of them: Error, Delete and SetZero (SetNone will be implemented if SpacetimeDB allows indices on columns with Option<T: SpacetimeType> type.)
-
The on delete Error strategy does what it says - if a row should be deleted which is referenced in a #[foreign_key] of another table the delete DSL method will return an error - no row is deleted, even if you're ignoring the error (which in SpacetimeDB you shouldn't do, because any other state changes in a reducer would be persisted if you don't return the Error).
-
Having an on delete SetZero strategy on your #[foreign_key] will set the value of the column to 0 for any row, which references the primary key of the other table.
-
If the on delete SetNone strategy is implemented, it will do the same as the SetZero strategy except it will set None instead of 0.
-
The on delete Delete strategy (Cascade) is the most advanced strategy of them. Before a row of table_a is deleted, table_b will check if it's #[primary_key] column has also #[referenced_by]'s as table_a has. If that's true, it will ask any referenced table (e. g. table_c and table_d) whether they would have something against the deletion. If table_c has a delete strategy but table_d has a error strategy, the whole deletion request won't happen if any row inside table table_b is referencing a row of table_d - the deletion request won't delete any row, even not in table_a.
To ensure referential integrity, #[foreign_key]'s and #[referenced_by]'s also influence the create / update DSL methods:
pub trait CreateIdentifierRow: spacetimedsl::DSLContext + crate::entity::GetEntityRowOptionById {
fn create_identifier(
&self,
entity_id: impl Into<crate::entity::EntityId>,
value: &str,
) -> Result<
Identifier,
spacetimedb::TryInsertError<identifier__TableHandle>,
> {
use spacetimedsl::Wrapper;
use spacetimedb::{DbContext, Table};
let id = u128::default();
let entity_id = entity_id.into().value();
let value = value.to_string();
let created_at = self.ctx().timestamp;
let modified_at = self.ctx().timestamp;
let identifier = Identifier {
id,
entity_id,
value,
created_at,
modified_at,
};
if entity_id.ne(&0) {
match self.get_entity_by_id(identifier.get_entity_id()) {
Some(_) => {}
None => {
{
panic!(
"There must be a row inside the `entity` table when trying to find one with primary key column `id` value `{0:?}`. Found none. There can be two reasons for this: You are inserting or updating somewhere using spacetimedb::ReducerContext instead of spacetimedsl::DSL or the Foreign Key / Referenced By SpacetimeDSL feature is broken.",
identifier.get_entity_id(),
);
};
}
};
}
self.ctx().db().identifier().try_insert(identifier)
}
}
impl CreateIdentifierRow for spacetimedsl::DSL<'_> {}
pub trait UpdateEntityRelationshipRowById: spacetimedsl::DSLContext + crate::entity::GetEntityRowOptionById {
fn update_entity_relationship_by_id(
&self,
mut entity_relationship: EntityRelationship,
) -> Result<
EntityRelationship,
spacetimedb::TryInsertError<entity_relationship__TableHandle>,
> {
use spacetimedsl::Wrapper;
use spacetimedb::{DbContext, Table};
let parent_entity_id = entity_relationship.get_parent_entity_id().value();
let child_entity_id = entity_relationship.get_child_entity_id().value();
if child_entity_id.ne(&0) {
match self.get_entity_by_id(entity_relationship.get_child_entity_id()) {
Some(_) => {}
None => {
{
panic!(
"There must be a row inside the `entity` table when trying to find one with primary key column `id` value `{0:?}`. Found none. There can be two reasons for this: You are inserting or updating somewhere using spacetimedb::ReducerContext instead of spacetimedsl::DSL or the Foreign Key / Referenced By SpacetimeDSL feature is broken.",
entity_relationship.get_child_entity_id(),
);
};
}
};
}
Ok(self.ctx().db().entity_relationship().id().update(entity_relationship))
}
}
impl UpdateEntityRelationshipRowById for spacetimedsl::DSL<'_> {}
If the value in the #[foreign_key] column isn't 0/None, it will try to find a row with the referenced primary key value of the referenced table when creating or updating.
Keep in mind that the referential integrity which SpacetimeDSL provides is only enforced if you never call db state mutating methods on the &spacetimedb::ReducerContext yourself (insert, update, delete) - so only use the DSL methods.
Also: This feature is unstable. First it will be removed if SpacetimeDB has implemented it's own referential integrity / foreign key features, second I have implemented tests to ensure referential integrity - but there may be edge cases. Make backups of your data before testing the feature and PLEASE, if you find any bug, create a GitHub issue!
Also in one of the next 0.x.0 versions of SpacetimeDSL I'll probably add proper error types, which should contain data about the rows which were deleted (of any table which is affected by a delete method call, not only of the initial deletions!) which will result in breaking changes on your own error handling code. (SpacetimeDB has TryInsertError/UniqueConstraintViolationError, but SpacetimeDSL needs a TryUpdateError and a TryDeleteError as well as a ReferentialIntegrityViolationError to increase it's maturity.)
🔥🥵 Wrapper Types
SpacetimeDSL allows developers to wrap their (primitive) table fields into unique, auto-generated alias types to decrease primitive obsession. This allows the following:
If you add #[wrap] to a field of your table-struct, SpacetimeDSL will generate such a Wrapper Type.
The name of it is generated through the name of the table-struct and the name of the field. If you want to override this default, just provide a name like so: #[wrap(GameObjectId)].
If they're generated, the generated code of other features is influenced, which is described in each feature's section.
You can reference Wrapper Types at other table's fields by adding their path to the attribute. In the input code, both the identifier and the position table contain a entity_id field with #[wrap(crate::entity::EntityId)]. SpacetimeDSL will require developers to provide an object of type EntityId instead of a u128, so the code won't compile if they provide something else.
More of, they can just provide a reference to a Entity object instead of calling get_id() on the entity because there is a impl From<&Entity> for EntityId, which reduces much boilerplate code. SpacetimeDSL will make the navigation to access the raw value for the developer instead.
If you need to reference the same Wrapper Type twice in the same table, you'll need to provide the name of the already generated Wrapper type and prefix it with self::.
For example, if we would want Entities to have a parent, you would add #[wrap(self::EntityId)] parent: u128, to your table-struct. First of all the name is needed because it would create a new Wrapper Type called EntityParent instead if it's not named, second it needs to be prefixed with self:: because SpacetimeDSL doesn't check whether it has already created a Wrapper Type with the same name, instead it looks whether the name is set and contains a :: and if so it generates no new one Wrapper Type and instead assumes it's generated while parsing another table field.
Understanding the implications of Wrapper Types
If you encounter an compilation error like:
The trait bound WrapperType: From<NumericType> is not satisifed.
The trait From<NumericType> is not implemented for WrapperType.
But trait From<&TableType> is implemented fort it.
For that trait implementation, expected &TableType, found NumericType.
Required for NumericType to implement Into<WrapperType>
this means that you've provided a NumericType (like u128) as argument where a WrapperType is required. The caller should instead provide a WrapperType and incorporate it into it's API (e. g. reducer arguments).
It's a common limitation of the SpacetimeDB CLI and the Admin Panel that they don't support custom, non-primitive types - they are affected by primitive obsession. Therefore they have no feature-parity with SpacetimeDB server modules. SpacetimeDSL tries to increase the developer experience and uses the full capacities of SpacetimeDB for it, which are supported by SpacetimeDB clients. That said: If you're creating a Wrapper Type object yourself (WrapperType::new(wrapped_type)) you're doing something what you shouldn't do, because the whole ecosystem around your server module should incorporate them and not be obsessed my primitives.
Output
The input code produces the following output (and many side effects in other features generated code, see the feature's respective section in the docs):
#[derive(Clone, Debug, PartialEq, spacetimedb::SpacetimeType)]
pub struct EntityId {
value: u128,
}
impl From<&Entity> for EntityId {
fn from(value: &Entity) -> Self {
value.get_id()
}
}
impl spacetimedsl::Wrapper<u128, EntityId> for EntityId {
fn new(value: u128) -> Self {
Self { value }
}
fn default() -> Self {
Self { value: u128::default() }
}
fn value(&self) -> u128 {
self.value.clone()
}
}
#[derive(Clone, Debug, PartialEq, spacetimedb::SpacetimeType)]
pub struct IdentifierId {
value: u128,
}
impl From<&Identifier> for IdentifierId {
fn from(value: &Identifier) -> Self {
value.get_id()
}
}
impl spacetimedsl::Wrapper<u128, IdentifierId> for IdentifierId {
fn new(value: u128) -> Self {
Self { value }
}
fn default() -> Self {
Self { value: u128::default() }
}
fn value(&self) -> u128 {
self.value.clone()
}
}
#[derive(Clone, Debug, PartialEq, spacetimedb::SpacetimeType)]
pub struct PositionId {
value: u128,
}
impl From<&Position> for PositionId {
fn from(value: &Position) -> Self {
value.get_id()
}
}
impl spacetimedsl::Wrapper<u128, PositionId> for PositionId {
fn new(value: u128) -> Self {
Self { value }
}
fn default() -> Self {
Self { value: u128::default() }
}
fn value(&self) -> u128 {
self.value.clone()
}
}
🔥🥵 Accessors (Getters and Setters)
For any field in the table-struct, a public getter is generated. It returns either a reference to the table field value or if #[wrap] it clones the value and creates a new instance of the Wrapper Type.
For any field in the table-struct which is not private, a setter with the visibility of the field is generated.
If the field is #[wrap], developers must provide a Wrapper Type or Row instead of an Column.
As you can see, in the generated code aren't any setters for fields which are private. You can use the visibility of fields to describe that a field value should never change after creating instances of the table-struct, which is useful for e. g. primary- and foreign key fields, which should never change. If https://github.com/rust-lang/rust/issues/105077 is released, the library will use the field mutability restrictions instead of the visibility to decide whether or not to generate setters.
impl Entity {
pub fn get_id(&self) -> EntityId {
EntityId::new(self.id.clone())
}
}
impl Identifier {
pub fn get_id(&self) -> IdentifierId {
IdentifierId::new(self.id.clone())
}
pub fn get_entity_id(&self) -> crate::entity::EntityId {
crate::entity::EntityId::new(self.entity_id.clone())
}
pub fn get_value(&self) -> &String {
&self.value
}
pub fn set_value(&mut self, value: String) {
self.value = value
}
}
impl Position {
pub fn get_id(&self) -> PositionId {
PositionId::new(self.id.clone())
}
pub fn get_entity_id(&self) -> crate::entity::EntityId {
crate::entity::EntityId::new(self.entity_id.clone())
}
pub fn get_x(&self) -> &i128 {
&self.x
}
pub fn set_x(&mut self, x: i128) {
self.x = x
}
pub fn get_y(&self) -> &i128 {
&self.y
}
pub fn set_y(&mut self, y: i128) {
self.y = y
}
pub fn get_z(&self) -> &i128 {
&self.z
}
pub fn set_z(&mut self, z: i128) {
self.z = z
}
}
DSL Extension Methods
SpacetimeDSL wraps a &spacetimedb::ReducerContext and provides a more ergonomic API for it, including added capabilities to reduce boilerplate code.
Each of the following headings are methods on spacetimedsl::DSL. Instances of the DSL can be created through the spacetimedsl::dsl(ctx: &ReducerContext) -> DSL; function.
Instead of passing the DSL and the ReducerContext to functions/methods which a reducer depend on, you should only pass the DSL and use the ctx() method on it, which is defined by the DSLContext trait.
In the next sections,
🔥🥵 Create Row
For any table-struct, a create_row DSL extension method is created, which performs a row().try_insert(row).
For each field which
- is not
#[auto_inc] or
- has the name
created_at or
modified_at,
the developer needs to provide an argument to the function.
For the others the default (0 for auto_inc, self.ctx().timestamp for created_at/modified_at) is assumed, like in the example for the Entity-struct, which reduces boilerplate code.
For each #[wrap] field the developer needs to provide a impl Into<WrapperType> instead of a instance of the field type. So to create a Position, you can provide either an &Entity (which you'll do if you have it in your scope) or an EntityId (which you'll do if you have it as a reducer argument provided by clients) for the entity_id parameter.
pub trait CreateEntity: spacetimedsl::DSLContext {
fn create_entity(
&self,
) -> Result<Entity, spacetimedb::TryInsertError<entity__TableHandle>> {
let entity = Entity {
id: u128::default(),
created_at: self.ctx().timestamp,
};
return self.ctx().db().entity().try_insert(entity);
}
}
impl CreateEntity for spacetimedsl::DSL<'_> {}
pub trait CreateIdentifier: spacetimedsl::DSLContext {
fn create_identifier(
&self,
entity_id: impl Into<crate::entity::EntityId>,
value: String,
) -> Result<
Identifier,
spacetimedb::TryInsertError<identifier__TableHandle>,
> {
let identifier = Identifier {
id: u128::default(),
entity_id: entity_id.into().value(),
value,
created_at: self.ctx().timestamp,
modified_at: self.ctx().timestamp,
};
return self.ctx().db().identifier().try_insert(identifier);
}
}
impl CreateIdentifier for spacetimedsl::DSL<'_> {}
pub trait CreatePosition: spacetimedsl::DSLContext {
fn create_position(
&self,
entity_id: impl Into<crate::entity::EntityId>,
x: i128,
y: i128,
z: i128,
) -> Result<Position, spacetimedb::TryInsertError<position__TableHandle>> {
let position = Position {
id: u128::default(),
entity_id: entity_id.into().value(),
x,
y,
z,
created_at: self.ctx().timestamp,
modified_at: self.ctx().timestamp,
};
return self.ctx().db().position().try_insert(position);
}
}
impl CreatePosition for spacetimedsl::DSL<'_> {}
Get Row by Column
For any #[primary_key] and #[unique] field on a table-struct, a get_row_by_column DSL extension method is created, which performs a row().column().find(column).
If the field is #[wrap], developers must provide a Wrapper Type or Row instead of an Column.
pub trait GetEntityRowOptionById: spacetimedsl::DSLContext {
fn get_entity_by_id(&self, id: impl Into<EntityId>) -> Option<Entity> {
return self.ctx().db().entity().id().find(id.into().value());
}
}
impl GetEntityRowOptionById for spacetimedsl::DSL<'_> {}
pub trait GetIdentifierRowOptionById: spacetimedsl::DSLContext {
fn get_identifier_by_id(
&self,
id: impl Into<IdentifierId>,
) -> Option<Identifier> {
return self.ctx().db().identifier().id().find(id.into().value());
}
}
impl GetIdentifierRowOptionById for spacetimedsl::DSL<'_> {}
pub trait GetIdentifierRowOptionByEntityId: spacetimedsl::DSLContext {
fn get_identifier_by_entity_id(
&self,
entity_id: impl Into<crate::entity::EntityId>,
) -> Option<Identifier> {
return self
.ctx()
.db()
.identifier()
.entity_id()
.find(entity_id.into().value());
}
}
impl GetIdentifierRowOptionByEntityId for spacetimedsl::DSL<'_> {}
pub trait GetIdentifierRowOptionByValue: spacetimedsl::DSLContext {
fn get_identifier_by_value(&self, value: String) -> Option<Identifier> {
return self.ctx().db().identifier().value().find(value);
}
}
impl GetIdentifierRowOptionByValue for spacetimedsl::DSL<'_> {}
pub trait GetPositionRowOptionById: spacetimedsl::DSLContext {
fn get_position_by_id(&self, id: impl Into<PositionId>) -> Option<Position> {
return self.ctx().db().position().id().find(id.into().value());
}
}
impl GetPositionRowOptionById for spacetimedsl::DSL<'_> {}
pub trait GetPositionRowOptionByEntityId: spacetimedsl::DSLContext {
fn get_position_by_entity_id(
&self,
entity_id: impl Into<crate::entity::EntityId>,
) -> Option<Position> {
return self
.ctx()
.db()
.position()
.entity_id()
.find(entity_id.into().value());
}
}
impl GetPositionRowOptionByEntityId for spacetimedsl::DSL<'_> {}
Get Rows by Column in
This is the same as the feature above but allows to get multiple rows instead of one.
For any #[primary_key] and #[unique] field on a table-struct, a get_rows_by_column_in DSL extension method is created, which performs a row().column().find(column) with multiple column values.
If the field is #[wrap], developers must provide a Vec<Wrapper Type> or Vec<Row> instead of an Vec<Column>.
pub trait GetEntityRowOptionsById: spacetimedsl::DSLContext {
fn get_entities_by_id_in<'a>(
&'a self,
ids: Vec<impl Into<EntityId>>,
) -> impl Iterator<Item = Option<Entity>> {
let mut result: Vec<Option<Entity>> = ::alloc::vec::Vec::new();
for id in ids {
result.push(self.ctx().db().entity().id().find(id.into().value()));
}
result.into_iter()
}
}
impl GetEntityRowOptionsById for spacetimedsl::DSL<'_> {}
pub trait GetIdentifierRowOptionsById: spacetimedsl::DSLContext {
fn get_identifiers_by_id_in<'a>(
&'a self,
ids: Vec<impl Into<IdentifierId>>,
) -> impl Iterator<Item = Option<Identifier>> {
let mut result: Vec<Option<Identifier>> = ::alloc::vec::Vec::new();
for id in ids {
result
.push(self.ctx().db().identifier().id().find(id.into().value()));
}
result.into_iter()
}
}
impl GetIdentifierRowOptionsById for spacetimedsl::DSL<'_> {}
pub trait GetIdentifierRowOptionsByEntityId: spacetimedsl::DSLContext {
fn get_identifiers_by_entity_id_in<'a>(
&'a self,
entity_ids: Vec<impl Into<crate::entity::EntityId>>,
) -> impl Iterator<Item = Option<Identifier>> {
let mut result: Vec<Option<Identifier>> = ::alloc::vec::Vec::new();
for entity_id in entity_ids {
result
.push(
self
.ctx()
.db()
.identifier()
.entity_id()
.find(entity_id.into().value()),
);
}
result.into_iter()
}
}
impl GetIdentifierRowOptionsByEntityId for spacetimedsl::DSL<'_> {}
pub trait GetIdentifierRowOptionsByValue: spacetimedsl::DSLContext {
fn get_identifiers_by_value_in<'a>(
&'a self,
values: Vec<Box<str>>,
) -> impl Iterator<Item = Option<Identifier>> {
let mut result: Vec<Option<Identifier>> = ::alloc::vec::Vec::new();
for value in values {
result.push(self.ctx().db().identifier().value().find(value));
}
result.into_iter()
}
}
impl GetIdentifierRowOptionsByValue for spacetimedsl::DSL<'_> {}
pub trait GetPositionRowOptionsById: spacetimedsl::DSLContext {
fn get_positions_by_id_in<'a>(
&'a self,
ids: Vec<impl Into<PositionId>>,
) -> impl Iterator<Item = Option<Position>> {
let mut result: Vec<Option<Position>> = ::alloc::vec::Vec::new();
for id in ids {
result.push(self.ctx().db().position().id().find(id.into().value()));
}
result.into_iter()
}
}
impl GetPositionRowOptionsById for spacetimedsl::DSL<'_> {}
pub trait GetPositionRowOptionsByEntityId: spacetimedsl::DSLContext {
fn get_positions_by_entity_id_in<'a>(
&'a self,
entity_ids: Vec<impl Into<crate::entity::EntityId>>,
) -> impl Iterator<Item = Option<Position>> {
let mut result: Vec<Option<Position>> = ::alloc::vec::Vec::new();
for entity_id in entity_ids {
result
.push(
self
.ctx()
.db()
.position()
.entity_id()
.find(entity_id.into().value()),
);
}
result.into_iter()
}
}
impl GetPositionRowOptionsByEntityId for spacetimedsl::DSL<'_> {}
Get Rows By Column
For any #[index] (field) on a table-struct, a get_rows_by_column DSL extension method is created, which performs a row().column().filter(column).
If the field is #[wrap], developers must provide a Wrapper Type or Row instead of an Column.
pub trait GetPositionRowsByXYZ: spacetimedsl::DSLContext {
#[doc=#comment]
fn get_positions_by_y<'a>(
&'a self,
y: &'a u128,
) -> impl Iterator<Item = Position> {
return self
.ctx()
.db()
.position()
.y()
.filter(y);
}
}
impl GetPositionRowsByXYZ for spacetimedsl::DSL<'_> {}
Get all Rows
For any table-struct, a get_all_rows DSL extension method is created, which performs a row().iter().
pub trait GetAllEntityRows: spacetimedsl::DSLContext {
fn get_all_entities<'a>(&'a self) -> impl Iterator<Item = Entity> {
return self.ctx().db().entity().iter();
}
}
impl GetAllEntityRows for spacetimedsl::DSL<'_> {}
pub trait GetAllIdentifierRows: spacetimedsl::DSLContext {
fn get_all_identifiers<'a>(&'a self) -> impl Iterator<Item = Identifier> {
return self.ctx().db().identifier().iter();
}
}
impl GetAllIdentifierRows for spacetimedsl::DSL<'_> {}
pub trait GetAllPositionRows: spacetimedsl::DSLContext {
fn get_all_positions<'a>(&'a self) -> impl Iterator<Item = Position> {
return self.ctx().db().position().iter();
}
}
impl GetAllPositionRows for spacetimedsl::DSL<'_> {}
Get count of Rows
For any table-struct, a get_count_of_rows DSL extension method is created, which performs a row().count().
pub trait GetCountOfEntityRows: spacetimedsl::DSLContext {
fn get_count_of_entities<'a>(&'a self) -> u64 {
return self.ctx().db().entity().count();
}
}
impl GetCountOfEntityRows for spacetimedsl::DSL<'_> {}
pub trait GetCountOfIdentifierRows: spacetimedsl::DSLContext {
fn get_count_of_identifiers<'a>(&'a self) -> u64 {
return self.ctx().db().identifier().count();
}
}
impl GetCountOfIdentifierRows for spacetimedsl::DSL<'_> {}
pub trait GetCountOfPositionRows: spacetimedsl::DSLContext {
fn get_count_of_positions<'a>(&'a self) -> u64 {
return self.ctx().db().position().count();
}
}
impl GetCountOfPositionRows for spacetimedsl::DSL<'_> {}
Update Row by Column
For any #[primary_key] and #[unique] field on a table-struct, a update_row_by_column DSL extension method is created, which performs a row().column().update(row).
If the table has a column with name modified_at, it's value is set to the current timestamp before updating.
If a table has only private (non-public) fields, no update method is generated. You can see that because for the Entity table, no one was generated, because its only field is a private field which is a primary key and auto inc and shouldn't change.
pub trait UpdateIdentifierRowById: spacetimedsl::DSLContext {
fn update_identifier_by_id(&self, mut identifier: Identifier) -> Identifier {
identifier.modified_at = self.ctx().timestamp;
return self.ctx().db().identifier().id().update(identifier);
}
}
impl UpdateIdentifierRowById for spacetimedsl::DSL<'_> {}
pub trait UpdateIdentifierRowByEntityId: spacetimedsl::DSLContext {
fn update_identifier_by_entity_id(
&self,
mut identifier: Identifier,
) -> Identifier {
identifier.modified_at = self.ctx().timestamp;
return self.ctx().db().identifier().entity_id().update(identifier);
}
}
impl UpdateIdentifierRowByEntityId for spacetimedsl::DSL<'_> {}
pub trait UpdateIdentifierRowByValue: spacetimedsl::DSLContext {
fn update_identifier_by_value(&self, mut identifier: Identifier) -> Identifier {
identifier.modified_at = self.ctx().timestamp;
return self.ctx().db().identifier().value().update(identifier);
}
}
impl UpdateIdentifierRowByValue for spacetimedsl::DSL<'_> {}
pub trait UpdatePositionRowById: spacetimedsl::DSLContext {
fn update_position_by_id(&self, mut position: Position) -> Position {
position.modified_at = self.ctx().timestamp;
return self.ctx().db().position().id().update(position);
}
}
impl UpdatePositionRowById for spacetimedsl::DSL<'_> {}
pub trait UpdatePositionRowByEntityId: spacetimedsl::DSLContext {
fn update_position_by_entity_id(&self, mut position: Position) -> Position {
position.modified_at = self.ctx().timestamp;
return self.ctx().db().position().entity_id().update(position);
}
}
impl UpdatePositionRowByEntityId for spacetimedsl::DSL<'_> {}
Delete Row by Column
For any #[primary_key] and #[unique] field on a table-struct, a delete_row_by_column DSL extension method is created, which performs a row().column().delete(column) (deletes one row).
If the field is #[wrap], developers must provide a Wrapper Type or Row instead of an Column.
pub trait DeleteEntityRowById: spacetimedsl::DSLContext {
fn delete_entity_by_id(&self, id: impl Into<EntityId>) -> bool {
return self.ctx().db().entity().id().delete(id.into().value());
}
}
impl DeleteEntityRowById for spacetimedsl::DSL<'_> {}
pub trait DeleteIdentifierRowById: spacetimedsl::DSLContext {
fn delete_identifier_by_id(&self, id: impl Into<IdentifierId>) -> bool {
return self.ctx().db().identifier().id().delete(id.into().value());
}
}
impl DeleteIdentifierRowById for spacetimedsl::DSL<'_> {}
pub trait DeleteIdentifierRowByEntityId: spacetimedsl::DSLContext {
fn delete_identifier_by_entity_id(
&self,
entity_id: impl Into<crate::entity::EntityId>,
) -> bool {
return self
.ctx()
.db()
.identifier()
.entity_id()
.delete(entity_id.into().value());
}
}
impl DeleteIdentifierRowByEntityId for spacetimedsl::DSL<'_> {}
pub trait DeleteIdentifierRowByValue: spacetimedsl::DSLContext {
fn delete_identifier_by_value(&self, value: String) -> bool {
return self.ctx().db().identifier().value().delete(value);
}
}
impl DeleteIdentifierRowByValue for spacetimedsl::DSL<'_> {}
pub trait DeletePositionRowById: spacetimedsl::DSLContext {
fn delete_position_by_id(&self, id: impl Into<PositionId>) -> bool {
return self.ctx().db().position().id().delete(id.into().value());
}
}
impl DeletePositionRowById for spacetimedsl::DSL<'_> {}
pub trait DeletePositionRowByEntityId: spacetimedsl::DSLContext {
fn delete_position_by_entity_id(
&self,
entity_id: impl Into<crate::entity::EntityId>,
) -> bool {
return self
.ctx()
.db()
.position()
.entity_id()
.delete(entity_id.into().value());
}
}
impl DeletePositionRowByEntityId for spacetimedsl::DSL<'_> {}
Delete Rows by Column
For any #[index] (field) on a table-struct, a delete_rows_by_columns DSL extension method is created, which performs a row().column().delete(column) (deletes many rows).
If the field is #[wrap], developers must provide a Wrapper Type or Row instead of an Column.
pub trait DeletePositionRowsByY: spacetimedsl::DSLContext {
fn delete_positions_by_y(
&self,
y: &'a u128,
) -> u64 {
return self
.ctx()
.db()
.position()
.y()
.delete(y);
}
}
impl DeletePositionRowsByY for spacetimedsl::DSL<'_> {}
The implementation is the same as the Delete Row By Column method, except that it returns a u64 instead of a bool.
Licensing
SpacetimeDSL is dual-licensed under both the MIT License and the Apache License (Version 2.0) and therefore Open Source.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.