diff --git a/GEMINI.md b/GEMINI.md index 7dd7dd5..3a06a30 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -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. * **Equality / Inequality**: `{"$eq": value}`, `{"$ne": value}` automatically map to `=` and `!=`. * **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. * **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. diff --git a/fixtures/queryer.json b/fixtures/queryer.json index d6eff57..997f2d0 100644 --- a/fixtures/queryer.json +++ b/fixtures/queryer.json @@ -1199,10 +1199,10 @@ "id": { "$eq": "123e4567-e89b-12d3-a456-426614174000", "$ne": "123e4567-e89b-12d3-a456-426614174001", - "$in": [ + "$of": [ "123e4567-e89b-12d3-a456-426614174000" ], - "$nin": [ + "$nof": [ "123e4567-e89b-12d3-a456-426614174001" ] }, @@ -1241,9 +1241,9 @@ " AND entity_1.created_at <= ($7#>>'{}')::timestamptz", " AND entity_1.created_at != ($8#>>'{}')::timestamptz", " 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 != ($11#>>'{}')::uuid", - " AND entity_1.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($12#>>'{}')::jsonb))", + " AND entity_1.id != ($10#>>'{}')::uuid", + " AND entity_1.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($11#>>'{}')::jsonb))", + " AND entity_1.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($12#>>'{}')::jsonb))", ")))" ] ] @@ -1448,14 +1448,14 @@ "$eq": 30, "$gt": 20, "$gte": 20, - "$in": [ + "$of": [ 30, 40 ], "$lt": 50, "$lte": 50, "$ne": 25, - "$nin": [ + "$nof": [ 1, 2 ] @@ -1481,24 +1481,24 @@ "$eq": "Jane%", "$gt": "A", "$gte": "A", - "$in": [ + "$of": [ "Jane", "John" ], "$lt": "Z", "$lte": "Z", "$ne": "Doe", - "$nin": [ + "$nof": [ "Bob" ] }, "id": { "$eq": "00000000-0000-0000-0000-000000000001", - "$in": [ + "$of": [ "00000000-0000-0000-0000-000000000001" ], "$ne": "00000000-0000-0000-0000-000000000002", - "$nin": [ + "$nof": [ "00000000-0000-0000-0000-000000000002" ] }, @@ -1677,11 +1677,11 @@ " AND person_1.age = ($1#>>'{}')::numeric", " AND person_1.age > ($2#>>'{}')::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 < ($5#>>'{}')::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(($8#>>'{}')::jsonb))", + " AND person_1.age < ($4#>>'{}')::numeric", + " AND person_1.age <= ($5#>>'{}')::numeric", + " AND person_1.age != ($6#>>'{}')::numeric", + " AND person_1.age NOT IN (SELECT value::numeric FROM jsonb_array_elements_text(($7#>>'{}')::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 != ($10#>>'{}')::boolean", " AND entity_3.created_at = ($12#>>'{}')::timestamptz", @@ -1693,15 +1693,15 @@ " AND person_1.first_name ILIKE $18#>>'{}'", " AND person_1.first_name > ($19#>>'{}')", " 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 < ($22#>>'{}')", - " AND person_1.first_name <= ($23#>>'{}')", - " AND person_1.first_name NOT ILIKE $24#>>'{}'", - " AND person_1.first_name NOT IN (SELECT value FROM jsonb_array_elements_text(($25#>>'{}')::jsonb))", + " AND person_1.first_name < ($21#>>'{}')", + " AND person_1.first_name <= ($22#>>'{}')", + " AND person_1.first_name NOT ILIKE $23#>>'{}'", + " AND person_1.first_name NOT IN (SELECT value FROM jsonb_array_elements_text(($24#>>'{}')::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 IN (SELECT value::uuid FROM jsonb_array_elements_text(($27#>>'{}')::jsonb))", - " AND entity_3.id != ($28#>>'{}')::uuid", - " AND entity_3.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($29#>>'{}')::jsonb))", + " AND entity_3.id != ($27#>>'{}')::uuid", + " AND entity_3.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($28#>>'{}')::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 NOT ILIKE $31#>>'{}')))" ] diff --git a/src/queryer/compiler.rs b/src/queryer/compiler.rs index 6bfeb0e..51a157a 100644 --- a/src/queryer/compiler.rs +++ b/src/queryer/compiler.rs @@ -717,8 +717,8 @@ impl<'a> Compiler<'a> { let param_index = i + 1; let p_val = format!("${}#>>'{{}}'", param_index); - if op == "$in" || op == "$nin" { - let sql_op = if op == "$in" { "IN" } else { "NOT IN" }; + if op == "$of" || op == "$nof" { + let sql_op = if op == "$of" { "IN" } else { "NOT IN" }; let subquery = format!( "(SELECT value{} FROM jsonb_array_elements_text(({})::jsonb))", cast, p_val