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