1#![deny(unsafe_op_in_unsafe_fn)]
2
3use crate::query_builder::Query;
4use crate::table::IndexAlgo;
5use crate::{sys, AnonymousViewContext, IterBuf, ReducerContext, ReducerResult, SpacetimeType, Table, ViewContext};
6use spacetimedb_lib::bsatn::EncodeError;
7pub use spacetimedb_lib::db::raw_def::v9::Lifecycle as LifecycleReducer;
8use spacetimedb_lib::db::raw_def::v9::{RawIndexAlgorithm, RawModuleDefV9Builder, TableType, ViewResultHeader};
9use spacetimedb_lib::de::{self, Deserialize, DeserializeOwned, Error as _, SeqProductAccess};
10use spacetimedb_lib::sats::typespace::TypespaceBuilder;
11use spacetimedb_lib::sats::{impl_deserialize, impl_serialize, ProductTypeElement};
12use spacetimedb_lib::ser::{Serialize, SerializeSeqProduct};
13use spacetimedb_lib::{bsatn, AlgebraicType, ConnectionId, Identity, ProductType, RawModuleDef, Timestamp};
14use spacetimedb_primitives::*;
15use std::convert::Infallible;
16use std::fmt;
17use std::marker::PhantomData;
18use std::sync::{Mutex, OnceLock};
19pub use sys::raw::{BytesSink, BytesSource};
20
21#[cfg(feature = "unstable")]
22use crate::{ProcedureContext, ProcedureResult};
23
24pub trait IntoVec<T> {
25 fn into_vec(self) -> Vec<T>;
26}
27
28impl<T> IntoVec<T> for Vec<T> {
29 fn into_vec(self) -> Vec<T> {
30 self
31 }
32}
33
34impl<T> IntoVec<T> for Option<T> {
35 fn into_vec(self) -> Vec<T> {
36 self.into_iter().collect()
37 }
38}
39
40pub fn invoke_reducer<'a, A: Args<'a>>(
45 reducer: impl Reducer<'a, A>,
46 ctx: ReducerContext,
47 args: &'a [u8],
48) -> Result<(), Box<str>> {
49 let SerDeArgs(args) = bsatn::from_slice(args).expect("unable to decode args");
51
52 reducer.invoke(&ctx, args)
53}
54
55#[cfg(feature = "unstable")]
56pub fn invoke_procedure<'a, A: Args<'a>, Ret: IntoProcedureResult>(
57 procedure: impl Procedure<'a, A, Ret>,
58 mut ctx: ProcedureContext,
59 args: &'a [u8],
60) -> ProcedureResult {
61 let SerDeArgs(args) = bsatn::from_slice(args).expect("unable to decode args");
63
64 let res = procedure.invoke(&mut ctx, args);
65
66 res.to_result()
67}
68
69#[diagnostic::on_unimplemented(
71 message = "invalid reducer signature",
72 label = "this reducer signature is not valid",
73 note = "",
74 note = "reducer signatures must match the following pattern:",
75 note = " `Fn(&ReducerContext, [T1, ...]) [-> Result<(), impl Display>]`",
76 note = "where each `Ti` type implements `SpacetimeType`.",
77 note = ""
78)]
79pub trait Reducer<'de, A: Args<'de>> {
80 fn invoke(&self, ctx: &ReducerContext, args: A) -> ReducerResult;
81}
82
83pub fn invoke_view<'a, A: Args<'a>, T: ViewReturn>(
86 view: impl View<'a, A, T>,
87 ctx: ViewContext,
88 args: &'a [u8],
89) -> Vec<u8> {
90 let SerDeArgs(args) = bsatn::from_slice(args).expect("unable to decode args");
92 let retn = view.invoke(&ctx, args);
93 let mut buf = IterBuf::take();
94 retn.to_writer(&mut buf).expect("unable to encode view return value");
95 std::mem::take(&mut *buf)
96}
97#[diagnostic::on_unimplemented(
99 message = "invalid view signature",
100 label = "this view signature is not valid",
101 note = "",
102 note = "view signatures must match:",
103 note = " `Fn(&ViewContext, [T1, ...]) -> Vec<Tn> | Option<Tn>`",
104 note = "where each `Ti` implements `SpacetimeType`.",
105 note = ""
106)]
107pub trait View<'de, A: Args<'de>, T: ViewReturn> {
108 fn invoke(&self, ctx: &ViewContext, args: A) -> T;
109}
110
111pub fn invoke_anonymous_view<'a, A: Args<'a>, T: ViewReturn>(
114 view: impl AnonymousView<'a, A, T>,
115 ctx: AnonymousViewContext,
116 args: &'a [u8],
117) -> Vec<u8> {
118 let SerDeArgs(args) = bsatn::from_slice(args).expect("unable to decode args");
120 let retn = view.invoke(&ctx, args);
121 let mut buf = IterBuf::take();
122 retn.to_writer(&mut buf).expect("unable to encode view return value");
123 std::mem::take(&mut *buf)
124}
125#[diagnostic::on_unimplemented(
127 message = "invalid anonymous view signature",
128 label = "this view signature is not valid",
129 note = "",
130 note = "anonymous view signatures must match:",
131 note = " `Fn(&AnonymousViewContext, [T1, ...]) -> Vec<Tn> | Option<Tn>`",
132 note = "where each `Ti` implements `SpacetimeType`.",
133 note = ""
134)]
135pub trait AnonymousView<'de, A: Args<'de>, T: ViewReturn> {
136 fn invoke(&self, ctx: &AnonymousViewContext, args: A) -> T;
137}
138
139pub trait FnInfo {
141 type Invoke;
143
144 #[cfg_attr(
145 feature = "unstable",
146 doc = "One of [`FnKindReducer`], [`FnKindProcedure`] or [`FnKindView`]."
147 )]
148 #[cfg_attr(not(feature = "unstable"), doc = "Either [`FnKindReducer`] or [`FnKindView`].")]
149 type FnKind;
153
154 const NAME: &'static str;
156
157 const LIFECYCLE: Option<LifecycleReducer> = None;
159
160 const ARG_NAMES: &'static [Option<&'static str>];
162
163 const INVOKE: Self::Invoke;
165
166 fn return_type(_ts: &mut impl TypespaceBuilder) -> Option<AlgebraicType> {
169 None
170 }
171}
172
173#[cfg(feature = "unstable")]
174pub trait Procedure<'de, A: Args<'de>, Ret: IntoProcedureResult> {
175 fn invoke(&self, ctx: &mut ProcedureContext, args: A) -> Ret;
176}
177
178pub trait Args<'de>: Sized {
184 const LEN: usize;
186
187 fn visit_seq_product<A: SeqProductAccess<'de>>(prod: A) -> Result<Self, A::Error>;
189
190 fn serialize_seq_product<S: SerializeSeqProduct>(&self, prod: &mut S) -> Result<(), S::Error>;
192
193 fn schema<I: FnInfo>(typespace: &mut impl TypespaceBuilder) -> ProductType;
195}
196
197#[diagnostic::on_unimplemented(
199 message = "`{Self}` is not a valid reducer return type",
200 note = "reducers cannot return values -- you can only return `()` or `Result<(), impl Display>`"
201)]
202pub trait IntoReducerResult {
203 fn into_result(self) -> Result<(), Box<str>>;
206}
207impl IntoReducerResult for () {
208 #[inline]
209 fn into_result(self) -> Result<(), Box<str>> {
210 Ok(self)
211 }
212}
213impl<E: fmt::Display> IntoReducerResult for Result<(), E> {
214 #[inline]
215 fn into_result(self) -> Result<(), Box<str>> {
216 self.map_err(|e| e.to_string().into())
217 }
218}
219
220#[cfg(feature = "unstable")]
221#[diagnostic::on_unimplemented(
222 message = "The procedure return type `{Self}` does not implement `SpacetimeType`",
223 note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition"
224)]
225pub trait IntoProcedureResult: SpacetimeType + Serialize {
226 #[inline]
227 fn to_result(&self) -> ProcedureResult {
228 bsatn::to_vec(&self).expect("Failed to serialize procedure result")
229 }
230}
231#[cfg(feature = "unstable")]
232impl<T: SpacetimeType + Serialize> IntoProcedureResult for T {}
233
234#[diagnostic::on_unimplemented(
235 message = "the first argument of a reducer must be `&ReducerContext`",
236 label = "first argument must be `&ReducerContext`"
237)]
238pub trait ReducerContextArg {
239 #[doc(hidden)]
241 const _ITEM: () = ();
242}
243impl ReducerContextArg for &ReducerContext {}
244
245#[diagnostic::on_unimplemented(
247 message = "the reducer argument `{Self}` does not implement `SpacetimeType`",
248 note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition"
249)]
250pub trait ReducerArg {
251 #[doc(hidden)]
253 const _ITEM: () = ();
254}
255impl<T: SpacetimeType> ReducerArg for T {}
256
257#[cfg(feature = "unstable")]
258#[diagnostic::on_unimplemented(
259 message = "the first argument of a procedure must be `&mut ProcedureContext`",
260 label = "first argument must be `&mut ProcedureContext`"
261)]
262pub trait ProcedureContextArg {
263 #[doc(hidden)]
265 const _ITEM: () = ();
266}
267#[cfg(feature = "unstable")]
268impl ProcedureContextArg for &mut ProcedureContext {}
269
270#[cfg(feature = "unstable")]
272#[diagnostic::on_unimplemented(
273 message = "the procedure argument `{Self}` does not implement `SpacetimeType`",
274 note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition"
275)]
276pub trait ProcedureArg {
277 #[doc(hidden)]
279 const _ITEM: () = ();
280}
281#[cfg(feature = "unstable")]
282impl<T: SpacetimeType> ProcedureArg for T {}
283
284#[diagnostic::on_unimplemented(
285 message = "The first parameter of a `#[view]` must be `&ViewContext` or `&AnonymousViewContext`"
286)]
287pub trait ViewContextArg {
288 #[doc(hidden)]
289 const _ITEM: () = ();
290}
291impl ViewContextArg for ViewContext {}
292impl ViewContextArg for AnonymousViewContext {}
293
294#[diagnostic::on_unimplemented(
296 message = "the view argument `{Self}` does not implement `SpacetimeType`",
297 note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition"
298)]
299pub trait ViewArg {
300 #[doc(hidden)]
301 const _ITEM: () = ();
302}
303impl<T: SpacetimeType> ViewArg for T {}
304
305#[diagnostic::on_unimplemented(message = "Views must return `Vec<T>` or `Option<T>` where `T` is a `SpacetimeType`")]
307pub trait ViewReturn {
308 #[doc(hidden)]
309 const _ITEM: () = ();
310
311 fn to_writer(self, w: &mut Vec<u8>) -> Result<(), EncodeError>;
312}
313
314impl<T: SpacetimeType + Serialize> ViewReturn for Vec<T> {
315 fn to_writer(self, buf: &mut Vec<u8>) -> Result<(), EncodeError> {
316 bsatn::to_writer(buf, &ViewResultHeader::RowData)?;
317 bsatn::to_writer(buf, &self)
318 }
319}
320
321impl<T: SpacetimeType + Serialize> ViewReturn for Option<T> {
322 fn to_writer(self, buf: &mut Vec<u8>) -> Result<(), EncodeError> {
323 bsatn::to_writer(buf, &ViewResultHeader::RowData)?;
324 bsatn::to_writer(buf, self.as_slice())
325 }
326}
327
328impl<T: SpacetimeType + Serialize> ViewReturn for Query<T> {
329 fn to_writer(self, buf: &mut Vec<u8>) -> Result<(), EncodeError> {
330 bsatn::to_writer(buf, &ViewResultHeader::RawSql(self.sql().to_string()))
331 }
332}
333
334pub struct ViewKind<Ctx> {
336 _marker: PhantomData<Ctx>,
337}
338
339pub trait ViewKindTrait {
340 type InvokeFn;
341}
342
343impl ViewKindTrait for ViewKind<ViewContext> {
344 type InvokeFn = ViewFn;
345}
346
347impl ViewKindTrait for ViewKind<AnonymousViewContext> {
348 type InvokeFn = AnonymousFn;
349}
350
351pub struct ViewDispatcher<Ctx> {
353 _marker: PhantomData<Ctx>,
354}
355
356impl ViewDispatcher<ViewContext> {
357 #[inline]
358 pub fn invoke<'a, A, T, V>(view: V, ctx: ViewContext, args: &'a [u8]) -> Vec<u8>
359 where
360 A: Args<'a>,
361 T: ViewReturn,
362 V: View<'a, A, T>,
363 {
364 invoke_view(view, ctx, args)
365 }
366}
367
368impl ViewDispatcher<AnonymousViewContext> {
369 #[inline]
370 pub fn invoke<'a, A, T, V>(view: V, ctx: AnonymousViewContext, args: &'a [u8]) -> Vec<u8>
371 where
372 A: Args<'a>,
373 T: ViewReturn,
374 V: AnonymousView<'a, A, T>,
375 {
376 invoke_anonymous_view(view, ctx, args)
377 }
378}
379
380pub struct ViewRegistrar<Ctx> {
382 _marker: PhantomData<Ctx>,
383}
384
385impl ViewRegistrar<ViewContext> {
386 #[inline]
387 pub fn register<'a, A, I, T, V>(view: V)
388 where
389 A: Args<'a>,
390 T: ViewReturn,
391 I: FnInfo<Invoke = ViewFn>,
392 V: View<'a, A, T>,
393 {
394 register_view::<A, I, T>(view)
395 }
396}
397
398impl ViewRegistrar<AnonymousViewContext> {
399 #[inline]
400 pub fn register<'a, A, I, T, V>(view: V)
401 where
402 A: Args<'a>,
403 T: ViewReturn,
404 I: FnInfo<Invoke = AnonymousFn>,
405 V: AnonymousView<'a, A, T>,
406 {
407 register_anonymous_view::<A, I, T>(view)
408 }
409}
410
411pub const fn scheduled_typecheck<'de, Row, FnKind>(_x: impl ExportFunctionForScheduledTable<'de, Row, FnKind>)
413where
414 Row: SpacetimeType + Serialize + Deserialize<'de>,
415{
416 core::mem::forget(_x);
417}
418
419pub struct FnKindReducer {
421 _never: Infallible,
422}
423
424#[cfg(feature = "unstable")]
425pub struct FnKindProcedure<Ret> {
429 _never: Infallible,
430 _ret_ty: PhantomData<fn() -> Ret>,
431}
432
433pub struct FnKindView {
438 _never: Infallible,
439}
440
441#[cfg_attr(
446 feature = "unstable",
447 doc = "It will be one of [`FnKindReducer`] or [`FnKindProcedure`] in modules that compile successfully."
448)]
449#[cfg_attr(
450 not(feature = "unstable"),
451 doc = "It will be [`FnKindReducer`] in modules that compile successfully."
452)]
453#[diagnostic::on_unimplemented(
456 message = "invalid signature for scheduled table reducer or procedure",
457 note = "views cannot be scheduled",
458 note = "the scheduled function must take `{TableRow}` as its sole argument",
459 note = "e.g: `fn scheduled_reducer(ctx: &ReducerContext, arg: {TableRow})`",
460 )]
462pub trait ExportFunctionForScheduledTable<'de, TableRow, FnKind> {}
463impl<'de, TableRow: SpacetimeType + Serialize + Deserialize<'de>, F: Reducer<'de, (TableRow,)>>
464 ExportFunctionForScheduledTable<'de, TableRow, FnKindReducer> for F
465{
466}
467
468#[cfg(feature = "unstable")]
469impl<
470 'de,
471 TableRow: SpacetimeType + Serialize + Deserialize<'de>,
472 Ret: SpacetimeType + Serialize + Deserialize<'de>,
473 F: Procedure<'de, (TableRow,), Ret>,
474 > ExportFunctionForScheduledTable<'de, TableRow, FnKindProcedure<Ret>> for F
475{
476}
477
478pub struct DummyTypespace;
480impl TypespaceBuilder for DummyTypespace {
481 fn add(
482 &mut self,
483 _: std::any::TypeId,
484 _: Option<&'static str>,
485 _: impl FnOnce(&mut Self) -> spacetimedb_lib::AlgebraicType,
486 ) -> spacetimedb_lib::AlgebraicType {
487 unreachable!()
488 }
489}
490
491#[diagnostic::on_unimplemented(
492 message = "the column type `{Self}` does not implement `SpacetimeType`",
493 note = "table column types all must implement `SpacetimeType`",
494 note = "if you own the type, try adding `#[derive(SpacetimeType)]` to its definition"
495)]
496pub trait TableColumn {
497 #[doc(hidden)]
499 const _ITEM: () = ();
500}
501impl<T: SpacetimeType> TableColumn for T {}
502
503pub const fn assert_scheduled_table_primary_key<T: ScheduledTablePrimaryKey>() {}
505
506mod sealed {
507 pub trait Sealed {}
508}
509#[diagnostic::on_unimplemented(
510 message = "scheduled table primary key must be a `u64`",
511 label = "should be `u64`, not `{Self}`"
512)]
513pub trait ScheduledTablePrimaryKey: sealed::Sealed {}
514impl sealed::Sealed for u64 {}
515impl ScheduledTablePrimaryKey for u64 {}
516
517pub struct ContextArg;
520
521struct ArgsVisitor<A> {
523 _marker: PhantomData<A>,
524}
525
526impl<'de, A: Args<'de>> de::ProductVisitor<'de> for ArgsVisitor<A> {
527 type Output = A;
528
529 fn product_name(&self) -> Option<&str> {
530 None
531 }
532 fn product_len(&self) -> usize {
533 A::LEN
534 }
535 fn product_kind(&self) -> de::ProductKind {
536 de::ProductKind::ReducerArgs
537 }
538 fn visit_seq_product<Acc: SeqProductAccess<'de>>(self, prod: Acc) -> Result<Self::Output, Acc::Error> {
539 A::visit_seq_product(prod)
540 }
541 fn visit_named_product<Acc: de::NamedProductAccess<'de>>(self, _prod: Acc) -> Result<Self::Output, Acc::Error> {
542 Err(Acc::Error::named_products_not_supported())
543 }
544}
545
546macro_rules! impl_reducer_procedure_view {
547 ($($T1:ident $(, $T:ident)*)?) => {
548 impl_reducer_procedure_view!(@impl $($T1 $(, $T)*)?);
549 $(impl_reducer_procedure_view!($($T),*);)?
550 };
551 (@impl $($T:ident),*) => {
552 impl<'de, $($T: SpacetimeType + Deserialize<'de> + Serialize),*> Args<'de> for ($($T,)*) {
554 const LEN: usize = impl_reducer_procedure_view!(@count $($T)*);
555 #[allow(non_snake_case)]
556 #[allow(unused)]
557 fn visit_seq_product<Acc: SeqProductAccess<'de>>(mut prod: Acc) -> Result<Self, Acc::Error> {
558 let vis = ArgsVisitor { _marker: PhantomData::<Self> };
559 let i = 0;
561 $(
563 let $T = prod.next_element::<$T>()?.ok_or_else(|| de::Error::missing_field(i, None, &vis))?;
564 let i = i + 1;
565 )*
566 Ok(($($T,)*))
567 }
568
569 #[allow(non_snake_case)]
570 fn serialize_seq_product<Ser: SerializeSeqProduct>(&self, _prod: &mut Ser) -> Result<(), Ser::Error> {
571 let ($($T,)*) = self;
573 $(_prod.serialize_element($T)?;)*
574 Ok(())
575 }
576
577 #[inline]
578 #[allow(non_snake_case, irrefutable_let_patterns)]
579 fn schema<Info: FnInfo>(_typespace: &mut impl TypespaceBuilder) -> ProductType {
580 let [.., $($T),*] = Info::ARG_NAMES else { panic!() };
582 ProductType::new(vec![
583 $(ProductTypeElement {
584 name: $T.map(Into::into),
585 algebraic_type: <$T>::make_type(_typespace),
586 }),*
587 ].into())
588 }
589 }
590
591 impl<'de, Func, Ret, $($T: SpacetimeType + Deserialize<'de> + Serialize),*> Reducer<'de, ($($T,)*)> for Func
593 where
594 Func: Fn(&ReducerContext, $($T),*) -> Ret,
595 Ret: IntoReducerResult
596 {
597 #[allow(non_snake_case)]
598 fn invoke(&self, ctx: &ReducerContext, args: ($($T,)*)) -> Result<(), Box<str>> {
599 let ($($T,)*) = args;
600 self(ctx, $($T),*).into_result()
601 }
602 }
603
604 #[cfg(feature = "unstable")]
605 impl<'de, Func, Ret, $($T: SpacetimeType + Deserialize<'de> + Serialize),*> Procedure<'de, ($($T,)*), Ret> for Func
606 where
607 Func: Fn(&mut ProcedureContext, $($T),*) -> Ret,
608 Ret: IntoProcedureResult,
609 {
610 #[allow(non_snake_case)]
611 fn invoke(&self, ctx: &mut ProcedureContext, args: ($($T,)*)) -> Ret {
612 let ($($T,)*) = args;
613 self(ctx, $($T),*)
614 }
615 }
616
617 impl<'de, Func, Retn, $($T),*>
619 View<'de, ($($T,)*), Retn> for Func
620 where
621 $($T: SpacetimeType + Deserialize<'de> + Serialize,)*
622 Func: Fn(&ViewContext, $($T),*) -> Retn,
623 Retn: ViewReturn,
624 {
625 #[allow(non_snake_case)]
626 fn invoke(&self, ctx: &ViewContext, args: ($($T,)*)) -> Retn {
627 let ($($T,)*) = args;
628 self(ctx, $($T),*)
629 }
630 }
631
632 impl<'de, Func, Retn, $($T),*>
634 AnonymousView<'de, ($($T,)*), Retn> for Func
635 where
636 $($T: SpacetimeType + Deserialize<'de> + Serialize,)*
637 Func: Fn(&AnonymousViewContext, $($T),*) -> Retn,
638 Retn: ViewReturn,
639 {
640 #[allow(non_snake_case)]
641 fn invoke(&self, ctx: &AnonymousViewContext, args: ($($T,)*)) -> Retn {
642 let ($($T,)*) = args;
643 self(ctx, $($T),*)
644 }
645 }
646 };
647 (@count $($T:ident)*) => {
649 0 $(+ impl_reducer_procedure_view!(@drop $T 1))*
650 };
651 (@drop $a:tt $b:tt) => { $b };
652}
653
654impl_reducer_procedure_view!(
655 A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, AA, AB, AC, AD, AE, AF
656);
657
658struct SerDeArgs<A>(A);
660impl_deserialize!(
661 [A: Args<'de>] SerDeArgs<A>,
662 de => de.deserialize_product(ArgsVisitor { _marker: PhantomData }).map(Self)
663);
664impl_serialize!(['de, A: Args<'de>] SerDeArgs<A>, (self, ser) => {
665 let mut prod = ser.serialize_seq_product(A::LEN)?;
666 self.0.serialize_seq_product(&mut prod)?;
667 prod.end()
668});
669
670pub trait RowLevelSecurityInfo {
672 const SQL: &'static str;
674}
675
676trait DescriberFn: FnMut(&mut ModuleBuilder) + Send + 'static {}
685impl<F: FnMut(&mut ModuleBuilder) + Send + 'static> DescriberFn for F {}
686
687fn register_describer(f: impl DescriberFn) {
689 DESCRIBERS.lock().unwrap().push(Box::new(f))
690}
691
692pub fn register_reftype<T: SpacetimeType>() {
694 register_describer(|module| {
695 T::make_type(&mut module.inner);
696 })
697}
698
699pub fn register_table<T: Table>() {
701 register_describer(|module| {
702 let product_type_ref = *T::Row::make_type(&mut module.inner).as_ref().unwrap();
703
704 let mut table = module
705 .inner
706 .build_table(T::TABLE_NAME, product_type_ref)
707 .with_type(TableType::User)
708 .with_access(T::TABLE_ACCESS);
709
710 for &col in T::UNIQUE_COLUMNS {
711 table = table.with_unique_constraint(col);
712 }
713 for &index in T::INDEXES {
714 table = table.with_index(index.algo.into(), index.accessor_name);
715 }
716 if let Some(primary_key) = T::PRIMARY_KEY {
717 table = table.with_primary_key(primary_key);
718 }
719 for &col in T::SEQUENCES {
720 table = table.with_column_sequence(col);
721 }
722 if let Some(schedule) = T::SCHEDULE {
723 table = table.with_schedule(schedule.reducer_or_procedure_name, schedule.scheduled_at_column);
724 }
725
726 for col in T::get_default_col_values().iter_mut() {
727 table = table.with_default_column_value(col.col_id, col.value.clone())
728 }
729
730 table.finish();
731 })
732}
733
734impl From<IndexAlgo<'_>> for RawIndexAlgorithm {
735 fn from(algo: IndexAlgo<'_>) -> RawIndexAlgorithm {
736 match algo {
737 IndexAlgo::BTree { columns } => RawIndexAlgorithm::BTree {
738 columns: columns.iter().copied().collect(),
739 },
740 IndexAlgo::Hash { columns } => RawIndexAlgorithm::Hash {
741 columns: columns.iter().copied().collect(),
742 },
743 IndexAlgo::Direct { column } => RawIndexAlgorithm::Direct { column: column.into() },
744 }
745 }
746}
747
748pub fn register_reducer<'a, A: Args<'a>, I: FnInfo<Invoke = ReducerFn>>(_: impl Reducer<'a, A>) {
750 register_describer(|module| {
751 let params = A::schema::<I>(&mut module.inner);
752 module.inner.add_reducer(I::NAME, params, I::LIFECYCLE);
753 module.reducers.push(I::INVOKE);
754 })
755}
756
757#[cfg(feature = "unstable")]
758pub fn register_procedure<'a, A, Ret, I>(_: impl Procedure<'a, A, Ret>)
759where
760 A: Args<'a>,
761 Ret: SpacetimeType + Serialize,
762 I: FnInfo<Invoke = ProcedureFn>,
763{
764 register_describer(|module| {
765 let params = A::schema::<I>(&mut module.inner);
766 let ret_ty = <Ret as SpacetimeType>::make_type(&mut module.inner);
767 module.inner.add_procedure(I::NAME, params, ret_ty);
768 module.procedures.push(I::INVOKE);
769 })
770}
771
772pub fn register_view<'a, A, I, T>(_: impl View<'a, A, T>)
774where
775 A: Args<'a>,
776 I: FnInfo<Invoke = ViewFn>,
777 T: ViewReturn,
778{
779 register_describer(|module| {
780 let params = A::schema::<I>(&mut module.inner);
781 let return_type = I::return_type(&mut module.inner).unwrap();
782 module
783 .inner
784 .add_view(I::NAME, module.views.len(), true, false, params, return_type);
785 module.views.push(I::INVOKE);
786 })
787}
788
789pub fn register_anonymous_view<'a, A, I, T>(_: impl AnonymousView<'a, A, T>)
791where
792 A: Args<'a>,
793 I: FnInfo<Invoke = AnonymousFn>,
794 T: ViewReturn,
795{
796 register_describer(|module| {
797 let params = A::schema::<I>(&mut module.inner);
798 let return_type = I::return_type(&mut module.inner).unwrap();
799 module
800 .inner
801 .add_view(I::NAME, module.views_anon.len(), true, true, params, return_type);
802 module.views_anon.push(I::INVOKE);
803 })
804}
805
806pub fn register_row_level_security(sql: &'static str) {
808 register_describer(|module| {
809 module.inner.add_row_level_security(sql);
810 })
811}
812
813#[derive(Default)]
815pub struct ModuleBuilder {
816 inner: RawModuleDefV9Builder,
818 reducers: Vec<ReducerFn>,
820 #[cfg(feature = "unstable")]
822 procedures: Vec<ProcedureFn>,
823 views: Vec<ViewFn>,
825 views_anon: Vec<AnonymousFn>,
827}
828
829static DESCRIBERS: Mutex<Vec<Box<dyn DescriberFn>>> = Mutex::new(Vec::new());
831
832pub type ReducerFn = fn(ReducerContext, &[u8]) -> ReducerResult;
835static REDUCERS: OnceLock<Vec<ReducerFn>> = OnceLock::new();
836
837#[cfg(feature = "unstable")]
838pub type ProcedureFn = fn(ProcedureContext, &[u8]) -> ProcedureResult;
839#[cfg(feature = "unstable")]
840static PROCEDURES: OnceLock<Vec<ProcedureFn>> = OnceLock::new();
841
842pub type ViewFn = fn(ViewContext, &[u8]) -> Vec<u8>;
844static VIEWS: OnceLock<Vec<ViewFn>> = OnceLock::new();
845
846pub type AnonymousFn = fn(AnonymousViewContext, &[u8]) -> Vec<u8>;
848static ANONYMOUS_VIEWS: OnceLock<Vec<AnonymousFn>> = OnceLock::new();
849
850#[no_mangle]
866extern "C" fn __describe_module__(description: BytesSink) {
867 let mut module = ModuleBuilder::default();
869 for describer in &mut *DESCRIBERS.lock().unwrap() {
870 describer(&mut module)
871 }
872
873 let module_def = module.inner.finish();
875 let module_def = RawModuleDef::V9(module_def);
876 let bytes = bsatn::to_vec(&module_def).expect("unable to serialize typespace");
877
878 REDUCERS.set(module.reducers).ok().unwrap();
880 #[cfg(feature = "unstable")]
881 PROCEDURES.set(module.procedures).ok().unwrap();
882 VIEWS.set(module.views).ok().unwrap();
883 ANONYMOUS_VIEWS.set(module.views_anon).ok().unwrap();
884
885 write_to_sink(description, &bytes);
887}
888
889#[no_mangle]
922extern "C" fn __call_reducer__(
923 id: usize,
924 sender_0: u64,
925 sender_1: u64,
926 sender_2: u64,
927 sender_3: u64,
928 conn_id_0: u64,
929 conn_id_1: u64,
930 timestamp: u64,
931 args: BytesSource,
932 error: BytesSink,
933) -> i16 {
934 let sender = reconstruct_sender_identity(sender_0, sender_1, sender_2, sender_3);
936
937 let conn_id = reconstruct_connection_id(conn_id_0, conn_id_1);
940
941 let timestamp = Timestamp::from_micros_since_unix_epoch(timestamp as i64);
943 let ctx = ReducerContext::new(crate::Local {}, sender, conn_id, timestamp);
944
945 let reducers = REDUCERS.get().unwrap();
947 let res = with_read_args(args, |args| reducers[id](ctx, args));
949 convert_err_to_errno(res, error)
951}
952
953fn reconstruct_sender_identity(sender_0: u64, sender_1: u64, sender_2: u64, sender_3: u64) -> Identity {
955 let sender = [sender_0, sender_1, sender_2, sender_3];
956 let sender: [u8; 32] = bytemuck::must_cast(sender);
957 Identity::from_byte_array(sender) }
959
960fn reconstruct_connection_id(conn_id_0: u64, conn_id_1: u64) -> Option<ConnectionId> {
964 let conn_id = [conn_id_0, conn_id_1];
967 let conn_id: [u8; 16] = bytemuck::must_cast(conn_id);
968 let conn_id = ConnectionId::from_le_byte_array(conn_id); (conn_id != ConnectionId::ZERO).then_some(conn_id)
970}
971
972fn convert_err_to_errno(res: Result<(), Box<str>>, out: BytesSink) -> i16 {
978 match res {
979 Ok(()) => 0,
980 Err(msg) => {
981 write_to_sink(out, msg.as_bytes());
982 errno::HOST_CALL_FAILURE.get() as i16
983 }
984 }
985}
986
987#[cfg(feature = "unstable")]
1018#[no_mangle]
1019extern "C" fn __call_procedure__(
1020 id: usize,
1021 sender_0: u64,
1022 sender_1: u64,
1023 sender_2: u64,
1024 sender_3: u64,
1025 conn_id_0: u64,
1026 conn_id_1: u64,
1027 timestamp: u64,
1028 args: BytesSource,
1029 result_sink: BytesSink,
1030) -> i16 {
1031 let sender = reconstruct_sender_identity(sender_0, sender_1, sender_2, sender_3);
1033
1034 let conn_id = reconstruct_connection_id(conn_id_0, conn_id_1);
1036
1037 let timestamp = Timestamp::from_micros_since_unix_epoch(timestamp as i64);
1038
1039 let ctx = ProcedureContext::new(sender, conn_id, timestamp);
1041
1042 let procedures = PROCEDURES.get().unwrap();
1044
1045 let res = with_read_args(args, |args| procedures[id](ctx, args));
1047
1048 write_to_sink(result_sink, &res);
1050
1051 0
1053}
1054
1055#[no_mangle]
1073extern "C" fn __call_view_anon__(id: usize, args: BytesSource, sink: BytesSink) -> i16 {
1074 let views = ANONYMOUS_VIEWS.get().unwrap();
1075 write_to_sink(
1076 sink,
1077 &with_read_args(args, |args| views[id](AnonymousViewContext::default(), args)),
1078 );
1079 2
1080}
1081
1082#[no_mangle]
1101extern "C" fn __call_view__(
1102 id: usize,
1103 sender_0: u64,
1104 sender_1: u64,
1105 sender_2: u64,
1106 sender_3: u64,
1107 args: BytesSource,
1108 sink: BytesSink,
1109) -> i16 {
1110 let sender = [sender_0, sender_1, sender_2, sender_3];
1112 let sender: [u8; 32] = bytemuck::must_cast(sender);
1113 let sender = Identity::from_byte_array(sender); let views = VIEWS.get().unwrap();
1116
1117 write_to_sink(
1118 sink,
1119 &with_read_args(args, |args| views[id](ViewContext::new(sender), args)),
1120 );
1121 2
1122}
1123
1124fn with_read_args<R>(args: BytesSource, logic: impl FnOnce(&[u8]) -> R) -> R {
1126 if args == BytesSource::INVALID {
1127 return logic(&[]);
1128 }
1129
1130 let mut buf = IterBuf::take();
1136
1137 read_bytes_source_into(args, &mut buf);
1139 logic(&buf)
1140}
1141
1142const NO_SPACE: u16 = errno::NO_SPACE.get();
1143const NO_SUCH_BYTES: u16 = errno::NO_SUCH_BYTES.get();
1144
1145pub fn get_jwt(connection_id: ConnectionId) -> Option<String> {
1147 let mut buf = IterBuf::take();
1148 let source = sys::get_jwt(connection_id.as_le_byte_array())?;
1149 if source == BytesSource::INVALID {
1150 return None;
1151 }
1152 read_bytes_source_into(source, &mut buf);
1153 Some(std::str::from_utf8(&buf).unwrap().to_string())
1154}
1155
1156pub(crate) fn read_bytes_source_into(source: BytesSource, buf: &mut Vec<u8>) {
1158 const INVALID: i16 = NO_SUCH_BYTES as i16;
1159
1160 let len = {
1166 let mut len = 0;
1167 let ret = unsafe { sys::raw::bytes_source_remaining_length(source, &raw mut len) };
1168 match ret {
1169 0 => len,
1170 INVALID => panic!("invalid source passed"),
1171 _ => unreachable!(),
1172 }
1173 };
1174 buf.reserve(buf.len().saturating_sub(len as usize));
1175
1176 loop {
1183 let buf_ptr = buf.spare_capacity_mut();
1185 let spare_len = buf_ptr.len();
1186 let mut buf_len = buf_ptr.len();
1187 let buf_ptr = buf_ptr.as_mut_ptr().cast();
1188 let ret = unsafe { sys::raw::bytes_source_read(source, buf_ptr, &mut buf_len) };
1189 if ret <= 0 {
1190 unsafe { buf.set_len(buf.len() + buf_len) };
1192 }
1193 match ret {
1194 -1 => break,
1196 0 if spare_len == buf_len => buf.reserve(1024),
1199 0 => {}
1204 INVALID => panic!("invalid source passed"),
1205 _ => unreachable!(),
1206 }
1207 }
1208}
1209
1210fn write_to_sink(sink: BytesSink, mut buf: &[u8]) {
1212 loop {
1213 let len = &mut buf.len();
1214 match unsafe { sys::raw::bytes_sink_write(sink, buf.as_ptr(), len) } {
1215 0 => {
1216 (_, buf) = buf.split_at(*len);
1218 if buf.is_empty() {
1219 break;
1220 }
1221 }
1222 NO_SUCH_BYTES => panic!("invalid sink passed"),
1223 NO_SPACE => panic!("no space left at sink"),
1224 _ => unreachable!(),
1225 }
1226 }
1227}
1228
1229#[macro_export]
1230#[doc(hidden)]
1231macro_rules! __make_register_reftype {
1232 ($ty:ty, $name:literal) => {
1233 const _: () = {
1234 #[export_name = concat!("__preinit__20_register_describer_", $name)]
1235 extern "C" fn __register_describer() {
1236 $crate::rt::register_reftype::<$ty>()
1237 }
1238 };
1239 };
1240}
1241
1242#[cfg(feature = "unstable")]
1243#[doc(hidden)]
1244pub fn volatile_nonatomic_schedule_immediate<'de, A: Args<'de>, R: Reducer<'de, A>, R2: FnInfo<Invoke = ReducerFn>>(
1245 _reducer: R,
1246 args: A,
1247) {
1248 let arg_bytes = bsatn::to_vec(&SerDeArgs(args)).unwrap();
1249
1250 sys::volatile_nonatomic_schedule_immediate(R2::NAME, &arg_bytes)
1252}
1253
1254#[cfg_attr(not(feature = "unstable"), allow(unused))]
1259pub(crate) fn read_bytes_source_as<T: DeserializeOwned + 'static>(source: BytesSource) -> T {
1260 let mut buf = IterBuf::take();
1261 read_bytes_source_into(source, &mut buf);
1262 bsatn::from_slice::<T>(&buf)
1263 .unwrap_or_else(|err| panic!("Failed to BSATN-deserialize `{}`: {err:#?}", std::any::type_name::<T>()))
1264}