reifydb-sdk 0.4.13

SDK for building ReifyDB operators, procedures, transforms and more
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2025 ReifyDB

pub mod exports;
pub mod wrapper;

use std::collections::HashMap;

use postcard::{from_bytes, to_stdvec};
use reifydb_abi::{constants::FFI_OK, context::context::ContextFFI, data::buffer::BufferFFI};
use reifydb_type::{
	params::Params,
	value::{Value, frame::frame::Frame},
};

use crate::{
	error::{FFIError, Result},
	operator::builder::ColumnsBuilder,
};

pub trait FFIProcedureMetadata {
	/// Procedure name (must be unique within a library)
	const NAME: &'static str;
	/// API version for FFI compatibility (must match host's CURRENT_API)
	const API: u32;
	/// Semantic version of the procedure (e.g., "1.0.0")
	const VERSION: &'static str;
	/// Human-readable description of the procedure
	const DESCRIPTION: &'static str;
}

pub trait FFIProcedure: 'static {
	fn new(config: &HashMap<String, Value>) -> Result<Self>
	where
		Self: Sized;

	/// Run the procedure.
	///
	/// Emit the result via `ctx.builder()` -- typically a single
	/// `emit_insert`, mirroring `FFIOperator::pull`.
	fn call(&mut self, ctx: &mut FFIProcedureContext, params: Params) -> Result<()>;
}

pub trait FFIProcedureWithMetadata: FFIProcedure + FFIProcedureMetadata {}
impl<T> FFIProcedureWithMetadata for T where T: FFIProcedure + FFIProcedureMetadata {}

pub struct FFIProcedureContext {
	pub(crate) ctx: *mut ContextFFI,
}

impl FFIProcedureContext {
	pub fn new(ctx: *mut ContextFFI) -> Self {
		assert!(!ctx.is_null(), "ContextFFI pointer must not be null");
		Self {
			ctx,
		}
	}

	pub fn rql(&self, rql: &str, params: Params) -> Result<Vec<Frame>> {
		raw_procedure_rql(self, rql, params)
	}

	/// Acquire a `ColumnsBuilder` for emitting output columns directly into
	/// host-pool-owned buffers. The builder borrows this context for the
	/// duration of the FFI call.
	pub fn builder(&mut self) -> ColumnsBuilder<'_> {
		ColumnsBuilder::from_raw_ctx(self.ctx)
	}
}

pub(crate) fn raw_procedure_rql(ctx: &FFIProcedureContext, rql: &str, params: Params) -> Result<Vec<Frame>> {
	let params_bytes = to_stdvec(&params)
		.map_err(|e| FFIError::Serialization(format!("failed to serialize params: {}", e)))?;

	let mut output = BufferFFI::empty();

	unsafe {
		let result = ((*ctx.ctx).callbacks.rql.rql)(
			ctx.ctx,
			rql.as_ptr(),
			rql.len(),
			params_bytes.as_ptr(),
			params_bytes.len(),
			&mut output,
		);

		if result == FFI_OK {
			let result_bytes = output.as_slice();
			let frames: Vec<Frame> = from_bytes(result_bytes)
				.map_err(|e| FFIError::Serialization(format!("failed to deserialize result: {}", e)))?;
			Ok(frames)
		} else {
			Err(FFIError::Other(format!("host_rql failed with code {}", result)))
		}
	}
}