query test progress

This commit is contained in:
2026-03-10 18:25:29 -04:00
parent bb263190f6
commit 1c08a8f2b8
20 changed files with 1949 additions and 225 deletions

View File

@ -1,23 +1,30 @@
pub mod r#enum;
pub mod executor;
pub mod formats;
pub mod page;
pub mod punc;
pub mod relation;
pub mod schema;
pub mod r#type;
use crate::database::r#enum::Enum;
use crate::database::punc::Punc;
use crate::database::executor::{DatabaseExecutor, SpiExecutor};
use crate::database::punc::{Punc, Stem};
use crate::database::relation::Relation;
use crate::database::schema::Schema;
use crate::database::r#type::Type;
use serde_json::Value;
use std::collections::{HashMap, HashSet};
pub struct Database {
pub enums: HashMap<String, Enum>,
pub types: HashMap<String, Type>,
pub puncs: HashMap<String, Punc>,
pub relations: HashMap<String, Relation>,
pub schemas: HashMap<String, Schema>,
pub descendants: HashMap<String, Vec<String>>,
pub depths: HashMap<String, usize>,
pub executor: Box<dyn DatabaseExecutor + Send + Sync>,
}
impl Database {
@ -25,10 +32,12 @@ impl Database {
let mut db = Self {
enums: HashMap::new(),
types: HashMap::new(),
relations: HashMap::new(),
puncs: HashMap::new(),
schemas: HashMap::new(),
descendants: HashMap::new(),
depths: HashMap::new(),
executor: Box::new(SpiExecutor::new()),
};
if let Some(arr) = val.get("enums").and_then(|v| v.as_array()) {
@ -47,6 +56,14 @@ impl Database {
}
}
if let Some(arr) = val.get("relations").and_then(|v| v.as_array()) {
for item in arr {
if let Ok(def) = serde_json::from_value::<Relation>(item.clone()) {
db.relations.insert(def.constraint.clone(), def);
}
}
}
if let Some(arr) = val.get("puncs").and_then(|v| v.as_array()) {
for item in arr {
if let Ok(def) = serde_json::from_value::<Punc>(item.clone()) {
@ -73,12 +90,39 @@ impl Database {
db
}
/// Override the default executor for unit testing
pub fn with_executor(mut self, executor: Box<dyn DatabaseExecutor + Send + Sync>) -> Self {
self.executor = executor;
self
}
/// Executes a query expecting a single JSONB array return, representing rows.
pub fn query(&self, sql: &str, args: Option<&[Value]>) -> Result<Value, String> {
self.executor.query(sql, args)
}
/// Executes an operation (INSERT, UPDATE, DELETE, or pg_notify) that does not return rows.
pub fn execute(&self, sql: &str, args: Option<&[Value]>) -> Result<(), String> {
self.executor.execute(sql, args)
}
/// Returns the current authenticated user's ID
pub fn auth_user_id(&self) -> Result<String, String> {
self.executor.auth_user_id()
}
/// Returns the current transaction timestamp
pub fn timestamp(&self) -> Result<String, String> {
self.executor.timestamp()
}
/// Organizes the graph of the database, compiling regex, format functions, and caching relationships.
fn compile(&mut self) -> Result<(), String> {
self.collect_schemas();
self.collect_depths();
self.collect_descendants();
self.compile_schemas();
self.collect_stems();
Ok(())
}
@ -184,4 +228,154 @@ impl Database {
}
}
}
fn collect_stems(&mut self) {
let mut st_map: HashMap<String, Vec<Stem>> = HashMap::new();
for (name, _) in &self.puncs {
let mut stems = Vec::new();
let response_id = format!("{}.response", name);
if let Some(resp_schema) = self.schemas.get(&response_id) {
Self::discover_stems(
&self.types,
&self.schemas,
&self.relations,
&response_id,
resp_schema,
String::from(""),
None,
None,
&mut stems,
);
}
st_map.insert(name.clone(), stems);
}
for (name, stems) in st_map {
if let Some(p) = self.puncs.get_mut(&name) {
p.stems = stems;
}
}
}
fn discover_stems(
types: &HashMap<String, Type>,
schemas: &HashMap<String, Schema>,
relations: &HashMap<String, Relation>,
_schema_id: &str,
schema: &Schema,
current_path: String,
parent_type: Option<String>,
property_name: Option<String>,
stems: &mut Vec<Stem>,
) {
let mut is_entity = false;
let mut entity_type = String::new();
// Check if this schema resolves to an Entity
let mut current_ref = schema.obj.r#ref.clone();
let mut depth = 0;
while let Some(r) = current_ref {
if types.contains_key(&r) {
is_entity = true;
entity_type = r.clone();
break;
}
if let Some(s) = schemas.get(&r) {
current_ref = s.obj.r#ref.clone();
} else {
break;
}
depth += 1;
if depth > 20 {
break;
} // prevent infinite loop
}
if is_entity {
let final_path = if current_path.is_empty() {
"/".to_string()
} else {
current_path.clone()
};
let mut relation_col = None;
if let (Some(pt), Some(prop)) = (&parent_type, &property_name) {
let expected_col = format!("{}_id", prop);
let mut found = false;
// Try to find the exact relation from the database schema
for rel in relations.values() {
if rel.source_type == *pt && rel.destination_type == entity_type {
if rel.source_columns.contains(&expected_col) {
relation_col = Some(expected_col.clone());
found = true;
break;
}
} else if rel.source_type == entity_type && rel.destination_type == *pt {
if rel.source_columns.contains(&expected_col) {
relation_col = Some(expected_col.clone());
found = true;
break;
}
}
}
if !found {
// Fallback guess if explicit matching fails
relation_col = Some(expected_col);
}
}
stems.push(Stem {
path: final_path,
r#type: entity_type.clone(),
relation: relation_col,
});
}
// Pass the new parent downwards
let next_parent = if is_entity {
Some(entity_type.clone())
} else {
parent_type.clone()
};
if let Some(props) = &schema.obj.properties {
for (k, v) in props {
let next_path = format!(
"{}/{}",
if current_path.is_empty() {
""
} else {
&current_path
},
k
);
Self::discover_stems(
types,
schemas,
relations,
"",
v,
next_path,
next_parent.clone(),
Some(k.clone()),
stems,
);
}
}
if let Some(items) = &schema.obj.items {
Self::discover_stems(
types,
schemas,
relations,
"",
items,
current_path.clone(),
next_parent.clone(),
property_name.clone(),
stems,
);
}
}
}