Repository navigation
Conversation
pandas parses into nanoseconds, which reach only 1677 to 2262, so the exported script emptied every moment outside that window where the engine holds a java.sql.Timestamp and reads it like any other. It also inferred one format for the whole column, so a row written differently from the first one was emptied too, where the engine hands DateParserUtils a field at a time. The text branch now reads the column cell by cell at microsecond resolution. Text neither side can read still leaves an empty cell rather than ending the run, which is what the cast has always promised. Closes apache#8595 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Automated Reviewer SuggestionsBased on the
|
|
| config | throughput | MB/s | latency | max Δ latest / 7d | |
|---|---|---|---|---|---|
| 🔴 | bs=10 sw=10 sl=64 | 374 | 0.228 | 25,856/38,936/38,936 us | 🔴 +14.3% / 🔴 +128.4% |
| 🔴 | bs=100 sw=10 sl=64 | 780 | 0.476 | 124,965/186,256/186,256 us | 🔴 +19.8% / 🔴 +61.8% |
| ⚪ | bs=1000 sw=10 sl=64 | 906 | 0.553 | 1,100,566/1,191,617/1,191,617 us | ⚪ within ±5% / 🔴 +7.6% |
Baseline details
Latest main 5264df2 from same runner
| config | metric | PR | latest main | 7d avg | Δ latest | Δ 7d |
|---|---|---|---|---|---|---|
| bs=10 sw=10 sl=64 | throughput | 374 tuples/sec | 405 tuples/sec | 721.84 tuples/sec | -7.7% | -48.2% |
| bs=10 sw=10 sl=64 | MB/s | 0.228 MB/s | 0.247 MB/s | 0.441 MB/s | -7.7% | -48.2% |
| bs=10 sw=10 sl=64 | p50 | 25,856 us | 23,839 us | 13,416 us | +8.5% | +92.7% |
| bs=10 sw=10 sl=64 | p95 | 38,936 us | 34,072 us | 17,046 us | +14.3% | +128.4% |
| bs=10 sw=10 sl=64 | p99 | 38,936 us | 34,072 us | 20,349 us | +14.3% | +91.3% |
| bs=100 sw=10 sl=64 | throughput | 780 tuples/sec | 797 tuples/sec | 914.39 tuples/sec | -2.1% | -14.7% |
| bs=100 sw=10 sl=64 | MB/s | 0.476 MB/s | 0.486 MB/s | 0.558 MB/s | -2.1% | -14.7% |
| bs=100 sw=10 sl=64 | p50 | 124,965 us | 124,577 us | 108,507 us | +0.3% | +15.2% |
| bs=100 sw=10 sl=64 | p95 | 186,256 us | 155,430 us | 115,131 us | +19.8% | +61.8% |
| bs=100 sw=10 sl=64 | p99 | 186,256 us | 155,430 us | 127,181 us | +19.8% | +46.4% |
| bs=1000 sw=10 sl=64 | throughput | 906 tuples/sec | 917 tuples/sec | 937.6 tuples/sec | -1.2% | -3.4% |
| bs=1000 sw=10 sl=64 | MB/s | 0.553 MB/s | 0.56 MB/s | 0.572 MB/s | -1.3% | -3.4% |
| bs=1000 sw=10 sl=64 | p50 | 1,100,566 us | 1,089,207 us | 1,065,461 us | +1.0% | +3.3% |
| bs=1000 sw=10 sl=64 | p95 | 1,191,617 us | 1,147,507 us | 1,107,736 us | +3.8% | +7.6% |
| bs=1000 sw=10 sl=64 | p99 | 1,191,617 us | 1,147,507 us | 1,133,556 us | +3.8% | +5.1% |
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,535.32,200,128000,374,0.228,25855.61,38936.13,38936.13
1,100,10,64,20,2565.65,2000,1280000,780,0.476,124964.59,186255.81,186255.81
2,1000,10,64,20,22070.32,20000,12800000,906,0.553,1100565.86,1191616.72,1191616.72
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8597 +/- ##
==========================================
Coverage 92.78% 92.79%
- Complexity 4898 4923 +25
==========================================
Files 1236 1238 +2
Lines 52121 52305 +184
Branches 6405 6434 +29
==========================================
+ Hits 48359 48534 +175
+ Misses 2186 2178 -8
- Partials 1576 1593 +17
*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:
|
carloea2
left a comment
There was a problem hiding this comment.
The new timestamp helper crashes on 2024-03-05T14:09:07Z. I ran the generated helper with pandas 2.2.3. Parsing keeps the timezone, then astype rejects the conversion to a timestamp without a timezone. The previous code accepts this value. Please handle timestamps with a timezone and add a test with Z and an explicit offset.
The cast stopped on one. A reading that carries a zone cannot be converted to a column that holds none, so `2024-03-05T14:09:07Z` ended the cast where the previous generator had read it, and an explicit offset did the same. The engine reads the offset and keeps no zone for it: DateParserUtils parses the reading and java.sql.Timestamp holds the wall clock of the machine's own zone, so `...T14:09:07Z` is 06:09:07 where the machine is eight hours behind UTC. The helper now does that, which is also what the epoch-milliseconds branch beside it already did. The two spellings join the cases the timestamp test compares against parseField cell by cell, so what they should read is taken from the engine rather than written down and the pair says the same thing in any zone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Fixed in |
carloea2
left a comment
There was a problem hiding this comment.
The timezone crash is fixed. I ran the current helper with Z, an explicit offset, year 2500, and null. Those checks passed.
carloea2
left a comment
There was a problem hiding this comment.
Reproduced locally with the generated Python code. One correctness issue below. I did not run a frontend workflow or the full Scala suite.
The cast narrowed one. A column the engine already holds as a moment went through the same astype as freshly parsed text, so 14:09:07.123456789 and 14:09:07.123456001 both came back as .123456 and two rows the run tells apart became one. The previous generator left such a column alone. parseField hands a java.sql.Timestamp back untouched and that class counts nanoseconds, so there is nothing for this branch to decide: the column is returned at the resolution it arrived in, zoned or not. The text branch below is unchanged and still reads into microseconds, which is what the years past the nanosecond edge need. A test casts a timestamp column to timestamp and compares the two values cell by cell against parseField, which is how the rest of the spec compares a cast. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… write text as Java's toString does Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@carloea2 Since your approval, text read into a timestamp is cut to the millisecond as DateParserUtils does, and a cast to text writes a value as Java's toString does. |
| | return "Infinity" if value > 0 else "-Infinity" | ||
| | if value == 0 or 1e-3 <= abs(value) < 1e7: | ||
| | return repr(value) | ||
| | sign, digits, exponent = decimal.Decimal(repr(value)).as_tuple() |
There was a problem hiding this comment.
Reproduced with the generated helper and JDK 17: 1e23 becomes 1.0E23 here, but Double.toString returns 9.999999999999999E22. String comparisons and joins after a cast can then differ. Please match Java's digit selection and add a regression test.
There was a problem hiding this comment.
Confirmed on JDK 17. Matching it exactly means reproducing the digit selection in OpenJDK's FloatingDecimal, which is GPL code, so is it OK to write a Python version of it here, or should the helper keep the shortest form that JDK 19+ gives?
What changes were proposed in this PR?
Type Casting to a timestamp read text with
pd.to_datetime(col, errors="coerce")in the exported script. That is wrong in two ways, and both empty a cell the run itself filled.pandas parses into nanoseconds, which reach only 1677-09-21 to 2262-04-11. The engine holds a
java.sql.Timestamp, where2500-01-01 00:00:00is an ordinary moment: it reads it, and the exported script answered with an empty cell. The same for1500-06-15 08:30:00and9999-12-31 23:59:59.pandas also infers one format for the whole column and coerces every row that does not match it. The engine hands
DateParserUtilsone field at a time, so a row states its own format. A column holding2024-03-05 14:09:07andMarch 5, 2024kept the first and emptied the second.The text branch now reads the column cell by cell and holds the result at microsecond resolution, which covers the years the engine covers. It still coerces: the engine accepts a set of formats no single pandas call states, so text neither side can read leaves an empty cell rather than ending an exported run halfway. That part is unchanged, and a test now says so.
Review and the verification's edge values found four more places where the cast parted from the engine:
Zor+05:30, stopped the cast. The engine moves it to the machine's zone and keeps that wall clock, and the script now does the same.parseFieldreturns ajava.sql.Timestampuntouched, so the script now hands such a column back at the resolution it arrived in.DateParserUtilsreads into ajava.util.Date, which counts milliseconds, so the script now cuts there too.str()does. Java writes a double past 1e7 or under 1e-3 in E notation, a boolean in lower case, and a timestamp with its nanoseconds and at least.0. A shared_texera_java_textnow writes each one as Java'stoStringdoes.The number branch is left alone. A LONG is read as epoch milliseconds and has the same window, but it also disagrees about the calendar before 1582-10-15, where
new Timestamp(long)is Julian and pandas is proleptic Gregorian. Widening the window there would answer with a moment eight days off instead of an empty cell, so what that branch should do is a separate decision. The issue records it.Any related issues, documentation, discussions?
Closes #8595. #8480 closed the same nanosecond gap for Extract Date/Time Fields, which parses strictly and could take the one-line form this operator cannot.
#8341, #8704, #8705 and #8709 call the timestamp and text helpers this adds, so this PR merges before them.
How was this PR tested?
Four tests in
TypeCastingOpDescSpecrun the generated Python and compare it cell by cell againstAttributeTypeUtils.parseField. They are tagged to run in the integration job, which has pandas.2500-01-01,1500-06-15,9999-12-31, readings withZand+05:30, and readings past the millisecond. It fails on the old generator.Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Claude Opus 5)