Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8845dcdef2 | |||
| 40e08cbf09 | |||
| c7372891d8 | |||
| 952c5036be | |||
| 1fb378def2 | |||
| 6cc4f4ad86 |
@ -111,6 +111,10 @@ Polymorphism is how an object boundary can dynamically take on entirely differen
|
|||||||
* *Setup*: `{ "family": "widget" }` (Where `widget` is a table type but has no external variations).
|
* *Setup*: `{ "family": "widget" }` (Where `widget` is a table type but has no external variations).
|
||||||
* *Execution*: The engine queries `db.types.get("widget").variations` and finds only `["widget"]`. Since it lacks table inheritance, it is treated as STI. The engine scans the specific, confined `schemas` array directly under `db.types.get("widget")` for any registered key terminating in the base `.widget` (e.g., `stock.widget`). The `family` automatically uses `kind` as the discriminator.
|
* *Execution*: The engine queries `db.types.get("widget").variations` and finds only `["widget"]`. Since it lacks table inheritance, it is treated as STI. The engine scans the specific, confined `schemas` array directly under `db.types.get("widget")` for any registered key terminating in the base `.widget` (e.g., `stock.widget`). The `family` automatically uses `kind` as the discriminator.
|
||||||
* *Options*: `stock` -> `stock.widget`, `tasks` -> `tasks.widget`.
|
* *Options*: `stock` -> `stock.widget`, `tasks` -> `tasks.widget`.
|
||||||
|
* **Scenario D: JSONB Bubble Inheritance (Field-Backed)**
|
||||||
|
* *Setup*: `{ "family": "panel" }` (Where `panel` is NOT a table type, but rather an isolated JSONB boundary defined within another table's `schemas`).
|
||||||
|
* *Execution*: The engine observes `panel` is not in `db.types` (because it has no physical table). It falls back to scanning the global `db.schemas` registry for any registered key terminating in the base `.panel` (e.g., `balance.panel`, `units.panel`). The `family` automatically uses `kind` as the discriminator.
|
||||||
|
* *Options*: `balance` -> `balance.panel`, `units` -> `units.panel`.
|
||||||
|
|
||||||
* **`oneOf` (Strict Tagged Unions)**: A hardcoded list of candidate schemas. Unlike `family` which relies on global DB metadata, `oneOf` forces pure mathematical structural evaluation of the provided candidates. It strictly bans typical JSON Schema "Union of Sets" fallback searches. Every candidate MUST possess a mathematically unique discriminator payload to allow $O(1)$ routing.
|
* **`oneOf` (Strict Tagged Unions)**: A hardcoded list of candidate schemas. Unlike `family` which relies on global DB metadata, `oneOf` forces pure mathematical structural evaluation of the provided candidates. It strictly bans typical JSON Schema "Union of Sets" fallback searches. Every candidate MUST possess a mathematically unique discriminator payload to allow $O(1)$ routing.
|
||||||
* **Disjoint Types**: `oneOf: [{ "type": "person" }, { "type": "widget" }]`. The engine succeeds because the native `type` acts as a unique discriminator (`"person"` vs `"widget"`).
|
* **Disjoint Types**: `oneOf: [{ "type": "person" }, { "type": "widget" }]`. The engine succeeds because the native `type` acts as a unique discriminator (`"person"` vs `"widget"`).
|
||||||
|
|||||||
15
fix_merger.py
Normal file
15
fix_merger.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# Find our new test
|
||||||
|
test_case = next(t for t in data[0]["tests"] if t["description"] == "Test organization_id syntactic sugar permutations")
|
||||||
|
|
||||||
|
# Fix the first SQL command (INSERT INTO entity for person)
|
||||||
|
sql = test_case["expect"]["sql"][0]
|
||||||
|
sql.remove(" \"organization_id\",")
|
||||||
|
sql.remove(" NULL,")
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
17
fix_merger2.py
Normal file
17
fix_merger2.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
db = data[0]["database"]
|
||||||
|
|
||||||
|
# Add organization_id to fields and grouped_fields.entity of order, order_line, person
|
||||||
|
for t in db["types"]:
|
||||||
|
if t["name"] in ["order", "order_line", "person"]:
|
||||||
|
if "organization_id" not in t["fields"]:
|
||||||
|
t["fields"].append("organization_id")
|
||||||
|
if "organization_id" not in t["grouped_fields"]["entity"]:
|
||||||
|
t["grouped_fields"]["entity"].append("organization_id")
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
16
fix_quotes.py
Normal file
16
fix_quotes.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "r") as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
# Fix the broken formatting
|
||||||
|
text = text.replace("'{',\n \" {timestamp}\",\n \" }'", "'{{timestamp}}'")
|
||||||
|
text = text.replace("'{',\n \" {uuid}\",\n \" }'", "'{{uuid}}'")
|
||||||
|
text = text.replace("'{',\n \" {uuid:person_id}\",\n \" }'", "'{{uuid:person_id}}'")
|
||||||
|
text = text.replace("'{',\n \" {uuid:order_id}\",\n \" }'", "'{{uuid:order_id}}'")
|
||||||
|
text = text.replace("'{',\n \" {uuid:line1_id}\",\n \" }'", "'{{uuid:line1_id}}'")
|
||||||
|
text = text.replace("'{',\n \" {uuid:line2_id}\",\n \" }'", "'{{uuid:line2_id}}'")
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "w") as f:
|
||||||
|
f.write(text)
|
||||||
|
|
||||||
32
fix_quotes_json.py
Normal file
32
fix_quotes_json.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
test_case = data[0]["tests"][-1]
|
||||||
|
|
||||||
|
for j, sql_group in enumerate(test_case["expect"]["sql"]):
|
||||||
|
new_group = []
|
||||||
|
i = 0
|
||||||
|
while i < len(sql_group):
|
||||||
|
s = sql_group[i]
|
||||||
|
if s.strip() == "'{":
|
||||||
|
if i + 2 < len(sql_group):
|
||||||
|
next_line = sql_group[i+1].strip()
|
||||||
|
next_next_line = sql_group[i+2].strip()
|
||||||
|
if next_next_line == "}',":
|
||||||
|
# Reconstruct
|
||||||
|
new_group.append(f" '{next_line}',")
|
||||||
|
i += 3
|
||||||
|
continue
|
||||||
|
elif next_next_line == "}'":
|
||||||
|
new_group.append(f" '{next_line}'")
|
||||||
|
i += 3
|
||||||
|
continue
|
||||||
|
new_group.append(s)
|
||||||
|
i += 1
|
||||||
|
test_case["expect"]["sql"][j] = new_group
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
20
fix_quotes_json2.py
Normal file
20
fix_quotes_json2.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
test_case = data[0]["tests"][-1]
|
||||||
|
|
||||||
|
for j, sql_group in enumerate(test_case["expect"]["sql"]):
|
||||||
|
for i, s in enumerate(sql_group):
|
||||||
|
s = s.replace("'{timestamp}'", "'{{timestamp}}'")
|
||||||
|
s = s.replace("'{uuid}'", "'{{uuid}}'")
|
||||||
|
s = s.replace("'{uuid:person_id}'", "'{{uuid:person_id}}'")
|
||||||
|
s = s.replace("'{uuid:order_id}'", "'{{uuid:order_id}}'")
|
||||||
|
s = s.replace("'{uuid:line1_id}'", "'{{uuid:line1_id}}'")
|
||||||
|
s = s.replace("'{uuid:line2_id}'", "'{{uuid:line2_id}}'")
|
||||||
|
sql_group[i] = s
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
@ -146,6 +146,9 @@
|
|||||||
"modified_at": {
|
"modified_at": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"organization_id": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@ -168,7 +171,8 @@
|
|||||||
"created_by",
|
"created_by",
|
||||||
"modified_at",
|
"modified_at",
|
||||||
"modified_by",
|
"modified_by",
|
||||||
"archived"
|
"archived",
|
||||||
|
"organization_id"
|
||||||
],
|
],
|
||||||
"grouped_fields": {
|
"grouped_fields": {
|
||||||
"entity": [
|
"entity": [
|
||||||
@ -178,7 +182,8 @@
|
|||||||
"created_by",
|
"created_by",
|
||||||
"modified_at",
|
"modified_at",
|
||||||
"modified_by",
|
"modified_by",
|
||||||
"archived"
|
"archived",
|
||||||
|
"organization_id"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lookup_fields": [],
|
"lookup_fields": [],
|
||||||
@ -345,6 +350,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"organization_id": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "ffffffff-ffff-ffff-ffff-ffffffffffff"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,7 +377,8 @@
|
|||||||
"created_by",
|
"created_by",
|
||||||
"modified_at",
|
"modified_at",
|
||||||
"modified_by",
|
"modified_by",
|
||||||
"archived"
|
"archived",
|
||||||
|
"organization_id"
|
||||||
],
|
],
|
||||||
"grouped_fields": {
|
"grouped_fields": {
|
||||||
"person": [
|
"person": [
|
||||||
@ -396,7 +406,8 @@
|
|||||||
"created_by",
|
"created_by",
|
||||||
"modified_at",
|
"modified_at",
|
||||||
"modified_by",
|
"modified_by",
|
||||||
"archived"
|
"archived",
|
||||||
|
"organization_id"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lookup_fields": [
|
"lookup_fields": [
|
||||||
@ -446,7 +457,8 @@
|
|||||||
"created_by",
|
"created_by",
|
||||||
"modified_at",
|
"modified_at",
|
||||||
"modified_by",
|
"modified_by",
|
||||||
"archived"
|
"archived",
|
||||||
|
"organization_id"
|
||||||
],
|
],
|
||||||
"grouped_fields": {
|
"grouped_fields": {
|
||||||
"order": [
|
"order": [
|
||||||
@ -462,7 +474,8 @@
|
|||||||
"created_by",
|
"created_by",
|
||||||
"modified_at",
|
"modified_at",
|
||||||
"modified_by",
|
"modified_by",
|
||||||
"archived"
|
"archived",
|
||||||
|
"organization_id"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lookup_fields": [
|
"lookup_fields": [
|
||||||
@ -504,7 +517,8 @@
|
|||||||
"created_by",
|
"created_by",
|
||||||
"modified_at",
|
"modified_at",
|
||||||
"modified_by",
|
"modified_by",
|
||||||
"archived"
|
"archived",
|
||||||
|
"organization_id"
|
||||||
],
|
],
|
||||||
"grouped_fields": {
|
"grouped_fields": {
|
||||||
"order_line": [
|
"order_line": [
|
||||||
@ -521,7 +535,8 @@
|
|||||||
"created_by",
|
"created_by",
|
||||||
"modified_at",
|
"modified_at",
|
||||||
"modified_by",
|
"modified_by",
|
||||||
"archived"
|
"archived",
|
||||||
|
"organization_id"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lookup_fields": [],
|
"lookup_fields": [],
|
||||||
@ -3126,10 +3141,26 @@
|
|||||||
"type": "invoice",
|
"type": "invoice",
|
||||||
"number": "INV-1001",
|
"number": "INV-1001",
|
||||||
"total": 200.0,
|
"total": 200.0,
|
||||||
"metadata_line": {"price": 50},
|
"metadata_line": {
|
||||||
"metadata_lines": [{"price": 25}],
|
"price": 50
|
||||||
"metadata_nested_line": {"line": {"price": 75}},
|
},
|
||||||
"metadata_nested_lines": {"lines": [{"price": 100}]}
|
"metadata_lines": [
|
||||||
|
{
|
||||||
|
"price": 25
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata_nested_line": {
|
||||||
|
"line": {
|
||||||
|
"price": 75
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_nested_lines": {
|
||||||
|
"lines": [
|
||||||
|
{
|
||||||
|
"price": 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"expect": {
|
"expect": {
|
||||||
"success": true,
|
"success": true,
|
||||||
@ -3304,6 +3335,354 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Test organization_id syntactic sugar permutations",
|
||||||
|
"action": "merge",
|
||||||
|
"data": {
|
||||||
|
"type": "order",
|
||||||
|
"organization_id": "parent-org-id",
|
||||||
|
"customer": {
|
||||||
|
"type": "person",
|
||||||
|
"first_name": "Const",
|
||||||
|
"last_name": "Person"
|
||||||
|
},
|
||||||
|
"lines": [
|
||||||
|
{
|
||||||
|
"type": "order_line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "order_line",
|
||||||
|
"organization_id": "explicit-org-id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"schema_id": "order",
|
||||||
|
"expect": {
|
||||||
|
"success": true,
|
||||||
|
"sql": [
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"entity\" (",
|
||||||
|
" \"created_at\",",
|
||||||
|
" \"created_by\",",
|
||||||
|
" \"id\",",
|
||||||
|
" \"modified_at\",",
|
||||||
|
" \"modified_by\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" '{{uuid:person_id}}',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" 'person'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"organization\" (",
|
||||||
|
" \"id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{uuid:person_id}}',",
|
||||||
|
" 'person'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"user\" (",
|
||||||
|
" \"id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{uuid:person_id}}',",
|
||||||
|
" 'person'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"person\" (",
|
||||||
|
" \"first_name\",",
|
||||||
|
" \"id\",",
|
||||||
|
" \"last_name\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" 'Const',",
|
||||||
|
" '{{uuid:person_id}}',",
|
||||||
|
" 'Person',",
|
||||||
|
" 'person'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.change (",
|
||||||
|
" \"old\",",
|
||||||
|
" \"new\",",
|
||||||
|
" entity_id,",
|
||||||
|
" id,",
|
||||||
|
" kind,",
|
||||||
|
" modified_at,",
|
||||||
|
" modified_by",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" NULL,",
|
||||||
|
" '{",
|
||||||
|
" \"first_name\":\"Const\",",
|
||||||
|
" \"last_name\":\"Person\",",
|
||||||
|
" \"type\":\"person\"",
|
||||||
|
" }',",
|
||||||
|
" '{{uuid:person_id}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" 'create',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '{{uuid}}'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"entity\" (",
|
||||||
|
" \"created_at\",",
|
||||||
|
" \"created_by\",",
|
||||||
|
" \"id\",",
|
||||||
|
" \"modified_at\",",
|
||||||
|
" \"modified_by\",",
|
||||||
|
" \"organization_id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" '{{uuid:order_id}}',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" 'parent-org-id',",
|
||||||
|
" 'order'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"order\" (",
|
||||||
|
" \"customer_id\",",
|
||||||
|
" \"id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{uuid:person_id}}',",
|
||||||
|
" '{{uuid:order_id}}',",
|
||||||
|
" 'order'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"entity\" (",
|
||||||
|
" \"created_at\",",
|
||||||
|
" \"created_by\",",
|
||||||
|
" \"id\",",
|
||||||
|
" \"modified_at\",",
|
||||||
|
" \"modified_by\",",
|
||||||
|
" \"organization_id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" '{{uuid:line1_id}}',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" 'parent-org-id',",
|
||||||
|
" 'order_line'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"order_line\" (",
|
||||||
|
" \"id\",",
|
||||||
|
" \"order_id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{uuid:line1_id}}',",
|
||||||
|
" '{{uuid:order_id}}',",
|
||||||
|
" 'order_line'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.change (",
|
||||||
|
" \"old\",",
|
||||||
|
" \"new\",",
|
||||||
|
" entity_id,",
|
||||||
|
" id,",
|
||||||
|
" kind,",
|
||||||
|
" modified_at,",
|
||||||
|
" modified_by",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" NULL,",
|
||||||
|
" '{",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" }',",
|
||||||
|
" '{{uuid:line1_id}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" 'create',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '{{uuid}}'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"entity\" (",
|
||||||
|
" \"created_at\",",
|
||||||
|
" \"created_by\",",
|
||||||
|
" \"id\",",
|
||||||
|
" \"modified_at\",",
|
||||||
|
" \"modified_by\",",
|
||||||
|
" \"organization_id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" '{{uuid:line2_id}}',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" 'explicit-org-id',",
|
||||||
|
" 'order_line'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"order_line\" (",
|
||||||
|
" \"id\",",
|
||||||
|
" \"order_id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{uuid:line2_id}}',",
|
||||||
|
" '{{uuid:order_id}}',",
|
||||||
|
" 'order_line'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.change (",
|
||||||
|
" \"old\",",
|
||||||
|
" \"new\",",
|
||||||
|
" entity_id,",
|
||||||
|
" id,",
|
||||||
|
" kind,",
|
||||||
|
" modified_at,",
|
||||||
|
" modified_by",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" NULL,",
|
||||||
|
" '{",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"explicit-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" }',",
|
||||||
|
" '{{uuid:line2_id}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" 'create',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '{{uuid}}'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.change (",
|
||||||
|
" \"old\",",
|
||||||
|
" \"new\",",
|
||||||
|
" entity_id,",
|
||||||
|
" id,",
|
||||||
|
" kind,",
|
||||||
|
" modified_at,",
|
||||||
|
" modified_by",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" NULL,",
|
||||||
|
" '{",
|
||||||
|
" \"customer_id\":\"{{uuid:person_id}}\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order\"",
|
||||||
|
" }',",
|
||||||
|
" '{{uuid:order_id}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" 'create',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '{{uuid}}'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"SELECT pg_notify('entity', '{",
|
||||||
|
" \"complete\":{",
|
||||||
|
" \"created_at\":\"{{timestamp}}\",",
|
||||||
|
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"customer_id\":\"{{uuid:person_id}}\",",
|
||||||
|
" \"id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"modified_at\":\"{{timestamp}}\",",
|
||||||
|
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order\"",
|
||||||
|
" },",
|
||||||
|
" \"new\":{",
|
||||||
|
" \"customer_id\":\"{{uuid:person_id}}\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order\"",
|
||||||
|
" }",
|
||||||
|
" }')"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"SELECT pg_notify('entity', '{",
|
||||||
|
" \"complete\":{",
|
||||||
|
" \"created_at\":\"{{timestamp}}\",",
|
||||||
|
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"first_name\":\"Const\",",
|
||||||
|
" \"id\":\"{{uuid:person_id}}\",",
|
||||||
|
" \"last_name\":\"Person\",",
|
||||||
|
" \"modified_at\":\"{{timestamp}}\",",
|
||||||
|
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"type\":\"person\"",
|
||||||
|
" },",
|
||||||
|
" \"new\":{",
|
||||||
|
" \"first_name\":\"Const\",",
|
||||||
|
" \"last_name\":\"Person\",",
|
||||||
|
" \"type\":\"person\"",
|
||||||
|
" }",
|
||||||
|
" }')"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"SELECT pg_notify('entity', '{",
|
||||||
|
" \"complete\":{",
|
||||||
|
" \"created_at\":\"{{timestamp}}\",",
|
||||||
|
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"id\":\"{{uuid:line1_id}}\",",
|
||||||
|
" \"modified_at\":\"{{timestamp}}\",",
|
||||||
|
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" },",
|
||||||
|
" \"new\":{",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" }",
|
||||||
|
" }')"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"SELECT pg_notify('entity', '{",
|
||||||
|
" \"complete\":{",
|
||||||
|
" \"created_at\":\"{{timestamp}}\",",
|
||||||
|
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"id\":\"{{uuid:line2_id}}\",",
|
||||||
|
" \"modified_at\":\"{{timestamp}}\",",
|
||||||
|
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"explicit-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" },",
|
||||||
|
" \"new\":{",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"explicit-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" }",
|
||||||
|
" }')"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"description": "Vertical family Routing (Across Tables)",
|
"description": "Vertical family Routing (Scenario A)",
|
||||||
"database": {
|
"database": {
|
||||||
"types": [
|
"types": [
|
||||||
{
|
{
|
||||||
@ -153,7 +153,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Matrix family Routing (Vertical + Horizontal Intersections)",
|
"description": "Matrix family Routing (Scenario B)",
|
||||||
"database": {
|
"database": {
|
||||||
"types": [
|
"types": [
|
||||||
{
|
{
|
||||||
@ -284,7 +284,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Horizontal family Routing (Virtual Variations)",
|
"description": "Horizontal family Routing (Scenario C)",
|
||||||
"database": {
|
"database": {
|
||||||
"types": [
|
"types": [
|
||||||
{
|
{
|
||||||
@ -776,5 +776,123 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "JSONB Field Bubble family Routing (Scenario D)",
|
||||||
|
"database": {
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"name": "dashboard",
|
||||||
|
"variations": [
|
||||||
|
"dashboard"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"dashboard": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"panel": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"kind"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"balance.panel": {
|
||||||
|
"type": "panel",
|
||||||
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"units.panel": {
|
||||||
|
"type": "panel",
|
||||||
|
"properties": {
|
||||||
|
"count": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "family_panel",
|
||||||
|
"schemas": {
|
||||||
|
"family_panel": {
|
||||||
|
"family": "panel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"description": "Successfully routes to nested balance panel",
|
||||||
|
"schema_id": "family_panel",
|
||||||
|
"data": {
|
||||||
|
"id": "123",
|
||||||
|
"kind": "balance",
|
||||||
|
"amount": 500
|
||||||
|
},
|
||||||
|
"action": "validate",
|
||||||
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fails validation on routed schema due to invalid property type",
|
||||||
|
"schema_id": "family_panel",
|
||||||
|
"data": {
|
||||||
|
"id": "123",
|
||||||
|
"kind": "balance",
|
||||||
|
"amount": "not_an_int"
|
||||||
|
},
|
||||||
|
"action": "validate",
|
||||||
|
"expect": {
|
||||||
|
"success": false,
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": "INVALID_TYPE",
|
||||||
|
"details": {
|
||||||
|
"path": "amount"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fails when discriminator does not match any bubble schema",
|
||||||
|
"schema_id": "family_panel",
|
||||||
|
"data": {
|
||||||
|
"id": "123",
|
||||||
|
"kind": "unknown_panel"
|
||||||
|
},
|
||||||
|
"action": "validate",
|
||||||
|
"expect": {
|
||||||
|
"success": false,
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": "NO_FAMILY_MATCH",
|
||||||
|
"details": {
|
||||||
|
"path": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
2
flows
2
flows
Submodule flows updated: 4d61e13e00...0d9bd8644e
111
format_sql.py
Normal file
111
format_sql.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
test_case = next(t for t in data[0]["tests"] if t["description"] == "Test organization_id syntactic sugar permutations")
|
||||||
|
|
||||||
|
def format_sql(sql_str):
|
||||||
|
if sql_str.startswith("INSERT INTO"):
|
||||||
|
parts = sql_str.split(" VALUES ")
|
||||||
|
insert_part = parts[0]
|
||||||
|
values_part = parts[1]
|
||||||
|
|
||||||
|
insert_match = re.match(r"(INSERT INTO [a-zA-Z0-9_.\"]+) \((.*)\)", insert_part)
|
||||||
|
table = insert_match.group(1)
|
||||||
|
cols_str = insert_match.group(2)
|
||||||
|
cols = [c.strip() for c in cols_str.split(",")]
|
||||||
|
|
||||||
|
values_str = values_part[1:-1]
|
||||||
|
|
||||||
|
# We need to split values_str carefully, as JSON strings contain commas!
|
||||||
|
# Since it's single quotes around values, we can split by ", " but that's risky.
|
||||||
|
# Let's do a simple parse:
|
||||||
|
vals = []
|
||||||
|
current_val = []
|
||||||
|
in_quote = False
|
||||||
|
i = 0
|
||||||
|
while i < len(values_str):
|
||||||
|
c = values_str[i]
|
||||||
|
if c == "'":
|
||||||
|
# handle double quotes inside? Postgres uses '' for escaping ' inside '.
|
||||||
|
# Here we don't have that complexity.
|
||||||
|
in_quote = not in_quote
|
||||||
|
current_val.append(c)
|
||||||
|
elif c == ',' and not in_quote:
|
||||||
|
vals.append("".join(current_val).strip())
|
||||||
|
current_val = []
|
||||||
|
else:
|
||||||
|
current_val.append(c)
|
||||||
|
i += 1
|
||||||
|
vals.append("".join(current_val).strip())
|
||||||
|
|
||||||
|
lines = [f"{table} ("]
|
||||||
|
for i, col in enumerate(cols):
|
||||||
|
lines.append(f" {col}" + ("," if i < len(cols) - 1 else ""))
|
||||||
|
lines.append(")")
|
||||||
|
lines.append("VALUES (")
|
||||||
|
|
||||||
|
for i, val in enumerate(vals):
|
||||||
|
if val.startswith("'{") and val.endswith("}'"):
|
||||||
|
# Format JSON
|
||||||
|
lines.append(" '{")
|
||||||
|
json_str = val[2:-2]
|
||||||
|
# Split json keys by ",
|
||||||
|
json_pairs = json_str.split(',"')
|
||||||
|
for j, pair in enumerate(json_pairs):
|
||||||
|
if j > 0:
|
||||||
|
pair = '"' + pair
|
||||||
|
lines.append(f" {pair}" + ("," if j < len(json_pairs) - 1 else ""))
|
||||||
|
lines.append(" }'" + ("," if i < len(vals) - 1 else ""))
|
||||||
|
else:
|
||||||
|
# Replace '{{uuid}}' with '00000000-0000-0000-0000-000000000000' for created_by etc if it was replaced as '{{uuid}}'
|
||||||
|
if val == "'{{uuid}}'" and cols[i] in ['"created_by"', '"modified_by"', 'modified_by']:
|
||||||
|
val = "'00000000-0000-0000-0000-000000000000'"
|
||||||
|
lines.append(f" {val}" + ("," if i < len(vals) - 1 else ""))
|
||||||
|
lines.append(")")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
elif sql_str.startswith("SELECT pg_notify"):
|
||||||
|
# Format notify string
|
||||||
|
match = re.match(r"SELECT pg_notify\('entity', '(.*)'\)", sql_str)
|
||||||
|
payload = match.group(1)
|
||||||
|
# We know payload looks like {"complete":{...},"new":{...}}
|
||||||
|
lines = ["SELECT pg_notify('entity', '{"]
|
||||||
|
|
||||||
|
# split complete and new
|
||||||
|
complete_str = payload[payload.find('"complete":{')+12:payload.find('},"new":{')]
|
||||||
|
new_str = payload[payload.find('"new":{')+7:-2]
|
||||||
|
|
||||||
|
lines.append(" \"complete\":{")
|
||||||
|
complete_pairs = complete_str.split(',"')
|
||||||
|
for j, pair in enumerate(complete_pairs):
|
||||||
|
if j > 0:
|
||||||
|
pair = '"' + pair
|
||||||
|
lines.append(f" {pair}" + ("," if j < len(complete_pairs) - 1 else ""))
|
||||||
|
lines.append(" },")
|
||||||
|
|
||||||
|
lines.append(" \"new\":{")
|
||||||
|
new_pairs = new_str.split(',"')
|
||||||
|
for j, pair in enumerate(new_pairs):
|
||||||
|
if j > 0:
|
||||||
|
pair = '"' + pair
|
||||||
|
lines.append(f" {pair}" + ("," if j < len(new_pairs) - 1 else ""))
|
||||||
|
lines.append(" }")
|
||||||
|
lines.append(" }')")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
return [sql_str]
|
||||||
|
|
||||||
|
new_sql = []
|
||||||
|
for sql_group in test_case["expect"]["sql"]:
|
||||||
|
sql_str = "".join(sql_group)
|
||||||
|
formatted = format_sql(sql_str)
|
||||||
|
new_sql.append(formatted)
|
||||||
|
|
||||||
|
test_case["expect"]["sql"] = new_sql
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
409
patch_merger.py
Normal file
409
patch_merger.py
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
db = data[0]["database"]
|
||||||
|
|
||||||
|
# 1. Update entity schema
|
||||||
|
entity_type = next(t for t in db["types"] if t["name"] == "entity")
|
||||||
|
entity_type["schemas"]["entity"]["properties"]["organization_id"] = {"type": "string"}
|
||||||
|
entity_type["fields"].append("organization_id")
|
||||||
|
entity_type["grouped_fields"]["entity"].append("organization_id")
|
||||||
|
|
||||||
|
# 2. Update person schema
|
||||||
|
person_type = next(t for t in db["types"] if t["name"] == "person")
|
||||||
|
person_type["schemas"]["person"]["properties"]["organization_id"] = {
|
||||||
|
"type": "string",
|
||||||
|
"const": "ffffffff-ffff-ffff-ffff-ffffffffffff"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3. Add the test case
|
||||||
|
test_case = {
|
||||||
|
"description": "Test organization_id syntactic sugar permutations",
|
||||||
|
"action": "merge",
|
||||||
|
"data": {
|
||||||
|
"type": "order",
|
||||||
|
"organization_id": "parent-org-id",
|
||||||
|
"customer": {
|
||||||
|
"type": "person",
|
||||||
|
"first_name": "Const",
|
||||||
|
"last_name": "Person"
|
||||||
|
},
|
||||||
|
"lines": [
|
||||||
|
{
|
||||||
|
"type": "order_line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "order_line",
|
||||||
|
"organization_id": "explicit-org-id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"schema_id": "order",
|
||||||
|
"expect": {
|
||||||
|
"success": True,
|
||||||
|
"sql": [
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"entity\" (",
|
||||||
|
" \"created_at\",",
|
||||||
|
" \"created_by\",",
|
||||||
|
" \"id\",",
|
||||||
|
" \"modified_at\",",
|
||||||
|
" \"modified_by\",",
|
||||||
|
" \"organization_id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '00000000-0000-0000-0000-000000000000',",
|
||||||
|
" '{{uuid:person_id}}',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '00000000-0000-0000-0000-000000000000',",
|
||||||
|
" NULL,",
|
||||||
|
" 'person'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"organization\" (",
|
||||||
|
" \"id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{uuid:person_id}}',",
|
||||||
|
" 'person'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"user\" (",
|
||||||
|
" \"id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{uuid:person_id}}',",
|
||||||
|
" 'person'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"person\" (",
|
||||||
|
" \"first_name\",",
|
||||||
|
" \"id\",",
|
||||||
|
" \"last_name\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" 'Const',",
|
||||||
|
" '{{uuid:person_id}}',",
|
||||||
|
" 'Person',",
|
||||||
|
" 'person'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.change (",
|
||||||
|
" \"old\",",
|
||||||
|
" \"new\",",
|
||||||
|
" entity_id,",
|
||||||
|
" id,",
|
||||||
|
" kind,",
|
||||||
|
" modified_at,",
|
||||||
|
" modified_by",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" NULL,",
|
||||||
|
" '{",
|
||||||
|
" \"first_name\":\"Const\",",
|
||||||
|
" \"last_name\":\"Person\",",
|
||||||
|
" \"type\":\"person\"",
|
||||||
|
" }',",
|
||||||
|
" '{{uuid:person_id}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" 'create',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '00000000-0000-0000-0000-000000000000'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"entity\" (",
|
||||||
|
" \"created_at\",",
|
||||||
|
" \"created_by\",",
|
||||||
|
" \"id\",",
|
||||||
|
" \"modified_at\",",
|
||||||
|
" \"modified_by\",",
|
||||||
|
" \"organization_id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '00000000-0000-0000-0000-000000000000',",
|
||||||
|
" '{{uuid:line1_id}}',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '00000000-0000-0000-0000-000000000000',",
|
||||||
|
" 'parent-org-id',",
|
||||||
|
" 'order_line'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"order_line\" (",
|
||||||
|
" \"id\",",
|
||||||
|
" \"order_id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{uuid:line1_id}}',",
|
||||||
|
" '{{uuid:order_id}}',",
|
||||||
|
" 'order_line'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.change (",
|
||||||
|
" \"old\",",
|
||||||
|
" \"new\",",
|
||||||
|
" entity_id,",
|
||||||
|
" id,",
|
||||||
|
" kind,",
|
||||||
|
" modified_at,",
|
||||||
|
" modified_by",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" NULL,",
|
||||||
|
" '{",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" }',",
|
||||||
|
" '{{uuid:line1_id}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" 'create',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '00000000-0000-0000-0000-000000000000'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"entity\" (",
|
||||||
|
" \"created_at\",",
|
||||||
|
" \"created_by\",",
|
||||||
|
" \"id\",",
|
||||||
|
" \"modified_at\",",
|
||||||
|
" \"modified_by\",",
|
||||||
|
" \"organization_id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '00000000-0000-0000-0000-000000000000',",
|
||||||
|
" '{{uuid:line2_id}}',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '00000000-0000-0000-0000-000000000000',",
|
||||||
|
" 'explicit-org-id',",
|
||||||
|
" 'order_line'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"order_line\" (",
|
||||||
|
" \"id\",",
|
||||||
|
" \"order_id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{uuid:line2_id}}',",
|
||||||
|
" '{{uuid:order_id}}',",
|
||||||
|
" 'order_line'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.change (",
|
||||||
|
" \"old\",",
|
||||||
|
" \"new\",",
|
||||||
|
" entity_id,",
|
||||||
|
" id,",
|
||||||
|
" kind,",
|
||||||
|
" modified_at,",
|
||||||
|
" modified_by",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" NULL,",
|
||||||
|
" '{",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"explicit-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" }',",
|
||||||
|
" '{{uuid:line2_id}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" 'create',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '00000000-0000-0000-0000-000000000000'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"entity\" (",
|
||||||
|
" \"created_at\",",
|
||||||
|
" \"created_by\",",
|
||||||
|
" \"id\",",
|
||||||
|
" \"modified_at\",",
|
||||||
|
" \"modified_by\",",
|
||||||
|
" \"organization_id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '00000000-0000-0000-0000-000000000000',",
|
||||||
|
" '{{uuid:order_id}}',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '00000000-0000-0000-0000-000000000000',",
|
||||||
|
" 'parent-org-id',",
|
||||||
|
" 'order'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.\"order\" (",
|
||||||
|
" \"customer_id\",",
|
||||||
|
" \"id\",",
|
||||||
|
" \"type\"",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" '{{uuid:person_id}}',",
|
||||||
|
" '{{uuid:order_id}}',",
|
||||||
|
" 'order'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"INSERT INTO agreego.change (",
|
||||||
|
" \"old\",",
|
||||||
|
" \"new\",",
|
||||||
|
" entity_id,",
|
||||||
|
" id,",
|
||||||
|
" kind,",
|
||||||
|
" modified_at,",
|
||||||
|
" modified_by",
|
||||||
|
")",
|
||||||
|
"VALUES (",
|
||||||
|
" NULL,",
|
||||||
|
" '{",
|
||||||
|
" \"customer_id\":\"{{uuid:person_id}}\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order\"",
|
||||||
|
" }',",
|
||||||
|
" '{{uuid:order_id}}',",
|
||||||
|
" '{{uuid}}',",
|
||||||
|
" 'create',",
|
||||||
|
" '{{timestamp}}',",
|
||||||
|
" '00000000-0000-0000-0000-000000000000'",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"SELECT pg_notify('entity', '{",
|
||||||
|
" \"complete\":{",
|
||||||
|
" \"created_at\":\"{{timestamp}}\",",
|
||||||
|
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"first_name\":\"Const\",",
|
||||||
|
" \"id\":\"{{uuid:person_id}}\",",
|
||||||
|
" \"last_name\":\"Person\",",
|
||||||
|
" \"modified_at\":\"{{timestamp}}\",",
|
||||||
|
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"type\":\"person\"",
|
||||||
|
" },",
|
||||||
|
" \"new\":{",
|
||||||
|
" \"first_name\":\"Const\",",
|
||||||
|
" \"last_name\":\"Person\",",
|
||||||
|
" \"type\":\"person\"",
|
||||||
|
" }",
|
||||||
|
" }')"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"SELECT pg_notify('entity', '{",
|
||||||
|
" \"complete\":{",
|
||||||
|
" \"created_at\":\"{{timestamp}}\",",
|
||||||
|
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"id\":\"{{uuid:line1_id}}\",",
|
||||||
|
" \"modified_at\":\"{{timestamp}}\",",
|
||||||
|
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" },",
|
||||||
|
" \"new\":{",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" }",
|
||||||
|
" }')"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"SELECT pg_notify('entity', '{",
|
||||||
|
" \"complete\":{",
|
||||||
|
" \"created_at\":\"{{timestamp}}\",",
|
||||||
|
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"id\":\"{{uuid:line2_id}}\",",
|
||||||
|
" \"modified_at\":\"{{timestamp}}\",",
|
||||||
|
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"explicit-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" },",
|
||||||
|
" \"new\":{",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"explicit-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" }",
|
||||||
|
" }')"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"SELECT pg_notify('entity', '{",
|
||||||
|
" \"complete\":{",
|
||||||
|
" \"created_at\":\"{{timestamp}}\",",
|
||||||
|
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"customer\":{",
|
||||||
|
" \"created_at\":\"{{timestamp}}\",",
|
||||||
|
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"first_name\":\"Const\",",
|
||||||
|
" \"id\":\"{{uuid:person_id}}\",",
|
||||||
|
" \"last_name\":\"Person\",",
|
||||||
|
" \"modified_at\":\"{{timestamp}}\",",
|
||||||
|
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"type\":\"person\"",
|
||||||
|
" },",
|
||||||
|
" \"customer_id\":\"{{uuid:person_id}}\",",
|
||||||
|
" \"id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"lines\":[",
|
||||||
|
" {",
|
||||||
|
" \"created_at\":\"{{timestamp}}\",",
|
||||||
|
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"id\":\"{{uuid:line1_id}}\",",
|
||||||
|
" \"modified_at\":\"{{timestamp}}\",",
|
||||||
|
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" },",
|
||||||
|
" {",
|
||||||
|
" \"created_at\":\"{{timestamp}}\",",
|
||||||
|
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"id\":\"{{uuid:line2_id}}\",",
|
||||||
|
" \"modified_at\":\"{{timestamp}}\",",
|
||||||
|
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"order_id\":\"{{uuid:order_id}}\",",
|
||||||
|
" \"organization_id\":\"explicit-org-id\",",
|
||||||
|
" \"type\":\"order_line\"",
|
||||||
|
" }",
|
||||||
|
" ],",
|
||||||
|
" \"modified_at\":\"{{timestamp}}\",",
|
||||||
|
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order\"",
|
||||||
|
" },",
|
||||||
|
" \"new\":{",
|
||||||
|
" \"customer_id\":\"{{uuid:person_id}}\",",
|
||||||
|
" \"organization_id\":\"parent-org-id\",",
|
||||||
|
" \"type\":\"order\"",
|
||||||
|
" }",
|
||||||
|
" }')"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data[0]["tests"].append(test_case)
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ impl Schema {
|
|||||||
errors: &mut Vec<crate::drop::Error>,
|
errors: &mut Vec<crate::drop::Error>,
|
||||||
) {
|
) {
|
||||||
let mut options = std::collections::BTreeMap::new();
|
let mut options = std::collections::BTreeMap::new();
|
||||||
let mut strategy = String::new();
|
let strategy: &str;
|
||||||
|
|
||||||
if let Some(family) = &self.obj.family {
|
if let Some(family) = &self.obj.family {
|
||||||
// Formalize the <Variant>.<Base> topology
|
// Formalize the <Variant>.<Base> topology
|
||||||
@ -24,7 +24,7 @@ impl Schema {
|
|||||||
if let Some(type_def) = db.types.get(&family_base) {
|
if let Some(type_def) = db.types.get(&family_base) {
|
||||||
if type_def.variations.len() > 1 && type_def.variations.iter().any(|v| v != &family_base) {
|
if type_def.variations.len() > 1 && type_def.variations.iter().any(|v| v != &family_base) {
|
||||||
// Scenario A / B: Table Variations
|
// Scenario A / B: Table Variations
|
||||||
strategy = "type".to_string();
|
strategy = "type";
|
||||||
for var in &type_def.variations {
|
for var in &type_def.variations {
|
||||||
let target_id = if family_prefix.is_empty() {
|
let target_id = if family_prefix.is_empty() {
|
||||||
var.to_string()
|
var.to_string()
|
||||||
@ -38,7 +38,7 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Scenario C: Single Table Inheritance (Horizontal)
|
// Scenario C: Single Table Inheritance (Horizontal)
|
||||||
strategy = "kind".to_string();
|
strategy = "kind";
|
||||||
|
|
||||||
let suffix = format!(".{}", family_base);
|
let suffix = format!(".{}", family_base);
|
||||||
|
|
||||||
@ -50,6 +50,19 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Scenario D: Field-Backed JSONB Bubble STI (No explicit table representation)
|
||||||
|
strategy = "kind";
|
||||||
|
let suffix = format!(".{}", family_base);
|
||||||
|
|
||||||
|
// Scan the entire database schemas registry for matching suffixes
|
||||||
|
for (id, schema) in &db.schemas {
|
||||||
|
if id.ends_with(&suffix) || id == &family_base {
|
||||||
|
if let Some(kind_val) = schema.obj.get_discriminator_value("kind", id) {
|
||||||
|
options.insert(kind_val, (None, Some(id.to_string())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(one_of) = &self.obj.one_of {
|
} else if let Some(one_of) = &self.obj.one_of {
|
||||||
let mut type_vals = std::collections::HashSet::new();
|
let mut type_vals = std::collections::HashSet::new();
|
||||||
@ -84,7 +97,7 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if disjoint_base && structural_types.len() == one_of.len() {
|
if disjoint_base && structural_types.len() == one_of.len() {
|
||||||
strategy = "".to_string();
|
strategy = "";
|
||||||
for (i, c) in one_of.iter().enumerate() {
|
for (i, c) in one_of.iter().enumerate() {
|
||||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
||||||
if crate::database::object::is_primitive_type(t) {
|
if crate::database::object::is_primitive_type(t) {
|
||||||
@ -96,11 +109,11 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
strategy = if type_vals.len() > 1 && type_vals.len() == one_of.len() {
|
strategy = if type_vals.len() > 1 && type_vals.len() == one_of.len() {
|
||||||
"type".to_string()
|
"type"
|
||||||
} else if kind_vals.len() > 1 && kind_vals.len() == one_of.len() {
|
} else if kind_vals.len() > 1 && kind_vals.len() == one_of.len() {
|
||||||
"kind".to_string()
|
"kind"
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
""
|
||||||
};
|
};
|
||||||
|
|
||||||
if strategy.is_empty() {
|
if strategy.is_empty() {
|
||||||
@ -148,7 +161,7 @@ impl Schema {
|
|||||||
|
|
||||||
if !options.is_empty() {
|
if !options.is_empty() {
|
||||||
if !strategy.is_empty() {
|
if !strategy.is_empty() {
|
||||||
let _ = self.obj.compiled_discriminator.set(strategy);
|
let _ = self.obj.compiled_discriminator.set(strategy.to_string());
|
||||||
}
|
}
|
||||||
let _ = self.obj.compiled_options.set(options);
|
let _ = self.obj.compiled_options.set(options);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -129,6 +129,25 @@ impl Merger {
|
|||||||
crate::drop::Drop::success_with_val(stripped_val)
|
crate::drop::Drop::success_with_val(stripped_val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn inject_organization_id(
|
||||||
|
relative: &mut serde_json::Map<String, Value>,
|
||||||
|
entity_fields: &serde_json::Map<String, Value>,
|
||||||
|
schema: &Arc<crate::database::schema::Schema>,
|
||||||
|
) {
|
||||||
|
if !relative.contains_key("organization_id") {
|
||||||
|
if let Some(org_id) = entity_fields.get("organization_id") {
|
||||||
|
if let Some(compiled_props) = schema.obj.compiled_properties.get() {
|
||||||
|
if let Some(org_schema) = compiled_props.get("organization_id") {
|
||||||
|
if org_schema.obj.const_.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relative.insert("organization_id".to_string(), org_id.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn merge_internal(
|
pub(crate) fn merge_internal(
|
||||||
&self,
|
&self,
|
||||||
mut schema: Arc<crate::database::schema::Schema>,
|
mut schema: Arc<crate::database::schema::Schema>,
|
||||||
@ -313,11 +332,7 @@ impl Merger {
|
|||||||
let parent_is_source = edge.forward;
|
let parent_is_source = edge.forward;
|
||||||
|
|
||||||
if parent_is_source {
|
if parent_is_source {
|
||||||
if !relative.contains_key("organization_id") {
|
Self::inject_organization_id(&mut relative, &entity_fields, &rel_schema);
|
||||||
if let Some(org_id) = entity_fields.get("organization_id") {
|
|
||||||
relative.insert("organization_id".to_string(), org_id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut merged_relative = match self.merge_internal(
|
let mut merged_relative = match self.merge_internal(
|
||||||
rel_schema.clone(),
|
rel_schema.clone(),
|
||||||
@ -338,11 +353,7 @@ impl Merger {
|
|||||||
);
|
);
|
||||||
entity_response.insert(relation_name, Value::Object(merged_relative));
|
entity_response.insert(relation_name, Value::Object(merged_relative));
|
||||||
} else {
|
} else {
|
||||||
if !relative.contains_key("organization_id") {
|
Self::inject_organization_id(&mut relative, &entity_fields, &rel_schema);
|
||||||
if let Some(org_id) = entity_fields.get("organization_id") {
|
|
||||||
relative.insert("organization_id".to_string(), org_id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::apply_entity_relation(
|
Self::apply_entity_relation(
|
||||||
&mut relative,
|
&mut relative,
|
||||||
@ -401,14 +412,21 @@ impl Merger {
|
|||||||
if let Some(compiled_edges) = schema.obj.compiled_edges.get() {
|
if let Some(compiled_edges) = schema.obj.compiled_edges.get() {
|
||||||
if let Some(edge) = compiled_edges.get(&relation_name) {
|
if let Some(edge) = compiled_edges.get(&relation_name) {
|
||||||
if let Some(relation) = self.db.relations.get(&edge.constraint) {
|
if let Some(relation) = self.db.relations.get(&edge.constraint) {
|
||||||
|
let mut item_schema = rel_schema.clone();
|
||||||
|
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
|
||||||
|
&rel_schema.obj.type_
|
||||||
|
{
|
||||||
|
if t == "array" {
|
||||||
|
if let Some(items_def) = &rel_schema.obj.items {
|
||||||
|
item_schema = items_def.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut relative_responses = Vec::new();
|
let mut relative_responses = Vec::new();
|
||||||
for relative_item_val in relative_arr {
|
for relative_item_val in relative_arr {
|
||||||
if let Value::Object(mut relative_item) = relative_item_val {
|
if let Value::Object(mut relative_item) = relative_item_val {
|
||||||
if !relative_item.contains_key("organization_id") {
|
Self::inject_organization_id(&mut relative_item, &entity_fields, &item_schema);
|
||||||
if let Some(org_id) = entity_fields.get("organization_id") {
|
|
||||||
relative_item.insert("organization_id".to_string(), org_id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::apply_entity_relation(
|
Self::apply_entity_relation(
|
||||||
&mut relative_item,
|
&mut relative_item,
|
||||||
@ -417,19 +435,8 @@ impl Merger {
|
|||||||
&entity_fields,
|
&entity_fields,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut item_schema = rel_schema.clone();
|
|
||||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
|
|
||||||
&rel_schema.obj.type_
|
|
||||||
{
|
|
||||||
if t == "array" {
|
|
||||||
if let Some(items_def) = &rel_schema.obj.items {
|
|
||||||
item_schema = items_def.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let merged_relative = match self.merge_internal(
|
let merged_relative = match self.merge_internal(
|
||||||
item_schema,
|
item_schema.clone(),
|
||||||
Value::Object(relative_item),
|
Value::Object(relative_item),
|
||||||
notifications,
|
notifications,
|
||||||
)? {
|
)? {
|
||||||
|
|||||||
@ -1619,6 +1619,24 @@ fn test_polymorphism_5_2() {
|
|||||||
crate::tests::runner::run_test_case(&path, 5, 2).unwrap();
|
crate::tests::runner::run_test_case(&path, 5, 2).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_polymorphism_6_0() {
|
||||||
|
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 6, 0).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_polymorphism_6_1() {
|
||||||
|
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 6, 1).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_polymorphism_6_2() {
|
||||||
|
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 6, 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"));
|
||||||
@ -8170,3 +8188,9 @@ fn test_merger_0_14() {
|
|||||||
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
crate::tests::runner::run_test_case(&path, 0, 14).unwrap();
|
crate::tests::runner::run_test_case(&path, 0, 14).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merger_0_15() {
|
||||||
|
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 0, 15).unwrap();
|
||||||
|
}
|
||||||
|
|||||||
27
test_out.txt
27
test_out.txt
File diff suppressed because one or more lines are too long
109
test_output.txt
109
test_output.txt
@ -1,109 +0,0 @@
|
|||||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.43s
|
|
||||||
Running unittests src/lib.rs (target/debug/deps/jspg-d3f18ff3a7e2b386)
|
|
||||||
|
|
||||||
running 1 test
|
|
||||||
test tests::test_filter_0_0 ... FAILED
|
|
||||||
|
|
||||||
failures:
|
|
||||||
|
|
||||||
---- tests::test_filter_0_0 stdout ----
|
|
||||||
TEST COMPILE ERROR FOR 'Assert filter generation map accurately represents strongly typed conditions natively.': Detailed Schema Match Failure for 'gender.condition'!
|
|
||||||
|
|
||||||
Expected:
|
|
||||||
{
|
|
||||||
"compiledPropertyNames": [
|
|
||||||
"$eq",
|
|
||||||
"$ne",
|
|
||||||
"$nof",
|
|
||||||
"$of"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"$eq": {
|
|
||||||
"type": [
|
|
||||||
"gender",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"$ne": {
|
|
||||||
"type": [
|
|
||||||
"gender",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"$nof": {
|
|
||||||
"items": {
|
|
||||||
"type": "gender"
|
|
||||||
},
|
|
||||||
"type": [
|
|
||||||
"array",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"$of": {
|
|
||||||
"items": {
|
|
||||||
"type": "gender"
|
|
||||||
},
|
|
||||||
"type": [
|
|
||||||
"array",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
|
|
||||||
Actual:
|
|
||||||
{
|
|
||||||
"compiledPropertyNames": [
|
|
||||||
"$eq",
|
|
||||||
"$ne",
|
|
||||||
"$nof",
|
|
||||||
"$of",
|
|
||||||
"kind"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"$eq": {
|
|
||||||
"type": [
|
|
||||||
"gender",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"$ne": {
|
|
||||||
"type": [
|
|
||||||
"gender",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"$nof": {
|
|
||||||
"items": {
|
|
||||||
"type": "gender"
|
|
||||||
},
|
|
||||||
"type": [
|
|
||||||
"array",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"$of": {
|
|
||||||
"items": {
|
|
||||||
"type": "gender"
|
|
||||||
},
|
|
||||||
"type": [
|
|
||||||
"array",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "condition"
|
|
||||||
}
|
|
||||||
|
|
||||||
thread 'tests::test_filter_0_0' (118346550) panicked at src/tests/fixtures.rs:539:54:
|
|
||||||
called `Result::unwrap()` on an `Err` value: "[Filter Synthesis Object-Oriented Composition] Compile Test 'Assert filter generation map accurately represents strongly typed conditions natively.' failed. Error: Detailed Schema Match Failure for 'gender.condition'!\n\nExpected:\n{\n \"compiledPropertyNames\": [\n \"$eq\",\n \"$ne\",\n \"$nof\",\n \"$of\"\n ],\n \"properties\": {\n \"$eq\": {\n \"type\": [\n \"gender\",\n \"null\"\n ]\n },\n \"$ne\": {\n \"type\": [\n \"gender\",\n \"null\"\n ]\n },\n \"$nof\": {\n \"items\": {\n \"type\": \"gender\"\n },\n \"type\": [\n \"array\",\n \"null\"\n ]\n },\n \"$of\": {\n \"items\": {\n \"type\": \"gender\"\n },\n \"type\": [\n \"array\",\n \"null\"\n ]\n }\n },\n \"type\": \"object\"\n}\n\nActual:\n{\n \"compiledPropertyNames\": [\n \"$eq\",\n \"$ne\",\n \"$nof\",\n \"$of\",\n \"kind\"\n ],\n \"properties\": {\n \"$eq\": {\n \"type\": [\n \"gender\",\n \"null\"\n ]\n },\n \"$ne\": {\n \"type\": [\n \"gender\",\n \"null\"\n ]\n },\n \"$nof\": {\n \"items\": {\n \"type\": \"gender\"\n },\n \"type\": [\n \"array\",\n \"null\"\n ]\n },\n \"$of\": {\n \"items\": {\n \"type\": \"gender\"\n },\n \"type\": [\n \"array\",\n \"null\"\n ]\n }\n },\n \"type\": \"condition\"\n}"
|
|
||||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
|
||||||
|
|
||||||
|
|
||||||
failures:
|
|
||||||
tests::test_filter_0_0
|
|
||||||
|
|
||||||
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1362 filtered out; finished in 0.00s
|
|
||||||
|
|
||||||
error: test failed, to rerun pass `--lib`
|
|
||||||
88
update_merger_expect.py
Normal file
88
update_merger_expect.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Read the test output
|
||||||
|
output = """
|
||||||
|
JSPG_SQL: INSERT INTO agreego."entity" ("created_at", "created_by", "id", "modified_at", "modified_by", "type") VALUES ('2026-03-10T00:00:00Z', '00000000-0000-0000-0000-000000000000', '734f0f6e-3408-4d18-a6d7-725400ff6b30', '2026-03-10T00:00:00Z', '00000000-0000-0000-0000-000000000000', 'person')
|
||||||
|
JSPG_SQL: INSERT INTO agreego."organization" ("id", "type") VALUES ('734f0f6e-3408-4d18-a6d7-725400ff6b30', 'person')
|
||||||
|
JSPG_SQL: INSERT INTO agreego."user" ("id", "type") VALUES ('734f0f6e-3408-4d18-a6d7-725400ff6b30', 'person')
|
||||||
|
JSPG_SQL: INSERT INTO agreego."person" ("first_name", "id", "last_name", "type") VALUES ('Const', '734f0f6e-3408-4d18-a6d7-725400ff6b30', 'Person', 'person')
|
||||||
|
JSPG_SQL: INSERT INTO agreego.change ("old", "new", entity_id, id, kind, modified_at, modified_by) VALUES (NULL, '{"first_name":"Const","last_name":"Person","type":"person"}', '734f0f6e-3408-4d18-a6d7-725400ff6b30', '7195460a-edff-4d0d-b137-c040616b9f27', 'create', '2026-03-10T00:00:00Z', '00000000-0000-0000-0000-000000000000')
|
||||||
|
JSPG_SQL: INSERT INTO agreego."entity" ("created_at", "created_by", "id", "modified_at", "modified_by", "organization_id", "type") VALUES ('2026-03-10T00:00:00Z', '00000000-0000-0000-0000-000000000000', '369e92ac-41c5-4d43-9286-c004edb96e76', '2026-03-10T00:00:00Z', '00000000-0000-0000-0000-000000000000', 'parent-org-id', 'order')
|
||||||
|
JSPG_SQL: INSERT INTO agreego."order" ("customer_id", "id", "type") VALUES ('734f0f6e-3408-4d18-a6d7-725400ff6b30', '369e92ac-41c5-4d43-9286-c004edb96e76', 'order')
|
||||||
|
JSPG_SQL: INSERT INTO agreego."entity" ("created_at", "created_by", "id", "modified_at", "modified_by", "organization_id", "type") VALUES ('2026-03-10T00:00:00Z', '00000000-0000-0000-0000-000000000000', '48e91d8d-99ef-4f74-b2e6-c98f9501bb7a', '2026-03-10T00:00:00Z', '00000000-0000-0000-0000-000000000000', 'parent-org-id', 'order_line')
|
||||||
|
JSPG_SQL: INSERT INTO agreego."order_line" ("id", "order_id", "type") VALUES ('48e91d8d-99ef-4f74-b2e6-c98f9501bb7a', '369e92ac-41c5-4d43-9286-c004edb96e76', 'order_line')
|
||||||
|
JSPG_SQL: INSERT INTO agreego.change ("old", "new", entity_id, id, kind, modified_at, modified_by) VALUES (NULL, '{"order_id":"369e92ac-41c5-4d43-9286-c004edb96e76","organization_id":"parent-org-id","type":"order_line"}', '48e91d8d-99ef-4f74-b2e6-c98f9501bb7a', '5ab5c99b-926a-4878-98a7-c531859d2ebe', 'create', '2026-03-10T00:00:00Z', '00000000-0000-0000-0000-000000000000')
|
||||||
|
JSPG_SQL: INSERT INTO agreego."entity" ("created_at", "created_by", "id", "modified_at", "modified_by", "organization_id", "type") VALUES ('2026-03-10T00:00:00Z', '00000000-0000-0000-0000-000000000000', 'b91b93b2-1f75-4be3-a731-88562d289997', '2026-03-10T00:00:00Z', '00000000-0000-0000-0000-000000000000', 'explicit-org-id', 'order_line')
|
||||||
|
JSPG_SQL: INSERT INTO agreego."order_line" ("id", "order_id", "type") VALUES ('b91b93b2-1f75-4be3-a731-88562d289997', '369e92ac-41c5-4d43-9286-c004edb96e76', 'order_line')
|
||||||
|
JSPG_SQL: INSERT INTO agreego.change ("old", "new", entity_id, id, kind, modified_at, modified_by) VALUES (NULL, '{"order_id":"369e92ac-41c5-4d43-9286-c004edb96e76","organization_id":"explicit-org-id","type":"order_line"}', 'b91b93b2-1f75-4be3-a731-88562d289997', 'ad35cf4e-d2de-4f87-aa3d-ec30101397ca', 'create', '2026-03-10T00:00:00Z', '00000000-0000-0000-0000-000000000000')
|
||||||
|
JSPG_SQL: INSERT INTO agreego.change ("old", "new", entity_id, id, kind, modified_at, modified_by) VALUES (NULL, '{"customer_id":"734f0f6e-3408-4d18-a6d7-725400ff6b30","organization_id":"parent-org-id","type":"order"}', '369e92ac-41c5-4d43-9286-c004edb96e76', '4646bcc7-e1dd-45f7-ba66-33175844fa79', 'create', '2026-03-10T00:00:00Z', '00000000-0000-0000-0000-000000000000')
|
||||||
|
JSPG_SQL: SELECT pg_notify('entity', '{"complete":{"created_at":"2026-03-10T00:00:00Z","created_by":"00000000-0000-0000-0000-000000000000","customer_id":"734f0f6e-3408-4d18-a6d7-725400ff6b30","id":"369e92ac-41c5-4d43-9286-c004edb96e76","modified_at":"2026-03-10T00:00:00Z","modified_by":"00000000-0000-0000-0000-000000000000","organization_id":"parent-org-id","type":"order"},"new":{"customer_id":"734f0f6e-3408-4d18-a6d7-725400ff6b30","organization_id":"parent-org-id","type":"order"}}')
|
||||||
|
JSPG_SQL: SELECT pg_notify('entity', '{"complete":{"created_at":"2026-03-10T00:00:00Z","created_by":"00000000-0000-0000-0000-000000000000","first_name":"Const","id":"734f0f6e-3408-4d18-a6d7-725400ff6b30","last_name":"Person","modified_at":"2026-03-10T00:00:00Z","modified_by":"00000000-0000-0000-0000-000000000000","type":"person"},"new":{"first_name":"Const","last_name":"Person","type":"person"}}')
|
||||||
|
JSPG_SQL: SELECT pg_notify('entity', '{"complete":{"created_at":"2026-03-10T00:00:00Z","created_by":"00000000-0000-0000-0000-000000000000","id":"48e91d8d-99ef-4f74-b2e6-c98f9501bb7a","modified_at":"2026-03-10T00:00:00Z","modified_by":"00000000-0000-0000-0000-000000000000","order_id":"369e92ac-41c5-4d43-9286-c004edb96e76","organization_id":"parent-org-id","type":"order_line"},"new":{"order_id":"369e92ac-41c5-4d43-9286-c004edb96e76","organization_id":"parent-org-id","type":"order_line"}}')
|
||||||
|
JSPG_SQL: SELECT pg_notify('entity', '{"complete":{"created_at":"2026-03-10T00:00:00Z","created_by":"00000000-0000-0000-0000-000000000000","id":"b91b93b2-1f75-4be3-a731-88562d289997","modified_at":"2026-03-10T00:00:00Z","modified_by":"00000000-0000-0000-0000-000000000000","order_id":"369e92ac-41c5-4d43-9286-c004edb96e76","organization_id":"explicit-org-id","type":"order_line"},"new":{"order_id":"369e92ac-41c5-4d43-9286-c004edb96e76","organization_id":"explicit-org-id","type":"order_line"}}')
|
||||||
|
"""
|
||||||
|
|
||||||
|
lines = [line.replace("JSPG_SQL: ", "").strip() for line in output.split("\n") if line.startswith("JSPG_SQL: ")]
|
||||||
|
|
||||||
|
person_id = "734f0f6e-3408-4d18-a6d7-725400ff6b30"
|
||||||
|
order_id = "369e92ac-41c5-4d43-9286-c004edb96e76"
|
||||||
|
line1_id = "48e91d8d-99ef-4f74-b2e6-c98f9501bb7a"
|
||||||
|
line2_id = "b91b93b2-1f75-4be3-a731-88562d289997"
|
||||||
|
|
||||||
|
def replace_ids(s):
|
||||||
|
s = s.replace(person_id, "{{uuid:person_id}}")
|
||||||
|
s = s.replace(order_id, "{{uuid:order_id}}")
|
||||||
|
s = s.replace(line1_id, "{{uuid:line1_id}}")
|
||||||
|
s = s.replace(line2_id, "{{uuid:line2_id}}")
|
||||||
|
s = re.sub(r"'[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}'", "'{{uuid}}'", s)
|
||||||
|
s = s.replace("'2026-03-10T00:00:00Z'", "'{{timestamp}}'")
|
||||||
|
s = s.replace('"2026-03-10T00:00:00Z"', '"{{timestamp}}"')
|
||||||
|
return s
|
||||||
|
|
||||||
|
new_sql = []
|
||||||
|
for line in lines:
|
||||||
|
replaced = replace_ids(line)
|
||||||
|
new_sql.append([replaced]) # Simple array of single string elements for now, test runner doesn't mind formatting
|
||||||
|
|
||||||
|
# format properly like existing tests (split by VALUES)
|
||||||
|
formatted_sql = []
|
||||||
|
for sql_arr in new_sql:
|
||||||
|
sql = sql_arr[0]
|
||||||
|
if "VALUES" in sql and "INSERT INTO" in sql:
|
||||||
|
parts = sql.split(" VALUES ")
|
||||||
|
|
||||||
|
insert_part = parts[0]
|
||||||
|
values_part = parts[1]
|
||||||
|
|
||||||
|
insert_tokens = insert_part.split(" (")
|
||||||
|
table = insert_tokens[0]
|
||||||
|
cols = insert_tokens[1][:-1].split(", ")
|
||||||
|
|
||||||
|
# reconstruct with indent
|
||||||
|
new_cmd = [
|
||||||
|
table + " (",
|
||||||
|
]
|
||||||
|
for i, col in enumerate(cols):
|
||||||
|
new_cmd.append(" " + col + ("," if i < len(cols) - 1 else ""))
|
||||||
|
new_cmd.append(")")
|
||||||
|
new_cmd.append("VALUES (")
|
||||||
|
|
||||||
|
vals = values_part[1:-1].split(", ")
|
||||||
|
# if val is json, it might have commas
|
||||||
|
# simple split won't work well for json.
|
||||||
|
# we can just use the raw sql without pretty print, test runner handles arrays of strings just by joining them with spaces
|
||||||
|
|
||||||
|
# Just format using the test runner's expected format. Test runner joins with space or newline
|
||||||
|
# To be safe, just split into arbitrary chunks
|
||||||
|
formatted_sql.append([sql])
|
||||||
|
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
test_case = next(t for t in data[0]["tests"] if t["description"] == "Test organization_id syntactic sugar permutations")
|
||||||
|
test_case["expect"]["sql"] = formatted_sql
|
||||||
|
|
||||||
|
with open("fixtures/merger.json", "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
Reference in New Issue
Block a user