use crate::runtime::path::render_host_visible_path;
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum LuaRuntimeDatabaseProviderMode {
#[default]
DynamicLibrary,
HostCallback,
SpaceController,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum LuaRuntimeDatabaseCallbackMode {
#[default]
Standard,
Json,
}
pub(crate) fn database_callback_mode_name(mode: LuaRuntimeDatabaseCallbackMode) -> &'static str {
match mode {
LuaRuntimeDatabaseCallbackMode::Standard => "standard",
LuaRuntimeDatabaseCallbackMode::Json => "json",
}
}
pub(crate) fn require_database_provider_callback_registration(
provider_label: &str,
callback_mode: LuaRuntimeDatabaseCallbackMode,
has_callback: bool,
) -> Result<(), String> {
if has_callback {
return Ok(());
}
Err(format!(
"{} host-callback mode is enabled but no {} callback is registered",
provider_label,
database_callback_mode_name(callback_mode)
))
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeDatabaseKind {
Sqlite,
LanceDb,
}
impl RuntimeDatabaseKind {
fn sidecar_directory_name(self) -> &'static str {
match self {
RuntimeDatabaseKind::Sqlite => "sqlite",
RuntimeDatabaseKind::LanceDb => "lancedb",
}
}
fn default_database_path(self, provider_storage_dir: &Path, skill_name: &str) -> PathBuf {
match self {
RuntimeDatabaseKind::Sqlite => {
provider_storage_dir.join(format!("{}.sqlite3", skill_name))
}
RuntimeDatabaseKind::LanceDb => provider_storage_dir.to_path_buf(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeDatabaseBindingContextSpec {
pub space_label: String,
pub skill_id: String,
pub root_name: String,
pub space_root: String,
pub skill_dir: String,
pub skill_dir_name: String,
pub database_kind: RuntimeDatabaseKind,
pub default_database_path: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RuntimeDatabaseBindingPlan {
pub(crate) skill_dir_name: String,
pub(crate) provider_storage_dir: PathBuf,
pub(crate) default_database_path: String,
pub(crate) context: RuntimeDatabaseBindingContext,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeDatabaseBindingContext {
pub space_label: String,
pub skill_id: String,
pub binding_tag: String,
pub root_name: String,
pub space_root: String,
pub skill_dir: String,
pub skill_dir_name: String,
pub database_kind: RuntimeDatabaseKind,
pub default_database_path: String,
}
impl RuntimeDatabaseBindingContext {
pub fn new(spec: RuntimeDatabaseBindingContextSpec) -> Self {
let RuntimeDatabaseBindingContextSpec {
space_label,
skill_id,
root_name,
space_root,
skill_dir,
skill_dir_name,
database_kind,
default_database_path,
} = spec;
Self {
binding_tag: format!("{}-{}", space_label, skill_id),
space_label,
skill_id,
root_name,
space_root,
skill_dir,
skill_dir_name,
database_kind,
default_database_path,
}
}
}
pub(crate) fn build_runtime_database_binding_plan(
root_name: &str,
skill_name: &str,
skill_dir: &Path,
database_dir_name: &str,
database_kind: RuntimeDatabaseKind,
) -> Result<RuntimeDatabaseBindingPlan, String> {
let skill_dir_name = skill_dir
.file_name()
.and_then(|name| name.to_str())
.ok_or_else(|| {
format!(
"invalid skill directory name for {}: {}",
skill_name,
render_host_visible_path(skill_dir)
)
})?
.to_string();
let skills_root = skill_dir.parent().ok_or_else(|| {
format!(
"invalid skill root for {}: {}",
skill_name,
render_host_visible_path(skill_dir)
)
})?;
let sidecar_root = skills_root
.parent()
.unwrap_or(skills_root)
.join(database_dir_name);
let provider_storage_dir = sidecar_root
.join(database_kind.sidecar_directory_name())
.join(skill_name);
let default_database_path = render_host_visible_path(
&database_kind.default_database_path(&provider_storage_dir, skill_name),
);
let context = RuntimeDatabaseBindingContext::new(RuntimeDatabaseBindingContextSpec {
space_label: root_name.to_string(),
skill_id: skill_name.to_string(),
root_name: root_name.to_string(),
space_root: render_host_visible_path(&sidecar_root),
skill_dir: render_host_visible_path(skill_dir),
skill_dir_name: skill_dir_name.clone(),
database_kind,
default_database_path: default_database_path.clone(),
});
Ok(RuntimeDatabaseBindingPlan {
skill_dir_name,
provider_storage_dir,
default_database_path,
context,
})
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeSqliteProviderAction {
ExecuteScript,
ExecuteBatch,
QueryJson,
QueryStream,
QueryStreamWaitMetrics,
QueryStreamChunk,
QueryStreamClose,
TokenizeText,
UpsertCustomWord,
RemoveCustomWord,
ListCustomWords,
EnsureFtsIndex,
RebuildFtsIndex,
UpsertFtsDocument,
DeleteFtsDocument,
SearchFts,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeLanceDbProviderAction {
CreateTable,
VectorUpsert,
VectorSearch,
Delete,
DropTable,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RuntimeSqliteProviderRequest {
pub action: RuntimeSqliteProviderAction,
pub binding: RuntimeDatabaseBindingContext,
pub input: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RuntimeLanceDbProviderRequest {
pub action: RuntimeLanceDbProviderAction,
pub binding: RuntimeDatabaseBindingContext,
pub input: Value,
}
pub type RuntimeSqliteProviderCallback =
Arc<dyn Fn(&RuntimeSqliteProviderRequest) -> Result<Value, String> + Send + Sync>;
pub type RuntimeLanceDbProviderCallback = Arc<
dyn Fn(&RuntimeLanceDbProviderRequest) -> Result<RuntimeLanceDbProviderResult, String>
+ Send
+ Sync,
>;
pub type RuntimeSqliteProviderJsonCallback =
Arc<dyn Fn(&str) -> Result<String, String> + Send + Sync>;
pub type RuntimeLanceDbProviderJsonCallback =
Arc<dyn Fn(&str) -> Result<String, String> + Send + Sync>;
#[derive(Clone, Default)]
pub(crate) struct RuntimeDatabaseProviderCallbacks {
sqlite_standard: Option<RuntimeSqliteProviderCallback>,
lancedb_standard: Option<RuntimeLanceDbProviderCallback>,
sqlite_json: Option<RuntimeSqliteProviderJsonCallback>,
lancedb_json: Option<RuntimeLanceDbProviderJsonCallback>,
}
impl RuntimeDatabaseProviderCallbacks {
pub(crate) fn capture_process_defaults() -> Self {
Self {
sqlite_standard: clone_database_provider_callback_registry_value(
sqlite_provider_callback_registry(),
),
lancedb_standard: clone_database_provider_callback_registry_value(
lancedb_provider_callback_registry(),
),
sqlite_json: clone_database_provider_callback_registry_value(
sqlite_provider_json_callback_registry(),
),
lancedb_json: clone_database_provider_callback_registry_value(
lancedb_provider_json_callback_registry(),
),
}
}
pub(crate) fn has_sqlite_provider_callback_for_mode(
&self,
callback_mode: LuaRuntimeDatabaseCallbackMode,
) -> bool {
match callback_mode {
LuaRuntimeDatabaseCallbackMode::Standard => self.sqlite_standard.is_some(),
LuaRuntimeDatabaseCallbackMode::Json => self.sqlite_json.is_some(),
}
}
pub(crate) fn has_lancedb_provider_callback_for_mode(
&self,
callback_mode: LuaRuntimeDatabaseCallbackMode,
) -> bool {
match callback_mode {
LuaRuntimeDatabaseCallbackMode::Standard => self.lancedb_standard.is_some(),
LuaRuntimeDatabaseCallbackMode::Json => self.lancedb_json.is_some(),
}
}
pub(crate) fn dispatch_sqlite_provider_request(
&self,
request: &RuntimeSqliteProviderRequest,
callback_mode: LuaRuntimeDatabaseCallbackMode,
) -> Result<Value, String> {
match callback_mode {
LuaRuntimeDatabaseCallbackMode::Standard => {
let callback = self.sqlite_standard.clone().ok_or_else(|| {
"SQLite host-callback mode requires one registered standard callback"
.to_string()
})?;
callback(request)
}
LuaRuntimeDatabaseCallbackMode::Json => {
let callback = self.sqlite_json.clone().ok_or_else(|| {
"SQLite host-callback JSON mode requires one registered JSON callback"
.to_string()
})?;
let request_json = serde_json::to_string(request).map_err(|error| {
format!("failed to encode sqlite provider request: {}", error)
})?;
let response_json = callback(&request_json)?;
serde_json::from_str::<Value>(&response_json).map_err(|error| {
format!("failed to parse sqlite provider response json: {}", error)
})
}
}
}
pub(crate) fn dispatch_lancedb_provider_request(
&self,
request: &RuntimeLanceDbProviderRequest,
callback_mode: LuaRuntimeDatabaseCallbackMode,
) -> Result<RuntimeLanceDbProviderResult, String> {
match callback_mode {
LuaRuntimeDatabaseCallbackMode::Standard => {
let callback = self.lancedb_standard.clone().ok_or_else(|| {
"LanceDB host-callback mode requires one registered standard callback"
.to_string()
})?;
callback(request)
}
LuaRuntimeDatabaseCallbackMode::Json => {
let callback = self.lancedb_json.clone().ok_or_else(|| {
"LanceDB host-callback JSON mode requires one registered JSON callback"
.to_string()
})?;
let request_json = serde_json::to_string(request).map_err(|error| {
format!("failed to encode lancedb provider request: {}", error)
})?;
let response_json = callback(&request_json)?;
let value: Value = serde_json::from_str(&response_json).map_err(|error| {
format!("failed to parse lancedb provider response json: {}", error)
})?;
let meta = value
.get("meta")
.cloned()
.unwrap_or_else(|| Value::Object(Default::default()));
let bytes = value
.get("data_base64")
.and_then(Value::as_str)
.map(|text| {
BASE64_STANDARD.decode(text.as_bytes()).map_err(|error| {
format!("failed to decode lancedb provider data_base64: {}", error)
})
})
.transpose()?
.unwrap_or_default();
Ok(RuntimeLanceDbProviderResult::binary(meta, bytes))
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RuntimeLanceDbProviderResult {
pub meta: Value,
pub bytes: Vec<u8>,
}
impl RuntimeLanceDbProviderResult {
pub fn json(meta: Value) -> Self {
Self {
meta,
bytes: Vec::new(),
}
}
pub fn binary(meta: Value, bytes: Vec<u8>) -> Self {
Self { meta, bytes }
}
}
pub fn set_sqlite_provider_callback(callback: Option<RuntimeSqliteProviderCallback>) {
set_database_provider_callback_registry_value(sqlite_provider_callback_registry(), callback);
}
pub fn set_lancedb_provider_callback(callback: Option<RuntimeLanceDbProviderCallback>) {
set_database_provider_callback_registry_value(lancedb_provider_callback_registry(), callback);
}
pub fn set_sqlite_provider_json_callback(callback: Option<RuntimeSqliteProviderJsonCallback>) {
set_database_provider_callback_registry_value(
sqlite_provider_json_callback_registry(),
callback,
);
}
pub fn set_lancedb_provider_json_callback(callback: Option<RuntimeLanceDbProviderJsonCallback>) {
set_database_provider_callback_registry_value(
lancedb_provider_json_callback_registry(),
callback,
);
}
fn lock_database_provider_callback_registry<T>(
registry: &'static Mutex<Option<T>>,
) -> MutexGuard<'static, Option<T>> {
registry
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn set_database_provider_callback_registry_value<T>(
registry: &'static Mutex<Option<T>>,
callback: Option<T>,
) {
let mut guard = lock_database_provider_callback_registry(registry);
*guard = callback;
}
fn clone_database_provider_callback_registry_value<T: Clone>(
registry: &'static Mutex<Option<T>>,
) -> Option<T> {
lock_database_provider_callback_registry(registry).clone()
}
fn sqlite_provider_callback_registry() -> &'static Mutex<Option<RuntimeSqliteProviderCallback>> {
static REGISTRY: OnceLock<Mutex<Option<RuntimeSqliteProviderCallback>>> = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(None))
}
fn lancedb_provider_callback_registry() -> &'static Mutex<Option<RuntimeLanceDbProviderCallback>> {
static REGISTRY: OnceLock<Mutex<Option<RuntimeLanceDbProviderCallback>>> = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(None))
}
fn sqlite_provider_json_callback_registry()
-> &'static Mutex<Option<RuntimeSqliteProviderJsonCallback>> {
static REGISTRY: OnceLock<Mutex<Option<RuntimeSqliteProviderJsonCallback>>> = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(None))
}
fn lancedb_provider_json_callback_registry()
-> &'static Mutex<Option<RuntimeLanceDbProviderJsonCallback>> {
static REGISTRY: OnceLock<Mutex<Option<RuntimeLanceDbProviderJsonCallback>>> = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(None))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use std::panic::{self, AssertUnwindSafe};
use std::path::Path;
use std::sync::{Mutex, OnceLock};
fn database_callback_test_lock() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
}
struct ProcessCallbackRestoreGuard {
snapshot: RuntimeDatabaseProviderCallbacks,
}
impl ProcessCallbackRestoreGuard {
fn capture() -> Self {
Self {
snapshot: RuntimeDatabaseProviderCallbacks::capture_process_defaults(),
}
}
}
impl Drop for ProcessCallbackRestoreGuard {
fn drop(&mut self) {
set_sqlite_provider_callback(self.snapshot.sqlite_standard.clone());
set_lancedb_provider_callback(self.snapshot.lancedb_standard.clone());
set_sqlite_provider_json_callback(self.snapshot.sqlite_json.clone());
set_lancedb_provider_json_callback(self.snapshot.lancedb_json.clone());
}
}
fn sample_binding_context(database_kind: RuntimeDatabaseKind) -> RuntimeDatabaseBindingContext {
RuntimeDatabaseBindingContext::new(RuntimeDatabaseBindingContextSpec {
space_label: "ROOT".to_string(),
skill_id: "test-skill".to_string(),
root_name: "ROOT".to_string(),
space_root: "D:/runtime-test-root/__database".to_string(),
skill_dir: "D:/runtime-test-root/skills/test-skill".to_string(),
skill_dir_name: "test-skill".to_string(),
database_kind,
default_database_path: "D:/runtime-test-root/__database/default.db".to_string(),
})
}
#[test]
fn database_binding_plan_resolves_provider_paths() {
let skill_dir = Path::new("D:/runtime-test-root/skills/demo-skill");
let runtime_root = skill_dir
.parent()
.and_then(Path::parent)
.expect("runtime root path");
let database_root = runtime_root.join("databases");
let sqlite_plan = build_runtime_database_binding_plan(
"ROOT",
"demo-skill",
skill_dir,
"databases",
RuntimeDatabaseKind::Sqlite,
)
.expect("sqlite binding plan");
let sqlite_storage_dir = database_root.join("sqlite").join("demo-skill");
let sqlite_database_path =
render_host_visible_path(&sqlite_storage_dir.join("demo-skill.sqlite3"));
assert_eq!(sqlite_plan.skill_dir_name, "demo-skill");
assert_eq!(sqlite_plan.provider_storage_dir, sqlite_storage_dir);
assert_eq!(sqlite_plan.default_database_path, sqlite_database_path);
assert_eq!(
sqlite_plan.context.space_root,
render_host_visible_path(&database_root)
);
assert_eq!(
sqlite_plan.context.default_database_path,
sqlite_plan.default_database_path
);
assert_eq!(
sqlite_plan.context.database_kind,
RuntimeDatabaseKind::Sqlite
);
let lancedb_plan = build_runtime_database_binding_plan(
"ROOT",
"demo-skill",
skill_dir,
"databases",
RuntimeDatabaseKind::LanceDb,
)
.expect("lancedb binding plan");
let lancedb_storage_dir = database_root.join("lancedb").join("demo-skill");
assert_eq!(lancedb_plan.skill_dir_name, "demo-skill");
assert_eq!(lancedb_plan.provider_storage_dir, lancedb_storage_dir);
assert_eq!(
lancedb_plan.default_database_path,
render_host_visible_path(&lancedb_plan.provider_storage_dir)
);
assert_eq!(
lancedb_plan.context.default_database_path,
lancedb_plan.default_database_path
);
assert_eq!(
lancedb_plan.context.database_kind,
RuntimeDatabaseKind::LanceDb
);
}
#[test]
fn database_binding_plan_invalid_skill_dir_error_uses_host_visible_path() {
let skill_dir = Path::new("");
let error = build_runtime_database_binding_plan(
"ROOT",
"demo-skill",
skill_dir,
"databases",
RuntimeDatabaseKind::Sqlite,
)
.expect_err("empty skill directory should fail");
let expected_prefix = format!(
"invalid skill directory name for demo-skill: {}",
render_host_visible_path(skill_dir)
);
assert_eq!(error, expected_prefix);
}
#[test]
fn callback_registration_requirement_reports_transport_mode() {
assert!(
require_database_provider_callback_registration(
"SQLite",
LuaRuntimeDatabaseCallbackMode::Standard,
true,
)
.is_ok()
);
assert_eq!(
require_database_provider_callback_registration(
"SQLite",
LuaRuntimeDatabaseCallbackMode::Standard,
false,
)
.expect_err("missing standard callback should fail"),
"SQLite host-callback mode is enabled but no standard callback is registered"
);
assert_eq!(
require_database_provider_callback_registration(
"LanceDB",
LuaRuntimeDatabaseCallbackMode::Json,
false,
)
.expect_err("missing JSON callback should fail"),
"LanceDB host-callback mode is enabled but no json callback is registered"
);
}
#[test]
fn database_provider_callback_registry_recovers_after_poisoned_lock() {
let _serial_guard = database_callback_test_lock()
.lock()
.expect("lock callback test guard");
let _restore_guard = ProcessCallbackRestoreGuard::capture();
set_sqlite_provider_callback(None);
let poison_result = panic::catch_unwind(AssertUnwindSafe(|| {
let _registry_guard = sqlite_provider_callback_registry()
.lock()
.expect("initial sqlite provider callback registry lock");
panic!("poison sqlite provider callback registry for recovery test");
}));
assert!(poison_result.is_err());
let callback: RuntimeSqliteProviderCallback =
Arc::new(|_| Ok(json!({ "source": "sqlite-standard-recovered" })));
set_sqlite_provider_callback(Some(callback));
let snapshot = RuntimeDatabaseProviderCallbacks::capture_process_defaults();
let sqlite_request = RuntimeSqliteProviderRequest {
action: RuntimeSqliteProviderAction::QueryJson,
binding: sample_binding_context(RuntimeDatabaseKind::Sqlite),
input: json!({ "sql": "select 1" }),
};
assert_eq!(
snapshot
.dispatch_sqlite_provider_request(
&sqlite_request,
LuaRuntimeDatabaseCallbackMode::Standard,
)
.expect("dispatch recovered sqlite callback"),
json!({ "source": "sqlite-standard-recovered" })
);
}
#[test]
fn captured_callback_snapshots_stay_engine_scoped() {
let _serial_guard = database_callback_test_lock()
.lock()
.expect("lock callback test guard");
let _restore_guard = ProcessCallbackRestoreGuard::capture();
set_sqlite_provider_callback(Some(Arc::new(|_| {
Ok(json!({ "source": "sqlite-standard-a" }))
})));
set_sqlite_provider_json_callback(Some(Arc::new(|_| {
Ok("{\"source\":\"sqlite-json-a\"}".to_string())
})));
set_lancedb_provider_callback(Some(Arc::new(|_| {
Ok(RuntimeLanceDbProviderResult::json(
json!({ "source": "lancedb-standard-a" }),
))
})));
set_lancedb_provider_json_callback(Some(Arc::new(|_| {
Ok("{\"meta\":{\"source\":\"lancedb-json-a\"}}".to_string())
})));
let snapshot_a = RuntimeDatabaseProviderCallbacks::capture_process_defaults();
set_sqlite_provider_callback(Some(Arc::new(|_| {
Ok(json!({ "source": "sqlite-standard-b" }))
})));
set_sqlite_provider_json_callback(Some(Arc::new(|_| {
Ok("{\"source\":\"sqlite-json-b\"}".to_string())
})));
set_lancedb_provider_callback(Some(Arc::new(|_| {
Ok(RuntimeLanceDbProviderResult::json(
json!({ "source": "lancedb-standard-b" }),
))
})));
set_lancedb_provider_json_callback(Some(Arc::new(|_| {
Ok("{\"meta\":{\"source\":\"lancedb-json-b\"}}".to_string())
})));
let snapshot_b = RuntimeDatabaseProviderCallbacks::capture_process_defaults();
let sqlite_request = RuntimeSqliteProviderRequest {
action: RuntimeSqliteProviderAction::QueryJson,
binding: sample_binding_context(RuntimeDatabaseKind::Sqlite),
input: json!({ "sql": "select 1" }),
};
let lancedb_request = RuntimeLanceDbProviderRequest {
action: RuntimeLanceDbProviderAction::VectorSearch,
binding: sample_binding_context(RuntimeDatabaseKind::LanceDb),
input: json!({ "table": "demo" }),
};
assert_eq!(
snapshot_a
.dispatch_sqlite_provider_request(
&sqlite_request,
LuaRuntimeDatabaseCallbackMode::Standard,
)
.expect("dispatch sqlite standard A"),
json!({ "source": "sqlite-standard-a" })
);
assert_eq!(
snapshot_a
.dispatch_sqlite_provider_request(
&sqlite_request,
LuaRuntimeDatabaseCallbackMode::Json,
)
.expect("dispatch sqlite json A"),
json!({ "source": "sqlite-json-a" })
);
assert_eq!(
snapshot_b
.dispatch_sqlite_provider_request(
&sqlite_request,
LuaRuntimeDatabaseCallbackMode::Standard,
)
.expect("dispatch sqlite standard B"),
json!({ "source": "sqlite-standard-b" })
);
assert_eq!(
snapshot_b
.dispatch_sqlite_provider_request(
&sqlite_request,
LuaRuntimeDatabaseCallbackMode::Json,
)
.expect("dispatch sqlite json B"),
json!({ "source": "sqlite-json-b" })
);
assert_eq!(
snapshot_a
.dispatch_lancedb_provider_request(
&lancedb_request,
LuaRuntimeDatabaseCallbackMode::Standard,
)
.expect("dispatch lancedb standard A")
.meta,
json!({ "source": "lancedb-standard-a" })
);
assert_eq!(
snapshot_a
.dispatch_lancedb_provider_request(
&lancedb_request,
LuaRuntimeDatabaseCallbackMode::Json,
)
.expect("dispatch lancedb json A")
.meta,
json!({ "source": "lancedb-json-a" })
);
assert_eq!(
snapshot_b
.dispatch_lancedb_provider_request(
&lancedb_request,
LuaRuntimeDatabaseCallbackMode::Standard,
)
.expect("dispatch lancedb standard B")
.meta,
json!({ "source": "lancedb-standard-b" })
);
assert_eq!(
snapshot_b
.dispatch_lancedb_provider_request(
&lancedb_request,
LuaRuntimeDatabaseCallbackMode::Json,
)
.expect("dispatch lancedb json B")
.meta,
json!({ "source": "lancedb-json-b" })
);
}
}