Skip to content

test(verify): hand-write the configurations a schema cannot describe - #8363

Draft
kz930 wants to merge 18 commits into
apache:mainfrom
kz930:feat/verify-curated-configurations
Draft

kz930 wants to merge 18 commits into
apache:mainfrom
kz930:feat/verify-curated-configurations

Conversation

@kz930

@kz930 kz930 commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

Some operators need an input a schema alone cannot produce. A join needs two
tables that share a key. A dictionary matcher needs a dictionary whose entries
appear in the column it reads. File Scan needs a table whose cells name the
files it reads. Deriving those from annotations would mean teaching the
generator what each operator means.

These are written by hand instead, one entry per operator, and the entry says
why the generator could not do it.

An entry can also carry extra scenarios, each an input and configuration its primary one does not reach. For example: an outer join over a long past 2^53, a model scored on the one row a drop left, a prediction with ground truth named beside a missing feature, and a cast to text of a double Java writes in E notation. Each scenario is named for the question it asks.

Any related issues, documentation, discussions?

Part of #8325, 9 of 27; that issue lists the set in order.
It does not compile until #8357 lands, which adds the PyOpExecHarness it calls to find the Python interpreter.

Closes #8413, the task this change is the whole of.

How was this PR tested?

The tests in this change cover it. The whole set is exercised together once the last piece lands: every operator run through the engine and through its generated script, and the two answers compared.

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Opus 5)

Some operators need an input a schema alone cannot produce. A join needs two
tables that share a key. A dictionary matcher needs a dictionary whose entries
appear in the column it reads. Deriving those from annotations would mean
teaching the generator what each operator means.

These are written by hand instead, one entry per operator, and the entry says
why the generator could not do it.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@github-actions github-actions Bot added feature platform Non-amber Scala service paths labels Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • No candidates found from git blame history.

@codecov-commenter

codecov-commenter commented Sep 2, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.29%. Comparing base (7976744) to head (303341b).
⚠️ Report is 196 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #8363      +/-   ##
============================================
- Coverage     94.11%   93.29%   -0.82%     
- Complexity     4811     4927     +116     
============================================
  Files          1197     1212      +15     
  Lines         48813    49833    +1020     
  Branches       5906     6064     +158     
============================================
+ Hits          45939    46493     +554     
- Misses         1420     1807     +387     
- Partials       1454     1533      +79     
Flag Coverage Δ *Carryforward flag
access-control-service 77.38% <ø> (-3.62%) ⬇️
agent-service 99.32% <ø> (ø) Carriedforward from 8d9cc74
amber 90.08% <ø> (ø) Carriedforward from 8d9cc74
computing-unit-managing-service 60.48% <ø> (-13.19%) ⬇️
config-service 87.37% <ø> (+0.37%) ⬆️
file-service 81.53% <ø> (-6.38%) ⬇️ Carriedforward from 8d9cc74
frontend 96.86% <ø> (ø) Carriedforward from 8d9cc74
notebook-migration-service 83.73% <ø> (+4.42%) ⬆️
pyamber 98.47% <ø> (ø) Carriedforward from 8d9cc74
workflow-compiling-service 74.09% <ø> (-3.10%) ⬇️ Carriedforward from 8d9cc74

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

kz930 and others added 2 commits September 2, 2026 16:56
The behaviour is what a reader needs; the change that introduced it is
what a blame is for.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The handler wrote a three-column table of its own: id, name, score. Those are
the first three columns of the shared one, so the table was never the reason
this operator is curated. Its config is: `attributes` is not declared required,
the auto tier starts it empty as the UI does, and the operator refuses an empty
list.

Pinning that one row is all it needs. Dropping the table puts the operator on
the same thirty-four columns every other one reads, where a projection has
something to leave out.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The curated configurations look good.

@kz930
kz930 marked this pull request as draft September 4, 2026 17:12
Every source the fixture held was a number, and a number is the one source
where Python and the engine cannot disagree: `bool(x)` is false exactly when x
is zero, which is what the engine's `x != 0` says. The disagreement lives in
the string column the fixture had none of, where Python answers true for
"false" and for "0".

The values are the ones both sides accept, since text the engine refuses would
end the run before there is anything to compare. That half is pinned against
`AttributeTypeUtils` in the operator's own spec.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new boolean cases cover the earlier mismatch. Looks good.

These four write their own tables, so the shared table's renaming does not reach
them. Each renames the column its own configuration points at, which is what
puts the escaping question to the code it emits.

Three of the headers here also claimed their rows compare by position. None of
those operators declares itself order-sensitive, so the comparator lex-sorts
both sides first, and the claim is gone.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@kz930
kz930 force-pushed the feat/verify-curated-configurations branch from b97eab5 to 082a6c3 Compare September 9, 2026 06:50
kz930 and others added 2 commits September 10, 2026 13:14
Regex, Keyword Search and Type Casting each gained the case their table
could not put: an integer column and a boolean one, whose text a hole
changes; a column named by two casting units, which the executor
collapses to the last; a 64-bit value past the Int bounds; a number read
as a timestamp; and text NumberFormat reads where Python's int refuses.

Aggregate gained a leading empty string, which is the only value that
tells the two spellings of CONCAT apart: a leading null reads the same
either way, and the canonical table holds no empty strings.

Keyword Search also gained a case-sensitive run over a column carrying
one word in three casings. Its base table is lower-case throughout, so
sweeping the flag there decided nothing.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
It restated its own signature and listed a codec table that lives in
TupleIO. The reasons a reader cannot derive stay.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@kz930

kz930 commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor Author

@carloea2 this moved after your approval: the curated tables got hostile column names, and they are now asked what a rendered column looks like. Would you take another look when you have a moment?

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The curated configurations cover the cases the schema cannot generate. Looks good.

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The If test only checks the true output. It never sends a condition state, so the false route can be broken and this test still passes. Please add a case that selects false and verify the rows go to the false output only.

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new verifier marks Keyword Search as passing, but punctuation already gives different results. With text perfect. and query perfect in case sensitive mode, native does not match because it keeps perfect. as one token. The exported regex matches perfect and keeps the row. Please add this case and do not certify the operator until both paths agree.

The comment sat above AggregateTransformHandler rather than the handler it
describes, and it read as a choice rather than a limit. If's route is decided
by a State message on the Condition port, and the harness has no State channel,
so a fixture can only reach the engine's default, True. Say that, and say where
the False route is covered instead.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@kz930

kz930 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

The route is chosen by a State message on the Condition port and the harness writes rows per port, so a fixture can only reach the engine's default, which is True. The False route is covered where it can be: IfOpExecSpec for the engine, and a new case in IfOpDescSpec (on #8506, which carries the exported block) that sets the switch off and asserts the rows leave by the False output alone.

The note explaining the punctuation-free rows described an analyzer that
tokenized on whitespace, which stopped being true when CaseSensitiveAnalyzer
moved to the StandardTokenizer: a term next to punctuation is a term to the
engine and to a word-boundary regex alike. Say what the two do part on instead,
a word punctuation belongs inside, and let the case-sensitive scenario carry a
row that ends its term in a period.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@kz930

kz930 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

CaseSensitiveAnalyzer has used the StandardTokenizer since #6801, so perfect. tokenizes to perfect and the engine does keep the row; KeywordSearchOpExecSpec pins exactly that case, and the exported \b(?:...)\b with case=True keeps the same rows the engine keeps. 5bd68fe adds a punctuated row to the case-sensitive scenario and corrects the note that claimed a whitespace tokenizer.

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My earlier claim about perfect. was wrong. The native analyzer uses StandardTokenizer, which removes the trailing period. The updated fixture covers this case. I withdraw that finding and approve.

kz930 and others added 3 commits September 24, 2026 20:44
Sklearn Prediction and Sklearn Testing read a fitted model on port 0, so
no auto fixture can run them. The handlers fit a DecisionTree in Python
on the table they write to port 1 and write it to port 0 the way
Tuple.cast_to_schema writes a model into a BINARY field.

The last row is held out of the fit and lands on the wrong side, so no
score is perfect and the averaging behind it is actually compared.
Testing also runs a regressor on a numeric target, which covers the
isRegression branch.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…s runs

The fixture read UDF_PYTHON_PATH itself. PyOpExecHarness.resolvePython
is the one place that says which interpreter the harness runs, and the
model has to be pickled by the same one that unpickles it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
… pin

HashJoin gains scenarios for names that collide across and within the two
sides and for outer joins whose unmatched rows leave holes in integer,
long and differently typed columns. Aggregate gains the engine's own
arithmetic: an INTEGER sum that wraps, a timestamp sum and average, and
CONCAT over booleans. The fixture writer now takes a TIMESTAMP value.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
kz930 and others added 3 commits September 26, 2026 13:28
…olumn names

HashJoin's default run now reads the shared table, whose two ports overlap
in five of their ten rows, so the swept join types part from one another
and every column but the key collides. Two scenarios join keys that the
left key or a left payload names, across an outer join.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…o join tables that asked nothing new

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…n scenarios, and read more cases from the shared table

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@kz930

kz930 commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

@carloea2 Since your approval there are new scenarios for HashJoin, Sklearn Prediction and Testing, and edge-value casts, and three curated tables now read from the shared one.

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The non-model primary and extra fixtures write and round-trip correctly, including Unicode, nulls and an exact long past 2^53. Both actual model fixtures fail on Windows before fitting. A file-based model control works with an ordinary target name, but the fixture's quoted target name is also lost in the command arguments. My six focused probes give 3 passes and 3 failures; formatting passes. Only declared harness and shared-fixture prerequisites were supplied locally.

