Compare commits

..

18 Commits

Author SHA1 Message Date
e25890461d version: 1.0.181 2026-07-16 13:43:22 -04:00
c4bf6bde58 added limit 1 to arrays treated as a single value in schemas 2026-07-16 13:43:08 -04:00
1fedf5253c test(queryer): update SQL expectations with LIMIT 1 on cover_attachment 2026-07-15 01:52:13 -04:00
9ef59422b8 version: 1.0.180 2026-07-14 16:03:01 -04:00
b49b216b36 fix(queryer): restrict polymorphic bounds compiler to forward edges only
Resolves a query compilation bug in JSPG where reverse/incoming polymorphic
edges (where edge.forward == false, e.g. pet ➡️ cover_attachment) mistakenly
appended the destination type constraint to the parent query's WHERE clause
(e.g., producing `entity_1.type = 'attachment'` on a pet entity query,
causing it to match 0 rows and return null).

- Modifies `compile_polymorphic_bounds` in `compiler.rs` to only compile
  type checks when `edge.forward` is true. For reverse edges, the parent's
  type check does not belong on the parent table and is already implicitly
  restricted by parent ID joins.
- Updates the `fk_attachment_attachable_entity` relation in the queryer test
  fixture to correctly model a two-column polymorphic relation, ensuring
  this code path is exercised by the unit test suite.
2026-07-14 16:02:41 -04:00
13b28387c2 doc update 2026-07-09 20:47:59 -04:00
5b8596f7bd version: 1.0.179 2026-07-09 18:47:25 -04:00
6220a0964d fixed proxy references without null option not being treated as proxies 2026-07-09 18:47:12 -04:00
6cd5bfea7e doc update 2026-07-09 15:26:17 -04:00
a006d3f00a version: 1.0.178 2026-07-09 14:48:03 -04:00
942520a36a added static property constraints using const and enums and fixed formatter ON rendering 2026-07-09 14:47:39 -04:00
1bb9586b9b Merge branch 'main' of gitea-ssh.thoughtpatterns.ai:cellular/jspg 2026-07-09 14:46:03 -04:00
b4882bcb27 generator checkpoint 2026-07-09 14:27:50 -04:00
3bb7eb312a generator checkpoint 2026-07-09 14:26:45 -04:00
73ab6d4ce7 version: 1.0.177 2026-07-09 12:44:55 -04:00
cef65958cb reversed jsonb subquerying for now 2026-07-07 23:10:43 -04:00
95600551a7 version: 1.0.172 2026-07-07 12:45:00 -04:00
c18a8c1561 remove icon from sidebar 2026-07-07 12:44:49 -04:00
54 changed files with 1126 additions and 686 deletions

View File

@ -229,6 +229,40 @@ Traits are reusable, non-generating schema fragments used to share properties an
* **Scalars / Arrays / Items**: Host definitions completely override included traits.
* The `"include"` keyword is stripped, and `"traits"` maps are omitted from serialization.
### Static Relation Constraints (Kind Constraints)
When modeling relational properties on a schema, a developer can define a specialized subset of a related table by applying static property constraints via the `const` or `enum` validation keywords.
For example, given a general `attachment` table containing a `kind` column (e.g. `'cover'`, `'thumbnail'`, `'document'`), you can define a `cover.attachment` schema that narrows the type using a static `const` assertion:
```json
"cover.attachment": {
"type": "attachment",
"properties": {
"kind": {
"type": "string",
"const": "cover"
}
}
}
```
A parent entity can then define a relationship using this constrained schema under a local property name (e.g. `cover_attachment`):
```json
"cover_attachment": {
"properties": {
"cover_attachment": {
"type": "cover.attachment"
}
}
}
```
**What it does:**
1. **Validation (L1)**: During payload validation (`jspg_validate`), any incoming object mapped to the constrained property is validated against the static rules (e.g. throwing `CONST_VIOLATED` if `kind` is not `"cover"`).
2. **Query Generation (L0)**: When fetching data via `jspg_query`, the Queryer automatically detects the static constraint and compiles it into the SQL subquery's `WHERE` clause (e.g. adding `AND attachment_X.kind = 'cover'`). This produces a pre-filtered view of the related entities natively at the database level.
---
## 3. Database
@ -314,6 +348,8 @@ The Queryer transforms Postgres into a pre-compiled Semantic Query Engine, desig
* **Multi-Table Branching**: If the Physical Table is a parent to other tables (e.g. `organization` has variations `["organization", "bot", "person"]`), the compiler generates a dynamic `CASE WHEN type = '...' THEN ...` query, expanding into sub-queries for each variation. To ensure safe resolution, the compiler dynamically evaluates correlation boundaries: it attempts standard Relational Edge discovery first. If no explicit relational edge exists (indicating pure Table Inheritance rather than a standard foreign-key graph relationship), it safely invokes a **Table Parity Fallback**. This generates an explicit ID correlation constraint (`AND inner.id = outer.id`), perfectly binding the structural variations back to the parent row to eliminate Cartesian products.
* **Single-Table Bypass**: If the Physical Table is a leaf node with only one variation (e.g. `person` has variations `["person"]`), the compiler cleanly bypasses `CASE` generation and compiles a simple `SELECT` across the base table, as all schema extensions (e.g. `light.person`, `full.person`) are guaranteed to reside in the exact same physical row.
* **Polymorphic Relation Type Filtering**: When a relationship maps to a polymorphic target with variations, the Queryer compiles an `IN` clause containing all allowed table variations (e.g., `counterparty_type IN ('bot', 'organization', 'person')`) rather than matching the base type literal, ensuring all polymorphic types are loaded correctly.
* **Static Relation Constraints (Kind Constraints)**: When a relationship (such as a nested object or array) is defined with a schema that constrains a field value statically using a `const` or `enum` keyword (for example, `kind` constrained to `"cover"` in a `cover_attachment`), the Queryer automatically extracts these static assertions during AST compilation. It injects them directly as static filters into the SQL subquery's `WHERE` clause (e.g. `AND attachment.kind = 'cover'`), allowing developers to query pre-filtered subsets of related tables natively through the schema.
* **Proxy Schema Dereferencing / Resolution**: To support punc endpoints that return non-polymorphic table-backed shapes (using `type: "full.X"` proxy schemas at the root response level), the Queryer compiler automatically dereferences non-table schema pointers to their target schemas prior to checking the types. This allows the Queryer to correctly resolve the table relationship edges pre-compiled on the full schema, while avoiding polluting the database registry with relations on ad-hoc punc response schemas during setup.
---

View File

