Skip to content

Explicit syntax for a legitimately optional filter condition (design only — no implementation until a real request) #3862

Description

@os-zhuang

Status: design placeholder. Do not implement until a real user asks. Filed so the next person who hits the guard finds the reasoning instead of re-deriving it — or worse, relaxing the guard.

The gap

Since #3810, a CRUD node refuses to run when interpolation erased any authored filter condition. That is correct as a default: an absent condition does not narrow a query, it widens it (see the decision note on #3810 for why get_record keeps the hard failure).

But it closes off a shape that is sometimes genuinely intended:

Filter by campaign if the run received one; otherwise return all rows.

Today that is inexpressible. { campaign: '{campaignId}' } on a run without campaignId is indistinguishable — to the guard and to a reader — from { campaign: '{campiagnId}' }, a typo. Both erase a condition; the guard refuses both, which is the right call precisely because it cannot tell them apart.

The constraint any design must respect

Fail-closed stays the default. The author must say "this condition is optional" explicitly, at authoring time. An implicit or inferred optionality re-opens the fail-open direction #3810 closed, and the typo case is far more common than the optional case.

Options sketched (not chosen)

A. An $optional marker on the condition

filter: { campaign: { $optional: '{campaignId}' } }

B. Branch to two get_record nodes

  • Zero new syntax; a decision node routes on whether the variable is set, each branch carrying its own fully-literal filter.
  • Both queries are explicit and greppable — arguably the clearest thing to read.
  • Verbose, and it duplicates the rest of the filter across both branches. Gets ugly with two or more optional conditions (2^N branches).

C. Author-side default at the variable, not the filter

  • Give the flow variable a declared default (campaignId ?? '*') so the token always resolves.
  • Requires a filter semantic for "match anything," which is a bigger and riskier change than either of the above.

Recommendation if this ever gets picked up

Start from A, and require it to be inert unless explicitly written. B already works today with no platform change — point people at it in the meantime; it is the honest answer for the one-or-two-condition case.

Do not

Relax the #3810 guard or downgrade it to a warning to serve this use case. That trades a deterministic build/run failure for a silent data-widening bug, and the optional case is the rare one. See the decision note on #3810.

Related

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