diff --git a/e2e/support/start-mock-trino.ts b/e2e/support/start-mock-trino.ts index 2610438e..6b587280 100644 --- a/e2e/support/start-mock-trino.ts +++ b/e2e/support/start-mock-trino.ts @@ -188,6 +188,28 @@ function startPagedQuery(id: string, sql: string): Page | null { }); return { id, nextUri: nextUri(id, 1), stats: { state: 'QUEUED' } }; } + if (sql.includes('FROM huge_table')) { + // First page is empty; the follow-up page exceeds MAX_CLIENT_ROWS. + queries.set(id, { + sql, + pages: [ + { + id, + columns: [{ name: 'id', type: 'integer' }], + data: Array.from({ length: 10_001 }, (_, i) => [i + 1]), + nextUri: nextUri(id, 2), + stats: { state: 'RUNNING' } + } + ] + }); + return { + id, + columns: [{ name: 'id', type: 'integer' }], + data: [], + nextUri: nextUri(id, 1), + stats: { state: 'RUNNING' } + }; + } if (sql.includes('FROM finished_paged_table')) { queries.set(id, { sql, pages: [{ id, data: rows(11, 10), stats: { state: 'FINISHED' } }] }); return { diff --git a/e2e/trino/trino.spec.ts b/e2e/trino/trino.spec.ts index 514794d1..5fd88e53 100644 --- a/e2e/trino/trino.spec.ts +++ b/e2e/trino/trino.spec.ts @@ -36,6 +36,50 @@ test.describe('Trino query editor', () => { await expect(table.getByRole('cell', { name: '1' })).toBeVisible(); await expect(table.getByRole('cell', { name: 'Alice' })).toBeVisible(); await expect(page.getByText('Rows 1–3 of 3')).toBeVisible(); + await expect(page.getByText(/^3 rows returned \|/)).toBeVisible(); + }); + + test('hitting the row limit is reported in the status bar', async ({ page }) => { + await setTabSql(page, 'SELECT * FROM huge_table'); + await page.goto('/trino'); + await waitForHydration(page); + await page.locator('.monaco-editor').first().click(); + + await page.getByRole('button', { name: 'Run', exact: true }).click(); + await waitForQueryComplete(page); + + const status = page.locator('[data-query-state="FINISHED"]'); + await expect(status.getByText('Row limit reached', { exact: true })).toBeVisible(); + await expect(status.getByText(/^10,000 rows returned \|/)).toBeVisible(); + await expect(status).not.toContainText('rows processed'); + await expect(page.getByRole('table', { name: 'Query results' })).toBeVisible(); + // A single statement's row limit is only reported in the status bar. + await expect(page.getByText(/query cancelled/)).toHaveCount(0); + }); + + test('row limit in a multi-statement script is reported on the affected statement', async ({ + page + }) => { + await setTabSql(page, 'SELECT * FROM huge_table; SELECT id, name FROM users'); + await page.goto('/trino'); + await waitForHydration(page); + await page.locator('.monaco-editor').first().click(); + + await page.keyboard.press('Control+Shift+Enter'); + await expect(page.getByRole('table', { name: 'Query results — Statement 2' })).toBeVisible(); + + const panelFor = async (index: number) => { + const toggle = page.getByRole('button', { name: `Toggle statement ${index} results` }); + return page.locator(`[id="${await toggle.getAttribute('aria-controls')}"]`); + }; + const warning = 'Row limit of 10,000 reached — query cancelled.'; + await expect((await panelFor(1)).getByText(warning)).toBeVisible(); + await expect((await panelFor(2)).getByText(warning)).toHaveCount(0); + + // The status bar describes the last statement, which completed normally. + const status = page.locator('[data-query-state="FINISHED"]'); + await expect(status.getByText('Finished', { exact: true })).toBeVisible(); + await expect(status.getByText(/^3 rows returned \|/)).toBeVisible(); }); test('Ctrl+Enter triggers query execution', async ({ page }) => { diff --git a/messages/de.json b/messages/de.json index 9859f874..5f538f33 100644 --- a/messages/de.json +++ b/messages/de.json @@ -84,7 +84,10 @@ "trino_state_finished": "Abgeschlossen", "trino_state_failed": "Fehlgeschlagen", "trino_state_cancelled": "Abgebrochen", - "trino_progress_info": "{rows} Zeilen verarbeitet | {elapsed} s", + "trino_state_row_limit": "Zeilenlimit erreicht", + "trino_progress_info": "{rows} Zeilen zurückgegeben | {elapsed}", + "trino_elapsed_ms": "{value} ms", + "trino_elapsed_s": "{value} s", "trino_row_limit_reached": "Zeilenlimit von {limit} erreicht — Abfrage abgebrochen.", "trino_catalog_browser": "Katalogbrowser", "trino_catalog_browser_toggle": "Katalogbrowser ein-/ausblenden", diff --git a/messages/en.json b/messages/en.json index 8e58ff22..465166a9 100644 --- a/messages/en.json +++ b/messages/en.json @@ -84,7 +84,10 @@ "trino_state_finished": "Finished", "trino_state_failed": "Failed", "trino_state_cancelled": "Cancelled", - "trino_progress_info": "{rows} rows processed | {elapsed}s", + "trino_state_row_limit": "Row limit reached", + "trino_progress_info": "{rows} rows returned | {elapsed}", + "trino_elapsed_ms": "{value} ms", + "trino_elapsed_s": "{value} s", "trino_row_limit_reached": "Row limit of {limit} reached — query cancelled.", "trino_catalog_browser": "Catalog browser", "trino_catalog_browser_toggle": "Toggle catalog browser", diff --git a/src/lib/components/trino/StatementResult.svelte b/src/lib/components/trino/StatementResult.svelte index 5b06f6d7..2f0baee5 100644 --- a/src/lib/components/trino/StatementResult.svelte +++ b/src/lib/components/trino/StatementResult.svelte @@ -1,7 +1,8 @@