wcustom 0.1.0

Custom commands and extensions registry for wedb
//! 编译期事务过程静态表(对标 libs/server/Custom/CustomCommandManager.cs:
//! Register(CustomTransactionProcedure) 与 libs/server/Servers/RegisterApi.cs:
//! NewTransactionProc 的静态化承接)。
//!
//! C# 经 ExpandableMap 运行时分配 id 并按 id 回查;rust 按转写规范以固定
//! 槽位 const 表承接:AOF 回放按日志头 procedure_id 直接索引,RUNTXP 同径,
//! 零锁零查表零分配。

use crate::custom_transaction_procedure::{CustomTxnProc, DefaultTxnProc, SetTxnProc};

/// 事务过程工厂(对齐 C# `Func<CustomTransactionProcedure>`:
/// 零动态分派函数指针,产出静态分派的 [`CustomTxnProc`])。
pub type TxnProcFactory = fn() -> CustomTxnProc;

/// 静态事务过程表项(过程元数据 + 实例工厂)。
#[derive(Debug, Clone, Copy)]
pub struct StaticTxnProc {
  /// 过程名(小写规范化;C# 注册名承接)
  pub name: &'static str,
  /// 元数(0 = 不校验;负值 = 至少 -arity-1 个过程参数)
  pub arity: i32,
  /// 过程实例工厂(C# procCreator)
  pub factory: TxnProcFactory,
}

/// 静态表槽位 id(编译期固定;AOF 日志头 procedure_id 与 RUNTXP 首参直取)
pub mod txn_proc_slot {
  /// 空事务过程(DefaultTxnProc:三段式直通,libs/server/Custom/
  /// CustomTransactionProcedure.cs 抽象基类最小投影)
  pub const DEFAULT: u8 = 0;
  /// SETX 键值对写入过程(SetTxnProc)
  pub const SET: u8 = 1;
}

/// 编译期事务过程静态表(下标即过程 id;空槽 = 未注册)
pub const TXN_PROCS: [Option<StaticTxnProc>; 2] = [
  Some(StaticTxnProc {
    name: "default",
    arity: 0,
    factory: || {
      CustomTxnProc::Default(DefaultTxnProc {
        id: txn_proc_slot::DEFAULT,
      })
    },
  }),
  Some(StaticTxnProc {
    name: "setx",
    arity: 0,
    factory: || {
      CustomTxnProc::Set(SetTxnProc {
        id: txn_proc_slot::SET,
        args: Vec::new(),
      })
    },
  }),
];

/// 按槽位 id 取事务过程表项(越界 / 空槽 = 未注册;对标
/// CustomCommandManagerSession.cs:GetCustomTransactionProcedure 的未命中路径)
pub const fn txn_proc(id: u8) -> Option<&'static StaticTxnProc> {
  if (id as usize) < TXN_PROCS.len() {
    match &TXN_PROCS[id as usize] {
      Some(entry) => Some(entry),
      None => None,
    }
  } else {
    None
  }
}

#[cfg(test)]
mod tests {
  use wtxn::TxnProcedure;

  use super::*;
  use crate::custom_transaction_procedure::CustomTransactionProcedure;

  #[test]
  fn table_indexes_by_slot_id() {
    // 槽位 id 直取:default / setx 两槽,越界未注册
    assert_eq!(txn_proc(txn_proc_slot::DEFAULT).unwrap().name, "default");
    assert_eq!(txn_proc(txn_proc_slot::SET).unwrap().name, "setx");
    assert!(txn_proc(9).is_none());
  }

  #[test]
  fn factory_rebuilds_proc_with_slot_id() {
    let built = (txn_proc(txn_proc_slot::SET).unwrap().factory)();
    assert_eq!(built.id(), txn_proc_slot::SET);
    let built = (txn_proc(txn_proc_slot::DEFAULT).unwrap().factory)();
    assert_eq!(built.id(), txn_proc_slot::DEFAULT);
  }

  #[test]
  fn set_proc_binds_args() {
    let mut proc = (txn_proc(txn_proc_slot::SET).unwrap().factory)();
    proc.bind_args(&[b"k".to_vec(), b"v".to_vec()]);
    assert!(matches!(
      &proc,
      CustomTxnProc::Set(p) if p.args == vec![b"k".to_vec(), b"v".to_vec()]
    ));
  }
}