reifydb-sdk 0.5.6

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

pub mod namespace;
pub mod table;

use std::{slice::from_raw_parts, str};

use reifydb_abi::catalog::{column::ColumnFFI, primary_key::PrimaryKeyFFI};
use reifydb_core::{
	common::CommitVersion,
	interface::catalog::{
		column::{Column, ColumnIndex},
		id::{ColumnId, NamespaceId, PrimaryKeyId, TableId},
		key::PrimaryKey,
		namespace::Namespace,
		table::Table,
	},
};
use reifydb_type::value::{
	constraint::{Constraint, TypeConstraint, bytes::MaxBytes, precision::Precision, scale::Scale},
	r#type::Type,
};

use crate::{error::FFIError, operator::context::OperatorContext};

pub struct Catalog<'a> {
	ctx: &'a mut OperatorContext,
}

impl<'a> Catalog<'a> {
	pub(crate) fn new(ctx: &'a mut OperatorContext) -> Self {
		Self {
			ctx,
		}
	}

	pub fn find_namespace(
		&self,
		namespace: NamespaceId,
		version: CommitVersion,
	) -> Result<Option<Namespace>, FFIError> {
		namespace::raw_catalog_find_namespace(self.ctx, namespace, version)
	}

	pub fn find_namespace_by_name(
		&self,
		namespace: &str,
		version: CommitVersion,
	) -> Result<Option<Namespace>, FFIError> {
		namespace::raw_catalog_find_namespace_by_name(self.ctx, namespace, version)
	}

	pub fn find_table(&self, table: TableId, version: CommitVersion) -> Result<Option<Table>, FFIError> {
		table::raw_catalog_find_table(self.ctx, table, version)
	}

	pub fn find_table_by_name(
		&self,
		namespace: NamespaceId,
		name: &str,
		version: CommitVersion,
	) -> Result<Option<Table>, FFIError> {
		table::raw_catalog_find_table_by_name(self.ctx, namespace, name, version)
	}
}

pub(crate) unsafe fn unmarshal_column(ffi_col: &ColumnFFI) -> Result<Column, FFIError> {
	let name_bytes = if !ffi_col.name.ptr.is_null() && ffi_col.name.len > 0 {
		unsafe { from_raw_parts(ffi_col.name.ptr, ffi_col.name.len) }
	} else {
		&[]
	};

	let name = str::from_utf8(name_bytes)
		.map_err(|_| FFIError::Other("Invalid UTF-8 in column name".to_string()))?
		.to_string();

	let constraint = decode_type_constraint(
		ffi_col.base_type,
		ffi_col.constraint_type,
		ffi_col.constraint_param1,
		ffi_col.constraint_param2,
	)?;

	Ok(Column {
		id: ColumnId(ffi_col.id),
		name,
		constraint,
		properties: Vec::new(),
		index: ColumnIndex(ffi_col.column_index),
		auto_increment: ffi_col.auto_increment != 0,
		dictionary_id: None,
	})
}

pub(crate) unsafe fn unmarshal_primary_key(ffi_pk: &PrimaryKeyFFI) -> Result<PrimaryKey, FFIError> {
	let column_ids = if !ffi_pk.column_ids.is_null() && ffi_pk.column_count > 0 {
		unsafe { from_raw_parts(ffi_pk.column_ids, ffi_pk.column_count).to_vec() }
	} else {
		Vec::new()
	};

	let columns = column_ids
		.into_iter()
		.enumerate()
		.map(|(idx, col_id)| Column {
			id: ColumnId(col_id),
			name: format!("col_{}", col_id),
			constraint: TypeConstraint::unconstrained(Type::Int4),
			properties: Vec::new(),
			index: ColumnIndex(idx as u8),
			auto_increment: false,
			dictionary_id: None,
		})
		.collect();

	Ok(PrimaryKey {
		id: PrimaryKeyId(ffi_pk.id),
		columns,
	})
}

fn decode_type_constraint(
	base_type: u8,
	constraint_type: u8,
	param1: u32,
	param2: u32,
) -> Result<TypeConstraint, FFIError> {
	let ty = Type::from_u8(base_type);

	match constraint_type {
		0 => Ok(TypeConstraint::unconstrained(ty)),
		1 => Ok(TypeConstraint::with_constraint(ty, Constraint::MaxBytes(MaxBytes::new(param1)))),
		2 => Ok(TypeConstraint::with_constraint(
			ty,
			Constraint::PrecisionScale(Precision::new(param1 as u8), Scale::new(param2 as u8)),
		)),
		_ => Err(FFIError::Other("Invalid constraint type".to_string())),
	}
}