Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 120 additions & 11 deletions docs/11-proposals/PROPOSAL_authorable_message_definitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,28 @@ MessageDefinitions$MessageDefinitionCollection 36 Name, Documentation, Exc
MessageDefinitions$ExposedAssociation 933 (same, plus Association + Entity)
```

Collections are small: 28 of 36 hold a single definition, 4 hold two, 3 hold
four, 1 holds eight.
Collections are small in one dimension and not at all in the other. 28 of 36
hold a single definition, 4 hold two, 3 hold four, 1 holds eight — but the
definitions themselves are **deep**:

| nesting depth | elements | |
|---|---|---|
| 0 (the definition root) | 56 | |
| 1 | 356 | |
| 2 | 265 | |
| 3 | 142 | |
| 4 | 208 | |
| 5 | 477 | |
| 6 | 974 | |
| **7** | **2,208** | the maximum observed |

Object elements carry a mean of 4.7 members, and the tail is long — 93 have 15
members, one has 22.

That shape decides more of this proposal than anything else. A definition is not
a flat field list you would happily restate; a whole-document `CREATE OR MODIFY`
is a poor tool for "expose one more attribute", and any addressing scheme for
targeted edits has to reach seven levels down.

### Almost every property is a constant or is derived

Expand Down Expand Up @@ -152,6 +172,8 @@ Following [ADR-0003](../13-decisions/0003-mdl-is-sql-shaped.md) and

### Statement set

**Whole document:**

| statement | notes |
|---|---|
| `CREATE [OR MODIFY] MESSAGE DEFINITION COLLECTION M.Name ( ... )` | `OR MODIFY` preserves the UUID — mappings reference the collection by qualified name, and a fresh document would break every `WITH MESSAGE DEFINITION` |
Expand All @@ -161,6 +183,69 @@ Following [ADR-0003](../13-decisions/0003-mdl-is-sql-shaped.md) and

`FOLDER 'path'` on create, as every other document type takes.

**Targeted edits.** `CREATE OR MODIFY` alone is not enough, for the reason the
depth table shows: adding one attribute to a definition seven levels deep would
mean restating the whole document, which is precisely the diff-unfriendliness
[ADR-0003](../13-decisions/0003-mdl-is-sql-shaped.md) argues against, and is why
`ALTER ENTITY ADD ATTRIBUTE` exists rather than only `CREATE OR MODIFY ENTITY`.

Definitions within a collection — this is the 8 collections that hold more than
one:

```sql
alter message definition collection Sales.MD_Order
add definition Line for Sales.Line as 'Lines' ( Sku, Quantity );

alter message definition collection Sales.MD_Order drop definition Line;
alter message definition collection Sales.MD_Order rename definition Line to OrderLine;
```

Members within a definition — this applies to all 36:

```sql
alter message definition Sales.MD_Order.Order add member Total;
alter message definition Sales.MD_Order.Order
add member Sales.Order_Line/Sales.Line as 'Lines' ( Sku, Quantity );

alter message definition Sales.MD_Order.Order drop member Total;
alter message definition Sales.MD_Order.Order set member Total as 'GrandTotal';
```

Three deliberate choices here:

- **The definition is addressed as `Module.Collection.Definition`** — the same
three-part reference `WITH MESSAGE DEFINITION` already takes. Nothing new to
learn, and the two cannot drift apart.
- **`SET member … AS`, not `RENAME member … TO`.** `ALTER ENTITY RENAME
ATTRIBUTE` renames the attribute *in the model* and rewrites every reference to
it. This changes only the element's `ExposedName` and touches nothing else, so
borrowing `RENAME` would promise something far larger than it does. `SET` is
the established verb for changing a property, and `as` for a name-to-name
mapping.
- **`IF NOT EXISTS` / `IF EXISTS`** on add and drop, so a definition script
re-runs cleanly — the same treatment `ALTER ENTITY` gives attributes.

**Reaching a nested member.** Members live up to seven levels down, so the
address needs a path. It is written in **exposed names**, the names the document
itself carries:

```sql
alter message definition Sales.MD_Order.Order
add member Price in Lines/Prices;

