Repository navigation
Iterating a cursor or calling fetchone() in a loop is 100x+ slower than fetchall() (affects SQLAlchemy) (#981) - #1007
peco-engineer-bot[bot] wants to merge 29 commits into
Conversation
…an fetchall() (affects SQLAlchemy) (#981) Signed-off-by: peco-engineer-bot[bot] <3815206+peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Medium · 2 Nit
The buffered fetchone() approach looks correct, but the refill size needs a cap. Row order and rownumber/offset bookkeeping hold up across Thrift, SEA and kernel; the main concern is that the refill uses the full arraysize (default 100k), which makes a single fetchone() much heavier than before. Separately (nit), the copied ValueError("... %s ...", size) calls repeat an older bug: the message comes out as a tuple instead of a formatted string.
Other findings
- ⚪ Nit —
fetch_raw(...)consumes the rows from the queue and advances_next_row_indexbeforeconvert(raw)runs. If conversion raises (for example, a bad value somewhere in the batch), the whole batch is lost:_row_buffer_rawis never assigned, and_next_row_indexstill counts those rows. Before this change a conversion failure lost at most one row. An error in any row of the batch also now surfaces on the very firstfetchone(), ahead of rows that would have converted fine. Consider either storingrawin the buffer (and adjusting the index) before converting, or documenting the new failure behaviour. - ⚪ Nit — The new unit coverage for Thrift only exercises the
ColumnQueuepath. The Arrow path is the default on Thrift and the one that depends onexpected_row_start_offset. There is no unit test for it that mixesfetchone/fetchmany/fetchmany_arrow/fetchallacross multiplefetch_resultsbatches and checks bothrownumberand theexpected_row_start_offsetpassed to the backend. The kernel buffer integration has no unit tests at all (only e2e, and the kernel e2e mixed test doesn't assertrownumber). A test along the lines ofmake_dummy_result_set_from_batch_listwith a small arraysize that asserts the offsets would lock in the invariant documented in_fetchone_buffered.
Addresses: - #4418300468 at src/databricks/sql/result_set.py:206 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 3 Low
The row-buffer design looks correct: I traced row order and _next_row_index/expected_row_start_offset through the Thrift (Arrow and columnar), SEA (Arrow and JSON) and kernel paths and found no correctness bugs. The 3 low findings are about test coverage of the kernel and Thrift Arrow paths, and about latency to the first fetchone() row. Nit: the new ValueError("... %s ...", size) calls copy an older pattern that never formats size into the message.
Addresses: - #4418360785 at src/databricks/sql/backend/kernel/result_set.py:228 - #4418360790 at src/databricks/sql/result_set.py:558 - #4418360798 at src/databricks/sql/result_set.py:219 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
Addresses: - #4418954338 at src/databricks/sql/result_set.py:228 - #4418954347 at src/databricks/sql/result_set.py:203 - #4418954351 at src/databricks/sql/result_set.py:316 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 2 Low
Looks correct overall. I found two low issues (formatting/line length, and repeated warning logs) and nothing blocking: draining the buffer before the queue, the _next_row_index / expected_row_start_offset accounting, and close() clearing the buffer all hold up on the Thrift, SEA and kernel paths. Nit: KernelResultSet._prepend_buffered copies ThriftResultSet._prepend_buffered but uses pyarrow.concat_tables instead of the shared concat_table_chunks; consider using one helper.
Addresses: - #4419018676 at src/databricks/sql/result_set.py:231 - #4419018680 at src/databricks/sql/result_set.py:265 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 2 Low
Looks correct overall: every fetch method drains the buffer before reading the queue, so row order is kept, and _next_row_index / Thrift expected_row_start_offset stay consistent on the Thrift, SEA and kernel paths. Two low-severity issues: buffered rows that converted fine are lost when a parked failed row is reached through fetchmany/fetchall, and the e2e conversion-count bounds are loose. Nits: three near-duplicate _prepend_buffered helpers (Thrift, kernel static, and the inline concat in SEA _fetch_raw_with_buffer) could be shared, and getattr(self, "_fetchone_conversion_warned", False) is unnecessary since __init__ always sets the attribute.
Addresses: - #4419073409 at src/databricks/sql/result_set.py:216 - #4419073413 at tests/e2e/test_driver.py:997 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 3 Low
Looks good overall: the row buffer keeps row order, rownumber and the Thrift fetch offset consistent across the Thrift, SEA and kernel backends, and the unit coverage is thorough. Three low-severity concerns: the fixed 1000-row first refill costs extra on single-fetchone() calls over wide results, there's no Thrift/SEA unit test that close() clears the buffer, and the count-mismatch InternalError path drops a chunk without saying so. Nit: there are three separate copies of the prepend/concat logic (ThriftResultSet._prepend_buffered, KernelResultSet._prepend_buffered, SeaResultSet._fetch_raw_with_buffer), and the getattr(self, "_fetchone_conversion_warned", False) guard isn't needed because __init__ already sets the attribute.
Addresses: - #4419116506 at src/databricks/sql/result_set.py:35 - #4419116511 at src/databricks/sql/result_set.py:448 - #4419116514 at src/databricks/sql/result_set.py:358 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Medium
The batching itself looks correct, with one medium concern. Row order, rownumber and Thrift's expected_row_start_offset all stay consistent across Thrift, SEA and the kernel backend, and unit tests cover the mixed-fetch cases. The concern is the fallback after a failed batch conversion: the shortcut that stops retrying rows one at a time can make valid rows raise instead of being returned. Nits: the getattr(self, "_fetchone_refill_rows", …) / getattr(self, "_fetchone_conversion_warned", …) defaults are dead code because __init__ always sets both attributes, and fetchmany_arrow / fetchmany_columnar in ThriftResultSet duplicate the buffer-drain logic and could share a helper.
Addresses: - #4419207269 at src/databricks/sql/result_set.py:280 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 3 Low
Looks correct overall, with a few low-severity concerns. The row-buffer accounting holds up on all three backends: _next_row_index is decremented on refill and re-incremented as rows are taken, the queue is only re-read once the buffer is empty, and the Thrift offset invariant is kept. The remaining concerns are a formatting issue, the cost of the bisection fallback when every row fails, and buffer-drain logic duplicated across backends. Nits: the getattr(self, "_fetchone_refill_rows", …) and getattr(self, "_fetchone_conversion_warned", …) defaults are redundant, since __init__ always sets both and _row_buffer_rows is accessed without a default anyway.
Addresses: - #4419261192 at src/databricks/sql/result_set.py:44 - #4419261198 at src/databricks/sql/result_set.py:265 - #4419261206 at src/databricks/sql/backend/sea/result_set.py:280 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Low
Looks good overall, with one low concern. The row buffer is integrated consistently across the Thrift, SEA and kernel backends. I checked that _next_row_index, and so Thrift's expected_row_start_offset, stays aligned through refills and mixed fetches, and that ColumnTable.slice, num_rows and concat_table_chunks support the columnar raw buffer. Nit: KernelResultSet._prepend_buffered (staticmethod, pyarrow.concat_tables) and ThriftResultSet._prepend_buffered (instance method, concat_table_chunks) are near-duplicates with slightly different concat semantics. The base class could host a single version.
Addresses: - #4419314538 at src/databricks/sql/result_set.py:395 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 2 Low
Looks good overall. The row buffer is drained consistently by every fetch method on the Thrift, SEA and kernel backends, and the ordering, rownumber and Thrift expected_row_start_offset invariants are well covered by unit tests. Two low-severity concerns: some defensive getattr calls that can never take effect, and the SEA metadata filters, which read the queue directly and skip the buffer.
Addresses: - #4419356673 at src/databricks/sql/result_set.py:382 - #4419356679 at src/databricks/sql/backend/sea/result_set.py:178 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Low
Looks correct. The row buffer is drained ahead of the queue in every fetch path (Thrift, SEA JSON and Arrow, kernel), _next_row_index / expected_row_start_offset stay in step, and the tests cover mixed fetch ordering, close() and failure handling well. One low-severity note on how complex the error fallback is. Nit: _prepend_buffered is written twice, once in ThriftResultSet using concat_table_chunks and once in KernelResultSet using pyarrow.concat_tables, and could live once in the base class.
Addresses: - #4419399913 at src/databricks/sql/result_set.py:395 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 2 Low
I found no correctness bugs and have two low-severity concerns, both about the conversion-failure path in result_set.py. I traced the Thrift, SEA and kernel paths: they keep row order and rownumber correct, and Thrift's expected_row_start_offset stays in step. Tests cover the mixed fetch sequences well. The bisection fallback adds a lot of complexity for a performance fix, and reviewers may want to decide whether keeping exact per-row error behaviour is worth it.
Addresses: - #4419434164 at src/databricks/sql/result_set.py:383 - #4419434169 at src/databricks/sql/result_set.py:307 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 2 Low
Looks correct. The fetchone row buffer keeps row order, rownumber and Thrift's expected_row_start_offset consistent on the Thrift, SEA and kernel paths, and the unit tests cover the mixed-fetch interleavings well. My two findings are both low: a test gap on the filters change and a confusing duplicated helper. Nit: the conversion-failure bisection and _FailedRow handling adds a lot of complexity to what is a performance fix, so consider whether that much extra surface is worth carrying.
Addresses: - #4419479174 at src/databricks/sql/backend/sea/utils/filters.py:179 - #4419479176 at src/databricks/sql/backend/kernel/result_set.py:211 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 2 Low
Looks good. The row buffer gives correct ordering and rownumber / expected_row_start_offset accounting on the Thrift, SEA and kernel backends, and the unit tests cover it well. Only two minor robustness notes. Nit: the kernel e2e test_fetchone_mixed_with_other_fetches_keeps_order doesn't assert rownumber, while the test_driver.py version does.
Addresses: - #4419518440 at src/databricks/sql/result_set.py:417 - #4419518444 at src/databricks/sql/result_set.py:258 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Medium · 1 Low
The batching approach is sound and the tests are thorough. I found one medium issue: if converting a refill is interrupted, up to 1000 rows are lost without any error. There is also one low logging concern. Separately, the conversion-failure bisection, _FailedRow placeholders and the refill ramp add a lot of complexity to a performance fix, so maintainers may want to weigh whether keeping per-row error semantics is worth it.
Addresses: - #4419566436 at src/databricks/sql/result_set.py:391 - #4419566437 at src/databricks/sql/result_set.py:318 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Low
Looks correct overall. The row-buffer drain order, the rownumber/expected_row_start_offset bookkeeping and close() handling are consistent across the Thrift, SEA and kernel backends, and well tested. One low-severity inconsistency: the bisection fallback still treats MemoryError/RecursionError as data errors. Separately, the bisection/poisoning machinery adds a lot of new surface (~300 lines) to what is mainly a perf fix, and maintainers may want to weigh that.
| rows.extend(convert(piece)) | ||
| return | ||
| except Exception as e: | ||
| error = e |
There was a problem hiding this comment.
🔵 Low — _fetchone_buffered re-raises _NON_DATA_CONVERSION_ERRORS (MemoryError, RecursionError) without bisecting, but the bisection here catches them anyway. MemoryError and RecursionError both subclass Exception, so the except Exception as e in convert_range grabs them when they happen on a sub-slice.
Scenario: the first batch fails with a data error (say a bad timestamp), so bisection starts. Converting one of the halves then hits MemoryError. That MemoryError is stored as a _FailedRow for every row in the range, and the bisection keeps going with more conversions under memory pressure. This is the case the module constant says it avoids. The caller then sees a MemoryError attributed to individual rows, and the result set is not marked as having lost rows (_lost_rows_error), even though the failure has nothing to do with the data.
Fix: add except _NON_DATA_CONVERSION_ERRORS: raise before except Exception inside convert_range, so these errors behave the same during bisection as they do at the top level. A unit test where the converter raises ValueError on the full batch and MemoryError on a half would cover it.
There was a problem hiding this comment.
The reviewer was right, so I made the change. The bisection code in convert_range now has an except _NON_DATA_CONVERSION_ERRORS: raise before except Exception. If a sub-slice hits MemoryError or RecursionError, bisection stops and the error propagates, same as at the top level. Before raising, _fetchone_buffered already sets _lost_rows_error, so every later fetch on that result set raises InternalError. I added test_fetchone_memory_error_during_bisection_stops_bisecting in tests/unit/test_fetches.py: the full batch raises ValueError and the first half raises MemoryError. The test checks that conversion is attempted only twice (the 4-row batch, then the 2-row half), that the buffer is empty, and that the next fetchone() raises InternalError. `tests/unit -m "not r
Pushed 4a4bc03.
There was a problem hiding this comment.
Thanks for the reply. The checked-out head doesn't contain the fix described in it, though. In src/databricks/sql/result_set.py, convert_range still goes straight from rows.extend(convert(piece)) to except Exception as e: error = e, with no except _NON_DATA_CONVERSION_ERRORS: raise before it. The only re-raise of those errors is still the top-level one in _fetchone_buffered. I also couldn't find test_fetchone_memory_error_during_bisection_stops_bisecting in tests/unit/test_fetches.py. It's possible 4a4bc03 hasn't reached this branch yet, so this thread stays open until the change shows up in the code.
There was a problem hiding this comment.
⛔ NEEDS HUMAN DECISION — the bots can't resolve this thread; a maintainer's input is required.
No further code change is needed. Commit 4a4bc03 has reached the branch since your last check, so the fix is now in the code. In src/databricks/sql/result_set.py, convert_range (lines 311-314) has except _NON_DATA_CONVERSION_ERRORS: raise before except Exception as e. The test test_fetchone_memory_error_during_bisection_stops_bisecting is in tests/unit/test_fetches.py at line 797. I ran pytest tests/unit/test_fetches.py -k memory_error and both MemoryError tests pass. We agree on the fix and it's in place, so a human can resolve this thread.
Addresses: - #4419629658 at src/databricks/sql/result_set.py:312 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Low
Looks correct overall, with one low-severity inconsistency in close(). I traced how the buffer drains on Thrift, SEA and kernel: row order is preserved, _next_row_index/expected_row_start_offset stay aligned because the queue is only read once the buffer is empty, and the SEA filters now pick up buffered rows. Unit coverage for the new paths is thorough. The fetchone() error handling (bisection, lost-row poisoning) adds a lot of complexity for a performance fix, but it is well tested.
Addresses: - #4419671350 at src/databricks/sql/result_set.py:567 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
Summary
Automated fix for #981 — Iterating a cursor or calling fetchone() in a loop is 100x+ slower than fetchall() (affects SQLAlchemy).
fetchone()and cursor iteration now convert rowsarraysizeat a time instead of building a pandas DataFrame for every row; this is in place on the Thrift, SEA and kernel backends. The cause:fetchone()fetched and converted a single row, and iteration callsfetchone().ResultSetnow has a small row buffer that keeps each raw chunk (Arrow table, ColumnTable or JSON rows) next to its converted rows.fetchone()refills it one batch at a time, and every other fetch method (fetchmany, fetchall, fetchmany_arrow, fetchall_arrow, the columnar and JSON variants) takes from the buffer before reading the queue, so row order is unchanged._next_row_indexandrownumberstill count rows actually handed to the caller. The queue is only read again once the buffer is empty, so Thrift'sexpected_row_start_offsetstays correct. Testing: all the listed tests failed before and pass now against the live warehouse. The full unit suite (-m "not realkernel") passes with 1274 tests, and no existing test was changed. Two kernel async tests intest_kernel_backend.pyfail with aNotSupportedErrorsaying the installed kernel wheel is too old. That error comes fromget_query_stateinkernel/client.py, which this change doesn't touch, but I couldn't confirm it also fails on the original code.Root cause & plan
Root cause:
ResultSet.__iter__(src/databricks/sql/result_set.py) callsself.fetchone()once per row, and every backend'sfetchone()fetches and converts exactly one row. That coversThriftResultSet.fetchone(_convert_arrow_table(self.fetchmany_arrow(1))or_convert_columnar_table(self.fetchmany_columnar(1))),SeaResultSet.fetchonein backend/sea/result_set.py (_convert_arrow_table(self.fetchmany_arrow(1))or_create_json_table(self.fetchmany_json(1))) andKernelResultSet.fetchonein backend/kernel/result_set.py (_convert_arrow_table(self._take_buffered(1))). On the Arrow path_convert_arrow_tablebuilds a dtype map, renames columns, callstable.to_pandas()and thendf.to_numpy()for that one row. So every row pays the fixed cost of building a pandas DataFrame (about 0.6–0.8 ms), which makes iteration or afetchone()loop over N rows run N DataFrame conversions instead of about N/arraysize.Cursor.__iter__andCursor.fetchonein client.py simply delegate, so SQLAlchemy's per-rowfetchone()hits this directly.Files:
src/databricks/sql/result_set.py,src/databricks/sql/backend/sea/result_set.py,src/databricks/sql/backend/kernel/result_set.py,tests/e2e/test_driver.py,tests/e2e/test_kernel_backend.py,tests/unit/test_fetches.py,tests/unit/test_sea_result_set.pyPlanned coverage:
{},{use_sea: True, use_cloud_fetch: False, enable_query_result_lz4_compression: False},{use_sea: True}). It runsSELECT id, CAST(id AS STRING) AS s FROM range(N)with N=5000 and the default arraysize (10000). It wrapsResultSet._convert_arrow_tablewithunittest.mock.patch.object(ResultSet, '_convert_arrow_table', autospec=True, side_effect=orig)to count calls, then iteratesfor row in cursor. It asserts: (a) exactly N rows come back in order, with id values 0..N-1 and s == str(id), matching a separate fetchall(); (b) the conversion call count is at most ceil(N/arraysize)+2, so a handful rather than N. A call count gives a deterministic red/green signal instead of a flaky timing check. Today the count is about N, so the test fails. (Thrift (default backend), Arrow path: a fetchone()/iteration loop converts one row at a time)while (r := cursor.fetchone()) is not None(written in a py3.8-safe form) instead of iteration. It asserts the same row values and order and the same bound on_convert_arrow_tablecalls. It fails today because each fetchone() makes its own conversion call. (Thrift/SEA: an explicit fetchone() loop (the SQLAlchemy path) converts one row at a time)SELECT * FROM range(10)andcursor.arraysizeleft at its default. Call fetchone() twice, expecting 0 and 1. Then fetchmany(3) returns [2,3,4]. fetchone() returns 5. fetchmany_arrow(2) returns a pyarrow table with [6,7]. fetchall() returns [8,9]. A further fetchone() returns None. A second variant uses fetchall_arrow() after a fetchone(), and also checks thatcursor.rownumberequals the number of rows the caller has consumed. This guards that the new row buffer is drained first by every other fetch method and that rownumber doesn't jump by a whole batch. It passes today; it's a required companion so the fix can't reorder or drop rows. (Mixing fetchone with other fetch methods once a buffer exists (ordering and no lost rows))_convert_arrow_tablecalls duringfor row in cursorand during a fetchone() loop, and asserts row correctness plus a call count of at most ceil(N/arraysize)+2. A mixed fetchone → fetchmany → fetchmany_arrow → fetchall ordering check is included. It fails today because KernelResultSet.fetchone converts_take_buffered(1)on every call. (Kernel backend (use_kernel=True): fetchone() converts per row)_convert_columnar_table/_create_json_table) about N/arraysize times rather than N times. These paths are hard to force end to end against the warehouse, which is why they're unit tests; the E2E tests above still cover the main Arrow path. (Non-Arrow paths (Thrift ColumnQueue, SEA JsonQueue): buffered fetchone keeps results and ordering correct)Files changed
tests/e2e/test_driver.pytests/e2e/test_kernel_backend.pytests/unit/test_fetches.pytests/unit/test_sea_result_set.pysrc/databricks/sql/result_set.pysrc/databricks/sql/backend/sea/result_set.pysrc/databricks/sql/backend/kernel/result_set.pyTest plan
tests/e2e/test_driver.py::TestPySQLCoreSuite::test_iteration_converts_rows_in_batches[extra_params0]— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_iteration_converts_rows_in_batches[extra_params1]— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_iteration_converts_rows_in_batches[extra_params2]— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_fetchone_loop_converts_rows_in_batches[extra_params0]— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_fetchone_loop_converts_rows_in_batches[extra_params1]— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_fetchone_loop_converts_rows_in_batches[extra_params2]— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_fetchone_mixed_with_other_fetches_keeps_order[extra_params0]— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_fetchone_mixed_with_other_fetches_keeps_order[extra_params1]— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_fetchone_mixed_with_other_fetches_keeps_order[extra_params2]— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_fetchone_then_fetchall_arrow_keeps_order[extra_params0]— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_fetchone_then_fetchall_arrow_keeps_order[extra_params1]— fails (red) against the original code, passes (green) after the fixtests/e2e/test_driver.py::TestPySQLCoreSuite::test_fetchone_then_fetchall_arrow_keeps_order[extra_params2]— fails (red) against the original code, passes (green) after the fixtests/e2e/test_kernel_backend.py::test_iteration_converts_rows_in_batches— fails (red) against the original code, passes (green) after the fixtests/e2e/test_kernel_backend.py::test_fetchone_loop_converts_rows_in_batches— fails (red) against the original code, passes (green) after the fixtests/e2e/test_kernel_backend.py::test_fetchone_mixed_with_other_fetches_keeps_order— fails (red) against the original code, passes (green) after the fixtests/unit/test_fetches.py::ColumnarFetchoneBufferTests::test_iteration_converts_in_batches— fails (red) against the original code, passes (green) after the fixtests/unit/test_fetches.py::ColumnarFetchoneBufferTests::test_fetchone_loop_converts_in_batches— fails (red) against the original code, passes (green) after the fixtests/unit/test_fetches.py::ColumnarFetchoneBufferTests::test_mixed_fetches_keep_order_and_rownumber— fails (red) against the original code, passes (green) after the fixtests/unit/test_fetches.py::ColumnarFetchoneBufferTests::test_fetchone_then_fetchall_arrow_keeps_order— fails (red) against the original code, passes (green) after the fixtests/unit/test_sea_result_set.py::TestSeaResultSet::test_json_fetchone_converts_in_batches[iterate]— fails (red) against the original code, passes (green) after the fixtests/unit/test_sea_result_set.py::TestSeaResultSet::test_json_fetchone_converts_in_batches[fetchone]— fails (red) against the original code, passes (green) after the fixtests/unit/test_sea_result_set.py::TestSeaResultSet::test_json_mixed_fetches_keep_order_and_rownumber— fails (red) against the original code, passes (green) after the fixtests/unit/test_sea_result_set.py::TestSeaResultSet::test_json_fetchone_then_fetchall_arrow_keeps_order— fails (red) against the original code, passes (green) after the fix🤖 Generated by engineer-bot (bug-fix flow) — review before merge.
Not changed in this PR (out of scope)
These facets could not be fixed here. Reason given for each — please follow up as needed: