spacetimedb_bindings_sys/lib.rs
1//! Defines sys calls to interact with SpacetimeDB.
2//! This forms an ABI of sorts that modules written in Rust can use.
3
4extern crate alloc;
5
6use core::fmt;
7use core::mem::MaybeUninit;
8use core::num::NonZeroU16;
9use std::ptr;
10
11use spacetimedb_primitives::{errno, errnos, ColId, IndexId, TableId};
12
13/// Provides a raw set of sys calls which abstractions can be built atop of.
14pub mod raw {
15 use spacetimedb_primitives::{ColId, IndexId, TableId};
16
17 // this module identifier determines the abi version that modules built with this crate depend
18 // on. Any non-breaking additions to the abi surface should be put in a new `extern {}` block
19 // with a module identifier with a minor version 1 above the previous highest minor version.
20 // For breaking changes, all functions should be moved into one new `spacetime_X.0` block.
21 #[link(wasm_import_module = "spacetime_10.0")]
22 extern "C" {
23 /// Queries the `table_id` associated with the given (table) `name`
24 /// where `name` is the UTF-8 slice in WASM memory at `name_ptr[..name_len]`.
25 ///
26 /// The table id is written into the `out` pointer.
27 ///
28 /// # Traps
29 ///
30 /// Traps if:
31 /// - `name_ptr` is NULL or `name` is not in bounds of WASM memory.
32 /// - `name` is not valid UTF-8.
33 /// - `out` is NULL or `out[..size_of::<TableId>()]` is not in bounds of WASM memory.
34 ///
35 /// # Errors
36 ///
37 /// Returns an error:
38 ///
39 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
40 /// - `NO_SUCH_TABLE`, when `name` is not the name of a table.
41 pub fn table_id_from_name(name: *const u8, name_len: usize, out: *mut TableId) -> u16;
42
43 /// Queries the `index_id` associated with the given (index) `name`
44 /// where `name` is the UTF-8 slice in WASM memory at `name_ptr[..name_len]`.
45 ///
46 /// If a `name` was provided for the `RawIndexDef` for this index, that is the name of the index.
47 /// If no name was provided, a name was autogenerated like so:
48 /// ```
49 /// let table_name: String = "my_table".into();
50 /// let column_names: Vec<String> = vec!["bananas".into(), "oranges".into()];
51 /// let column_names = column_names.join("_");
52 /// let name = format!("{table_name}_{column_names}_idx_btree");
53 /// ```
54 /// (See the function `spacetimedb_schema::def::validate::v9::generate_index_name` for more
55 /// information.)
56 ///
57 /// The index id is written into the `out` pointer.
58 ///
59 /// # Traps
60 ///
61 /// Traps if:
62 /// - `name_ptr` is NULL or `name` is not in bounds of WASM memory.
63 /// - `name` is not valid UTF-8.
64 /// - `out` is NULL or `out[..size_of::<IndexId>()]` is not in bounds of WASM memory.
65 ///
66 /// # Errors
67 ///
68 /// Returns an error:
69 ///
70 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
71 /// - `NO_SUCH_INDEX`, when `name` is not the name of an index.
72 pub fn index_id_from_name(name_ptr: *const u8, name_len: usize, out: *mut IndexId) -> u16;
73
74 /// Writes the number of rows currently in table identified by `table_id` to `out`.
75 ///
76 /// # Traps
77 ///
78 /// Traps if:
79 /// - `out` is NULL or `out[..size_of::<u64>()]` is not in bounds of WASM memory.
80 ///
81 /// # Errors
82 ///
83 /// Returns an error:
84 ///
85 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
86 /// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
87 pub fn datastore_table_row_count(table_id: TableId, out: *mut u64) -> u16;
88
89 /// Starts iteration on each row, as BSATN-encoded, of a table identified by `table_id`.
90 ///
91 /// On success, the iterator handle is written to the `out` pointer.
92 /// This handle can be advanced by [`row_iter_bsatn_advance`].
93 ///
94 /// # Traps
95 ///
96 /// This function does not trap.
97 ///
98 /// # Errors
99 ///
100 /// Returns an error:
101 ///
102 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
103 /// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
104 pub fn datastore_table_scan_bsatn(table_id: TableId, out: *mut RowIter) -> u16;
105
106 /// Finds all rows in the index identified by `index_id`,
107 /// according to the:
108 /// - `prefix = prefix_ptr[..prefix_len]`,
109 /// - `rstart = rstart_ptr[..rstart_len]`,
110 /// - `rend = rend_ptr[..rend_len]`,
111 ///
112 /// in WASM memory.
113 ///
114 /// The index itself has a schema/type.
115 /// The `prefix` is decoded to the initial `prefix_elems` `AlgebraicType`s
116 /// whereas `rstart` and `rend` are decoded to the `prefix_elems + 1` `AlgebraicType`
117 /// where the `AlgebraicValue`s are wrapped in `Bound`.
118 /// That is, `rstart, rend` are BSATN-encoded `Bound<AlgebraicValue>`s.
119 ///
120 /// Matching is then defined by equating `prefix`
121 /// to the initial `prefix_elems` columns of the index
122 /// and then imposing `rstart` as the starting bound
123 /// and `rend` as the ending bound on the `prefix_elems + 1` column of the index.
124 /// Remaining columns of the index are then unbounded.
125 /// Note that the `prefix` in this case can be empty (`prefix_elems = 0`),
126 /// in which case this becomes a ranged index scan on a single-col index
127 /// or even a full table scan if `rstart` and `rend` are both unbounded.
128 ///
129 /// The relevant table for the index is found implicitly via the `index_id`,
130 /// which is unique for the module.
131 ///
132 /// On success, the iterator handle is written to the `out` pointer.
133 /// This handle can be advanced by [`row_iter_bsatn_advance`].
134 ///
135 /// # Non-obvious queries
136 ///
137 /// For an index on columns `[a, b, c]`:
138 ///
139 /// - `a = x, b = y` is encoded as a prefix `[x, y]`
140 /// and a range `Range::Unbounded`,
141 /// or as a prefix `[x]` and a range `rstart = rend = Range::Inclusive(y)`.
142 /// - `a = x, b = y, c = z` is encoded as a prefix `[x, y]`
143 /// and a range `rstart = rend = Range::Inclusive(z)`.
144 /// - A sorted full scan is encoded as an empty prefix
145 /// and a range `Range::Unbounded`.
146 ///
147 /// # Traps
148 ///
149 /// Traps if:
150 /// - `prefix_elems > 0`
151 /// and (`prefix_ptr` is NULL or `prefix` is not in bounds of WASM memory).
152 /// - `rstart` is NULL or `rstart` is not in bounds of WASM memory.
153 /// - `rend` is NULL or `rend` is not in bounds of WASM memory.
154 /// - `out` is NULL or `out[..size_of::<RowIter>()]` is not in bounds of WASM memory.
155 ///
156 /// # Errors
157 ///
158 /// Returns an error:
159 ///
160 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
161 /// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
162 /// - `WRONG_INDEX_ALGO` if the index is not a range-compatible index.
163 /// - `BSATN_DECODE_ERROR`, when `prefix` cannot be decoded to
164 /// a `prefix_elems` number of `AlgebraicValue`
165 /// typed at the initial `prefix_elems` `AlgebraicType`s of the index's key type.
166 /// Or when `rstart` or `rend` cannot be decoded to an `Bound<AlgebraicValue>`
167 /// where the inner `AlgebraicValue`s are
168 /// typed at the `prefix_elems + 1` `AlgebraicType` of the index's key type.
169 pub fn datastore_index_scan_range_bsatn(
170 index_id: IndexId,
171 prefix_ptr: *const u8,
172 prefix_len: usize,
173 prefix_elems: ColId,
174 rstart_ptr: *const u8, // Bound<AlgebraicValue>
175 rstart_len: usize,
176 rend_ptr: *const u8, // Bound<AlgebraicValue>
177 rend_len: usize,
178 out: *mut RowIter,
179 ) -> u16;
180
181 /// This is the same as [`datastore_index_scan_range_bsatn`].
182 #[deprecated = "use `datastore_index_scan_range_bsatn` instead"]
183 #[doc(alias = "datastore_index_scan_range_bsatn")]
184 pub fn datastore_btree_scan_bsatn(
185 index_id: IndexId,
186 prefix_ptr: *const u8,
187 prefix_len: usize,
188 prefix_elems: ColId,
189 rstart_ptr: *const u8, // Bound<AlgebraicValue>
190 rstart_len: usize,
191 rend_ptr: *const u8, // Bound<AlgebraicValue>
192 rend_len: usize,
193 out: *mut RowIter,
194 ) -> u16;
195
196 /// Deletes all rows found in the index identified by `index_id`,
197 /// according to the:
198 /// - `prefix = prefix_ptr[..prefix_len]`,
199 /// - `rstart = rstart_ptr[..rstart_len]`,
200 /// - `rend = rend_ptr[..rend_len]`,
201 ///
202 /// in WASM memory.
203 ///
204 /// This syscall will delete all the rows found by
205 /// [`datastore_index_scan_range_bsatn`] with the same arguments passed,
206 /// including `prefix_elems`.
207 /// See `datastore_index_scan_range_bsatn` for details.
208 ///
209 /// The number of rows deleted is written to the WASM pointer `out`.
210 ///
211 /// # Traps
212 ///
213 /// Traps if:
214 /// - `prefix_elems > 0`
215 /// and (`prefix_ptr` is NULL or `prefix` is not in bounds of WASM memory).
216 /// - `rstart` is NULL or `rstart` is not in bounds of WASM memory.
217 /// - `rend` is NULL or `rend` is not in bounds of WASM memory.
218 /// - `out` is NULL or `out[..size_of::<u32>()]` is not in bounds of WASM memory.
219 ///
220 /// # Errors
221 ///
222 /// Returns an error:
223 ///
224 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
225 /// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
226 /// - `WRONG_INDEX_ALGO` if the index is not a range-compatible index.
227 /// - `BSATN_DECODE_ERROR`, when `prefix` cannot be decoded to
228 /// a `prefix_elems` number of `AlgebraicValue`
229 /// typed at the initial `prefix_elems` `AlgebraicType`s of the index's key type.
230 /// Or when `rstart` or `rend` cannot be decoded to an `Bound<AlgebraicValue>`
231 /// where the inner `AlgebraicValue`s are
232 /// typed at the `prefix_elems + 1` `AlgebraicType` of the index's key type.
233 pub fn datastore_delete_by_index_scan_range_bsatn(
234 index_id: IndexId,
235 prefix_ptr: *const u8,
236 prefix_len: usize,
237 prefix_elems: ColId,
238 rstart_ptr: *const u8, // Bound<AlgebraicValue>
239 rstart_len: usize,
240 rend_ptr: *const u8, // Bound<AlgebraicValue>
241 rend_len: usize,
242 out: *mut u32,
243 ) -> u16;
244
245 /// This is the same as [`datastore_delete_by_index_scan_range_bsatn`].
246 #[deprecated = "use `datastore_delete_by_index_scan_range_bsatn` instead"]
247 #[doc(alias = "datastore_delete_by_index_scan_range_bsatn")]
248 pub fn datastore_delete_by_btree_scan_bsatn(
249 index_id: IndexId,
250 prefix_ptr: *const u8,
251 prefix_len: usize,
252 prefix_elems: ColId,
253 rstart_ptr: *const u8, // Bound<AlgebraicValue>
254 rstart_len: usize,
255 rend_ptr: *const u8, // Bound<AlgebraicValue>
256 rend_len: usize,
257 out: *mut u32,
258 ) -> u16;
259
260 /// Deletes those rows, in the table identified by `table_id`,
261 /// that match any row in the byte string `rel = rel_ptr[..rel_len]` in WASM memory.
262 ///
263 /// Matching is defined by first BSATN-decoding
264 /// the byte string pointed to at by `relation` to a `Vec<ProductValue>`
265 /// according to the row schema of the table
266 /// and then using `Ord for AlgebraicValue`.
267 /// A match happens when `Ordering::Equal` is returned from `fn cmp`.
268 /// This occurs exactly when the row's BSATN-encoding is equal to the encoding of the `ProductValue`.
269 ///
270 /// The number of rows deleted is written to the WASM pointer `out`.
271 ///
272 /// # Traps
273 ///
274 /// Traps if:
275 /// - `rel_ptr` is NULL or `rel` is not in bounds of WASM memory.
276 /// - `out` is NULL or `out[..size_of::<u32>()]` is not in bounds of WASM memory.
277 ///
278 /// # Errors
279 ///
280 /// Returns an error:
281 ///
282 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
283 /// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
284 /// - `BSATN_DECODE_ERROR`, when `rel` cannot be decoded to `Vec<ProductValue>`
285 /// where each `ProductValue` is typed at the `ProductType` the table's schema specifies.
286 pub fn datastore_delete_all_by_eq_bsatn(
287 table_id: TableId,
288 rel_ptr: *const u8,
289 rel_len: usize,
290 out: *mut u32,
291 ) -> u16;
292
293 /// Reads rows from the given iterator registered under `iter`.
294 ///
295 /// Takes rows from the iterator
296 /// and stores them in the memory pointed to by `buffer = buffer_ptr[..buffer_len]`,
297 /// encoded in BSATN format.
298 ///
299 /// The `buffer_len = buffer_len_ptr[..size_of::<usize>()]` stores the capacity of `buffer`.
300 /// On success (`0` or `-1` is returned),
301 /// `buffer_len` is set to the combined length of the encoded rows.
302 /// When `-1` is returned, the iterator has been exhausted
303 /// and there are no more rows to read,
304 /// leading to the iterator being immediately destroyed.
305 /// Note that the host is free to reuse allocations in a pool,
306 /// destroying the handle logically does not entail that memory is necessarily reclaimed.
307 ///
308 /// # Traps
309 ///
310 /// Traps if:
311 ///
312 /// - `buffer_len_ptr` is NULL or `buffer_len` is not in bounds of WASM memory.
313 /// - `buffer_ptr` is NULL or `buffer` is not in bounds of WASM memory.
314 ///
315 /// # Errors
316 ///
317 /// Returns an error:
318 ///
319 /// - `NO_SUCH_ITER`, when `iter` is not a valid iterator.
320 /// - `BUFFER_TOO_SMALL`, when there are rows left but they cannot fit in `buffer`.
321 /// When this occurs, `buffer_len` is set to the size of the next item in the iterator.
322 /// To make progress, the caller should reallocate the buffer to at least that size and try again.
323 pub fn row_iter_bsatn_advance(iter: RowIter, buffer_ptr: *mut u8, buffer_len_ptr: *mut usize) -> i16;
324
325 /// Destroys the iterator registered under `iter`.
326 ///
327 /// Once `row_iter_bsatn_close` is called on `iter`, the `iter` is invalid.
328 /// That is, `row_iter_bsatn_close(iter)` the second time will yield `NO_SUCH_ITER`.
329 ///
330 /// # Errors
331 ///
332 /// Returns an error:
333 ///
334 /// - `NO_SUCH_ITER`, when `iter` is not a valid iterator.
335 pub fn row_iter_bsatn_close(iter: RowIter) -> u16;
336
337 /// Inserts a row into the table identified by `table_id`,
338 /// where the row is read from the byte string `row = row_ptr[..row_len]` in WASM memory
339 /// where `row_len = row_len_ptr[..size_of::<usize>()]` stores the capacity of `row`.
340 ///
341 /// The byte string `row` must be a BSATN-encoded `ProductValue`
342 /// typed at the table's `ProductType` row-schema.
343 ///
344 /// To handle auto-incrementing columns,
345 /// when the call is successful,
346 /// the `row` is written back to with the generated sequence values.
347 /// These values are written as a BSATN-encoded `pv: ProductValue`.
348 /// Each `v: AlgebraicValue` in `pv` is typed at the sequence's column type.
349 /// The `v`s in `pv` are ordered by the order of the columns, in the schema of the table.
350 /// When the table has no sequences,
351 /// this implies that the `pv`, and thus `row`, will be empty.
352 /// The `row_len` is set to the length of `bsatn(pv)`.
353 ///
354 /// # Traps
355 ///
356 /// Traps if:
357 /// - `row_len_ptr` is NULL or `row_len` is not in bounds of WASM memory.
358 /// - `row_ptr` is NULL or `row` is not in bounds of WASM memory.
359 ///
360 /// # Errors
361 ///
362 /// Returns an error:
363 ///
364 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
365 /// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
366 /// - `BSATN_DECODE_ERROR`, when `row` cannot be decoded to a `ProductValue`.
367 /// typed at the `ProductType` the table's schema specifies.
368 /// - `UNIQUE_ALREADY_EXISTS`, when inserting `row` would violate a unique constraint.
369 /// - `SCHEDULE_AT_DELAY_TOO_LONG`, when the delay specified in the row was too long.
370 pub fn datastore_insert_bsatn(table_id: TableId, row_ptr: *mut u8, row_len_ptr: *mut usize) -> u16;
371
372 /// Updates a row in the table identified by `table_id` to `row`
373 /// where the row is read from the byte string `row = row_ptr[..row_len]` in WASM memory
374 /// where `row_len = row_len_ptr[..size_of::<usize>()]` stores the capacity of `row`.
375 ///
376 /// The byte string `row` must be a BSATN-encoded `ProductValue`
377 /// typed at the table's `ProductType` row-schema.
378 ///
379 /// The row to update is found by projecting `row`
380 /// to the type of the *unique* index identified by `index_id`.
381 /// If no row is found, the error `NO_SUCH_ROW` is returned.
382 ///
383 /// To handle auto-incrementing columns,
384 /// when the call is successful,
385 /// the `row` is written back to with the generated sequence values.
386 /// These values are written as a BSATN-encoded `pv: ProductValue`.
387 /// Each `v: AlgebraicValue` in `pv` is typed at the sequence's column type.
388 /// The `v`s in `pv` are ordered by the order of the columns, in the schema of the table.
389 /// When the table has no sequences,
390 /// this implies that the `pv`, and thus `row`, will be empty.
391 /// The `row_len` is set to the length of `bsatn(pv)`.
392 ///
393 /// # Traps
394 ///
395 /// Traps if:
396 /// - `row_len_ptr` is NULL or `row_len` is not in bounds of WASM memory.
397 /// - `row_ptr` is NULL or `row` is not in bounds of WASM memory.
398 ///
399 /// # Errors
400 ///
401 /// Returns an error:
402 ///
403 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
404 /// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
405 /// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
406 /// - `INDEX_NOT_UNIQUE`, when the index was not unique.
407 /// - `NO_SUCH_ROW`, when the row was not found in the unique index.
408 /// - `BSATN_DECODE_ERROR`, when `row` cannot be decoded to a `ProductValue`
409 /// typed at the `ProductType` the table's schema specifies
410 /// or when it cannot be projected to the index identified by `index_id`.
411 /// - `UNIQUE_ALREADY_EXISTS`, when inserting `row` would violate a unique constraint.
412 /// - `SCHEDULE_AT_DELAY_TOO_LONG`, when the delay specified in the row was too long.
413 pub fn datastore_update_bsatn(
414 table_id: TableId,
415 index_id: IndexId,
416 row_ptr: *mut u8,
417 row_len_ptr: *mut usize,
418 ) -> u16;
419
420 /// Schedules a reducer to be called asynchronously, nonatomically,
421 /// and immediately on a best effort basis.
422 ///
423 /// The reducer is named as the valid UTF-8 slice `(name, name_len)`,
424 /// and is passed the slice `(args, args_len)` as its argument.
425 ///
426 /// Traps if
427 /// - `name` does not point to valid UTF-8
428 /// - `name + name_len` or `args + args_len` overflow a 64-bit integer
429 #[cfg(feature = "unstable")]
430 pub fn volatile_nonatomic_schedule_immediate(
431 name: *const u8,
432 name_len: usize,
433 args: *const u8,
434 args_len: usize,
435 );
436
437 /// Writes up to `buffer_len` bytes from `buffer = buffer_ptr[..buffer_len]`,
438 /// to the `sink`, registered in the host environment.
439 ///
440 /// The `buffer_len = buffer_len_ptr[..size_of::<usize>()]` stores the capacity of `buffer`.
441 /// On success (`0` is returned),
442 /// `buffer_len` is set to the number of bytes written to `sink`.
443 ///
444 /// # Traps
445 ///
446 /// - `buffer_len_ptr` is NULL or `buffer_len` is not in bounds of WASM memory.
447 /// - `buffer_ptr` is NULL or `buffer` is not in bounds of WASM memory.
448 ///
449 /// # Errors
450 ///
451 /// Returns an error:
452 ///
453 /// - `NO_SUCH_BYTES`, when `sink` is not a valid bytes sink.
454 /// - `NO_SPACE`, when there is no room for more bytes in `sink`.
455 pub fn bytes_sink_write(sink: BytesSink, buffer_ptr: *const u8, buffer_len_ptr: *mut usize) -> u16;
456
457 /// Reads bytes from `source`, registered in the host environment,
458 /// and stores them in the memory pointed to by `buffer = buffer_ptr[..buffer_len]`.
459 ///
460 /// The `buffer_len = buffer_len_ptr[..size_of::<usize>()]` stores the capacity of `buffer`.
461 /// On success (`0` or `-1` is returned),
462 /// `buffer_len` is set to the number of bytes written to `buffer`.
463 /// When `-1` is returned, the resource has been exhausted
464 /// and there are no more bytes to read,
465 /// leading to the resource being immediately destroyed.
466 /// Note that the host is free to reuse allocations in a pool,
467 /// destroying the handle logically does not entail that memory is necessarily reclaimed.
468 ///
469 /// # Traps
470 ///
471 /// Traps if:
472 ///
473 /// - `buffer_len_ptr` is NULL or `buffer_len` is not in bounds of WASM memory.
474 /// - `buffer_ptr` is NULL or `buffer` is not in bounds of WASM memory.
475 ///
476 /// # Errors
477 ///
478 /// Returns an error:
479 ///
480 /// - `NO_SUCH_BYTES`, when `source` is not a valid bytes source.
481 ///
482 /// # Example
483 ///
484 /// The typical use case for this ABI is in `__call_reducer__`,
485 /// to read and deserialize the `args`.
486 /// An example definition, dealing with `args` might be:
487 /// ```rust,ignore
488 /// /// #[no_mangle]
489 /// extern "C" fn __call_reducer__(..., args: BytesSource, ...) -> i16 {
490 /// // ...
491 ///
492 /// let mut buf = Vec::<u8>::with_capacity(1024);
493 /// loop {
494 /// // Write into the spare capacity of the buffer.
495 /// let buf_ptr = buf.spare_capacity_mut();
496 /// let spare_len = buf_ptr.len();
497 /// let mut buf_len = buf_ptr.len();
498 /// let buf_ptr = buf_ptr.as_mut_ptr().cast();
499 /// let ret = unsafe { bytes_source_read(args, buf_ptr, &mut buf_len) };
500 /// // SAFETY: `bytes_source_read` just appended `spare_len` bytes to `buf`.
501 /// unsafe { buf.set_len(buf.len() + spare_len) };
502 /// match ret {
503 /// // Host side source exhausted, we're done.
504 /// -1 => break,
505 /// // Wrote the entire spare capacity.
506 /// // Need to reserve more space in the buffer.
507 /// 0 if spare_len == buf_len => buf.reserve(1024),
508 /// // Host didn't write as much as possible.
509 /// // Try to read some more.
510 /// // The host will likely not trigger this branch,
511 /// // but a module should be prepared for it.
512 /// 0 => {}
513 /// _ => unreachable!(),
514 /// }
515 /// }
516 ///
517 /// // ...
518 /// }
519 /// ```
520 pub fn bytes_source_read(source: BytesSource, buffer_ptr: *mut u8, buffer_len_ptr: *mut usize) -> i16;
521
522 /// Logs at `level` a `message` message occurring in `filename:line_number`
523 /// with `target` being the module path at the `log!` invocation site.
524 ///
525 /// These various pointers are interpreted lossily as UTF-8 strings with a corresponding `_len`.
526 ///
527 /// The `target` and `filename` pointers are ignored by passing `NULL`.
528 /// The line number is ignored if `line_number == u32::MAX`.
529 ///
530 /// No message is logged if
531 /// - `target != NULL && target + target_len > u64::MAX`
532 /// - `filename != NULL && filename + filename_len > u64::MAX`
533 /// - `message + message_len > u64::MAX`
534 ///
535 /// # Traps
536 ///
537 /// Traps if:
538 /// - `target` is not NULL and `target_ptr[..target_len]` is not in bounds of WASM memory.
539 /// - `filename` is not NULL and `filename_ptr[..filename_len]` is not in bounds of WASM memory.
540 /// - `message` is not NULL and `message_ptr[..message_len]` is not in bounds of WASM memory.
541 ///
542 /// [target]: https://docs.rs/log/latest/log/struct.Record.html#method.target
543 pub fn console_log(
544 level: u8,
545 target_ptr: *const u8,
546 target_len: usize,
547 filename_ptr: *const u8,
548 filename_len: usize,
549 line_number: u32,
550 message_ptr: *const u8,
551 message_len: usize,
552 );
553
554 /// Begins a timing span with `name = name_ptr[..name_len]`.
555 ///
556 /// When the returned `ConsoleTimerId` is passed to [`console_timer_end`],
557 /// the duration between the calls will be printed to the module's logs.
558 ///
559 /// The `name` is interpreted lossily as UTF-8.
560 ///
561 /// # Traps
562 ///
563 /// Traps if:
564 /// - `name_ptr` is NULL or `name` is not in bounds of WASM memory.
565 pub fn console_timer_start(name_ptr: *const u8, name_len: usize) -> u32;
566
567 /// End a timing span.
568 ///
569 /// The `timer_id` must be the result of a call to `console_timer_start`.
570 /// The duration between the two calls will be computed and printed to the module's logs.
571 /// Once `console_timer_end` is called on `id: ConsoleTimerId`, the `id` is invalid.
572 /// That is, `console_timer_end(id)` the second time will yield `NO_SUCH_CONSOLE_TIMER`.
573 ///
574 /// Note that the host is free to reuse allocations in a pool,
575 /// destroying the handle logically does not entail that memory is necessarily reclaimed.
576 ///
577 /// # Errors
578 ///
579 /// Returns an error:
580 /// - `NO_SUCH_CONSOLE_TIMER`, when `timer_id` does not exist.
581 pub fn console_timer_end(timer_id: u32) -> u16;
582
583 /// Writes the identity of the module into `out = out_ptr[..32]`.
584 ///
585 /// # Traps
586 ///
587 /// Traps if:
588 ///
589 /// - `out_ptr` is NULL or `out` is not in bounds of WASM memory.
590 pub fn identity(out_ptr: *mut u8);
591 }
592
593 // See comment on previous `extern "C"` block re: ABI version.
594 #[link(wasm_import_module = "spacetime_10.1")]
595 extern "C" {
596 /// Read the remaining length of a [`BytesSource`] and write it to `out`.
597 ///
598 /// Note that the host automatically frees byte sources which are exhausted.
599 /// Such sources are invalid, and this method will return an error when passed one.
600 /// Callers of [`bytes_source_read`] should check for a return of -1
601 /// before invoking this function on the same `source`.
602 ///
603 /// Also note that the special [`BytesSource::INVALID`] (zero) is always invalid.
604 /// Callers should check for that value before invoking this function.
605 ///
606 /// # Traps
607 ///
608 /// Traps if:
609 ///
610 /// - `out` is NULL or `out` is not in bounds of WASM memory.
611 ///
612 /// # Errors
613 ///
614 /// Returns an error:
615 ///
616 /// - `NO_SUCH_BYTES`, when `source` is not a valid bytes source.
617 ///
618 /// If this function returns an error, `out` is not written.
619 pub fn bytes_source_remaining_length(source: BytesSource, out: *mut u32) -> i16;
620 }
621
622 // See comment on previous `extern "C"` block re: ABI version.
623 #[link(wasm_import_module = "spacetime_10.2")]
624 extern "C" {
625 /// Finds the JWT payload associated with `connection_id`.
626 /// A `[ByteSourceId]` for the payload will be written to `target_ptr`.
627 /// If nothing is found for the connection, `[ByteSourceId::INVALID]` (zero) is written to `target_ptr`.
628 ///
629 /// This must be called inside a transaction (because it reads from a system table).
630 ///
631 /// # Errors
632 ///
633 /// Returns an error:
634 ///
635 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
636 ///
637 /// # Traps
638 ///
639 /// Traps if:
640 ///
641 /// - `connection_id` does not point to a valid little-endian `ConnectionId`.
642 /// - `target_ptr` is NULL or `target_ptr[..size_of::<u32>()]` is not in bounds of WASM memory.
643 /// - The `ByteSourceId` to be written to `target_ptr` would overflow [`u32::MAX`].
644 pub fn get_jwt(connection_id_ptr: *const u8, bytes_source_id: *mut BytesSource) -> u16;
645 }
646
647 #[cfg(feature = "unstable")]
648 #[link(wasm_import_module = "spacetime_10.3")]
649 extern "C" {
650 /// Suspends execution of this WASM instance until approximately `wake_at_micros_since_unix_epoch`.
651 ///
652 /// Returns immediately if `wake_at_micros_since_unix_epoch` is in the past.
653 ///
654 /// Upon resuming, returns the current timestamp as microseconds since the Unix epoch.
655 ///
656 /// Not particularly useful, except for testing SpacetimeDB internals related to suspending procedure execution.
657 /// # Traps
658 ///
659 /// Traps if:
660 ///
661 /// - The calling WASM instance is holding open a transaction.
662 /// - The calling WASM instance is not executing a procedure.
663 // TODO(procedure-sleep-until): remove this
664 pub fn procedure_sleep_until(wake_at_micros_since_unix_epoch: i64) -> i64;
665
666 /// Starts a mutable transaction,
667 /// suspending execution of this WASM instance until
668 /// a mutable transaction lock is aquired.
669 ///
670 /// Upon resuming, returns `0` on success,
671 /// enabling further calls that require a pending transaction,
672 /// or an error code otherwise.
673 ///
674 /// # Traps
675 ///
676 /// Traps if:
677 /// - `out` is NULL or `out[..size_of::<i64>()]` is not in bounds of WASM memory.
678 ///
679 /// # Errors
680 ///
681 /// Returns an error:
682 ///
683 /// - `WOULD_BLOCK_TRANSACTION`, if there's already an ongoing transaction.
684 pub fn procedure_start_mut_tx(out: *mut i64) -> u16;
685
686 /// Commits a mutable transaction,
687 /// suspending execution of this WASM instance until
688 /// the transaction has been committed
689 /// and subscription queries have been run and broadcast.
690 ///
691 /// Upon resuming, returns `0` on success, or an error code otherwise.
692 ///
693 /// # Traps
694 ///
695 /// This function does not trap.
696 ///
697 /// # Errors
698 ///
699 /// Returns an error:
700 ///
701 /// - `TRANSACTION_NOT_ANONYMOUS`,
702 /// if the transaction was not started in [`procedure_start_mut_tx`].
703 /// This can happen if this syscall is erroneously called by a reducer.
704 /// The code `NOT_IN_TRANSACTION` does not happen,
705 /// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
706 /// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
707 /// This currently does not happen as anonymous read transactions
708 /// are not exposed to modules.
709 pub fn procedure_commit_mut_tx() -> u16;
710
711 /// Aborts a mutable transaction,
712 /// suspending execution of this WASM instance until
713 /// the transaction has been rolled back.
714 ///
715 /// Upon resuming, returns `0` on success, or an error code otherwise.
716 ///
717 /// # Traps
718 ///
719 /// This function does not trap.
720 ///
721 /// # Errors
722 ///
723 /// Returns an error:
724 ///
725 /// - `TRANSACTION_NOT_ANONYMOUS`,
726 /// if the transaction was not started in [`procedure_start_mut_tx`].
727 /// This can happen if this syscall is erroneously called by a reducer.
728 /// The code `NOT_IN_TRANSACTION` does not happen,
729 /// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
730 /// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
731 /// This currently does not happen as anonymous read transactions
732 /// are not exposed to modules.
733 pub fn procedure_abort_mut_tx() -> u16;
734
735 /// Perform an HTTP request as specified by the buffer `request_ptr[..request_len]`,
736 /// suspending execution until the request is complete,
737 /// then return its response via a [`BytesSource`] written to `out`.
738 ///
739 /// `request_ptr[..request_len]` should store a BSATN-serialized `spacetimedb_lib::http::Request` object
740 /// containing the details of the request to be performed.
741 ///
742 /// If the request is successful, a [`BytesSource`] is written to `out`
743 /// containing a BSATN-encoded `spacetimedb_lib::http::Response` object.
744 /// "Successful" in this context includes any connection which results in any HTTP status code,
745 /// regardless of the specified meaning of that code.
746 ///
747 /// # Errors
748 ///
749 /// Returns an error:
750 ///
751 /// - `WOULD_BLOCK_TRANSACTION` if there is currently a transaction open.
752 /// In this case, `out` is not written.
753 /// - `BSATN_DECODE_ERROR` if `request_ptr[..request_len]` does not contain
754 /// a valid BSATN-serialized `spacetimedb_lib::http::Request` object.
755 /// In this case, `out` is not written.
756 /// - `HTTP_ERROR` if an error occurs while executing the HTTP request.
757 /// In this case, a [`BytesSource`] is written to `out`
758 /// containing a BSATN-encoded `spacetimedb_lib::http::Error` object.
759 ///
760 /// # Traps
761 ///
762 /// Traps if:
763 ///
764 /// - `request_ptr` is NULL or `request_ptr[..request_len]` is not in bounds of WASM memory.
765 /// - `out` is NULL or `out[..size_of::<RowIter>()]` is not in bounds of WASM memory.
766 /// - `request_ptr[..request_len]` does not contain a valid BSATN-serialized `spacetimedb_lib::http::Request` object.
767 #[cfg(feature = "unstable")]
768 pub fn procedure_http_request(
769 request_ptr: *const u8,
770 request_len: u32,
771 body_ptr: *const u8,
772 body_len: u32,
773 out: *mut [BytesSource; 2],
774 ) -> u16;
775 }
776
777 #[link(wasm_import_module = "spacetime_10.4")]
778 extern "C" {
779 /// Finds all rows in the index identified by `index_id`,
780 /// according to `point = point_ptr[..point_len]` in WASM memory.
781 ///
782 /// The index itself has a schema/type.
783 /// Matching defined by first BSATN-decoding `point` to that `AlgebraicType`
784 /// and then comparing the decoded `point` to the keys in the index
785 /// using `Ord for AlgebraicValue`.
786 /// to the keys in the index.
787 /// The `point` is BSATN-decoded to that `AlgebraicType`.
788 /// A match happens when `Ordering::Equal` is returned from `fn cmp`.
789 /// This occurs exactly when the row's BSATN-encoding
790 /// is equal to the encoding of the `AlgebraicValue`.
791 ///
792 /// This ABI is not limited to single column indices.
793 /// Multi-column indices can be queried by providing
794 /// a BSATN-encoded `ProductValue`
795 /// that is typed at the `ProductType` of the index.
796 ///
797 /// The relevant table for the index is found implicitly via the `index_id`,
798 /// which is unique for the module.
799 ///
800 /// On success, the iterator handle is written to the `out` pointer.
801 /// This handle can be advanced by [`row_iter_bsatn_advance`].
802 ///
803 /// # Traps
804 ///
805 /// Traps if:
806 /// - `point_ptr` is NULL or `point` is not in bounds of WASM memory.
807 /// - `out` is NULL or `out[..size_of::<RowIter>()]` is not in bounds of WASM memory.
808 ///
809 /// # Errors
810 ///
811 /// Returns an error:
812 ///
813 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
814 /// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
815 /// - `WRONG_INDEX_ALGO` if the index is not a range-scan compatible index.
816 /// - `BSATN_DECODE_ERROR`, when `point` cannot be decoded to an `AlgebraicValue`
817 /// typed at the index's key type (`AlgebraicType`).
818 pub fn datastore_index_scan_point_bsatn(
819 index_id: IndexId,
820 point_ptr: *const u8, // AlgebraicValue
821 point_len: usize,
822 out: *mut RowIter,
823 ) -> u16;
824
825 /// Deletes all rows found in the index identified by `index_id`,
826 /// according to `point = point_ptr[..point_len]` in WASM memory.
827 ///
828 /// This syscall will delete all the rows found by
829 /// [`datastore_index_scan_point_bsatn`] with the same arguments passed.
830 /// See `datastore_index_scan_point_bsatn` for details.
831 ///
832 /// The number of rows deleted is written to the WASM pointer `out`.
833 ///
834 /// # Traps
835 ///
836 /// Traps if:
837 /// - `point_ptr` is NULL or `point` is not in bounds of WASM memory.
838 /// - `out` is NULL or `out[..size_of::<u32>()]` is not in bounds of WASM memory.
839 ///
840 /// # Errors
841 ///
842 /// Returns an error:
843 ///
844 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
845 /// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
846 /// - `WRONG_INDEX_ALGO` if the index is not a range-compatible index.
847 /// - `BSATN_DECODE_ERROR`, when `point` cannot be decoded to an `AlgebraicValue`
848 /// typed at the index's key type (`AlgebraicType`).
849 pub fn datastore_delete_by_index_scan_point_bsatn(
850 index_id: IndexId,
851 point_ptr: *const u8, // AlgebraicValue
852 point_len: usize,
853 out: *mut u32,
854 ) -> u16;
855 }
856
857 /// What strategy does the database index use?
858 ///
859 /// See also: <https://www.postgresql.org/docs/current/sql-createindex.html>
860 #[repr(u8)]
861 #[non_exhaustive]
862 pub enum IndexType {
863 /// Indexing works by putting the index key into a b-tree.
864 BTree = 0,
865 /// Indexing works by hashing the index key.
866 Hash = 1,
867 }
868
869 /// The error log level. See [`console_log`].
870 pub const LOG_LEVEL_ERROR: u8 = 0;
871 /// The warn log level. See [`console_log`].
872 pub const LOG_LEVEL_WARN: u8 = 1;
873 /// The info log level. See [`console_log`].
874 pub const LOG_LEVEL_INFO: u8 = 2;
875 /// The debug log level. See [`console_log`].
876 pub const LOG_LEVEL_DEBUG: u8 = 3;
877 /// The trace log level. See [`console_log`].
878 pub const LOG_LEVEL_TRACE: u8 = 4;
879 /// The panic log level. See [`console_log`].
880 ///
881 /// A panic level is emitted just before a fatal error causes the WASM module to trap.
882 pub const LOG_LEVEL_PANIC: u8 = 101;
883
884 /// A handle into a buffer of bytes in the host environment that can be read from.
885 ///
886 /// Used for transporting bytes from host to WASM linear memory.
887 #[derive(PartialEq, Eq, Copy, Clone)]
888 #[repr(transparent)]
889 pub struct BytesSource(u32);
890
891 impl BytesSource {
892 /// An invalid handle, used e.g., when the reducer arguments were empty.
893 pub const INVALID: Self = Self(0);
894 }
895
896 /// A handle into a buffer of bytes in the host environment that can be written to.
897 ///
898 /// Used for transporting bytes from WASM linear memory to host.
899 #[derive(PartialEq, Eq, Copy, Clone)]
900 #[repr(transparent)]
901 pub struct BytesSink(u32);
902
903 /// Represents table iterators.
904 #[derive(PartialEq, Eq, Copy, Clone)]
905 #[repr(transparent)]
906 pub struct RowIter(u32);
907
908 impl RowIter {
909 /// An invalid handle, used e.g., when the iterator has been exhausted.
910 pub const INVALID: Self = Self(0);
911 }
912
913 #[cfg(any())]
914 mod module_exports {
915 type Encoded<T> = Buffer;
916 type Identity = Encoded<[u8; 32]>;
917 /// Microseconds since the unix epoch
918 type Timestamp = u64;
919 /// Buffer::INVALID => Ok(()); else errmsg => Err(errmsg)
920 type Result = Buffer;
921 extern "C" {
922 /// All functions prefixed with `__preinit__` are run first in alphabetical order.
923 /// For those it's recommended to use /etc/xxxx.d conventions of like `__preinit__20_do_thing`:
924 /// <https://man7.org/linux/man-pages/man5/sysctl.d.5.html#CONFIGURATION_DIRECTORIES_AND_PRECEDENCE>
925 fn __preinit__XX_XXXX();
926 /// Optional. Run after `__preinit__`; can return an error. Intended for dynamic languages; this
927 /// would be where you would initialize the interepreter and load the user module into it.
928 fn __setup__() -> Result;
929 /// Required. Runs after `__setup__`; returns all the exports for the module.
930 fn __describe_module__() -> Encoded<ModuleDef>;
931 /// Required. id is an index into the `ModuleDef.reducers` returned from `__describe_module__`.
932 /// args is a bsatn-encoded product value defined by the schema at `reducers[id]`.
933 fn __call_reducer__(
934 id: usize,
935 sender_0: u64,
936 sender_1: u64,
937 sender_2: u64,
938 sender_3: u64,
939 conn_id_0: u64,
940 conn_id_1: u64,
941 timestamp: u64,
942 args: Buffer,
943 ) -> Result;
944 /// Currently unused?
945 fn __migrate_database__XXXX(sender: Identity, timestamp: Timestamp, something: Buffer) -> Result;
946 }
947 }
948}
949
950/// Error values used in the safe bindings API.
951#[derive(Copy, Clone, PartialEq, Eq)]
952#[repr(transparent)]
953pub struct Errno(NonZeroU16);
954
955// once Error gets exposed from core this crate can be no_std again
956impl std::error::Error for Errno {}
957
958pub type Result<T, E = Errno> = core::result::Result<T, E>;
959
960macro_rules! def_errno {
961 ($($err_name:ident($errno:literal, $errmsg:literal),)*) => {
962 impl Errno {
963 $(#[doc = $errmsg] pub const $err_name: Errno = Errno(errno::$err_name);)*
964 }
965 };
966}
967errnos!(def_errno);
968
969impl Errno {
970 /// Returns a description of the errno value, if any.
971 pub const fn message(self) -> Option<&'static str> {
972 errno::strerror(self.0)
973 }
974
975 /// Converts the given `code` to an error number in `Errno`'s representation.
976 #[inline]
977 pub const fn from_code(code: u16) -> Option<Self> {
978 match NonZeroU16::new(code) {
979 Some(code) => Some(Errno(code)),
980 None => None,
981 }
982 }
983
984 /// Converts this `errno` into a primitive error code.
985 #[inline]
986 pub const fn code(self) -> u16 {
987 self.0.get()
988 }
989}
990
991impl fmt::Debug for Errno {
992 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
993 let mut fmt = f.debug_struct("Errno");
994 fmt.field("code", &self.code());
995 if let Some(msg) = self.message() {
996 fmt.field("message", &msg);
997 }
998 fmt.finish()
999 }
1000}
1001
1002impl fmt::Display for Errno {
1003 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1004 let message = self.message().unwrap_or("Unknown error");
1005 write!(f, "{message} (error {})", self.code())
1006 }
1007}
1008
1009/// Convert the status value `x` into a result.
1010/// When `x = 0`, we have a success status.
1011fn cvt(x: u16) -> Result<()> {
1012 match Errno::from_code(x) {
1013 None => Ok(()),
1014 Some(err) => Err(err),
1015 }
1016}
1017
1018/// Runs the given function `f` provided with an uninitialized `out` pointer.
1019///
1020/// Assuming the call to `f` succeeds (`Ok(_)`), the `out` pointer's value is returned.
1021///
1022/// # Safety
1023///
1024/// This function is safe to call, if and only if,
1025/// - The function `f` writes a safe and valid `T` to the `out` pointer.
1026/// It's not required to write to `out` when `f(out)` returns an error code.
1027/// - The function `f` never reads a safe and valid `T` from the `out` pointer
1028/// before writing a safe and valid `T` to it.
1029#[inline]
1030unsafe fn call<T: Copy>(f: impl FnOnce(*mut T) -> u16) -> Result<T> {
1031 let mut out = MaybeUninit::uninit();
1032 let f_code = f(out.as_mut_ptr());
1033 cvt(f_code)?;
1034 Ok(out.assume_init())
1035}
1036
1037/// Runs the given function `f`.
1038///
1039/// Assuming the call to `f` returns 0, `Ok(())` is returned,
1040/// and otherwise `Err(err)` is returned.
1041#[inline]
1042#[cfg(feature = "unstable")]
1043fn call_no_ret(f: impl FnOnce() -> u16) -> Result<()> {
1044 let f_code = f();
1045 cvt(f_code)?;
1046 Ok(())
1047}
1048
1049/// Queries the `table_id` associated with the given (table) `name`.
1050///
1051/// The table id is returned.
1052///
1053/// # Errors
1054///
1055/// Returns an error:
1056///
1057/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1058/// - `NO_SUCH_TABLE`, when `name` is not the name of a table.
1059#[inline]
1060pub fn table_id_from_name(name: &str) -> Result<TableId> {
1061 unsafe { call(|out| raw::table_id_from_name(name.as_ptr(), name.len(), out)) }
1062}
1063
1064/// Queries the `index_id` associated with the given (index) `name`.
1065///
1066/// The index id is returned.
1067///
1068/// # Errors
1069///
1070/// Returns an error:
1071///
1072/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1073/// - `NO_SUCH_INDEX`, when `name` is not the name of an index.
1074#[inline]
1075pub fn index_id_from_name(name: &str) -> Result<IndexId> {
1076 unsafe { call(|out| raw::index_id_from_name(name.as_ptr(), name.len(), out)) }
1077}
1078
1079/// Returns the number of rows currently in table identified by `table_id`.
1080///
1081/// # Errors
1082///
1083/// Returns an error:
1084///
1085/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1086/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
1087#[inline]
1088pub fn datastore_table_row_count(table_id: TableId) -> Result<u64> {
1089 unsafe { call(|out| raw::datastore_table_row_count(table_id, out)) }
1090}
1091
1092/// Inserts a row into the table identified by `table_id`,
1093/// where the row is a BSATN-encoded `ProductValue`
1094/// matching the table's `ProductType` row-schema.
1095///
1096/// The `row` is `&mut` due to auto-incrementing columns.
1097/// So `row` is written to with the inserted row re-encoded.
1098///
1099/// Returns an error if
1100/// - a table with the provided `table_id` doesn't exist
1101/// - there were unique constraint violations
1102/// - `row` doesn't decode from BSATN to a `ProductValue`
1103/// according to the `ProductType` that the table's schema specifies.
1104#[inline]
1105pub fn datastore_insert_bsatn(table_id: TableId, row: &mut [u8]) -> Result<&[u8]> {
1106 let row_ptr = row.as_mut_ptr();
1107 let row_len = &mut row.len();
1108 cvt(unsafe { raw::datastore_insert_bsatn(table_id, row_ptr, row_len) }).map(|()| &row[..*row_len])
1109}
1110
1111/// Updates a row into the table identified by `table_id`,
1112/// where the row is a BSATN-encoded `ProductValue`
1113/// matching the table's `ProductType` row-schema.
1114///
1115/// The row to update is found by projecting `row`
1116/// to the type of the *unique* index identified by `index_id`.
1117/// If no row is found, `row` is inserted.
1118///
1119/// The `row` is `&mut` due to auto-incrementing columns.
1120/// So `row` is written to with the updated row re-encoded.
1121///
1122/// Returns an error if
1123/// - a table with the provided `table_id` doesn't exist
1124/// - an index with the provided `index_id` doesn't exist or if the index was not unique.
1125/// - there were unique constraint violations
1126/// - `row` doesn't decode from BSATN to a `ProductValue`
1127/// according to the `ProductType` that the table's schema specifies
1128/// or if `row` cannot project to the index's type.
1129/// - the row was not found
1130#[inline]
1131pub fn datastore_update_bsatn(table_id: TableId, index_id: IndexId, row: &mut [u8]) -> Result<&[u8]> {
1132 let row_ptr = row.as_mut_ptr();
1133 let row_len = &mut row.len();
1134 cvt(unsafe { raw::datastore_update_bsatn(table_id, index_id, row_ptr, row_len) }).map(|()| &row[..*row_len])
1135}
1136
1137/// Deletes those rows, in the table identified by `table_id`,
1138/// that match any row in the byte string `relation`.
1139///
1140/// Matching is defined by first BSATN-decoding
1141/// the byte string pointed to at by `relation` to a `Vec<ProductValue>`
1142/// according to the row schema of the table
1143/// and then using `Ord for AlgebraicValue`.
1144/// A match happens when `Ordering::Equal` is returned from `fn cmp`.
1145/// This occurs exactly when the row's BSATN-encoding is equal to the encoding of the `ProductValue`.
1146///
1147/// The number of rows deleted is returned.
1148///
1149/// # Errors
1150///
1151/// Returns an error:
1152///
1153/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1154/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
1155/// - `BSATN_DECODE_ERROR`, when `rel` cannot be decoded to `Vec<ProductValue>`
1156/// where each `ProductValue` is typed at the `ProductType` the table's schema specifies.
1157#[inline]
1158pub fn datastore_delete_all_by_eq_bsatn(table_id: TableId, relation: &[u8]) -> Result<u32> {
1159 unsafe { call(|out| raw::datastore_delete_all_by_eq_bsatn(table_id, relation.as_ptr(), relation.len(), out)) }
1160}
1161
1162/// Starts iteration on each row, as BSATN-encoded, of a table identified by `table_id`.
1163/// Returns iterator handle is written to the `out` pointer.
1164/// This handle can be advanced by [`RowIter::read`].
1165///
1166/// # Errors
1167///
1168/// Returns an error:
1169///
1170/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1171/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
1172pub fn datastore_table_scan_bsatn(table_id: TableId) -> Result<RowIter> {
1173 let raw = unsafe { call(|out| raw::datastore_table_scan_bsatn(table_id, out))? };
1174 Ok(RowIter { raw })
1175}
1176
1177/// Finds all rows in the index identified by `index_id`,
1178/// according to the `point.
1179///
1180/// The index itself has a schema/type.
1181/// Matching defined by first BSATN-decoding `point` to that `AlgebraicType`
1182/// and then comparing the decoded `point` to the keys in the index
1183/// using `Ord for AlgebraicValue`.
1184/// to the keys in the index.
1185/// The `point` is BSATN-decoded to that `AlgebraicType`.
1186/// A match happens when `Ordering::Equal` is returned from `fn cmp`.
1187/// This occurs exactly when the row's BSATN-encoding
1188/// is equal to the encoding of the `AlgebraicValue`.
1189///
1190/// This ABI is not limited to single column indices.
1191/// Multi-column indices can be queried by providing
1192/// a BSATN-encoded `ProductValue`
1193/// that is typed at the `ProductType` of the index.
1194///
1195/// The relevant table for the index is found implicitly via the `index_id`,
1196/// which is unique for the module.
1197///
1198/// On success, the iterator handle is written to the `out` pointer.
1199/// This handle can be advanced by [`RowIter::read`].
1200///
1201/// # Errors
1202///
1203/// Returns an error:
1204///
1205/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1206/// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
1207/// - `WRONG_INDEX_ALGO` if the index is not a range-compatible index.
1208/// - `BSATN_DECODE_ERROR`, when `point` cannot be decoded to an `AlgebraicValue`
1209/// typed at the index's key type (`AlgebraicType`).
1210pub fn datastore_index_scan_point_bsatn(index_id: IndexId, point: &[u8]) -> Result<RowIter> {
1211 let raw = unsafe { call(|out| raw::datastore_index_scan_point_bsatn(index_id, point.as_ptr(), point.len(), out))? };
1212 Ok(RowIter { raw })
1213}
1214
1215/// Finds all rows in the index identified by `index_id`,
1216/// according to the `prefix`, `rstart`, and `rend`.
1217///
1218/// The index itself has a schema/type.
1219/// The `prefix` is decoded to the initial `prefix_elems` `AlgebraicType`s
1220/// whereas `rstart` and `rend` are decoded to the `prefix_elems + 1` `AlgebraicType`
1221/// where the `AlgebraicValue`s are wrapped in `Bound`.
1222/// That is, `rstart, rend` are BSATN-encoded `Bound<AlgebraicValue>`s.
1223///
1224/// Matching is then defined by equating `prefix`
1225/// to the initial `prefix_elems` columns of the index
1226/// and then imposing `rstart` as the starting bound
1227/// and `rend` as the ending bound on the `prefix_elems + 1` column of the index.
1228/// Remaining columns of the index are then unbounded.
1229/// Note that the `prefix` in this case can be empty (`prefix_elems = 0`),
1230/// in which case this becomes a ranged index scan on a single-col index
1231/// or even a full table scan if `rstart` and `rend` are both unbounded.
1232///
1233/// The relevant table for the index is found implicitly via the `index_id`,
1234/// which is unique for the module.
1235///
1236/// On success, the iterator handle is written to the `out` pointer.
1237/// This handle can be advanced by [`RowIter::read`].
1238///
1239/// # Non-obvious queries
1240///
1241/// For an index on columns `[a, b, c]`:
1242///
1243/// - `a = x, b = y` is encoded as a prefix `[x, y]`
1244/// and a range `Range::Unbounded`,
1245/// or as a prefix `[x]` and a range `rstart = rend = Range::Inclusive(y)`.
1246/// - `a = x, b = y, c = z` is encoded as a prefix `[x, y]`
1247/// and a range `rstart = rend = Range::Inclusive(z)`.
1248/// - A sorted full scan is encoded as an empty prefix
1249/// and a range `Range::Unbounded`.
1250///
1251/// # Errors
1252///
1253/// Returns an error:
1254///
1255/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1256/// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
1257/// - `WRONG_INDEX_ALGO` if the index is not a range-compatible index.
1258/// - `BSATN_DECODE_ERROR`, when `prefix` cannot be decoded to
1259/// a `prefix_elems` number of `AlgebraicValue`
1260/// typed at the initial `prefix_elems` `AlgebraicType`s of the index's key type.
1261/// Or when `rstart` or `rend` cannot be decoded to an `Bound<AlgebraicValue>`
1262/// where the inner `AlgebraicValue`s are
1263/// typed at the `prefix_elems + 1` `AlgebraicType` of the index's key type.
1264pub fn datastore_index_scan_range_bsatn(
1265 index_id: IndexId,
1266 prefix: &[u8],
1267 prefix_elems: ColId,
1268 rstart: &[u8],
1269 rend: &[u8],
1270) -> Result<RowIter> {
1271 let raw = unsafe {
1272 call(|out| {
1273 raw::datastore_index_scan_range_bsatn(
1274 index_id,
1275 prefix.as_ptr(),
1276 prefix.len(),
1277 prefix_elems,
1278 rstart.as_ptr(),
1279 rstart.len(),
1280 rend.as_ptr(),
1281 rend.len(),
1282 out,
1283 )
1284 })?
1285 };
1286 Ok(RowIter { raw })
1287}
1288
1289/// Deletes all rows found in the index identified by `index_id`,
1290/// according to the `point.
1291///
1292/// This syscall will delete all the rows found by
1293/// [`datastore_index_scan_point_bsatn`] with the same arguments passed.
1294/// See `datastore_index_scan_point_bsatn` for details.
1295///
1296/// The number of rows deleted is returned on success.
1297///
1298/// # Errors
1299///
1300/// Returns an error:
1301///
1302/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1303/// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
1304/// - `WRONG_INDEX_ALGO` if the index is not a range-compatible index.
1305/// - `BSATN_DECODE_ERROR`, when `point` cannot be decoded to an `AlgebraicValue`
1306/// typed at the index's key type (`AlgebraicType`).
1307pub fn datastore_delete_by_index_scan_point_bsatn(index_id: IndexId, point: &[u8]) -> Result<u32> {
1308 unsafe { call(|out| raw::datastore_delete_by_index_scan_point_bsatn(index_id, point.as_ptr(), point.len(), out)) }
1309}
1310
1311/// Deletes all rows found in the index identified by `index_id`,
1312/// according to the `prefix`, `rstart`, and `rend`.
1313///
1314/// This syscall will delete all the rows found by
1315/// [`datastore_index_scan_range_bsatn`] with the same arguments passed,
1316/// including `prefix_elems`.
1317/// See `datastore_index_scan_range_bsatn` for details.
1318///
1319/// The number of rows deleted is returned on success.
1320///
1321/// # Errors
1322///
1323/// Returns an error:
1324///
1325/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1326/// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
1327/// - `WRONG_INDEX_ALGO` if the index is not a range-compatible index.
1328/// - `BSATN_DECODE_ERROR`, when `prefix` cannot be decoded to
1329/// a `prefix_elems` number of `AlgebraicValue`
1330/// typed at the initial `prefix_elems` `AlgebraicType`s of the index's key type.
1331/// Or when `rstart` or `rend` cannot be decoded to an `Bound<AlgebraicValue>`
1332/// where the inner `AlgebraicValue`s are
1333/// typed at the `prefix_elems + 1` `AlgebraicType` of the index's key type.
1334pub fn datastore_delete_by_index_scan_range_bsatn(
1335 index_id: IndexId,
1336 prefix: &[u8],
1337 prefix_elems: ColId,
1338 rstart: &[u8],
1339 rend: &[u8],
1340) -> Result<u32> {
1341 unsafe {
1342 call(|out| {
1343 raw::datastore_delete_by_index_scan_range_bsatn(
1344 index_id,
1345 prefix.as_ptr(),
1346 prefix.len(),
1347 prefix_elems,
1348 rstart.as_ptr(),
1349 rstart.len(),
1350 rend.as_ptr(),
1351 rend.len(),
1352 out,
1353 )
1354 })
1355 }
1356}
1357
1358/// A log level that can be used in `console_log`.
1359/// The variants are convertible into a raw `u8` log level.
1360#[repr(u8)]
1361pub enum LogLevel {
1362 /// The error log level. See [`console_log`].
1363 Error = raw::LOG_LEVEL_ERROR,
1364 /// The warn log level. See [`console_log`].
1365 Warn = raw::LOG_LEVEL_WARN,
1366 /// The info log level. See [`console_log`].
1367 Info = raw::LOG_LEVEL_INFO,
1368 /// The debug log level. See [`console_log`].
1369 Debug = raw::LOG_LEVEL_DEBUG,
1370 /// The trace log level. See [`console_log`].
1371 Trace = raw::LOG_LEVEL_TRACE,
1372 /// The panic log level. See [`console_log`].
1373 ///
1374 /// A panic level is emitted just before a fatal error causes the WASM module to trap.
1375 Panic = raw::LOG_LEVEL_PANIC,
1376}
1377
1378/// Log at `level` a `text` message occurring in `filename:line_number`
1379/// with [`target`] being the module path at the `log!` invocation site.
1380///
1381/// [`target`]: https://docs.rs/log/latest/log/struct.Record.html#method.target
1382#[inline]
1383pub fn console_log(
1384 level: LogLevel,
1385 target: Option<&str>,
1386 filename: Option<&str>,
1387 line_number: Option<u32>,
1388 text: &str,
1389) {
1390 let opt_ptr = |b: Option<&str>| b.map_or(ptr::null(), |b| b.as_ptr());
1391 let opt_len = |b: Option<&str>| b.map_or(0, |b| b.len());
1392 unsafe {
1393 raw::console_log(
1394 level as u8,
1395 opt_ptr(target),
1396 opt_len(target),
1397 opt_ptr(filename),
1398 opt_len(filename),
1399 line_number.unwrap_or(u32::MAX),
1400 text.as_ptr(),
1401 text.len(),
1402 )
1403 }
1404}
1405
1406/// Schedule a reducer to be called asynchronously, nonatomically, and immediately
1407/// on a best-effort basis.
1408///
1409/// The reducer is assigned `name` and is provided `args` as its argument.
1410#[cfg(feature = "unstable")]
1411#[inline]
1412pub fn volatile_nonatomic_schedule_immediate(name: &str, args: &[u8]) {
1413 unsafe { raw::volatile_nonatomic_schedule_immediate(name.as_ptr(), name.len(), args.as_ptr(), args.len()) }
1414}
1415
1416/// Read the current module's identity, as a little-endian byte array.
1417///
1418/// Doesn't return a proper typed `Identity` because this crate doesn't depend on `spacetimedb_lib`.
1419#[inline]
1420pub fn identity() -> [u8; 32] {
1421 let mut buf = [0u8; 32];
1422 unsafe {
1423 raw::identity(buf.as_mut_ptr());
1424 }
1425 buf
1426}
1427
1428/// Finds the JWT payload associated with `connection_id`.
1429/// If nothing is found for the connection, this returns None.
1430/// If a payload is found, this will return a valid [`raw::BytesSource`].
1431///
1432/// This must be called inside a transaction (because it reads from a system table).
1433///
1434/// # Errors
1435///
1436/// This panics on any error. You can see details about errors in [`raw::get_jwt`].
1437#[inline]
1438pub fn get_jwt(connection_id: [u8; 16]) -> Option<raw::BytesSource> {
1439 let source = unsafe {
1440 call(|out| raw::get_jwt(connection_id.as_ptr(), out))
1441 .unwrap_or_else(|errno: Errno| panic!("Error getting jwt: {errno}"))
1442 };
1443
1444 if source == raw::BytesSource::INVALID {
1445 None // No JWT found.
1446 } else {
1447 Some(source)
1448 }
1449}
1450
1451pub struct RowIter {
1452 raw: raw::RowIter,
1453}
1454
1455impl RowIter {
1456 /// Read some number of BSATN-encoded rows into the provided buffer.
1457 ///
1458 /// Returns the number of new bytes added to the end of the buffer.
1459 /// When the iterator has been exhausted,
1460 /// `self.is_exhausted()` will return `true`.
1461 pub fn read(&mut self, buf: &mut Vec<u8>) -> usize {
1462 loop {
1463 let buf_ptr = buf.spare_capacity_mut();
1464 let mut buf_len = buf_ptr.len();
1465 let ret = unsafe { raw::row_iter_bsatn_advance(self.raw, buf_ptr.as_mut_ptr().cast(), &mut buf_len) };
1466 if let -1 | 0 = ret {
1467 // SAFETY: `_row_iter_bsatn_advance` just wrote `buf_len` bytes into the end of `buf`.
1468 unsafe { buf.set_len(buf.len() + buf_len) };
1469 }
1470
1471 const TOO_SMALL: i16 = errno::BUFFER_TOO_SMALL.get() as i16;
1472 match ret {
1473 -1 => {
1474 self.raw = raw::RowIter::INVALID;
1475 return buf_len;
1476 }
1477 0 => return buf_len,
1478 TOO_SMALL => buf.reserve(buf_len),
1479 e => panic!("unexpected error from `_row_iter_bsatn_advance`: {e}"),
1480 }
1481 }
1482 }
1483
1484 /// Returns whether the iterator is exhausted or not.
1485 pub fn is_exhausted(&self) -> bool {
1486 self.raw == raw::RowIter::INVALID
1487 }
1488}
1489
1490impl Drop for RowIter {
1491 fn drop(&mut self) {
1492 // Avoid this syscall when `_row_iter_bsatn_advance` above
1493 // notifies us that the iterator is exhausted.
1494 if self.is_exhausted() {
1495 return;
1496 }
1497 unsafe {
1498 raw::row_iter_bsatn_close(self.raw);
1499 }
1500 }
1501}
1502
1503#[cfg(feature = "unstable")]
1504pub mod procedure {
1505 //! Side-effecting or asynchronous operations which only procedures are allowed to perform.
1506
1507 use super::{call, call_no_ret, raw, Result};
1508
1509 #[inline]
1510 pub fn sleep_until(wake_at_timestamp: i64) -> i64 {
1511 // Safety: Just calling an `extern "C"` function.
1512 // Nothing weird happening here.
1513 unsafe { raw::procedure_sleep_until(wake_at_timestamp) }
1514 }
1515
1516 /// Starts a mutable transaction,
1517 /// suspending execution of this WASM instance until
1518 /// a mutable transaction lock is aquired.
1519 ///
1520 /// Upon resuming, returns `Ok(timestamp)` on success,
1521 /// enabling further calls that require a pending transaction,
1522 /// or [`Errno`] otherwise.
1523 ///
1524 /// # Errors
1525 ///
1526 /// Returns an error:
1527 ///
1528 /// - `WOULD_BLOCK_TRANSACTION`, if there's already an ongoing transaction.
1529 #[inline]
1530 pub fn procedure_start_mut_tx() -> Result<i64> {
1531 unsafe { call(|out| raw::procedure_start_mut_tx(out)) }
1532 }
1533
1534 /// Commits a mutable transaction,
1535 /// suspending execution of this WASM instance until
1536 /// the transaction has been committed
1537 /// and subscription queries have been run and broadcast.
1538 ///
1539 /// Upon resuming, returns `Ok(()` on success, or an [`Errno`] otherwise.
1540 ///
1541 /// # Errors
1542 ///
1543 /// Returns an error:
1544 ///
1545 /// - `TRANSACTION_NOT_ANONYMOUS`,
1546 /// if the transaction was not started in [`procedure_start_mut_tx`].
1547 /// This can happen if this syscall is erroneously called by a reducer.
1548 /// The code `NOT_IN_TRANSACTION` does not happen,
1549 /// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
1550 /// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
1551 /// This currently does not happen as anonymous read transactions
1552 /// are not exposed to modules.
1553 #[inline]
1554 pub fn procedure_commit_mut_tx() -> Result<()> {
1555 call_no_ret(|| unsafe { raw::procedure_commit_mut_tx() })
1556 }
1557
1558 /// Aborts a mutable transaction,
1559 /// suspending execution of this WASM instance until
1560 /// the transaction has been rolled back.
1561 ///
1562 /// Upon resuming, returns `Ok(())` on success, or an [`Errno`] otherwise.
1563 ///
1564 /// # Errors
1565 ///
1566 /// Returns an error:
1567 ///
1568 /// - `TRANSACTION_NOT_ANONYMOUS`,
1569 /// if the transaction was not started in [`procedure_start_mut_tx`].
1570 /// This can happen if this syscall is erroneously called by a reducer.
1571 /// The code `NOT_IN_TRANSACTION` does not happen,
1572 /// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
1573 /// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
1574 /// This currently does not happen as anonymous read transactions
1575 /// are not exposed to modules.
1576 #[inline]
1577 pub fn procedure_abort_mut_tx() -> Result<()> {
1578 call_no_ret(|| unsafe { raw::procedure_abort_mut_tx() })
1579 }
1580
1581 #[inline]
1582 #[cfg(feature = "unstable")]
1583 /// Perform an HTTP request as specified by `http_request_bsatn`,
1584 /// suspending execution until the request is complete,
1585 /// then return its response or error.
1586 ///
1587 /// `http_request_bsatn` should be a BSATN-serialized `spacetimedb_lib::http::Request`.
1588 ///
1589 /// If the request completes successfully,
1590 /// this function returns `Ok(bytes)`, where `bytes` contains a BSATN-serialized `spacetimedb_lib::http::Response`.
1591 /// All HTTP response codes are treated as successful for these purposes;
1592 /// this method only returns an error if it is unable to produce any HTTP response whatsoever.
1593 /// In that case, this function returns `Err(bytes)`, where `bytes` contains a BSATN-serialized `spacetimedb_lib::http::Error`.
1594 pub fn http_request(
1595 http_request_bsatn: &[u8],
1596 body: &[u8],
1597 ) -> Result<(raw::BytesSource, raw::BytesSource), raw::BytesSource> {
1598 let mut out = [raw::BytesSource::INVALID; 2];
1599
1600 let res = unsafe {
1601 super::raw::procedure_http_request(
1602 http_request_bsatn.as_ptr(),
1603 http_request_bsatn.len() as u32,
1604 body.as_ptr(),
1605 body.len() as u32,
1606 &mut out as *mut [raw::BytesSource; 2],
1607 )
1608 };
1609
1610 match super::Errno::from_code(res) {
1611 // Success: `out` is a `spacetimedb_lib::http::Response`.
1612 None => Ok((out[0], out[1])),
1613 // HTTP_ERROR: `out` is a `spacetimedb_lib::http::Error`.
1614 Some(errno) if errno == super::Errno::HTTP_ERROR => Err(out[0]),
1615 Some(errno) => panic!("{errno}"),
1616 }
1617 }
1618}