Compare commits

...

4 Commits

Author SHA1 Message Date
c2267b68d8 version: 1.0.124 2026-04-17 05:53:54 -04:00
f58d1a32a3 full database extracton 2026-04-17 05:53:44 -04:00
d9f4a90225 version: 1.0.123 2026-04-17 02:24:09 -04:00
509194b55f in, nin to of, nof for go and dart generator compatibility 2026-04-17 02:24:03 -04:00
7 changed files with 213 additions and 105 deletions

View File

@ -234,7 +234,7 @@ The Queryer transforms Postgres into a pre-compiled Semantic Query Engine, desig
* **Dynamic Filtering**: Binds parameters natively through `cue.filters` objects. The queryer enforces a strict, structured, MongoDB-style operator syntax to map incoming JSON request constraints directly to their originating structural table columns. Filters support both flat path notation (e.g., `"contacts/is_primary": {...}`) and deeply nested recursive JSON structures (e.g., `{"contacts": {"is_primary": {...}}}`). The queryer recursively traverses and flattens these structures at AST compilation time. * **Dynamic Filtering**: Binds parameters natively through `cue.filters` objects. The queryer enforces a strict, structured, MongoDB-style operator syntax to map incoming JSON request constraints directly to their originating structural table columns. Filters support both flat path notation (e.g., `"contacts/is_primary": {...}`) and deeply nested recursive JSON structures (e.g., `{"contacts": {"is_primary": {...}}}`). The queryer recursively traverses and flattens these structures at AST compilation time.
* **Equality / Inequality**: `{"$eq": value}`, `{"$ne": value}` automatically map to `=` and `!=`. * **Equality / Inequality**: `{"$eq": value}`, `{"$ne": value}` automatically map to `=` and `!=`.
* **Comparison**: `{"$gt": ...}`, `{"$gte": ...}`, `{"$lt": ...}`, `{"$lte": ...}` directly compile to Postgres comparison operators (`> `, `>=`, `<`, `<=`). * **Comparison**: `{"$gt": ...}`, `{"$gte": ...}`, `{"$lt": ...}`, `{"$lte": ...}` directly compile to Postgres comparison operators (`> `, `>=`, `<`, `<=`).
* **Array Inclusion**: `{"$in": [values]}`, `{"$nin": [values]}` use native `jsonb_array_elements_text()` bindings to enforce `IN` and `NOT IN` logic without runtime SQL injection risks. * **Array Inclusion**: `{"$of": [values]}`, `{"$nof": [values]}` use native `jsonb_array_elements_text()` bindings to enforce `IN` and `NOT IN` logic without runtime SQL injection risks.
* **Text Matching (ILIKE)**: Evaluates `$eq` or `$ne` against string fields containing the `%` character natively into Postgres `ILIKE` and `NOT ILIKE` partial substring matches. * **Text Matching (ILIKE)**: Evaluates `$eq` or `$ne` against string fields containing the `%` character natively into Postgres `ILIKE` and `NOT ILIKE` partial substring matches.
* **Type Casting**: Safely resolves dynamic combinations by casting values instantly into the physical database types mapped in the schema (e.g. parsing `uuid` bindings to `::uuid`, formatting DateTimes to `::timestamptz`, and numbers to `::numeric`). * **Type Casting**: Safely resolves dynamic combinations by casting values instantly into the physical database types mapped in the schema (e.g. parsing `uuid` bindings to `::uuid`, formatting DateTimes to `::timestamptz`, and numbers to `::numeric`).
* **Polymorphic SQL Generation (`family`)**: Compiles `family` properties by analyzing the **Physical Database Variations**, *not* the schema descendants. * **Polymorphic SQL Generation (`family`)**: Compiles `family` properties by analyzing the **Physical Database Variations**, *not* the schema descendants.

View File

