Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd85a8a2c3 | |||
| d3cb72a5e2 | |||
| 57baa389b6 | |||
| 8ceb4f05a2 | |||
| a3bd79deef |
@ -664,5 +664,268 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "JSONB boundaries",
|
||||||
|
"database": {
|
||||||
|
"relations": [
|
||||||
|
{
|
||||||
|
"id": "33333333-3333-3333-3333-333333333333",
|
||||||
|
"type": "relation",
|
||||||
|
"constraint": "fk_invoice_line_invoice",
|
||||||
|
"source_type": "invoice_line",
|
||||||
|
"source_columns": [
|
||||||
|
"invoice_id"
|
||||||
|
],
|
||||||
|
"destination_type": "invoice",
|
||||||
|
"destination_columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"name": "entity",
|
||||||
|
"hierarchy": [
|
||||||
|
"entity"
|
||||||
|
],
|
||||||
|
"grouped_fields": {
|
||||||
|
"entity": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"archived",
|
||||||
|
"created_at"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"field_types": {
|
||||||
|
"id": "uuid",
|
||||||
|
"archived": "boolean",
|
||||||
|
"created_at": "timestamptz",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"schemas": {
|
||||||
|
"entity": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"archived": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fields": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"archived",
|
||||||
|
"created_at"
|
||||||
|
],
|
||||||
|
"variations": [
|
||||||
|
"entity",
|
||||||
|
"invoice",
|
||||||
|
"invoice_line"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "invoice",
|
||||||
|
"schemas": {
|
||||||
|
"invoice": {
|
||||||
|
"type": "entity",
|
||||||
|
"properties": {
|
||||||
|
"total": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"lines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_line": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
},
|
||||||
|
"metadata_lines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_nested_line": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"line": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_nested_lines": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"lines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hierarchy": [
|
||||||
|
"invoice",
|
||||||
|
"entity"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"total",
|
||||||
|
"metadata_line",
|
||||||
|
"metadata_lines",
|
||||||
|
"metadata_nested_line",
|
||||||
|
"metadata_nested_lines",
|
||||||
|
"created_at",
|
||||||
|
"created_by",
|
||||||
|
"modified_at",
|
||||||
|
"modified_by",
|
||||||
|
"archived"
|
||||||
|
],
|
||||||
|
"grouped_fields": {
|
||||||
|
"invoice": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"total",
|
||||||
|
"metadata_line",
|
||||||
|
"metadata_lines",
|
||||||
|
"metadata_nested_line",
|
||||||
|
"metadata_nested_lines"
|
||||||
|
],
|
||||||
|
"entity": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"created_at",
|
||||||
|
"created_by",
|
||||||
|
"modified_at",
|
||||||
|
"modified_by",
|
||||||
|
"archived"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lookup_fields": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"historical": true,
|
||||||
|
"relationship": false,
|
||||||
|
"field_types": {
|
||||||
|
"id": "uuid",
|
||||||
|
"type": "text",
|
||||||
|
"archived": "boolean",
|
||||||
|
"total": "numeric",
|
||||||
|
"metadata_line": "jsonb",
|
||||||
|
"metadata_lines": "jsonb",
|
||||||
|
"metadata_nested_line": "jsonb",
|
||||||
|
"metadata_nested_lines": "jsonb",
|
||||||
|
"created_at": "timestamptz",
|
||||||
|
"created_by": "uuid",
|
||||||
|
"modified_at": "timestamptz",
|
||||||
|
"modified_by": "uuid"
|
||||||
|
},
|
||||||
|
"variations": [
|
||||||
|
"invoice"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "invoice_line",
|
||||||
|
"schemas": {
|
||||||
|
"invoice_line": {
|
||||||
|
"type": "entity",
|
||||||
|
"properties": {
|
||||||
|
"invoice_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hierarchy": [
|
||||||
|
"invoice_line",
|
||||||
|
"entity"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"invoice_id",
|
||||||
|
"price",
|
||||||
|
"created_at",
|
||||||
|
"created_by",
|
||||||
|
"modified_at",
|
||||||
|
"modified_by",
|
||||||
|
"archived"
|
||||||
|
],
|
||||||
|
"grouped_fields": {
|
||||||
|
"invoice_line": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"invoice_id",
|
||||||
|
"price"
|
||||||
|
],
|
||||||
|
"entity": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"created_at",
|
||||||
|
"created_by",
|
||||||
|
"modified_at",
|
||||||
|
"modified_by",
|
||||||
|
"archived"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lookup_fields": [],
|
||||||
|
"historical": true,
|
||||||
|
"relationship": false,
|
||||||
|
"field_types": {
|
||||||
|
"id": "uuid",
|
||||||
|
"type": "text",
|
||||||
|
"archived": "boolean",
|
||||||
|
"invoice_id": "uuid",
|
||||||
|
"price": "numeric",
|
||||||
|
"created_at": "timestamptz",
|
||||||
|
"created_by": "uuid",
|
||||||
|
"modified_at": "timestamptz",
|
||||||
|
"modified_by": "uuid"
|
||||||
|
},
|
||||||
|
"variations": [
|
||||||
|
"invoice_line"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"description": "Assert no JSONB paths promoted",
|
||||||
|
"action": "compile",
|
||||||
|
"expect": {
|
||||||
|
"success": true,
|
||||||
|
"schemas": [
|
||||||
|
"entity",
|
||||||
|
"invoice",
|
||||||
|
"invoice_line"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -88,6 +88,33 @@
|
|||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"prefix": null
|
"prefix": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "66666666-6666-6666-6666-666666666666",
|
||||||
|
"type": "relation",
|
||||||
|
"constraint": "fk_entity_organization",
|
||||||
|
"source_type": "entity",
|
||||||
|
"source_columns": [
|
||||||
|
"organization_id"
|
||||||
|
],
|
||||||
|
"destination_type": "organization",
|
||||||
|
"destination_columns": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"prefix": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "33333333-3333-3333-3333-333333333334",
|
||||||
|
"type": "relation",
|
||||||
|
"constraint": "fk_invoice_line_invoice",
|
||||||
|
"source_type": "invoice_line",
|
||||||
|
"source_columns": [
|
||||||
|
"invoice_id"
|
||||||
|
],
|
||||||
|
"destination_type": "invoice",
|
||||||
|
"destination_columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"types": [
|
"types": [
|
||||||
@ -157,7 +184,22 @@
|
|||||||
"lookup_fields": [],
|
"lookup_fields": [],
|
||||||
"historical": true,
|
"historical": true,
|
||||||
"notify": true,
|
"notify": true,
|
||||||
"relationship": false
|
"relationship": false,
|
||||||
|
"variations": [
|
||||||
|
"entity",
|
||||||
|
"organization",
|
||||||
|
"user",
|
||||||
|
"person",
|
||||||
|
"order",
|
||||||
|
"order_line",
|
||||||
|
"relationship",
|
||||||
|
"contact",
|
||||||
|
"phone_number",
|
||||||
|
"email_address",
|
||||||
|
"attachment",
|
||||||
|
"invoice",
|
||||||
|
"invoice_line"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "organization",
|
"name": "organization",
|
||||||
@ -829,6 +871,50 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "invoice",
|
"name": "invoice",
|
||||||
|
"schemas": {
|
||||||
|
"invoice": {
|
||||||
|
"type": "entity",
|
||||||
|
"properties": {
|
||||||
|
"total": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"lines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_line": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
},
|
||||||
|
"metadata_lines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_nested_line": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"line": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_nested_lines": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"lines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"hierarchy": [
|
"hierarchy": [
|
||||||
"invoice",
|
"invoice",
|
||||||
"entity"
|
"entity"
|
||||||
@ -836,8 +922,11 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
"id",
|
"id",
|
||||||
"type",
|
"type",
|
||||||
"number",
|
"total",
|
||||||
"metadata",
|
"metadata_line",
|
||||||
|
"metadata_lines",
|
||||||
|
"metadata_nested_line",
|
||||||
|
"metadata_nested_lines",
|
||||||
"created_at",
|
"created_at",
|
||||||
"created_by",
|
"created_by",
|
||||||
"modified_at",
|
"modified_at",
|
||||||
@ -848,8 +937,11 @@
|
|||||||
"invoice": [
|
"invoice": [
|
||||||
"id",
|
"id",
|
||||||
"type",
|
"type",
|
||||||
"number",
|
"total",
|
||||||
"metadata"
|
"metadata_line",
|
||||||
|
"metadata_lines",
|
||||||
|
"metadata_nested_line",
|
||||||
|
"metadata_nested_lines"
|
||||||
],
|
],
|
||||||
"entity": [
|
"entity": [
|
||||||
"id",
|
"id",
|
||||||
@ -870,43 +962,84 @@
|
|||||||
"id": "uuid",
|
"id": "uuid",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"archived": "boolean",
|
"archived": "boolean",
|
||||||
"number": "text",
|
"total": "numeric",
|
||||||
"metadata": "jsonb",
|
"metadata_line": "jsonb",
|
||||||
|
"metadata_lines": "jsonb",
|
||||||
|
"metadata_nested_line": "jsonb",
|
||||||
|
"metadata_nested_lines": "jsonb",
|
||||||
"created_at": "timestamptz",
|
"created_at": "timestamptz",
|
||||||
"created_by": "uuid",
|
"created_by": "uuid",
|
||||||
"modified_at": "timestamptz",
|
"modified_at": "timestamptz",
|
||||||
"modified_by": "uuid"
|
"modified_by": "uuid"
|
||||||
},
|
},
|
||||||
|
"variations": [
|
||||||
|
"invoice"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "invoice_line",
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"invoice": {
|
"invoice_line": {
|
||||||
"type": "entity",
|
"type": "entity",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"invoice_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"number": {
|
"price": {
|
||||||
"type": "string"
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"metadata": {
|
"hierarchy": [
|
||||||
"type": "object",
|
"invoice_line",
|
||||||
"properties": {
|
"entity"
|
||||||
"internal_note": {
|
],
|
||||||
"type": "string"
|
"fields": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"invoice_id",
|
||||||
|
"price",
|
||||||
|
"created_at",
|
||||||
|
"created_by",
|
||||||
|
"modified_at",
|
||||||
|
"modified_by",
|
||||||
|
"archived"
|
||||||
|
],
|
||||||
|
"grouped_fields": {
|
||||||
|
"invoice_line": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"invoice_id",
|
||||||
|
"price"
|
||||||
|
],
|
||||||
|
"entity": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"created_at",
|
||||||
|
"created_by",
|
||||||
|
"modified_at",
|
||||||
|
"modified_by",
|
||||||
|
"archived"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"customer_snapshot": {
|
"lookup_fields": [],
|
||||||
"type": "entity"
|
"historical": true,
|
||||||
|
"relationship": false,
|
||||||
|
"field_types": {
|
||||||
|
"id": "uuid",
|
||||||
|
"type": "text",
|
||||||
|
"archived": "boolean",
|
||||||
|
"invoice_id": "uuid",
|
||||||
|
"price": "numeric",
|
||||||
|
"created_at": "timestamptz",
|
||||||
|
"created_by": "uuid",
|
||||||
|
"modified_at": "timestamptz",
|
||||||
|
"modified_by": "uuid"
|
||||||
},
|
},
|
||||||
"related_rules": {
|
"variations": [
|
||||||
"type": "array",
|
"invoice_line"
|
||||||
"items": {
|
]
|
||||||
"type": "entity"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2885,27 +3018,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Insert invoice with deep jsonb metadata",
|
"description": "Insert invoice with JSONB metadata",
|
||||||
"action": "merge",
|
"action": "merge",
|
||||||
"schema_id": "invoice",
|
"schema_id": "invoice",
|
||||||
"data": {
|
"data": {
|
||||||
"id": "11111111-2222-3333-4444-555555555555",
|
"id": "11111111-2222-3333-4444-555555555555",
|
||||||
"type": "invoice",
|
"type": "invoice",
|
||||||
"number": "INV-1001",
|
"number": "INV-1001",
|
||||||
"metadata": {
|
"total": 200.0,
|
||||||
"internal_note": "Confidential",
|
"metadata_line": {"price": 50},
|
||||||
"customer_snapshot": {
|
"metadata_lines": [{"price": 25}],
|
||||||
"id": "00000000-0000-0000-0000-000000000000",
|
"metadata_nested_line": {"line": {"price": 75}},
|
||||||
"type": "person",
|
"metadata_nested_lines": {"lines": [{"price": 100}]}
|
||||||
"first_name": "John"
|
|
||||||
},
|
|
||||||
"related_rules": [
|
|
||||||
{
|
|
||||||
"id": "11111111-1111-1111-1111-111111111111",
|
|
||||||
"type": "entity"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"expect": {
|
"expect": {
|
||||||
"success": true,
|
"success": true,
|
||||||
@ -2937,27 +3061,20 @@
|
|||||||
[
|
[
|
||||||
"INSERT INTO agreego.\"invoice\" (",
|
"INSERT INTO agreego.\"invoice\" (",
|
||||||
" \"id\",",
|
" \"id\",",
|
||||||
" \"metadata\",",
|
" \"metadata_line\",",
|
||||||
" \"number\",",
|
" \"metadata_lines\",",
|
||||||
|
" \"metadata_nested_line\",",
|
||||||
|
" \"metadata_nested_lines\",",
|
||||||
|
" \"total\",",
|
||||||
" \"type\"",
|
" \"type\"",
|
||||||
")",
|
")",
|
||||||
"VALUES (",
|
"VALUES (",
|
||||||
" '11111111-2222-3333-4444-555555555555',",
|
" '11111111-2222-3333-4444-555555555555',",
|
||||||
" '{",
|
" '{\"price\":50}',",
|
||||||
" \"customer_snapshot\":{",
|
" '[{\"price\":25}]',",
|
||||||
" \"first_name\":\"John\",",
|
" '{\"line\":{\"price\":75}}',",
|
||||||
" \"id\":\"00000000-0000-0000-0000-000000000000\",",
|
" '{\"lines\":[{\"price\":100}]}',",
|
||||||
" \"type\":\"person\"",
|
" 200,",
|
||||||
" },",
|
|
||||||
" \"internal_note\":\"Confidential\",",
|
|
||||||
" \"related_rules\":[",
|
|
||||||
" {",
|
|
||||||
" \"id\":\"11111111-1111-1111-1111-111111111111\",",
|
|
||||||
" \"type\":\"entity\"",
|
|
||||||
" }",
|
|
||||||
" ]",
|
|
||||||
" }',",
|
|
||||||
" 'INV-1001',",
|
|
||||||
" 'invoice'",
|
" 'invoice'",
|
||||||
")"
|
")"
|
||||||
],
|
],
|
||||||
@ -2974,21 +3091,11 @@
|
|||||||
"VALUES (",
|
"VALUES (",
|
||||||
" NULL,",
|
" NULL,",
|
||||||
" '{",
|
" '{",
|
||||||
" \"metadata\":{",
|
" \"metadata_line\":{\"price\":50},",
|
||||||
" \"customer_snapshot\":{",
|
" \"metadata_lines\":[{\"price\":25}],",
|
||||||
" \"first_name\":\"John\",",
|
" \"metadata_nested_line\":{\"line\":{\"price\":75}},",
|
||||||
" \"id\":\"00000000-0000-0000-0000-000000000000\",",
|
" \"metadata_nested_lines\":{\"lines\":[{\"price\":100}]},",
|
||||||
" \"type\":\"person\"",
|
" \"total\":200.0,",
|
||||||
" },",
|
|
||||||
" \"internal_note\":\"Confidential\",",
|
|
||||||
" \"related_rules\":[",
|
|
||||||
" {",
|
|
||||||
" \"id\":\"11111111-1111-1111-1111-111111111111\",",
|
|
||||||
" \"type\":\"entity\"",
|
|
||||||
" }",
|
|
||||||
" ]",
|
|
||||||
" },",
|
|
||||||
" \"number\":\"INV-1001\",",
|
|
||||||
" \"type\":\"invoice\"",
|
" \"type\":\"invoice\"",
|
||||||
" }',",
|
" }',",
|
||||||
" '11111111-2222-3333-4444-555555555555',",
|
" '11111111-2222-3333-4444-555555555555',",
|
||||||
|
|||||||
@ -636,5 +636,110 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "STI Projections (Lacking Kind Discriminator Definitions)",
|
||||||
|
"database": {
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"name": "widget",
|
||||||
|
"variations": [
|
||||||
|
"widget"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"widget": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stock.widget": {
|
||||||
|
"type": "widget",
|
||||||
|
"properties": {
|
||||||
|
"kind": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"projected.widget": {
|
||||||
|
"type": "widget",
|
||||||
|
"properties": {
|
||||||
|
"alias": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"stock_widget_validation": {
|
||||||
|
"type": "stock.widget"
|
||||||
|
},
|
||||||
|
"projected_widget_validation": {
|
||||||
|
"type": "projected.widget"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"description": "stock.widget securely expects kind when configured",
|
||||||
|
"schema_id": "stock_widget_validation",
|
||||||
|
"data": {
|
||||||
|
"type": "widget",
|
||||||
|
"amount": 5
|
||||||
|
},
|
||||||
|
"action": "validate",
|
||||||
|
"expect": {
|
||||||
|
"success": false,
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": "MISSING_KIND",
|
||||||
|
"details": {
|
||||||
|
"path": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "projected.widget seamlessly bypasses kind expectation when excluded from schema",
|
||||||
|
"schema_id": "projected_widget_validation",
|
||||||
|
"data": {
|
||||||
|
"type": "widget",
|
||||||
|
"alias": "Test Projection"
|
||||||
|
},
|
||||||
|
"action": "validate",
|
||||||
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "projected.widget securely fails if user erroneously provides extra kind property",
|
||||||
|
"schema_id": "projected_widget_validation",
|
||||||
|
"data": {
|
||||||
|
"type": "widget",
|
||||||
|
"alias": "Test Projection",
|
||||||
|
"kind": "projected"
|
||||||
|
},
|
||||||
|
"action": "validate",
|
||||||
|
"expect": {
|
||||||
|
"success": false,
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": "STRICT_PROPERTY_VIOLATION",
|
||||||
|
"details": {
|
||||||
|
"path": "kind"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -137,6 +137,19 @@
|
|||||||
"destination_columns": [
|
"destination_columns": [
|
||||||
"id"
|
"id"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "33333333-3333-3333-3333-333333333334",
|
||||||
|
"type": "relation",
|
||||||
|
"constraint": "fk_invoice_line_invoice",
|
||||||
|
"source_type": "invoice_line",
|
||||||
|
"source_columns": [
|
||||||
|
"invoice_id"
|
||||||
|
],
|
||||||
|
"destination_type": "invoice",
|
||||||
|
"destination_columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"types": [
|
"types": [
|
||||||
@ -754,9 +767,6 @@
|
|||||||
"total",
|
"total",
|
||||||
"customer_id",
|
"customer_id",
|
||||||
"created_at",
|
"created_at",
|
||||||
"created_by",
|
|
||||||
"modified_at",
|
|
||||||
"modified_by",
|
|
||||||
"archived",
|
"archived",
|
||||||
"counterparty_id",
|
"counterparty_id",
|
||||||
"counterparty_type"
|
"counterparty_type"
|
||||||
@ -774,9 +784,6 @@
|
|||||||
"id",
|
"id",
|
||||||
"type",
|
"type",
|
||||||
"created_at",
|
"created_at",
|
||||||
"created_by",
|
|
||||||
"modified_at",
|
|
||||||
"modified_by",
|
|
||||||
"archived"
|
"archived"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -831,9 +838,6 @@
|
|||||||
"product",
|
"product",
|
||||||
"price",
|
"price",
|
||||||
"created_at",
|
"created_at",
|
||||||
"created_by",
|
|
||||||
"modified_at",
|
|
||||||
"modified_by",
|
|
||||||
"archived"
|
"archived"
|
||||||
],
|
],
|
||||||
"grouped_fields": {
|
"grouped_fields": {
|
||||||
@ -848,9 +852,6 @@
|
|||||||
"id",
|
"id",
|
||||||
"type",
|
"type",
|
||||||
"created_at",
|
"created_at",
|
||||||
"created_by",
|
|
||||||
"modified_at",
|
|
||||||
"modified_by",
|
|
||||||
"archived"
|
"archived"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -864,10 +865,7 @@
|
|||||||
"order_id": "uuid",
|
"order_id": "uuid",
|
||||||
"product": "text",
|
"product": "text",
|
||||||
"price": "numeric",
|
"price": "numeric",
|
||||||
"created_at": "timestamptz",
|
"created_at": "timestamptz"
|
||||||
"created_by": "uuid",
|
|
||||||
"modified_at": "timestamptz",
|
|
||||||
"modified_by": "uuid"
|
|
||||||
},
|
},
|
||||||
"variations": [
|
"variations": [
|
||||||
"order_line"
|
"order_line"
|
||||||
@ -928,6 +926,50 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "invoice",
|
"name": "invoice",
|
||||||
|
"schemas": {
|
||||||
|
"invoice": {
|
||||||
|
"type": "entity",
|
||||||
|
"properties": {
|
||||||
|
"total": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"lines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_line": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
},
|
||||||
|
"metadata_lines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_nested_line": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"line": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_nested_lines": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"lines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"hierarchy": [
|
"hierarchy": [
|
||||||
"invoice",
|
"invoice",
|
||||||
"entity"
|
"entity"
|
||||||
@ -935,28 +977,28 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
"id",
|
"id",
|
||||||
"type",
|
"type",
|
||||||
"number",
|
"total",
|
||||||
"metadata",
|
"metadata_line",
|
||||||
|
"metadata_lines",
|
||||||
|
"metadata_nested_line",
|
||||||
|
"metadata_nested_lines",
|
||||||
"created_at",
|
"created_at",
|
||||||
"created_by",
|
|
||||||
"modified_at",
|
|
||||||
"modified_by",
|
|
||||||
"archived"
|
"archived"
|
||||||
],
|
],
|
||||||
"grouped_fields": {
|
"grouped_fields": {
|
||||||
"invoice": [
|
"invoice": [
|
||||||
"id",
|
"id",
|
||||||
"type",
|
"type",
|
||||||
"number",
|
"total",
|
||||||
"metadata"
|
"metadata_line",
|
||||||
|
"metadata_lines",
|
||||||
|
"metadata_nested_line",
|
||||||
|
"metadata_nested_lines"
|
||||||
],
|
],
|
||||||
"entity": [
|
"entity": [
|
||||||
"id",
|
"id",
|
||||||
"type",
|
"type",
|
||||||
"created_at",
|
"created_at",
|
||||||
"created_by",
|
|
||||||
"modified_at",
|
|
||||||
"modified_by",
|
|
||||||
"archived"
|
"archived"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -969,8 +1011,11 @@
|
|||||||
"id": "uuid",
|
"id": "uuid",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"archived": "boolean",
|
"archived": "boolean",
|
||||||
"number": "text",
|
"total": "numeric",
|
||||||
"metadata": "jsonb",
|
"metadata_line": "jsonb",
|
||||||
|
"metadata_lines": "jsonb",
|
||||||
|
"metadata_nested_line": "jsonb",
|
||||||
|
"metadata_nested_lines": "jsonb",
|
||||||
"created_at": "timestamptz",
|
"created_at": "timestamptz",
|
||||||
"created_by": "uuid",
|
"created_by": "uuid",
|
||||||
"modified_at": "timestamptz",
|
"modified_at": "timestamptz",
|
||||||
@ -978,37 +1023,66 @@
|
|||||||
},
|
},
|
||||||
"variations": [
|
"variations": [
|
||||||
"invoice"
|
"invoice"
|
||||||
],
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "invoice_line",
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"invoice": {
|
"invoice_line": {
|
||||||
"type": "entity",
|
"type": "entity",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"invoice_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"number": {
|
"price": {
|
||||||
"type": "string"
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"metadata": {
|
"hierarchy": [
|
||||||
"type": "object",
|
"invoice_line",
|
||||||
"properties": {
|
"entity"
|
||||||
"internal_note": {
|
],
|
||||||
"type": "string"
|
"fields": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"invoice_id",
|
||||||
|
"price",
|
||||||
|
"created_at",
|
||||||
|
"archived"
|
||||||
|
],
|
||||||
|
"grouped_fields": {
|
||||||
|
"invoice_line": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"invoice_id",
|
||||||
|
"price"
|
||||||
|
],
|
||||||
|
"entity": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"created_at",
|
||||||
|
"created_by",
|
||||||
|
"modified_at",
|
||||||
|
"modified_by",
|
||||||
|
"archived"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"customer_snapshot": {
|
"lookup_fields": [],
|
||||||
"type": "entity"
|
"historical": true,
|
||||||
|
"relationship": false,
|
||||||
|
"field_types": {
|
||||||
|
"id": "uuid",
|
||||||
|
"type": "text",
|
||||||
|
"archived": "boolean",
|
||||||
|
"invoice_id": "uuid",
|
||||||
|
"price": "numeric",
|
||||||
|
"created_at": "timestamptz"
|
||||||
},
|
},
|
||||||
"related_rules": {
|
"variations": [
|
||||||
"type": "array",
|
"invoice_line"
|
||||||
"items": {
|
]
|
||||||
"type": "entity"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2037,7 +2111,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Query invoice with complex JSONB metadata field extraction",
|
"description": "Query invoice with JSONB metadata fields",
|
||||||
"action": "query",
|
"action": "query",
|
||||||
"schema_id": "invoice",
|
"schema_id": "invoice",
|
||||||
"expect": {
|
"expect": {
|
||||||
@ -2048,8 +2122,25 @@
|
|||||||
" 'archived', entity_2.archived,",
|
" 'archived', entity_2.archived,",
|
||||||
" 'created_at', entity_2.created_at,",
|
" 'created_at', entity_2.created_at,",
|
||||||
" 'id', entity_2.id,",
|
" 'id', entity_2.id,",
|
||||||
" 'metadata', invoice_1.metadata,",
|
" 'lines',",
|
||||||
" 'number', invoice_1.number,",
|
" (SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
||||||
|
" 'archived', entity_4.archived,",
|
||||||
|
" 'created_at', entity_4.created_at,",
|
||||||
|
" 'id', entity_4.id,",
|
||||||
|
" 'invoice_id', invoice_line_3.invoice_id,",
|
||||||
|
" 'price', invoice_line_3.price,",
|
||||||
|
" 'type', entity_4.type",
|
||||||
|
" )), '[]'::jsonb)",
|
||||||
|
" FROM agreego.invoice_line invoice_line_3",
|
||||||
|
" JOIN agreego.entity entity_4 ON entity_4.id = invoice_line_3.id",
|
||||||
|
" WHERE",
|
||||||
|
" NOT entity_4.archived",
|
||||||
|
" AND invoice_line_3.invoice_id = invoice_1.id),",
|
||||||
|
" 'metadata_line', invoice_1.metadata_line,",
|
||||||
|
" 'metadata_lines', invoice_1.metadata_lines,",
|
||||||
|
" 'metadata_nested_line', invoice_1.metadata_nested_line,",
|
||||||
|
" 'metadata_nested_lines', invoice_1.metadata_nested_lines,",
|
||||||
|
" 'total', invoice_1.total,",
|
||||||
" 'type', entity_2.type",
|
" 'type', entity_2.type",
|
||||||
")",
|
")",
|
||||||
"FROM agreego.invoice invoice_1",
|
"FROM agreego.invoice invoice_1",
|
||||||
|
|||||||
@ -2,9 +2,9 @@ pub mod edge;
|
|||||||
pub mod r#enum;
|
pub mod r#enum;
|
||||||
pub mod executors;
|
pub mod executors;
|
||||||
pub mod formats;
|
pub mod formats;
|
||||||
|
pub mod object;
|
||||||
pub mod page;
|
pub mod page;
|
||||||
pub mod punc;
|
pub mod punc;
|
||||||
pub mod object;
|
|
||||||
pub mod relation;
|
pub mod relation;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
pub mod r#type;
|
pub mod r#type;
|
||||||
@ -60,7 +60,10 @@ impl Database {
|
|||||||
db.enums.insert(def.name.clone(), def);
|
db.enums.insert(def.name.clone(), def);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let name = item.get("name").and_then(|v| v.as_str()).unwrap_or("unknown");
|
let name = item
|
||||||
|
.get("name")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("unknown");
|
||||||
errors.push(crate::drop::Error {
|
errors.push(crate::drop::Error {
|
||||||
code: "DATABASE_ENUM_PARSE_FAILED".to_string(),
|
code: "DATABASE_ENUM_PARSE_FAILED".to_string(),
|
||||||
message: format!("Failed to parse database enum '{}': {}", name, e),
|
message: format!("Failed to parse database enum '{}': {}", name, e),
|
||||||
@ -81,7 +84,10 @@ impl Database {
|
|||||||
db.types.insert(def.name.clone(), def);
|
db.types.insert(def.name.clone(), def);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let name = item.get("name").and_then(|v| v.as_str()).unwrap_or("unknown");
|
let name = item
|
||||||
|
.get("name")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("unknown");
|
||||||
errors.push(crate::drop::Error {
|
errors.push(crate::drop::Error {
|
||||||
code: "DATABASE_TYPE_PARSE_FAILED".to_string(),
|
code: "DATABASE_TYPE_PARSE_FAILED".to_string(),
|
||||||
message: format!("Failed to parse database type '{}': {}", name, e),
|
message: format!("Failed to parse database type '{}': {}", name, e),
|
||||||
@ -106,7 +112,10 @@ impl Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let constraint = item.get("constraint").and_then(|v| v.as_str()).unwrap_or("unknown");
|
let constraint = item
|
||||||
|
.get("constraint")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("unknown");
|
||||||
errors.push(crate::drop::Error {
|
errors.push(crate::drop::Error {
|
||||||
code: "DATABASE_RELATION_PARSE_FAILED".to_string(),
|
code: "DATABASE_RELATION_PARSE_FAILED".to_string(),
|
||||||
message: format!("Failed to parse database relation '{}': {}", constraint, e),
|
message: format!("Failed to parse database relation '{}': {}", constraint, e),
|
||||||
@ -127,7 +136,10 @@ impl Database {
|
|||||||
db.puncs.insert(def.name.clone(), def);
|
db.puncs.insert(def.name.clone(), def);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let name = item.get("name").and_then(|v| v.as_str()).unwrap_or("unknown");
|
let name = item
|
||||||
|
.get("name")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("unknown");
|
||||||
errors.push(crate::drop::Error {
|
errors.push(crate::drop::Error {
|
||||||
code: "DATABASE_PUNC_PARSE_FAILED".to_string(),
|
code: "DATABASE_PUNC_PARSE_FAILED".to_string(),
|
||||||
message: format!("Failed to parse database punc '{}': {}", name, e),
|
message: format!("Failed to parse database punc '{}': {}", name, e),
|
||||||
@ -199,7 +211,13 @@ impl Database {
|
|||||||
pub fn compile(&mut self, errors: &mut Vec<crate::drop::Error>) {
|
pub fn compile(&mut self, errors: &mut Vec<crate::drop::Error>) {
|
||||||
let mut harvested = Vec::new();
|
let mut harvested = Vec::new();
|
||||||
for (id, schema_arc) in &self.schemas {
|
for (id, schema_arc) in &self.schemas {
|
||||||
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut harvested, errors);
|
crate::database::schema::Schema::collect_schemas(
|
||||||
|
schema_arc,
|
||||||
|
id,
|
||||||
|
id.clone(),
|
||||||
|
&mut harvested,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for (id, schema_arc) in harvested {
|
for (id, schema_arc) in harvested {
|
||||||
self.schemas.insert(id, schema_arc);
|
self.schemas.insert(id, schema_arc);
|
||||||
@ -208,11 +226,12 @@ impl Database {
|
|||||||
self.collect_schemas(errors);
|
self.collect_schemas(errors);
|
||||||
|
|
||||||
// Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks
|
// Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks
|
||||||
let mut visited = std::collections::HashSet::new();
|
|
||||||
for (id, schema_arc) in &self.schemas {
|
for (id, schema_arc) in &self.schemas {
|
||||||
// First compile pass initializes exact structural root_id mapping to resolve DB constraints
|
// First compile pass initializes exact structural root_id mapping to resolve DB constraints
|
||||||
let root_id = id.split('/').next().unwrap_or(id);
|
let root_id = id.split('/').next().unwrap_or(id);
|
||||||
schema_arc.as_ref().compile(self, root_id, id.clone(), &mut visited, errors);
|
schema_arc
|
||||||
|
.as_ref()
|
||||||
|
.compile(self, root_id, id.clone(), errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,19 +243,37 @@ impl Database {
|
|||||||
for type_def in self.types.values() {
|
for type_def in self.types.values() {
|
||||||
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)));
|
to_insert.push((id.clone(), Arc::clone(schema_arc)));
|
||||||
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut to_insert, errors);
|
crate::database::schema::Schema::collect_schemas(
|
||||||
|
schema_arc,
|
||||||
|
id,
|
||||||
|
id.clone(),
|
||||||
|
&mut to_insert,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for punc_def in self.puncs.values() {
|
for punc_def in self.puncs.values() {
|
||||||
for (id, schema_arc) in &punc_def.schemas {
|
for (id, schema_arc) in &punc_def.schemas {
|
||||||
to_insert.push((id.clone(), Arc::clone(schema_arc)));
|
to_insert.push((id.clone(), Arc::clone(schema_arc)));
|
||||||
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut to_insert, errors);
|
crate::database::schema::Schema::collect_schemas(
|
||||||
|
schema_arc,
|
||||||
|
id,
|
||||||
|
id.clone(),
|
||||||
|
&mut to_insert,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for enum_def in self.enums.values() {
|
for enum_def in self.enums.values() {
|
||||||
for (id, schema_arc) in &enum_def.schemas {
|
for (id, schema_arc) in &enum_def.schemas {
|
||||||
to_insert.push((id.clone(), Arc::clone(schema_arc)));
|
to_insert.push((id.clone(), Arc::clone(schema_arc)));
|
||||||
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut to_insert, errors);
|
crate::database::schema::Schema::collect_schemas(
|
||||||
|
schema_arc,
|
||||||
|
id,
|
||||||
|
id.clone(),
|
||||||
|
&mut to_insert,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,10 +313,10 @@ impl Database {
|
|||||||
all_rels.sort_by(|a, b| a.constraint.cmp(&b.constraint));
|
all_rels.sort_by(|a, b| a.constraint.cmp(&b.constraint));
|
||||||
|
|
||||||
for rel in all_rels {
|
for rel in all_rels {
|
||||||
let mut is_forward =
|
let mut is_forward = p_def.hierarchy.contains(&rel.source_type)
|
||||||
p_def.hierarchy.contains(&rel.source_type) && c_def.hierarchy.contains(&rel.destination_type);
|
&& c_def.hierarchy.contains(&rel.destination_type);
|
||||||
let is_reverse =
|
let is_reverse = p_def.hierarchy.contains(&rel.destination_type)
|
||||||
p_def.hierarchy.contains(&rel.destination_type) && c_def.hierarchy.contains(&rel.source_type);
|
&& c_def.hierarchy.contains(&rel.source_type);
|
||||||
|
|
||||||
// Structural Cardinality Filtration:
|
// Structural Cardinality Filtration:
|
||||||
// If the schema requires a collection (Array), it is mathematically impossible for a pure
|
// If the schema requires a collection (Array), it is mathematically impossible for a pure
|
||||||
|
|||||||
@ -28,21 +28,12 @@ impl Schema {
|
|||||||
db: &crate::database::Database,
|
db: &crate::database::Database,
|
||||||
root_id: &str,
|
root_id: &str,
|
||||||
path: String,
|
path: String,
|
||||||
visited: &mut std::collections::HashSet<String>,
|
|
||||||
errors: &mut Vec<crate::drop::Error>,
|
errors: &mut Vec<crate::drop::Error>,
|
||||||
) {
|
) {
|
||||||
if self.obj.compiled_properties.get().is_some() {
|
if self.obj.compiled_properties.get().is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
|
||||||
if !crate::database::object::is_primitive_type(t) {
|
|
||||||
if !visited.insert(t.clone()) {
|
|
||||||
return; // Break cyclical resolution
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(format_str) = &self.obj.format {
|
if let Some(format_str) = &self.obj.format {
|
||||||
if let Some(fmt) = crate::database::formats::FORMATS.get(format_str.as_str()) {
|
if let Some(fmt) = crate::database::formats::FORMATS.get(format_str.as_str()) {
|
||||||
let _ = self
|
let _ = self
|
||||||
@ -79,7 +70,7 @@ impl Schema {
|
|||||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||||
if !crate::database::object::is_primitive_type(t) {
|
if !crate::database::object::is_primitive_type(t) {
|
||||||
if let Some(parent) = db.schemas.get(t) {
|
if let Some(parent) = db.schemas.get(t) {
|
||||||
parent.as_ref().compile(db, t, t.clone(), visited, errors);
|
parent.as_ref().compile(db, t, t.clone(), errors);
|
||||||
if let Some(p_props) = parent.obj.compiled_properties.get() {
|
if let Some(p_props) = parent.obj.compiled_properties.get() {
|
||||||
props.extend(p_props.clone());
|
props.extend(p_props.clone());
|
||||||
}
|
}
|
||||||
@ -113,7 +104,7 @@ impl Schema {
|
|||||||
for t in types {
|
for t in types {
|
||||||
if !crate::database::object::is_primitive_type(t) {
|
if !crate::database::object::is_primitive_type(t) {
|
||||||
if let Some(parent) = db.schemas.get(t) {
|
if let Some(parent) = db.schemas.get(t) {
|
||||||
parent.as_ref().compile(db, t, t.clone(), visited, errors);
|
parent.as_ref().compile(db, t, t.clone(), errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,21 +124,21 @@ impl Schema {
|
|||||||
let _ = self.obj.compiled_property_names.set(names);
|
let _ = self.obj.compiled_property_names.set(names);
|
||||||
|
|
||||||
// 4. Compute Edges natively
|
// 4. Compute Edges natively
|
||||||
let schema_edges = self.compile_edges(db, root_id, &path, visited, &props, errors);
|
let schema_edges = self.compile_edges(db, root_id, &path, &props, errors);
|
||||||
let _ = self.obj.compiled_edges.set(schema_edges);
|
let _ = self.obj.compiled_edges.set(schema_edges);
|
||||||
|
|
||||||
// 5. Build our inline children properties recursively NOW! (Depth-first search)
|
// 5. Build our inline children properties recursively NOW! (Depth-first search)
|
||||||
if let Some(local_props) = &self.obj.properties {
|
if let Some(local_props) = &self.obj.properties {
|
||||||
for (k, child) in local_props {
|
for (k, child) in local_props {
|
||||||
child.compile(db, root_id, format!("{}/{}", path, k), visited, errors);
|
child.compile(db, root_id, format!("{}/{}", path, k), errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(items) = &self.obj.items {
|
if let Some(items) = &self.obj.items {
|
||||||
items.compile(db, root_id, format!("{}/items", path), visited, errors);
|
items.compile(db, root_id, format!("{}/items", path), errors);
|
||||||
}
|
}
|
||||||
if let Some(pattern_props) = &self.obj.pattern_properties {
|
if let Some(pattern_props) = &self.obj.pattern_properties {
|
||||||
for (k, child) in pattern_props {
|
for (k, child) in pattern_props {
|
||||||
child.compile(db, root_id, format!("{}/{}", path, k), visited, errors);
|
child.compile(db, root_id, format!("{}/{}", path, k), errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(additional_props) = &self.obj.additional_properties {
|
if let Some(additional_props) = &self.obj.additional_properties {
|
||||||
@ -155,7 +146,6 @@ impl Schema {
|
|||||||
db,
|
db,
|
||||||
root_id,
|
root_id,
|
||||||
format!("{}/additionalProperties", path),
|
format!("{}/additionalProperties", path),
|
||||||
visited,
|
|
||||||
errors,
|
errors,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -165,7 +155,6 @@ impl Schema {
|
|||||||
db,
|
db,
|
||||||
root_id,
|
root_id,
|
||||||
format!("{}/oneOf/{}", path, i),
|
format!("{}/oneOf/{}", path, i),
|
||||||
visited,
|
|
||||||
errors,
|
errors,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -176,16 +165,15 @@ impl Schema {
|
|||||||
db,
|
db,
|
||||||
root_id,
|
root_id,
|
||||||
format!("{}/prefixItems/{}", path, i),
|
format!("{}/prefixItems/{}", path, i),
|
||||||
visited,
|
|
||||||
errors,
|
errors,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(child) = &self.obj.not {
|
if let Some(child) = &self.obj.not {
|
||||||
child.compile(db, root_id, format!("{}/not", path), visited, errors);
|
child.compile(db, root_id, format!("{}/not", path), errors);
|
||||||
}
|
}
|
||||||
if let Some(child) = &self.obj.contains {
|
if let Some(child) = &self.obj.contains {
|
||||||
child.compile(db, root_id, format!("{}/contains", path), visited, errors);
|
child.compile(db, root_id, format!("{}/contains", path), errors);
|
||||||
}
|
}
|
||||||
if let Some(cases) = &self.obj.cases {
|
if let Some(cases) = &self.obj.cases {
|
||||||
for (i, c) in cases.iter().enumerate() {
|
for (i, c) in cases.iter().enumerate() {
|
||||||
@ -194,7 +182,6 @@ impl Schema {
|
|||||||
db,
|
db,
|
||||||
root_id,
|
root_id,
|
||||||
format!("{}/cases/{}/when", path, i),
|
format!("{}/cases/{}/when", path, i),
|
||||||
visited,
|
|
||||||
errors,
|
errors,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -203,7 +190,6 @@ impl Schema {
|
|||||||
db,
|
db,
|
||||||
root_id,
|
root_id,
|
||||||
format!("{}/cases/{}/then", path, i),
|
format!("{}/cases/{}/then", path, i),
|
||||||
visited,
|
|
||||||
errors,
|
errors,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -212,7 +198,6 @@ impl Schema {
|
|||||||
db,
|
db,
|
||||||
root_id,
|
root_id,
|
||||||
format!("{}/cases/{}/else", path, i),
|
format!("{}/cases/{}/else", path, i),
|
||||||
visited,
|
|
||||||
errors,
|
errors,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -220,12 +205,6 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.compile_polymorphism(db, root_id, &path, errors);
|
self.compile_polymorphism(db, root_id, &path, errors);
|
||||||
|
|
||||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
|
||||||
if !crate::database::object::is_primitive_type(t) {
|
|
||||||
visited.remove(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dynamically infers and compiles all structural database relationships between this Schema
|
/// Dynamically infers and compiles all structural database relationships between this Schema
|
||||||
@ -237,7 +216,6 @@ impl Schema {
|
|||||||
db: &crate::database::Database,
|
db: &crate::database::Database,
|
||||||
root_id: &str,
|
root_id: &str,
|
||||||
path: &str,
|
path: &str,
|
||||||
visited: &mut std::collections::HashSet<String>,
|
|
||||||
props: &std::collections::BTreeMap<String, std::sync::Arc<Schema>>,
|
props: &std::collections::BTreeMap<String, std::sync::Arc<Schema>>,
|
||||||
errors: &mut Vec<crate::drop::Error>,
|
errors: &mut Vec<crate::drop::Error>,
|
||||||
) -> std::collections::BTreeMap<String, crate::database::edge::Edge> {
|
) -> std::collections::BTreeMap<String, crate::database::edge::Edge> {
|
||||||
@ -263,31 +241,11 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if parent_type_name.is_none() {
|
|
||||||
// 3. Absolute fallback for anonymous inline structures
|
|
||||||
let base_type_name = root_id
|
|
||||||
.split('.')
|
|
||||||
.next_back()
|
|
||||||
.unwrap_or(root_id)
|
|
||||||
.to_string();
|
|
||||||
if db.types.contains_key(&base_type_name) {
|
|
||||||
parent_type_name = Some(base_type_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(p_type) = parent_type_name {
|
if let Some(p_type) = parent_type_name {
|
||||||
// Proceed only if the resolved table physically exists within the Postgres Type hierarchy
|
// Proceed only if the resolved table physically exists within the Postgres Type hierarchy
|
||||||
if let Some(type_def) = db.types.get(&p_type) {
|
if let Some(type_def) = db.types.get(&p_type) {
|
||||||
// Iterate over all discovered schema boundaries mapped inside the object
|
// Iterate over all discovered schema boundaries mapped inside the object
|
||||||
for (prop_name, prop_schema) in props {
|
for (prop_name, prop_schema) in props {
|
||||||
if let Some(field_types_map) = type_def.field_types.as_ref().and_then(|v| v.as_object()) {
|
|
||||||
if let Some(pg_type) = field_types_map.get(prop_name).and_then(|v| v.as_str()) {
|
|
||||||
if pg_type == "json" || pg_type == "jsonb" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut child_type_name = None;
|
let mut child_type_name = None;
|
||||||
let mut target_schema = prop_schema.clone();
|
let mut target_schema = prop_schema.clone();
|
||||||
let mut is_array = false;
|
let mut is_array = false;
|
||||||
@ -325,6 +283,16 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(c_type) = child_type_name {
|
if let Some(c_type) = child_type_name {
|
||||||
|
// Skip edge compilation for JSONB columns — they store data inline, not relationally.
|
||||||
|
// The physical column type from field_types is the single source of truth.
|
||||||
|
if let Some(ft) = type_def.field_types.as_ref()
|
||||||
|
.and_then(|v| v.get(prop_name.as_str()))
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
{
|
||||||
|
if ft == "jsonb" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
if db.types.contains_key(&c_type) {
|
if db.types.contains_key(&c_type) {
|
||||||
// Ensure the child Schema's AST has accurately compiled its own physical property keys so we can
|
// Ensure the child Schema's AST has accurately compiled its own physical property keys so we can
|
||||||
// inject them securely for Many-to-Many Twin Deduction disambiguation matching.
|
// inject them securely for Many-to-Many Twin Deduction disambiguation matching.
|
||||||
@ -332,9 +300,9 @@ impl Schema {
|
|||||||
db,
|
db,
|
||||||
root_id,
|
root_id,
|
||||||
format!("{}/{}", path, prop_name),
|
format!("{}/{}", path, prop_name),
|
||||||
visited,
|
|
||||||
errors,
|
errors,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(compiled_target_props) = target_schema.obj.compiled_properties.get() {
|
if let Some(compiled_target_props) = target_schema.obj.compiled_properties.get() {
|
||||||
let keys_for_ambiguity: Vec<String> =
|
let keys_for_ambiguity: Vec<String> =
|
||||||
compiled_target_props.keys().cloned().collect();
|
compiled_target_props.keys().cloned().collect();
|
||||||
|
|||||||
@ -347,7 +347,8 @@ impl<'a> Compiler<'a> {
|
|||||||
child_node.schema = Arc::clone(target_schema);
|
child_node.schema = Arc::clone(target_schema);
|
||||||
child_node.is_polymorphic_branch = true;
|
child_node.is_polymorphic_branch = true;
|
||||||
|
|
||||||
let val_sql = if disc == "kind" && node.parent_type.is_some() && node.parent_type_aliases.is_some() {
|
let val_sql =
|
||||||
|
if disc == "kind" && node.parent_type.is_some() && node.parent_type_aliases.is_some() {
|
||||||
let aliases_arc = node.parent_type_aliases.as_ref().unwrap();
|
let aliases_arc = node.parent_type_aliases.as_ref().unwrap();
|
||||||
let aliases = aliases_arc.as_ref();
|
let aliases = aliases_arc.as_ref();
|
||||||
let p_type = node.parent_type.unwrap();
|
let p_type = node.parent_type.unwrap();
|
||||||
|
|||||||
@ -1559,6 +1559,24 @@ fn test_polymorphism_4_1() {
|
|||||||
crate::tests::runner::run_test_case(&path, 4, 1).unwrap();
|
crate::tests::runner::run_test_case(&path, 4, 1).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_polymorphism_5_0() {
|
||||||
|
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 5, 0).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_polymorphism_5_1() {
|
||||||
|
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 5, 1).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_polymorphism_5_2() {
|
||||||
|
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 5, 2).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_not_0_0() {
|
fn test_not_0_0() {
|
||||||
let path = format!("{}/fixtures/not.json", env!("CARGO_MANIFEST_DIR"));
|
let path = format!("{}/fixtures/not.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
@ -3695,6 +3713,12 @@ fn test_database_5_0() {
|
|||||||
crate::tests::runner::run_test_case(&path, 5, 0).unwrap();
|
crate::tests::runner::run_test_case(&path, 5, 0).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_database_6_0() {
|
||||||
|
let path = format!("{}/fixtures/database.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 6, 0).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cases_0_0() {
|
fn test_cases_0_0() {
|
||||||
let path = format!("{}/fixtures/cases.json", env!("CARGO_MANIFEST_DIR"));
|
let path = format!("{}/fixtures/cases.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
|||||||
@ -24,9 +24,6 @@ impl<'a> ValidationContext<'a> {
|
|||||||
|
|
||||||
if let Some(obj) = self.instance.as_object() {
|
if let Some(obj) = self.instance.as_object() {
|
||||||
for key in obj.keys() {
|
for key in obj.keys() {
|
||||||
if key == "type" || key == "kind" {
|
|
||||||
continue; // Reserved keywords implicitly allowed
|
|
||||||
}
|
|
||||||
if !result.evaluated_keys.contains(key) && !self.overrides.contains(key) {
|
if !result.evaluated_keys.contains(key) && !self.overrides.contains(key) {
|
||||||
result.errors.push(ValidationError {
|
result.errors.push(ValidationError {
|
||||||
code: "STRICT_PROPERTY_VIOLATION".to_string(),
|
code: "STRICT_PROPERTY_VIOLATION".to_string(),
|
||||||
|
|||||||
@ -54,6 +54,10 @@ impl<'a> ValidationContext<'a> {
|
|||||||
|
|
||||||
// If the target mathematically declares a horizontal structural STI variation natively
|
// If the target mathematically declares a horizontal structural STI variation natively
|
||||||
if schema_identifier_str.contains('.') {
|
if schema_identifier_str.contains('.') {
|
||||||
|
let requires_kind = self.schema.compiled_properties.get()
|
||||||
|
.map_or(false, |p| p.contains_key("kind"));
|
||||||
|
|
||||||
|
if requires_kind {
|
||||||
if obj.get("kind").is_none() {
|
if obj.get("kind").is_none() {
|
||||||
result.errors.push(ValidationError {
|
result.errors.push(ValidationError {
|
||||||
code: "MISSING_KIND".to_string(),
|
code: "MISSING_KIND".to_string(),
|
||||||
@ -64,6 +68,7 @@ impl<'a> ValidationContext<'a> {
|
|||||||
result.evaluated_keys.insert("kind".to_string());
|
result.evaluated_keys.insert("kind".to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// If it isn't registered globally, it might be a nested Ad-Hoc candidate running via O(1) union routers.
|
// If it isn't registered globally, it might be a nested Ad-Hoc candidate running via O(1) union routers.
|
||||||
// Because they lack manual type property descriptors, we natively shield "type" and "kind" keys from
|
// Because they lack manual type property descriptors, we natively shield "type" and "kind" keys from
|
||||||
|
|||||||
Reference in New Issue
Block a user