#[cfg(not(test))] use pgrx::*; #[cfg(not(test))] pg_module_magic!(); #[cfg(test)] pub struct JsonB(pub serde_json::Value); pub mod database; pub mod drop; pub mod jspg; pub mod merger; pub mod queryer; pub mod validator; use std::sync::{Arc, RwLock}; lazy_static::lazy_static! { // Global Atomic Swap Container: // - RwLock: To protect the SWAP of the Option. // - Option: Because it starts empty. // - Arc: Because multiple running threads might hold the OLD engine while we swap. // - Jspg: The root semantic engine encapsulating the database metadata, validator, queryer, and merger. static ref GLOBAL_JSPG: RwLock>> = RwLock::new(None); } fn jspg_failure() -> JsonB { let error = crate::drop::Error { code: "ENGINE_NOT_INITIALIZED".to_string(), message: "JSPG extension has not been initialized via jspg_setup".to_string(), details: crate::drop::ErrorDetails { path: "".to_string(), }, }; let drop = crate::drop::Drop::with_errors(vec![error]); JsonB(serde_json::to_value(drop).unwrap()) } #[cfg_attr(not(test), pg_extern(strict))] pub fn jspg_setup(database: JsonB) -> JsonB { match crate::jspg::Jspg::new(&database.0) { Ok(new_jspg) => { let new_arc = Arc::new(new_jspg); // 3. ATOMIC SWAP { let mut lock = GLOBAL_JSPG.write().unwrap(); *lock = Some(new_arc); } let drop = crate::drop::Drop::success(); JsonB(serde_json::to_value(drop).unwrap()) } Err(drop) => JsonB(serde_json::to_value(drop).unwrap()), } } #[cfg_attr(not(test), pg_extern)] pub fn jspg_merge(data: JsonB) -> JsonB { // Try to acquire a read lock to get a clone of the Engine Arc let engine_opt = { let lock = GLOBAL_JSPG.read().unwrap(); lock.clone() }; match engine_opt { Some(engine) => { let drop = engine.merger.merge(data.0); JsonB(serde_json::to_value(drop).unwrap()) } None => jspg_failure(), } } #[cfg_attr(not(test), pg_extern)] pub fn jspg_query(schema_id: &str, stem: Option<&str>, filters: Option) -> JsonB { let engine_opt = { let lock = GLOBAL_JSPG.read().unwrap(); lock.clone() }; match engine_opt { Some(engine) => { let drop = engine .queryer .query(schema_id, stem, filters.as_ref().map(|f| &f.0)); JsonB(serde_json::to_value(drop).unwrap()) } None => jspg_failure(), } } // `mask_json_schema` has been removed as the mask architecture is fully replaced by Spi string queries during DB interactions. #[cfg_attr(not(test), pg_extern(strict, parallel_safe))] pub fn jspg_validate(schema_id: &str, instance: JsonB) -> JsonB { // 1. Acquire Snapshot let jspg_arc = { let lock = GLOBAL_JSPG.read().unwrap(); lock.clone() }; // 2. Validate (Lock-Free) if let Some(engine) = jspg_arc { let drop = engine.validator.validate(schema_id, &instance.0); JsonB(serde_json::to_value(drop).unwrap()) } else { jspg_failure() } } #[cfg_attr(not(test), pg_extern)] pub fn jspg_stems() -> JsonB { use serde_json::Value; let engine_opt = { let lock = GLOBAL_JSPG.read().unwrap(); lock.clone() }; match engine_opt { Some(engine) => { let mut result_arr = Vec::new(); for (schema_name, stems_map) in &engine.database.stems { let mut stems_arr = Vec::new(); for (path_key, stem_arc) in stems_map { stems_arr.push(serde_json::json!({ "path": path_key, "type": stem_arc.r#type, "relation": stem_arc.relation })); } result_arr.push(serde_json::json!({ "schema": schema_name, "stems": stems_arr })); } JsonB(serde_json::to_value(result_arr).unwrap_or(Value::Array(Vec::new()))) } None => JsonB(Value::Array(Vec::new())), } } #[cfg_attr(not(test), pg_extern(strict))] pub fn jspg_teardown() -> JsonB { let mut lock = GLOBAL_JSPG.write().unwrap(); *lock = None; let drop = crate::drop::Drop::success(); JsonB(serde_json::to_value(drop).unwrap()) } #[cfg(test)] pub mod tests;