extern crate alloc;
#[macro_use]
mod errno;
use core::fmt;
use core::mem::MaybeUninit;
use core::num::NonZeroU16;
use std::ptr::{self};
use alloc::boxed::Box;
pub const ABI_VERSION: u32 = 0x0002_0000;
pub mod raw {
use core::mem::ManuallyDrop;
#[link(wasm_import_module = "spacetime")]
extern "C" {
pub fn _create_table(
name: *const u8,
name_len: usize,
schema: *const u8,
schema_len: usize,
out: *mut u32,
) -> u16;
pub fn _get_table_id(name: *const u8, name_len: usize, out: *mut u32) -> u16;
pub fn _create_index(
index_name: *const u8,
index_name_len: usize,
table_id: u32,
index_type: u8,
col_ids: *const u8,
col_len: usize,
) -> u16;
pub fn _seek_eq(table_id: u32, col_id: u32, value: *const u8, value_len: usize, out: *mut Buffer) -> u16;
pub fn _insert(table_id: u32, row: *mut u8, row_len: usize) -> u16;
pub fn _delete_pk(table_id: u32, pk: *const u8, pk_len: usize) -> u16;
pub fn _delete_value(table_id: u32, row: *const u8, row_len: usize) -> u16;
pub fn _delete_eq(table_id: u32, col_id: u32, value: *const u8, value_len: usize, out: *mut u32) -> u16;
pub fn _delete_range(
table_id: u32,
col_id: u32,
range_start: *const u8,
range_start_len: usize,
range_end: *const u8,
range_end_len: usize,
out: *mut u32,
) -> u16;
pub fn _iter_start(table_id: u32, out: *mut BufferIter) -> u16;
pub fn _iter_start_filtered(table_id: u32, filter: *const u8, filter_len: usize, out: *mut BufferIter) -> u16;
pub fn _iter_next(iter: ManuallyDrop<BufferIter>, out: *mut Buffer) -> u16;
pub fn _iter_drop(iter: ManuallyDrop<BufferIter>) -> u16;
pub fn _console_log(
level: u8,
target: *const u8,
target_len: usize,
filename: *const u8,
filename_len: usize,
line_number: u32,
text: *const u8,
text_len: usize,
);
pub fn _schedule_reducer(
name: *const u8,
name_len: usize,
args: *const u8,
args_len: usize,
time: u64,
out: *mut u64,
);
pub fn _cancel_reducer(id: u64);
pub fn _buffer_len(bufh: ManuallyDrop<Buffer>) -> usize;
pub fn _buffer_consume(bufh: Buffer, into: *mut u8, len: usize);
pub fn _buffer_alloc(data: *const u8, data_len: usize) -> Buffer;
}
#[repr(u8)]
#[non_exhaustive]
pub enum IndexType {
BTree = 0,
Hash = 1,
}
pub const LOG_LEVEL_ERROR: u8 = 0;
pub const LOG_LEVEL_WARN: u8 = 1;
pub const LOG_LEVEL_INFO: u8 = 2;
pub const LOG_LEVEL_DEBUG: u8 = 3;
pub const LOG_LEVEL_TRACE: u8 = 4;
pub const LOG_LEVEL_PANIC: u8 = 101;
#[repr(transparent)]
pub struct Buffer {
raw: u32,
}
impl Buffer {
pub const fn handle(&self) -> ManuallyDrop<Self> {
ManuallyDrop::new(Self { raw: self.raw })
}
pub const INVALID: Self = Self { raw: u32::MAX };
pub const fn is_invalid(&self) -> bool {
self.raw == Self::INVALID.raw
}
}
#[repr(transparent)]
pub struct BufferIter {
raw: u32,
}
impl BufferIter {
pub const fn handle(&self) -> ManuallyDrop<Self> {
ManuallyDrop::new(Self { raw: self.raw })
}
}
#[cfg(any())]
mod module_exports {
type Encoded<T> = Buffer;
type Identity = Encoded<[u8; 32]>;
type Timestamp = u64;
type Result = Buffer;
extern "C" {
fn __preinit__XX_XXXX();
fn __setup__() -> Result;
fn __describe_module__() -> Encoded<ModuleDef>;
fn __call_reducer__(id: usize, sender: Identity, timestamp: Timestamp, args: Buffer) -> Result;
fn __identity_connected__(sender: Identity, timestamp: Timestamp) -> Result;
fn __identity_disconnected__(sender: Identity, timestamp: Timestamp) -> Result;
fn __migrate_database__XXXX(sender: Identity, timestamp: Timestamp, something: Buffer) -> Result;
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct Errno(NonZeroU16);
impl std::error::Error for Errno {}
macro_rules! def_errno {
($($name:ident => $desc:literal,)*) => {
impl Errno {
$(#[doc = $desc] pub const $name: Errno = Errno(unsafe { NonZeroU16::new_unchecked(errno::$name) });)*
}
const fn strerror(err: Errno) -> Option<&'static str> {
match err {
$(Errno::$name => Some($desc),)*
_ => None,
}
}
};
}
errnos!(def_errno);
impl Errno {
pub const fn message(self) -> Option<&'static str> {
strerror(self)
}
#[inline]
pub const fn from_code(code: u16) -> Option<Self> {
match NonZeroU16::new(code) {
Some(code) => Some(Errno(code)),
None => None,
}
}
#[inline]
pub const fn code(self) -> u16 {
self.0.get()
}
}
impl fmt::Debug for Errno {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut fmt = f.debug_struct("Errno");
fmt.field("code", &self.code());
if let Some(msg) = self.message() {
fmt.field("message", &msg);
}
fmt.finish()
}
}
impl fmt::Display for Errno {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = self.message().unwrap_or("Unknown error");
write!(f, "{message} (error {})", self.code())
}
}
fn cvt(x: u16) -> Result<(), Errno> {
match Errno::from_code(x) {
None => Ok(()),
Some(err) => Err(err),
}
}
#[inline]
unsafe fn call<T>(f: impl FnOnce(*mut T) -> u16) -> Result<T, Errno> {
let mut ret = MaybeUninit::uninit();
cvt(f(ret.as_mut_ptr()))?;
Ok(ret.assume_init())
}
#[inline]
pub fn create_table(name: &str, schema: &[u8]) -> Result<u32, Errno> {
unsafe { call(|out| raw::_create_table(name.as_ptr(), name.len(), schema.as_ptr(), schema.len(), out)) }
}
#[inline]
pub fn get_table_id(name: &str) -> Result<u32, Errno> {
unsafe { call(|out| raw::_get_table_id(name.as_ptr(), name.len(), out)) }
}
#[inline]
pub fn create_index(index_name: &str, table_id: u32, index_type: u8, col_ids: &[u8]) -> Result<(), Errno> {
cvt(unsafe {
raw::_create_index(
index_name.as_ptr(),
index_name.len(),
table_id,
index_type,
col_ids.as_ptr(),
col_ids.len(),
)
})
}
#[inline]
pub fn seek_eq(table_id: u32, col_id: u32, val: &[u8]) -> Result<Buffer, Errno> {
unsafe { call(|out| raw::_seek_eq(table_id, col_id, val.as_ptr(), val.len(), out)) }
}
#[inline]
pub fn insert(table_id: u32, row: &mut [u8]) -> Result<(), Errno> {
cvt(unsafe { raw::_insert(table_id, row.as_mut_ptr(), row.len()) })
}
#[inline]
pub fn delete_pk(table_id: u32, pk: &[u8]) -> Result<(), Errno> {
cvt(unsafe { raw::_delete_pk(table_id, pk.as_ptr(), pk.len()) })
}
#[inline]
pub fn delete_value(table_id: u32, row: &[u8]) -> Result<(), Errno> {
cvt(unsafe { raw::_delete_value(table_id, row.as_ptr(), row.len()) })
}
#[inline]
pub fn delete_eq(table_id: u32, col_id: u32, value: &[u8]) -> Result<u32, Errno> {
unsafe { call(|out| raw::_delete_eq(table_id, col_id, value.as_ptr(), value.len(), out)) }
}
#[inline]
pub fn delete_range(table_id: u32, col_id: u32, range_start: &[u8], range_end: &[u8]) -> Result<u32, Errno> {
unsafe {
call(|out| {
raw::_delete_range(
table_id,
col_id,
range_start.as_ptr(),
range_start.len(),
range_end.as_ptr(),
range_end.len(),
out,
)
})
}
}
#[inline]
pub fn iter(table_id: u32, filter: Option<&[u8]>) -> Result<BufferIter, Errno> {
unsafe {
call(|out| match filter {
None => raw::_iter_start(table_id, out),
Some(filter) => raw::_iter_start_filtered(table_id, filter.as_ptr(), filter.len(), out),
})
}
}
#[repr(u8)]
pub enum LogLevel {
Error = raw::LOG_LEVEL_ERROR,
Warn = raw::LOG_LEVEL_WARN,
Info = raw::LOG_LEVEL_INFO,
Debug = raw::LOG_LEVEL_DEBUG,
Trace = raw::LOG_LEVEL_TRACE,
Panic = raw::LOG_LEVEL_PANIC,
}
#[inline]
pub fn console_log(
level: LogLevel,
target: Option<&str>,
filename: Option<&str>,
line_number: Option<u32>,
text: &str,
) {
let opt_ptr = |b: Option<&str>| b.map_or(ptr::null(), |b| b.as_ptr());
let opt_len = |b: Option<&str>| b.map_or(0, |b| b.len());
unsafe {
raw::_console_log(
level as u8,
opt_ptr(target),
opt_len(target),
opt_ptr(filename),
opt_len(filename),
line_number.unwrap_or(u32::MAX),
text.as_ptr(),
text.len(),
)
}
}
#[inline]
pub fn schedule(name: &str, args: &[u8], time: u64) -> u64 {
let mut out = 0;
unsafe { raw::_schedule_reducer(name.as_ptr(), name.len(), args.as_ptr(), args.len(), time, &mut out) }
out
}
pub fn cancel_reducer(id: u64) {
unsafe { raw::_cancel_reducer(id) }
}
pub use raw::{Buffer, BufferIter};
impl Buffer {
pub fn data_len(&self) -> usize {
unsafe { raw::_buffer_len(self.handle()) }
}
pub fn read(self) -> Box<[u8]> {
let len = self.data_len();
let mut buf = alloc::vec::Vec::with_capacity(len);
self.read_uninit(buf.spare_capacity_mut());
unsafe { buf.set_len(len) };
buf.into_boxed_slice()
}
pub fn read_array<const N: usize>(self) -> [u8; N] {
let mut arr = unsafe { MaybeUninit::<[MaybeUninit<u8>; N]>::uninit().assume_init() };
self.read_uninit(&mut arr);
unsafe { (&arr as *const [_; N]).cast::<[u8; N]>().read() }
}
pub fn read_uninit(self, buf: &mut [MaybeUninit<u8>]) {
unsafe { raw::_buffer_consume(self, buf.as_mut_ptr().cast(), buf.len()) }
}
pub fn alloc(data: &[u8]) -> Self {
unsafe { raw::_buffer_alloc(data.as_ptr(), data.len()) }
}
}
impl Iterator for BufferIter {
type Item = Result<Box<[u8]>, Errno>;
fn next(&mut self) -> Option<Self::Item> {
let buf = unsafe { call(|out| raw::_iter_next(self.handle(), out)) };
match buf {
Ok(buf) if buf.is_invalid() => None,
Ok(buf) => Some(Ok(buf.read())),
Err(e) => Some(Err(e)),
}
}
}
impl Drop for BufferIter {
fn drop(&mut self) {
cvt(unsafe { raw::_iter_drop(self.handle()) }).unwrap();
}
}
#[cfg(feature = "getrandom")]
fn fake_random(buf: &mut [u8]) -> Result<(), getrandom::Error> {
#[allow(clippy::needless_range_loop)]
for i in 0..buf.len() {
let start = match i % 4 {
0 => 0x64,
1 => 0xe9,
2 => 0x48,
_ => 0xb5,
};
buf[i] = (start ^ i) as u8;
}
Result::Ok(())
}
#[cfg(feature = "getrandom")]
getrandom::register_custom_getrandom!(fake_random);