diff --git a/apps/studio/components/interfaces/Explorer/ExplorerQuerySourceMenu.test.tsx b/apps/studio/components/interfaces/Explorer/ExplorerQuerySourceMenu.test.tsx index 0f6ed7b2be2..6717b27d57f 100644 --- a/apps/studio/components/interfaces/Explorer/ExplorerQuerySourceMenu.test.tsx +++ b/apps/studio/components/interfaces/Explorer/ExplorerQuerySourceMenu.test.tsx @@ -44,7 +44,12 @@ describe('ExplorerQuerySourceMenu', () => { ) => customRender( - + ) @@ -53,6 +58,8 @@ describe('ExplorerQuerySourceMenu', () => { customRender( { customRender( void source: QuerySourceBinding onSourceChange: (source: QuerySourceBinding) => void } @@ -37,6 +40,8 @@ export type ExplorerQuerySourceMenuProps = { * has SQL to preserve or discard and a fresh draft does not. */ export const ExplorerQuerySourceMenu = ({ + rowLimit = 100, + onRowLimitChange, source, onSourceChange, }: ExplorerQuerySourceMenuProps) => { @@ -97,12 +102,20 @@ export const ExplorerQuerySourceMenu = ({ {source._tag === 'database' ? ( - - onSourceChange({ _tag: 'database', database_identifier }) - } - /> + <> + + onSourceChange({ _tag: 'database', database_identifier }) + } + /> + {onRowLimitChange !== undefined && ( + onRowLimitChange(Number(val))} + /> + )} + ) : ( { }) }) +describe('setCellRowLimit', () => { + it('writes the row limit onto a database cell without touching its query', () => { + expect(setCellRowLimit(DATABASE_CELL, 500)).toEqual({ ...DATABASE_CELL, row_limit: 500 }) + }) + + it('leaves a log cell unchanged, since it has no row limit concept', () => { + expect(setCellRowLimit(LOG_CELL, 500)).toEqual(LOG_CELL) + }) +}) + describe('cloneQueryCell', () => { it('copies the chart series array rather than aliasing it', () => { const clone = cloneQueryCell(DATABASE_CELL) diff --git a/apps/studio/components/interfaces/Explorer/QueryCell/QueryCell.utils.ts b/apps/studio/components/interfaces/Explorer/QueryCell/QueryCell.utils.ts index 0e5386d9831..ca6cb3edb57 100644 --- a/apps/studio/components/interfaces/Explorer/QueryCell/QueryCell.utils.ts +++ b/apps/studio/components/interfaces/Explorer/QueryCell/QueryCell.utils.ts @@ -117,6 +117,22 @@ export function setCellSql(cell: Snapshot, sql: string): QueryCell { } } +/** + * Writes a new row limit onto a database cell. A log cell has no row limit concept, so it + * passes through unchanged. + */ +export function setCellRowLimit(cell: Snapshot, rowLimit: number): QueryCell { + if (cell._tag === 'log_cell') return cloneQueryCell(cell) + + return { + ...copyQueryCellBase(cell), + _tag: 'database_cell', + unchecked_sql: cell.unchecked_sql, + row_limit: rowLimit, + database_identifier: cell.database_identifier, + } +} + /** * Builds the editor's query model from a cell and the editor's live text buffer. Branding * the buffer is the editor boundary the safe-SQL model expects; which brand applies is diff --git a/apps/studio/components/interfaces/Explorer/QueryCell/index.tsx b/apps/studio/components/interfaces/Explorer/QueryCell/index.tsx index 3807fca17c8..a58d8445433 100644 --- a/apps/studio/components/interfaces/Explorer/QueryCell/index.tsx +++ b/apps/studio/components/interfaces/Explorer/QueryCell/index.tsx @@ -10,6 +10,7 @@ import { cloneChartConfig, cloneQueryCell, getCellDisplay, + setCellRowLimit, setCellSql, toQueryModel, } from './QueryCell.utils' @@ -78,6 +79,9 @@ export const QueryCell = ({ cell }: QueryCellProps) => { chart: cloneChartConfig(display.chart), })) + const handleRowLimitChange = (rowLimit: number) => + updateQueryCell((candidate) => setCellRowLimit(candidate, rowLimit)) + return ( { onSqlCommit={handleSqlCommit} onSourceChange={handleSourceChange} onResultChange={setResult} + onRowLimitChange={handleRowLimitChange} onDisplayChange={handleDisplayChange} /> diff --git a/apps/studio/components/interfaces/Explorer/QueryEditor.tsx b/apps/studio/components/interfaces/Explorer/QueryEditor.tsx index 06e391b58ae..a9116d848e5 100644 --- a/apps/studio/components/interfaces/Explorer/QueryEditor.tsx +++ b/apps/studio/components/interfaces/Explorer/QueryEditor.tsx @@ -76,6 +76,7 @@ export type QueryEditorProps = { onSqlCommit?: (sql: string) => void onSourceChange?: (source: QuerySourceBinding) => void onResultChange: (result: QueryResult) => void + onRowLimitChange?: (val: number) => void onDisplayChange?: (display: QueryDisplay) => void } @@ -97,6 +98,7 @@ export const QueryEditor = ({ onSqlCommit, onSourceChange, onResultChange, + onRowLimitChange, onDisplayChange, }: QueryEditorProps) => { const sql = query.uncheckedSql @@ -205,6 +207,8 @@ export const QueryEditor = ({ )} {display && onDisplayChange && ( @@ -267,7 +271,7 @@ export const QueryEditor = ({ {rowLimit && ( <>

ยท

-

Limit {rowLimit} rows

+

{rowLimit < 0 ? 'No row limit' : `Limit ${rowLimit} rows`}

)} diff --git a/apps/studio/components/interfaces/Explorer/QueryTab.tsx b/apps/studio/components/interfaces/Explorer/QueryTab.tsx index 83f32f4aa5e..365dad0600d 100644 --- a/apps/studio/components/interfaces/Explorer/QueryTab.tsx +++ b/apps/studio/components/interfaces/Explorer/QueryTab.tsx @@ -10,15 +10,16 @@ import { toQuerySourceBinding } from '@/data/query-sources/query-source-registry import { explorerQueryState, useExplorerQueryStateSnapshot } from '@/state/explorer-query' import { createTabId, TabsStateContext } from '@/state/tabs' -const QUERY_ROW_LIMIT = 100 - /** Query-tab lifecycle adapter around the shared QueryEditor. */ export const QueryTab = () => { const { id, ref } = useParams() const router = useRouter() const tabs = useContext(TabsStateContext) const querySnap = useExplorerQueryStateSnapshot() + + const [rowLimit, setRowLimit] = useState(100) const [restoredQueryKey, setRestoredQueryKey] = useState() + const stateDraft = id ? querySnap.drafts[id] : undefined const draft = stateDraft?.projectRef === ref ? stateDraft : undefined const result = draft && id ? querySnap.results[id] : undefined @@ -81,7 +82,7 @@ export const QueryTab = () => { : { ...toQuerySourceBinding(draft), uncheckedSql: draft.uncheckedSql, - rowLimit: QUERY_ROW_LIMIT, + rowLimit, } return ( @@ -99,6 +100,7 @@ export const QueryTab = () => { onSqlChange={(sql) => explorerQueryState.updateDraft({ id, sql })} onSourceChange={(source) => explorerQueryState.updateDraft({ id, source })} onResultChange={handleResultChange} + onRowLimitChange={setRowLimit} /> ) } diff --git a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/QuerySourceMenu/QuerySourceMenu.tsx b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/QuerySourceMenu/QuerySourceMenu.tsx index 2bb4f249667..2725c004c3a 100644 --- a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/QuerySourceMenu/QuerySourceMenu.tsx +++ b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/QuerySourceMenu/QuerySourceMenu.tsx @@ -149,7 +149,10 @@ export const QuerySourceMenu = ({ id, runSource, canCreateLogsSnippet }: QuerySo /> )} - + sessionSnap.setLimit(Number(val))} + /> )} diff --git a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/QuerySourceMenu/RowLimitSubMenu.tsx b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/QuerySourceMenu/RowLimitSubMenu.tsx index 9015dbada13..01d7b88b199 100644 --- a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/QuerySourceMenu/RowLimitSubMenu.tsx +++ b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/QuerySourceMenu/RowLimitSubMenu.tsx @@ -7,11 +7,14 @@ import { } from 'ui' import { ROWS_PER_PAGE_OPTIONS } from '../../SQLEditor.constants' -import { useSqlEditorSessionSnapshot } from '@/state/sql-editor/sql-editor-session-state' -export const RowLimitSubMenu = () => { - const sessionSnap = useSqlEditorSessionSnapshot() - const currentLabel = ROWS_PER_PAGE_OPTIONS.find((opt) => opt.value === sessionSnap.limit)?.label +interface RowLimitSubMenuProps { + value: number + onValueChange: (value: string) => void +} + +export const RowLimitSubMenu = ({ value, onValueChange }: RowLimitSubMenuProps) => { + const currentLabel = ROWS_PER_PAGE_OPTIONS.find((opt) => opt.value === value)?.label return ( @@ -22,10 +25,7 @@ export const RowLimitSubMenu = () => { - sessionSnap.setLimit(Number(val))} - > + {ROWS_PER_PAGE_OPTIONS.map((option) => ( {option.label}