sbt -batch 'WorkflowCompilingService/testOnly *CuratedHandlerReviewSpec -- -oDF' 'scalafmtCheckAll'

…and line

Windows rebuilds argv from one command line and drops the quotes inside an
argument, so the multiline -c script and the a"b\c_species target both reached
Python broken. The script now goes in a file and the target in an environment
variable.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@kz930

kz930 commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

@carloea2 The Windows model-fixture finding is fixed in 303341b. Could you take another look?

File Scan reads the files its input names. Its one row names the shared
table's own file, and the extra scenarios read File Scan Source's files
from the paths their inputs name.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@kz930

kz930 commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

@carloea2 One new commit: File Scan's input is now curated from the shared table's own path. Could you take a look?

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new File Scan scenario creates a merge dependency cycle. Reproduced on fresh upstream main with sbt -batch 'WorkflowCompilingService/Test/compile'. The existing #8357 dependency is separate from this finding. Leaving the PR as a draft.

…ing from the sources change

The File Scan scenarios used FileScanSourceHandler's files and the fixture's
resource name from the sources change, which itself builds on this one. The
files now live with File Scan's handler, and File Scan Source reads them from
here.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fixture cycle is gone: this checkout compiles and runs without anything from #8364. The 29 canonical-fixture tests and two added fixture probes pass, covering all seven shared files, transform input rows and a missing destination directory. scalafmtCheckAll passes with the earlier harness and fixture prerequisites supplied. CI is still blocked before tests by the Ampel action.

