Repository navigation
[fix](arrow) Reject invalid UTF-8 and out-of-range timestamps in Flight results - #69771
Conversation
…ht results ### What problem does this PR solve? Issue Number: None Related PR: apache#68596, apache#68604 Problem Summary: Port branch-4.x's apache#68596 and apache#68604. An Arrow Flight result could carry values that Arrow clients cannot decode, and the BE published them anyway: 1. A STRING value that is not valid UTF-8, for example unhex('84'), also inside an ARRAY, a STRUCT or a MAP key or value. The STRING serde appends the bytes to an Arrow utf8 builder unchecked, and Arrow only verifies UTF-8 in Array::ValidateFull(), so PyArrow failed decoding the batch on the client. 2. A DATETIME outside 0001-01-01..9999-12-31, which Doris can hold: its range starts at 0000-01-01 00:00:00, and 0000-12-31 00:00:00 is published as -62135683200000000 us. Arrow accepts any int64 timestamp, while Python's datetime only covers years 1..9999, so PyArrow raised OverflowError converting it. The server saw a successful query, and the client got an error that did not say which value caused it. ArrowBlockConvertor::convert_to_arrow is now virtual, and ArrowFlightArrowBlockConvertor overrides it: after the base conversion it runs ValidateFull() on every column, and a new FlightTimestampValidator checks every timestamp, nested ones and map keys included, against the supported calendar range in the actual Arrow unit (and against the local calendar for zoned values), skipping values under a NULL list, map or struct parent. Either failure returns InvalidArgument naming the column, the nested path and the row, before the batch is handed out. The code is branch-4.2's final form of the two PRs, placed next to the init() and write_column() overrides master's Flight convertor already has. Other Arrow convertors are unchanged. ### Release note Arrow Flight results now fail with a server-side error when a STRING value is not valid UTF-8 or a DATETIME value is outside 0001-01-01..9999-12-31, instead of returning data that clients such as PyArrow cannot decode. This also applies to remote Doris catalogs reading over Arrow Flight and to the _query_plan reads of the Spark and Flink connectors. ### Check List (For Author) - Test: Regression test / Unit Test - BE UT: ArrowBlockConvertorTest gains 6 tests, new ArrowFlightTimestampTest has 7; 75 tests in 6 Arrow suites pass - Regression: test_flight_utf8_validation and test_flight_timestamp_range fail without this change and pass with it; the three remote Doris suites use year 0001 as their minimum DATETIME (two .out files regenerated) and pass; test_flight_int96_timestamp_range needs the Hive docker environment and was not run locally - Behavior changed: Yes. A Flight result with an invalid UTF-8 STRING or an out-of-range DATETIME fails on the server. - Does this need documentation: No Co-authored-by: Gabriel <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
|
run buildall |
|
/review |
There was a problem hiding this comment.
Request changes: the new rejection can silently truncate Spark/Flink external scans (P1), and a named-zone boundary still reaches a PyArrow/pytz calendar overflow (P2). Both findings are inline and independently traced against head 5e729b1. There were no existing inline threads or P0/P1 findings to carry forward.
Reviewed all 12 changed files and their producers/consumers. The relevant flow is: output Block -> base Arrow conversion/materialized columns -> Flight validation -> local/remote Flight reader or memory scratch sink -> client. The scratch-sink error/EOF branch is the blocking gap.
Critical checkpoint conclusions:
- Goal and proof: UTF-8 and timestamp rejection works along the ordinary BE Flight path by inspection, but the two reported cases keep the stated end-to-end goal incomplete. Added tests cover most intended cases; the two findings lack their relevant end-to-end cases.
- Scope and clarity: implementation and fixture changes are focused on Flight output validation.
- Concurrency and locking: validation adds no shared mutable state or locks. Existing asynchronous scanner consumption matters to P1, including fetches already blocked on the queue.
- Lifecycle and memory safety: validator state and batch ownership are local and released on error; the output is assigned only after validation. No new global/static initialization dependency or ownership cycle was found.
- Configuration: no configuration item is added or changed.
- Compatibility and symbols/formats: output rejection intentionally changes client behavior, including remote Doris and connector scans. Ordinary/Python/table writers retain their converter paths. The new virtual method does not change a persisted or wire layout; no new libc API is introduced.
- Thrift/protobuf: no definitions, field IDs or enum values change.
- Parallel paths: local/remote BE Flight readers and scratch-sink scans were traced. Readers propagate errors; scratch scans have P1. FE-materialized results use UTF-8 vectors, not these typed timestamp arrays.
- Special conditions and nulls: range units, exclusive upper bounds, slices, const materialization, null strings and null container masking match the reachable producers; named-zone client interpretation has P2.
- Test coverage: BE tests and Flight/INT96/remote-Doris regressions cover malformed UTF-8, binary, nested/key values, nulls, calendar bounds, fractions, slices and later batches. Scanner error publication and pytz historical-zone boundaries need coverage.
- Changed expected results: the datetime replacements and statistics minimum match the changed input values and existing fixture/golden correspondence. Generation provenance was not independently established.
- Observability and errors: ordinary Flight errors carry column/path/row context and readers add reader context; P1 discards the error before the external client sees it.
- Transactions and persistence: no transaction, EditLog, rowset or persistence logic changes.
- Data writes and crash behavior: this is result conversion, with no stored-data mutation or visibility/version/delete-bitmap change; successful scan completeness is compromised by P1.
- FE/BE variables: no new variable or execution-version negotiation is needed or added.
- Performance: UTF-8/full validation and timestamp traversal add linear work in emitted data; subtrees without timestamps skip row traversal. No benchmark was run or throughput claim made.
- Other BE checkpoints: no new hub-header dependency, PCH edit, gate-table exception, explicit instantiation family or test inclusion of a source .cpp was found.
User focus: no additional focus was supplied; the whole PR was reviewed. Static-only limits: no builds, tests, benchmark, live connector/client reproduction, fixture download, or final ELF compatibility audit was performed. Findings are established from the code paths and, for P2, the linked primary decoder/timezone sources. There are no unresolved candidates or unreviewed changed paths.
Review converged after 3 round(s).
| FlightTimestampValidator validator; | ||
| RETURN_IF_ERROR( | ||
| validator.init(batch->column(i), batch->schema()->field(i)->name(), _timezone)); | ||
| RETURN_IF_ERROR(validator.validate(0, batch->num_rows(), start_row)); |
There was a problem hiding this comment.
[P1] Propagate validation failures to external scan readers
MemoryScratchSinkOperatorX::sink_impl() also calls this converter for Spark/Flink _query_plan scans. With a stored year-zero DATETIMEV2 (or invalid UTF-8 STRING), the new validation error returns before that batch is queued. The pipeline then closes the sink, but MemoryScratchSinkLocalState::close() ignores exec_status and queues nullptr without updating the queue's status. ResultQueueMgr::fetch_result() interprets that sentinel as successful EOF, and BaseBackendService::get_next() has no separate query-status check; external fragments also skip FE status reporting. Consequently these scans now succeed with zero or partial rows, silently losing the rejected batch and remaining data. Propagate the failure through the external scanner's queue/get_next path, including a fetch already waiting for data, and add a scanner-level regression before enabling this rejection for that consumer.
There was a problem hiding this comment.
Fixed in 3b3f2a6. The mechanism is right, and it is older than this PR: nothing has called ResultQueueMgr::update_queue_status since the non-pipeline engine was removed (#35803), so any failure on this path (a scan error, an expression error, a cancellation such as a memory-limit kill) already ended a _query_plan read as a complete result; the rejected batch is one more such failure. MemoryScratchSinkLocalState::close now records a non-OK exec_status in the queue before it puts the sentinel, and ResultQueueMgr::fetch_result checks the queue status again when it takes the sentinel, which covers a fetch already blocked in blocking_get.
Tests: BE UT ResultQueueMgrTest.fetch_result_reports_failure_recorded_before_sentinel (a fetch waiting for data) and fetch_result_end_after_ok_status; regression arrow_flight_sql_p0/test_query_plan_scan_validation reads _query_plan scans through open_scanner / get_next. On the previous head its invalid UTF-8 scan read 0 rows and ended with eos; now get_next returns [INVALID_ARGUMENT]Invalid Arrow Flight result in column 2 (''): Invalid: Invalid UTF8 sequence at string index 1, the year-0 scan returns the timestamp error, and a valid scan still reads all rows. branch-4.1 / 4.2 have the same close() and already ship this validation; that is passed on to their maintainers.
| const auto local_end = cctz::convert(cctz::civil_second(10000, 1, 1), timezone); | ||
| // Python first constructs UTC and then applies the Arrow timezone. Both calendar | ||
| // representations must fit, including offsets at the first and last supported day. | ||
| min_seconds = |
There was a problem hiding this comment.
[P2] Account for PyArrow's historical timezone offsets at the lower bound
For time_zone='America/Chicago', a stored TIMESTAMPTZ(6) value 0001-01-01 05:50:36.000000 +00:00 produces -62135575764000000 us. The TZDB offset is -05:50:36, so this equals cctz's local_start and passes validation. However, PyArrow chooses pytz when installed, and pytz rounds historical offsets to minutes, giving -05:51 here. The inferred Python local datetime is then 24 seconds before year 1: TimestampScalar.as_py() converts UTC using astimezone and still raises OverflowError after this batch is published. The fixed-offset tests do not cover this named-zone case. Account for the supported decoder's timezone rules in the safe bound and add a named-zone boundary case; the zoneinfo path without pytz preserves seconds and is unaffected.
There was a problem hiding this comment.
Fixed in 3489284. Agreed: pytz rounds every offset with (offset + 30) // 60 * 60, and PyArrow uses it for zone names when it is installed. The zoned bounds are now computed with both the exact and the pytz-rounded offsets at 0001-01-01 and 10000-01-01, and the stricter one wins; for America/Chicago the lower bound moves from 05:50:36Z to 05:51:00Z. Whole-minute offsets (UTC, fixed offsets, the year-9999 offsets) keep their bounds. For a zone whose local mean time rounds to a more negative offset, up to 30 seconds at the start of year 1 are now also rejected for zoneinfo clients; one bound that holds for both decoders is the trade-off we take. New UT ArrowFlightTimestampTest.ZonedLowerBoundHoldsWithPytzRoundedOffsets: the exact-offset start is rejected and 05:51:00Z is accepted; it fails when the bound uses the exact offset.
TPC-H: Total hot run time: 28431 ms |
TPC-DS: Total hot run time: 151676 ms |
ClickBench: Total hot run time: 24.4 s |
### What problem does this PR solve? Issue Number: None Related PR: apache#69771 Problem Summary: The Spark and Flink Doris connectors read a table through FE's _query_plan API and the BE Thrift scanner (open_scanner / get_next), which drains the queue that MemoryScratchSink fills with Arrow batches. When the fragment failed, the sink's close() still put the nullptr sentinel and nothing recorded the failure: no code has called ResultQueueMgr::update_queue_status since the non-pipeline engine was removed (apache#35803), so RecordBatchQueue::status() stayed OK and get_next reported eos. A scan error, a cancellation or, with this PR, a batch the Arrow Flight convertor rejects (invalid UTF-8, a DATETIME before year 1) ended the read normally with the rows fetched so far: a table with one invalid UTF-8 row read zero rows and no error. MemoryScratchSinkLocalState::close now records a failed exec_status in the queue before the sentinel (update_status ignores OK), and fetch_result checks the queue status again when it takes the sentinel, because a fetch already waiting for data passed the first check before the failure. get_next then returns the error, for example "[INVALID_ARGUMENT]Invalid Arrow Flight result in column 2 (''): Invalid: Invalid UTF8 sequence at string index 1". ### Release note A failed read through the _query_plan scan API (Spark / Flink Doris connectors) now returns the error instead of ending as a complete result. ### Check List (For Author) - Test: Unit Test / Regression test - BE UT ResultQueueMgrTest: a fetch waiting for data reports a failure recorded before the sentinel; an OK status keeps a normal end. - Regression arrow_flight_sql_p0/test_query_plan_scan_validation reads _query_plan scans over the BE Thrift scanner: before this change the invalid UTF-8 scan read 0 rows and no error; with it, the UTF-8 and timestamp scans get the errors and a valid scan reads all rows. - Behavior changed: Yes. Such scans fail instead of returning partial data. - Does this need documentation: No Co-Authored-By: Claude Opus 5.5 <[email protected]>
### What problem does this PR solve? Issue Number: None Related PR: apache#69771 Problem Summary: For an Arrow timestamp with a time zone, the Flight validator bounded values so that the local calendar in the zone, computed by cctz with the exact offsets, stays in 0001..9999. PyArrow resolves the zone name with pytz when pytz is installed, and pytz rounds every UTC offset to whole minutes. With time_zone 'America/Chicago', whose local mean time is -05:50:36, the lower bound 0001-01-01 05:50:36Z is local 0001-01-01 00:00:00 for cctz and zoneinfo, but 0000-12-31 23:59:36 for pytz (-05:51), so TimestampScalar.as_py() still raised OverflowError on a published batch. The local bounds are now also computed with the offsets rounded the way pytz does ((offset + 30) // 60 * 60, floor division), and the stricter bound wins: 05:51:00Z in that example. Bounds whose offsets are whole minutes (UTC, fixed offsets, the offsets at year 9999) do not change; for a zone whose local mean time rounds to a larger negative offset, up to 30 seconds at the start of year 1 are now rejected for every client. ### Release note None ### Check List (For Author) - Test: Unit Test - BE UT ArrowFlightTimestampTest.ZonedLowerBoundHoldsWithPytzRoundedOffsets: America/Chicago's exact-offset start is rejected and 05:51:00Z is accepted; the test fails when the bound uses the exact offset. - Behavior changed: Yes. The values above are rejected. - Does this need documentation: No Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Addressed the first review round in 3b3f2a6 and 3489284. Per finding:
The description now covers both changes, including the behavior change for |
|
run buildall |
|
/review |
There was a problem hiding this comment.
Static review of all 16 changed files at 3489284: no new verified findings. Both earlier inline findings are repaired: 4400505759 now propagates external-scan failures, including a waiting fetch; 4400505772's America/Chicago boundary value is rejected. Neither is reposted, and no earlier P0/P1 remains blocking.
Critical checkpoint conclusions:
- Goal and correctness: completed Flight batches are validated before publication; timestamp units, naive/zoned bindings, NULL parents, nested paths, const materialization and slice offsets agree with the actual schema/SerDes. Added unit and scanner regressions target the failures; they were inspected, not executed.
- Scope and reuse: validation is confined to the Flight subclass; queue propagation reuses existing first-error-wins status handling. The changes are focused on result validation and error delivery.
- Concurrency and locking: pipeline workers publish status before EOF; the Thrift fetch worker rechecks it after EOF. The queue map lock is released before blocking, the status mutex protects both reads and writes, and no new nested lock order or heavy locked work was found.
- Lifecycle and memory: temporary batches/validator trees and shared queue ownership preserve lifetimes through failure. Existing task/reader memory contexts remain active; no new ownership cycle, thread, or cross-TU static initialization dependency was introduced.
- Configuration: no new configuration or dynamic-update requirement.
- Compatibility: the added virtual override is internal BE code; no external protocol, stored layout or Arrow schema changes were found. The documented behavior change is rejection of invalid Flight results and propagation of failed connector scans.
- Thrift/protobuf IDs: no gensrc definitions changed.
- Parallel paths: both local and remote Flight readers and the external scratch sink use validation. Ordinary Doris/Python and table-format converters retain their existing paths.
- Conditional checks: comments explain UTC/local bounds, negative offset rounding, masked children and the waiting-fetch race. The actual external-plan whitelist excludes the speculative LIMIT/TopN/exchange termination case.
- Test coverage: inspected six new UTF8 cases, eight timestamp cases, two queue cases, four new regression suites and three adjusted remote suites. Coverage includes malformed encodings, row boundaries, binary exemption, nested values, NULLs, fractions, slices, calendar boundaries, historical offsets, INT96 and scanner error delivery. Test discovery and scanner API/defaults match their producers.
- Expected results: both changed .out files match the changed DATETIME input minima; separate zero-year rejection coverage remains. Output-generation provenance was not independently verified.
- Observability: validation errors retain column/path/type and row details where applicable; existing reader, pipeline and get_next context identifies the failed query/fragment. No new metric requirement was found.
- Transactions and persistence: outgoing result handling does not change visible-version, delete-bitmap, EditLog or failover logic.
- Writes and atomicity: no production data-write or commit path changes.
- FE/BE variables: no new variable or omitted transmission path.
- Performance: added work is linear validation over returned text/timestamp-bearing branches plus schema-sized temporary metadata; queue transfer adds a small status check. No new batch copy or pathological complexity was found; performance was not measured.
- Additional risks and user focus: dedicated reviews of timezone decoder behavior, nested representations, queue cancellation and test configuration found no additional verified issue. No additional user focus was supplied. Boundary assumptions were checked against PyArrow conversion and pytz rounding.
Converged in 1 round: all three reviewers returned NO_NEW_VALUABLE_FINDINGS, all changed files/checkpoints were covered, and every candidate and initial risk has a recorded disposition. No material unresolved source-code question remains. This was static review only: no builds, tests or benchmarks ran. The sleep-based waiting-fetch unit test does not deterministically force the interleaving on every execution, and arbitrary client/server timezone-database version combinations were not exhaustively validated.
Review converged after 1 round(s).
TPC-H: Total hot run time: 28635 ms |
TPC-DS: Total hot run time: 150873 ms |
ClickBench: Total hot run time: 24.26 s |
BE UT Coverage ReportIncrement line coverage Increment coverage report
|
BE Regression && UT Coverage ReportIncrement line coverage Increment coverage report
|
What problem does this PR solve?
Issue Number: None
Related PR: #68596, #68604
Problem Summary:
In short
This ports branch-4.x's #68596 and #68604 to master. An Arrow Flight result can carry values that Arrow clients cannot decode: a
STRINGvalue that is not valid UTF-8 (for exampleunhex('84')), and aDATETIMEoutside 0001-01-01..9999-12-31 (Doris's own range starts at0000-01-01 00:00:00). The Arrow writers accept both, so the BE publishes the batch and the failure surfaces later in the client: PyArrow rejects the string, and raisesOverflowErrorwhen it converts the timestamp. This PR checks every batch in the Flight convertor before it is handed out and fails the query with a server-side error that names the column, the nested path and the row. It also makes a failed_query_planscan of the Spark and Flink connectors report its error: the BE used to end such a scan as a complete result, so a rejected batch would have become silently missing rows.Background
ArrowFlightArrowBlockConvertor(be/src/format/arrow/arrow_block_convertor.h) builds the Arrow batches of an Arrow Flight result:ArrowFlightBatchReaderuses it for Flight SQL and ADBC clients, which also covers a remote Doris catalog withuse_arrow_flight = true, and the memory scratch sink uses it for the_query_planscan API of the Spark and Flink Doris connectors.utf8(orlarge_utf8) builder without looking at them. Arrow requires these values to be valid UTF-8, but onlyArray::ValidateFull()checks it; building and publishing the array does not.get_nextof the BE Thrift scanner hands to those connectors, and ends the queue with anullptrsentinel. Itsclose()put the sentinel whatever the execution status, and nothing has recorded a status in the queue since the non-pipeline engine was removed ([refactor](pipeline) Delete non-pipeline exec nodes #35803), so a fragment that failed (a scan error, a cancellation) ended the scan as if it were complete.DATETIME(p)is published astimestamp[s],timestamp[ms]ortimestamp[us]by its scale. Arrow accepts anyint64there, so a year-0 value such as0000-12-31 00:00:00(-62135683200000000us) is published as it is, while Python'sdatetime, which PyArrow converts to, only covers years 1..9999.The problem, and what it cost
unhex('84'),unhex('C0AF'), a surrogate or a code point above U+10FFFF, also inside an ARRAY / STRUCT / MAP key or valueunhex('C2')andunhex('A2')CAST('0000-12-31 00:00:00' AS DATETIME(6)), or a zero date read from a table or an INT96 Parquet file-62135683200000000; PyArrow raisesOverflowErrorconverting it_query_planscan (Spark / Flink connector) whose fragment failsget_nextreports the end of the scan after the rows fetched so far; with the validation below, a table with one invalid UTF-8 row would read zero rows and no errorThe query looks successful on the server, so neither the error nor the offending column shows up in Doris; the Flight client gets a failure that does not say which value caused it.
How this PR fixes it
ArrowBlockConvertor::convert_to_arrowbecomes virtual, andArrowFlightArrowBlockConvertoroverrides it: after the base conversion, it runsValidateFull()on every column and returnsInvalidArgument("Invalid Arrow Flight result in column <n> ('<name>'): ...")on failure.ValidateFull()checks each value, nested children included, so the row boundaries of theC2/A2case are respected. Binary columns keep their bytes; NULL values are skipped through the validity bitmap.FlightTimestampValidatorwalks each column recursively (list values, map keys and values, struct fields), reads the actual timestamp unit, and checks each value against 0001-01-01..9999-12-31, also against the local calendar when the Arrow type has a time zone. Values under a NULL list / map / struct parent are not observable and are not checked. A value outside the range returnsInvalidArgument("Arrow Flight timestamp in column '<path>' at row <n> is outside the supported 0001-9999 range (type: ...)"), with the row counted in the block.(offset + 30) // 60 * 60), and the stricter bound wins. pytz turns America/Chicago's local mean time -05:50:36 into -05:51, so 0001-01-01 05:50:36Z, local 00:00:00 for cctz, would be 0000-12-31 23:59:36 in PyArrow; the lower bound there is 05:51:00Z. In such zones up to 30 seconds at the start of year 1 are rejected.MemoryScratchSinkLocalState::closerecords a failed execution status in the result queue before it puts the sentinel, andResultQueueMgr::fetch_resultchecks the queue status again when it takes the sentinel, because a fetch already waiting for data passed the first check before the failure.get_nextthen returns the error to the connector.DorisArrowBlockConvertorstill publishes the same values in the new tests.init()andwrite_column(), and the new override sits next to them.Results
SELECT unhex(encoded) AS payloadwith an invalid UTF-8 rowInvalid Arrow Flight result in column 1 ('payload'): Invalid: Invalid UTF8 sequence at string index 0SELECT array(unhex(encoded)) AS nested...... ('nested'): Invalid: List child array invalid: Invalid: Invalid UTF8 sequence at string index 0SELECT CAST(value AS DATETIME(6)) AS event_timeover0000-12-31 00:00:00Arrow Flight timestamp in column 'event_time' at row 1 is outside the supported 0001-9999 range (type: timestamp[us]); nested values report paths such asnested[],nested.key,nested.child0000-01-01 00:00:00DATETIMEArrow Flight timestamp in column 'datetime_0' at row 1 is outside the supported 0001-9999 range (type: timestamp[s])_query_planscan throughopen_scanner/get_nextover a table with an invalid UTF-8 rowget_nextreturns[INVALID_ARGUMENT]Invalid Arrow Flight result in column 2 (''): Invalid: Invalid UTF8 sequence at string index 10000-12-31 00:00:00DATETIMEArrow Flight timestamp in column '' at row 2 is outside the supported 0001-9999 range (type: timestamp[us])0001-01-01,9999-12-31 23:59:59.999999Behavior change: a Flight query (Flight SQL, ADBC, a remote Doris catalog with
use_arrow_flight = true, or a_query_planread of the Spark / Flink connector) whose result contains aSTRINGvalue that is not valid UTF-8, or aDATETIMEoutside years 0001..9999, now fails with the error above instead of returning data. A remote Doris catalog that used to round-trip the zero date now reports this error; the three remote Doris suites that inserted0000-01-01 00:00:00as their minimum value now use0001-01-01 00:00:00, andtest_remote_doris_all_types_selectasserts the zero-date error separately. A_query_planread whose fragment fails for any reason, also a scan error or a cancellation, now gets the error fromget_nextinstead of ending as a complete result.Tests:
ArrowBlockConvertorTest: six new cases for invalid UTF-8 (four malformed encodings, per-row boundaries across a slice and the next batch, nested array / struct / map key / map value, large strings, bytes under a NULL), and valid text, NULLs and binary.ArrowFlightTimestampTest(new file): zero date, year 0 and year 10000 at scales 0 / 3 / 6, calendar boundaries and pre-epoch fractions, slices and later batches, nested values and map keys, UTC and zoned bounds, the America/Chicago lower bound with pytz's rounded offset, naive bounds that do not depend on the session time zone, and values under NULL parents; the ordinary Doris convertor still publishes the same values.ResultQueueMgrTest: a fetch already waiting for data reports a failure recorded before the sentinel; an OK status keeps a normal end.arrow_flight_sql_p0/test_query_plan_scan_validationreads_query_planscans the way the connectors do (FE_query_plan, then BEopen_scanner/get_nextover Thrift): a valid scan reads all rows, and the invalid UTF-8 and year-0 scans get the errors above.arrow_flight_sql_p0/test_flight_utf8_validation,test_flight_timestamp_range(both red before this PR on master, green with it),test_flight_int96_timestamp_range(reads the INT96 fixtures ofgroup4over HDFS; needs the Hive docker environment), and the three remote Doris suites.Release note
Arrow Flight results now fail with a server-side error when a STRING value is not valid UTF-8 or a DATETIME value is outside 0001-01-01..9999-12-31, instead of returning data that clients such as PyArrow cannot decode. This also applies to remote Doris catalogs that read over Arrow Flight and to the
_query_planreads of the Spark and Flink connectors, which now also get the error of any failed scan instead of a result that ends early.Check List (For Author)
ArrowBlockConvertorTest,ArrowFlightTimestampTest,DataTypeSerDeArrowTest,ArrowFlightVariantTest,ArrowSchemaConvertorTest,ArrowRowBatchMetadataTest: 75 tests pass; after the review fixesResultQueueMgrTest,ArrowFlightTimestampTest,ArrowBlockConvertorTest,ExternalScanContextMgrTest: 37 tests pass. Mutations: dropping the second queue-status check turns the waiting-fetch test red; bounding with the exact offset turns the America/Chicago test red. Mutations: dropping theValidateFull()result turns the four invalid-UTF-8 tests red; dropping the timestamp check turns the four out-of-range tests red.IgnoresTimestampsMaskedByNullParentsguards against false positives and stays green either way, because master's serdes write NULL parents withAppendNull().test_flight_utf8_validationandtest_flight_timestamp_rangefail without this change and pass with it;test_query_plan_scan_validationfails before the queue-status fix (the invalid UTF-8 scan reads 0 rows and no error) and passes with it;test_remote_doris_all_types_select,test_remote_doris_statistics(both.outfiles regenerated with the scripts) andtest_remote_doris_table_statspasstest_flight_int96_timestamp_range(needsenableHiveTestand the Hive docker HDFS fixtures)build-support/check-build-hygiene.shpasses_query_planscan returns its error.Original author: @Gabriel39