Skip to content

Carry request-level time bounds on Relation, not in each source name - #20

Open
penghuo wants to merge 1 commit into
mainfrom
refactor/carry-time-bounds-on-relation
Open

penghuo wants to merge 1 commit into
mainfrom
refactor/carry-time-bounds-on-relation

Conversation

@penghuo

@penghuo penghuo commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Why

opensearch-project#5766 carries the request's time bounds encoded into the table name. opensearch-project#5852 fixed a bug there: encoding every source produced a name decode could not read back, because Relation joins sources with , — the same character the encoded block uses internally.

opensearch-project#5852's fix encodes only the last source, so the joined name ends in exactly one block. It works, but it leaves AstBuilder correct only because of how Relation.getTableQualifiedName() joins names three layers later. Nothing links the two except a comment at the call site. Change that join and the bug returns — silently, since a malformed name fails at index resolution, far from the code that built it.

Change

Move the bounds onto Relation as a nullable field and append the block once inside getTableQualifiedName(), after the join. The method that joins is now the method that appends, so the constraint holds locally instead of across files.

relation() collapses to one line and withTimeBounds is deleted (−21/+1). No behaviour change: the encoded name is byte-identical, the storage engine decodes it the same way, and "outermost source= only" is still decided by isMainSource.

Review notes

  • Existing Relation(List) / Relation(UnresolvedExpression) constructors are kept, so all 21 call sites and both subclasses are untouched.
  • @ToString / @EqualsAndHashCode now include the new field. Equality is unaffected when bounds are null (the only case AstDSL and the SQL parser produce); toString() does change for bounded relations, hence the test regex change.
  • Out of scope, pre-existing and unchanged: TimeBounds still uses , unescaped, so a bound containing a comma silently disables pruning.

Testing

spotlessCheck and compile across all modules; unit tests for core / ppl / opensearch / sql / protocol; CalciteTimeBoundsPruningIT 12 tests × both pushdown configurations — all pass, including opensearch-project#5852's comma-separated cases.

opensearch-project#5766 encoded the bounds into every table source in AstBuilder, but
Relation joins its sources with a comma -- the same character the encoded
block uses to separate its own three fields -- and TimeBounds.decode only
peels the trailing block. A comma-separated source therefore left every
earlier block behind in the index expression, which then split into bogus
index names (and, for a cross-cluster source, read a colon inside a
timestamp as a cluster prefix).

opensearch-project#5852 fixed that by encoding only the last source, so the joined name ends
in exactly one block. That is correct, but it makes AstBuilder depend on
how Relation serialises its names three layers later, with a comment at
the call site as the only link.

Move the bounds onto Relation as a nullable field and append the block
once inside getTableQualifiedName(), after the join. The method that
joins is now the method that appends, so the constraint is local instead
of implied across files. AstBuilder.relation() collapses to one line and
withTimeBounds() is deleted.

No behaviour change: the encoded name is byte-identical, the storage
engine decodes it the same way, and "outermost source= only" is still
decided by isMainSource.

Relation keeps its existing Relation(List) and
Relation(UnresolvedExpression) constructors, so all call sites and both
subclasses are untouched. Its @tostring and @EqualsAndHashCode now
include the new field; equality is unaffected when bounds are null, which
is the only case AstDSL and the SQL parser produce.

AstTimeBoundsTest matches the new rendering, since the bounds no longer
appear inside tableNames. opensearch-project#5852's comma-separated case is kept as-is --
it asserts through getTableQualifiedName(), which is the right level --
and the plan walker gained a null guard, as Node.getChild() returns null
by default.

Verified: spotlessCheck and compileJava/compileTestJava across all
modules; core, ppl, opensearch, sql and protocol unit tests; and
CalciteTimeBoundsPruningIT at 12 tests in both pushdown configurations,
including opensearch-project#5852's two comma-separated cases.

Signed-off-by: Peng Huo <penghuo@gmail.com>
@penghuo
penghuo requested a review from dai-chen as a code owner October 9, 2026 16:38
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.

1 participant