155 lines
4.0 KiB
Rust
155 lines
4.0 KiB
Rust
#[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<Option<Arc<jspg::Jspg>>> = 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>) -> 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;
|