@ -1199,10 +1199,10 @@
"id": { "id": {
"$eq": "123e4567-e89b-12d3-a456-426614174000", "$eq": "123e4567-e89b-12d3-a456-426614174000",
"$ne": "123e4567-e89b-12d3-a456-426614174001", "$ne": "123e4567-e89b-12d3-a456-426614174001",
"$in": [ "$of": [
"123e4567-e89b-12d3-a456-426614174000" "123e4567-e89b-12d3-a456-426614174000"
], ],
"$nin": [ "$nof": [
"123e4567-e89b-12d3-a456-426614174001" "123e4567-e89b-12d3-a456-426614174001"
] ]
}, },
@ -1241,9 +1241,9 @@
" AND entity_1.created_at <= ($7#>>'{}')::timestamptz", " AND entity_1.created_at <= ($7#>>'{}')::timestamptz",
" AND entity_1.created_at != ($8#>>'{}')::timestamptz", " AND entity_1.created_at != ($8#>>'{}')::timestamptz",
" AND entity_1.id = ($9#>>'{}')::uuid", " AND entity_1.id = ($9#>>'{}')::uuid",
" AND entity_1.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($10#>>'{}')::jsonb))", " AND entity_1.id != ($10#>>'{}')::uuid",
" AND entity_1.id != ($11#>>'{}')::uuid", " AND entity_1.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($11#>>'{}')::jsonb))",
" AND entity_1.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($12#>>'{}')::jsonb))", " AND entity_1.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($12#>>'{}')::jsonb))",
")))" ")))"
] ]
] ]
@ -1448,14 +1448,14 @@
"$eq": 30, "$eq": 30,
"$gt": 20, "$gt": 20,
"$gte": 20, "$gte": 20,
"$in": [ "$of": [
30, 30,
40 40
], ],
"$lt": 50, "$lt": 50,
"$lte": 50, "$lte": 50,
"$ne": 25, "$ne": 25,
"$nin": [ "$nof": [
1, 1,
2 2
] ]
@ -1481,24 +1481,24 @@
"$eq": "Jane%", "$eq": "Jane%",
"$gt": "A", "$gt": "A",
"$gte": "A", "$gte": "A",
"$in": [ "$of": [
"Jane", "Jane",
"John" "John"
], ],
"$lt": "Z", "$lt": "Z",
"$lte": "Z", "$lte": "Z",
"$ne": "Doe", "$ne": "Doe",
"$nin": [ "$nof": [
"Bob" "Bob"
] ]
}, },
"id": { "id": {
"$eq": "00000000-0000-0000-0000-000000000001", "$eq": "00000000-0000-0000-0000-000000000001",
"$in": [ "$of": [
"00000000-0000-0000-0000-000000000001" "00000000-0000-0000-0000-000000000001"
], ],
"$ne": "00000000-0000-0000-0000-000000000002", "$ne": "00000000-0000-0000-0000-000000000002",
"$nin": [ "$nof": [
"00000000-0000-0000-0000-000000000002" "00000000-0000-0000-0000-000000000002"
] ]
}, },
@ -1677,11 +1677,11 @@
" AND person_1.age = ($1#>>'{}')::numeric", " AND person_1.age = ($1#>>'{}')::numeric",
" AND person_1.age > ($2#>>'{}')::numeric", " AND person_1.age > ($2#>>'{}')::numeric",
" AND person_1.age >= ($3#>>'{}')::numeric", " AND person_1.age >= ($3#>>'{}')::numeric",
" AND person_1.age IN (SELECT value::numeric FROM jsonb_array_elements_text(($4#>>'{}')::jsonb))", " AND person_1.age < ($4#>>'{}')::numeric",
" AND person_1.age < ($5#>>'{}')::numeric", " AND person_1.age <= ($5#>>'{}')::numeric",
" AND person_1.age <= ($6#>>'{}')::numeric", " AND person_1.age != ($6#>>'{}')::numeric",
" AND person_1.age != ($7#>>'{}')::numeric", " AND person_1.age NOT IN (SELECT value::numeric FROM jsonb_array_elements_text(($7#>>'{}')::jsonb))",
" AND person_1.age NOT IN (SELECT value::numeric FROM jsonb_array_elements_text(($8#>>'{}')::jsonb))", " AND person_1.age IN (SELECT value::numeric FROM jsonb_array_elements_text(($8#>>'{}')::jsonb))",
" AND entity_3.archived = ($9#>>'{}')::boolean", " AND entity_3.archived = ($9#>>'{}')::boolean",
" AND entity_3.archived != ($10#>>'{}')::boolean", " AND entity_3.archived != ($10#>>'{}')::boolean",
" AND entity_3.created_at = ($12#>>'{}')::timestamptz", " AND entity_3.created_at = ($12#>>'{}')::timestamptz",
@ -1693,15 +1693,15 @@
" AND person_1.first_name ILIKE $18#>>'{}'", " AND person_1.first_name ILIKE $18#>>'{}'",
" AND person_1.first_name > ($19#>>'{}')", " AND person_1.first_name > ($19#>>'{}')",
" AND person_1.first_name >= ($20#>>'{}')", " AND person_1.first_name >= ($20#>>'{}')",
" AND person_1.first_name IN (SELECT value FROM jsonb_array_elements_text(($21#>>'{}')::jsonb))", " AND person_1.first_name < ($21#>>'{}')",
" AND person_1.first_name < ($22#>>'{}')", " AND person_1.first_name <= ($22#>>'{}')",
" AND person_1.first_name <= ($23#>>'{}')", " AND person_1.first_name NOT ILIKE $23#>>'{}'",
" AND person_1.first_name NOT ILIKE $24#>>'{}'", " AND person_1.first_name NOT IN (SELECT value FROM jsonb_array_elements_text(($24#>>'{}')::jsonb))",
" AND person_1.first_name NOT IN (SELECT value FROM jsonb_array_elements_text(($25#>>'{}')::jsonb))", " AND person_1.first_name IN (SELECT value FROM jsonb_array_elements_text(($25#>>'{}')::jsonb))",
" AND entity_3.id = ($26#>>'{}')::uuid", " AND entity_3.id = ($26#>>'{}')::uuid",
" AND entity_3.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($27#>>'{}')::jsonb))", " AND entity_3.id != ($27#>>'{}')::uuid",
" AND entity_3.id != ($28#>>'{}')::uuid", " AND entity_3.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($28#>>'{}')::jsonb))",
" AND entity_3.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($29#>>'{}')::jsonb))", " AND entity_3.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($29#>>'{}')::jsonb))",
" AND person_1.last_name ILIKE $30#>>'{}'", " AND person_1.last_name ILIKE $30#>>'{}'",
" AND person_1.last_name NOT ILIKE $31#>>'{}')))" " AND person_1.last_name NOT ILIKE $31#>>'{}')))"
] ]

