Skip to content

feat(intent): a register lookup can filter the register with a constant predicate - #6858

Merged
delchev merged 18 commits into
eclipse-dirigible:masterfrom
TIVMOF:fix/filter-register
Aug 20, 2026
Merged

feat(intent): a register lookup can filter the register with a constant predicate#6858
delchev merged 18 commits into
eclipse-dirigible:masterfrom
TIVMOF:fix/filter-register

Conversation

@TIVMOF

@TIVMOF TIVMOF commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds an optional where: { <register property>: <literal> } to resolves:, ANDed onto the same Criteria as the match keys.

A resolves: lookup had no way to narrow its register by a constant. Every match: pair binds a register column to a column of the record, so "and only the rows that are still valid" had no form at all — and a register is exactly the kind of table that keeps its corrections:

id vehicle driver validFrom validTo status
1 CA1234AB Petrov 2026-01-01 2026-06-30 CANCELLED
2 CA1234AB Ivanov 2026-01-01 2026-06-30 ACTIVE

A fine on 2026-03-14 should resolve to Ivanov. Both rows match and both cover the date, so the lookup reported ambiguous, routed to manual handling and logged "multiple matches" — for a register with exactly one valid answer. Every correction ever made earned that, quietly, and it got worse the longer the register lived.

resolves:
  - name: identifyDriver
    event: { onCreate: Fine }
    set: driver
    from: VehicleAssignment
    match: { vehicle: vehicle }
    where: { status: ACTIVE }        # <- new
    between: { start: validFrom, end: validTo, value: violationAt }

Three things are deliberately not a mirror of the relation-level where:

  • Multiple pairs are allowed and ANDed. That one is capped at a single pair because it lands in two EDM attributes (widgetOptionsFilterBy / widgetOptionsFilterValue); these become chained Criteria.eq calls, where a second condition costs nothing.
  • A status pair may use the seeded name, resolved on the register's nomenclature. The existing StatusSymbolResolver.rewriteResolves resolves against the record's lifecycle, which for a register filter hands back a plausible id from the wrong entity. New rewriteResolveWhere resolves against from:. The tests seed ACTIVE as 2 on the record and 7 on the register precisely so that mistake cannot pass.
  • Only that pair is offered to the resolver. Handing an ordinary column like kind: PRIMARY to it would report a perfectly valid string as an unknown status.

Refused rather than guessed: a where pair that repeats a match key. On a column already bound to the record a literal either says the same thing twice or contradicts it into matching nothing, and which one it is depends on data the parser cannot see.

The generated handler names the filter in its javadoc and in its notFound log line, so a filter that is too narrow reads as a filter rather than as missing data.

Also fixes two latent defects in the javaLiteral helper this reuses: a Boolean was quoted into the string "true" (which matches no boolean column), and a backslash was not escaped.

Verification

  • engine-intent 762/762; IntentEngineIT 55/55.
  • Red-first: all 9 new GlueResolveWhereTest cases fail without the change — including unknown key [where], confirming the closed vocabulary really did reject it.

What issues does this PR fix or reference?

Release Notes

An effective-dated register lookup (resolves:) can now narrow its register with a constant filter: where: { status: ACTIVE }. This is what lets a register keep its history — cancelled and superseded rows no longer collide with the valid one and force the lookup to give up as ambiguous. Several conditions may be given and are ANDed; a status may be named by its seeded name rather than its id.

Documentation

intent-assistant-guide.md and components/engine/engine-intent/CLAUDE.md are updated in this PR. No matching docs-repo PR — the intent DSL reference lives in-tree.

TIVMOF and others added 18 commits August 10, 2026 08:20
A `resolves:` lookup could not narrow its register by a literal. Every `match:`
pair binds a register column to a column of the RECORD, so "and only the rows
that are still valid" had no form at all - and a register is exactly the kind of
table that keeps its corrections. The cancelled row stays beside the active one
over the same dates, so both cover the violation date, and a lookup with one
right answer reports `ambiguous`, routes to manual handling and logs "multiple
matches". Every correction ever made earns that, quietly, and it gets worse the
longer the register lives.

Add an optional `where: { <register property>: <literal> }`, ANDed onto the same
Criteria as the match keys so it narrows the query rather than being applied
after the period comparison.

Three things are deliberately not a mirror of the relation-level `where:`.
Multiple pairs are allowed: that one is capped at a single pair because it lands
in two EDM attributes, whereas these are chained `eq` calls where a second
condition costs nothing. A pair naming the register's `function: EntityStatus`
relation may use the seeded NAME, resolved on the REGISTER's own nomenclature -
the record's would hand back a plausible id from the wrong lifecycle - and only
that pair is offered to the resolver, so an ordinary `kind: PRIMARY` is not
reported as an unknown status. And a pair repeating a `match` key is refused
rather than ANDed: on a column already bound to the record a literal either
repeats the match or contradicts it into matching nothing, and which one depends
on data the parser cannot see.

The generated handler names the filter in its javadoc and in the notFound log, so
a filter that is too narrow reads as a filter rather than as missing data.

Also two defects in the javaLiteral helper this reuses: a Boolean was quoted into
the string "true", which matches no boolean column, and a backslash was not
escaped.
Two separate races against one cold page load, both of which report a healthy
Builder as broken.

openBuilder() returns as soon as navigation does and the shell's scripts are
deferred, so the probe for Alpine and the Builder's stores sampled a page that had
not finished booting. That is the failure seen on this PR's smoke run and on
eclipse-dirigible#6841 - a test with nothing to do with either change. It now polls the same
condition instead; a mis-ordered or missing script still never registers the
stores, so a real breakage still fails, just at the deadline rather than instantly.

The second race had not been drawn yet. This class configures no Selenide timeout,
so every shouldBe without an explicit Duration gets the library default of four
seconds - and three of them wait on elements Alpine renders after those stores
register, one straight after a page load. Four seconds there is a race against the
boot, not a check on it. They get explicit deadlines, in line with every other
wait in the class.

Left alone: the waits that follow an already-satisfied wait on the same rendered
region. The DOM is present by then, and inflating every timeout only makes a
genuine breakage take minutes to surface instead of seconds.
# Conflicts:
#	components/engine/engine-intent/CLAUDE.md
#	components/engine/engine-intent/src/main/resources/intent-assistant-guide.md
#	tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/IntentBuilderShellIT.java
…dering (eclipse-dirigible#6813)

GlueGenerator.copy REMOVES an absent key, and Velocity renders the removed
reference as its own literal - so a resolves entry written before the
where: keys existed emitted ${filterSummary} into the generated javadoc
and read as filtered while filtering nothing. The binder now defaults
filters to the empty list and filterSummary to "" - the no-filter shape,
the same migration pattern the create-from's topicSuffix uses. Caught by
ModelGenerationIT's unresolved-reference guard on the pre-existing
fixture entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@delchev
delchev merged commit 64bb4b0 into eclipse-dirigible:master Aug 20, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

resolves: cannot filter the register with a literal predicate - cancelled register rows poison lookups as false ambiguous

2 participants