1use std::{
5 any::Any,
6 cell::{Cell, UnsafeCell},
7 collections::HashMap,
8 panic::{AssertUnwindSafe, catch_unwind},
9 path::{Path, PathBuf},
10 process::abort,
11 sync::OnceLock,
12};
13
14use libloading::Symbol;
15use reifydb_abi::operator::capabilities::OperatorCapability;
16use reifydb_codec::{
17 encoded::{
18 row::EncodedRow,
19 shape::{RowShape, fingerprint::RowShapeFingerprint},
20 },
21 key::encoded::{EncodedKey, EncodedKeyRange},
22};
23use reifydb_core::{
24 common::CommitVersion,
25 interface::{
26 catalog::{
27 flow::FlowNodeId,
28 id::{NamespaceId, TableId},
29 namespace::Namespace,
30 table::Table,
31 },
32 change::Change,
33 },
34};
35use reifydb_extension::loader::ffi::LibraryCache;
36use reifydb_runtime::sync::rwlock::RwLock;
37use reifydb_sdk::{
38 config::Config,
39 error::{Result as SdkResult, SdkError},
40 operator::{OperatorLogic, Tick, view::native::NativeChangeView},
41};
42use reifydb_transaction::multi::RangeScope;
43use reifydb_value::{
44 Result,
45 error::Error,
46 value::{
47 Value,
48 constraint::TypeConstraint,
49 dictionary::{DictionaryEntryId, DictionaryId},
50 duration::Duration,
51 row_number::RowNumber,
52 },
53};
54use tracing::error;
55
56use crate::{
57 error::NativeOperatorError,
58 operator::{
59 BoxedOperator, Operator,
60 context::native::{NativeBridge, NativeOperatorContext},
61 stateful::row::allocate_row_numbers,
62 },
63 transaction::{FlowTransaction, slot::PersistFn},
64};
65
66fn run_or_abort<R>(node: FlowNodeId, stage: &'static str, f: impl FnOnce() -> SdkResult<R>) -> R {
67 match catch_unwind(AssertUnwindSafe(f)) {
68 Ok(Ok(value)) => value,
69 Ok(Err(e)) => {
70 error!(
71 operator_id = node.0,
72 stage, "native operator returned an error; operators must not fail - aborting: {:?}", e
73 );
74 abort();
75 }
76 Err(_) => {
77 error!(operator_id = node.0, stage, "native operator panicked - aborting");
78 abort();
79 }
80 }
81}
82
83pub const NATIVE_OPERATOR_MAGIC: u32 = 0x5244_424E;
84
85pub const NATIVE_ABI_TAG: u32 = 0x0308;
86
87pub type NativeOperatorCreateFn = fn(FlowNodeId, &Config) -> Result<BoxedBridgedOperator>;
88
89pub struct NativeOperatorColumn {
90 pub name: String,
91 pub field_type: TypeConstraint,
92 pub description: String,
93}
94
95pub struct NativeOperatorDescriptor {
96 pub abi_tag: u32,
97 pub name: String,
98 pub version: String,
99 pub description: String,
100 pub capabilities: u32,
101 pub input_columns: Vec<NativeOperatorColumn>,
102 pub output_columns: Vec<NativeOperatorColumn>,
103}
104
105pub fn native_operator_magic() -> u32 {
106 NATIVE_OPERATOR_MAGIC
107}
108
109pub fn check_native_abi_tag(abi_tag: u32) -> Result<()> {
110 if abi_tag != NATIVE_ABI_TAG {
111 return Err(Error::from(NativeOperatorError::AbiTagMismatch {
112 plugin: abi_tag,
113 host: NATIVE_ABI_TAG,
114 }));
115 }
116 Ok(())
117}
118
119pub trait BridgedOperator: Send {
120 fn id(&self) -> FlowNodeId;
121
122 fn capabilities(&self) -> &'static [OperatorCapability];
123
124 fn apply(&self, bridge: &mut dyn NativeBridge, change: Change) -> Result<Change>;
125
126 fn tick(&self, _bridge: &mut dyn NativeBridge, _tick: Tick) -> Result<Option<Change>> {
127 Ok(None)
128 }
129
130 fn ticks(&self) -> Option<Duration> {
131 None
132 }
133
134 fn flush_state(&self, _bridge: &mut dyn NativeBridge) -> Result<()> {
135 Ok(())
136 }
137}
138
139pub type BoxedBridgedOperator = Box<dyn BridgedOperator>;
140
141pub struct FlowNativeBridge<'a> {
142 txn: &'a mut FlowTransaction,
143 node: FlowNodeId,
144 now_nanos: u64,
145}
146
147impl<'a> FlowNativeBridge<'a> {
148 pub fn new(txn: &'a mut FlowTransaction, node: FlowNodeId) -> Self {
149 let now_nanos = txn.clock().now_nanos();
150 Self {
151 txn,
152 node,
153 now_nanos,
154 }
155 }
156}
157
158impl NativeBridge for FlowNativeBridge<'_> {
159 fn clock_now_nanos(&self) -> u64 {
160 self.now_nanos
161 }
162 fn state_get(&mut self, key: &EncodedKey) -> Result<Option<EncodedRow>> {
163 self.txn.state_get(self.node, key)
164 }
165 fn state_get_many(&mut self, keys: &[EncodedKey]) -> Result<Vec<(EncodedKey, EncodedRow)>> {
166 Ok(self.txn.state_get_many(self.node, keys)?.items.into_iter().map(|r| (r.key, r.row)).collect())
167 }
168 fn state_set(&mut self, key: &EncodedKey, value: EncodedRow) -> Result<()> {
169 self.txn.state_set(self.node, key, value)
170 }
171 fn state_remove(&mut self, key: &EncodedKey) -> Result<()> {
172 self.txn.state_remove(self.node, key)
173 }
174 fn state_drop(&mut self, key: &EncodedKey) -> Result<()> {
175 self.txn.state_drop(self.node, key)
176 }
177 fn state_clear(&mut self) -> Result<()> {
178 self.txn.state_clear(self.node)
179 }
180 fn state_range(&mut self, range: EncodedKeyRange) -> Result<Vec<(EncodedKey, EncodedRow)>> {
181 Ok(self.txn.state_range_all(self.node, range)?.items.into_iter().map(|r| (r.key, r.row)).collect())
182 }
183 fn internal_state_get(&mut self, key: &EncodedKey) -> Result<Option<EncodedRow>> {
184 self.txn.internal_state_get(self.node, key)
185 }
186 fn internal_state_get_many(&mut self, keys: &[EncodedKey]) -> Result<Vec<(EncodedKey, EncodedRow)>> {
187 Ok(self.txn
188 .internal_state_get_many(self.node, keys)?
189 .items
190 .into_iter()
191 .map(|r| (r.key, r.row))
192 .collect())
193 }
194 fn internal_state_set(&mut self, key: &EncodedKey, value: EncodedRow) -> Result<()> {
195 self.txn.internal_state_set(self.node, key, value)
196 }
197 fn internal_state_remove(&mut self, key: &EncodedKey) -> Result<()> {
198 self.txn.internal_state_remove(self.node, key)
199 }
200 fn internal_state_drop(&mut self, key: &EncodedKey) -> Result<()> {
201 self.txn.internal_state_drop(self.node, key)
202 }
203 fn internal_state_range(&mut self, range: EncodedKeyRange) -> Result<Vec<(EncodedKey, EncodedRow)>> {
204 Ok(self.txn
205 .internal_state_range_all(self.node, range)?
206 .items
207 .into_iter()
208 .map(|r| (r.key, r.row))
209 .collect())
210 }
211 fn allocate_row_numbers(&mut self, count: u64) -> Result<RowNumber> {
212 allocate_row_numbers(self.txn, self.node, count).map(RowNumber)
213 }
214 fn store_get(&mut self, key: &EncodedKey) -> Result<Option<EncodedRow>> {
215 self.txn.get(key)
216 }
217 fn store_contains(&mut self, key: &EncodedKey) -> Result<bool> {
218 self.txn.contains_key(key)
219 }
220 fn store_prefix(&mut self, prefix: &EncodedKey) -> Result<Vec<(EncodedKey, EncodedRow)>> {
221 Ok(self.txn.prefix(prefix)?.items.into_iter().map(|r| (r.key, r.row)).collect())
222 }
223 fn store_range(&mut self, range: EncodedKeyRange) -> Result<Vec<(EncodedKey, EncodedRow)>> {
224 let rows = self.txn.range(range, RangeScope::All, 1024).collect::<Result<Vec<_>>>()?;
225 Ok(rows.into_iter().map(|r| (r.key, r.row)).collect())
226 }
227 fn catalog_find_namespace(
228 &mut self,
229 namespace: NamespaceId,
230 version: CommitVersion,
231 ) -> Result<Option<Namespace>> {
232 Ok(self.txn.host_catalog().find_namespace(namespace, version))
233 }
234 fn catalog_find_namespace_by_name(
235 &mut self,
236 namespace: &str,
237 version: CommitVersion,
238 ) -> Result<Option<Namespace>> {
239 Ok(self.txn.host_catalog().find_namespace_by_name(namespace, version))
240 }
241 fn catalog_find_table(&mut self, table: TableId, version: CommitVersion) -> Result<Option<Table>> {
242 Ok(self.txn.host_catalog().find_table(table, version))
243 }
244 fn catalog_find_table_by_name(
245 &mut self,
246 namespace: NamespaceId,
247 name: &str,
248 version: CommitVersion,
249 ) -> Result<Option<Table>> {
250 Ok(self.txn.host_catalog().find_table_by_name(namespace, name, version))
251 }
252 fn catalog_find_row_shape(&mut self, fingerprint: RowShapeFingerprint) -> Result<Option<RowShape>> {
253 Ok(self.txn.host_catalog().find_row_shape(fingerprint))
254 }
255 fn dictionary_id_by_name(&mut self, name: &str) -> Result<Option<DictionaryId>> {
256 Ok(self.txn.find_dictionary_by_name(name).map(|d| d.id))
257 }
258 fn dictionary_find(&mut self, dictionary: DictionaryId, value: &Value) -> Result<Option<DictionaryEntryId>> {
259 match self.txn.find_dictionary(dictionary) {
260 Some(dict) => self.txn.find_in_dictionary(&dict, value),
261 None => Ok(None),
262 }
263 }
264 fn dictionary_get(&mut self, dictionary: DictionaryId, id: DictionaryEntryId) -> Result<Option<Value>> {
265 match self.txn.find_dictionary(dictionary) {
266 Some(dict) => self.txn.get_from_dictionary(&dict, id),
267 None => Ok(None),
268 }
269 }
270 fn state_get_many_visit(
271 &mut self,
272 keys: &[EncodedKey],
273 visit: &mut dyn FnMut(&EncodedKey, &EncodedRow) -> SdkResult<()>,
274 ) -> SdkResult<()> {
275 let batch = self.txn.state_get_many(self.node, keys).map_err(|e| SdkError::Other(e.to_string()))?;
276 for r in &batch.items {
277 visit(&r.key, &r.row)?;
278 }
279 Ok(())
280 }
281 fn internal_state_get_many_visit(
282 &mut self,
283 keys: &[EncodedKey],
284 visit: &mut dyn FnMut(&EncodedKey, &EncodedRow) -> SdkResult<()>,
285 ) -> SdkResult<()> {
286 let batch =
287 self.txn.internal_state_get_many(self.node, keys)
288 .map_err(|e| SdkError::Other(e.to_string()))?;
289 for r in &batch.items {
290 visit(&r.key, &r.row)?;
291 }
292 Ok(())
293 }
294 fn state_range_visit(
295 &mut self,
296 range: EncodedKeyRange,
297 visit: &mut dyn FnMut(&EncodedKey, &EncodedRow) -> SdkResult<()>,
298 ) -> SdkResult<()> {
299 let batch = self.txn.state_range_all(self.node, range).map_err(|e| SdkError::Other(e.to_string()))?;
300 for r in &batch.items {
301 visit(&r.key, &r.row)?;
302 }
303 Ok(())
304 }
305 fn store_range_visit(
306 &mut self,
307 range: EncodedKeyRange,
308 visit: &mut dyn FnMut(&EncodedKey, &EncodedRow) -> SdkResult<()>,
309 ) -> SdkResult<()> {
310 let rows =
311 self.txn.range(range, RangeScope::All, 1024)
312 .collect::<Result<Vec<_>>>()
313 .map_err(|e| SdkError::Other(e.to_string()))?;
314 for r in &rows {
315 visit(&r.key, &r.row)?;
316 }
317 Ok(())
318 }
319 fn store_prefix_visit(
320 &mut self,
321 prefix: &EncodedKey,
322 visit: &mut dyn FnMut(&EncodedKey, &EncodedRow) -> SdkResult<()>,
323 ) -> SdkResult<()> {
324 let batch = self.txn.prefix(prefix).map_err(|e| SdkError::Other(e.to_string()))?;
325 for r in &batch.items {
326 visit(&r.key, &r.row)?;
327 }
328 Ok(())
329 }
330}
331
332pub struct LoadedNativeOperatorInfo {
333 pub operator: String,
334 pub library_path: PathBuf,
335 pub version: String,
336 pub description: String,
337 pub input_columns: Vec<NativeOperatorColumn>,
338 pub output_columns: Vec<NativeOperatorColumn>,
339 pub capabilities: u32,
340}
341
342static GLOBAL_NATIVE_OPERATOR_LOADER: OnceLock<RwLock<NativeOperatorLoader>> = OnceLock::new();
343
344pub fn native_operator_loader() -> &'static RwLock<NativeOperatorLoader> {
345 GLOBAL_NATIVE_OPERATOR_LOADER.get_or_init(|| RwLock::new(NativeOperatorLoader::new()))
346}
347
348pub struct NativeOperatorLoader {
349 cache: LibraryCache,
350 operator_paths: HashMap<String, PathBuf>,
351}
352
353impl NativeOperatorLoader {
354 fn new() -> Self {
355 Self {
356 cache: LibraryCache::new(),
357 operator_paths: HashMap::new(),
358 }
359 }
360
361 fn load_library(&mut self, path: &Path) -> Result<bool> {
362 self.cache.check_magic(path, b"reifydb_native_operator_magic\0", NATIVE_OPERATOR_MAGIC).map_err(|_e| {
363 Error::from(NativeOperatorError::LibraryNotLoaded {
364 path: path.display().to_string(),
365 })
366 })
367 }
368
369 fn descriptor(&self, path: &Path) -> Result<NativeOperatorDescriptor> {
370 let library = self.cache.get(path).ok_or_else(|| {
371 Error::from(NativeOperatorError::LibraryNotLoaded {
372 path: path.display().to_string(),
373 })
374 })?;
375
376 let descriptor = unsafe {
377 let get_descriptor: Symbol<fn() -> NativeOperatorDescriptor> =
378 library.get(b"reifydb_native_operator_descriptor\0").map_err(|e| {
379 Error::from(NativeOperatorError::SymbolNotFound {
380 symbol: "reifydb_native_operator_descriptor",
381 cause: e.to_string(),
382 })
383 })?;
384 get_descriptor()
385 };
386
387 check_native_abi_tag(descriptor.abi_tag)?;
388
389 Ok(descriptor)
390 }
391
392 pub fn register_operator(&mut self, path: &Path) -> Result<Option<LoadedNativeOperatorInfo>> {
393 if !self.load_library(path)? {
394 return Ok(None);
395 }
396
397 let descriptor = self.descriptor(path)?;
398 self.operator_paths.insert(descriptor.name.clone(), path.to_path_buf());
399
400 Ok(Some(LoadedNativeOperatorInfo {
401 operator: descriptor.name,
402 library_path: path.to_path_buf(),
403 version: descriptor.version,
404 description: descriptor.description,
405 input_columns: descriptor.input_columns,
406 output_columns: descriptor.output_columns,
407 capabilities: descriptor.capabilities,
408 }))
409 }
410
411 pub fn has_operator(&self, operator: &str) -> bool {
412 self.operator_paths.contains_key(operator)
413 }
414
415 pub fn create_operator_by_name(
416 &mut self,
417 operator: &str,
418 operator_id: FlowNodeId,
419 config: &Config,
420 ) -> Result<BoxedOperator> {
421 let path = self
422 .operator_paths
423 .get(operator)
424 .ok_or_else(|| {
425 Error::from(NativeOperatorError::OperatorNotFound {
426 operator: operator.to_string(),
427 })
428 })?
429 .clone();
430
431 if !self.load_library(&path)? {
432 return Err(Error::from(NativeOperatorError::LibraryNotLoaded {
433 path: operator.to_string(),
434 }));
435 }
436
437 self.descriptor(&path)?;
438
439 let library = self.cache.get(&path).unwrap();
440 let create: NativeOperatorCreateFn = unsafe {
441 let create_symbol: Symbol<NativeOperatorCreateFn> =
442 library.get(b"reifydb_native_operator_create\0").map_err(|e| {
443 Error::from(NativeOperatorError::SymbolNotFound {
444 symbol: "reifydb_native_operator_create",
445 cause: e.to_string(),
446 })
447 })?;
448 *create_symbol
449 };
450
451 let bridged = create(operator_id, config)?;
452 let capabilities = bridged.capabilities();
453 Ok(Box::new(NativeBridgedOperator::new(bridged, operator_id, capabilities)))
454 }
455}
456
457impl Default for NativeOperatorLoader {
458 fn default() -> Self {
459 Self::new()
460 }
461}
462
463pub struct NativeOperatorAdapter<C> {
464 logic: UnsafeCell<C>,
465 node: FlowNodeId,
466 capabilities: &'static [OperatorCapability],
467}
468
469impl<C> NativeOperatorAdapter<C> {
470 pub fn new(logic: C, node: FlowNodeId, capabilities: &'static [OperatorCapability]) -> Self {
471 Self {
472 logic: UnsafeCell::new(logic),
473 node,
474 capabilities,
475 }
476 }
477}
478
479unsafe impl<C: Send> Send for NativeOperatorAdapter<C> {}
480
481impl<C: OperatorLogic + 'static> BridgedOperator for NativeOperatorAdapter<C> {
482 fn id(&self) -> FlowNodeId {
483 self.node
484 }
485
486 fn capabilities(&self) -> &'static [OperatorCapability] {
487 self.capabilities
488 }
489
490 fn apply(&self, bridge: &mut dyn NativeBridge, change: Change) -> Result<Change> {
491 let version = change.version;
492 let changed_at = change.changed_at;
493 let mut ctx = NativeOperatorContext::new(bridge, self.node);
494 {
495 let view = NativeChangeView::new(&change);
496 let logic = unsafe { &mut *self.logic.get() };
497 run_or_abort(self.node, "apply", || logic.apply(&mut ctx, view));
498 }
499 let diffs = ctx.take_diffs();
500 Ok(Change::from_flow(self.node, version, diffs, changed_at))
501 }
502
503 fn ticks(&self) -> Option<Duration> {
504 let logic = unsafe { &*self.logic.get() };
505 logic.ticks()
506 }
507
508 fn tick(&self, bridge: &mut dyn NativeBridge, tick: Tick) -> Result<Option<Change>> {
509 let now = tick.now;
510 let mut ctx = NativeOperatorContext::new(bridge, self.node);
511 {
512 let logic = unsafe { &mut *self.logic.get() };
513 run_or_abort(self.node, "tick", || logic.tick(&mut ctx, tick));
514 }
515 let diffs = ctx.take_diffs();
516 if diffs.is_empty() {
517 return Ok(None);
518 }
519 Ok(Some(Change::from_flow(self.node, CommitVersion(now.to_nanos()), diffs, now)))
520 }
521
522 fn flush_state(&self, bridge: &mut dyn NativeBridge) -> Result<()> {
523 let mut ctx = NativeOperatorContext::new(bridge, self.node);
524 let logic = unsafe { &mut *self.logic.get() };
525 run_or_abort(self.node, "flush_state", || logic.flush_state(&mut ctx));
526 Ok(())
527 }
528}
529
530#[derive(Clone, Copy)]
531struct SendableBridged(*const dyn BridgedOperator);
532unsafe impl Send for SendableBridged {}
533
534pub struct NativeBridgedOperator {
535 inner: BoxedBridgedOperator,
536 node: FlowNodeId,
537 capabilities: &'static [OperatorCapability],
538 last_registered_txn: Cell<u64>,
539}
540
541impl NativeBridgedOperator {
542 pub fn new(inner: BoxedBridgedOperator, node: FlowNodeId, capabilities: &'static [OperatorCapability]) -> Self {
543 Self {
544 inner,
545 node,
546 capabilities,
547 last_registered_txn: Cell::new(u64::MAX),
548 }
549 }
550
551 fn ensure_flush_slot(&self, txn: &mut FlowTransaction) -> Result<()> {
552 let txn_version = txn.version().0;
553 if self.last_registered_txn.get() != txn_version {
554 let captured = SendableBridged(&*self.inner as *const dyn BridgedOperator);
555 let node = self.node;
556 let persist: PersistFn = Box::new(move |txn: &mut FlowTransaction, _value: Box<dyn Any>| {
557 let captured = captured;
558 let bridged = unsafe { &*captured.0 };
559 let mut bridge = FlowNativeBridge::new(txn, node);
560 bridged.flush_state(&mut bridge)
561 });
562 let _ = txn.operator_state::<(), _>(node, move |_txn| Ok(((), persist)))?;
563 txn.mark_state_dirty(node);
564 self.last_registered_txn.set(txn_version);
565 }
566 Ok(())
567 }
568}
569
570unsafe impl Send for NativeBridgedOperator {}
571
572impl Operator for NativeBridgedOperator {
573 fn id(&self) -> FlowNodeId {
574 self.node
575 }
576
577 fn capabilities(&self) -> &[OperatorCapability] {
578 self.capabilities
579 }
580
581 fn apply(&self, txn: &mut FlowTransaction, change: Change) -> Result<Change> {
582 self.ensure_flush_slot(txn)?;
583 let mut bridge = FlowNativeBridge::new(txn, self.node);
584 self.inner.apply(&mut bridge, change)
585 }
586
587 fn ticks(&self) -> Option<Duration> {
588 self.inner.ticks()
589 }
590
591 fn tick(&self, txn: &mut FlowTransaction, tick: Tick) -> Result<Option<Change>> {
592 self.ensure_flush_slot(txn)?;
593 let mut bridge = FlowNativeBridge::new(txn, self.node);
594 self.inner.tick(&mut bridge, tick)
595 }
596}
597
598#[cfg(test)]
599mod tests {
600 use reifydb_abi::constants::OPERATOR_ABI_TAG;
601 use reifydb_extension::operator::ffi_loader::check_operator_abi_tag;
602
603 use super::{NATIVE_ABI_TAG, check_native_abi_tag};
604
605 #[test]
608 fn native_abi_tag_accepts_match_rejects_mismatch() {
609 assert!(check_native_abi_tag(NATIVE_ABI_TAG).is_ok());
610 assert!(check_native_abi_tag(NATIVE_ABI_TAG ^ 0x1).is_err());
611 assert!(check_native_abi_tag(0).is_err());
612 }
613
614 #[test]
615 fn ffi_abi_tag_accepts_match_rejects_mismatch() {
616 assert!(check_operator_abi_tag(OPERATOR_ABI_TAG).is_ok());
617 assert!(check_operator_abi_tag(OPERATOR_ABI_TAG ^ 0x1).is_err());
618 assert!(check_operator_abi_tag(0).is_err());
619 }
620
621 #[test]
624 fn native_and_ffi_tags_do_not_accept_each_other() {
625 assert_ne!(NATIVE_ABI_TAG, OPERATOR_ABI_TAG);
626 assert!(check_native_abi_tag(OPERATOR_ABI_TAG).is_err());
627 assert!(check_operator_abi_tag(NATIVE_ABI_TAG).is_err());
628 }
629}