Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,6 @@ export function isExplainQuery(rows: readonly unknown[]): boolean {
return 'QUERY PLAN' in firstRow && Object.keys(firstRow).length === 1
}

export function isTextFormatExplain(rows: readonly unknown[]): boolean {
if (!isExplainQuery(rows)) return false
const firstRow = rows[0] as Record<string, unknown>
return typeof firstRow['QUERY PLAN'] === 'string'
}

export function isExplainSql(sql: string): boolean {
return /^\s*explain\b/i.test(sql)
}

export function formatNodeDuration(ms: number | undefined): string {
if (ms === undefined) return '-'
if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s`
Expand Down Expand Up @@ -196,35 +186,3 @@ export function getScanBorderColor(operation: string): string {
// Default neutral color for other operations
return 'border-l-border-muted'
}

export function splitSqlStatements(sql: string): string[] {
// Enhanced tokenizer that handles:
// - Single-quoted strings: '...' (with '' escaping)
// - Double-quoted strings: "..." (with "" escaping)
// - Dollar-quoted strings: $tag$...$tag$
// - Line comments: -- (until end of line)
// - Block comments: /* ... */ (may be multiline)
// - Semicolons: ;
const tokens =
sql.match(
/'([^']|'')*'|"([^"]|"")*"|\$[a-zA-Z0-9_]*\$[\s\S]*?\$[a-zA-Z0-9_]*\$|--[^\r\n]*|\/\*[\s\S]*?\*\/|;|[^'"$;\-\/]+|./g
) || []

const statements: string[] = []
let current = ''

for (const token of tokens) {
if (token === ';') {
if (current.trim()) statements.push(current.trim())
current = ''
} else {
current += token
}
}

if (current.trim()) {
statements.push(current.trim())
}

return statements
}
20 changes: 0 additions & 20 deletions apps/studio/components/interfaces/SQLEditor/MonacoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ export type MonacoEditorProps = {
monacoRef: RefObject<Monaco | null>
autoFocus?: boolean
executeQuery: () => void
executeExplainQuery: () => void
showExplainAction?: boolean
prettifyQuery: () => void
onHasSelection: (value: boolean) => void
onMount?: (editor: IStandaloneCodeEditor) => void
Expand All @@ -48,8 +46,6 @@ export const MonacoEditor = ({
placeholder = '',
className,
executeQuery,
executeExplainQuery,
showExplainAction = true,
prettifyQuery,
onHasSelection,
onPrompt,
Expand All @@ -72,9 +68,6 @@ export const MonacoEditor = ({
const snippetRef = useRef(snippet)
snippetRef.current = snippet

const executeExplainQueryRef = useRef(executeExplainQuery)
executeExplainQueryRef.current = executeExplainQuery

const prettifyQueryRef = useRef(prettifyQuery)
prettifyQueryRef.current = prettifyQuery

Expand Down Expand Up @@ -115,19 +108,6 @@ export const MonacoEditor = ({
].join(' && ')
)

if (showExplainAction) {
editor.addAction({
id: 'run-explain-query',
label: 'Run EXPLAIN ANALYZE',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Enter],
contextMenuGroupId: 'operation',
contextMenuOrder: 1,
run: () => {
executeExplainQueryRef.current()
},
})
}

editor.addAction({
id: 'save-query',
label: 'Save Query',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ export type PotentialIssues = {
}

/** The tabs available in the SQL editor's results/utility panel. */
export type UtilityTab = 'results' | 'explain'
export type UtilityTab = 'results'
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
appendEnableRLSStatements,
assembleCompletionDiff,
buildDebugPromptText,
buildExplainSql,
checkAlterDatabaseConnection,
checkDestructiveQuery,
checkIfAppendLimitRequired,
Expand Down Expand Up @@ -1039,22 +1038,6 @@ describe('SQLEditor.utils:assembleCompletionDiff', () => {
})
})

describe('SQLEditor.utils:buildExplainSql', () => {
it('wraps a non-EXPLAIN query in EXPLAIN ANALYZE and a rollback transaction', () => {
const result = buildExplainSql(safeSql`select 1`)
expect(result).toMatch(/begin;/i)
expect(result).toMatch(/explain analyze select 1/i)
expect(result).toMatch(/rollback;/i)
})

it('does not double-wrap a query that is already an EXPLAIN', () => {
const result = buildExplainSql(safeSql`explain select 1`)
expect(result).toMatch(/explain select 1/i)
expect(result).not.toMatch(/analyze/i)
expect(result).toMatch(/rollback;/i)
})
})

describe('SQLEditor.utils:buildDebugPromptText', () => {
it('builds the debug prompt with the error message and SQL block', () => {
const result = buildDebugPromptText('select 1;', 'relation does not exist')
Expand Down
27 changes: 1 addition & 26 deletions apps/studio/components/interfaces/SQLEditor/SQLEditor.utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
safeSql,
untrustedSql,
type SafeSqlFragment,
type UntrustedSqlFragment,
} from '@supabase/pg-meta'
import { wrapWithRollback } from '@supabase/pg-meta/src/query'
import { untrustedSql, type SafeSqlFragment, type UntrustedSqlFragment } from '@supabase/pg-meta'
import { TABLE_EVENT_ACTIONS } from 'common/telemetry-constants'

import {
Expand All @@ -15,14 +9,11 @@ import {
updateWithoutWhereRegex,
} from './SQLEditor.constants'
import { ContentDiff, type IStandaloneCodeEditor } from './SQLEditor.types'
import { isExplainSql } from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer.utils'
import type { SnippetWithContent } from '@/data/content/sql-folders-query'
import type { DatabaseEventTrigger } from '@/data/database-event-triggers/database-event-triggers-query'
import { generateUuid } from '@/lib/api/snippets.browser'
import { removeCommentsFromSql } from '@/lib/helpers'
import { wrapWithRoleImpersonation } from '@/lib/role-impersonation'
import { sqlEventParser } from '@/lib/sql-event-parser'
import type { RoleImpersonationState } from '@/state/role-impersonation-state'

export type CreateTableWithoutRLS = {
schema?: string
Expand Down Expand Up @@ -323,22 +314,6 @@ export function assembleCompletionDiff(
}
}

/**
* Builds the SQL sent for an EXPLAIN run: wraps the query in `EXPLAIN ANALYZE`
* (unless it is already an EXPLAIN), applies role impersonation, and wraps the
* whole thing in a rollback transaction so EXPLAIN ANALYZE never mutates data.
*
* Takes an already-safe fragment — the untrusted→safe promotion happens at the
* caller's user-action boundary (see `acceptUntrustedSql`), not in here.
*/
export function buildExplainSql(
sql: SafeSqlFragment,
impersonatedRoleState?: RoleImpersonationState
): SafeSqlFragment {
const explainSql = isExplainSql(sql) ? sql : safeSql`EXPLAIN ANALYZE ${sql}`
return wrapWithRollback(wrapWithRoleImpersonation(explainSql, impersonatedRoleState))
}

/**
* Builds the prompt text used to ask the assistant to debug a failing snippet.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type UntrustedSqlFragment } from '@supabase/pg-meta'
import { useFlag, useParams } from 'common'
import { useParams } from 'common'
import {
createContext,
use,
Expand All @@ -24,7 +24,6 @@ import { useSnippetIdentity } from './useSnippetIdentity'
import { useSnippetTitleGenerator } from './useSnippetTitleGenerator'
import { useSqlEditorAi } from './useSqlEditorAi'
import { useSqlEditorExecution } from './useSqlEditorExecution'
import { useSqlEditorExplain } from './useSqlEditorExplain'
import { useSqlEditorShortcuts } from './useSqlEditorShortcuts'
import { isValidConnString } from '@/data/fetchers'
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
Expand All @@ -44,7 +43,7 @@ import { createTabId, useTabsStateSnapshot } from '@/state/tabs'
* state.
*
* SQL that a user runs is NOT promoted to safety here — the provider only
* exposes the already-safe `executeQuery` / `executeExplainQuery` pipelines plus
* exposes the already-safe `executeQuery` pipeline plus
* `readEditorSql` (which returns an `UntrustedSqlFragment`). Each user-action
* site (the toolbar/editor/results components and the shortcut handlers)
* promotes with `acceptUntrustedSql` right where the user acts, so the promotion
Expand All @@ -64,16 +63,13 @@ type AssistantContextValue = {
}

type SqlEditorExecution = ReturnType<typeof useSqlEditorExecution>
type SqlEditorExplain = ReturnType<typeof useSqlEditorExplain>
type SqlEditorMount = ReturnType<typeof useEditorMount>

/** Running SQL: the safe execute pipelines, their status, and formatting. */
/** Running SQL: the safe execute pipeline, its status, and formatting. */
type RunContextValue = {
executeQuery: SqlEditorExecution['executeQuery']
executeExplainQuery: SqlEditorExplain['executeExplainQuery']
readEditorSql: () => UntrustedSqlFragment | undefined
isExecuting: boolean
isExplainExecuting: boolean
potentialIssues: SqlEditorExecution['potentialIssues']
resetPotentialIssues: () => void
prettifyQuery: () => void
Expand Down Expand Up @@ -108,7 +104,7 @@ export const useSqlEditorSnippet = () => useGuardedContext(SnippetContext, 'useS
export const useSqlEditorAssistant = () =>
useGuardedContext(AssistantContext, 'useSqlEditorAssistant')

/** Run / explain execution, warnings, and query formatting. */
/** Run execution, warnings, and query formatting. */
export const useSqlEditorRun = () => useGuardedContext(RunContext, 'useSqlEditorRun')

/** Editor-surface UI state (selection, active results tab, editor mount). */
Expand All @@ -124,9 +120,6 @@ export const SQLEditorControllersProvider = ({ children }: PropsWithChildren) =>
const snapV2 = useSqlEditorV2StateSnapshot()
const { setSelectedDatabaseId } = useDatabaseSelectorStateSnapshot()

// [Ali] Kill switch to hide the SQL Editor Explain tab and its entry points
const disablePrettyExplain = useFlag('DisablePrettyExplainOnSqlEditor')

const diff = useSqlEditorDiff()
const { isDiffOpen } = diff
const prompt = useSqlEditorPrompt()
Expand Down Expand Up @@ -163,27 +156,16 @@ export const SQLEditorControllersProvider = ({ children }: PropsWithChildren) =>
id,
isDiffOpen,
hasSelection,
activeUtilityTab,
setActiveUtilityTab,
setAiTitle,
})

const { executeExplainQuery, isExplainExecuting } = useSqlEditorExplain({
id,
isDiffOpen,
setActiveUtilityTab,
})

const ai = useSqlEditorAi({ id, editorMountCount, diff, prompt })
const { acceptAiHandler, discardAiHandler } = ai

useSqlEditorShortcuts({
isDiffOpen,
isPromptOpen: promptState.isOpen,
disablePrettyExplain,
prettifyQuery,
readEditorSql,
executeExplainQuery,
acceptAiHandler,
discardAiHandler,
resetPrompt,
Expand Down Expand Up @@ -227,24 +209,13 @@ export const SQLEditorControllersProvider = ({ children }: PropsWithChildren) =>
const runValue = useMemo<RunContextValue>(
() => ({
executeQuery,
executeExplainQuery,
readEditorSql,
isExecuting,
isExplainExecuting,
potentialIssues,
resetPotentialIssues,
prettifyQuery,
}),
[
executeQuery,
executeExplainQuery,
readEditorSql,
isExecuting,
isExplainExecuting,
potentialIssues,
resetPotentialIssues,
prettifyQuery,
]
[executeQuery, readEditorSql, isExecuting, potentialIssues, resetPotentialIssues, prettifyQuery]
)

const uiValue = useMemo<UiContextValue>(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { acceptUntrustedSql } from '@supabase/pg-meta'
import { useFlag } from 'common'
import { Loader2 } from 'lucide-react'
import dynamic from 'next/dynamic'
import { useCallback } from 'react'
Expand Down Expand Up @@ -125,23 +124,17 @@ const SQLEditorMainView = () => {
const { diff, prompt } = useSqlEditorAssistant()
const { isDiffOpen } = diff
const { promptState, openPrompt } = prompt
const { executeQuery, executeExplainQuery, readEditorSql, prettifyQuery } = useSqlEditorRun()
const { executeQuery, readEditorSql, prettifyQuery } = useSqlEditorRun()
const { onMount, setHasSelection } = useSqlEditorUi()

const os = detectOS()
const showExplainAction = !useFlag('DisablePrettyExplainOnSqlEditor')

// Run/explain gestures from the editor — promote here, at the user action.
// Run gesture from the editor — promote here, at the user action.
const runQuery = useCallback(() => {
const sql = readEditorSql()
if (sql !== undefined) void executeQuery(acceptUntrustedSql(sql))
}, [executeQuery, readEditorSql])

const runExplain = useCallback(() => {
const sql = readEditorSql()
if (sql !== undefined) void executeExplainQuery(acceptUntrustedSql(sql))
}, [executeExplainQuery, readEditorSql])

return (
<div key={id} className="w-full h-full relative">
<MonacoEditor
Expand All @@ -155,8 +148,6 @@ const SQLEditorMainView = () => {
editorRef={editorRef}
monacoRef={monacoRef}
executeQuery={runQuery}
executeExplainQuery={runExplain}
showExplainAction={showExplainAction}
prettifyQuery={prettifyQuery}
onHasSelection={setHasSelection}
onMount={onMount}
Expand Down
15 changes: 2 additions & 13 deletions apps/studio/components/interfaces/SQLEditor/SQLEditorLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { acceptUntrustedSql, untrustedSql } from '@supabase/pg-meta'
import { LOCAL_STORAGE_KEYS, useFlag } from 'common'
import { LOCAL_STORAGE_KEYS } from 'common'
import { Loader2 } from 'lucide-react'
import { useCallback } from 'react'
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from 'ui'
Expand Down Expand Up @@ -86,17 +86,9 @@ const SQLEditorToolbar = () => {
const SQLEditorResultsPanel = () => {
const { id, isLoading } = useSqlEditorSnippet()
const { diff, ai } = useSqlEditorAssistant()
const { executeExplainQuery, readEditorSql, isExecuting, isExplainExecuting } = useSqlEditorRun()
const { isExecuting } = useSqlEditorRun()
const { activeUtilityTab, setActiveUtilityTab } = useSqlEditorUi()

const showExplainTab = !useFlag('DisablePrettyExplainOnSqlEditor')

// Explain gesture from the results panel — promote here, at the user action.
const runExplain = useCallback(() => {
const sql = readEditorSql()
if (sql !== undefined) void executeExplainQuery(acceptUntrustedSql(sql))
}, [executeExplainQuery, readEditorSql])

return isLoading ? (
<div className="flex h-full w-full items-center justify-center">
<Loader2 className="animate-spin text-brand" />
Expand All @@ -105,10 +97,7 @@ const SQLEditorResultsPanel = () => {
<UtilityPanel
id={id}
isExecuting={isExecuting}
isExplainExecuting={isExplainExecuting}
isDisabled={diff.isDiffOpen}
executeExplainQuery={runExplain}
showExplainTab={showExplainTab}
onDebug={ai.onDebug}
buildDebugPrompt={ai.buildDebugPrompt}
activeTab={activeUtilityTab}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const UtilityActions = ({
const removeFavorite = () => snapV2.removeFavorite(id)

const onSelectDatabase = (databaseId: string) => {
sessionSnap.resetResults(id)
sessionSnap.resetResult(id)
setLastSelectedDb(databaseId)
}

Expand Down
Loading
Loading