Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring over Rust module doc fixes from the C# module docs #2360

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 28 additions & 13 deletions crates/bindings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
but you need to use `./bindings-doctests.sh` to actually test them.
-->

[SpacetimeDB](https://spacetimedb.com/) allows using the Rust language to write server-side applications called **modules**. Modules run inside a relational database. They have direct access to database tables, and expose public functions called **reducers** that can be invoked over the network. Clients connect directly to the database to read data.
[SpacetimeDB](https://spacetimedb.com/) allows using the Rust language to write server-side applications called **modules**. Modules, which run inside a relational database, have direct access to database tables, and expose public functions called **reducers** that can be invoked over the network. Clients connect directly to the database to read data.

```text
Client Application SpacetimeDB
Expand All @@ -40,7 +40,7 @@

Rust modules are written with the the Rust Module Library (this crate). They are built using [cargo](https://doc.rust-lang.org/cargo/) and deployed using the [`spacetime` CLI tool](https://spacetimedb.com/install). Rust modules can import any Rust [crate](https://crates.io/) that supports being compiled to WebAssembly.

(Note: Rust can also be used to write **clients** of SpacetimeDB databases, but this requires using a completely different library, the SpacetimeDB Rust Client SDK. See the documentation on [clients] for more information.)
(Note: Rust can also be used to write **clients** of SpacetimeDB databases, but this requires using a different library, the SpacetimeDB Rust Client SDK. See the documentation on [clients] for more information.)

This reference assumes you are familiar with the basics of Rust. If you aren't, check out Rust's [excellent documentation](https://www.rust-lang.org/learn). For a guided introduction to Rust Modules, see the [Rust Module Quickstart](https://spacetimedb.com/docs/modules/rust/quickstart).

Expand Down Expand Up @@ -75,7 +75,7 @@ fn add_person(ctx: &ReducerContext, id: u32, name: String) {
```


Note that reducers don't return data directly; they can only modify the database. Clients connect directly to the database and use SQL to query [public](#public-and-private-tables) tables. Clients can also open subscriptions to receive streaming updates as the results of a SQL query change.
Note that reducers don't return data directly; they can only modify the database. Clients connect directly to the database and use SQL to query [public](#public-and-private-tables) tables. Clients can also subscribe to a set of rows using SQL queries and receive streaming updates whenever any of those rows change.

Tables and reducers in Rust modules can use any type that implements the [`SpacetimeType`] trait.

Expand Down Expand Up @@ -184,7 +184,7 @@ For example:
spacetime publish silly_demo_app
```

When you publish your module, a database named will be created with the requested tables, and the module will be installed inside it.
When you publish your module, a database named `silly_demo_app` will be created with the requested tables, and the module will be installed inside it.

The output of `spacetime publish` will end with a line:
```text
Expand Down Expand Up @@ -257,8 +257,8 @@ fn do_nothing() {
drop(person);
}

// To interact with the database, you need a `ReducerContext`.
// The first argument of a reducer is always a `ReducerContext`.
// To interact with the database, you need a `ReducerContext`,
// which is provided as the first parameter of any reducer.
#[reducer]
fn do_something(ctx: &ReducerContext) {
// `ctx.db.{table_name}()` gets a handle to a database table.
Expand Down Expand Up @@ -382,7 +382,7 @@ ctx.db.person().ssn()

Notice that updating a row is only possible if a row has a unique column -- there is no `update` method in the base [`Table`] trait. SpacetimeDB has no notion of rows having an "identity" aside from their unique / primary keys.

The `#[primary_key]` annotation is similar to the `#[unique]` annotation, except that it leads to additional methods being made available in the [client]-side SDKs.
The `#[primary_key]` annotation implies `#[unique]` annotation, but avails additional methods in the [client]-side SDKs.

It is not currently possible to mark a group of fields as collectively unique.

Expand Down Expand Up @@ -433,7 +433,6 @@ For example:

```no_run
# #[cfg(target_arch = "wasm32")] mod demo {

use spacetimedb::table;

#[table(name = paper, index(name = url_and_country, btree(columns = [url, country])))]
Expand All @@ -454,6 +453,23 @@ Single-column indexes can also be declared using the

column attribute.

For example:

```no_run
# #[cfg(target_arch = "wasm32")] mod demo {
use spacetimedb::table;

#[table(name = paper)]
struct Paper {
url: String,
country: String,
#[index(btree)]
venue: String
}
# }
```


Any index supports getting a [`RangedIndex`] using [`ctx`](crate::ReducerContext)`.db.{table}().{index}()`. For example, `ctx.db.person().name()`.

[`RangedIndex`] provides:
Expand Down Expand Up @@ -483,11 +499,11 @@ fn give_player_item(
# }
```

Every reducer runs inside a [database transaction](https://en.wikipedia.org/wiki/Database_transaction). <!-- TODO: specific transaction level guarantees. --> This means that reducers will not observe the effects of other reducers modifying the database while they run. Also, if a reducer fails, all of its changes to the database will automatically be rolled back. Reducers can fail by [panicking](::std::panic!) or by returning an `Err`.
Every reducer runs inside a [database transaction](https://en.wikipedia.org/wiki/Database_transaction). <!-- TODO: specific transaction level guarantees. --> This means that reducers will not observe the effects of other reducers modifying the database while they run. If a reducer fails, all of its changes to the database will automatically be rolled back. Reducers can fail by [panicking](::std::panic!) or by returning an `Err`.

#### The `ReducerContext` Type

Reducers have access to a special [`ReducerContext`] argument. This argument allows reading and writing the database attached to a module. It also provides some additional functionality, like generating random numbers and scheduling future operations.
Reducers have access to a special [`ReducerContext`] parameter. This parameter allows reading and writing the database attached to a module. It also provides some additional functionality, like generating random numbers and scheduling future operations.

[`ReducerContext`] provides access to the database tables via [the `.db` field](ReducerContext#structfield.db). The [`#[table]`](macro@crate::table) macro generates traits that add accessor methods to this field.

Expand Down Expand Up @@ -516,14 +532,13 @@ the database and respond to client connections. See [Lifecycle Reducers](macro@c

#### Scheduled Reducers

Reducers can be scheduled to run repeatedly. This can be used to implement timers, game loops, and
maintenance tasks. See [Scheduled Reducers](macro@crate::reducer#scheduled-reducers).
Reducers can schedule other reducers to run asynchronously. This allows calling the reducers at a particular time, or at repeating intervals. This can be used to implement timers, game loops, and maintenance tasks. See [Scheduled Reducers](macro@crate::reducer#scheduled-reducers).

## Automatic migrations

When you `spacetime publish` a module that has already been published using `spacetime publish <DATABASE_NAME_OR_IDENTITY>`,
SpacetimeDB attempts to automatically migrate your existing database to the new schema. (The "schema" is just the collection
of tables and reducers you've declared in your code, together with the types they depend on.) This form of migration is very limited and only supports a few kinds of changes.
of tables and reducers you've declared in your code, together with the types they depend on.) This form of migration is limited and only supports a few kinds of changes.
On the plus side, automatic migrations usually don't break clients. The situations that may break clients are documented below.

The following changes are always allowed and never breaking:
Expand Down
40 changes: 25 additions & 15 deletions crates/bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,28 +548,15 @@ pub use spacetimedb_bindings_macro::table;
/// This allows calling the reducers at a particular time, or in a loop.
/// This can be used for game loops.
///
/// Scheduled reducers are normal reducers, and may still be called by clients.
/// If a scheduled reducer should only be called by the scheduler,
/// consider beginning it with a check that the caller `Identity` is the module:
///
/// ```no_run
/// # #[cfg(target_arch = "wasm32")] mod demo {
/// #[reducer]
/// fn scheduled(ctx: &ReducerContext, args: ScheduledArgs) -> Result<(), String> {
/// if ctx.sender != ctx.identity() {
/// return Err("Reducer `scheduled` may not be invoked by clients, only via scheduling.");
/// }
/// // Reducer body...
/// }
/// # }
/// ```

///
/// The scheduling information for a reducer is stored in a table.
/// This table has two mandatory fields:
/// - A primary key that identifies scheduled reducer calls.
/// - A [`ScheduleAt`] field that says when to call the reducer.
///
/// Managing timers with a scheduled table is as simple as inserting or deleting rows from the table.
/// This makes scheduling transactional in SpacetimeDB. If a reducer A first schedules B but then errors for some other reason, B will not be scheduled to run.
///
/// A [`ScheduleAt`] can be created from a [`spacetimedb::Timestamp`](crate::Timestamp), in which case the reducer will be scheduled once,
/// or from a [`std::time::Duration`], in which case the reducer will be scheduled in a loop. In either case the conversion can be performed using [`Into::into`].
Expand Down Expand Up @@ -652,6 +639,29 @@ pub use spacetimedb_bindings_macro::table;
/// Scheduled reducers are called on a best-effort basis and may be slightly delayed in their execution
/// when a database is under heavy load.
///
/// ### Restricting scheduled reducers
///
/// Scheduled reducers are normal reducers, and may still be called by clients.
/// If a scheduled reducer should only be called by the scheduler,
/// consider beginning it with a check that the caller `Identity` is the module:
///
/// ```no_run
/// # #[cfg(target_arch = "wasm32")] mod demo {
/// use spacetimedb::{reducer, ReducerContext};
///
/// # #[derive(spacetimedb::SpacetimeType)] struct ScheduledArgs {}
///
/// #[reducer]
/// fn scheduled(ctx: &ReducerContext, args: ScheduledArgs) -> Result<(), String> {
/// if ctx.sender != ctx.identity() {
/// return Err("Reducer `scheduled` may not be invoked by clients, only via scheduling.".into());
/// }
/// // Reducer body...
/// # Ok(())
/// }
/// # }
/// ```
///
/// <!-- TODO: SLAs? -->
///
/// [`&ReducerContext`]: `ReducerContext`
Expand Down
9 changes: 6 additions & 3 deletions crates/bindings/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@ pub trait Table: TableInternal {
///
/// This takes into account modifications by the current transaction,
/// even though those modifications have not yet been committed or broadcast to clients.
/// This applies generally to insertions, deletions, updates, and iteration as well.
fn count(&self) -> u64 {
sys::datastore_table_row_count(Self::table_id()).expect("datastore_table_row_count() call failed")
}

/// Iterate over all rows of the table.
///
/// For large tables, this can be a very slow operation!
/// For large tables, this can be a slow operation!
/// Prefer [filtering](RangedIndex::filter) a [`RangedIndex`] or [finding](UniqueColumn::find) a [`UniqueColumn`] if
/// possible.
///
/// (This keeps track of changes made to the table since the start of this reducer invocation. For example, if rows have been deleted since the start of this reducer invocation, those rows will not be returned by `iter`. Similarly, inserted rows WILL be returned.)
#[inline]
fn iter(&self) -> impl Iterator<Item = Self::Row> {
let table_id = Self::table_id();
Expand Down Expand Up @@ -373,10 +376,10 @@ impl<Tbl: Table, Col: Index + Column<Table = Tbl>> UniqueColumn<Tbl, Col::ColTyp
(n_del > 0, args.data)
}

/// Deletes the row where the value in the unique column matches that in the corresponding field of `new_row`,
/// Deletes the row where the value in the unique column matches that in the corresponding field of `new_row`, and
/// then inserts the `new_row`.
///
/// Returns the new row as actually inserted, with any auto-inc placeholders substituted for computed values.
/// Returns the new row as actually inserted, with computed values substituted for any auto-inc placeholders.
///
/// # Panics
/// Panics if no row was previously present with the matching value in the unique column,
Expand Down
Loading