@@ -7,11 +7,21 @@ import { QueryTabs } from '../QueryTabs/QueryTabs'
77import { TableView } from '../TableView/TableView'
88import { AIAssistant } from '../AIAssistant'
99import { SqlEditor } from './SqlEditor'
10+ import { ExclamationTriangleIcon } from '@radix-ui/react-icons'
1011
1112import { exportToCSV , exportToJSON } from '../../utils/exportData'
1213import { Tab , QueryTab , TableTab , QueryExecutionResult } from '../../types/tabs'
14+ import {
15+ isSelectQuery ,
16+ hasLimitClause ,
17+ addLimitToQuery ,
18+ mightReturnLargeResultSet
19+ } from '../../utils/queryParser'
1320import './QueryWorkspace.css'
1421
22+
23+ const DEFAULT_LIMIT = 100
24+
1525interface QueryWorkspaceProps {
1626 connectionId : string
1727 onOpenTableTab ?: ( database : string , tableName : string ) => void
@@ -33,7 +43,9 @@ export function QueryWorkspace({ connectionId, onOpenTableTab }: QueryWorkspaceP
3343 const [ selectedText , setSelectedText ] = useState ( '' )
3444 const [ showAIPanel , setShowAIPanel ] = useState ( false )
3545 const editorRef = useRef < any > ( null )
36- const executeQueryRef = useRef < ( ) => void > ( )
46+ const executeQueryRef = useRef < ( ) => void > ( ( ) => { } )
47+ const [ showLimitWarning , setShowLimitWarning ] = useState ( false )
48+ const [ queryLimitOverride , setQueryLimitOverride ] = useState ( false )
3749
3850 const activeTab = tabs . find ( ( tab ) => tab . id === activeTabId )
3951 const activeResult = activeTab ? results [ activeTab . id ] : null
@@ -96,6 +108,8 @@ export function QueryWorkspace({ connectionId, onOpenTableTab }: QueryWorkspaceP
96108
97109 const handleSelectTab = useCallback ( ( tabId : string ) => {
98110 setActiveTabId ( tabId )
111+ setShowLimitWarning ( false )
112+ setQueryLimitOverride ( false )
99113 } , [ ] )
100114
101115 const handleUpdateTabTitle = useCallback (
@@ -132,7 +146,7 @@ export function QueryWorkspace({ connectionId, onOpenTableTab }: QueryWorkspaceP
132146 } , [ openTableTab , onOpenTableTab ] )
133147
134148 const executeQuery = useCallback (
135- async ( queryToExecute : string ) => {
149+ async ( queryToExecute : string , forceUnlimited = false ) => {
136150 if ( ! activeTab || activeTab . type !== 'query' ) return
137151
138152 if ( ! queryToExecute . trim ( ) ) return
@@ -141,7 +155,22 @@ export function QueryWorkspace({ connectionId, onOpenTableTab }: QueryWorkspaceP
141155 setIsExecuting ( true )
142156 const startTime = Date . now ( )
143157
144- const queryResult = await window . api . database . query ( connectionId , queryToExecute . trim ( ) )
158+ let finalQuery = queryToExecute . trim ( )
159+
160+ // Check if we should add a limit
161+ if (
162+ isSelectQuery ( finalQuery ) &&
163+ ! hasLimitClause ( finalQuery ) &&
164+ ! forceUnlimited &&
165+ ! queryLimitOverride
166+ ) {
167+ if ( mightReturnLargeResultSet ( finalQuery ) ) {
168+ setShowLimitWarning ( true )
169+ finalQuery = addLimitToQuery ( finalQuery , DEFAULT_LIMIT )
170+ }
171+ }
172+
173+ const queryResult = await window . api . database . query ( connectionId , finalQuery )
145174 const executionTime = Date . now ( ) - startTime
146175
147176 const result : QueryExecutionResult = {
@@ -164,7 +193,7 @@ export function QueryWorkspace({ connectionId, onOpenTableTab }: QueryWorkspaceP
164193 setIsExecuting ( false )
165194 }
166195 } ,
167- [ activeTab , results , connectionId ]
196+ [ activeTab , results , connectionId , queryLimitOverride ]
168197 )
169198
170199 const handleExecuteQuery = useCallback ( async ( ) => {
@@ -346,12 +375,15 @@ export function QueryWorkspace({ connectionId, onOpenTableTab }: QueryWorkspaceP
346375 < SqlEditor
347376 connectionId = { connectionId }
348377 value = { activeTab . query }
349- onChange = { ( value ) =>
378+ onChange = { ( value ) => {
350379 handleUpdateTabContent ( activeTab . id , {
351380 query : value || '' ,
352381 isDirty : true
353382 } )
354- }
383+ // Reset limit override when query changes
384+ setQueryLimitOverride ( false )
385+ setShowLimitWarning ( false )
386+ } }
355387 onMount = { handleEditorDidMount }
356388 onSelectionChange = { setSelectedText }
357389 height = "100%"
@@ -391,6 +423,9 @@ export function QueryWorkspace({ connectionId, onOpenTableTab }: QueryWorkspaceP
391423 < >
392424 < Badge size = "1" variant = "soft" >
393425 { activeResult . rowCount || activeResult . data . length } rows
426+ { activeResult . data . length === 1000 &&
427+ ! hasLimitClause ( editorRef . current ?. getValue ( ) || '' ) &&
428+ ' (limited)' }
394429 </ Badge >
395430 { activeResult . executionTime && (
396431 < Badge size = "1" variant = "soft" color = "gray" >
@@ -421,6 +456,45 @@ export function QueryWorkspace({ connectionId, onOpenTableTab }: QueryWorkspaceP
421456 ) }
422457 </ Flex >
423458
459+ { /* Limit warning */ }
460+ { showLimitWarning && activeResult ?. success && (
461+ < Box
462+ p = "2"
463+ style = { {
464+ backgroundColor : 'var(--amber-3)' ,
465+ borderBottom : '1px solid var(--amber-6)'
466+ } }
467+ >
468+ < Flex align = "center" justify = "between" >
469+ < Flex align = "center" gap = "2" >
470+ < ExclamationTriangleIcon color = "var(--amber-11)" />
471+ < Text size = "1" color = "amber" weight = "medium" >
472+ Query limited to { DEFAULT_LIMIT } rows for safety
473+ </ Text >
474+ < Text size = "1" color = "gray" >
475+ Remove LIMIT to see all results
476+ </ Text >
477+ </ Flex >
478+ < Flex gap = "2" >
479+ < Button
480+ size = "1"
481+ variant = "soft"
482+ onClick = { ( ) => {
483+ setShowLimitWarning ( false )
484+ setQueryLimitOverride ( true )
485+ handleExecuteQuery ( )
486+ } }
487+ >
488+ Run without limit
489+ </ Button >
490+ < Button size = "1" variant = "ghost" onClick = { ( ) => setShowLimitWarning ( false ) } >
491+ Dismiss
492+ </ Button >
493+ </ Flex >
494+ </ Flex >
495+ </ Box >
496+ ) }
497+
424498 < Box className = "results-content" style = { { flex : 1 } } >
425499 { activeResult ? (
426500 activeResult . success ? (
0 commit comments