Skip to content

Commit ce98902

Browse files
dmealingclaude
andcommitted
docs(#206): entity read-view on-ramp for legacy SELECT A.* views + decision table
source-kinds.md pointed straight at object.projection for any read-only view, so an adopter migrating a legacy SQL-view-heavy schema reached for a projection and immediately hit "enumerate every column." But the most common legacy shape — `SELECT A.*, extras FROM A JOIN …` — is not a projection; it is the entity with a read route (FR-024 §7 entity read-view, shipped + gated by error-derived-field-no-read-source across all ports). Surfaces entity read-views as the FIRST answer for that shape (verified-loadable example: writable table source + non-primary view source + one derived origin.passthrough for the extra; writes→table, reads→view, DDL emitted by the same assembly logic as a projection view), adds an entity-vs-projection decision table, and states the two boundaries that still push to a projection: renamed base columns (one @column per paradigm) and row-filtered views (no view-level WHERE yet — tracked by the #207 @filter FR). Docs-only; no metamodel change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGQ7oSuNcjhsMHWwZzhBwr
1 parent 905b327 commit ce98902

1 file changed

Lines changed: 65 additions & 2 deletions

File tree

docs/features/source-kinds.md

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,69 @@ in a non-primary read `@role`.
4545
`@kind` is omitted — it defaults to `"table"`. `@table` is the physical table name
4646
(NOT `@name`).
4747

48+
### Entity read-view — the `SELECT A.*, extras` legacy shape
49+
50+
The single most common legacy view is an entity's **own** columns plus a joined extra:
51+
52+
```sql
53+
CREATE VIEW order_with_customer AS
54+
SELECT o.*, c.name AS customer_name
55+
FROM orders o JOIN customers c ON o.customer_id = c.id;
56+
```
57+
58+
This is **not** a projection — it is the `Order` entity *with a read route*. Reach for an
59+
**entity read-view** first: an `object.entity` that keeps its writable `table` source and
60+
adds a **non-primary** read-only `view` source, declaring only the *extra* as a derived
61+
(`origin.*`) field. The entity's own field set **is** the `o.*`, so you re-state nothing
62+
but the extra. Writes route to the table (derived fields don't exist there and are excluded
63+
from the write codecs); reads route to the view; the `CREATE VIEW` DDL is emitted by
64+
`meta migrate` from the **same assembly logic** as a projection view — one emitter, two hosts.
65+
66+
```json
67+
{
68+
"object.entity": {
69+
"name": "Order",
70+
"children": [
71+
{ "source.rdb": { "@role": "primary", "@table": "orders" } },
72+
{ "source.rdb": { "@role": "replica", "@kind": "view", "@table": "v_order_with_customer" } },
73+
{ "field.long": { "name": "id" } },
74+
{ "field.long": { "name": "customerId", "@required": true } },
75+
{ "field.string": { "name": "customerName",
76+
"children": [ { "origin.passthrough": { "@from": "Customer.name" } } ] } },
77+
{ "relationship.association": { "name": "customer", "@objectRef": "Customer", "@cardinality": "one" } },
78+
{ "identity.primary": { "name": "pk", "@fields": "id" } },
79+
{ "identity.reference": { "name": "ref_customer", "@fields": ["customerId"], "@references": "Customer" } }
80+
]
81+
}
82+
}
83+
```
84+
85+
A derived field must be **providable** — a read-capable source must be able to carry it. A
86+
derived field on a table-only entity (no read source) is a load error
87+
(`ERR_DERIVED_FIELD_NO_READ_SOURCE`).
88+
89+
### Entity read-view vs projection — which to reach for
90+
91+
| Use an **entity read-view** when | Use a **projection** when |
92+
|---|---|
93+
| the extras sit over the entity's **own** table | it is an independent **exposure contract** |
94+
| same read trust-domain — a new entity field appearing in the view is correct, not a leak | a subset / **renamed** columns / versioned / external consumers |
95+
| you can still INSERT the base and it is the record of truth | keyless, multi-base, or proc-backed; all-derived; borrowed / no identity |
96+
97+
Two boundaries still push you to a projection (or a later FR):
98+
99+
- **Renamed base columns.** A field carries one `@column` per paradigm, so renaming a base
100+
column *in the view* is the projection's whole point — a projection field maps an
101+
`extends`-bound source column to a new `@column`.
102+
- **Row-filtered views** (`… WHERE status = 'active'`, soft-delete) have no view-level `WHERE`
103+
slot yet — tracked by the view-level `@filter` FR ([#207](https://github.com/metaobjectsdev/metaobjects/issues/207)).
104+
48105
### View — projection over an existing entity
49106

50-
A read-only view is declared as an `object.projection`, not an `object.entity` (an
51-
entity's primary source must be writable). A projection's identity is optional and, when
107+
When the read shape is an **independent exposure contract** (a subset, renamed columns, a
108+
versioned or externally-consumed shape — see the decision table above) rather than the
109+
owning entity's own columns, declare it as an `object.projection`, not an `object.entity`
110+
(an entity's primary source must be writable). A projection's identity is optional and, when
52111
present, MUST extend an entity identity; the example below omits it (a keyless read
53112
model).
54113

@@ -251,6 +310,10 @@ The following conformance fixtures gate this feature's behavior across ports:
251310
- [`fixtures/conformance/source-db-view-projection/`](../../fixtures/conformance/source-db-view-projection/) — view with origins
252311
- [`fixtures/conformance/source-db-view-with-schema/`](../../fixtures/conformance/source-db-view-with-schema/)`@schema` on view
253312

313+
**Entity read-view (derived field must be providable)**
314+
315+
- [`fixtures/conformance/error-derived-field-no-read-source/`](../../fixtures/conformance/error-derived-field-no-read-source/) — a derived (`origin.*`) field on a table-only entity (no read-capable source) is `ERR_DERIVED_FIELD_NO_READ_SOURCE`
316+
254317
**Multi-source via `@role`**
255318

256319
- [`fixtures/conformance/source-multi-source-roles/`](../../fixtures/conformance/source-multi-source-roles/) — primary + secondary sources on one object

0 commit comments

Comments
 (0)