interstice_sdk_core/host_calls/
core.rs1use interstice_abi::{
2 CallQueryRequest, CallQueryResponse, CallReducerRequest, CallReducerResponse,
3 HostCall, IndexKey, IndexQuery,
4 InsertRowResponse, IntersticeValue, ModuleSelection,
5 NodeSelection, Row, ScheduleRequest, ScheduleResponse, TableGetByPrimaryKeyRequest,
6 TableGetByPrimaryKeyResponse, TableIndexScanRequest, TableIndexScanResponse, TableScanRequest,
7 TableScanResponse,
8 decode, encode,
9};
10
11#[cfg(target_arch = "wasm32")]
14static mut ENCODE_BUF: [u8; 16384] = [0u8; 16384];
15
16enum EncodedBuf<'a> {
19 Static(&'a [u8]),
20 Heap(Vec<u8>),
21}
22
23impl EncodedBuf<'_> {
24 #[inline]
25 fn ptr_len(&self) -> (i32, i32) {
26 match self {
27 Self::Static(s) => (s.as_ptr() as i32, s.len() as i32),
28 Self::Heap(v) => (v.as_ptr() as i32, v.len() as i32),
29 }
30 }
31}
32
33#[cfg(target_arch = "wasm32")]
36#[inline]
37fn encode_scratch<T: serde::Serialize>(value: &T) -> Result<EncodedBuf<'static>, String> {
38 unsafe {
39 let buf_ptr = std::ptr::addr_of_mut!(ENCODE_BUF);
41 let scratch = std::slice::from_raw_parts_mut(buf_ptr as *mut u8, 16384);
42 match postcard::to_slice(value, scratch) {
43 Ok(slice) => {
44 let static_slice: &'static [u8] = &*(slice as *const [u8]);
48 Ok(EncodedBuf::Static(static_slice))
49 }
50 Err(_) => {
51 let v = encode(value).map_err(|e| e.to_string())?;
52 Ok(EncodedBuf::Heap(v))
53 }
54 }
55 }
56}
57
58#[cfg(not(target_arch = "wasm32"))]
59fn encode_scratch<T: serde::Serialize>(value: &T) -> Result<EncodedBuf<'static>, String> {
60 let v = encode(value).map_err(|e| e.to_string())?;
61 Ok(EncodedBuf::Heap(v))
62}
63
64pub type NodeId = String;
65
66pub fn log(message: &str) {
67 let msg = message.as_bytes();
68 unsafe { crate::host_calls::interstice_log(msg.as_ptr() as i32, msg.len() as i32) };
69}
70
71pub fn current_node_id() -> NodeId {
72 let call = HostCall::CurrentNodeId;
73 let pack = host_call(call);
74 let response: NodeId = unpack(pack);
75 return response;
76}
77
78pub fn call_reducer(
79 node_selection: NodeSelection,
80 module_selection: ModuleSelection,
81 reducer_name: String,
82 input: IntersticeValue,
83) -> Result<(), String> {
84 let call = HostCall::CallReducer(CallReducerRequest {
85 node_selection,
86 module_selection,
87 reducer_name,
88 input,
89 });
90
91 let pack = host_call(call);
92 let response: CallReducerResponse = unpack(pack);
93 match response {
94 CallReducerResponse::Ok => Ok(()),
95 CallReducerResponse::Err(err) => Err(err),
96 }
97}
98
99pub fn schedule(reducer_name: String, delay_ms: u64) -> Result<(), String> {
100 let call = HostCall::Schedule(ScheduleRequest {
101 reducer_name,
102 delay_ms,
103 });
104
105 let pack = host_call(call);
106 let response: ScheduleResponse = unpack(pack);
107 match response {
108 ScheduleResponse::Ok => Ok(()),
109 ScheduleResponse::Err(err) => Err(err),
110 }
111}
112
113pub fn call_query(
114 node_selection: NodeSelection,
115 module_selection: ModuleSelection,
116 query_name: String,
117 input: IntersticeValue,
118) -> Result<IntersticeValue, String> {
119 let call = HostCall::CallQuery(CallQueryRequest {
120 node_selection,
121 module_selection,
122 query_name,
123 input,
124 });
125
126 let pack = host_call(call);
127 let response: CallQueryResponse = unpack(pack);
128 match response {
129 CallQueryResponse::Ok(value) => Ok(value),
130 CallQueryResponse::Err(err) => Err(err),
131 }
132}
133
134pub fn deterministic_random_u64() -> Result<u64, String> {
135 let v = unsafe { crate::host_calls::interstice_random() };
136 Ok(v as u64)
137}
138
139pub fn time_now_ms() -> Result<u64, String> {
140 let v = unsafe { crate::host_calls::interstice_time() };
141 Ok(v as u64)
142}
143
144pub fn insert_row(table_name: &str, row: Row) -> Result<Row, String> {
145 let table_bytes = table_name.as_bytes();
146 let row_buf = encode_scratch(&row)?;
147 let (row_ptr, row_len) = row_buf.ptr_len();
148 #[cfg(target_arch = "wasm32")]
149 {
150 let written = unsafe {
152 let resp_ptr = std::ptr::addr_of_mut!(crate::host_calls::DIRECT_RESP_BUF) as *mut u8 as i32;
153 crate::host_calls::interstice_insert_row(
154 table_bytes.as_ptr() as i32, table_bytes.len() as i32,
155 row_ptr, row_len,
156 resp_ptr, 8192i32,
157 )
158 };
159 if written >= 0 {
160 let resp: InsertRowResponse = unsafe {
161 let buf = std::slice::from_raw_parts(
162 std::ptr::addr_of!(crate::host_calls::DIRECT_RESP_BUF) as *const u8,
163 written as usize,
164 );
165 decode(buf).map_err(|e| e.to_string())?
166 };
167 return match resp {
168 InsertRowResponse::Ok(None) => Ok(row),
169 InsertRowResponse::Ok(Some(modified)) => Ok(modified),
170 InsertRowResponse::Err(err) => Err(err),
171 };
172 }
173 let needed = (-written) as usize;
175 let mut heap_buf: Vec<u8> = vec![0u8; needed];
176 let written2 = unsafe {
177 crate::host_calls::interstice_insert_row(
178 table_bytes.as_ptr() as i32, table_bytes.len() as i32,
179 row_ptr, row_len,
180 heap_buf.as_mut_ptr() as i32, needed as i32,
181 )
182 };
183 if written2 < 0 {
184 return Err("insert_row: response buffer too small".into());
185 }
186 let resp: InsertRowResponse = decode(&heap_buf[..written2 as usize]).map_err(|e| e.to_string())?;
187 return match resp {
188 InsertRowResponse::Ok(None) => Ok(row),
189 InsertRowResponse::Ok(Some(modified)) => Ok(modified),
190 InsertRowResponse::Err(err) => Err(err),
191 };
192 }
193 #[cfg(not(target_arch = "wasm32"))]
194 {
195 let _ = (row_ptr, row_len);
196 Err("wasm32 only".into())
197 }
198}
199
200pub fn update_row(table_name: &str, row: Row) -> Result<(), String> {
201 let table_bytes = table_name.as_bytes();
202 let row_buf = encode_scratch(&row)?;
203 let (row_ptr, row_len) = row_buf.ptr_len();
204 let result = unsafe {
205 crate::host_calls::interstice_update_row(
206 table_bytes.as_ptr() as i32, table_bytes.len() as i32,
207 row_ptr, row_len,
208 )
209 };
210 if result < 0 { Err("update_row failed".into()) } else { Ok(()) }
211}
212
213pub fn delete_row(table_name: &str, primary_key: IndexKey) -> Result<(), String> {
214 let table_bytes = table_name.as_bytes();
215 let pk_buf = encode_scratch(&primary_key)?;
216 let (pk_ptr, pk_len) = pk_buf.ptr_len();
217 let result = unsafe {
218 crate::host_calls::interstice_delete_row(
219 table_bytes.as_ptr() as i32, table_bytes.len() as i32,
220 pk_ptr, pk_len,
221 )
222 };
223 if result < 0 { Err("delete_row failed".into()) } else { Ok(()) }
224}
225
226pub fn clear_table(module_selection: ModuleSelection, table_name: &str) -> Result<(), String> {
227 match module_selection {
228 ModuleSelection::Current => {
229 let table_bytes = table_name.as_bytes();
230 let result = unsafe {
231 crate::host_calls::interstice_clear_table(
232 table_bytes.as_ptr() as i32, table_bytes.len() as i32,
233 )
234 };
235 if result < 0 { Err("clear_table failed".into()) } else { Ok(()) }
236 }
237 ModuleSelection::Other(_) => {
238 Err("clear_table with ModuleSelection::Other is not supported in ABI v2".into())
239 }
240 }
241}
242
243pub fn scan(module_selection: ModuleSelection, table_name: String) -> Result<Vec<Row>, String> {
244 let call = HostCall::TableScan(TableScanRequest {
245 module_selection,
246 table_name,
247 });
248
249 let pack = host_call(call);
250 let response: TableScanResponse = unpack(pack);
251 match response {
252 TableScanResponse::Ok { rows } => Ok(rows),
253 TableScanResponse::Err(err) => Err(err),
254 }
255}
256
257pub fn get_by_primary_key(
258 module_selection: ModuleSelection,
259 table_name: &str,
260 primary_key: IndexKey,
261) -> Result<Option<Row>, String> {
262 match module_selection {
263 ModuleSelection::Current => {
264 let table_bytes = table_name.as_bytes();
265 let pk_buf = encode_scratch(&primary_key)?;
266 let (pk_ptr, pk_len) = pk_buf.ptr_len();
267 let written = unsafe {
268 #[cfg(target_arch = "wasm32")]
269 let resp_ptr = std::ptr::addr_of_mut!(crate::host_calls::DIRECT_RESP_BUF) as *mut u8 as i32;
270 #[cfg(target_arch = "wasm32")]
271 let resp_cap = 8192i32;
272 #[cfg(not(target_arch = "wasm32"))]
273 let resp_ptr = 0i32;
274 #[cfg(not(target_arch = "wasm32"))]
275 let resp_cap = 0i32;
276 crate::host_calls::interstice_get_by_pk(
277 table_bytes.as_ptr() as i32, table_bytes.len() as i32,
278 pk_ptr, pk_len,
279 resp_ptr, resp_cap,
280 )
281 };
282 if written < 0 {
283 return Err("get_by_pk: response buffer too small".into());
284 }
285 #[cfg(target_arch = "wasm32")]
286 let resp: TableGetByPrimaryKeyResponse = unsafe {
287 let buf = std::slice::from_raw_parts(
288 std::ptr::addr_of!(crate::host_calls::DIRECT_RESP_BUF) as *const u8,
289 written as usize,
290 );
291 decode(buf).map_err(|e| e.to_string())?
292 };
293 #[cfg(not(target_arch = "wasm32"))]
294 let resp: TableGetByPrimaryKeyResponse = { let _ = written; TableGetByPrimaryKeyResponse::Err("wasm32 only".into()) };
295 match resp {
296 TableGetByPrimaryKeyResponse::Ok(row) => Ok(row),
297 TableGetByPrimaryKeyResponse::Err(err) => Err(err),
298 }
299 }
300 other => {
301 let call = HostCall::TableGetByPrimaryKey(TableGetByPrimaryKeyRequest {
302 module_selection: other,
303 table_name: table_name.to_string(),
304 primary_key,
305 });
306 let pack = host_call(call);
307 let response: TableGetByPrimaryKeyResponse = unpack(pack);
308 match response {
309 TableGetByPrimaryKeyResponse::Ok(row) => Ok(row),
310 TableGetByPrimaryKeyResponse::Err(err) => Err(err),
311 }
312 }
313 }
314}
315
316pub fn scan_index(
317 module_selection: ModuleSelection,
318 table_name: String,
319 field_name: String,
320 query: IndexQuery,
321) -> Result<Vec<Row>, String> {
322 let call = HostCall::TableIndexScan(TableIndexScanRequest {
323 module_selection,
324 table_name,
325 field_name,
326 query,
327 });
328
329 let pack = host_call(call);
330 let response: TableIndexScanResponse = unpack(pack);
331 match response {
332 TableIndexScanResponse::Ok { rows } => Ok(rows),
333 TableIndexScanResponse::Err(err) => Err(err),
334 }
335}
336
337use interstice_abi::{QueryContext, ReducerContext};
338
339use crate::host_calls::{host_call, unpack};
340
341pub trait HostLog {
342 fn log(&self, message: &str);
343}
344
345pub trait HostCurrentNodeId {
346 fn current_node_id(&self) -> NodeId;
347}
348
349pub trait HostTime {
350 fn time_now_ms(&self) -> Result<u64, String>;
351}
352
353pub trait HostDeterministicRandom {
354 fn deterministic_random_u64(&self) -> Result<u64, String>;
355}
356
357pub trait HostSchedule {
358 fn schedule(&self, reducer_name: &str, delay_ms: u64) -> Result<(), String>;
359}
360
361impl HostLog for ReducerContext {
362 fn log(&self, message: &str) {
363 log(message);
364 }
365}
366
367impl HostLog for QueryContext {
368 fn log(&self, message: &str) {
369 log(message);
370 }
371}
372
373impl HostCurrentNodeId for ReducerContext {
374 fn current_node_id(&self) -> NodeId {
375 current_node_id()
376 }
377}
378
379impl HostCurrentNodeId for QueryContext {
380 fn current_node_id(&self) -> NodeId {
381 current_node_id()
382 }
383}
384
385impl HostTime for ReducerContext {
386 fn time_now_ms(&self) -> Result<u64, String> {
387 time_now_ms()
388 }
389}
390
391impl HostTime for QueryContext {
392 fn time_now_ms(&self) -> Result<u64, String> {
393 time_now_ms()
394 }
395}
396
397impl HostDeterministicRandom for ReducerContext {
398 fn deterministic_random_u64(&self) -> Result<u64, String> {
399 deterministic_random_u64()
400 }
401}
402
403impl HostDeterministicRandom for QueryContext {
404 fn deterministic_random_u64(&self) -> Result<u64, String> {
405 deterministic_random_u64()
406 }
407}
408
409impl HostSchedule for ReducerContext {
410 fn schedule(&self, reducer_name: &str, delay_ms: u64) -> Result<(), String> {
411 schedule(reducer_name.to_string(), delay_ms)
412 }
413}