@ -227,8 +227,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "0"
"count": "0",
"limit": "1"
},
"details": {
"path": "",
@ -268,8 +268,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "0"
"count": "0",
"limit": "1"
},
"details": {
"path": "",
@ -347,8 +347,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "0"
"count": "0",
"limit": "1"
},
"details": {
"path": "",
@ -373,8 +373,8 @@
{
"code": "MULTIPLE_OF_VIOLATED",
"values": {
"multiple_of": "2",
"value": "3"
"value": "3",
"multiple_of": "2"
},
"details": {
"path": "0",
@ -384,8 +384,8 @@
{
"code": "MULTIPLE_OF_VIOLATED",
"values": {
"multiple_of": "2",
"value": "9"
"value": "9",
"multiple_of": "2"
},
"details": {
"path": "2",
@ -432,8 +432,8 @@
{
"code": "MULTIPLE_OF_VIOLATED",
"values": {
"multiple_of": "2",
"value": "1"
"value": "1",
"multiple_of": "2"
},
"details": {
"path": "0",

View File

@ -62,8 +62,8 @@
"code": "EDGE_MISSING",
"values": {
"parent_type": "org",
"property_name": "missing_users",
"child_type": "user"
"child_type": "user",
"property_name": "missing_users"
},
"details": {
"path": "full.org/missing_users",
@ -151,8 +151,8 @@
{
"code": "EDGE_MISSING",
"values": {
"child_type": "child",
"parent_type": "parent",
"child_type": "child",
"property_name": "children"
},
"details": {
@ -470,8 +470,8 @@
{
"code": "DATABASE_TYPE_PARSE_FAILED",
"values": {
"reason": "invalid type: sequence, expected a string",
"type": "failure"
"type": "failure",
"reason": "invalid type: sequence, expected a string"
},
"details": {
"context": "failure"

View File

@ -227,8 +227,8 @@
{
"code": "DEPENDENCY_MISSING",
"values": {
"required_property": "bar",
"property_name": "quux"
"property_name": "quux",
"required_property": "bar"
},
"details": {
"path": "",
@ -386,8 +386,8 @@
{
"code": "DEPENDENCY_MISSING",
"values": {
"required_property": "foo'bar",
"property_name": "foo\"bar"
"property_name": "foo\"bar",
"required_property": "foo'bar"
},
"details": {
"path": "",
@ -829,8 +829,8 @@
{
"code": "MIN_PROPERTIES_VIOLATED",
"values": {
"limit": "4",
"count": "2"
"count": "2",
"limit": "4"
},
"details": {
"path": "",

View File

@ -34,8 +34,8 @@
{
"code": "EXCLUSIVE_MAXIMUM_VIOLATED",
"values": {
"limit": "3",
"value": "3"
"value": "3",
"limit": "3"
},
"details": {
"path": "",

View File

@ -34,8 +34,8 @@
{
"code": "EXCLUSIVE_MINIMUM_VIOLATED",
"values": {
"limit": "1.1",
"value": "1.1"
"value": "1.1",
"limit": "1.1"
},
"details": {
"path": "",

View File

@ -211,13 +211,6 @@
"gender": {},
"gender.condition": {
"type": "condition",
"compiledPropertyNames": [
"kind",
"$eq",
"$ne",
"$of",
"$nof"
],
"properties": {
"$eq": {
"type": [
@ -231,15 +224,6 @@
"null"
]
},
"$nof": {
"type": [
"array",
"null"
],
"items": {
"type": "gender"
}
},
"$of": {
"type": [
"array",
@ -248,80 +232,32 @@
"items": {
"type": "gender"
}
},
"$nof": {
"type": [
"array",
"null"
],
"items": {
"type": "gender"
}
}
}
},
"compiledPropertyNames": [
"kind",
"$eq",
"$ne",
"$of",
"$nof"
]
},
"person": {},
"person.filter": {
"compiledPropertyNames": [
"first_name",
"age",
"billing_address",
"gender",
"birth_date",
"uuid_field",
"tags",
"ad_hoc",
"$and",
"$or"
],
"type": "filter",
"properties": {
"$and": {
"items": {
"compiledPropertyNames": [
"first_name",
"age",
"billing_address",
"gender",
"birth_date",
"uuid_field",
"tags",
"ad_hoc",
"$and",
"$or"
],
"type": "person.filter"
},
"first_name": {
"type": [
"array",
"null"
]
},
"$or": {
"items": {
"compiledPropertyNames": [
"first_name",
"age",
"billing_address",
"gender",
"birth_date",
"uuid_field",
"tags",
"ad_hoc",
"$and",
"$or"
],
"type": "person.filter"
},
"type": [
"array",
"null"
]
},
"ad_hoc": {
"compiledPropertyNames": [
"foo"
],
"properties": {
"foo": {
"type": [
"string.condition",
"null"
]
}
},
"type": [
"filter",
"string.condition",
"null"
]
},
@ -337,6 +273,12 @@
"null"
]
},
"gender": {
"type": [
"gender.condition",
"null"
]
},
"birth_date": {
"type": [
"date.condition",
@ -349,48 +291,48 @@
"null"
]
},
"first_name": {
"type": [
"string.condition",
"null"
]
},
"gender": {
"type": [
"gender.condition",
"null"
]
},
"tags": {
"type": [
"string.condition",
"null"
]
}
},
"type": "filter"
},
"address": {},
"address.filter": {
"type": "filter",
"compiledPropertyNames": [
"city",
"$and",
"$or"
],
"properties": {
},
"ad_hoc": {
"type": [
"filter",
"null"
],
"properties": {
"foo": {
"type": [
"string.condition",
"null"
]
}
},
"compiledPropertyNames": [
"foo"
]
},
"$and": {
"type": [
"array",
"null"
],
"items": {
"type": "person.filter",
"compiledPropertyNames": [
"city",
"first_name",
"age",
"billing_address",
"gender",
"birth_date",
"uuid_field",
"tags",
"ad_hoc",
"$and",
"$or"
],
"type": "address.filter"
]
}
},
"$or": {
@ -399,21 +341,79 @@
"null"
],
"items": {
"type": "person.filter",
"compiledPropertyNames": [
"city",
"first_name",
"age",
"billing_address",
"gender",
"birth_date",
"uuid_field",
"tags",
"ad_hoc",
"$and",
"$or"
],
"type": "address.filter"
]
}
},
}
},
"compiledPropertyNames": [
"first_name",
"age",
"billing_address",
"gender",
"birth_date",
"uuid_field",
"tags",
"ad_hoc",
"$and",
"$or"
]
},
"address": {},
"address.filter": {
"type": "filter",
"properties": {
"city": {
"type": [
"string.condition",
"null"
]
},
"$and": {
"type": [
"array",
"null"
],
"items": {
"type": "address.filter",
"compiledPropertyNames": [
"city",
"$and",
"$or"
]
}
},
"$or": {
"type": [
"array",
"null"
],
"items": {
"type": "address.filter",
"compiledPropertyNames": [
"city",
"$and",
"$or"
]
}
}
}
},
"compiledPropertyNames": [
"city",
"$and",
"$or"
]
},
"condition": {},
"filter": {},
@ -424,52 +424,7 @@
"search": {},
"search.filter": {
"type": "filter",
"compiledPropertyNames": [
"kind",
"name",
"filter",
"$and",
"$or"
],
"properties": {
"$and": {
"type": [
"array",
"null"
],
"items": {
"compiledPropertyNames": [
"kind",
"name",
"filter",
"$and",
"$or"
],
"type": "search.filter"
}
},
"$or": {
"type": [
"array",
"null"
],
"items": {
"compiledPropertyNames": [
"kind",
"name",
"filter",
"$and",
"$or"
],
"type": "search.filter"
}
},
"filter": {
"type": [
"$kind.filter",
"null"
]
},
"kind": {
"type": [
"string.condition",
@ -481,8 +436,53 @@
"string.condition",
"null"
]
},
"filter": {
"type": [
"$kind.filter",
"null"
]
},
"$and": {
"type": [
"array",
"null"
],
"items": {
"type": "search.filter",
"compiledPropertyNames": [
"kind",
"name",
"filter",
"$and",
"$or"
]
}
},
"$or": {
"type": [
"array",
"null"
],
"items": {
"type": "search.filter",
"compiledPropertyNames": [
"kind",
"name",
"filter",
"$and",
"$or"
]
}
}
}
},
"compiledPropertyNames": [
"kind",
"name",
"filter",
"$and",
"$or"
]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -106,8 +106,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "2"
"count": "2",
"limit": "1"
},
"details": {
"path": "",
@ -144,8 +144,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "2"
"count": "2",
"limit": "1"
},
"details": {
"path": "",
@ -284,8 +284,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "3",
"count": "4"
"count": "4",
"limit": "3"
},
"details": {
"path": "",

View File

@ -52,8 +52,8 @@
{
"code": "MAX_ITEMS_VIOLATED",
"values": {
"limit": "2",
"count": "3"
"count": "3",
"limit": "2"
},
"details": {
"path": "",

View File

@ -43,8 +43,8 @@
{
"code": "MAX_LENGTH_VIOLATED",
"values": {
"limit": "2",
"count": "3"
"count": "3",
"limit": "2"
},
"details": {
"path": "",
@ -109,8 +109,8 @@
{
"code": "MAX_LENGTH_VIOLATED",
"values": {
"limit": "2",
"count": "3"
"count": "3",
"limit": "2"
},
"details": {
"path": "",

View File

@ -139,8 +139,8 @@
{
"code": "MAX_PROPERTIES_VIOLATED",
"values": {
"limit": "2",
"count": "3"
"count": "3",
"limit": "2"
},
"details": {
"path": "",
@ -190,8 +190,8 @@
{
"code": "MAX_PROPERTIES_VIOLATED",
"values": {
"limit": "0",
"count": "1"
"count": "1",
"limit": "0"
},
"details": {
"path": "",

View File

@ -118,8 +118,8 @@
{
"code": "MAXIMUM_VIOLATED",
"values": {
"limit": "300",
"value": "300.5"
"value": "300.5",
"limit": "300"
},
"details": {
"path": "",

View File

@ -239,8 +239,8 @@
{
"code": "DEPENDENCY_MISSING",
"values": {
"required_property": "base_dep",
"property_name": "trigger"
"property_name": "trigger",
"required_property": "base_dep"
},
"details": {
"path": "",

View File

@ -169,8 +169,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "2",
"count": "0"
"count": "0",
"limit": "2"
},
"details": {
"path": "",
@ -357,8 +357,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "2",
"count": "0"
"count": "0",
"limit": "2"
},
"details": {
"path": "",
@ -463,8 +463,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "3",
"count": "0"
"count": "0",
"limit": "3"
},
"details": {
"path": "",
@ -487,8 +487,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "3",
"count": "1"
"count": "1",
"limit": "3"
},
"details": {
"path": "",
@ -513,8 +513,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "3"
"count": "3",
"limit": "1"
},
"details": {
"path": "",
@ -657,8 +657,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "2"
"count": "2",
"limit": "1"
},
"details": {
"path": "",

View File

@ -108,8 +108,8 @@
{
"code": "MIN_ITEMS_VIOLATED",
"values": {
"limit": "1",
"count": "0"
"count": "0",
"limit": "1"
},
"details": {
"path": "",

View File

@ -43,8 +43,8 @@
{
"code": "MIN_LENGTH_VIOLATED",
"values": {
"limit": "2",
"count": "1"
"count": "1",
"limit": "2"
},
"details": {
"path": "",

View File

@ -43,8 +43,8 @@
{
"code": "MINIMUM_VIOLATED",
"values": {
"limit": "1.1",
"value": "0.6"
"value": "0.6",
"limit": "1.1"
},
"details": {
"path": "",
@ -127,8 +127,8 @@
{
"code": "MINIMUM_VIOLATED",
"values": {
"limit": "-2",
"value": "-2.0001"
"value": "-2.0001",
"limit": "-2"
},
"details": {
"path": "",
@ -149,8 +149,8 @@
{
"code": "MINIMUM_VIOLATED",
"values": {
"limit": "-2",
"value": "-3"
"value": "-3",
"limit": "-2"
},
"details": {
"path": "",

View File

@ -148,8 +148,8 @@
{
"code": "MULTIPLE_OF_VIOLATED",
"values": {
"multiple_of": "0.0001",
"value": "0.00751"
"value": "0.00751",
"multiple_of": "0.0001"
},
"details": {
"path": "",

View File

@ -156,8 +156,8 @@
{
"code": "MAXIMUM_VIOLATED",
"values": {
"limit": "50",
"value": "60"
"value": "60",
"limit": "50"
},
"details": {
"path": "max",
@ -191,6 +191,9 @@
},
{
"name": "request",
"field_types": {
"inv": "jsonb"
},
"schemas": {
"request": {
"type": "object",

View File

@ -200,8 +200,8 @@
{
"code": "MINIMUM_VIOLATED",
"values": {
"limit": "10",
"value": "5"
"value": "5",
"limit": "10"
},
"details": {
"path": "entities/entity-beta/value",

View File

@ -34,8 +34,8 @@
{
"code": "PATTERN_VIOLATED",
"values": {
"value": "abc",
"pattern": "^a*$"
"pattern": "^a*$",
"value": "abc"
},
"details": {
"path": "",

View File

@ -148,8 +148,8 @@
{
"code": "NO_FAMILY_MATCH",
"values": {
"value": "alien",
"discriminator": "type"
"discriminator": "type",
"value": "alien"
},
"details": {
"path": "",
@ -284,8 +284,8 @@
{
"code": "NO_FAMILY_MATCH",
"values": {
"value": "bot",
"discriminator": "type"
"discriminator": "type",
"value": "bot"
},
"details": {
"path": "",
@ -591,8 +591,8 @@
{
"code": "NO_ONEOF_MATCH",
"values": {
"value": "alien",
"discriminator": "type"
"discriminator": "type",
"value": "alien"
},
"details": {
"path": "",
@ -931,8 +931,8 @@
{
"code": "NO_FAMILY_MATCH",
"values": {
"value": "unknown_panel",
"discriminator": "kind"
"discriminator": "kind",
"value": "unknown_panel"
},
"details": {
"path": "",

View File

@ -431,8 +431,8 @@
{
"code": "MAX_LENGTH_VIOLATED",
"values": {
"limit": "3",
"count": "6"
"count": "6",
"limit": "3"
},
"details": {
"path": "",

View File

@ -1,6 +1,6 @@
[
{
"description": "Queryer Execution",
"description": "Queryer Suite 1",
"database": {
"puncs": [
{
@ -1293,8 +1293,8 @@
" 'age', person_3.age",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2",
" JOIN agreego.person person_3",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" JOIN agreego.person person_3 ON person_3.id = organization_2.id",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -1336,15 +1336,15 @@
" 'number', phone_number_8.number",
" )",
" FROM agreego.entity entity_7",
" JOIN agreego.phone_number phone_number_8",
" JOIN agreego.phone_number phone_number_8 ON phone_number_8.id = entity_7.id",
" WHERE",
" NOT entity_7.archived",
" AND relationship_5.target_id = entity_7.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_4",
" JOIN agreego.relationship relationship_5",
" JOIN agreego.contact contact_6",
" JOIN agreego.relationship relationship_5 ON relationship_5.id = entity_4.id",
" JOIN agreego.contact contact_6 ON contact_6.id = relationship_5.id",
" WHERE",
" NOT entity_4.archived",
" AND relationship_5.target_type = 'phone_number'",
@ -1366,15 +1366,15 @@
" 'address', email_address_13.address",
" )",
" FROM agreego.entity entity_12",
" JOIN agreego.email_address email_address_13",
" JOIN agreego.email_address email_address_13 ON email_address_13.id = entity_12.id",
" WHERE",
" NOT entity_12.archived",
" AND relationship_10.target_id = entity_12.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_9",
" JOIN agreego.relationship relationship_10",
" JOIN agreego.contact contact_11",
" JOIN agreego.relationship relationship_10 ON relationship_10.id = entity_9.id",
" JOIN agreego.contact contact_11 ON contact_11.id = relationship_10.id",
" WHERE",
" NOT entity_9.archived",
" AND relationship_10.target_type = 'email_address'",
@ -1396,15 +1396,15 @@
" 'city', address_18.city",
" )",
" FROM agreego.entity entity_17",
" JOIN agreego.address address_18",
" JOIN agreego.address address_18 ON address_18.id = entity_17.id",
" WHERE",
" NOT entity_17.archived",
" AND relationship_15.target_id = entity_17.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_14",
" JOIN agreego.relationship relationship_15",
" JOIN agreego.contact contact_16",
" JOIN agreego.relationship relationship_15 ON relationship_15.id = entity_14.id",
" JOIN agreego.contact contact_16 ON contact_16.id = relationship_15.id",
" WHERE",
" NOT entity_14.archived",
" AND relationship_15.target_type = 'address'",
@ -1427,7 +1427,7 @@
" 'number', phone_number_23.number",
" )",
" FROM agreego.entity entity_22",
" JOIN agreego.phone_number phone_number_23",
" JOIN agreego.phone_number phone_number_23 ON phone_number_23.id = entity_22.id",
" WHERE",
" NOT entity_22.archived",
" AND relationship_20.target_id = entity_22.id",
@ -1441,7 +1441,7 @@
" 'address', email_address_25.address",
" )",
" FROM agreego.entity entity_24",
" JOIN agreego.email_address email_address_25",
" JOIN agreego.email_address email_address_25 ON email_address_25.id = entity_24.id",
" WHERE",
" NOT entity_24.archived",
" AND relationship_20.target_id = entity_24.id",
@ -1455,7 +1455,7 @@
" 'city', address_27.city",
" )",
" FROM agreego.entity entity_26",
" JOIN agreego.address address_27",
" JOIN agreego.address address_27 ON address_27.id = entity_26.id",
" WHERE",
" NOT entity_26.archived",
" AND relationship_20.target_id = entity_26.id",
@ -1464,16 +1464,16 @@
" END",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_19",
" JOIN agreego.relationship relationship_20",
" JOIN agreego.contact contact_21",
" JOIN agreego.relationship relationship_20 ON relationship_20.id = entity_19.id",
" JOIN agreego.contact contact_21 ON contact_21.id = relationship_20.id",
" WHERE",
" NOT entity_19.archived",
" AND relationship_20.source_id = entity_1.id",
" )",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2",
" JOIN agreego.person person_3",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" JOIN agreego.person person_3 ON person_3.id = organization_2.id",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -1586,7 +1586,7 @@
" 'number', phone_number_8.number",
" )",
" FROM agreego.entity entity_7",
" JOIN agreego.phone_number phone_number_8",
" JOIN agreego.phone_number phone_number_8 ON phone_number_8.id = entity_7.id",
" WHERE",
" NOT entity_7.archived",
" AND phone_number_8.number ILIKE $32 #>> '{}'",
@ -1594,8 +1594,8 @@
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_4",
" JOIN agreego.relationship relationship_5",
" JOIN agreego.contact contact_6",
" JOIN agreego.relationship relationship_5 ON relationship_5.id = entity_4.id",
" JOIN agreego.contact contact_6 ON contact_6.id = relationship_5.id",
" WHERE",
" NOT entity_4.archived",
" AND relationship_5.target_type = 'phone_number'",
@ -1617,15 +1617,15 @@
" 'address', email_address_13.address",
" )",
" FROM agreego.entity entity_12",
" JOIN agreego.email_address email_address_13",
" JOIN agreego.email_address email_address_13 ON email_address_13.id = entity_12.id",
" WHERE",
" NOT entity_12.archived",
" AND relationship_10.target_id = entity_12.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_9",
" JOIN agreego.relationship relationship_10",
" JOIN agreego.contact contact_11",
" JOIN agreego.relationship relationship_10 ON relationship_10.id = entity_9.id",
" JOIN agreego.contact contact_11 ON contact_11.id = relationship_10.id",
" WHERE",
" NOT entity_9.archived",
" AND relationship_10.target_type = 'email_address'",
@ -1647,15 +1647,15 @@
" 'city', address_18.city",
" )",
" FROM agreego.entity entity_17",
" JOIN agreego.address address_18",
" JOIN agreego.address address_18 ON address_18.id = entity_17.id",
" WHERE",
" NOT entity_17.archived",
" AND relationship_15.target_id = entity_17.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_14",
" JOIN agreego.relationship relationship_15",
" JOIN agreego.contact contact_16",
" JOIN agreego.relationship relationship_15 ON relationship_15.id = entity_14.id",
" JOIN agreego.contact contact_16 ON contact_16.id = relationship_15.id",
" WHERE",
" NOT entity_14.archived",
" AND relationship_15.target_type = 'address'",
@ -1678,7 +1678,7 @@
" 'number', phone_number_23.number",
" )",
" FROM agreego.entity entity_22",
" JOIN agreego.phone_number phone_number_23",
" JOIN agreego.phone_number phone_number_23 ON phone_number_23.id = entity_22.id",
" WHERE",
" NOT entity_22.archived",
" AND relationship_20.target_id = entity_22.id",
@ -1692,7 +1692,7 @@
" 'address', email_address_25.address",
" )",
" FROM agreego.entity entity_24",
" JOIN agreego.email_address email_address_25",
" JOIN agreego.email_address email_address_25 ON email_address_25.id = entity_24.id",
" WHERE",
" NOT entity_24.archived",
" AND relationship_20.target_id = entity_24.id",
@ -1706,7 +1706,7 @@
" 'city', address_27.city",
" )",
" FROM agreego.entity entity_26",
" JOIN agreego.address address_27",
" JOIN agreego.address address_27 ON address_27.id = entity_26.id",
" WHERE",
" NOT entity_26.archived",
" AND relationship_20.target_id = entity_26.id",
@ -1715,8 +1715,8 @@
" END",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_19",
" JOIN agreego.relationship relationship_20",
" JOIN agreego.contact contact_21",
" JOIN agreego.relationship relationship_20 ON relationship_20.id = entity_19.id",
" JOIN agreego.contact contact_21 ON contact_21.id = relationship_20.id",
" WHERE",
" NOT entity_19.archived",
" AND contact_21.is_primary = ($11 #>> '{}')::BOOLEAN",
@ -1724,8 +1724,8 @@
" )",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2",
" JOIN agreego.person person_3",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" JOIN agreego.person person_3 ON person_3.id = organization_2.id",
" WHERE",
" NOT entity_1.archived",
" AND person_3.age = ($1 #>> '{}')::NUMERIC",
@ -1787,15 +1787,15 @@
" 'address', email_address_5.address",
" )",
" FROM agreego.entity entity_4",
" JOIN agreego.email_address email_address_5",
" JOIN agreego.email_address email_address_5 ON email_address_5.id = entity_4.id",
" WHERE",
" NOT entity_4.archived",
" AND relationship_2.target_id = entity_4.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_1",
" JOIN agreego.relationship relationship_2",
" JOIN agreego.contact contact_3",
" JOIN agreego.relationship relationship_2 ON relationship_2.id = entity_1.id",
" JOIN agreego.contact contact_3 ON contact_3.id = relationship_2.id",
" WHERE",
" NOT entity_1.archived",
" AND relationship_2.target_type = 'email_address'",
@ -1832,8 +1832,8 @@
" 'age', person_5.age",
" )",
" FROM agreego.entity entity_3",
" JOIN agreego.organization organization_4",
" JOIN agreego.person person_5",
" JOIN agreego.organization organization_4 ON organization_4.id = entity_3.id",
" JOIN agreego.person person_5 ON person_5.id = organization_4.id",
" WHERE",
" NOT entity_3.archived",
" AND order_2.customer_id = person_5.id",
@ -1849,14 +1849,14 @@
" 'price', order_line_7.price",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_6",
" JOIN agreego.order_line order_line_7",
" JOIN agreego.order_line order_line_7 ON order_line_7.id = entity_6.id",
" WHERE",
" NOT entity_6.archived",
" AND order_line_7.order_id = order_2.id",
" )",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.order order_2",
" JOIN agreego.order order_2 ON order_2.id = entity_1.id",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -1881,7 +1881,7 @@
" 'name', organization_2.name",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -1910,8 +1910,8 @@
" 'role', bot_5.role",
" )",
" FROM agreego.entity entity_3",
" JOIN agreego.organization organization_4",
" JOIN agreego.bot bot_5",
" JOIN agreego.organization organization_4 ON organization_4.id = entity_3.id",
" JOIN agreego.bot bot_5 ON bot_5.id = organization_4.id",
" WHERE",
" NOT entity_3.archived",
" AND entity_3.id = entity_1.id",
@ -1925,7 +1925,7 @@
" 'name', organization_7.name",
" )",
" FROM agreego.entity entity_6",
" JOIN agreego.organization organization_7",
" JOIN agreego.organization organization_7 ON organization_7.id = entity_6.id",
" WHERE",
" NOT entity_6.archived",
" AND entity_6.id = entity_1.id",
@ -1942,8 +1942,8 @@
" 'age', person_10.age",
" )",
" FROM agreego.entity entity_8",
" JOIN agreego.organization organization_9",
" JOIN agreego.person person_10",
" JOIN agreego.organization organization_9 ON organization_9.id = entity_8.id",
" JOIN agreego.person person_10 ON person_10.id = organization_9.id",
" WHERE",
" NOT entity_8.archived",
" AND entity_8.id = entity_1.id",
@ -1951,7 +1951,7 @@
" ELSE NULL",
" END), '[]'::jsonb)",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -1979,8 +1979,8 @@
" 'token', bot_5.token",
" )",
" FROM agreego.entity entity_3",
" JOIN agreego.organization organization_4",
" JOIN agreego.bot bot_5",
" JOIN agreego.organization organization_4 ON organization_4.id = entity_3.id",
" JOIN agreego.bot bot_5 ON bot_5.id = organization_4.id",
" WHERE",
" NOT entity_3.archived",
" AND entity_3.id = entity_1.id",
@ -1996,8 +1996,8 @@
" 'last_name', person_8.last_name",
" )",
" FROM agreego.entity entity_6",
" JOIN agreego.organization organization_7",
" JOIN agreego.person person_8",
" JOIN agreego.organization organization_7 ON organization_7.id = entity_6.id",
" JOIN agreego.person person_8 ON person_8.id = organization_7.id",
" WHERE",
" NOT entity_6.archived",
" AND entity_6.id = entity_1.id",
@ -2005,7 +2005,7 @@
" ELSE NULL",
" END",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -2049,15 +2049,15 @@
" 'number', phone_number_10.number",
" )",
" FROM agreego.entity entity_9",
" JOIN agreego.phone_number phone_number_10",
" JOIN agreego.phone_number phone_number_10 ON phone_number_10.id = entity_9.id",
" WHERE",
" NOT entity_9.archived",
" AND relationship_7.target_id = entity_9.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_6",
" JOIN agreego.relationship relationship_7",
" JOIN agreego.contact contact_8",
" JOIN agreego.relationship relationship_7 ON relationship_7.id = entity_6.id",
" JOIN agreego.contact contact_8 ON contact_8.id = relationship_7.id",
" WHERE",
" NOT entity_6.archived",
" AND relationship_7.target_type = 'phone_number'",
@ -2079,15 +2079,15 @@
" 'address', email_address_15.address",
" )",
" FROM agreego.entity entity_14",
" JOIN agreego.email_address email_address_15",
" JOIN agreego.email_address email_address_15 ON email_address_15.id = entity_14.id",
" WHERE",
" NOT entity_14.archived",
" AND relationship_12.target_id = entity_14.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_11",
" JOIN agreego.relationship relationship_12",
" JOIN agreego.contact contact_13",
" JOIN agreego.relationship relationship_12 ON relationship_12.id = entity_11.id",
" JOIN agreego.contact contact_13 ON contact_13.id = relationship_12.id",
" WHERE",
" NOT entity_11.archived",
" AND relationship_12.target_type = 'email_address'",
@ -2109,15 +2109,15 @@
" 'city', address_20.city",
" )",
" FROM agreego.entity entity_19",
" JOIN agreego.address address_20",
" JOIN agreego.address address_20 ON address_20.id = entity_19.id",
" WHERE",
" NOT entity_19.archived",
" AND relationship_17.target_id = entity_19.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_16",
" JOIN agreego.relationship relationship_17",
" JOIN agreego.contact contact_18",
" JOIN agreego.relationship relationship_17 ON relationship_17.id = entity_16.id",
" JOIN agreego.contact contact_18 ON contact_18.id = relationship_17.id",
" WHERE",
" NOT entity_16.archived",
" AND relationship_17.target_type = 'address'",
@ -2140,7 +2140,7 @@
" 'number', phone_number_25.number",
" )",
" FROM agreego.entity entity_24",
" JOIN agreego.phone_number phone_number_25",
" JOIN agreego.phone_number phone_number_25 ON phone_number_25.id = entity_24.id",
" WHERE",
" NOT entity_24.archived",
" AND relationship_22.target_id = entity_24.id",
@ -2154,7 +2154,7 @@
" 'address', email_address_27.address",
" )",
" FROM agreego.entity entity_26",
" JOIN agreego.email_address email_address_27",
" JOIN agreego.email_address email_address_27 ON email_address_27.id = entity_26.id",
" WHERE",
" NOT entity_26.archived",
" AND relationship_22.target_id = entity_26.id",
@ -2168,7 +2168,7 @@
" 'city', address_29.city",
" )",
" FROM agreego.entity entity_28",
" JOIN agreego.address address_29",
" JOIN agreego.address address_29 ON address_29.id = entity_28.id",
" WHERE",
" NOT entity_28.archived",
" AND relationship_22.target_id = entity_28.id",
@ -2177,16 +2177,16 @@
" END",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_21",
" JOIN agreego.relationship relationship_22",
" JOIN agreego.contact contact_23",
" JOIN agreego.relationship relationship_22 ON relationship_22.id = entity_21.id",
" JOIN agreego.contact contact_23 ON contact_23.id = relationship_22.id",
" WHERE",
" NOT entity_21.archived",
" AND relationship_22.source_id = entity_3.id",
" )",
" )",
" FROM agreego.entity entity_3",
" JOIN agreego.organization organization_4",
" JOIN agreego.person person_5",
" JOIN agreego.organization organization_4 ON organization_4.id = entity_3.id",
" JOIN agreego.person person_5 ON person_5.id = organization_4.id",
" WHERE",
" NOT entity_3.archived",
" AND entity_3.id = entity_1.id",
@ -2194,7 +2194,7 @@
" ELSE NULL",
" END",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -2230,15 +2230,15 @@
" 'age', person_5.age",
" )",
" FROM agreego.entity entity_3",
" JOIN agreego.organization organization_4",
" JOIN agreego.person person_5",
" JOIN agreego.organization organization_4 ON organization_4.id = entity_3.id",
" JOIN agreego.person person_5 ON person_5.id = organization_4.id",
" WHERE",
" NOT entity_3.archived",
" AND order_2.customer_id = person_5.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_1",
" JOIN agreego.order order_2",
" JOIN agreego.order order_2 ON order_2.id = entity_1.id",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -2273,7 +2273,7 @@
" ELSE NULL",
" END), '[]'::jsonb)",
" FROM agreego.entity entity_1",
" JOIN agreego.widget widget_2",
" JOIN agreego.widget widget_2 ON widget_2.id = entity_1.id",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -2306,7 +2306,7 @@
" 'price', invoice_line_4.price",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_3",
" JOIN agreego.invoice_line invoice_line_4",
" JOIN agreego.invoice_line invoice_line_4 ON invoice_line_4.id = entity_3.id",
" WHERE",
" NOT entity_3.archived",
" AND invoice_line_4.invoice_id = invoice_2.id",
@ -2317,7 +2317,7 @@
" 'metadata_nested_lines', invoice_2.metadata_nested_lines",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.invoice invoice_2",
" JOIN agreego.invoice invoice_2 ON invoice_2.id = entity_1.id",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -2344,7 +2344,7 @@
" 'card_number', account_2.card_number",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.account account_2",
" JOIN agreego.account account_2 ON account_2.id = entity_1.id",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -2381,8 +2381,8 @@
" 'role', bot_7.role",
" )",
" FROM agreego.entity entity_5",
" JOIN agreego.organization organization_6",
" JOIN agreego.bot bot_7",
" JOIN agreego.organization organization_6 ON organization_6.id = entity_5.id",
" JOIN agreego.bot bot_7 ON bot_7.id = organization_6.id",
" WHERE",
" NOT entity_5.archived",
" AND entity_5.id = entity_3.id",
@ -2396,7 +2396,7 @@
" 'name', organization_9.name",
" )",
" FROM agreego.entity entity_8",
" JOIN agreego.organization organization_9",
" JOIN agreego.organization organization_9 ON organization_9.id = entity_8.id",
" WHERE",
" NOT entity_8.archived",
" AND entity_8.id = entity_3.id",
@ -2413,8 +2413,8 @@
" 'age', person_12.age",
" )",
" FROM agreego.entity entity_10",
" JOIN agreego.organization organization_11",
" JOIN agreego.person person_12",
" JOIN agreego.organization organization_11 ON organization_11.id = entity_10.id",
" JOIN agreego.person person_12 ON person_12.id = organization_11.id",
" WHERE",
" NOT entity_10.archived",
" AND entity_10.id = entity_3.id",
@ -2422,14 +2422,14 @@
" ELSE NULL",
" END",
" FROM agreego.entity entity_3",
" JOIN agreego.organization organization_4",
" JOIN agreego.organization organization_4 ON organization_4.id = entity_3.id",
" WHERE",
" NOT entity_3.archived",
" AND order_2.counterparty_id = entity_3.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_1",
" JOIN agreego.order order_2",
" JOIN agreego.order order_2 ON order_2.id = entity_1.id",
" WHERE",
" NOT entity_1.archived",
" AND order_2.counterparty_type IN ('bot', 'organization', 'person')",
@ -2439,5 +2439,276 @@
}
}
]
},
{
"description": "Queryer Suite 2",
"database": {
"puncs": [
{
"name": "get_pet",
"schemas": {
"get_pet.response": {
"type": "full.pet"
}
}
}
],
"enums": [],
"relations": [
{
"type": "relation",
"id": "00000000-0000-0000-0000-000000000001",
"constraint": "fk_attachment_attachable_entity",
"source_type": "attachment",
"source_columns": [
"attachable_id",
"attachable_type"
],
"destination_type": "entity",
"destination_columns": [
"id",
"type"
],
"prefix": "attachable"
}
],
"types": [
{
"name": "entity",
"hierarchy": [
"entity"
],
"grouped_fields": {
"entity": [
"id",
"type",
"archived"
]
},
"field_types": {
"id": "uuid",
"archived": "boolean",
"type": "text"
},
"schemas": {
"entity": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
},
"archived": {
"type": "boolean"
}
}
}
},
"fields": [
"id",
"type",
"archived"
],
"variations": [
"entity",
"pet",
"attachment"
]
},
{
"name": "pet",
"hierarchy": [
"entity",
"pet"
],
"grouped_fields": {
"entity": [
"id",
"type",
"archived"
],
"pet": [
"name"
]
},
"field_types": {
"name": "text"
},
"schemas": {
"pet": {
"type": "entity",
"properties": {
"name": {
"type": "string"
}
}
},
"full.pet": {
"type": "pet",
"properties": {
"cover_attachment": {
"type": "attachment",
"properties": {
"kind": {
"const": "cover"
}
}
},
"cover_attachments": {
"type": "array",
"items": {
"type": "attachment",
"properties": {
"kind": {
"const": "cover"
}
}
}
},
"media_attachments": {
"type": "array",
"items": {
"type": "attachment",
"properties": {
"kind": {
"enum": [
"cover",
"thumbnail"
]
}
}
}
}
}
}
},
"fields": [
"id",
"type",
"archived",
"name"
],
"variations": [
"pet"
]
},
{
"name": "attachment",
"hierarchy": [
"entity",
"attachment"
],
"grouped_fields": {
"entity": [
"id",
"type",
"archived"
],
"attachment": [
"attachable_id",
"name",
"kind"
]
},
"field_types": {
"attachable_id": "uuid",
"name": "text",
"kind": "text"
},
"schemas": {
"attachment": {
"type": "entity",
"properties": {
"attachable_id": {
"type": "string"
},
"name": {
"type": "string"
},
"kind": {
"type": "string"
}
}
}
},
"fields": [
"id",
"type",
"archived",
"attachable_id",
"name",
"kind"
],
"variations": [
"attachment"
]
}
]
},
"tests": [
{
"description": "Select query on pet with static properties constraints on attachments",
"action": "query",
"schema_id": "get_pet.response",
"expect": {
"success": true,
"sql": [
[
"((SELECT jsonb_strip_nulls((",
" SELECT jsonb_build_object(",
" 'name', pet_2.name,",
" 'cover_attachment', (",
" SELECT jsonb_build_object(",
" 'attachable_id', attachment_4.attachable_id,",
" 'name', attachment_4.name,",
" 'kind', attachment_4.kind",
" )",
" FROM agreego.entity entity_3",
" JOIN agreego.attachment attachment_4 ON attachment_4.id = entity_3.id",
" WHERE",
" NOT entity_3.archived",
" AND attachment_4.kind = 'cover'",
" AND attachment_4.attachable_id = entity_1.id",
" LIMIT 1",
" ),",
" 'cover_attachments', (",
" SELECT COALESCE(jsonb_agg(jsonb_build_object(",
" 'attachable_id', attachment_6.attachable_id,",
" 'name', attachment_6.name,",
" 'kind', attachment_6.kind",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_5",
" JOIN agreego.attachment attachment_6 ON attachment_6.id = entity_5.id",
" WHERE",
" NOT entity_5.archived",
" AND attachment_6.kind = 'cover'",
" AND attachment_6.attachable_id = entity_1.id",
" ),",
" 'media_attachments', (",
" SELECT COALESCE(jsonb_agg(jsonb_build_object(",
" 'attachable_id', attachment_8.attachable_id,",
" 'name', attachment_8.name,",
" 'kind', attachment_8.kind",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_7",
" JOIN agreego.attachment attachment_8 ON attachment_8.id = entity_7.id",
" WHERE",
" NOT entity_7.archived",
" AND attachment_8.kind IN ('cover', 'thumbnail')",
" AND attachment_8.attachable_id = entity_1.id",
" )",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.pet pet_2 ON pet_2.id = entity_1.id",
" WHERE",
" NOT entity_1.archived",
"))))"
]
]
}
}
]
}
]

View File

@ -139,8 +139,8 @@
{
"code": "MAX_LENGTH_VIOLATED",
"values": {
"limit": "5",
"count": "26"
"count": "26",
"limit": "5"
},
"details": {
"path": "email",

1
flow
View File

@ -36,7 +36,6 @@ pgrx-down() {
info "Taking pgrx down..."
}
build() {
local version
version=$(get-version) || return $?

View File

@ -2,7 +2,7 @@ use crate::database::schema::Schema;
#[allow(unused_imports)]
use crate::drop::{Error, ErrorDetails};
#[allow(unused_imports)]
use std::collections::HashMap;
use indexmap::IndexMap;
use std::sync::Arc;
impl Schema {
@ -19,7 +19,7 @@ impl Schema {
if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '_' && c != '.' && c != '$' {
errors.push(Error {
code: "INVALID_IDENTIFIER".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("character".to_string(), c.to_string()),
("field_name".to_string(), field_name.to_string()),
("identifier".to_string(), id.to_string()),

View File

@ -7,7 +7,6 @@ pub mod polymorphism;
use crate::database::schema::Schema;
use crate::drop::{Error, ErrorDetails};
use indexmap::IndexMap;
use std::collections::HashMap;
impl Schema {
pub fn compile(
@ -64,7 +63,7 @@ impl Schema {
if custom_type_count > 1 {
errors.push(Error {
code: "MULTIPLE_INHERITANCE_PROHIBITED".to_string(),
values: Some(HashMap::from([("types".to_string(), types.join(", "))])),
values: Some(IndexMap::from([("types".to_string(), types.join(", "))])),
details: ErrorDetails {
path: Some(path.clone()),
schema: Some(root_id.to_string()),

View File

@ -1,6 +1,6 @@
use crate::drop::{Error, ErrorDetails};
use indexmap::IndexSet;
use std::collections::HashMap;
use indexmap::IndexMap;
use crate::database::schema::Schema;
impl Schema {
@ -144,7 +144,7 @@ impl Schema {
if options.contains_key(&val) {
errors.push(Error {
code: "POLYMORPHIC_COLLISION".to_string(),
values: Some(HashMap::from([("value".to_string(), val.to_string())])),
values: Some(IndexMap::from([("value".to_string(), val.to_string())])),
details: ErrorDetails {
path: Some(path.to_string()),
schema: Some(root_id.to_string()),

View File

@ -1,6 +1,7 @@
use crate::drop::{Error, ErrorDetails};
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use indexmap::IndexMap;
pub fn compose(val: &mut Value, errors: &mut Vec<Error>) {
let mut traits = HashMap::new();
@ -114,7 +115,7 @@ fn resolve_in_place(
if visited.contains(inc_name) {
errors.push(Error {
code: "CIRCULAR_INCLUDE_DETECTED".to_string(),
values: Some(HashMap::from([(
values: Some(IndexMap::from([(
"include".to_string(),
inc_name.to_string(),
)])),
@ -218,7 +219,7 @@ fn resolve_in_place(
} else {
errors.push(Error {
code: "TRAIT_NOT_FOUND".to_string(),
values: Some(HashMap::from([(
values: Some(IndexMap::from([(
"include".to_string(),
inc_name.to_string(),
)])),

View File

@ -28,7 +28,7 @@ use serde_json::Value;
use indexmap::IndexMap;
use std::sync::Arc;
use r#type::Type;
use std::collections::HashMap;
use crate::drop::{Drop, Error, ErrorDetails};
#[derive(serde::Serialize)]
@ -76,7 +76,7 @@ impl Database {
Err(e) => {
errors.push(Error {
code: "DATABASE_ENUM_PARSE_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("enum".to_string(), name.clone()),
("reason".to_string(), e.to_string()),
])),
@ -104,7 +104,7 @@ impl Database {
Err(e) => {
errors.push(Error {
code: "DATABASE_TYPE_PARSE_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("type".to_string(), name.clone()),
("reason".to_string(), e.to_string()),
])),
@ -136,7 +136,7 @@ impl Database {
Err(e) => {
errors.push(Error {
code: "DATABASE_RELATION_PARSE_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("relation".to_string(), constraint.clone()),
("reason".to_string(), e.to_string()),
])),
@ -164,7 +164,7 @@ impl Database {
Err(e) => {
errors.push(Error {
code: "DATABASE_PUNC_PARSE_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("punc".to_string(), name.clone()),
("reason".to_string(), e.to_string()),
])),
@ -461,7 +461,7 @@ impl Database {
errors.push(Error {
code: "EDGE_MISSING".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("parent_type".to_string(), parent_type.to_string()),
("child_type".to_string(), child_type.to_string()),
("property_name".to_string(), prop_name.to_string()),
@ -563,7 +563,7 @@ impl Database {
errors.push(Error {
code: "AMBIGUOUS_TYPE_RELATIONS".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("parent_type".to_string(), parent_type.to_string()),
("child_type".to_string(), child_type.to_string()),
("property_name".to_string(), prop_name.to_string()),

View File

@ -28,6 +28,4 @@ pub struct Sidebar {
pub category: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<String>,
}

View File

@ -57,13 +57,13 @@ impl Drop {
}
}
use std::collections::HashMap;
use indexmap::IndexMap;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Error {
pub code: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub values: Option<HashMap<String, String>>,
pub values: Option<IndexMap<String, String>>,
pub details: ErrorDetails,
}

View File

@ -7,7 +7,7 @@ use crate::database::Database;
use crate::database::r#type::Type;
use crate::drop::{Drop, Error, ErrorDetails};
use serde_json::Value;
use std::collections::HashMap;
use indexmap::IndexMap;
use std::sync::Arc;
pub struct Merger {
@ -31,7 +31,7 @@ impl Merger {
None => {
return Drop::with_errors(vec![Error {
code: "SCHEMA_NOT_FOUND".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("schema".to_string(), schema_id.to_string()),
])),
details: ErrorDetails {
@ -78,7 +78,7 @@ impl Merger {
return Drop::with_errors(vec![Error {
code: final_code,
values: Some(HashMap::from([
values: Some(IndexMap::from([
("error".to_string(), final_message),
])),
details: ErrorDetails {
@ -96,7 +96,7 @@ impl Merger {
if let Err(e) = self.db.execute(&notify_sql, None) {
return Drop::with_errors(vec![Error {
code: "MERGE_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("error".to_string(), e.to_string()),
])),
details: ErrorDetails {

View File

@ -1,5 +1,6 @@
use crate::database::Database;
use indexmap::IndexMap;
use serde_json::Value;
use std::sync::Arc;
pub struct Compiler<'a> {
@ -120,6 +121,21 @@ impl<'a> Compiler<'a> {
}
fn compile_reference(&mut self, node: Node<'a>) -> Result<(String, String), String> {
// Handle Direct Refs via type pointer first
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &node.schema.obj.type_ {
if !crate::database::object::is_primitive_type(t) {
if !self.db.types.contains_key(t) {
// If it's just an ad-hoc struct ref, we should resolve it
if let Some(target_schema) = self.db.schemas.get(t).cloned() {
let mut ref_node = node.clone();
ref_node.schema = target_schema.clone();
ref_node.schema_id = Some(t.clone());
return self.compile_node(ref_node);
}
}
}
}
// Determine if this schema represents a Database Entity
let mut resolved_type = None;
@ -149,16 +165,9 @@ impl<'a> Compiler<'a> {
return self.compile_entity(type_def, node.clone(), false);
}
// Handle Direct Refs via type pointer
// Fallback error if the schema pointer was unresolved
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &node.schema.obj.type_ {
if !crate::database::object::is_primitive_type(t) {
// If it's just an ad-hoc struct ref, we should resolve it
if let Some(target_schema) = self.db.schemas.get(t).cloned() {
let mut ref_node = node.clone();
ref_node.schema = target_schema.clone();
ref_node.schema_id = Some(t.clone());
return self.compile_node(ref_node);
}
return Err(format!("Unresolved schema type pointer: {}", t));
}
}
@ -231,7 +240,7 @@ impl<'a> Compiler<'a> {
};
// 3. Build WHERE clauses
let where_clauses = self.compile_where_clause(r#type, &table_aliases, node)?;
let where_clauses = self.compile_where_clause(r#type, &table_aliases, node.clone())?;
let selection = if is_array {
format!("COALESCE(jsonb_agg({}), '[]'::jsonb)", jsonb_obj_sql)
@ -239,11 +248,38 @@ impl<'a> Compiler<'a> {
jsonb_obj_sql
};
let mut limit_clause = "";
if !is_array {
let is_reverse = if let Some(prop_ref) = &node.property_name {
let prop = prop_ref.as_str();
if let Some(parent_schema) = &node.parent_schema {
if let Some(compiled_edges) = parent_schema.obj.compiled_edges.get() {
if let Some(edge) = compiled_edges.get(prop) {
!edge.forward
} else {
false
}
} else {
false
}
} else {
false
}
} else {
false
};
if is_reverse {
limit_clause = " LIMIT 1";
}
}
let full_sql = format!(
"(SELECT {} FROM {} WHERE {})",
"(SELECT {} FROM {} WHERE {}{})",
selection,
from_clauses.join(" "),
where_clauses.join(" AND ")
where_clauses.join(" AND "),
limit_clause
);
Ok((
@ -551,6 +587,7 @@ impl<'a> Compiler<'a> {
self.compile_filter_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses);
self.compile_polymorphic_bounds(r#type, type_aliases, &node, &mut where_clauses);
self.compile_static_property_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses);
let start_len = where_clauses.len();
self.compile_relation_conditions(
@ -611,9 +648,6 @@ impl<'a> Compiler<'a> {
if edge.forward && relation.source_columns.len() > 1 {
poly_col = Some(&relation.source_columns[1]); // e.g., target_type
table_to_alias = &relation.source_type; // e.g., relationship
} else if !edge.forward && relation.destination_columns.len() > 1 {
poly_col = Some(&relation.destination_columns[1]); // e.g., source_type
table_to_alias = &relation.destination_type; // e.g., relationship
}
if let Some(col) = poly_col {
@ -865,4 +899,69 @@ impl<'a> Compiler<'a> {
}
Ok(())
}
fn compile_static_property_conditions(
&self,
r#type: &crate::database::r#type::Type,
type_aliases: &std::collections::HashMap<String, String>,
node: &Node,
base_alias: &str,
where_clauses: &mut Vec<String>,
) {
if let Some(props) = node.schema.obj.properties.as_ref() {
for (prop_name, prop_schema) in props {
let filter_alias = Self::resolve_filter_alias(r#type, type_aliases, base_alias, prop_name);
if let Some(const_val) = prop_schema.obj.const_.as_ref() {
let sql_val = Self::quote_literal(const_val);
where_clauses.push(format!("{}.{} = {}", filter_alias, prop_name, sql_val));
}
if let Some(enum_vals) = prop_schema.obj.enum_.as_ref() {
if !enum_vals.is_empty() {
let sql_vals: Vec<String> = enum_vals
.iter()
.map(|v| Self::quote_literal(v))
.collect();
where_clauses.push(format!(
"{}.{} IN ({})",
filter_alias,
prop_name,
sql_vals.join(", ")
));
}
}
}
}
}
fn quote_literal(val: &Value) -> String {
match val {
Value::Null => "NULL".to_string(),
Value::Bool(b) => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
Value::Number(n) => {
if let Some(f) = n.as_f64() {
if f.fract() == 0.0 {
return f.trunc().to_string();
}
}
n.to_string()
}
Value::String(s) => {
if s.is_empty() {
"NULL".to_string()
} else {
format!("'{}'", s.replace('\'', "''"))
}
}
_ => format!(
"'{}'",
serde_json::to_string(val).unwrap().replace('\'', "''")
),
}
}
}

View File

@ -1,6 +1,6 @@
use crate::database::Database;
use crate::drop::{Drop, Error, ErrorDetails};
use std::collections::HashMap;
use indexmap::IndexMap;
use std::sync::Arc;
pub mod compiler;
@ -33,7 +33,7 @@ impl Queryer {
Err(msg) => {
return Drop::with_errors(vec![Error {
code: "FILTER_PARSE_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("error".to_string(), msg.clone()),
])),
details: ErrorDetails {
@ -140,7 +140,7 @@ impl Queryer {
}
Err(e) => Err(Drop::with_errors(vec![Error {
code: "QUERY_COMPILATION_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("error".to_string(), e.clone()),
])),
details: ErrorDetails {
@ -169,7 +169,7 @@ impl Queryer {
}
Ok(other) => Drop::with_errors(vec![Error {
code: "QUERY_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("error".to_string(), format!("Expected array from generic query, got: {:?}", other)),
])),
details: ErrorDetails {
@ -181,7 +181,7 @@ impl Queryer {
}]),
Err(e) => Drop::with_errors(vec![Error {
code: "QUERY_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("error".to_string(), e.to_string()),
])),
details: ErrorDetails {

View File

@ -1301,6 +1301,12 @@ fn test_queryer_0_15() {
crate::tests::runner::run_test_case(&path, 0, 15).unwrap();
}
#[test]
fn test_queryer_1_0() {
let path = format!("{}/fixtures/queryer.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 1, 0).unwrap();
}
#[test]
fn test_polymorphism_0_0() {
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));

View File

@ -180,6 +180,9 @@ impl SqlFormatter {
fn format_query(&mut self, query: &Query) {
self.format_set_expr(&query.body);
if let Some(limit_clause) = &query.limit_clause {
self.push_line(&format!("{}", limit_clause.to_string().trim()));
}
}
fn format_set_expr(&mut self, set_expr: &SetExpr) {
@ -268,7 +271,8 @@ impl SqlFormatter {
match &join.join_operator {
JoinOperator::Inner(JoinConstraint::On(expr))
| JoinOperator::Left(JoinConstraint::On(expr))
| JoinOperator::Right(JoinConstraint::On(expr)) => {
| JoinOperator::Right(JoinConstraint::On(expr))
| JoinOperator::Join(JoinConstraint::On(expr)) => {
self.push_str(" ON ");
self.format_expr(expr);
}

View File

@ -259,3 +259,25 @@ pub fn update_sql_fixture(path: &str, suite_idx: usize, case_idx: usize, queries
let formatted_json = serde_json::to_string_pretty(&file_data).unwrap();
fs::write(path, formatted_json).unwrap();
}
pub fn update_schemas_fixture(path: &str, suite_idx: usize, case_idx: usize, db: &crate::database::Database) {
let content = fs::read_to_string(path).unwrap();
let mut file_data: Value = serde_json::from_str(&content).unwrap();
if let Some(expect) = file_data[suite_idx]["tests"][case_idx].get_mut("expect") {
if let Some(schemas_map) = expect.get_mut("schemas").and_then(|v| v.as_object_mut()) {
for (key, expected_val) in schemas_map {
if expected_val.is_object() && expected_val.as_object().unwrap().is_empty() {
continue;
}
if let Some(actual_ast) = db.schemas.get(key) {
let actual_val = serde_json::to_value(actual_ast).unwrap();
*expected_val = actual_val;
}
}
}
}
let formatted_json = serde_json::to_string_pretty(&file_data).unwrap();
fs::write(path, formatted_json).unwrap();
}

View File

@ -57,6 +57,9 @@ impl Case {
if env::var("UPDATE_EXPECT").is_ok() {
update_validation_fixture(path, suite_idx, case_idx, &result.errors);
if let Ok(db) = db_res {
crate::tests::runner::update_schemas_fixture(path, suite_idx, case_idx, db);
}
}
expect.assert_drop(&result)?;

View File

@ -1,9 +1,9 @@
use std::collections::HashMap;
use indexmap::IndexMap;
#[derive(Debug, Clone, serde::Serialize)]
pub struct ValidationError {
pub code: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub values: Option<HashMap<String, String>>,
pub values: Option<IndexMap<String, String>>,
pub path: String,
}

View File

@ -1,4 +1,5 @@
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
use indexmap::IndexMap;
pub mod context;
pub mod error;
@ -92,7 +93,7 @@ impl Validator {
} else {
Drop::with_errors(vec![Error {
code: "SCHEMA_NOT_FOUND".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("schema".to_string(), schema_id.to_string()),
])),
details: ErrorDetails {

View File

@ -1,4 +1,5 @@
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
use indexmap::IndexMap;
use serde_json::Value;
@ -18,7 +19,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "MIN_ITEMS_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("count".to_string(), arr.len().to_string()),
("limit".to_string(), min.to_string()),
])),
@ -30,7 +31,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "MAX_ITEMS_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("count".to_string(), arr.len().to_string()),
("limit".to_string(), max.to_string()),
])),
@ -77,7 +78,7 @@ impl<'a> ValidationContext<'a> {
if _match_count < min {
result.errors.push(ValidationError {
code: "CONTAINS_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("count".to_string(), _match_count.to_string()),
("limit".to_string(), min.to_string()),
])),
@ -89,7 +90,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "CONTAINS_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("count".to_string(), _match_count.to_string()),
("limit".to_string(), max.to_string()),
])),

View File

@ -1,4 +1,4 @@
use std::collections::HashMap;
use indexmap::IndexMap;
use crate::validator::Validator;
use crate::validator::context::ValidationContext;
@ -19,7 +19,7 @@ impl<'a> ValidationContext<'a> {
if !Validator::check_type(t, current) {
result.errors.push(ValidationError {
code: "INVALID_TYPE".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("expected".to_string(), t.to_string()),
])),
path: self.path.to_string(),
@ -37,7 +37,7 @@ impl<'a> ValidationContext<'a> {
if !valid {
result.errors.push(ValidationError {
code: "INVALID_TYPE".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("expected".to_string(), format!("{:?}", types)),
])),
path: self.path.to_string(),
@ -51,7 +51,7 @@ impl<'a> ValidationContext<'a> {
if !equals(current, const_val) {
result.errors.push(ValidationError {
code: "CONST_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("expected".to_string(), format!("{:?}", const_val)),
])),
path: self.path.to_string(),
@ -74,7 +74,7 @@ impl<'a> ValidationContext<'a> {
if !found {
result.errors.push(ValidationError {
code: "ENUM_MISMATCH".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("expected".to_string(), format!("{:?}", enum_vals)),
])),
path: self.path.to_string(),

View File

@ -1,4 +1,4 @@
use std::collections::HashMap;
use indexmap::IndexMap;
use crate::database::object::{is_primitive_type, SchemaTypeOrArray};
use crate::database::schema::Schema;
@ -75,7 +75,7 @@ impl<'a> ValidationContext<'a> {
if !result.evaluated_keys.contains(key) && !self.overrides.contains(key) {
result.errors.push(ValidationError {
code: "STRICT_PROPERTY_VIOLATION".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("property_name".to_string(), key.to_string()),
])),
path: self.join_path(key),

View File

@ -1,4 +1,4 @@
use std::collections::HashMap;
use indexmap::IndexMap;
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
@ -21,7 +21,7 @@ impl<'a> ValidationContext<'a> {
if should && let Err(e) = f(current) {
result.errors.push(ValidationError {
code: "FORMAT_MISMATCH".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("format".to_string(), self.schema.format.clone().unwrap_or_default()),
("error".to_string(), e.to_string()),
])),
@ -35,7 +35,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "FORMAT_MISMATCH".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("format".to_string(), self.schema.format.clone().unwrap_or_default()),
])),
path: self.path.to_string(),

View File

@ -1,7 +1,7 @@
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
use std::collections::HashMap;
use indexmap::IndexMap;
pub mod array;
pub mod cases;
@ -64,7 +64,7 @@ impl<'a> ValidationContext<'a> {
if self.depth > 100 {
Err(ValidationError {
code: "RECURSION_LIMIT_EXCEEDED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("limit".to_string(), 100.to_string()),
])),
path: self.path.to_string(),

View File

@ -1,4 +1,4 @@
use std::collections::HashMap;
use indexmap::IndexMap;
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
@ -16,7 +16,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "MINIMUM_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("value".to_string(), num.to_string()),
("limit".to_string(), min.to_string()),
])),
@ -28,7 +28,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "MAXIMUM_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("value".to_string(), num.to_string()),
("limit".to_string(), max.to_string()),
])),
@ -40,7 +40,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "EXCLUSIVE_MINIMUM_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("value".to_string(), num.to_string()),
("limit".to_string(), ex_min.to_string()),
])),
@ -52,7 +52,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "EXCLUSIVE_MAXIMUM_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("value".to_string(), num.to_string()),
("limit".to_string(), ex_max.to_string()),
])),
@ -64,7 +64,7 @@ impl<'a> ValidationContext<'a> {
if (val - val.round()).abs() > f64::EPSILON {
result.errors.push(ValidationError {
code: "MULTIPLE_OF_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("value".to_string(), num.to_string()),
("multiple_of".to_string(), multiple_of.to_string()),
])),

View File

@ -1,4 +1,5 @@
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
use indexmap::IndexMap;
use serde_json::Value;
@ -38,7 +39,7 @@ impl<'a> ValidationContext<'a> {
} else {
result.errors.push(ValidationError {
code: "CONST_VIOLATED".to_string(), // Aligning with original const override errors natively
values: Some(HashMap::from([
values: Some(IndexMap::from([
("value".to_string(), type_str.to_string()),
])),
path: self.join_path("type"),
@ -49,7 +50,7 @@ impl<'a> ValidationContext<'a> {
// Because it's a global entity target, the payload must structurally provide a discriminator natively
result.errors.push(ValidationError {
code: "MISSING_TYPE".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("expected".to_string(), expected_type.to_string()),
])),
path: self.path.clone(), // Empty boundary
@ -104,7 +105,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "MIN_PROPERTIES_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("count".to_string(), obj.len().to_string()),
("limit".to_string(), min.to_string()),
])),
@ -117,7 +118,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "MAX_PROPERTIES_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("count".to_string(), obj.len().to_string()),
("limit".to_string(), max.to_string()),
])),
@ -131,7 +132,7 @@ impl<'a> ValidationContext<'a> {
if field == "type" {
result.errors.push(ValidationError {
code: "MISSING_TYPE".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("field_name".to_string(), "type".to_string()),
])),
path: self.join_path(field),
@ -139,7 +140,7 @@ impl<'a> ValidationContext<'a> {
} else {
result.errors.push(ValidationError {
code: "REQUIRED_FIELD_MISSING".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("field_name".to_string(), field.to_string()),
])),
path: self.join_path(field),
@ -158,7 +159,7 @@ impl<'a> ValidationContext<'a> {
if !obj.contains_key(req_prop) {
result.errors.push(ValidationError {
code: "DEPENDENCY_MISSING".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("property_name".to_string(), prop.to_string()),
("required_property".to_string(), req_prop.to_string()),
])),

View File

@ -1,5 +1,3 @@
use std::collections::HashMap;
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
@ -55,10 +53,10 @@ impl<'a> ValidationContext<'a> {
return self.execute_polymorph(options, result);
} else {
result.errors.push(ValidationError {
code: "UNCOMPILED_ONEOF".to_string(),
values: None,
path: self.path.to_string(),
});
code: "UNCOMPILED_ONEOF".to_string(),
values: None,
path: self.path.to_string(),
});
return Ok(false);
}
}
@ -110,9 +108,10 @@ impl<'a> ValidationContext<'a> {
} else {
result.errors.push(ValidationError {
code: "MISSING_COMPILED_SCHEMA".to_string(),
values: Some(HashMap::from([
("target_id".to_string(), target_id.to_string()),
])),
values: Some(IndexMap::from([(
"target_id".to_string(),
target_id.to_string(),
)])),
path: self.path.to_string(),
});
return Ok(false);
@ -132,9 +131,7 @@ impl<'a> ValidationContext<'a> {
} else {
result.errors.push(ValidationError {
code: "MISSING_COMPILED_SCHEMA".to_string(),
values: Some(HashMap::from([
("index".to_string(), idx.to_string()),
])),
values: Some(IndexMap::from([("index".to_string(), idx.to_string())])),
path: self.path.to_string(),
});
return Ok(false);
@ -144,14 +141,12 @@ impl<'a> ValidationContext<'a> {
}
} else {
let values = if let Some(d) = self.schema.compiled_discriminator.get() {
Some(HashMap::from([
Some(IndexMap::from([
("discriminator".to_string(), d.to_string()),
("value".to_string(), val.to_string()),
]))
} else {
Some(HashMap::from([
("primitive".to_string(), val.to_string()),
]))
Some(IndexMap::from([("primitive".to_string(), val.to_string())]))
};
result.errors.push(ValidationError {
code: if self.schema.family.is_some() {
@ -168,9 +163,10 @@ impl<'a> ValidationContext<'a> {
if let Some(d) = self.schema.compiled_discriminator.get() {
result.errors.push(ValidationError {
code: "MISSING_TYPE".to_string(),
values: Some(HashMap::from([
("discriminator".to_string(), d.to_string()),
])),
values: Some(IndexMap::from([(
"discriminator".to_string(),
d.to_string(),
)])),
path: self.path.to_string(),
});
}

View File

@ -1,4 +1,4 @@
use std::collections::HashMap;
use indexmap::IndexMap;
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
@ -17,7 +17,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "MIN_LENGTH_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("count".to_string(), s.chars().count().to_string()),
("limit".to_string(), min.to_string()),
])),
@ -29,7 +29,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "MAX_LENGTH_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("count".to_string(), s.chars().count().to_string()),
("limit".to_string(), max.to_string()),
])),
@ -40,7 +40,7 @@ impl<'a> ValidationContext<'a> {
if !compiled_re.0.is_match(s) {
result.errors.push(ValidationError {
code: "PATTERN_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("pattern".to_string(), self.schema.pattern.clone().unwrap_or_default()),
("value".to_string(), s.to_string()),
])),
@ -53,7 +53,7 @@ impl<'a> ValidationContext<'a> {
{
result.errors.push(ValidationError {
code: "PATTERN_VIOLATED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("pattern".to_string(), pattern.to_string()),
("value".to_string(), s.to_string()),
])),

View File

@ -2,7 +2,7 @@ use crate::database::object::{is_primitive_type, SchemaTypeOrArray};
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
use std::collections::HashMap;
use indexmap::IndexMap;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_type(
@ -76,7 +76,7 @@ impl<'a> ValidationContext<'a> {
if !resolved {
result.errors.push(ValidationError {
code: "DYNAMIC_TYPE_RESOLUTION_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("pointer".to_string(), t.clone()),
("discriminator".to_string(), var_name.to_string()),
])),
@ -109,7 +109,7 @@ impl<'a> ValidationContext<'a> {
if t.starts_with('$') {
result.errors.push(ValidationError {
code: "DYNAMIC_TYPE_RESOLUTION_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("pointer".to_string(), target_id.to_string()),
])),
path: self.path.to_string(),
@ -117,7 +117,7 @@ impl<'a> ValidationContext<'a> {
} else if self.schema.is_proxy() {
result.errors.push(ValidationError {
code: "PROXY_TYPE_RESOLUTION_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("pointer".to_string(), target_id.to_string()),
])),
path: self.path.to_string(),
@ -125,7 +125,7 @@ impl<'a> ValidationContext<'a> {
} else {
result.errors.push(ValidationError {
code: "INHERITANCE_RESOLUTION_FAILED".to_string(),
values: Some(HashMap::from([
values: Some(IndexMap::from([
("pointer".to_string(), target_id.to_string()),
])),
path: self.path.to_string(),

View File

@ -1 +1 @@
1.0.171
1.0.181