Skip to content

Projection / entity read-view: semantic row-scope @filter (view-level WHERE) reusing the shipped attr.filter AST #207

Description

@dmealing

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 = 0WHERE flag IS NULL OR flag = 1WHERE 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions