mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-22 07:32:16 -04:00
4f297880ca
Signed-off-by: Mazdak Farrokhzad <twingoow@gmail.com> Co-authored-by: Phoebe Goldman <phoebe@clockworklabs.io> Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
1021 lines
42 KiB
Rust
1021 lines
42 KiB
Rust
//! Defines sys calls to interact with SpacetimeDB.
|
|
//! This forms an ABI of sorts that modules written in Rust can use.
|
|
|
|
extern crate alloc;
|
|
|
|
use core::fmt;
|
|
use core::mem::MaybeUninit;
|
|
use core::num::NonZeroU16;
|
|
use std::ptr;
|
|
|
|
use spacetimedb_primitives::{errno, errnos, ColId, IndexId, TableId};
|
|
|
|
/// Provides a raw set of sys calls which abstractions can be built atop of.
|
|
pub mod raw {
|
|
use spacetimedb_primitives::{ColId, IndexId, TableId};
|
|
|
|
// this module identifier determines the abi version that modules built with this crate depend
|
|
// on. Any non-breaking additions to the abi surface should be put in a new `extern {}` block
|
|
// with a module identifier with a minor version 1 above the previous highest minor version.
|
|
// For breaking changes, all functions should be moved into one new `spacetime_X.0` block.
|
|
#[link(wasm_import_module = "spacetime_10.0")]
|
|
extern "C" {
|
|
/// Queries the `table_id` associated with the given (table) `name`
|
|
/// where `name` is the UTF-8 slice in WASM memory at `name_ptr[..name_len]`.
|
|
///
|
|
/// The table id is written into the `out` pointer.
|
|
///
|
|
/// # Traps
|
|
///
|
|
/// Traps if:
|
|
/// - `name_ptr` is NULL or `name` is not in bounds of WASM memory.
|
|
/// - `name` is not valid UTF-8.
|
|
/// - `out` is NULL or `out[..size_of::<TableId>()]` is not in bounds of WASM memory.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_TABLE`, when `name` is not the name of a table.
|
|
pub fn _table_id_from_name(name: *const u8, name_len: usize, out: *mut TableId) -> u16;
|
|
|
|
/// Queries the `index_id` associated with the given (index) `name`
|
|
/// where `name` is the UTF-8 slice in WASM memory at `name_ptr[..name_len]`.
|
|
///
|
|
/// The index id is written into the `out` pointer.
|
|
///
|
|
/// # Traps
|
|
///
|
|
/// Traps if:
|
|
/// - `name_ptr` is NULL or `name` is not in bounds of WASM memory.
|
|
/// - `name` is not valid UTF-8.
|
|
/// - `out` is NULL or `out[..size_of::<IndexId>()]` is not in bounds of WASM memory.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_INDEX`, when `name` is not the name of an index.
|
|
pub fn _index_id_from_name(name_ptr: *const u8, name_len: usize, out: *mut IndexId) -> u16;
|
|
|
|
/// Writes the number of rows currently in table identified by `table_id` to `out`.
|
|
///
|
|
/// # Traps
|
|
///
|
|
/// Traps if:
|
|
/// - `out` is NULL or `out[..size_of::<u64>()]` is not in bounds of WASM memory.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
|
|
pub fn _datastore_table_row_count(table_id: TableId, out: *mut u64) -> u16;
|
|
|
|
/// Starts iteration on each row, as BSATN-encoded, of a table identified by `table_id`.
|
|
///
|
|
/// On success, the iterator handle is written to the `out` pointer.
|
|
/// This handle can be advanced by [`row_iter_bsatn_advance`].
|
|
///
|
|
/// # Traps
|
|
///
|
|
/// This function does not trap.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
|
|
pub fn _datastore_table_scan_bsatn(table_id: TableId, out: *mut RowIter) -> u16;
|
|
|
|
/// Finds all rows in the index identified by `index_id`,
|
|
/// according to the:
|
|
/// - `prefix = prefix_ptr[..prefix_len]`,
|
|
/// - `rstart = rstart_ptr[..rstart_len]`,
|
|
/// - `rend = rend_ptr[..rend_len]`,
|
|
/// in WASM memory.
|
|
///
|
|
/// The index itself has a schema/type.
|
|
/// The `prefix` is decoded to the initial `prefix_elems` `AlgebraicType`s
|
|
/// whereas `rstart` and `rend` are decoded to the `prefix_elems + 1` `AlgebraicType`
|
|
/// where the `AlgebraicValue`s are wrapped in `Bound`.
|
|
/// That is, `rstart, rend` are BSATN-encoded `Bound<AlgebraicValue>`s.
|
|
///
|
|
/// Matching is then defined by equating `prefix`
|
|
/// to the initial `prefix_elems` columns of the index
|
|
/// and then imposing `rstart` as the starting bound
|
|
/// and `rend` as the ending bound on the `prefix_elems + 1` column of the index.
|
|
/// Remaining columns of the index are then unbounded.
|
|
/// Note that the `prefix` in this case can be empty (`prefix_elems = 0`),
|
|
/// in which case this becomes a ranged index scan on a single-col index
|
|
/// or even a full table scan if `rstart` and `rend` are both unbounded.
|
|
///
|
|
/// The relevant table for the index is found implicitly via the `index_id`,
|
|
/// which is unique for the module.
|
|
///
|
|
/// On success, the iterator handle is written to the `out` pointer.
|
|
/// This handle can be advanced by [`row_iter_bsatn_advance`].
|
|
///
|
|
/// # Non-obvious queries
|
|
///
|
|
/// For an index on columns `[a, b, c]`:
|
|
///
|
|
/// - `a = x, b = y` is encoded as a prefix `[x, y]`
|
|
/// and a range `Range::Unbounded`,
|
|
/// or as a prefix `[x]` and a range `rstart = rend = Range::Inclusive(y)`.
|
|
/// - `a = x, b = y, c = z` is encoded as a prefix `[x, y]`
|
|
/// and a range `rstart = rend = Range::Inclusive(z)`.
|
|
/// - A sorted full scan is encoded as an empty prefix
|
|
/// and a range `Range::Unbounded`.
|
|
///
|
|
/// # Traps
|
|
///
|
|
/// Traps if:
|
|
/// - `prefix_elems > 0`
|
|
/// and (`prefix_ptr` is NULL or `prefix` is not in bounds of WASM memory).
|
|
/// - `rstart` is NULL or `rstart` is not in bounds of WASM memory.
|
|
/// - `rend` is NULL or `rend` is not in bounds of WASM memory.
|
|
/// - `out` is NULL or `out[..size_of::<RowIter>()]` is not in bounds of WASM memory.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
|
|
/// - `WRONG_INDEX_ALGO` if the index is not a btree index.
|
|
/// - `BSATN_DECODE_ERROR`, when `prefix` cannot be decoded to
|
|
/// a `prefix_elems` number of `AlgebraicValue`
|
|
/// typed at the initial `prefix_elems` `AlgebraicType`s of the index's key type.
|
|
/// Or when `rstart` or `rend` cannot be decoded to an `Bound<AlgebraicValue>`
|
|
/// where the inner `AlgebraicValue`s are
|
|
/// typed at the `prefix_elems + 1` `AlgebraicType` of the index's key type.
|
|
pub fn _datastore_btree_scan_bsatn(
|
|
index_id: IndexId,
|
|
prefix_ptr: *const u8,
|
|
prefix_len: usize,
|
|
prefix_elems: ColId,
|
|
rstart_ptr: *const u8, // Bound<AlgebraicValue>
|
|
rstart_len: usize,
|
|
rend_ptr: *const u8, // Bound<AlgebraicValue>
|
|
rend_len: usize,
|
|
out: *mut RowIter,
|
|
) -> u16;
|
|
|
|
/// Finds all rows in the table identified by `table_id`,
|
|
/// where the row has a column, identified by `col_id`,
|
|
/// with data matching the byte string, in WASM memory, pointed to at by `val`.
|
|
///
|
|
/// Matching is defined BSATN-decoding `val` to an `AlgebraicValue`
|
|
/// according to the column's schema and then `Ord for AlgebraicValue`.
|
|
///
|
|
/// On success, the iterator handle is written to the `out` pointer.
|
|
///
|
|
/// Returns an error if
|
|
/// - a table with the provided `table_id` doesn't exist
|
|
/// - `col_id` does not identify a column of the table,
|
|
/// - `(val, val_len)` cannot be decoded to an `AlgebraicValue`
|
|
/// typed at the `AlgebraicType` of the column,
|
|
/// - `val + val_len` overflows a 64-bit integer
|
|
pub fn _iter_by_col_eq(
|
|
table_id: TableId,
|
|
col_id: ColId,
|
|
val: *const u8,
|
|
val_len: usize,
|
|
out: *mut RowIter,
|
|
) -> u16;
|
|
|
|
/// Deletes all rows in the table identified by `table_id`
|
|
/// where the column identified by `col_id` matches the byte string,
|
|
/// in WASM memory, pointed to at by `value`.
|
|
///
|
|
/// Matching is defined by BSATN-decoding `value` to an `AlgebraicValue`
|
|
/// according to the column's schema and then `Ord for AlgebraicValue`.
|
|
///
|
|
/// The number of rows deleted is written to the WASM pointer `out`.
|
|
///
|
|
/// Returns an error if
|
|
/// - a table with the provided `table_id` doesn't exist
|
|
/// - no columns were deleted
|
|
/// - `col_id` does not identify a column of the table,
|
|
/// - `(value, value_len)` doesn't decode from BSATN to an `AlgebraicValue`
|
|
/// according to the `AlgebraicType` that the table's schema specifies for `col_id`.
|
|
/// - `value + value_len` overflows a 64-bit integer
|
|
/// - writing to `out` would overflow a 32-bit integer
|
|
pub fn _delete_by_col_eq(
|
|
table_id: TableId,
|
|
col_id: ColId,
|
|
value: *const u8,
|
|
value_len: usize,
|
|
out: *mut u32,
|
|
) -> u16;
|
|
|
|
/// Deletes those rows, in the table identified by `table_id`,
|
|
/// that match any row in the byte string `rel = rel_ptr[..rel_len]` in WASM memory.
|
|
///
|
|
/// Matching is defined by first BSATN-decoding
|
|
/// the byte string pointed to at by `relation` to a `Vec<ProductValue>`
|
|
/// according to the row schema of the table
|
|
/// and then using `Ord for AlgebraicValue`.
|
|
/// A match happens when `Ordering::Equal` is returned from `fn cmp`.
|
|
/// This occurs exactly when the row's BSATN-encoding is equal to the encoding of the `ProductValue`.
|
|
///
|
|
/// The number of rows deleted is written to the WASM pointer `out`.
|
|
///
|
|
/// # Traps
|
|
///
|
|
/// Traps if:
|
|
/// - `rel_ptr` is NULL or `rel` is not in bounds of WASM memory.
|
|
/// - `out` is NULL or `out[..size_of::<u32>()]` is not in bounds of WASM memory.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
|
|
/// - `BSATN_DECODE_ERROR`, when `rel` cannot be decoded to `Vec<ProductValue>`
|
|
/// where each `ProductValue` is typed at the `ProductType` the table's schema specifies.
|
|
pub fn _datastore_delete_all_by_eq_bsatn(
|
|
table_id: TableId,
|
|
rel_ptr: *const u8,
|
|
rel_len: usize,
|
|
out: *mut u32,
|
|
) -> u16;
|
|
|
|
/// Like [`_datastore_table_scan_bsatn`], start iteration on each row,
|
|
/// as bytes, of a table identified by `table_id`.
|
|
///
|
|
/// The rows are filtered through `filter`, which is read from WASM memory
|
|
/// and is encoded in the embedded language defined by `spacetimedb_lib::filter::Expr`.
|
|
///
|
|
/// The iterator is registered in the host environment
|
|
/// under an assigned index which is written to the `out` pointer provided.
|
|
///
|
|
/// Returns an error if
|
|
/// - a table with the provided `table_id` doesn't exist
|
|
/// - `(filter, filter_len)` doesn't decode to a filter expression
|
|
/// - `filter + filter_len` overflows a 64-bit integer
|
|
pub fn _iter_start_filtered(table_id: TableId, filter: *const u8, filter_len: usize, out: *mut RowIter) -> u16;
|
|
|
|
/// Reads rows from the given iterator registered under `iter`.
|
|
///
|
|
/// Takes rows from the iterator
|
|
/// and stores them in the memory pointed to by `buffer = buffer_ptr[..buffer_len]`,
|
|
/// encoded in BSATN format.
|
|
///
|
|
/// The `buffer_len = buffer_len_ptr[..size_of::<usize>()]` stores the capacity of `buffer`.
|
|
/// On success (`0` or `-1` is returned),
|
|
/// `buffer_len` is set to the combined length of the encoded rows.
|
|
/// When `-1` is returned, the iterator has been exhausted
|
|
/// and there are no more rows to read,
|
|
/// leading to the iterator being immediately destroyed.
|
|
/// Note that the host is free to reuse allocations in a pool,
|
|
/// destroying the handle logically does not entail that memory is necessarily reclaimed.
|
|
///
|
|
/// # Traps
|
|
///
|
|
/// Traps if:
|
|
///
|
|
/// - `buffer_len_ptr` is NULL or `buffer_len` is not in bounds of WASM memory.
|
|
/// - `buffer_ptr` is NULL or `buffer` is not in bounds of WASM memory.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NO_SUCH_ITER`, when `iter` is not a valid iterator.
|
|
/// - `BUFFER_TOO_SMALL`, when there are rows left but they cannot fit in `buffer`.
|
|
/// When this occurs, `buffer_len` is set to the size of the next item in the iterator.
|
|
/// To make progress, the caller should reallocate the buffer to at least that size and try again.
|
|
pub fn _row_iter_bsatn_advance(iter: RowIter, buffer_ptr: *mut u8, buffer_len_ptr: *mut usize) -> i16;
|
|
|
|
/// Destroys the iterator registered under `iter`.
|
|
///
|
|
/// Once `row_iter_bsatn_close` is called on `iter`, the `iter` is invalid.
|
|
/// That is, `row_iter_bsatn_close(iter)` the second time will yield `NO_SUCH_ITER`.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NO_SUCH_ITER`, when `iter` is not a valid iterator.
|
|
pub fn _row_iter_bsatn_close(iter: RowIter) -> u16;
|
|
|
|
/// Inserts a row into the table identified by `table_id`,
|
|
/// where the row is read from the byte string `row = row_ptr[..row_len]` in WASM memory
|
|
/// where `row_len = row_len_ptr[..size_of::<usize>()]` stores the capacity of `row`.
|
|
///
|
|
/// The byte string `row` must be a BSATN-encoded `ProductValue`
|
|
/// typed at the table's `ProductType` row-schema.
|
|
///
|
|
/// To handle auto-incrementing columns,
|
|
/// when the call is successful,
|
|
/// the `row` is written back to with the generated sequence values.
|
|
/// These values are written as a BSATN-encoded `pv: ProductValue`.
|
|
/// Each `v: AlgebraicValue` in `pv` is typed at the sequence's column type.
|
|
/// The `v`s in `pv` are ordered by the order of the columns, in the schema of the table.
|
|
/// When the table has no sequences,
|
|
/// this implies that the `pv`, and thus `row`, will be empty.
|
|
/// The `row_len` is set to the length of `bsatn(pv)`.
|
|
///
|
|
/// # Traps
|
|
///
|
|
/// Traps if:
|
|
/// - `row_len_ptr` is NULL or `row_len` is not in bounds of WASM memory.
|
|
/// - `row_ptr` is NULL or `row` is not in bounds of WASM memory.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
|
|
/// - `BSATN_DECODE_ERROR`, when `row` cannot be decoded to a `ProductValue`.
|
|
/// typed at the `ProductType` the table's schema specifies.
|
|
/// - `UNIQUE_ALREADY_EXISTS`, when inserting `row` would violate a unique constraint.
|
|
/// - `SCHEDULE_AT_DELAY_TOO_LONG`, when the delay specified in the row was too long.
|
|
pub fn _datastore_insert_bsatn(table_id: TableId, row_ptr: *mut u8, row_len_ptr: *mut usize) -> u16;
|
|
|
|
/// Schedules a reducer to be called asynchronously, nonatomically,
|
|
/// and immediately on a best effort basis.
|
|
///
|
|
/// The reducer is named as the valid UTF-8 slice `(name, name_len)`,
|
|
/// and is passed the slice `(args, args_len)` as its argument.
|
|
///
|
|
/// Traps if
|
|
/// - `name` does not point to valid UTF-8
|
|
/// - `name + name_len` or `args + args_len` overflow a 64-bit integer
|
|
#[cfg(feature = "unstable_abi")]
|
|
pub fn _volatile_nonatomic_schedule_immediate(
|
|
name: *const u8,
|
|
name_len: usize,
|
|
args: *const u8,
|
|
args_len: usize,
|
|
);
|
|
|
|
/// Writes up to `buffer_len` bytes from `buffer = buffer_ptr[..buffer_len]`,
|
|
/// to the `sink`, registered in the host environment.
|
|
///
|
|
/// The `buffer_len = buffer_len_ptr[..size_of::<usize>()]` stores the capacity of `buffer`.
|
|
/// On success (`0` is returned),
|
|
/// `buffer_len` is set to the number of bytes written to `sink`.
|
|
///
|
|
/// # Traps
|
|
///
|
|
/// - `buffer_len_ptr` is NULL or `buffer_len` is not in bounds of WASM memory.
|
|
/// - `buffer_ptr` is NULL or `buffer` is not in bounds of WASM memory.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NO_SUCH_BYTES`, when `sink` is not a valid bytes sink.
|
|
/// - `NO_SPACE`, when there is no room for more bytes in `sink`.
|
|
pub fn _bytes_sink_write(sink: BytesSink, buffer_ptr: *const u8, buffer_len_ptr: *mut usize) -> u16;
|
|
|
|
/// Reads bytes from `source`, registered in the host environment,
|
|
/// and stores them in the memory pointed to by `buffer = buffer_ptr[..buffer_len]`.
|
|
///
|
|
/// The `buffer_len = buffer_len_ptr[..size_of::<usize>()]` stores the capacity of `buffer`.
|
|
/// On success (`0` or `-1` is returned),
|
|
/// `buffer_len` is set to the number of bytes written to `buffer`.
|
|
/// When `-1` is returned, the resource has been exhausted
|
|
/// and there are no more bytes to read,
|
|
/// leading to the resource being immediately destroyed.
|
|
/// Note that the host is free to reuse allocations in a pool,
|
|
/// destroying the handle logically does not entail that memory is necessarily reclaimed.
|
|
///
|
|
/// # Traps
|
|
///
|
|
/// Traps if:
|
|
///
|
|
/// - `buffer_len_ptr` is NULL or `buffer_len` is not in bounds of WASM memory.
|
|
/// - `buffer_ptr` is NULL or `buffer` is not in bounds of WASM memory.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NO_SUCH_BYTES`, when `source` is not a valid bytes source.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// The typical use case for this ABI is in `__call_reducer__`,
|
|
/// to read and deserialize the `args`.
|
|
/// An example definition, dealing with `args` might be:
|
|
/// ```rust,ignore
|
|
/// /// #[no_mangle]
|
|
/// extern "C" fn __call_reducer__(..., args: BytesSource, ...) -> i16 {
|
|
/// // ...
|
|
///
|
|
/// let mut buf = Vec::<u8>::with_capacity(1024);
|
|
/// loop {
|
|
/// // Write into the spare capacity of the buffer.
|
|
/// let buf_ptr = buf.spare_capacity_mut();
|
|
/// let spare_len = buf_ptr.len();
|
|
/// let mut buf_len = buf_ptr.len();
|
|
/// let buf_ptr = buf_ptr.as_mut_ptr().cast();
|
|
/// let ret = unsafe { bytes_source_read(args, buf_ptr, &mut buf_len) };
|
|
/// // SAFETY: `bytes_source_read` just appended `spare_len` bytes to `buf`.
|
|
/// unsafe { buf.set_len(buf.len() + spare_len) };
|
|
/// match ret {
|
|
/// // Host side source exhausted, we're done.
|
|
/// -1 => break,
|
|
/// // Wrote the entire spare capacity.
|
|
/// // Need to reserve more space in the buffer.
|
|
/// 0 if spare_len == buf_len => buf.reserve(1024),
|
|
/// // Host didn't write as much as possible.
|
|
/// // Try to read some more.
|
|
/// // The host will likely not trigger this branch,
|
|
/// // but a module should be prepared for it.
|
|
/// 0 => {}
|
|
/// _ => unreachable!(),
|
|
/// }
|
|
/// }
|
|
///
|
|
/// // ...
|
|
/// }
|
|
/// ```
|
|
pub fn _bytes_source_read(source: BytesSource, buffer_ptr: *mut u8, buffer_len_ptr: *mut usize) -> i16;
|
|
|
|
/// Logs at `level` a `message` message occuring in `filename:line_number`
|
|
/// with [`target`](target) being the module path at the `log!` invocation site.
|
|
///
|
|
/// These various pointers are interpreted lossily as UTF-8 strings with a corresponding `_len`.
|
|
///
|
|
/// The `target` and `filename` pointers are ignored by passing `NULL`.
|
|
/// The line number is ignored if `line_number == u32::MAX`.
|
|
///
|
|
/// No message is logged if
|
|
/// - `target != NULL && target + target_len > u64::MAX`
|
|
/// - `filename != NULL && filename + filename_len > u64::MAX`
|
|
/// - `message + message_len > u64::MAX`
|
|
///
|
|
/// # Traps
|
|
///
|
|
/// Traps if:
|
|
/// - `target` is not NULL and `target_ptr[..target_len]` is not in bounds of WASM memory.
|
|
/// - `filename` is not NULL and `filename_ptr[..filename_len]` is not in bounds of WASM memory.
|
|
/// - `message` is not NULL and `message_ptr[..message_len]` is not in bounds of WASM memory.
|
|
///
|
|
/// [target]: https://docs.rs/log/latest/log/struct.Record.html#method.target
|
|
pub fn _console_log(
|
|
level: u8,
|
|
target_ptr: *const u8,
|
|
target_len: usize,
|
|
filename_ptr: *const u8,
|
|
filename_len: usize,
|
|
line_number: u32,
|
|
message_ptr: *const u8,
|
|
message_len: usize,
|
|
);
|
|
|
|
/// Begins a timing span with `name = name_ptr[..name_len]`.
|
|
///
|
|
/// When the returned `ConsoleTimerId` is passed to [`console_timer_end`],
|
|
/// the duration between the calls will be printed to the module's logs.
|
|
///
|
|
/// The `name` is interpreted lossily as UTF-8.
|
|
///
|
|
/// # Traps
|
|
///
|
|
/// Traps if:
|
|
/// - `name_ptr` is NULL or `name` is not in bounds of WASM memory.
|
|
pub fn _console_timer_start(name_ptr: *const u8, name_len: usize) -> u32;
|
|
|
|
/// End a timing span.
|
|
///
|
|
/// The `timer_id` must be the result of a call to `console_timer_start`.
|
|
/// The duration between the two calls will be computed and printed to the module's logs.
|
|
/// Once `console_timer_end` is called on `id: ConsoleTimerId`, the `id` is invalid.
|
|
/// That is, `console_timer_end(id)` the second time will yield `NO_SUCH_CONSOLE_TIMER`.
|
|
///
|
|
/// Note that the host is free to reuse allocations in a pool,
|
|
/// destroying the handle logically does not entail that memory is necessarily reclaimed.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
/// - `NO_SUCH_CONSOLE_TIMER`, when `timer_id` does not exist.
|
|
pub fn _console_timer_end(timer_id: u32) -> u16;
|
|
}
|
|
|
|
/// What strategy does the database index use?
|
|
///
|
|
/// See also: https://www.postgresql.org/docs/current/sql-createindex.html
|
|
#[repr(u8)]
|
|
#[non_exhaustive]
|
|
pub enum IndexType {
|
|
/// Indexing works by putting the index key into a b-tree.
|
|
BTree = 0,
|
|
/// Indexing works by hashing the index key.
|
|
Hash = 1,
|
|
}
|
|
|
|
/// The error log level. See [`_console_log`].
|
|
pub const LOG_LEVEL_ERROR: u8 = 0;
|
|
/// The warn log level. See [`_console_log`].
|
|
pub const LOG_LEVEL_WARN: u8 = 1;
|
|
/// The info log level. See [`_console_log`].
|
|
pub const LOG_LEVEL_INFO: u8 = 2;
|
|
/// The debug log level. See [`_console_log`].
|
|
pub const LOG_LEVEL_DEBUG: u8 = 3;
|
|
/// The trace log level. See [`_console_log`].
|
|
pub const LOG_LEVEL_TRACE: u8 = 4;
|
|
/// The panic log level. See [`_console_log`].
|
|
///
|
|
/// A panic level is emitted just before a fatal error causes the WASM module to trap.
|
|
pub const LOG_LEVEL_PANIC: u8 = 101;
|
|
|
|
/// A handle into a buffer of bytes in the host environment that can be read from.
|
|
///
|
|
/// Used for transporting bytes from host to WASM linear memory.
|
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
|
#[repr(transparent)]
|
|
pub struct BytesSource(u32);
|
|
|
|
impl BytesSource {
|
|
/// An invalid handle, used e.g., when the reducer arguments were empty.
|
|
pub const INVALID: Self = Self(0);
|
|
}
|
|
|
|
/// A handle into a buffer of bytes in the host environment that can be written to.
|
|
///
|
|
/// Used for transporting bytes from WASM linear memory to host.
|
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
|
#[repr(transparent)]
|
|
pub struct BytesSink(u32);
|
|
|
|
/// Represents table iterators.
|
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
|
#[repr(transparent)]
|
|
pub struct RowIter(u32);
|
|
|
|
impl RowIter {
|
|
/// An invalid handle, used e.g., when the iterator has been exhausted.
|
|
pub const INVALID: Self = Self(0);
|
|
}
|
|
|
|
#[cfg(any())]
|
|
mod module_exports {
|
|
type Encoded<T> = Buffer;
|
|
type Identity = Encoded<[u8; 32]>;
|
|
/// microseconds since the unix epoch
|
|
type Timestamp = u64;
|
|
/// Buffer::INVALID => Ok(()); else errmsg => Err(errmsg)
|
|
type Result = Buffer;
|
|
extern "C" {
|
|
/// All functions prefixed with `__preinit__` are run first in alphabetical order.
|
|
/// For those it's recommended to use /etc/xxxx.d conventions of like `__preinit__20_do_thing`:
|
|
/// <https://man7.org/linux/man-pages/man5/sysctl.d.5.html#CONFIGURATION_DIRECTORIES_AND_PRECEDENCE>
|
|
fn __preinit__XX_XXXX();
|
|
/// Optional. Run after `__preinit__`; can return an error. Intended for dynamic languages; this
|
|
/// would be where you would initialize the interepreter and load the user module into it.
|
|
fn __setup__() -> Result;
|
|
/// Required. Runs after `__setup__`; returns all the exports for the module.
|
|
fn __describe_module__() -> Encoded<ModuleDef>;
|
|
/// Required. id is an index into the `ModuleDef.reducers` returned from `__describe_module__`.
|
|
/// args is a bsatn-encoded product value defined by the schema at `reducers[id]`.
|
|
fn __call_reducer__(
|
|
id: usize,
|
|
sender_0: u64,
|
|
sender_1: u64,
|
|
sender_2: u64,
|
|
sender_3: u64,
|
|
address_0: u64,
|
|
address_1: u64,
|
|
timestamp: u64,
|
|
args: Buffer,
|
|
) -> Result;
|
|
/// Currently unused?
|
|
fn __migrate_database__XXXX(sender: Identity, timestamp: Timestamp, something: Buffer) -> Result;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Error values used in the safe bindings API.
|
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
|
#[repr(transparent)]
|
|
pub struct Errno(NonZeroU16);
|
|
|
|
// once Error gets exposed from core this crate can be no_std again
|
|
impl std::error::Error for Errno {}
|
|
|
|
macro_rules! def_errno {
|
|
($($err_name:ident($errno:literal, $errmsg:literal),)*) => {
|
|
impl Errno {
|
|
$(#[doc = $errmsg] pub const $err_name: Errno = Errno(errno::$err_name);)*
|
|
}
|
|
};
|
|
}
|
|
errnos!(def_errno);
|
|
|
|
impl Errno {
|
|
/// Returns a description of the errno value, if any.
|
|
pub const fn message(self) -> Option<&'static str> {
|
|
errno::strerror(self.0)
|
|
}
|
|
|
|
/// Converts the given `code` to an error number in `Errno`'s representation.
|
|
#[inline]
|
|
pub const fn from_code(code: u16) -> Option<Self> {
|
|
match NonZeroU16::new(code) {
|
|
Some(code) => Some(Errno(code)),
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
/// Converts this `errno` into a primitive error code.
|
|
#[inline]
|
|
pub const fn code(self) -> u16 {
|
|
self.0.get()
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Errno {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let mut fmt = f.debug_struct("Errno");
|
|
fmt.field("code", &self.code());
|
|
if let Some(msg) = self.message() {
|
|
fmt.field("message", &msg);
|
|
}
|
|
fmt.finish()
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Errno {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let message = self.message().unwrap_or("Unknown error");
|
|
write!(f, "{message} (error {})", self.code())
|
|
}
|
|
}
|
|
|
|
/// Convert the status value `x` into a result.
|
|
/// When `x = 0`, we have a success status.
|
|
fn cvt(x: u16) -> Result<(), Errno> {
|
|
match Errno::from_code(x) {
|
|
None => Ok(()),
|
|
Some(err) => Err(err),
|
|
}
|
|
}
|
|
|
|
/// Runs the given function `f` provided with an uninitialized `out` pointer.
|
|
///
|
|
/// Assuming the call to `f` succeeds (`Ok(_)`), the `out` pointer's value is returned.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// This function is safe to call, if and only if,
|
|
/// - The function `f` writes a safe and valid `T` to the `out` pointer.
|
|
/// It's not required to write to `out` when `f(out)` returns an error code.
|
|
/// - The function `f` never reads a safe and valid `T` from the `out` pointer
|
|
/// before writing a safe and valid `T` to it.
|
|
#[inline]
|
|
unsafe fn call<T: Copy>(f: impl FnOnce(*mut T) -> u16) -> Result<T, Errno> {
|
|
let mut out = MaybeUninit::uninit();
|
|
let f_code = f(out.as_mut_ptr());
|
|
cvt(f_code)?;
|
|
Ok(out.assume_init())
|
|
}
|
|
|
|
/// Queries the `table_id` associated with the given (table) `name`.
|
|
///
|
|
/// The table id is returned.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_TABLE`, when `name` is not the name of a table.
|
|
#[inline]
|
|
pub fn table_id_from_name(name: &str) -> Result<TableId, Errno> {
|
|
unsafe { call(|out| raw::_table_id_from_name(name.as_ptr(), name.len(), out)) }
|
|
}
|
|
|
|
/// Queries the `index_id` associated with the given (index) `name`.
|
|
///
|
|
/// The index id is returned.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_INDEX`, when `name` is not the name of an index.
|
|
#[inline]
|
|
pub fn index_id_from_name(name: &str) -> Result<IndexId, Errno> {
|
|
unsafe { call(|out| raw::_index_id_from_name(name.as_ptr(), name.len(), out)) }
|
|
}
|
|
|
|
/// Returns the number of rows currently in table identified by `table_id`.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
|
|
#[inline]
|
|
pub fn datastore_table_row_count(table_id: TableId) -> Result<u64, Errno> {
|
|
unsafe { call(|out| raw::_datastore_table_row_count(table_id, out)) }
|
|
}
|
|
|
|
/// Finds all rows in the table identified by `table_id`,
|
|
/// where the row has a column, identified by `col_id`,
|
|
/// with data matching the byte string `val`.
|
|
///
|
|
/// Matching is defined BSATN-decoding `val` to an `AlgebraicValue`
|
|
/// according to the column's schema and then `Ord for AlgebraicValue`.
|
|
///
|
|
/// The rows found are BSATN encoded and then concatenated.
|
|
/// The resulting byte string from the concatenation is written
|
|
/// to a fresh buffer with a handle to it returned as a `Buffer`.
|
|
///
|
|
/// Returns an error if
|
|
/// - a table with the provided `table_id` doesn't exist
|
|
/// - `col_id` does not identify a column of the table
|
|
/// - `val` cannot be BSATN-decoded to an `AlgebraicValue`
|
|
/// typed at the `AlgebraicType` of the column
|
|
#[inline]
|
|
pub fn iter_by_col_eq(table_id: TableId, col_id: ColId, val: &[u8]) -> Result<RowIter, Errno> {
|
|
let raw = unsafe { call(|out| raw::_iter_by_col_eq(table_id, col_id, val.as_ptr(), val.len(), out)) }?;
|
|
Ok(RowIter { raw })
|
|
}
|
|
|
|
/// Inserts a row into the table identified by `table_id`,
|
|
/// where the row is a BSATN-encoded `ProductValue`
|
|
/// matching the table's `ProductType` row-schema.
|
|
///
|
|
/// The `row` is `&mut` due to auto-incrementing columns.
|
|
/// So `row` is written to with the inserted row re-encoded.
|
|
///
|
|
/// Returns an error if
|
|
/// - a table with the provided `table_id` doesn't exist
|
|
/// - there were unique constraint violations
|
|
/// - `row` doesn't decode from BSATN to a `ProductValue`
|
|
/// according to the `ProductType` that the table's schema specifies.
|
|
#[inline]
|
|
pub fn insert(table_id: TableId, row: &mut [u8]) -> Result<&[u8], Errno> {
|
|
let row_ptr = row.as_mut_ptr();
|
|
let row_len = &mut row.len();
|
|
cvt(unsafe { raw::_datastore_insert_bsatn(table_id, row_ptr, row_len) }).map(|()| &row[..*row_len])
|
|
}
|
|
|
|
/// Deletes all rows in the table identified by `table_id`
|
|
/// where the column identified by `col_id` matches `value`.
|
|
///
|
|
/// Matching is defined by BSATN-decoding `value` to an `AlgebraicValue`
|
|
/// according to the column's schema and then `Ord for AlgebraicValue`.
|
|
///
|
|
/// Returns the number of rows deleted.
|
|
///
|
|
/// Returns an error if
|
|
/// - a table with the provided `table_id` doesn't exist
|
|
/// - no columns were deleted
|
|
/// - `col_id` does not identify a column of the table
|
|
#[inline]
|
|
pub fn delete_by_col_eq(table_id: TableId, col_id: ColId, value: &[u8]) -> Result<u32, Errno> {
|
|
unsafe { call(|out| raw::_delete_by_col_eq(table_id, col_id, value.as_ptr(), value.len(), out)) }
|
|
}
|
|
|
|
/// Deletes those rows, in the table identified by `table_id`,
|
|
/// that match any row in the byte string `relation`.
|
|
///
|
|
/// Matching is defined by first BSATN-decoding
|
|
/// the byte string pointed to at by `relation` to a `Vec<ProductValue>`
|
|
/// according to the row schema of the table
|
|
/// and then using `Ord for AlgebraicValue`.
|
|
/// A match happens when `Ordering::Equal` is returned from `fn cmp`.
|
|
/// This occurs exactly when the row's BSATN-encoding is equal to the encoding of the `ProductValue`.
|
|
///
|
|
/// The number of rows deleted is returned.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
|
|
/// - `BSATN_DECODE_ERROR`, when `rel` cannot be decoded to `Vec<ProductValue>`
|
|
/// where each `ProductValue` is typed at the `ProductType` the table's schema specifies.
|
|
#[inline]
|
|
pub fn datastore_delete_all_by_eq_bsatn(table_id: TableId, relation: &[u8]) -> Result<u32, Errno> {
|
|
unsafe { call(|out| raw::_datastore_delete_all_by_eq_bsatn(table_id, relation.as_ptr(), relation.len(), out)) }
|
|
}
|
|
|
|
/// Starts iteration on each row, as BSATN-encoded, of a table identified by `table_id`.
|
|
/// Returns iterator handle is written to the `out` pointer.
|
|
/// This handle can be advanced by [`row_iter_bsatn_advance`].
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
|
|
pub fn datastore_table_scan_bsatn(table_id: TableId) -> Result<RowIter, Errno> {
|
|
let raw = unsafe { call(|out| raw::_datastore_table_scan_bsatn(table_id, out))? };
|
|
Ok(RowIter { raw })
|
|
}
|
|
|
|
/// Finds all rows in the index identified by `index_id`,
|
|
/// according to the `prefix`, `rstart`, and `rend`.
|
|
///
|
|
/// The index itself has a schema/type.
|
|
/// The `prefix` is decoded to the initial `prefix_elems` `AlgebraicType`s
|
|
/// whereas `rstart` and `rend` are decoded to the `prefix_elems + 1` `AlgebraicType`
|
|
/// where the `AlgebraicValue`s are wrapped in `Bound`.
|
|
/// That is, `rstart, rend` are BSATN-encoded `Bound<AlgebraicValue>`s.
|
|
///
|
|
/// Matching is then defined by equating `prefix`
|
|
/// to the initial `prefix_elems` columns of the index
|
|
/// and then imposing `rstart` as the starting bound
|
|
/// and `rend` as the ending bound on the `prefix_elems + 1` column of the index.
|
|
/// Remaining columns of the index are then unbounded.
|
|
/// Note that the `prefix` in this case can be empty (`prefix_elems = 0`),
|
|
/// in which case this becomes a ranged index scan on a single-col index
|
|
/// or even a full table scan if `rstart` and `rend` are both unbounded.
|
|
///
|
|
/// The relevant table for the index is found implicitly via the `index_id`,
|
|
/// which is unique for the module.
|
|
///
|
|
/// On success, the iterator handle is written to the `out` pointer.
|
|
/// This handle can be advanced by [`row_iter_bsatn_advance`].
|
|
///
|
|
/// # Non-obvious queries
|
|
///
|
|
/// For an index on columns `[a, b, c]`:
|
|
///
|
|
/// - `a = x, b = y` is encoded as a prefix `[x, y]`
|
|
/// and a range `Range::Unbounded`,
|
|
/// or as a prefix `[x]` and a range `rstart = rend = Range::Inclusive(y)`.
|
|
/// - `a = x, b = y, c = z` is encoded as a prefix `[x, y]`
|
|
/// and a range `rstart = rend = Range::Inclusive(z)`.
|
|
/// - A sorted full scan is encoded as an empty prefix
|
|
/// and a range `Range::Unbounded`.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error:
|
|
///
|
|
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
|
|
/// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
|
|
/// - `WRONG_INDEX_ALGO` if the index is not a btree index.
|
|
/// - `BSATN_DECODE_ERROR`, when `prefix` cannot be decoded to
|
|
/// a `prefix_elems` number of `AlgebraicValue`
|
|
/// typed at the initial `prefix_elems` `AlgebraicType`s of the index's key type.
|
|
/// Or when `rstart` or `rend` cannot be decoded to an `Bound<AlgebraicValue>`
|
|
/// where the inner `AlgebraicValue`s are
|
|
/// typed at the `prefix_elems + 1` `AlgebraicType` of the index's key type.
|
|
pub fn datastore_btree_scan_bsatn(
|
|
index_id: IndexId,
|
|
prefix: &[u8],
|
|
prefix_elems: ColId,
|
|
rstart: &[u8],
|
|
rend: &[u8],
|
|
) -> Result<RowIter, Errno> {
|
|
let raw = unsafe {
|
|
call(|out| {
|
|
raw::_datastore_btree_scan_bsatn(
|
|
index_id,
|
|
prefix.as_ptr(),
|
|
prefix.len(),
|
|
prefix_elems,
|
|
rstart.as_ptr(),
|
|
rstart.len(),
|
|
rend.as_ptr(),
|
|
rend.len(),
|
|
out,
|
|
)
|
|
})?
|
|
};
|
|
Ok(RowIter { raw })
|
|
}
|
|
|
|
/// Iterate through a table, filtering by an encoded `spacetimedb_lib::filter::Expr`.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// - `NO_SUCH_TABLE`, if `table_id` doesn't exist.
|
|
#[inline]
|
|
pub fn iter_filtered(table_id: TableId, filter: &[u8]) -> Result<RowIter, Errno> {
|
|
let raw = unsafe { call(|out| raw::_iter_start_filtered(table_id, filter.as_ptr(), filter.len(), out))? };
|
|
Ok(RowIter { raw })
|
|
}
|
|
|
|
/// A log level that can be used in `console_log`.
|
|
/// The variants are convertible into a raw `u8` log level.
|
|
#[repr(u8)]
|
|
pub enum LogLevel {
|
|
/// The error log level. See [`console_log`].
|
|
Error = raw::LOG_LEVEL_ERROR,
|
|
/// The warn log level. See [`console_log`].
|
|
Warn = raw::LOG_LEVEL_WARN,
|
|
/// The info log level. See [`console_log`].
|
|
Info = raw::LOG_LEVEL_INFO,
|
|
/// The debug log level. See [`console_log`].
|
|
Debug = raw::LOG_LEVEL_DEBUG,
|
|
/// The trace log level. See [`console_log`].
|
|
Trace = raw::LOG_LEVEL_TRACE,
|
|
/// The panic log level. See [`console_log`].
|
|
///
|
|
/// A panic level is emitted just before a fatal error causes the WASM module to trap.
|
|
Panic = raw::LOG_LEVEL_PANIC,
|
|
}
|
|
|
|
/// Log at `level` a `text` message occuring in `filename:line_number`
|
|
/// with [`target`] being the module path at the `log!` invocation site.
|
|
///
|
|
/// [`target`]: https://docs.rs/log/latest/log/struct.Record.html#method.target
|
|
#[inline]
|
|
pub fn console_log(
|
|
level: LogLevel,
|
|
target: Option<&str>,
|
|
filename: Option<&str>,
|
|
line_number: Option<u32>,
|
|
text: &str,
|
|
) {
|
|
let opt_ptr = |b: Option<&str>| b.map_or(ptr::null(), |b| b.as_ptr());
|
|
let opt_len = |b: Option<&str>| b.map_or(0, |b| b.len());
|
|
unsafe {
|
|
raw::_console_log(
|
|
level as u8,
|
|
opt_ptr(target),
|
|
opt_len(target),
|
|
opt_ptr(filename),
|
|
opt_len(filename),
|
|
line_number.unwrap_or(u32::MAX),
|
|
text.as_ptr(),
|
|
text.len(),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Schedule a reducer to be called asynchronously, nonatomically, and immediately
|
|
/// on a best-effort basis.
|
|
///
|
|
/// The reducer is assigned `name` and is provided `args` as its argument.
|
|
#[cfg(feature = "unstable_abi")]
|
|
#[inline]
|
|
pub fn volatile_nonatomic_schedule_immediate(name: &str, args: &[u8]) {
|
|
unsafe { raw::_volatile_nonatomic_schedule_immediate(name.as_ptr(), name.len(), args.as_ptr(), args.len()) }
|
|
}
|
|
|
|
pub struct RowIter {
|
|
raw: raw::RowIter,
|
|
}
|
|
|
|
impl RowIter {
|
|
/// Read some number of BSATN-encoded rows into the provided buffer.
|
|
///
|
|
/// Returns the number of new bytes added to the end of the buffer.
|
|
/// When the iterator has been exhausted,
|
|
/// `self.is_exhausted()` will return `true`.
|
|
pub fn read(&mut self, buf: &mut Vec<u8>) -> usize {
|
|
loop {
|
|
let buf_ptr = buf.spare_capacity_mut();
|
|
let mut buf_len = buf_ptr.len();
|
|
let ret = unsafe { raw::_row_iter_bsatn_advance(self.raw, buf_ptr.as_mut_ptr().cast(), &mut buf_len) };
|
|
if let -1 | 0 = ret {
|
|
// SAFETY: `_row_iter_bsatn_advance` just wrote `buf_len` bytes into the end of `buf`.
|
|
unsafe { buf.set_len(buf.len() + buf_len) };
|
|
}
|
|
|
|
const TOO_SMALL: i16 = errno::BUFFER_TOO_SMALL.get() as i16;
|
|
match ret {
|
|
-1 => {
|
|
self.raw = raw::RowIter::INVALID;
|
|
return buf_len;
|
|
}
|
|
0 => return buf_len,
|
|
TOO_SMALL => buf.reserve(buf_len),
|
|
e => panic!("unexpected error from `_row_iter_bsatn_advance`: {e}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns whether the iterator is exhausted or not.
|
|
pub fn is_exhausted(&self) -> bool {
|
|
self.raw == raw::RowIter::INVALID
|
|
}
|
|
}
|
|
|
|
impl Drop for RowIter {
|
|
fn drop(&mut self) {
|
|
// Avoid this syscall when `_row_iter_bsatn_advance` above
|
|
// notifies us that the iterator is exhausted.
|
|
if self.is_exhausted() {
|
|
return;
|
|
}
|
|
unsafe {
|
|
raw::_row_iter_bsatn_close(self.raw);
|
|
}
|
|
}
|
|
}
|