alter message definition Sales.MD_Order.Order
drop member Sku in Lines;
```

`in <path>` rather than a `/`-joined member name, because `/` already means
"association to entity" inside a member (`Sales.Order_Line/Sales.Line`) and
overloading it in the same clause would be ambiguous to a reader even where the
grammar could tell them apart.

New members append. `describe` emits stored order, so the round trip is stable
either way; a positional form (`before` / `after`) is deliberately not proposed
until something needs it.

## The name of a repeating element, and why not to guess it

Studio Pro **pluralises** `ExposedName` for a repeating element while keeping
Expand Down Expand Up @@ -196,10 +281,11 @@ makes the cardinality visible at the point the author writes it.

## Scope

**In:** the four statements above; attributes, associations in both directions,
nested definitions to arbitrary depth; `OR MODIFY`; folder placement; the
`describe` round trip; a check-time rule for members that do not exist on the
entity (the shape `MDL-JSON01` established).
**In:** the statements above — whole-document and targeted; attributes,
associations in both directions, nesting to the depth the corpus shows;
`OR MODIFY`; folder placement; the `describe` round trip; a check-time rule for
members that do not exist on the entity, and for an `in` path that reaches
nothing (the shape `MDL-JSON01` / `MDL-JSON02` established).

**Out:**

Expand All @@ -210,24 +296,44 @@ entity (the shape `MDL-JSON01` established).
4,686 of 4,686. Add them when a document needs them, not before.
- **Published message definitions** (the Business Events surface). A different
document.
- **A wildcard member** (`definition Order for Sales.Order ( * )` to expose every
attribute). Studio Pro's UI is a tree of checkboxes, so "tick everything" is a
natural thing to want, and with a mean of 4.7 members and a tail out to 22 it
would save real typing. But `*` appears nowhere else in MDL, and no measurement
here says anyone wants it — proposing it now would be designing for a shape
with no document behind it. Raised as an open question instead.

## Open questions for review

1. **Require `as` on a repeating element?** See above — it removes the silent
divergence from Studio Pro at the cost of friction.
2. **A wildcard member.** Worth it, or speculative?
3. **Inherited attributes** — refuse, or block the proposal until measured?
4. **Legacy engine** — author there too, or modelsdk-only like rules, menus and
layouts?

## Implementation outline

Full-stack, per the CLAUDE.md checklist:

1. **Grammar** — `createMessageDefinitionCollectionStatement`, plus `drop` /
`describe` / `show`. New keyword: none required; `MESSAGE` and `DEFINITION`
already exist (`WITH MESSAGE DEFINITION`), and `COLLECTION` is used by image
and icon collections.
`describe` / `show`, and `alterMessageDefinitionCollectionStatement` /
`alterMessageDefinitionStatement`. New keyword: none required; `MESSAGE` and
`DEFINITION` already exist (`WITH MESSAGE DEFINITION`), `COLLECTION` is used
by image and icon collections, and `IN` / `AS` / `SET` / `ADD` / `DROP` /
`RENAME` are all in the lexer.
2. **AST** — `CreateMessageDefinitionCollectionStmt`, with a member tree that
distinguishes attribute from association by the presence of a qualified name.
3. **Visitor** — bridge, as usual.
4. **Backend** — `CreateMessageDefinitionCollection` /
`UpdateMessageDefinitionCollection` / `DeleteMessageDefinitionCollection` on
`MappingBackend`, beside the existing `ListMessageDefinitionCollections`;
implemented in both engines and stubbed in the mock.
5. **Executor** — thin handler; the element tree is built from the domain model,
which is where `MaxOccurs` and `PrimitiveType` are resolved.
5. **Executor** — thin handlers. The element tree is built from the domain model,
which is where `MaxOccurs` and `PrimitiveType` are resolved. The ALTER path
edits the stored document rather than rebuilding it, so an untouched
definition is never round-tripped through the describer — the argument that
made `ALTER LAYOUT` a capability rather than a convenience.
6. **`describe`** — re-executable, deterministic order.

The document is written through the codec on the modelsdk engine. Whether the
Expand All @@ -252,6 +358,9 @@ uniform.
4. **The constants.** One test asserting `MinOccurs`/`Nillable`/`MaxLength`/…
against the measured values, so a future change that starts varying one of
them fails loudly rather than drifting.
5. **ALTER leaves the rest alone.** Add one member to a pinned collection and
assert every *other* element is byte-identical — the control that separates a
targeted edit from a rebuild that happened to produce the same thing.

## A note on measuring this document type

Expand Down
Loading