You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit dbd7b86
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -40,7 +40,7 @@ _Last refreshed 2026-08-09._
40
40
-**Kotlin** — `codegen-kotlin` (KotlinPoet on JVM): entity + Exposed table + Spring controller + payload + relations + filter allowlist + validator + stored-proc + output-parser generators. `integration-tests-kotlin` runs the persistence-conformance corpus through Exposed against Testcontainers Postgres.
41
41
42
42
**Cross-port conformance corpora** (every port runs the shared corpus):
43
-
- Metamodel: `fixtures/conformance/` (270 fixtures; 19 shared corpora in total — per-corpus counts + the corpus x port matrix live in `docs/CONFORMANCE.md`). TS / C# / Java / Python all green.
43
+
- Metamodel: `fixtures/conformance/` (271 fixtures; 19 shared corpora in total — per-corpus counts + the corpus x port matrix live in `docs/CONFORMANCE.md`). TS / C# / Java / Python all green.
- Persistence: `fixtures/persistence-conformance/`. **Query** scenarios run on every port (TS / C# / Java / Kotlin / Python), each provisioning its test DB by executing the committed, TS-produced `canonical/schema.postgres.sql` (Postgres only — Derby dropped for the cross-port query corpus, ADR-0015). The **migration** scenarios are exercised by **TS only** (TS owns schema migrations). **The corpus now gates WRITES, not just reads (SP-H):** an `op: roundtrip` scenario type INSERTs through each port's runtime/ORM write codec (NOT raw SQL), reads the row back, and asserts the wire-normalized value. The `AllTypes` entity (`roundtrip-all-types.yaml`) carries one field of **every** persistable `field.*` subtype — string/int/long/double/float/decimal/boolean/date/time/timestamp(+tz)/currency/enum/uuid/object — plus an **array-of-VO** `field.object @isArray @storage:jsonb` column (`labels`, written as 2-element / empty-`[]` / single-element arrays across the three rows) — so every subtype write+read (incl. the array-of-value-object jsonb codec) round-trips through every port against Testcontainers PG. (`field.byte`/`field.short`/`field.class` were cut as non-functional registration-only stubs — the matrix tracks only genuinely-supported subtypes; see `fixtures/registry-conformance/README.md` → "Per-subtype write-round-trip matrix".)
46
46
- API-contract: `fixtures/api-contract-conformance/`. TS / C# / Java / Kotlin / Python all green — each port runs **two lanes**: a hand-rolled reference server AND its **generated** API artifact booted over HTTP (the deployed controller/routes; TS+C# full-stack vs Testcontainers PG, Java/Kotlin/Python generated controller + in-memory repo behind the consumer seam). The generated fan-out found 10 real deployment bugs golden snapshots missed.
Copy file name to clipboardExpand all lines: docs/features/relationships.md
+35-6Lines changed: 35 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -102,14 +102,42 @@ metadata:
102
102
| `@cardinality` | `relationship.composition` | `one` / `many` | Multiplicity on the target side |
103
103
| `@fields` | `identity.reference` | One field name or array | The FK column(s) on this entity |
104
104
| `@references` | `identity.reference` | Entity name | The target entity (PK on the other side) |
105
-
| `@onDelete` | `relationship.*` only | `cascade` / `set-null` / `restrict` / `no-action` | RDB referential action. Default derives from the subtype: composition -> `cascade`, aggregation -> `set-null`, association -> `restrict`. |
106
-
| `@onUpdate` | `relationship.*` only | same as `@onDelete` (default `cascade`) | RDB referential action |
105
+
| `@onDelete` | `relationship.*` and `identity.reference` | `cascade` / `set-null` / `restrict` / `no-action` | RDB referential action. Default derives from the relationship subtype: composition -> `cascade`, aggregation -> `set-null`, association -> `restrict`. |
106
+
| `@onUpdate` | `relationship.*` and `identity.reference` | same as `@onDelete` (default `cascade` when a relationship correlates) | RDB referential action |
107
107
108
108
`@onDelete`/ `@onUpdate` are registered on `relationship.association` /
109
-
`aggregation`/ `composition`. They are **not** attributes of `identity.reference` —
110
-
putting one there fails load with `ERR_UNKNOWN_ATTR`. Declare the action on the
111
-
relationship; the reference stays a pure FK declaration (`@fields`, `@references`,
112
-
`@constraintName`, `@enforce`).
109
+
`aggregation`/ `composition` **and** — as the explicit per-FK override
— on `identity.reference` itself. Resolution precedence for the FK's emitted
112
+
action, highest first:
113
+
114
+
1. `@onDelete` / `@onUpdate` declared directly on the `identity.reference` —
115
+
the reference IS the FK, so the action may be declared right where the FK
116
+
is. This is the only way to put an action on a **reference-only FK** (no
117
+
relationship node) or on an **M:N junction's** FK sides (no relationship
118
+
ever correlates with a junction FK).
119
+
2. A relationship declared on the FK-owning (child) entity targeting the
120
+
referenced entity (matched package-aware, so bare and FQN spellings pair
121
+
correctly; an M:N `@through` relationship never correlates with a direct
122
+
FK): its explicit action, else its subtype default.
123
+
3. A relationship declared on the **referenced (parent) entity** pointing back
124
+
at the FK-owning entity — the canonical parent-side authoring above
125
+
(`Author` declares `posts`): its explicit action, else its subtype default.
126
+
Guards: the M:N exclusion again; when the child holds more than one FK to
127
+
the same parent the parent-side relationship contributes to none of them
128
+
(it cannot say which FK carries the ownership edge); and an inferred
129
+
set-null default (aggregation, no explicit `@onDelete`) on a NOT NULL FK
130
+
contributes nothing — SET NULL cannot fire there (an explicit `set-null`
131
+
instead fails migration generation loudly, telling you to make the FK
132
+
nullable).
133
+
4. None of the above → the FK is emitted with no `ON DELETE` / `ON UPDATE`
134
+
clause (SQL `NO ACTION`).
135
+
136
+
Prefer declaring the action on the relationship (the subtype carries the
137
+
semantics and the default); reach for the reference-level attr only when no
138
+
relationship exists or a single FK needs to deviate. The values are
139
+
load-validated everywhere — a misspelling (e.g. the retired `setnull` alias)
140
+
fails load with `ERR_BAD_ATTR_VALUE`.
113
141
114
142
The kebab-case metamodel values map to the SCREAMING_SNAKE forms in Exposed / EF
115
143
Core / the target ORM or DDL at codegen time.
@@ -248,6 +276,7 @@ The following conformance fixtures gate this feature's behavior across ports:
248
276
-[`fixtures/conformance/relationship-one-to-many/`](../../fixtures/conformance/relationship-one-to-many/) — `relationship.composition` 1:N with the parent owning the collection
249
277
-[`fixtures/conformance/identity-reference-simple/`](../../fixtures/conformance/identity-reference-simple/) — `identity.reference` declares the FK column-set on the child
250
278
-[`fixtures/conformance/source-rdb-referential-actions/`](../../fixtures/conformance/source-rdb-referential-actions/) — `@onDelete` / `@onUpdate` on relationships
279
+
-[`fixtures/conformance/identity-reference-referential-actions/`](../../fixtures/conformance/identity-reference-referential-actions/) — the parent-side `@cardinality: many` composition (subtype-default cascade) + `@onDelete` / `@onUpdate` declared directly on `identity.reference` (ADR-0047)
0 commit comments