renovate-bot pushed a commit to renovate-bot/apache-_-texera that referenced this pull request Nov 5, 2026
…ython (apache#8709)

### What changes were proposed in this PR?

Aggregate and the sort family (Sort, Stable Merge Sort and Sort
Partitions) implement `StandaloneCodeGenerator`. The sort family's row
order is its contract, so `LogicalOp` gains `orderSensitive`, which
those three override. Anything comparing two runs of an operator reads
it to decide whether the rows have to arrive in the same order or only
be the same rows.

Review then found five places where the block answered a question the
engine answers differently. Aggregate summed an INTEGER column without
the wrap Java ints have, refused a TIMESTAMP column outright, answered a
timestamp average with a timestamp where AVERAGE is declared DOUBLE, and
spelled a boolean in Python's capitals rather than Java's lower case.
The sort put a NaN last on the way down, where the engine puts it first.

The edge values the shared table now carries found one more. Aggregate's
concatenation wrote a double past 1e7 or under 1e-3, and a timestamp, as
Python prints them, where the engine writes what Java's toString gives.
It now writes every value, the lower-case boolean the review asked for
included, through the helper apache#8597 adds for casting to text.

One change reaches code the engine runs. `getPhysicalPlan` swapped
Aggregate's aggregations for their global-stage form and left them on
the operator. The export compiles a plan and then translates the same
operators, so the script grouped on the result column names and failed.
The operator now keeps what the user set. The desc the global stage
receives is unchanged.

### Any related issues, documentation, discussions?

Part of apache#8325, step 11b; that issue lists the set in order. Aggregate's
concatenation calls a helper apache#8597 adds.

Sorting a NaN apart from a null, and grouping on one, need the nullable
dtypes apache#8704 reads an Arrow file into. Without it a null and a NaN share
a slot and both behave as they did before, so this PR is no worse on its
own and only complete alongside that one.

Closes apache#8707, the task this change is the whole of.

### How was this PR tested?

Each operator asserts the block it emits in its own spec. The Aggregate
cases the review named run in the verify tables of apache#8363, which put the
same input through the engine and through the script and compare the
two. What those tables cannot reach stays here as executed tests: a
timestamp at two resolutions and in a zone other than UTC, and a null
kept apart from a NaN in the sort and the group key.

`AggregateOpDescSpec` builds the physical plan and then checks that the
operator still holds the user's aggregations and that its generated code
still reads the input column.

### Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Claude Opus 5)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
renovate-bot pushed a commit to renovate-bot/apache-_-texera that referenced this pull request Nov 5, 2026
…as Python (apache#8705)

### What changes were proposed in this PR?

File Scan, the file-scan source, Text Input and URL Fetcher implement
`StandaloneCodeGenerator`. Each slices the raw lines before parsing, as
the engine does, so a line outside the window is never converted. A
boolean line is read as `parseField` reads one, where 1 is true and
other text ends the run. A timestamp line is kept as text and parsed as
a column with the helper apache#8597 adds, as the CSV readers parse theirs.
Every file is read as bytes and decoded the way the engine's reader
decodes it. Bytes the charset cannot hold become U+FFFD, and UTF-16 with
no byte-order mark reads as big-endian. Python's codecs refuse both, so
a file read under the wrong Encoding used to end the script while the
engine read on.

URL Fetcher is reported as unverifiable rather than exported blind. It
reads a live URL, so two runs are not required to agree and a comparison
would only measure the network. File Scan takes its filenames from an
input port, so the verification feeds it a table that names the files to
read, as it feeds a transform. That lands with apache#8364 and apache#8363.

### Any related issues, documentation, discussions?

Part of apache#8325, step 12c; that issue lists the set in order. It needs
apache#8341 for the shared source code and apache#8597 for the timestamp helpers, so
both merge first.

Closes apache#8703, the task this change is the whole of.

One engine defect was found while matching these readers, and it is
fixed here rather than worked around: the export alone could not close
it. apache#8598, where a file scan extracting an archive could not also
include the filename, because the line-by-line branch emitted a
one-field row against the two-column schema its own flag declares. A
second, apache#8596, where the Encoding field never reached the executor, was
fixed upstream by apache#8605, and this branch takes that fix.

Closes apache#8598.

### How was this PR tested?

Each operator asserts the block it emits in its own spec. Where a
difference is one of behaviour rather than of text, the spec runs the
engine's own executor and the generated script over the same input and
compares what each produced: the boolean parsing rules and the entries
read out of an archive. Once the verification lands, every operator is
also run both ways on every configuration its schema offers; this branch
is cut from main and does not carry that machinery, so those runs are
not on this diff's CI.

### Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Opus 5)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
renovate-bot pushed a commit to renovate-bot/apache-_-texera that referenced this pull request Nov 8, 2026
…pache#8506)

### What changes were proposed in this PR?

Six operators implement `StandaloneCodeGenerator`: the three joins and
the three set operations. What they have in common is that they combine
or compare tables rather than read what is inside a row.

apache#8340 keeps the operators that work on text. Aggregate and the sort
family follow in apache#8709, and the samplers and control flow in apache#8710.

Review then found two places where the block answered a question the
engine answers differently. The hash join asked pandas for a suffix,
which appends once and then refuses the duplicate it just made, and let
an outer join's holes cost an integer column its type.

Running the engine on those cases found the platform out of step with
itself. HashJoin declared a right column's new name by checking only the
left names, while the executor also avoids the other right names. So a
right `x` beside a right `x#@1` was declared as a second `x#@1`, and one
value was lost. An unmatched right row in an outer join kept its raw
names and landed in the left column. Both now go through one renaming
rule in `JoinUtils`.

The shared table then found one more. In a right or full outer join on
keys both sides name alike, the engine writes an unmatched right row's
key into the left key column, and the export left it empty. The engine
had its own half: an unmatched right key named like a left payload
column landed in that payload. The export now fills the key, and the
engine leaves it out of the payload.

The set operations matched rows the way pandas does. The engine's
HashSet compares rows with Scala's `==`, under which NaN equals nothing,
and hashes them by each double's bits, which tell 0.0 from -0.0. The
export now selects rows by a key that follows both rules.

A probe worker that received no build rows could not name an unmatched
right row's columns, because it read the left names off the first build
row. This happens when the left input is empty or no left key hashes to
that worker. The runtime now records each input port's declared schema
on the executor before the port finishes
(`OperatorExecutor.inputSchemas`, filled in `EndChannelHandler`), and
the probe reads the left names from it. This is the Scala counterpart of
what apache#8766 does for Python operators.

### Any related issues, documentation, discussions?

Part of apache#8325, step 11a; apache#8709 and apache#8710 follow. That issue lists the
set in order.

The set operations and joins keyed on a null or a NaN need the nullable
dtypes apache#8704 reads an Arrow file into. Without it a null and a NaN share
a slot and both behave as they did before, so this PR is no worse on its
own and only complete alongside that one.

Closes apache#8505, the task this change is the whole of.

### How was this PR tested?

Each operator asserts the block it emits in its own spec. The interval
join reads its keys off the merged frame rather than writing over an
input column that shares a temporary's name.

The join cases the review named run in the verify tables of apache#8363, which
put the same input through the engine and through the script and compare
the two. What those tables cannot reach stays here as executed tests: a
null kept apart from a NaN in the set operations and the join key.
`HashJoinProbeOpExecSpec` pins the platform fixes.

Each set operation's spec runs its native executor and the export over
NaN, signed-zero, finite and null rows. `HashJoinProbeOpExecSpec` covers
an empty build side, and `EndChannelHandlerSpec` checks that the schema
is in place before the operator finishes the port.

### Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Claude Opus 5)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature platform Non-amber Scala service paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hand-write the configurations a schema cannot describe

3 participants