View File

@ -29,12 +29,15 @@ use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use r#type::Type; use r#type::Type;
#[derive(serde::Serialize)]
pub struct Database { pub struct Database {
pub enums: HashMap<String, Enum>, pub enums: HashMap<String, Enum>,
pub types: HashMap<String, Type>, pub types: HashMap<String, Type>,
pub puncs: HashMap<String, Punc>, pub puncs: HashMap<String, Punc>,
pub relations: HashMap<String, Relation>, pub relations: HashMap<String, Relation>,
#[serde(skip)]
pub schemas: HashMap<String, Arc<Schema>>, pub schemas: HashMap<String, Arc<Schema>>,
#[serde(skip)]
pub executor: Box<dyn DatabaseExecutor + Send + Sync>, pub executor: Box<dyn DatabaseExecutor + Send + Sync>,
} }
@ -238,23 +241,30 @@ impl Database {
// Phase 2: Synthesize Composed Filter References // Phase 2: Synthesize Composed Filter References
let mut filter_schemas = Vec::new(); let mut filter_schemas = Vec::new();
for type_def in self.types.values() { for (type_name, type_def) in &self.types {
for (id, schema_arc) in &type_def.schemas { for (id, schema_arc) in &type_def.schemas {
// Only run synthesis on actual structured, table-backed boundaries. Exclude subschemas! // Only run synthesis on actual structured, table-backed boundaries. Exclude subschemas!
let base_name = id.split('.').last().unwrap_or(id); let base_name = id.split('.').last().unwrap_or(id);
let is_table_backed = base_name == type_def.name; let is_table_backed = base_name == type_def.name;
if is_table_backed && !id.contains('/') { if is_table_backed && !id.contains('/') {
if let Some(filter_schema) = schema_arc.compile_filter(self, id, errors) { if let Some(filter_schema) = schema_arc.compile_filter(self, id, errors) {
filter_schemas.push((format!("{}.filter", id), Arc::new(filter_schema))); filter_schemas.push((
type_name.clone(),
format!("{}.filter", id),
Arc::new(filter_schema),
));
} }
} }
} }
} }
let mut filter_ids = Vec::new(); let mut filter_ids = Vec::new();
for (id, filter_arc) in filter_schemas { for (type_name, id, filter_arc) in filter_schemas {
filter_ids.push(id.clone()); filter_ids.push(id.clone());
self.schemas.insert(id, filter_arc); self.schemas.insert(id.clone(), filter_arc.clone());
if let Some(t) = self.types.get_mut(&type_name) {
t.schemas.insert(id, filter_arc);
}
} }
// Now actively compile the newly injected filters to lock all nested compose references natively // Now actively compile the newly injected filters to lock all nested compose references natively
@ -269,50 +279,88 @@ impl Database {
} }
fn collect_schemas(&mut self, errors: &mut Vec<crate::drop::Error>) { fn collect_schemas(&mut self, errors: &mut Vec<crate::drop::Error>) {
let mut to_insert = Vec::new(); let mut type_insert = Vec::new();
let mut punc_insert = Vec::new();
let mut enum_insert = Vec::new();
let mut global_insert = Vec::new();
// Pass 1: Extract all Schemas structurally off top level definitions into the master registry. // Pass 1: Extract all Schemas structurally off top level definitions into the master registry.
// Validate every node recursively via string filters natively! // Validate every node recursively via string filters natively!
for type_def in self.types.values() { for (type_name, type_def) in &self.types {
for (id, schema_arc) in &type_def.schemas { for (id, schema_arc) in &type_def.schemas {
to_insert.push((id.clone(), Arc::clone(schema_arc))); global_insert.push((id.clone(), Arc::clone(schema_arc)));
let mut local_insert = Vec::new();
crate::database::schema::Schema::collect_schemas( crate::database::schema::Schema::collect_schemas(
schema_arc, schema_arc,
id, id,
id.clone(), id.clone(),
&mut to_insert, &mut local_insert,
errors,
);
}
}
for punc_def in self.puncs.values() {
for (id, schema_arc) in &punc_def.schemas {
to_insert.push((id.clone(), Arc::clone(schema_arc)));
crate::database::schema::Schema::collect_schemas(
schema_arc,
id,
id.clone(),
&mut to_insert,
errors,
);
}
}
for enum_def in self.enums.values() {
for (id, schema_arc) in &enum_def.schemas {
to_insert.push((id.clone(), Arc::clone(schema_arc)));
crate::database::schema::Schema::collect_schemas(
schema_arc,
id,
id.clone(),
&mut to_insert,
errors, errors,
); );
for entry in &local_insert {
type_insert.push((type_name.clone(), entry.0.clone(), Arc::clone(&entry.1)));
global_insert.push((entry.0.clone(), Arc::clone(&entry.1)));
}
} }
} }
for (id, schema_arc) in to_insert { for (punc_name, punc_def) in &self.puncs {
for (id, schema_arc) in &punc_def.schemas {
global_insert.push((id.clone(), Arc::clone(schema_arc)));
let mut local_insert = Vec::new();
crate::database::schema::Schema::collect_schemas(
schema_arc,
id,
id.clone(),
&mut local_insert,
errors,
);
for entry in &local_insert {
punc_insert.push((punc_name.clone(), entry.0.clone(), Arc::clone(&entry.1)));
global_insert.push((entry.0.clone(), Arc::clone(&entry.1)));
}
}
}
for (enum_name, enum_def) in &self.enums {
for (id, schema_arc) in &enum_def.schemas {
global_insert.push((id.clone(), Arc::clone(schema_arc)));
let mut local_insert = Vec::new();
crate::database::schema::Schema::collect_schemas(
schema_arc,
id,
id.clone(),
&mut local_insert,
errors,
);
for entry in &local_insert {
enum_insert.push((enum_name.clone(), entry.0.clone(), Arc::clone(&entry.1)));
global_insert.push((entry.0.clone(), Arc::clone(&entry.1)));
}
}
}
// Apply global inserts
for (id, schema_arc) in global_insert {
self.schemas.insert(id, schema_arc); self.schemas.insert(id, schema_arc);
} }
// Apply local scopes
for (origin_name, id, schema_arc) in type_insert {
if let Some(t) = self.types.get_mut(&origin_name) {
t.schemas.insert(id, schema_arc);
}
}
for (origin_name, id, schema_arc) in punc_insert {
if let Some(p) = self.puncs.get_mut(&origin_name) {
p.schemas.insert(id, schema_arc);
}
}
for (origin_name, id, schema_arc) in enum_insert {
if let Some(e) = self.enums.get_mut(&origin_name) {
e.schemas.insert(id, schema_arc);
}
}
} }
/// Inspects the Postgres pg_constraint relations catalog to securely identify /// Inspects the Postgres pg_constraint relations catalog to securely identify

View File

@ -109,7 +109,7 @@ pub fn jspg_validate(schema_id: &str, instance: JsonB) -> JsonB {
} }
#[cfg_attr(not(test), pg_extern)] #[cfg_attr(not(test), pg_extern)]
pub fn jspg_schemas() -> JsonB { pub fn jspg_database() -> JsonB {
let engine_opt = { let engine_opt = {
let lock = GLOBAL_JSPG.read().unwrap(); let lock = GLOBAL_JSPG.read().unwrap();
lock.clone() lock.clone()
@ -117,9 +117,9 @@ pub fn jspg_schemas() -> JsonB {
match engine_opt { match engine_opt {
Some(engine) => { Some(engine) => {
let schemas_json = serde_json::to_value(&engine.database.schemas) let database_json = serde_json::to_value(&engine.database)
.unwrap_or(serde_json::Value::Object(serde_json::Map::new())); .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
let drop = crate::drop::Drop::success_with_val(schemas_json); let drop = crate::drop::Drop::success_with_val(database_json);
JsonB(serde_json::to_value(drop).unwrap()) JsonB(serde_json::to_value(drop).unwrap())
} }
None => jspg_failure(), None => jspg_failure(),

View File

@ -717,8 +717,8 @@ impl<'a> Compiler<'a> {
let param_index = i + 1; let param_index = i + 1;
let p_val = format!("${}#>>'{{}}'", param_index); let p_val = format!("${}#>>'{{}}'", param_index);
if op == "$in" || op == "$nin" { if op == "$of" || op == "$nof" {
let sql_op = if op == "$in" { "IN" } else { "NOT IN" }; let sql_op = if op == "$of" { "IN" } else { "NOT IN" };
let subquery = format!( let subquery = format!(
"(SELECT value{} FROM jsonb_array_elements_text(({})::jsonb))", "(SELECT value{} FROM jsonb_array_elements_text(({})::jsonb))",
cast, p_val cast, p_val

View File

@ -81,54 +81,114 @@ fn test_library_api() {
}) })
); );
// 3. Validate jspg_schemas // 3. Validate jspg_database mapping natively!
let schemas_drop = jspg_schemas(); let db_drop = jspg_database();
assert_eq!( assert_eq!(
schemas_drop.0, db_drop.0,
json!({ json!({
"type": "drop", "type": "drop",
"response": { "response": {
"source_schema": { "enums": {},
"type": "object", "puncs": {},
"properties": { "relations": {
"type": { "type": "string" }, "fk_test_target": {
"name": { "type": "string" }, "constraint": "fk_test_target",
"target": { "destination_columns": ["id"],
"type": "target_schema", "destination_type": "target_schema",
"compiledPropertyNames": ["value"] "prefix": "target",
} "source_columns": ["target_id"],
}, "source_type": "source_schema"
"required": ["name"],
"compiledPropertyNames": ["name", "target", "type"],
"compiledEdges": {
"target": {
"constraint": "fk_test_target",
"forward": true
}
} }
}, },
"source_schema.filter": { "types": {
"type": "object", "source_schema": {
"properties": { "default_fields": [],
"type": { "type": ["string.condition", "null"] }, "field_types": null,
"name": { "type": ["string.condition", "null"] }, "fields": [],
"target": { "type": ["target_schema.filter", "null"] } "grouped_fields": null,
"hierarchy": ["source_schema", "entity"],
"historical": false,
"id": "",
"longevity": null,
"lookup_fields": [],
"module": "",
"name": "source_schema",
"notify": false,
"null_fields": [],
"ownable": false,
"relationship": false,
"schemas": {
"source_schema": {
"compiledEdges": {
"target": {
"constraint": "fk_test_target",
"forward": true
}
},
"compiledPropertyNames": ["name", "target", "type"],
"properties": {
"name": { "type": "string" },
"target": {
"compiledPropertyNames": ["value"],
"type": "target_schema"
},
"type": { "type": "string" }
},
"required": ["name"],
"type": "object"
},
"source_schema.filter": {
"compiledPropertyNames": ["name", "target", "type"],
"properties": {
"name": { "type": ["string.condition", "null"] },
"target": { "type": ["target_schema.filter", "null"] },
"type": { "type": ["string.condition", "null"] }
},
"type": "object"
}
},
"sensitive": false,
"source": "",
"type": "",
"variations": ["source_schema"]
}, },
"compiledPropertyNames": ["name", "target", "type"] "target_schema": {
}, "default_fields": [],
"target_schema": { "field_types": null,
"type": "object", "fields": [],
"properties": { "grouped_fields": null,
"value": { "type": "number" } "hierarchy": ["target_schema", "entity"],
}, "historical": false,
"compiledPropertyNames": ["value"] "id": "",
}, "longevity": null,
"target_schema.filter": { "lookup_fields": [],
"type": "object", "module": "",
"properties": { "name": "target_schema",
"value": { "type": ["number.condition", "null"] } "notify": false,
}, "null_fields": [],
"compiledPropertyNames": ["value"] "ownable": false,
"relationship": false,
"schemas": {
"target_schema": {
"compiledPropertyNames": ["value"],
"properties": {
"value": { "type": "number" }
},
"type": "object"
},
"target_schema.filter": {
"compiledPropertyNames": ["value"],
"properties": {
"value": { "type": ["number.condition", "null"] }
},
"type": "object"
}
},
"sensitive": false,
"source": "",
"type": "",
"variations": ["target_schema"]
}
} }
} }
}) })

View File

@ -1 +1 @@
1.0.122 1.0.124