Skip to content

schema.yaml: LQL support inside functions[].body (currently raw SQL only) [NAP Tier 1] #43

@MelbourneDeveloper

Description

@MelbourneDeveloper

DataProvider v0.9.8-beta closed #33 (declarative `CREATE FUNCTION`). The `body:` field on `functions[]` accepts raw SQL — that's the only place NAP still ships SQL through migration after the operator's "no loose SQL" mandate.

NAP request: allow LQL in `functions[].body` so the entire migration is portable. Today every NAP helper is forced to be SQL:

```yaml
functions:

  • name: is_member
    arguments: [{name: u, type: uuid}, {name: t, type: uuid}]
    returns: boolean
    securityDefiner: true
    body: |
    SELECT EXISTS (
    SELECT 1 FROM public.tenant_members
    WHERE user_id = u AND tenant_id = t
    )
    ```

The same body in LQL (per the README's `exists(pipeline)` syntax used in policy predicates):

```yaml
functions:

  • name: is_member
    arguments: [{name: u, type: uuid}, {name: t, type: uuid}]
    returns: boolean
    securityDefiner: true
    bodyLql: |
    exists(
    tenant_members
    |> filter(fn(m) => m.user_id = u and m.tenant_id = t)
    )
    ```

Acceptance criteria:

  • New optional field (suggested name: `bodyLql:`) accepts LQL pipeline expressions plus simple `SELECT expr` shapes.
  • Mutually exclusive with `body:`. Schema validator rejects both.
  • Postgres transpilation emits the same SQL the existing LQL policy-predicate transpiler does (e.g. `SELECT EXISTS (SELECT 1 FROM public.tenant_members WHERE m.user_id = u AND m.tenant_id = t)`).
  • SQLite transpilation deferred until SQLite RLS emulation needs it.

NAP Tier 1. Until this lands, NAP's `schema.yaml` ships 5 SECURITY DEFINER functions with raw SQL bodies — a violation of the operator's "every migration in YAML, no loose SQL" rule. `body:` (raw SQL) stays as the documented escape hatch for platform-specific stuff that LQL can't express, but the default path must be LQL.

Pairs with the GUC-reader gap: separate issue tracking the need for an LQL builtin to read arbitrary session settings (Postgres GUCs / SQLite context-table rows) so `app_tenant_id()` / `app_user_id()` can also be written in LQL.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions