diff --git a/Cargo.lock b/Cargo.lock index 6b676ae..a25003e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1663,6 +1663,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ + "indexmap", "itoa", "memchr", "serde", diff --git a/Cargo.toml b/Cargo.toml index 5d4e917..ca1e480 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] pgrx = "0.16.1" serde = { version = "1.0.228", features = ["derive", "rc"] } -serde_json = "1.0.149" +serde_json = { version = "1.0.149", features = ["preserve_order"] } lazy_static = "1.5.0" once_cell = "1.21.3" ahash = "0.8.12" @@ -30,7 +30,7 @@ pgrx-tests = "0.16.1" [build-dependencies] serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.149" +serde_json = { version = "1.0.149", features = ["preserve_order"] } [lib] crate-type = ["cdylib", "lib"] diff --git a/fixtures/filter.json b/fixtures/filter.json index 5ba6d87..0cafcc0 100644 --- a/fixtures/filter.json +++ b/fixtures/filter.json @@ -197,11 +197,11 @@ "gender.condition": { "type": "condition", "compiledPropertyNames": [ + "kind", "$eq", "$ne", - "$nof", "$of", - "kind" + "$nof" ], "properties": { "$eq": { @@ -239,29 +239,29 @@ "person": {}, "person.filter": { "compiledPropertyNames": [ - "$and", - "$or", - "ad_hoc", + "first_name", "age", "billing_address", - "birth_date", - "first_name", "gender", - "tags" + "birth_date", + "tags", + "ad_hoc", + "$and", + "$or" ], "properties": { "$and": { "items": { "compiledPropertyNames": [ - "$and", - "$or", - "ad_hoc", + "first_name", "age", "billing_address", - "birth_date", - "first_name", "gender", - "tags" + "birth_date", + "tags", + "ad_hoc", + "$and", + "$or" ], "type": "person.filter" }, @@ -273,15 +273,15 @@ "$or": { "items": { "compiledPropertyNames": [ - "$and", - "$or", - "ad_hoc", + "first_name", "age", "billing_address", - "birth_date", - "first_name", "gender", - "tags" + "birth_date", + "tags", + "ad_hoc", + "$and", + "$or" ], "type": "person.filter" }, @@ -350,9 +350,9 @@ "address.filter": { "type": "filter", "compiledPropertyNames": [ + "city", "$and", - "$or", - "city" + "$or" ], "properties": { "$and": { @@ -362,9 +362,9 @@ ], "items": { "compiledPropertyNames": [ + "city", "$and", - "$or", - "city" + "$or" ], "type": "address.filter" } @@ -376,9 +376,9 @@ ], "items": { "compiledPropertyNames": [ + "city", "$and", - "$or", - "city" + "$or" ], "type": "address.filter" } @@ -400,11 +400,11 @@ "search.filter": { "type": "filter", "compiledPropertyNames": [ - "$and", - "$or", - "filter", "kind", - "name" + "name", + "filter", + "$and", + "$or" ], "properties": { "$and": { @@ -414,11 +414,11 @@ ], "items": { "compiledPropertyNames": [ - "$and", - "$or", - "filter", "kind", - "name" + "name", + "filter", + "$and", + "$or" ], "type": "search.filter" } @@ -430,11 +430,11 @@ ], "items": { "compiledPropertyNames": [ - "$and", - "$or", - "filter", "kind", - "name" + "name", + "filter", + "$and", + "$or" ], "type": "search.filter" } diff --git a/fixtures/queryer.json b/fixtures/queryer.json index 3571efa..9aa4507 100644 --- a/fixtures/queryer.json +++ b/fixtures/queryer.json @@ -1258,14 +1258,14 @@ "sql": [ [ "(SELECT jsonb_strip_nulls((SELECT jsonb_build_object(", - " 'age', person_1.age,", " 'archived', entity_3.archived,", " 'created_at', entity_3.created_at,", - " 'first_name', person_1.first_name,", " 'id', entity_3.id,", - " 'last_name', person_1.last_name,", + " 'type', entity_3.type,", " 'name', organization_2.name,", - " 'type', entity_3.type)", + " 'age', person_1.age,", + " 'first_name', person_1.first_name,", + " 'last_name', person_1.last_name)", "FROM agreego.person person_1", "JOIN agreego.organization organization_2 ON organization_2.id = person_1.id", "JOIN agreego.entity entity_3 ON entity_3.id = organization_2.id", diff --git a/src/database/compile/condition.rs b/src/database/compile/condition.rs index e89e3d3..42efd31 100644 --- a/src/database/compile/condition.rs +++ b/src/database/compile/condition.rs @@ -1,12 +1,12 @@ use crate::database::object::{SchemaObject, SchemaTypeOrArray}; use crate::database::schema::Schema; use crate::database::r#enum::Enum; -use std::collections::BTreeMap; +use indexmap::IndexMap; use std::sync::Arc; impl Enum { pub fn compile_condition(&self) -> Schema { - let mut props = BTreeMap::new(); + let mut props = IndexMap::new(); let enum_name = &self.name; let mut eq_obj = SchemaObject::default(); diff --git a/src/database/compile/edges.rs b/src/database/compile/edges.rs index 9356880..11073ac 100644 --- a/src/database/compile/edges.rs +++ b/src/database/compile/edges.rs @@ -1,4 +1,5 @@ use crate::database::schema::Schema; +use indexmap::IndexMap; impl Schema { /// Dynamically infers and compiles all structural database relationships between this Schema @@ -10,10 +11,10 @@ impl Schema { db: &crate::database::Database, root_id: &str, path: &str, - props: &std::collections::BTreeMap>, + props: &IndexMap>, errors: &mut Vec, - ) -> std::collections::BTreeMap { - let mut schema_edges = std::collections::BTreeMap::new(); + ) -> IndexMap { + let mut schema_edges = IndexMap::new(); // Determine the physical Database Table Name this schema structurally represents // Plucks the polymorphic discriminator via dot-notation (e.g. extracting "person" from "full.person") diff --git a/src/database/compile/filter.rs b/src/database/compile/filter.rs index 700e128..d889c0f 100644 --- a/src/database/compile/filter.rs +++ b/src/database/compile/filter.rs @@ -1,7 +1,7 @@ use crate::database::Database; use crate::database::object::{SchemaObject, SchemaTypeOrArray}; use crate::database::schema::Schema; -use std::collections::BTreeMap; +use indexmap::IndexMap; use std::sync::Arc; impl Schema { @@ -12,7 +12,7 @@ impl Schema { _errors: &mut Vec, ) -> Option { if let Some(props) = self.obj.compiled_properties.get() { - let mut filter_props = BTreeMap::new(); + let mut filter_props = IndexMap::new(); for (key, child) in props { let mut structural_filter = None; diff --git a/src/database/compile/mod.rs b/src/database/compile/mod.rs index dbfac02..c5c3ef9 100644 --- a/src/database/compile/mod.rs +++ b/src/database/compile/mod.rs @@ -5,6 +5,7 @@ pub mod filter; pub mod polymorphism; use crate::database::schema::Schema; +use indexmap::IndexMap; impl Schema { pub fn compile( @@ -48,7 +49,7 @@ impl Schema { } } - let mut props = std::collections::BTreeMap::new(); + let mut props = IndexMap::new(); // 1. Resolve INHERITANCE dependencies first if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ { @@ -124,8 +125,7 @@ impl Schema { // 4. Set the OnceLock! let _ = self.obj.compiled_properties.set(props.clone()); - let mut names: Vec = props.keys().cloned().collect(); - names.sort(); + let names: Vec = props.keys().cloned().collect(); let _ = self.obj.compiled_property_names.set(names); // 5. Compute Edges natively diff --git a/src/database/compile/polymorphism.rs b/src/database/compile/polymorphism.rs index 4b0153e..df0ca9c 100644 --- a/src/database/compile/polymorphism.rs +++ b/src/database/compile/polymorphism.rs @@ -8,7 +8,7 @@ impl Schema { path: &str, errors: &mut Vec, ) { - let mut options = std::collections::BTreeMap::new(); + let mut options = indexmap::IndexMap::new(); let strategy: &str; if let Some(family) = &self.obj.family { diff --git a/src/database/mod.rs b/src/database/mod.rs index 50bccfc..2e2582e 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -23,18 +23,18 @@ use punc::Punc; use relation::Relation; use schema::Schema; use serde_json::Value; -use std::collections::HashMap; +use indexmap::IndexMap; use std::sync::Arc; use r#type::Type; #[derive(serde::Serialize)] pub struct Database { - pub enums: HashMap, - pub types: HashMap, - pub puncs: HashMap, - pub relations: HashMap, + pub enums: IndexMap, + pub types: IndexMap, + pub puncs: IndexMap, + pub relations: IndexMap, #[serde(skip)] - pub schemas: HashMap>, + pub schemas: IndexMap>, #[serde(skip)] pub executor: Box, } @@ -42,11 +42,11 @@ pub struct Database { impl Database { pub fn new(val: &serde_json::Value) -> (Self, crate::drop::Drop) { let mut db = Self { - enums: HashMap::new(), - types: HashMap::new(), - relations: HashMap::new(), - puncs: HashMap::new(), - schemas: HashMap::new(), + enums: IndexMap::new(), + types: IndexMap::new(), + relations: IndexMap::new(), + puncs: IndexMap::new(), + schemas: IndexMap::new(), #[cfg(not(test))] executor: Box::new(SpiExecutor::new()), #[cfg(test)] diff --git a/src/database/object.rs b/src/database/object.rs index 0af64f1..2d289f9 100644 --- a/src/database/object.rs +++ b/src/database/object.rs @@ -1,7 +1,7 @@ use crate::database::schema::Schema; use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::collections::BTreeMap; +use indexmap::IndexMap; use std::sync::Arc; use std::sync::OnceLock; @@ -30,10 +30,10 @@ pub struct SchemaObject { // Object Keywords #[serde(skip_serializing_if = "Option::is_none")] - pub properties: Option>>, + pub properties: Option>>, #[serde(rename = "patternProperties")] #[serde(skip_serializing_if = "Option::is_none")] - pub pattern_properties: Option>>, + pub pattern_properties: Option>>, #[serde(rename = "additionalProperties")] #[serde(skip_serializing_if = "Option::is_none")] pub additional_properties: Option>, @@ -46,7 +46,7 @@ pub struct SchemaObject { // dependencies can be schema dependencies or property dependencies #[serde(skip_serializing_if = "Option::is_none")] - pub dependencies: Option>, + pub dependencies: Option>, // Array Keywords #[serde(rename = "items")] @@ -147,7 +147,7 @@ pub struct SchemaObject { #[serde(skip_serializing_if = "Option::is_none")] pub control: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub actions: Option>, + pub actions: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub computer: Option, #[serde(default)] @@ -164,7 +164,7 @@ pub struct SchemaObject { // Internal structural representation caching active AST Node maps. Unlike the Go framework counterpart, the JSPG implementation DOES natively include ALL ancestral inheritance boundary schemas because it compiles locally against the raw database graph. #[serde(skip)] - pub compiled_properties: OnceLock>>, + pub compiled_properties: OnceLock>>, #[serde(rename = "compiledDiscriminator")] #[serde(skip_deserializing)] @@ -176,13 +176,13 @@ pub struct SchemaObject { #[serde(skip_deserializing)] #[serde(skip_serializing_if = "crate::database::object::is_once_lock_map_empty")] #[serde(serialize_with = "crate::database::object::serialize_once_lock")] - pub compiled_options: OnceLock, Option)>>, + pub compiled_options: OnceLock, Option)>>, #[serde(rename = "compiledEdges")] #[serde(skip_deserializing)] #[serde(skip_serializing_if = "crate::database::object::is_once_lock_map_empty")] #[serde(serialize_with = "crate::database::object::serialize_once_lock")] - pub compiled_edges: OnceLock>, + pub compiled_edges: OnceLock>, #[serde(skip)] pub compiled_format: OnceLock, @@ -245,7 +245,7 @@ pub fn serialize_once_lock( } } -pub fn is_once_lock_map_empty(lock: &OnceLock>) -> bool { +pub fn is_once_lock_map_empty(lock: &OnceLock>) -> bool { lock.get().map_or(true, |m| m.is_empty()) } diff --git a/src/queryer/compiler.rs b/src/queryer/compiler.rs index 535be8c..17eec70 100644 --- a/src/queryer/compiler.rs +++ b/src/queryer/compiler.rs @@ -1,5 +1,6 @@ use crate::database::Database; use std::sync::Arc; +use indexmap::IndexMap; pub struct Compiler<'a> { pub db: &'a Database, @@ -256,7 +257,7 @@ impl<'a> Compiler<'a> { fn compile_object( &mut self, - props: &std::collections::BTreeMap>, + props: &IndexMap>, node: Node<'a>, ) -> Result<(String, String), String> { let mut build_args = Vec::new(); @@ -417,7 +418,7 @@ impl<'a> Compiler<'a> { ) -> Result, String> { let mut select_args = Vec::new(); let grouped_fields = r#type.grouped_fields.as_ref().and_then(|v| v.as_object()); - let default_props = std::collections::BTreeMap::new(); + let default_props = IndexMap::new(); let merged_props = node .schema .obj diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 6d85e4b..268b2a7 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -127,7 +127,7 @@ fn test_library_api() { "forward": true } }, - "compiledPropertyNames": ["name", "target", "type"], + "compiledPropertyNames": ["type", "name", "target"], "properties": { "name": { "type": "string" }, "target": { @@ -140,19 +140,19 @@ fn test_library_api() { "type": "object" }, "source_schema.filter": { - "compiledPropertyNames": ["$and", "$or", "name", "target", "type"], + "compiledPropertyNames": ["type", "name", "target", "$and", "$or"], "properties": { "$and": { "type": ["array", "null"], "items": { - "compiledPropertyNames": ["$and", "$or", "name", "target", "type"], + "compiledPropertyNames": ["type", "name", "target", "$and", "$or"], "type": "source_schema.filter" } }, "$or": { "type": ["array", "null"], "items": { - "compiledPropertyNames": ["$and", "$or", "name", "target", "type"], + "compiledPropertyNames": ["type", "name", "target", "$and", "$or"], "type": "source_schema.filter" } }, @@ -193,19 +193,19 @@ fn test_library_api() { "type": "object" }, "target_schema.filter": { - "compiledPropertyNames": ["$and", "$or", "value"], + "compiledPropertyNames": ["value", "$and", "$or"], "properties": { "$and": { "type": ["array", "null"], "items": { - "compiledPropertyNames": ["$and", "$or", "value"], + "compiledPropertyNames": ["value", "$and", "$or"], "type": "target_schema.filter" } }, "$or": { "type": ["array", "null"], "items": { - "compiledPropertyNames": ["$and", "$or", "value"], + "compiledPropertyNames": ["value", "$and", "$or"], "type": "target_schema.filter" } }, diff --git a/src/validator/rules/polymorphism.rs b/src/validator/rules/polymorphism.rs index d45dc69..511ed33 100644 --- a/src/validator/rules/polymorphism.rs +++ b/src/validator/rules/polymorphism.rs @@ -1,6 +1,7 @@ use crate::validator::context::ValidationContext; use crate::validator::error::ValidationError; use crate::validator::result::ValidationResult; +use indexmap::IndexMap; impl<'a> ValidationContext<'a> { pub(crate) fn validate_family( @@ -65,7 +66,7 @@ impl<'a> ValidationContext<'a> { pub(crate) fn execute_polymorph( &self, - options: &std::collections::BTreeMap, Option)>, + options: &IndexMap, Option)>, result: &mut ValidationResult, ) -> Result { // 1. O(1) Fast-Path Router & Extractor