Summary
A synthesized view (projection or entity read-view) is rigidly SELECT <cols> FROM <base> [LEFT OUTER JOIN …] [GROUP BY …] — emitViewDdl (view-ddl-emit.ts:237-257) has no outer WHERE slot, and ViewSpec (view-spec.ts:147-153) has no filter/predicate field. The shipped attr.filter AST is reachable only scoped inside an aggregate's / first's @filter (filtering aggregate inputs / subquery rows), never as a filter on which base rows the view returns.
So a legacy view with a row filter cannot be modeled, and because migrate is all-or-nothing (extract-view-spec.ts:226-231) a single such view blocks the whole run:
… WHERE status = 'active'
… WHERE type = 0
… WHERE flag IS NULL OR flag = 1
… WHERE deleted_at IS NULL -- soft-delete: extremely common, not legacy-specific
This is a large, universal class (row scoping, soft-delete, status/type views), and #195's four origin capabilities explicitly do not cover it (roadmap.md: "the four capabilities … do not remove the all-or-nothing abort").
Proposal
A projection/read-view-level @filter (or a view.filter child) that carries the same closed, 5-port attr.filter AST already shipped (eq/ne/gt/gte/lt/lte/like/in/isNull composed with and/or), resolved against the projection's / base's own fields, and lowered to an outer WHERE in emitViewDdl. Semantic (field + operator) — never raw SQL or physical columns — exactly like origin.aggregate @filter resolves today (extract-view-spec.ts:73).
{ "object.projection": { "name": "ActiveOrders", "children": [
{ "source.rdb": { "@kind": "view", "@view": "v_active_orders" } },
{ "field.long": { "name": "id", "extends": "Order.id" } },
{ "attr.filter": { "status": { "eq": "active" } } }
] } }
→ … FROM orders o WHERE o.status = 'active'.
Backend-agnostic (per the #195 lens)
The same predicate lowers to a Mongo $match, a search filter clause, or an in-process rows.filter(pred) — one semantic contract, N lowerings. Nothing here is RDB-only.
Composition
The filter scopes base rows, so it emits a WHERE before any GROUP BY (a post-aggregate HAVING is a separate, later concern). It must compose with the origin.first correlated-subquery shape (view-ddl-emit.ts:116-131).
Scope
Reuse the attr.filter AST + renderer (already 5-port); add the outer-WHERE slot to ViewSpec + emitViewDdl; validation resolves filter fields against the projection/base symbol table (dangling ref → load error); golden-DDL + persistence conformance fixtures (including the zero-match case). This is the single largest functional gap blocking full meta migrate adoption for filtered views.
Summary
A synthesized view (projection or entity read-view) is rigidly
SELECT <cols> FROM <base> [LEFT OUTER JOIN …] [GROUP BY …]—emitViewDdl(view-ddl-emit.ts:237-257) has no outerWHEREslot, andViewSpec(view-spec.ts:147-153) has no filter/predicate field. The shippedattr.filterAST is reachable only scoped inside an aggregate's /first's@filter(filtering aggregate inputs / subquery rows), never as a filter on which base rows the view returns.So a legacy view with a row filter cannot be modeled, and because migrate is all-or-nothing (
extract-view-spec.ts:226-231) a single such view blocks the whole run:This is a large, universal class (row scoping, soft-delete, status/type views), and #195's four origin capabilities explicitly do not cover it (
roadmap.md: "the four capabilities … do not remove the all-or-nothing abort").Proposal
A projection/read-view-level
@filter(or aview.filterchild) that carries the same closed, 5-portattr.filterAST already shipped (eq/ne/gt/gte/lt/lte/like/in/isNullcomposed withand/or), resolved against the projection's / base's own fields, and lowered to an outerWHEREinemitViewDdl. Semantic (field + operator) — never raw SQL or physical columns — exactly likeorigin.aggregate @filterresolves today (extract-view-spec.ts:73).{ "object.projection": { "name": "ActiveOrders", "children": [ { "source.rdb": { "@kind": "view", "@view": "v_active_orders" } }, { "field.long": { "name": "id", "extends": "Order.id" } }, { "attr.filter": { "status": { "eq": "active" } } } ] } }→
… FROM orders o WHERE o.status = 'active'.Backend-agnostic (per the #195 lens)
The same predicate lowers to a Mongo
$match, a search filter clause, or an in-processrows.filter(pred)— one semantic contract, N lowerings. Nothing here is RDB-only.Composition
The filter scopes base rows, so it emits a
WHEREbefore anyGROUP BY(a post-aggregateHAVINGis a separate, later concern). It must compose with theorigin.firstcorrelated-subquery shape (view-ddl-emit.ts:116-131).Scope
Reuse the
attr.filterAST + renderer (already 5-port); add the outer-WHERE slot toViewSpec+emitViewDdl; validation resolves filter fields against the projection/base symbol table (dangling ref → load error); golden-DDL + persistence conformance fixtures (including the zero-match case). This is the single largest functional gap blocking fullmeta migrateadoption for filtered views.