Repository navigation
Conversation
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]>
Automated Reviewer SuggestionsBased on the
|
Codecov Report✅ All modified and coverable lines are covered by tests. 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
*This pull request uses carry forward flags. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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
left a comment
There was a problem hiding this comment.
The curated configurations look good.
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
left a comment
There was a problem hiding this comment.
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]>
b97eab5 to
082a6c3
Compare
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]>
|
@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
left a comment
There was a problem hiding this comment.
The curated configurations cover the cases the schema cannot generate. Looks good.
carloea2
left a comment
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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]>
|
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: |
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]>
|
|
carloea2
left a comment
There was a problem hiding this comment.
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.
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]>
…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]>
|
@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
left a comment
There was a problem hiding this comment.
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]>
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]>
|
@carloea2 One new commit: File Scan's input is now curated from the shared table's own path. Could you take a look? |
carloea2
left a comment
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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.
…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]>
…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]>
…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]>
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
PyOpExecHarnessit 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)