From 8f21997fdc0673887606ab25958006d0e664368a Mon Sep 17 00:00:00 2001 From: Peter Adams Date: Thu, 10 Sep 2026 06:11:08 +0200 Subject: [PATCH] perf: render large result sets in larger batches --- .../domains/query/ui/mixins/query_results.py | 7 ++- .../ui/test_results_incremental_rendering.py | 52 +++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/sqlit/domains/query/ui/mixins/query_results.py b/sqlit/domains/query/ui/mixins/query_results.py index a68da143..910a70a7 100644 --- a/sqlit/domains/query/ui/mixins/query_results.py +++ b/sqlit/domains/query/ui/mixins/query_results.py @@ -12,7 +12,10 @@ from .query_constants import MAX_COLUMN_CONTENT_WIDTH, MAX_RENDER_ROWS -RESULTS_RENDER_CHUNK_SIZE = 200 +# Preserve the quick initial paint for medium and large result sets, then use +# larger continuation batches to avoid repeated Arrow table rebuilds and renders. +RESULTS_RENDER_INCREMENTAL_THRESHOLD = 200 +RESULTS_RENDER_CHUNK_SIZE = 2_000 RESULTS_RENDER_INITIAL_ROWS = 20 @@ -333,7 +336,7 @@ async def _display_query_results( self._cancel_results_render() render_token = getattr(self, "_results_render_token", 0) row_limit = min(len(rows), MAX_RENDER_ROWS) - if row_limit > RESULTS_RENDER_CHUNK_SIZE: + if row_limit > RESULTS_RENDER_INCREMENTAL_THRESHOLD: self._render_results_table_incremental( columns, rows, diff --git a/tests/ui/test_results_incremental_rendering.py b/tests/ui/test_results_incremental_rendering.py index f41a53b9..098d7b25 100644 --- a/tests/ui/test_results_incremental_rendering.py +++ b/tests/ui/test_results_incremental_rendering.py @@ -7,11 +7,63 @@ import pytest +from sqlit.domains.query.ui.mixins.query_results import RESULTS_RENDER_CHUNK_SIZE from sqlit.domains.shell.app.main import SSMSTUI +from sqlit.shared.ui.widgets_tables import SqlitDataTable from .mocks import MockConnectionStore, MockSettingsStore, build_test_services, create_test_connection +@pytest.mark.asyncio +async def test_incremental_rendering_uses_large_batches(monkeypatch): + """Large results should minimize table updates after the fast first paint.""" + connections = [create_test_connection("test-db", "sqlite")] + services = build_test_services( + connection_store=MockConnectionStore(connections), + settings_store=MockSettingsStore({"theme": "tokyo-night"}), + ) + app = SSMSTUI(services=services) + columns = ["id", "value"] + rows = [(index, f"value-{index}") for index in range(5_000)] + batch_sizes: list[int] = [] + initial_row_counts: list[int] = [] + original_add_rows = SqlitDataTable.add_rows + original_replace = app._replace_results_table_with_table + + def _record_add_rows(self, batch): + batch = list(batch) + batch_sizes.append(len(batch)) + return original_add_rows(self, batch) + + def _record_replace(self, table): + initial_row_counts.append(table.row_count) + return original_replace(table) + + monkeypatch.setattr(SqlitDataTable, "add_rows", _record_add_rows) + app._replace_results_table_with_table = MethodType(_record_replace, app) + + async with app.run_test(size=(120, 40)) as pilot: + await pilot.pause() + await app._display_query_results( + columns=columns, + rows=rows, + row_count=len(rows), + truncated=False, + elapsed_ms=0, + ) + + assert initial_row_counts == [20] + + await pilot.pause() + + assert app.results_table.row_count == len(rows) + assert batch_sizes == [ + RESULTS_RENDER_CHUNK_SIZE, + RESULTS_RENDER_CHUNK_SIZE, + len(rows) - 20 - (2 * RESULTS_RENDER_CHUNK_SIZE), + ] + + @pytest.mark.asyncio async def test_incremental_rendering_decimal_scale_mismatch(): """Incremental rendering should not drop rows when Decimal scale changes mid-stream."""