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 unsafe 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 unsafe 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 unsafe 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 unsafe 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 /// blocking until a mutable transaction lock is acquired.
668 ///
669 /// Returns `0` on success,
670 /// enabling further calls that require a pending transaction,
671 /// or an error code otherwise.
672 ///
673 /// # Traps
674 ///
675 /// Traps if:
676 /// - `out` is NULL or `out[..size_of::<i64>()]` is not in bounds of WASM memory.
677 ///
678 /// # Errors
679 ///
680 /// Returns an error:
681 ///
682 /// - `WOULD_BLOCK_TRANSACTION`, if there's already an ongoing transaction.
683 pub fn procedure_start_mut_tx(out: *mut i64) -> u16;
684
685 /// Commits a mutable transaction,
686 /// blocking until the transaction has been committed
687 /// and subscription queries have been run and broadcast.
688 ///
689 /// Once complete, it returns `0` on success, or an error code otherwise.
690 ///
691 /// # Traps
692 ///
693 /// This function does not trap.
694 ///
695 /// # Errors
696 ///
697 /// Returns an error:
698 ///
699 /// - `TRANSACTION_NOT_ANONYMOUS`,
700 /// if the transaction was not started in [`procedure_start_mut_tx`].
701 /// This can happen if this syscall is erroneously called by a reducer.
702 /// The code `NOT_IN_TRANSACTION` does not happen,
703 /// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
704 /// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
705 /// This currently does not happen as anonymous read transactions
706 /// are not exposed to modules.
707 pub fn procedure_commit_mut_tx() -> u16;
708
709 /// Aborts a mutable transaction,
710 /// blocking until the transaction has been aborted.
711 ///
712 /// Returns `0` on success, or an error code otherwise.
713 ///
714 /// # Traps
715 ///
716 /// This function does not trap.
717 ///
718 /// # Errors
719 ///
720 /// Returns an error:
721 ///
722 /// - `TRANSACTION_NOT_ANONYMOUS`,
723 /// if the transaction was not started in [`procedure_start_mut_tx`].
724 /// This can happen if this syscall is erroneously called by a reducer.
725 /// The code `NOT_IN_TRANSACTION` does not happen,
726 /// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
727 /// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
728 /// This currently does not happen as anonymous read transactions
729 /// are not exposed to modules.
730 pub fn procedure_abort_mut_tx() -> u16;
731
732 /// Perform an HTTP request as specified by the buffer `request_ptr[..request_len]`,
733 /// suspending execution until the request is complete,
734 /// then return its response details via a [`BytesSource`] written to `out[0]`
735 /// and its response body via another [`BytesSource`] written to `out[1]`.
736 ///
737 /// `request_ptr[..request_len]` should store a BSATN-serialized `spacetimedb_lib::http::Request` object
738 /// containing the details of the request to be performed.
739 ///
740 /// `body_ptr[..body_len]` should store a byte array, which will be treated as the body of the request.
741 /// `body_ptr` should be non-null and within the bounds of linear memory even when `body_len` is 0.
742 ///
743 /// If the request is successful, a [`BytesSource`] is written to `out[0]`
744 /// containing a BSATN-encoded `spacetimedb_lib::http::Response` object,
745 /// another [`BytesSource`] containing the bytes of the response body are written to `out[1]`,
746 /// and this function returns 0.
747 ///
748 /// "Successful" in this context includes any connection which results in any HTTP status code,
749 /// regardless of the specified meaning of that code.
750 /// This includes HTTP error codes such as 404 Not Found and 500 Internal Server Error.
751 ///
752 /// If the request fails, a [`BytesSource`] is written to `out[0]`
753 /// containing a BSATN-encoded [`String`] describing the failure,
754 /// and this function returns `HTTP_ERROR`.
755 /// In this case, `out[1]` is not written.
756 ///
757 /// # Errors
758 ///
759 /// Returns an error:
760 ///
761 /// - `WOULD_BLOCK_TRANSACTION` if there is currently a transaction open.
762 /// In this case, `out` is not written.
763 /// - `BSATN_DECODE_ERROR` if `request_ptr[..request_len]` does not contain
764 /// a valid BSATN-serialized `spacetimedb_lib::http::Request` object.
765 /// In this case, `out` is not written.
766 /// - `HTTP_ERROR` if an error occurs while executing the HTTP request.
767 /// In this case, a [`BytesSource`] is written to `out`
768 /// containing a BSATN-encoded `spacetimedb_lib::http::Error` object.
769 ///
770 /// # Traps
771 ///
772 /// Traps if:
773 ///
774 /// - `request_ptr` is NULL or `request_ptr[..request_len]` is not in bounds of WASM memory.
775 /// - `body_ptr` is NULL or `body_ptr[..body_len]` is not in bounds of WASM memory.
776 /// - `out` is NULL or `out[..size_of::<RowIter>()]` is not in bounds of WASM memory.
777 /// - `request_ptr[..request_len]` does not contain a valid BSATN-serialized `spacetimedb_lib::http::Request` object.
778 #[cfg(feature = "unstable")]
779 pub fn procedure_http_request(
780 request_ptr: *const u8,
781 request_len: u32,
782 body_ptr: *const u8,
783 body_len: u32,
784 out: *mut [BytesSource; 2],
785 ) -> u16;
786 }
787
788 #[link(wasm_import_module = "spacetime_10.4")]
789 unsafe extern "C" {
790 /// Finds all rows in the index identified by `index_id`,
791 /// according to `point = point_ptr[..point_len]` in WASM memory.
792 ///
793 /// The index itself has a schema/type.
794 /// Matching defined by first BSATN-decoding `point` to that `AlgebraicType`
795 /// and then comparing the decoded `point` to the keys in the index
796 /// using `Ord for AlgebraicValue`.
797 /// to the keys in the index.
798 /// The `point` is BSATN-decoded to that `AlgebraicType`.
799 /// A match happens when `Ordering::Equal` is returned from `fn cmp`.
800 /// This occurs exactly when the row's BSATN-encoding
801 /// is equal to the encoding of the `AlgebraicValue`.
802 ///
803 /// This ABI is not limited to single column indices.
804 /// Multi-column indices can be queried by providing
805 /// a BSATN-encoded `ProductValue`
806 /// that is typed at the `ProductType` of the index.
807 ///
808 /// The relevant table for the index is found implicitly via the `index_id`,
809 /// which is unique for the module.
810 ///
811 /// On success, the iterator handle is written to the `out` pointer.
812 /// This handle can be advanced by [`row_iter_bsatn_advance`].
813 ///
814 /// # Traps
815 ///
816 /// Traps if:
817 /// - `point_ptr` is NULL or `point` is not in bounds of WASM memory.
818 /// - `out` is NULL or `out[..size_of::<RowIter>()]` is not in bounds of WASM memory.
819 ///
820 /// # Errors
821 ///
822 /// Returns an error:
823 ///
824 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
825 /// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
826 /// - `WRONG_INDEX_ALGO` if the index is not a range-scan compatible index.
827 /// - `BSATN_DECODE_ERROR`, when `point` cannot be decoded to an `AlgebraicValue`
828 /// typed at the index's key type (`AlgebraicType`).
829 pub fn datastore_index_scan_point_bsatn(
830 index_id: IndexId,
831 point_ptr: *const u8, // AlgebraicValue
832 point_len: usize,
833 out: *mut RowIter,
834 ) -> u16;
835
836 /// Deletes all rows found in the index identified by `index_id`,
837 /// according to `point = point_ptr[..point_len]` in WASM memory.
838 ///
839 /// This syscall will delete all the rows found by
840 /// [`datastore_index_scan_point_bsatn`] with the same arguments passed.
841 /// See `datastore_index_scan_point_bsatn` for details.
842 ///
843 /// The number of rows deleted is written to the WASM pointer `out`.
844 ///
845 /// # Traps
846 ///
847 /// Traps if:
848 /// - `point_ptr` is NULL or `point` is not in bounds of WASM memory.
849 /// - `out` is NULL or `out[..size_of::<u32>()]` is not in bounds of WASM memory.
850 ///
851 /// # Errors
852 ///
853 /// Returns an error:
854 ///
855 /// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
856 /// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
857 /// - `WRONG_INDEX_ALGO` if the index is not a range-compatible index.
858 /// - `BSATN_DECODE_ERROR`, when `point` cannot be decoded to an `AlgebraicValue`
859 /// typed at the index's key type (`AlgebraicType`).
860 pub fn datastore_delete_by_index_scan_point_bsatn(
861 index_id: IndexId,
862 point_ptr: *const u8, // AlgebraicValue
863 point_len: usize,
864 out: *mut u32,
865 ) -> u16;
866 }
867
868 /// What strategy does the database index use?
869 ///
870 /// See also: <https://www.postgresql.org/docs/current/sql-createindex.html>
871 #[repr(u8)]
872 #[non_exhaustive]
873 pub enum IndexType {
874 /// Indexing works by putting the index key into a b-tree.
875 BTree = 0,
876 /// Indexing works by hashing the index key.
877 Hash = 1,
878 }
879
880 /// The error log level. See [`console_log`].
881 pub const LOG_LEVEL_ERROR: u8 = 0;
882 /// The warn log level. See [`console_log`].
883 pub const LOG_LEVEL_WARN: u8 = 1;
884 /// The info log level. See [`console_log`].
885 pub const LOG_LEVEL_INFO: u8 = 2;
886 /// The debug log level. See [`console_log`].
887 pub const LOG_LEVEL_DEBUG: u8 = 3;
888 /// The trace log level. See [`console_log`].
889 pub const LOG_LEVEL_TRACE: u8 = 4;
890 /// The panic log level. See [`console_log`].
891 ///
892 /// A panic level is emitted just before a fatal error causes the WASM module to trap.
893 pub const LOG_LEVEL_PANIC: u8 = 101;
894
895 /// A handle into a buffer of bytes in the host environment that can be read from.
896 ///
897 /// Used for transporting bytes from host to WASM linear memory.
898 #[derive(PartialEq, Eq, Copy, Clone)]
899 #[repr(transparent)]
900 pub struct BytesSource(u32);
901
902 impl BytesSource {
903 /// An invalid handle, used e.g., when the reducer arguments were empty.
904 pub const INVALID: Self = Self(0);
905 }
906
907 /// A handle into a buffer of bytes in the host environment that can be written to.
908 ///
909 /// Used for transporting bytes from WASM linear memory to host.
910 #[derive(PartialEq, Eq, Copy, Clone)]
911 #[repr(transparent)]
912 pub struct BytesSink(u32);
913
914 /// Represents table iterators.
915 #[derive(PartialEq, Eq, Copy, Clone)]
916 #[repr(transparent)]
917 pub struct RowIter(u32);
918
919 impl RowIter {
920 /// An invalid handle, used e.g., when the iterator has been exhausted.
921 pub const INVALID: Self = Self(0);
922 }
923
924 #[cfg(any())]
925 mod module_exports {
926 type Encoded<T> = Buffer;
927 type Identity = Encoded<[u8; 32]>;
928 /// Microseconds since the unix epoch
929 type Timestamp = u64;
930 /// Buffer::INVALID => Ok(()); else errmsg => Err(errmsg)
931 type Result = Buffer;
932 extern "C" {
933 /// All functions prefixed with `__preinit__` are run first in alphabetical order.
934 /// For those it's recommended to use /etc/xxxx.d conventions of like `__preinit__20_do_thing`:
935 /// <https://man7.org/linux/man-pages/man5/sysctl.d.5.html#CONFIGURATION_DIRECTORIES_AND_PRECEDENCE>
936 fn __preinit__XX_XXXX();
937 /// Optional. Run after `__preinit__`; can return an error. Intended for dynamic languages; this
938 /// would be where you would initialize the interepreter and load the user module into it.
939 fn __setup__() -> Result;
940 /// Required. Runs after `__setup__`; returns all the exports for the module.
941 fn __describe_module__() -> Encoded<ModuleDef>;
942 /// Required. id is an index into the `ModuleDef.reducers` returned from `__describe_module__`.
943 /// args is a bsatn-encoded product value defined by the schema at `reducers[id]`.
944 fn __call_reducer__(
945 id: usize,
946 sender_0: u64,
947 sender_1: u64,
948 sender_2: u64,
949 sender_3: u64,
950 conn_id_0: u64,
951 conn_id_1: u64,
952 timestamp: u64,
953 args: Buffer,
954 ) -> Result;
955 /// Currently unused?
956 fn __migrate_database__XXXX(sender: Identity, timestamp: Timestamp, something: Buffer) -> Result;
957 }
958 }
959}
960
961/// Error values used in the safe bindings API.
962#[derive(Copy, Clone, PartialEq, Eq)]
963#[repr(transparent)]
964pub struct Errno(NonZeroU16);
965
966// once Error gets exposed from core this crate can be no_std again
967impl std::error::Error for Errno {}
968
969pub type Result<T, E = Errno> = core::result::Result<T, E>;
970
971macro_rules! def_errno {
972 ($($err_name:ident($errno:literal, $errmsg:literal),)*) => {
973 impl Errno {
974 $(#[doc = $errmsg] pub const $err_name: Errno = Errno(errno::$err_name);)*
975 }
976 };
977}
978errnos!(def_errno);
979
980impl Errno {
981 /// Returns a description of the errno value, if any.
982 pub const fn message(self) -> Option<&'static str> {
983 errno::strerror(self.0)
984 }
985
986 /// Converts the given `code` to an error number in `Errno`'s representation.
987 #[inline]
988 pub const fn from_code(code: u16) -> Option<Self> {
989 match NonZeroU16::new(code) {
990 Some(code) => Some(Errno(code)),
991 None => None,
992 }
993 }
994
995 /// Converts this `errno` into a primitive error code.
996 #[inline]
997 pub const fn code(self) -> u16 {
998 self.0.get()
999 }
1000}
1001
1002impl fmt::Debug for Errno {
1003 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1004 let mut fmt = f.debug_struct("Errno");
1005 fmt.field("code", &self.code());
1006 if let Some(msg) = self.message() {
1007 fmt.field("message", &msg);
1008 }
1009 fmt.finish()
1010 }
1011}
1012
1013impl fmt::Display for Errno {
1014 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1015 let message = self.message().unwrap_or("Unknown error");
1016 write!(f, "{message} (error {})", self.code())
1017 }
1018}
1019
1020/// Convert the status value `x` into a result.
1021/// When `x = 0`, we have a success status.
1022fn cvt(x: u16) -> Result<()> {
1023 match Errno::from_code(x) {
1024 None => Ok(()),
1025 Some(err) => Err(err),
1026 }
1027}
1028
1029/// Runs the given function `f` provided with an uninitialized `out` pointer.
1030///
1031/// Assuming the call to `f` succeeds (`Ok(_)`), the `out` pointer's value is returned.
1032///
1033/// # Safety
1034///
1035/// This function is safe to call, if and only if,
1036/// - The function `f` writes a safe and valid `T` to the `out` pointer.
1037/// It's not required to write to `out` when `f(out)` returns an error code.
1038/// - The function `f` never reads a safe and valid `T` from the `out` pointer
1039/// before writing a safe and valid `T` to it.
1040#[inline]
1041unsafe fn call<T: Copy>(f: impl FnOnce(*mut T) -> u16) -> Result<T> {
1042 unsafe {
1043 let mut out = MaybeUninit::uninit();
1044 let f_code = f(out.as_mut_ptr());
1045 cvt(f_code)?;
1046 Ok(out.assume_init())
1047 }
1048}
1049
1050/// Runs the given function `f`.
1051///
1052/// Assuming the call to `f` returns 0, `Ok(())` is returned,
1053/// and otherwise `Err(err)` is returned.
1054#[inline]
1055#[cfg(feature = "unstable")]
1056fn call_no_ret(f: impl FnOnce() -> u16) -> Result<()> {
1057 let f_code = f();
1058 cvt(f_code)?;
1059 Ok(())
1060}
1061
1062/// Queries the `table_id` associated with the given (table) `name`.
1063///
1064/// The table id is returned.
1065///
1066/// # Errors
1067///
1068/// Returns an error:
1069///
1070/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1071/// - `NO_SUCH_TABLE`, when `name` is not the name of a table.
1072#[inline]
1073pub fn table_id_from_name(name: &str) -> Result<TableId> {
1074 unsafe { call(|out| raw::table_id_from_name(name.as_ptr(), name.len(), out)) }
1075}
1076
1077/// Queries the `index_id` associated with the given (index) `name`.
1078///
1079/// The index id is returned.
1080///
1081/// # Errors
1082///
1083/// Returns an error:
1084///
1085/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1086/// - `NO_SUCH_INDEX`, when `name` is not the name of an index.
1087#[inline]
1088pub fn index_id_from_name(name: &str) -> Result<IndexId> {
1089 unsafe { call(|out| raw::index_id_from_name(name.as_ptr(), name.len(), out)) }
1090}
1091
1092/// Returns the number of rows currently in table identified by `table_id`.
1093///
1094/// # Errors
1095///
1096/// Returns an error:
1097///
1098/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1099/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
1100#[inline]
1101pub fn datastore_table_row_count(table_id: TableId) -> Result<u64> {
1102 unsafe { call(|out| raw::datastore_table_row_count(table_id, out)) }
1103}
1104
1105/// Inserts a row into the table identified by `table_id`,
1106/// where the row is a BSATN-encoded `ProductValue`
1107/// matching the table's `ProductType` row-schema.
1108///
1109/// The `row` is `&mut` due to auto-incrementing columns.
1110/// So `row` is written to with the inserted row re-encoded.
1111///
1112/// Returns an error if
1113/// - a table with the provided `table_id` doesn't exist
1114/// - there were unique constraint violations
1115/// - `row` doesn't decode from BSATN to a `ProductValue`
1116/// according to the `ProductType` that the table's schema specifies.
1117#[inline]
1118pub fn datastore_insert_bsatn(table_id: TableId, row: &mut [u8]) -> Result<&[u8]> {
1119 let row_ptr = row.as_mut_ptr();
1120 let row_len = &mut row.len();
1121 cvt(unsafe { raw::datastore_insert_bsatn(table_id, row_ptr, row_len) }).map(|()| &row[..*row_len])
1122}
1123
1124/// Updates a row into the table identified by `table_id`,
1125/// where the row is a BSATN-encoded `ProductValue`
1126/// matching the table's `ProductType` row-schema.
1127///
1128/// The row to update is found by projecting `row`
1129/// to the type of the *unique* index identified by `index_id`.
1130/// If no row is found, `row` is inserted.
1131///
1132/// The `row` is `&mut` due to auto-incrementing columns.
1133/// So `row` is written to with the updated row re-encoded.
1134///
1135/// Returns an error if
1136/// - a table with the provided `table_id` doesn't exist
1137/// - an index with the provided `index_id` doesn't exist or if the index was not unique.
1138/// - there were unique constraint violations
1139/// - `row` doesn't decode from BSATN to a `ProductValue`
1140/// according to the `ProductType` that the table's schema specifies
1141/// or if `row` cannot project to the index's type.
1142/// - the row was not found
1143#[inline]
1144pub fn datastore_update_bsatn(table_id: TableId, index_id: IndexId, row: &mut [u8]) -> Result<&[u8]> {
1145 let row_ptr = row.as_mut_ptr();
1146 let row_len = &mut row.len();
1147 cvt(unsafe { raw::datastore_update_bsatn(table_id, index_id, row_ptr, row_len) }).map(|()| &row[..*row_len])
1148}
1149
1150/// Deletes those rows, in the table identified by `table_id`,
1151/// that match any row in the byte string `relation`.
1152///
1153/// Matching is defined by first BSATN-decoding
1154/// the byte string pointed to at by `relation` to a `Vec<ProductValue>`
1155/// according to the row schema of the table
1156/// and then using `Ord for AlgebraicValue`.
1157/// A match happens when `Ordering::Equal` is returned from `fn cmp`.
1158/// This occurs exactly when the row's BSATN-encoding is equal to the encoding of the `ProductValue`.
1159///
1160/// The number of rows deleted is returned.
1161///
1162/// # Errors
1163///
1164/// Returns an error:
1165///
1166/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1167/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
1168/// - `BSATN_DECODE_ERROR`, when `rel` cannot be decoded to `Vec<ProductValue>`
1169/// where each `ProductValue` is typed at the `ProductType` the table's schema specifies.
1170#[inline]
1171pub fn datastore_delete_all_by_eq_bsatn(table_id: TableId, relation: &[u8]) -> Result<u32> {
1172 unsafe { call(|out| raw::datastore_delete_all_by_eq_bsatn(table_id, relation.as_ptr(), relation.len(), out)) }
1173}
1174
1175/// Starts iteration on each row, as BSATN-encoded, of a table identified by `table_id`.
1176/// Returns iterator handle is written to the `out` pointer.
1177/// This handle can be advanced by [`RowIter::read`].
1178///
1179/// # Errors
1180///
1181/// Returns an error:
1182///
1183/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1184/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
1185pub fn datastore_table_scan_bsatn(table_id: TableId) -> Result<RowIter> {
1186 let raw = unsafe { call(|out| raw::datastore_table_scan_bsatn(table_id, out))? };
1187 Ok(RowIter { raw })
1188}
1189
1190/// Finds all rows in the index identified by `index_id`,
1191/// according to the `point.
1192///
1193/// The index itself has a schema/type.
1194/// Matching defined by first BSATN-decoding `point` to that `AlgebraicType`
1195/// and then comparing the decoded `point` to the keys in the index
1196/// using `Ord for AlgebraicValue`.
1197/// to the keys in the index.
1198/// The `point` is BSATN-decoded to that `AlgebraicType`.
1199/// A match happens when `Ordering::Equal` is returned from `fn cmp`.
1200/// This occurs exactly when the row's BSATN-encoding
1201/// is equal to the encoding of the `AlgebraicValue`.
1202///
1203/// This ABI is not limited to single column indices.
1204/// Multi-column indices can be queried by providing
1205/// a BSATN-encoded `ProductValue`
1206/// that is typed at the `ProductType` of the index.
1207///
1208/// The relevant table for the index is found implicitly via the `index_id`,
1209/// which is unique for the module.
1210///
1211/// On success, the iterator handle is written to the `out` pointer.
1212/// This handle can be advanced by [`RowIter::read`].
1213///
1214/// # Errors
1215///
1216/// Returns an error:
1217///
1218/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1219/// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
1220/// - `WRONG_INDEX_ALGO` if the index is not a range-compatible index.
1221/// - `BSATN_DECODE_ERROR`, when `point` cannot be decoded to an `AlgebraicValue`
1222/// typed at the index's key type (`AlgebraicType`).
1223pub fn datastore_index_scan_point_bsatn(index_id: IndexId, point: &[u8]) -> Result<RowIter> {
1224 let raw = unsafe { call(|out| raw::datastore_index_scan_point_bsatn(index_id, point.as_ptr(), point.len(), out))? };
1225 Ok(RowIter { raw })
1226}
1227
1228/// Finds all rows in the index identified by `index_id`,
1229/// according to the `prefix`, `rstart`, and `rend`.
1230///
1231/// The index itself has a schema/type.
1232/// The `prefix` is decoded to the initial `prefix_elems` `AlgebraicType`s
1233/// whereas `rstart` and `rend` are decoded to the `prefix_elems + 1` `AlgebraicType`
1234/// where the `AlgebraicValue`s are wrapped in `Bound`.
1235/// That is, `rstart, rend` are BSATN-encoded `Bound<AlgebraicValue>`s.
1236///
1237/// Matching is then defined by equating `prefix`
1238/// to the initial `prefix_elems` columns of the index
1239/// and then imposing `rstart` as the starting bound
1240/// and `rend` as the ending bound on the `prefix_elems + 1` column of the index.
1241/// Remaining columns of the index are then unbounded.
1242/// Note that the `prefix` in this case can be empty (`prefix_elems = 0`),
1243/// in which case this becomes a ranged index scan on a single-col index
1244/// or even a full table scan if `rstart` and `rend` are both unbounded.
1245///
1246/// The relevant table for the index is found implicitly via the `index_id`,
1247/// which is unique for the module.
1248///
1249/// On success, the iterator handle is written to the `out` pointer.
1250/// This handle can be advanced by [`RowIter::read`].
1251///
1252/// # Non-obvious queries
1253///
1254/// For an index on columns `[a, b, c]`:
1255///
1256/// - `a = x, b = y` is encoded as a prefix `[x, y]`
1257/// and a range `Range::Unbounded`,
1258/// or as a prefix `[x]` and a range `rstart = rend = Range::Inclusive(y)`.
1259/// - `a = x, b = y, c = z` is encoded as a prefix `[x, y]`
1260/// and a range `rstart = rend = Range::Inclusive(z)`.
1261/// - A sorted full scan is encoded as an empty prefix
1262/// and a range `Range::Unbounded`.
1263///
1264/// # Errors
1265///
1266/// Returns an error:
1267///
1268/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1269/// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
1270/// - `WRONG_INDEX_ALGO` if the index is not a range-compatible index.
1271/// - `BSATN_DECODE_ERROR`, when `prefix` cannot be decoded to
1272/// a `prefix_elems` number of `AlgebraicValue`
1273/// typed at the initial `prefix_elems` `AlgebraicType`s of the index's key type.
1274/// Or when `rstart` or `rend` cannot be decoded to an `Bound<AlgebraicValue>`
1275/// where the inner `AlgebraicValue`s are
1276/// typed at the `prefix_elems + 1` `AlgebraicType` of the index's key type.
1277pub fn datastore_index_scan_range_bsatn(
1278 index_id: IndexId,
1279 prefix: &[u8],
1280 prefix_elems: ColId,
1281 rstart: &[u8],
1282 rend: &[u8],
1283) -> Result<RowIter> {
1284 let raw = unsafe {
1285 call(|out| {
1286 raw::datastore_index_scan_range_bsatn(
1287 index_id,
1288 prefix.as_ptr(),
1289 prefix.len(),
1290 prefix_elems,
1291 rstart.as_ptr(),
1292 rstart.len(),
1293 rend.as_ptr(),
1294 rend.len(),
1295 out,
1296 )
1297 })?
1298 };
1299 Ok(RowIter { raw })
1300}
1301
1302/// Deletes all rows found in the index identified by `index_id`,
1303/// according to the `point.
1304///
1305/// This syscall will delete all the rows found by
1306/// [`datastore_index_scan_point_bsatn`] with the same arguments passed.
1307/// See `datastore_index_scan_point_bsatn` for details.
1308///
1309/// The number of rows deleted is returned on success.
1310///
1311/// # Errors
1312///
1313/// Returns an error:
1314///
1315/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1316/// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
1317/// - `WRONG_INDEX_ALGO` if the index is not a range-compatible index.
1318/// - `BSATN_DECODE_ERROR`, when `point` cannot be decoded to an `AlgebraicValue`
1319/// typed at the index's key type (`AlgebraicType`).
1320pub fn datastore_delete_by_index_scan_point_bsatn(index_id: IndexId, point: &[u8]) -> Result<u32> {
1321 unsafe { call(|out| raw::datastore_delete_by_index_scan_point_bsatn(index_id, point.as_ptr(), point.len(), out)) }
1322}
1323
1324/// Deletes all rows found in the index identified by `index_id`,
1325/// according to the `prefix`, `rstart`, and `rend`.
1326///
1327/// This syscall will delete all the rows found by
1328/// [`datastore_index_scan_range_bsatn`] with the same arguments passed,
1329/// including `prefix_elems`.
1330/// See `datastore_index_scan_range_bsatn` for details.
1331///
1332/// The number of rows deleted is returned on success.
1333///
1334/// # Errors
1335///
1336/// Returns an error:
1337///
1338/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
1339/// - `NO_SUCH_INDEX`, when `index_id` is not a known ID of an index.
1340/// - `WRONG_INDEX_ALGO` if the index is not a range-compatible index.
1341/// - `BSATN_DECODE_ERROR`, when `prefix` cannot be decoded to
1342/// a `prefix_elems` number of `AlgebraicValue`
1343/// typed at the initial `prefix_elems` `AlgebraicType`s of the index's key type.
1344/// Or when `rstart` or `rend` cannot be decoded to an `Bound<AlgebraicValue>`
1345/// where the inner `AlgebraicValue`s are
1346/// typed at the `prefix_elems + 1` `AlgebraicType` of the index's key type.
1347pub fn datastore_delete_by_index_scan_range_bsatn(
1348 index_id: IndexId,
1349 prefix: &[u8],
1350 prefix_elems: ColId,
1351 rstart: &[u8],
1352 rend: &[u8],
1353) -> Result<u32> {
1354 unsafe {
1355 call(|out| {
1356 raw::datastore_delete_by_index_scan_range_bsatn(
1357 index_id,
1358 prefix.as_ptr(),
1359 prefix.len(),
1360 prefix_elems,
1361 rstart.as_ptr(),
1362 rstart.len(),
1363 rend.as_ptr(),
1364 rend.len(),
1365 out,
1366 )
1367 })
1368 }
1369}
1370
1371/// A log level that can be used in `console_log`.
1372/// The variants are convertible into a raw `u8` log level.
1373#[repr(u8)]
1374pub enum LogLevel {
1375 /// The error log level. See [`console_log`].
1376 Error = raw::LOG_LEVEL_ERROR,
1377 /// The warn log level. See [`console_log`].
1378 Warn = raw::LOG_LEVEL_WARN,
1379 /// The info log level. See [`console_log`].
1380 Info = raw::LOG_LEVEL_INFO,
1381 /// The debug log level. See [`console_log`].
1382 Debug = raw::LOG_LEVEL_DEBUG,
1383 /// The trace log level. See [`console_log`].
1384 Trace = raw::LOG_LEVEL_TRACE,
1385 /// The panic log level. See [`console_log`].
1386 ///
1387 /// A panic level is emitted just before a fatal error causes the WASM module to trap.
1388 Panic = raw::LOG_LEVEL_PANIC,
1389}
1390
1391/// Log at `level` a `text` message occurring in `filename:line_number`
1392/// with [`target`] being the module path at the `log!` invocation site.
1393///
1394/// [`target`]: https://docs.rs/log/latest/log/struct.Record.html#method.target
1395#[inline]
1396pub fn console_log(
1397 level: LogLevel,
1398 target: Option<&str>,
1399 filename: Option<&str>,
1400 line_number: Option<u32>,
1401 text: &str,
1402) {
1403 let opt_ptr = |b: Option<&str>| b.map_or(ptr::null(), |b| b.as_ptr());
1404 let opt_len = |b: Option<&str>| b.map_or(0, |b| b.len());
1405 unsafe {
1406 raw::console_log(
1407 level as u8,
1408 opt_ptr(target),
1409 opt_len(target),
1410 opt_ptr(filename),
1411 opt_len(filename),
1412 line_number.unwrap_or(u32::MAX),
1413 text.as_ptr(),
1414 text.len(),
1415 )
1416 }
1417}
1418
1419/// Schedule a reducer to be called asynchronously, nonatomically, and immediately
1420/// on a best-effort basis.
1421///
1422/// The reducer is assigned `name` and is provided `args` as its argument.
1423#[cfg(feature = "unstable")]
1424#[inline]
1425pub fn volatile_nonatomic_schedule_immediate(name: &str, args: &[u8]) {
1426 unsafe { raw::volatile_nonatomic_schedule_immediate(name.as_ptr(), name.len(), args.as_ptr(), args.len()) }
1427}
1428
1429/// Read the current module's identity, as a little-endian byte array.
1430///
1431/// Doesn't return a proper typed `Identity` because this crate doesn't depend on `spacetimedb_lib`.
1432#[inline]
1433pub fn identity() -> [u8; 32] {
1434 let mut buf = [0u8; 32];
1435 unsafe {
1436 raw::identity(buf.as_mut_ptr());
1437 }
1438 buf
1439}
1440
1441/// Finds the JWT payload associated with `connection_id`.
1442/// If nothing is found for the connection, this returns None.
1443/// If a payload is found, this will return a valid [`raw::BytesSource`].
1444///
1445/// This must be called inside a transaction (because it reads from a system table).
1446///
1447/// # Errors
1448///
1449/// This panics on any error. You can see details about errors in [`raw::get_jwt`].
1450#[inline]
1451pub fn get_jwt(connection_id: [u8; 16]) -> Option<raw::BytesSource> {
1452 let source = unsafe {
1453 call(|out| raw::get_jwt(connection_id.as_ptr(), out))
1454 .unwrap_or_else(|errno: Errno| panic!("Error getting jwt: {errno}"))
1455 };
1456
1457 if source == raw::BytesSource::INVALID {
1458 None // No JWT found.
1459 } else {
1460 Some(source)
1461 }
1462}
1463
1464pub struct RowIter {
1465 raw: raw::RowIter,
1466}
1467
1468impl RowIter {
1469 /// Read some number of BSATN-encoded rows into the provided buffer.
1470 ///
1471 /// Returns the number of new bytes added to the end of the buffer.
1472 /// When the iterator has been exhausted,
1473 /// `self.is_exhausted()` will return `true`.
1474 pub fn read(&mut self, buf: &mut Vec<u8>) -> usize {
1475 loop {
1476 let buf_ptr = buf.spare_capacity_mut();
1477 let mut buf_len = buf_ptr.len();
1478 let ret = unsafe { raw::row_iter_bsatn_advance(self.raw, buf_ptr.as_mut_ptr().cast(), &mut buf_len) };
1479 if let -1 | 0 = ret {
1480 // SAFETY: `_row_iter_bsatn_advance` just wrote `buf_len` bytes into the end of `buf`.
1481 unsafe { buf.set_len(buf.len() + buf_len) };
1482 }
1483
1484 const TOO_SMALL: i16 = errno::BUFFER_TOO_SMALL.get() as i16;
1485 match ret {
1486 -1 => {
1487 self.raw = raw::RowIter::INVALID;
1488 return buf_len;
1489 }
1490 0 => return buf_len,
1491 TOO_SMALL => buf.reserve(buf_len),
1492 e => panic!("unexpected error from `_row_iter_bsatn_advance`: {e}"),
1493 }
1494 }
1495 }
1496
1497 /// Returns whether the iterator is exhausted or not.
1498 pub fn is_exhausted(&self) -> bool {
1499 self.raw == raw::RowIter::INVALID
1500 }
1501}
1502
1503impl Drop for RowIter {
1504 fn drop(&mut self) {
1505 // Avoid this syscall when `_row_iter_bsatn_advance` above
1506 // notifies us that the iterator is exhausted.
1507 if self.is_exhausted() {
1508 return;
1509 }
1510 unsafe {
1511 raw::row_iter_bsatn_close(self.raw);
1512 }
1513 }
1514}
1515
1516#[cfg(feature = "unstable")]
1517pub mod procedure {
1518 //! Side-effecting or asynchronous operations which only procedures are allowed to perform.
1519
1520 use super::{call, call_no_ret, raw, Result};
1521
1522 #[inline]
1523 pub fn sleep_until(wake_at_timestamp: i64) -> i64 {
1524 // Safety: Just calling an `extern "C"` function.
1525 // Nothing weird happening here.
1526 unsafe { raw::procedure_sleep_until(wake_at_timestamp) }
1527 }
1528
1529 /// Starts a mutable transaction,
1530 /// blocking until a mutable transaction lock is acquired.
1531 ///
1532 /// Once complete, returns `Ok(timestamp)` on success,
1533 /// enabling further calls that require a pending transaction,
1534 /// or [`Errno`] otherwise.
1535 ///
1536 /// # Errors
1537 ///
1538 /// Returns an error:
1539 ///
1540 /// - `WOULD_BLOCK_TRANSACTION`, if there's already an ongoing transaction.
1541 #[inline]
1542 pub fn procedure_start_mut_tx() -> Result<i64> {
1543 unsafe { call(|out| raw::procedure_start_mut_tx(out)) }
1544 }
1545
1546 /// Commits a mutable transaction,
1547 /// blocking until the transaction has been committed
1548 /// and subscription queries have been run and broadcast.
1549 ///
1550 /// Once complete, returns `Ok(())` on success, or an [`Errno`] otherwise.
1551 ///
1552 /// # Errors
1553 ///
1554 /// Returns an error:
1555 ///
1556 /// - `TRANSACTION_NOT_ANONYMOUS`,
1557 /// if the transaction was not started in [`procedure_start_mut_tx`].
1558 /// This can happen if this syscall is erroneously called by a reducer.
1559 /// The code `NOT_IN_TRANSACTION` does not happen,
1560 /// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
1561 /// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
1562 /// This currently does not happen as anonymous read transactions
1563 /// are not exposed to modules.
1564 #[inline]
1565 pub fn procedure_commit_mut_tx() -> Result<()> {
1566 call_no_ret(|| unsafe { raw::procedure_commit_mut_tx() })
1567 }
1568
1569 /// Aborts a mutable transaction,
1570 /// blocking until the transaction has been rolled back.
1571 ///
1572 /// Once complete, returns `Ok(())` on success, or an [`Errno`] otherwise.
1573 ///
1574 /// # Errors
1575 ///
1576 /// Returns an error:
1577 ///
1578 /// - `TRANSACTION_NOT_ANONYMOUS`,
1579 /// if the transaction was not started in [`procedure_start_mut_tx`].
1580 /// This can happen if this syscall is erroneously called by a reducer.
1581 /// The code `NOT_IN_TRANSACTION` does not happen,
1582 /// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
1583 /// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
1584 /// This currently does not happen as anonymous read transactions
1585 /// are not exposed to modules.
1586 #[inline]
1587 pub fn procedure_abort_mut_tx() -> Result<()> {
1588 call_no_ret(|| unsafe { raw::procedure_abort_mut_tx() })
1589 }
1590
1591 #[inline]
1592 #[cfg(feature = "unstable")]
1593 /// Perform an HTTP request as specified by `http_request_bsatn`,
1594 /// suspending execution until the request is complete,
1595 /// then return its response or error.
1596 ///
1597 /// `http_request_bsatn` should be a BSATN-serialized `spacetimedb_lib::http::Request`.
1598 ///
1599 /// If the request completes successfully,
1600 /// this function returns `Ok(bytes)`, where `bytes` contains a BSATN-serialized `spacetimedb_lib::http::Response`.
1601 /// All HTTP response codes are treated as successful for these purposes;
1602 /// this method only returns an error if it is unable to produce any HTTP response whatsoever.
1603 /// In that case, this function returns `Err(bytes)`, where `bytes` contains a BSATN-serialized `spacetimedb_lib::http::Error`.
1604 pub fn http_request(
1605 http_request_bsatn: &[u8],
1606 body: &[u8],
1607 ) -> Result<(raw::BytesSource, raw::BytesSource), raw::BytesSource> {
1608 let mut out = [raw::BytesSource::INVALID; 2];
1609
1610 let res = unsafe {
1611 super::raw::procedure_http_request(
1612 http_request_bsatn.as_ptr(),
1613 http_request_bsatn.len() as u32,
1614 body.as_ptr(),
1615 body.len() as u32,
1616 &mut out as *mut [raw::BytesSource; 2],
1617 )
1618 };
1619
1620 match super::Errno::from_code(res) {
1621 // Success: `out` is a `spacetimedb_lib::http::Response`.
1622 None => Ok((out[0], out[1])),
1623 // HTTP_ERROR: `out` is a `spacetimedb_lib::http::Error`.
1624 Some(errno) if errno == super::Errno::HTTP_ERROR => Err(out[0]),
1625 Some(errno) => panic!("{errno}"),
1626 }
1627 }
1628}