Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a8a15a82ef | |||
| 8dcc714963 | |||
| f87ac81f3b | |||
| 8ca9017cc4 | |||
| 10c57e59ec | |||
| ef4571767c | |||
| 29bd25eaff | |||
| 4d9b510819 | |||
| 3c4b1066df | |||
| 4c59d9ba7f | |||
| a1038490dd | |||
| 14707330a7 |
819
d1.json
819
d1.json
@ -1,819 +0,0 @@
|
||||
{
|
||||
"database": {
|
||||
"puncs": [],
|
||||
"enums": [
|
||||
{
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"type": "relation_type",
|
||||
"enum": "relation_type",
|
||||
"values": [
|
||||
"foreign_key",
|
||||
"polymorphic",
|
||||
"graph"
|
||||
]
|
||||
}
|
||||
],
|
||||
"relations": [
|
||||
{
|
||||
"id": "22222222-2222-2222-2222-222222222222",
|
||||
"type": "relation",
|
||||
"constraint": "fk_order_customer",
|
||||
"source_type": "order",
|
||||
"source_columns": [
|
||||
"customer_id"
|
||||
],
|
||||
"destination_type": "person",
|
||||
"destination_columns": [
|
||||
"id"
|
||||
],
|
||||
"prefix": "customer"
|
||||
},
|
||||
{
|
||||
"id": "33333333-3333-3333-3333-333333333333",
|
||||
"type": "relation",
|
||||
"constraint": "fk_order_line_order",
|
||||
"source_type": "order_line",
|
||||
"source_columns": [
|
||||
"order_id"
|
||||
],
|
||||
"destination_type": "order",
|
||||
"destination_columns": [
|
||||
"id"
|
||||
],
|
||||
"prefix": "lines"
|
||||
},
|
||||
{
|
||||
"id": "44444444-4444-4444-4444-444444444444",
|
||||
"type": "relation",
|
||||
"constraint": "fk_relationship_source_entity",
|
||||
"source_type": "relationship",
|
||||
"source_columns": [
|
||||
"source_id",
|
||||
"source_type"
|
||||
],
|
||||
"destination_type": "entity",
|
||||
"destination_columns": [
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"prefix": "source"
|
||||
},
|
||||
{
|
||||
"id": "55555555-5555-5555-5555-555555555555",
|
||||
"type": "relation",
|
||||
"constraint": "fk_relationship_target_entity",
|
||||
"source_type": "relationship",
|
||||
"source_columns": [
|
||||
"target_id",
|
||||
"target_type"
|
||||
],
|
||||
"destination_type": "entity",
|
||||
"destination_columns": [
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"prefix": "target"
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"archived": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_by": {
|
||||
"type": "string"
|
||||
},
|
||||
"modified_by": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"modified_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"type",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"modified_by",
|
||||
"modified_at"
|
||||
]
|
||||
}
|
||||
],
|
||||
"hierarchy": [
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
]
|
||||
},
|
||||
"lookup_fields": [],
|
||||
"historical": true,
|
||||
"notify": true,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "organization",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "organization",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"hierarchy": [
|
||||
"organization",
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"name",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"organization": [
|
||||
"id",
|
||||
"type",
|
||||
"name"
|
||||
],
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
]
|
||||
},
|
||||
"lookup_fields": [],
|
||||
"historical": true,
|
||||
"notify": true,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "user",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "user",
|
||||
"$ref": "organization",
|
||||
"properties": {}
|
||||
}
|
||||
],
|
||||
"hierarchy": [
|
||||
"user",
|
||||
"organization",
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"name",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"user": [
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"organization": [
|
||||
"id",
|
||||
"type",
|
||||
"name"
|
||||
],
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
]
|
||||
},
|
||||
"lookup_fields": [],
|
||||
"historical": true,
|
||||
"notify": true,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "person",
|
||||
"$ref": "user",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"date_of_birth": {
|
||||
"type": "string"
|
||||
},
|
||||
"pronouns": {
|
||||
"type": "string"
|
||||
},
|
||||
"contact_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"contacts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "contact",
|
||||
"properties": {
|
||||
"target": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "phone_number"
|
||||
},
|
||||
{
|
||||
"$ref": "email_address"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"hierarchy": [
|
||||
"person",
|
||||
"user",
|
||||
"organization",
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"date_of_birth",
|
||||
"pronouns",
|
||||
"contact_id",
|
||||
"name",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"person": [
|
||||
"id",
|
||||
"type",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"date_of_birth",
|
||||
"pronouns",
|
||||
"contact_id"
|
||||
],
|
||||
"user": [
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"organization": [
|
||||
"id",
|
||||
"type",
|
||||
"name"
|
||||
],
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
]
|
||||
},
|
||||
"lookup_fields": [
|
||||
"first_name",
|
||||
"last_name",
|
||||
"date_of_birth",
|
||||
"pronouns"
|
||||
],
|
||||
"historical": true,
|
||||
"notify": true,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "order",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "order",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "number"
|
||||
},
|
||||
"customer_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"customer": {
|
||||
"$ref": "person"
|
||||
},
|
||||
"lines": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "order_line"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"hierarchy": [
|
||||
"order",
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"total",
|
||||
"customer_id",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"order": [
|
||||
"id",
|
||||
"type",
|
||||
"total",
|
||||
"customer_id"
|
||||
],
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
]
|
||||
},
|
||||
"lookup_fields": [
|
||||
"id"
|
||||
],
|
||||
"historical": true,
|
||||
"notify": true,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "order_line",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "order_line",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"order_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"product": {
|
||||
"type": "string"
|
||||
},
|
||||
"price": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"hierarchy": [
|
||||
"order_line",
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"order_id",
|
||||
"product",
|
||||
"price",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"order_line": [
|
||||
"id",
|
||||
"type",
|
||||
"order_id",
|
||||
"product",
|
||||
"price"
|
||||
],
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
]
|
||||
},
|
||||
"lookup_fields": [],
|
||||
"historical": true,
|
||||
"notify": true,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "relationship",
|
||||
"relationship": true,
|
||||
"hierarchy": [
|
||||
"relationship",
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"source_id",
|
||||
"source_type",
|
||||
"target_id",
|
||||
"target_type",
|
||||
"id",
|
||||
"type",
|
||||
"name",
|
||||
"archived",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"name",
|
||||
"archived",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by"
|
||||
],
|
||||
"relationship": [
|
||||
"source_id",
|
||||
"source_type",
|
||||
"target_id",
|
||||
"target_type"
|
||||
]
|
||||
},
|
||||
"field_types": {
|
||||
"id": "uuid",
|
||||
"type": "text",
|
||||
"archived": "boolean",
|
||||
"source_id": "uuid",
|
||||
"source_type": "text",
|
||||
"target_id": "uuid",
|
||||
"target_type": "text",
|
||||
"name": "text",
|
||||
"created_at": "timestamptz",
|
||||
"created_by": "uuid",
|
||||
"modified_at": "timestamptz",
|
||||
"modified_by": "uuid"
|
||||
},
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "relationship",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
}
|
||||
],
|
||||
"lookup_fields": [],
|
||||
"historical": true,
|
||||
"notify": true
|
||||
},
|
||||
{
|
||||
"name": "contact",
|
||||
"relationship": true,
|
||||
"hierarchy": [
|
||||
"contact",
|
||||
"relationship",
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"is_primary",
|
||||
"source_id",
|
||||
"source_type",
|
||||
"target_id",
|
||||
"target_type",
|
||||
"id",
|
||||
"type",
|
||||
"name",
|
||||
"archived",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"name",
|
||||
"archived",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by"
|
||||
],
|
||||
"relationship": [
|
||||
"source_id",
|
||||
"source_type",
|
||||
"target_id",
|
||||
"target_type"
|
||||
],
|
||||
"contact": [
|
||||
"is_primary"
|
||||
]
|
||||
},
|
||||
"field_types": {
|
||||
"id": "uuid",
|
||||
"type": "text",
|
||||
"archived": "boolean",
|
||||
"source_id": "uuid",
|
||||
"source_type": "text",
|
||||
"target_id": "uuid",
|
||||
"target_type": "text",
|
||||
"is_primary": "boolean",
|
||||
"name": "text",
|
||||
"created_at": "timestamptz",
|
||||
"created_by": "uuid",
|
||||
"modified_at": "timestamptz",
|
||||
"modified_by": "uuid"
|
||||
},
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "contact",
|
||||
"$ref": "relationship",
|
||||
"properties": {
|
||||
"is_primary": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"lookup_fields": [],
|
||||
"historical": true,
|
||||
"notify": true
|
||||
},
|
||||
{
|
||||
"name": "phone_number",
|
||||
"hierarchy": [
|
||||
"phone_number",
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"number",
|
||||
"id",
|
||||
"type",
|
||||
"name",
|
||||
"archived",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"name",
|
||||
"archived",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by"
|
||||
],
|
||||
"phone_number": [
|
||||
"number"
|
||||
]
|
||||
},
|
||||
"field_types": {
|
||||
"id": "uuid",
|
||||
"type": "text",
|
||||
"archived": "boolean",
|
||||
"number": "text",
|
||||
"name": "text",
|
||||
"created_at": "timestamptz",
|
||||
"created_by": "uuid",
|
||||
"modified_at": "timestamptz",
|
||||
"modified_by": "uuid"
|
||||
},
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "phone_number",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"number": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"lookup_fields": [],
|
||||
"historical": true,
|
||||
"notify": true,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "email_address",
|
||||
"hierarchy": [
|
||||
"email_address",
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"address",
|
||||
"id",
|
||||
"type",
|
||||
"name",
|
||||
"archived",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"name",
|
||||
"archived",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by"
|
||||
],
|
||||
"email_address": [
|
||||
"address"
|
||||
]
|
||||
},
|
||||
"field_types": {
|
||||
"id": "uuid",
|
||||
"type": "text",
|
||||
"archived": "boolean",
|
||||
"address": "text",
|
||||
"name": "text",
|
||||
"created_at": "timestamptz",
|
||||
"created_by": "uuid",
|
||||
"modified_at": "timestamptz",
|
||||
"modified_by": "uuid"
|
||||
},
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "email_address",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"lookup_fields": [],
|
||||
"historical": true,
|
||||
"notify": true,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "attachment",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "type_metadata",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "other_metadata",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"other": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "attachment",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"flags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type_metadata": {
|
||||
"$ref": "type_metadata"
|
||||
},
|
||||
"other_metadata": {
|
||||
"$ref": "other_metadata"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"hierarchy": [
|
||||
"attachment",
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"flags",
|
||||
"type_metadata",
|
||||
"other_metadata",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"attachment": [
|
||||
"id",
|
||||
"type",
|
||||
"flags",
|
||||
"type_metadata",
|
||||
"other_metadata"
|
||||
],
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"archived"
|
||||
]
|
||||
},
|
||||
"field_types": {
|
||||
"id": "uuid",
|
||||
"type": "text",
|
||||
"flags": "_text",
|
||||
"type_metadata": "jsonb",
|
||||
"other_metadata": "jsonb",
|
||||
"created_at": "timestamptz",
|
||||
"created_by": "uuid",
|
||||
"modified_at": "timestamptz",
|
||||
"modified_by": "uuid",
|
||||
"archived": "boolean"
|
||||
},
|
||||
"lookup_fields": [],
|
||||
"historical": true,
|
||||
"notify": true,
|
||||
"relationship": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -2401,6 +2401,215 @@
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Anchor order and insert new line (no line id)",
|
||||
"action": "merge",
|
||||
"data": {
|
||||
"id": "abc",
|
||||
"type": "order",
|
||||
"lines": [
|
||||
{
|
||||
"type": "order_line",
|
||||
"product": "Widget",
|
||||
"price": 99.0
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema_id": "order",
|
||||
"expect": {
|
||||
"success": true,
|
||||
"sql": [
|
||||
[
|
||||
"INSERT INTO agreego.\"entity\" (",
|
||||
" \"created_at\",",
|
||||
" \"created_by\",",
|
||||
" \"id\",",
|
||||
" \"modified_at\",",
|
||||
" \"modified_by\",",
|
||||
" \"type\"",
|
||||
")",
|
||||
"VALUES (",
|
||||
" '{{timestamp}}',",
|
||||
" '00000000-0000-0000-0000-000000000000',",
|
||||
" '{{uuid:line_id}}',",
|
||||
" '{{timestamp}}',",
|
||||
" '00000000-0000-0000-0000-000000000000',",
|
||||
" 'order_line'",
|
||||
")"
|
||||
],
|
||||
[
|
||||
"INSERT INTO agreego.\"order_line\" (",
|
||||
" \"id\",",
|
||||
" \"order_id\",",
|
||||
" \"price\",",
|
||||
" \"product\",",
|
||||
" \"type\"",
|
||||
")",
|
||||
"VALUES (",
|
||||
" '{{uuid:line_id}}',",
|
||||
" 'abc',",
|
||||
" 99,",
|
||||
" 'Widget',",
|
||||
" 'order_line'",
|
||||
")"
|
||||
],
|
||||
[
|
||||
"INSERT INTO agreego.change (",
|
||||
" \"old\",",
|
||||
" \"new\",",
|
||||
" entity_id,",
|
||||
" id,",
|
||||
" kind,",
|
||||
" modified_at,",
|
||||
" modified_by",
|
||||
")",
|
||||
"VALUES (",
|
||||
" NULL,",
|
||||
" '{",
|
||||
" \"order_id\":\"abc\",",
|
||||
" \"price\":99.0,",
|
||||
" \"product\":\"Widget\",",
|
||||
" \"type\":\"order_line\"",
|
||||
" }',",
|
||||
" '{{uuid:line_id}}',",
|
||||
" '{{uuid}}',",
|
||||
" 'create',",
|
||||
" '{{timestamp}}',",
|
||||
" '00000000-0000-0000-0000-000000000000'",
|
||||
")"
|
||||
],
|
||||
[
|
||||
"SELECT pg_notify('entity', '{",
|
||||
" \"complete\":{",
|
||||
" \"created_at\":\"{{timestamp}}\",",
|
||||
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||
" \"id\":\"{{uuid:line_id}}\",",
|
||||
" \"modified_at\":\"{{timestamp}}\",",
|
||||
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||
" \"order_id\":\"abc\",",
|
||||
" \"price\":99.0,",
|
||||
" \"product\":\"Widget\",",
|
||||
" \"type\":\"order_line\"",
|
||||
" },",
|
||||
" \"new\":{",
|
||||
" \"order_id\":\"abc\",",
|
||||
" \"price\":99.0,",
|
||||
" \"product\":\"Widget\",",
|
||||
" \"type\":\"order_line\"",
|
||||
" }",
|
||||
" }')"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Anchor order and insert new line (with line id)",
|
||||
"action": "merge",
|
||||
"data": {
|
||||
"id": "abc",
|
||||
"type": "order",
|
||||
"lines": [
|
||||
{
|
||||
"id": "11111111-2222-3333-4444-555555555555",
|
||||
"type": "order_line",
|
||||
"product": "Widget",
|
||||
"price": 99.0
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema_id": "order",
|
||||
"expect": {
|
||||
"success": true,
|
||||
"sql": [
|
||||
[
|
||||
"SELECT to_jsonb(t1.*) || to_jsonb(t2.*)",
|
||||
"FROM agreego.\"order_line\" t1",
|
||||
"LEFT JOIN agreego.\"entity\" t2 ON t2.id = t1.id",
|
||||
"WHERE t1.id = '11111111-2222-3333-4444-555555555555'"
|
||||
],
|
||||
[
|
||||
"INSERT INTO agreego.\"entity\" (",
|
||||
" \"created_at\",",
|
||||
" \"created_by\",",
|
||||
" \"id\",",
|
||||
" \"modified_at\",",
|
||||
" \"modified_by\",",
|
||||
" \"type\"",
|
||||
")",
|
||||
"VALUES (",
|
||||
" '{{timestamp}}',",
|
||||
" '00000000-0000-0000-0000-000000000000',",
|
||||
" '11111111-2222-3333-4444-555555555555',",
|
||||
" '{{timestamp}}',",
|
||||
" '00000000-0000-0000-0000-000000000000',",
|
||||
" 'order_line'",
|
||||
")"
|
||||
],
|
||||
[
|
||||
"INSERT INTO agreego.\"order_line\" (",
|
||||
" \"id\",",
|
||||
" \"order_id\",",
|
||||
" \"price\",",
|
||||
" \"product\",",
|
||||
" \"type\"",
|
||||
")",
|
||||
"VALUES (",
|
||||
" '11111111-2222-3333-4444-555555555555',",
|
||||
" 'abc',",
|
||||
" 99,",
|
||||
" 'Widget',",
|
||||
" 'order_line'",
|
||||
")"
|
||||
],
|
||||
[
|
||||
"INSERT INTO agreego.change (",
|
||||
" \"old\",",
|
||||
" \"new\",",
|
||||
" entity_id,",
|
||||
" id,",
|
||||
" kind,",
|
||||
" modified_at,",
|
||||
" modified_by",
|
||||
")",
|
||||
"VALUES (",
|
||||
" NULL,",
|
||||
" '{",
|
||||
" \"order_id\":\"abc\",",
|
||||
" \"price\":99.0,",
|
||||
" \"product\":\"Widget\",",
|
||||
" \"type\":\"order_line\"",
|
||||
" }',",
|
||||
" '11111111-2222-3333-4444-555555555555',",
|
||||
" '{{uuid}}',",
|
||||
" 'create',",
|
||||
" '{{timestamp}}',",
|
||||
" '00000000-0000-0000-0000-000000000000'",
|
||||
")"
|
||||
],
|
||||
[
|
||||
"SELECT pg_notify('entity', '{",
|
||||
" \"complete\":{",
|
||||
" \"created_at\":\"{{timestamp}}\",",
|
||||
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||
" \"id\":\"11111111-2222-3333-4444-555555555555\",",
|
||||
" \"modified_at\":\"{{timestamp}}\",",
|
||||
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||
" \"order_id\":\"abc\",",
|
||||
" \"price\":99.0,",
|
||||
" \"product\":\"Widget\",",
|
||||
" \"type\":\"order_line\"",
|
||||
" },",
|
||||
" \"new\":{",
|
||||
" \"order_id\":\"abc\",",
|
||||
" \"price\":99.0,",
|
||||
" \"product\":\"Widget\",",
|
||||
" \"type\":\"order_line\"",
|
||||
" }",
|
||||
" }')"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1003,6 +1003,7 @@
|
||||
" JOIN agreego.entity entity_6 ON entity_6.id = relationship_5.id",
|
||||
" WHERE",
|
||||
" NOT entity_6.archived",
|
||||
" AND relationship_5.target_type = 'address'",
|
||||
" AND relationship_5.source_id = entity_3.id),",
|
||||
" 'age', person_1.age,",
|
||||
" 'archived', entity_3.archived,",
|
||||
@ -1094,6 +1095,7 @@
|
||||
" JOIN agreego.entity entity_20 ON entity_20.id = relationship_19.id",
|
||||
" WHERE",
|
||||
" NOT entity_20.archived",
|
||||
" AND relationship_19.target_type = 'email_address'",
|
||||
" AND relationship_19.source_id = entity_3.id),",
|
||||
" 'first_name', person_1.first_name,",
|
||||
" 'id', entity_3.id,",
|
||||
@ -1127,6 +1129,7 @@
|
||||
" JOIN agreego.entity entity_25 ON entity_25.id = relationship_24.id",
|
||||
" WHERE",
|
||||
" NOT entity_25.archived",
|
||||
" AND relationship_24.target_type = 'phone_number'",
|
||||
" AND relationship_24.source_id = entity_3.id),",
|
||||
" 'type', entity_3.type",
|
||||
")",
|
||||
@ -1163,7 +1166,7 @@
|
||||
"$eq": true,
|
||||
"$ne": false
|
||||
},
|
||||
"contacts.#.is_primary": {
|
||||
"contacts/is_primary": {
|
||||
"$eq": true
|
||||
},
|
||||
"created_at": {
|
||||
@ -1203,7 +1206,7 @@
|
||||
"$eq": "%Doe%",
|
||||
"$ne": "%Smith%"
|
||||
},
|
||||
"phone_numbers.#.target.number": {
|
||||
"phone_numbers/target/number": {
|
||||
"$eq": "555-1234"
|
||||
}
|
||||
},
|
||||
@ -1240,6 +1243,7 @@
|
||||
" JOIN agreego.entity entity_6 ON entity_6.id = relationship_5.id",
|
||||
" WHERE",
|
||||
" NOT entity_6.archived",
|
||||
" AND relationship_5.target_type = 'address'",
|
||||
" AND relationship_5.source_id = entity_3.id),",
|
||||
" 'age', person_1.age,",
|
||||
" 'archived', entity_3.archived,",
|
||||
@ -1332,6 +1336,7 @@
|
||||
" JOIN agreego.entity entity_20 ON entity_20.id = relationship_19.id",
|
||||
" WHERE",
|
||||
" NOT entity_20.archived",
|
||||
" AND relationship_19.target_type = 'email_address'",
|
||||
" AND relationship_19.source_id = entity_3.id),",
|
||||
" 'first_name', person_1.first_name,",
|
||||
" 'id', entity_3.id,",
|
||||
@ -1366,6 +1371,7 @@
|
||||
" JOIN agreego.entity entity_25 ON entity_25.id = relationship_24.id",
|
||||
" WHERE",
|
||||
" NOT entity_25.archived",
|
||||
" AND relationship_24.target_type = 'phone_number'",
|
||||
" AND relationship_24.source_id = entity_3.id),",
|
||||
" 'type', entity_3.type",
|
||||
")",
|
||||
@ -1408,6 +1414,46 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Person ad-hoc email addresses select",
|
||||
"action": "query",
|
||||
"schema_id": "full.person/email_addresses",
|
||||
"expect": {
|
||||
"success": true,
|
||||
"sql": [
|
||||
[
|
||||
"(SELECT jsonb_build_object(",
|
||||
" 'archived', entity_3.archived,",
|
||||
" 'created_at', entity_3.created_at,",
|
||||
" 'id', entity_3.id,",
|
||||
" 'is_primary', contact_1.is_primary,",
|
||||
" 'name', entity_3.name,",
|
||||
" 'target',",
|
||||
" (SELECT jsonb_build_object(",
|
||||
" 'address', email_address_4.address,",
|
||||
" 'archived', entity_5.archived,",
|
||||
" 'created_at', entity_5.created_at,",
|
||||
" 'id', entity_5.id,",
|
||||
" 'name', entity_5.name,",
|
||||
" 'type', entity_5.type",
|
||||
" )",
|
||||
" FROM agreego.email_address email_address_4",
|
||||
" JOIN agreego.entity entity_5 ON entity_5.id = email_address_4.id",
|
||||
" WHERE",
|
||||
" NOT entity_5.archived",
|
||||
" AND relationship_2.target_id = entity_5.id),",
|
||||
" 'type', entity_3.type",
|
||||
")",
|
||||
"FROM agreego.contact contact_1",
|
||||
"JOIN agreego.relationship relationship_2 ON relationship_2.id = contact_1.id",
|
||||
"JOIN agreego.entity entity_3 ON entity_3.id = relationship_2.id",
|
||||
"WHERE",
|
||||
" NOT entity_3.archived",
|
||||
" AND relationship_2.target_type = 'email_address')"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Order select with customer and lines",
|
||||
"action": "query",
|
||||
|
||||
@ -403,6 +403,23 @@ impl Merger {
|
||||
> {
|
||||
let type_name = type_def.name.as_str();
|
||||
|
||||
// 🚀 Anchor Short-Circuit Optimization
|
||||
// An anchor is STRICTLY a struct containing merely an `id` and `type`.
|
||||
// We aggressively bypass Database SPI `SELECT` fetches because there are no primitive
|
||||
// mutations to apply to the row. PostgreSQL inherently protects relationships via Foreign Keys downstream.
|
||||
let is_anchor = entity_fields.len() == 2
|
||||
&& entity_fields.contains_key("id")
|
||||
&& entity_fields.contains_key("type");
|
||||
|
||||
let has_valid_id = entity_fields
|
||||
.get("id")
|
||||
.and_then(|v| v.as_str())
|
||||
.map_or(false, |s| !s.is_empty());
|
||||
|
||||
if is_anchor && has_valid_id {
|
||||
return Ok((entity_fields, None, None));
|
||||
}
|
||||
|
||||
let entity_fetched = self.fetch_entity(&entity_fields, type_def)?;
|
||||
|
||||
let system_keys = vec![
|
||||
|
||||
@ -64,11 +64,7 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
fn compile_array(&mut self, node: Node<'a>) -> Result<(String, String), String> {
|
||||
if let Some(items) = &node.schema.obj.items {
|
||||
let next_path = if node.ast_path.is_empty() {
|
||||
String::from("#")
|
||||
} else {
|
||||
format!("{}.#", node.ast_path)
|
||||
};
|
||||
let next_path = node.ast_path.clone();
|
||||
|
||||
if let Some(ref_id) = &items.obj.r#ref {
|
||||
if let Some(type_def) = self.db.types.get(ref_id) {
|
||||
@ -448,22 +444,21 @@ impl<'a> Compiler<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut child_node = node.clone();
|
||||
child_node.parent_alias = owner_alias.clone();
|
||||
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
|
||||
child_node.parent_type_aliases = Some(arc_aliases);
|
||||
child_node.parent_type = Some(r#type);
|
||||
child_node.parent_schema = Some(std::sync::Arc::clone(&node.schema));
|
||||
child_node.property_name = Some(prop_key.clone());
|
||||
child_node.depth += 1;
|
||||
let next_path = if node.ast_path.is_empty() {
|
||||
prop_key.clone()
|
||||
} else {
|
||||
format!("{}.{}", node.ast_path, prop_key)
|
||||
let child_node = Node {
|
||||
schema: std::sync::Arc::clone(prop_schema),
|
||||
parent_alias: owner_alias.clone(),
|
||||
parent_type_aliases: Some(std::sync::Arc::new(table_aliases.clone())),
|
||||
parent_type: Some(r#type),
|
||||
parent_schema: Some(std::sync::Arc::clone(&node.schema)),
|
||||
property_name: Some(prop_key.clone()),
|
||||
depth: node.depth + 1,
|
||||
ast_path: if node.ast_path.is_empty() {
|
||||
prop_key.clone()
|
||||
} else {
|
||||
format!("{}/{}", node.ast_path, prop_key)
|
||||
},
|
||||
};
|
||||
|
||||
child_node.ast_path = next_path;
|
||||
child_node.schema = std::sync::Arc::clone(prop_schema);
|
||||
|
||||
let (val_sql, val_type) = self.compile_node(child_node)?;
|
||||
|
||||
@ -491,9 +486,17 @@ impl<'a> Compiler<'a> {
|
||||
.unwrap_or_else(|| base_alias.clone());
|
||||
|
||||
let mut where_clauses = Vec::new();
|
||||
where_clauses.push(format!("NOT {}.archived", entity_alias));
|
||||
|
||||
// Dynamically apply the 'active-only' default ONLY if the client
|
||||
// didn't explicitly request to filter on 'archived' themselves!
|
||||
let has_archived_override = self.filter_keys.iter().any(|k| k == "archived");
|
||||
|
||||
if !has_archived_override {
|
||||
where_clauses.push(format!("NOT {}.archived", entity_alias));
|
||||
}
|
||||
|
||||
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_relation_conditions(
|
||||
r#type,
|
||||
type_aliases,
|
||||
@ -505,6 +508,54 @@ impl<'a> Compiler<'a> {
|
||||
Ok(where_clauses)
|
||||
}
|
||||
|
||||
fn compile_polymorphic_bounds(
|
||||
&self,
|
||||
_type: &crate::database::r#type::Type,
|
||||
type_aliases: &std::collections::HashMap<String, String>,
|
||||
node: &Node,
|
||||
where_clauses: &mut Vec<String>,
|
||||
) {
|
||||
if let Some(edges) = node.schema.obj.compiled_edges.get() {
|
||||
if let Some(props) = node.schema.obj.compiled_properties.get() {
|
||||
for (prop_name, edge) in edges {
|
||||
if let Some(prop_schema) = props.get(prop_name) {
|
||||
// Determine if the property schema resolves to a physical Database Entity
|
||||
let mut bound_type_name = None;
|
||||
if let Some(family_target) = prop_schema.obj.family.as_ref() {
|
||||
bound_type_name = Some(family_target.split('.').next_back().unwrap_or(family_target).to_string());
|
||||
} else if let Some(lookup_key) = prop_schema.obj.id.as_ref().or(prop_schema.obj.r#ref.as_ref()) {
|
||||
bound_type_name = Some(lookup_key.split('.').next_back().unwrap_or(lookup_key).to_string());
|
||||
}
|
||||
|
||||
if let Some(type_name) = bound_type_name {
|
||||
// Ensure this type actually exists
|
||||
if self.db.types.contains_key(&type_name) {
|
||||
if let Some(relation) = self.db.relations.get(&edge.constraint) {
|
||||
let mut poly_col = None;
|
||||
let mut table_to_alias = "";
|
||||
|
||||
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 {
|
||||
if let Some(alias) = type_aliases.get(table_to_alias).or_else(|| type_aliases.get(&node.parent_alias)) {
|
||||
where_clauses.push(format!("{}.{} = '{}'", alias, col, type_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_filter_alias(
|
||||
r#type: &crate::database::r#type::Type,
|
||||
type_aliases: &std::collections::HashMap<String, String>,
|
||||
@ -580,15 +631,15 @@ impl<'a> Compiler<'a> {
|
||||
let op = parts.next().unwrap_or("$eq");
|
||||
|
||||
let field_name = if node.ast_path.is_empty() {
|
||||
if full_field_path.contains('.') || full_field_path.contains('#') {
|
||||
if full_field_path.contains('/') {
|
||||
continue;
|
||||
}
|
||||
full_field_path
|
||||
} else {
|
||||
let prefix = format!("{}.", node.ast_path);
|
||||
let prefix = format!("{}/", node.ast_path);
|
||||
if full_field_path.starts_with(&prefix) {
|
||||
let remainder = &full_field_path[prefix.len()..];
|
||||
if remainder.contains('.') || remainder.contains('#') {
|
||||
if remainder.contains('/') {
|
||||
continue;
|
||||
}
|
||||
remainder
|
||||
|
||||
@ -54,6 +54,45 @@ impl Queryer {
|
||||
self.execute_sql(schema_id, &sql, &args)
|
||||
}
|
||||
|
||||
fn extract_filters(
|
||||
prefix: String,
|
||||
val: &serde_json::Value,
|
||||
entries: &mut Vec<(String, serde_json::Value)>,
|
||||
) -> Result<(), String> {
|
||||
if let Some(obj) = val.as_object() {
|
||||
let mut is_op_obj = false;
|
||||
if let Some(first_key) = obj.keys().next() {
|
||||
if first_key.starts_with('$') {
|
||||
is_op_obj = true;
|
||||
}
|
||||
}
|
||||
|
||||
if is_op_obj {
|
||||
for (op, op_val) in obj {
|
||||
if !op.starts_with('$') {
|
||||
return Err(format!("Filter operator must start with '$', got: {}", op));
|
||||
}
|
||||
entries.push((format!("{}:{}", prefix, op), op_val.clone()));
|
||||
}
|
||||
} else {
|
||||
for (k, v) in obj {
|
||||
let next_prefix = if prefix.is_empty() {
|
||||
k.clone()
|
||||
} else {
|
||||
format!("{}/{}", prefix, k)
|
||||
};
|
||||
Self::extract_filters(next_prefix, v, entries)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Filter for path '{}' must be an operator object like {{$eq: ...}} or a nested map.",
|
||||
prefix
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_filter_entries(
|
||||
&self,
|
||||
filters_map: Option<&serde_json::Map<String, serde_json::Value>>,
|
||||
@ -61,19 +100,7 @@ impl Queryer {
|
||||
let mut filter_entries: Vec<(String, serde_json::Value)> = Vec::new();
|
||||
if let Some(fm) = filters_map {
|
||||
for (key, val) in fm {
|
||||
if let Some(obj) = val.as_object() {
|
||||
for (op, op_val) in obj {
|
||||
if !op.starts_with('$') {
|
||||
return Err(format!("Filter operator must start with '$', got: {}", op));
|
||||
}
|
||||
filter_entries.push((format!("{}:{}", key, op), op_val.clone()));
|
||||
}
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Filter for field '{}' must be an object with operators like $eq, $in, etc.",
|
||||
key
|
||||
));
|
||||
}
|
||||
Self::extract_filters(key.clone(), val, &mut filter_entries)?;
|
||||
}
|
||||
}
|
||||
filter_entries.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
@ -1451,6 +1451,12 @@ fn test_queryer_0_6() {
|
||||
crate::tests::runner::run_test_case(&path, 0, 6).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_queryer_0_7() {
|
||||
let path = format!("{}/fixtures/queryer.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 0, 7).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_0_0() {
|
||||
let path = format!("{}/fixtures/not.json", env!("CARGO_MANIFEST_DIR"));
|
||||
@ -8542,3 +8548,15 @@ fn test_merger_0_8() {
|
||||
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 0, 8).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merger_0_9() {
|
||||
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 0, 9).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merger_0_10() {
|
||||
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 0, 10).unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user