mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 07:09:07 +03:00
## Summary PR **4 of 6** in the SQLEditor decomposition stack. Extracts the two query-run concerns out of the composition root into cohesive hooks. Behavior-preserving — the characterization suite stays green. ## New hooks - `useSqlEditorExecution` — the execute mutation, the `executeQuery` pipeline (destructive-query gating, lazy title generation, connection resolution), and the `potentialIssues` warning-modal state. - `useSqlEditorExplain` — the execute-explain mutation and the `executeExplainQuery` pipeline. Both pipelines accept an **already-promoted `SafeSqlFragment`**. ## Safe-SQL boundary Per review guidance, `acceptUntrustedSql` promotion stays in the **composition root's** run/explain gesture handlers and warning-modal confirm handlers — as close to the explicit user action as possible, so it's auditable. The hooks never call `acceptUntrustedSql`; they only accept safe SQL. `executeQuery` lists its (now all-stable) dependencies directly rather than suppressing `react-hooks/exhaustive-deps`. ## Verification - `vitest` — 11 characterization tests pass - `pnpm --filter studio typecheck` — clean for SQL editor files - `eslint` / `lint:ratchet` — pass (0 new violations) - `prettier --check` — clean `SQLEditorContent` is down to ~530 lines (from 1056 at the start of the stack). Remaining: PR5 (AI/diff + shortcuts), PR6 (JSX pane split + cleanup). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved SQL query execution safeguards for potentially destructive statements, unsafe updates, and missing row-level security coverage. * Improved EXPLAIN handling, including clearer validation for unsupported multi-statement queries. * Preserved query results, errors, and EXPLAIN output more reliably in the SQL editor. * **Improvements** * Added clearer execution status handling and more consistent editor focus, highlighting, and utility-tab behavior. * Improved connection selection and SQL execution reliability across supported database configurations. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
118 lines
3.9 KiB
TypeScript
118 lines
3.9 KiB
TypeScript
import { type SafeSqlFragment } from '@supabase/pg-meta'
|
|
import { useParams } from 'common'
|
|
import { useCallback } from 'react'
|
|
import { toast } from 'sonner'
|
|
|
|
import { buildExplainSql } from './SQLEditor.utils'
|
|
import { useSQLEditorContext } from './SQLEditorContext'
|
|
import { splitSqlStatements } from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer.utils'
|
|
import { isValidConnString } from '@/data/fetchers'
|
|
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
|
|
import { useExecuteSqlMutation } from '@/data/sql/execute-sql-mutation'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
|
|
import {
|
|
isRoleImpersonationEnabled,
|
|
useGetImpersonatedRoleState,
|
|
} from '@/state/role-impersonation-state'
|
|
import { useSqlEditorSessionSnapshot } from '@/state/sql-editor/sql-editor-session-state'
|
|
|
|
type UseSqlEditorExplainArgs = {
|
|
id: string
|
|
isDiffOpen: boolean
|
|
setActiveUtilityTab: (tab: string) => void
|
|
}
|
|
|
|
export function useSqlEditorExplain({
|
|
id,
|
|
isDiffOpen,
|
|
setActiveUtilityTab,
|
|
}: UseSqlEditorExplainArgs) {
|
|
const { ref } = useParams()
|
|
const { editorRef, clearHighlights } = useSQLEditorContext()
|
|
const { data: project } = useSelectedProjectQuery()
|
|
const sessionSnap = useSqlEditorSessionSnapshot()
|
|
const databaseSelectorState = useDatabaseSelectorStateSnapshot()
|
|
const getImpersonatedRoleState = useGetImpersonatedRoleState()
|
|
|
|
const { data: databases } = useReadReplicasQuery(
|
|
{ projectRef: ref },
|
|
{ enabled: isValidConnString(project?.connectionString) }
|
|
)
|
|
|
|
const { mutate: executeExplain, isPending: isExplainExecuting } = useExecuteSqlMutation({
|
|
onSuccess(data) {
|
|
if (id) {
|
|
sessionSnap.addExplainResult(id, data.result)
|
|
setActiveUtilityTab('explain')
|
|
}
|
|
},
|
|
onError(error) {
|
|
if (id) {
|
|
sessionSnap.addExplainResultError(id, error)
|
|
setActiveUtilityTab('explain')
|
|
}
|
|
},
|
|
})
|
|
|
|
const executeExplainQuery = useCallback(
|
|
async (sql: SafeSqlFragment) => {
|
|
if (isDiffOpen) return
|
|
|
|
if (editorRef.current !== null && !isExplainExecuting && project !== undefined) {
|
|
// Check for multiple statements - EXPLAIN only works on a single statement
|
|
const statements = splitSqlStatements(sql)
|
|
if (statements.length > 1) {
|
|
sessionSnap.addExplainResultError(id, {
|
|
message:
|
|
'EXPLAIN only works on a single SQL statement. Please select just one query to analyze.',
|
|
})
|
|
setActiveUtilityTab('explain')
|
|
return
|
|
}
|
|
|
|
clearHighlights()
|
|
|
|
const impersonatedRoleState = getImpersonatedRoleState()
|
|
const connectionString = databases?.find(
|
|
(db) => db.identifier === databaseSelectorState.selectedDatabaseId
|
|
)?.connectionString
|
|
if (!isValidConnString(connectionString)) {
|
|
return toast.error('Unable to run query: Connection string is missing')
|
|
}
|
|
|
|
// Wrap in EXPLAIN ANALYZE (unless already an EXPLAIN), apply role
|
|
// impersonation, and wrap in a rollback transaction so EXPLAIN ANALYZE
|
|
// INSERT/UPDATE/DELETE queries don't actually modify data.
|
|
const explainSqlWithTransaction = buildExplainSql(sql, impersonatedRoleState)
|
|
|
|
executeExplain({
|
|
projectRef: project.ref,
|
|
connectionString: connectionString,
|
|
sql: explainSqlWithTransaction,
|
|
isRoleImpersonationEnabled: isRoleImpersonationEnabled(impersonatedRoleState.role),
|
|
handleError: (error) => {
|
|
throw error
|
|
},
|
|
})
|
|
}
|
|
},
|
|
[
|
|
editorRef,
|
|
isDiffOpen,
|
|
id,
|
|
isExplainExecuting,
|
|
project,
|
|
executeExplain,
|
|
getImpersonatedRoleState,
|
|
databaseSelectorState.selectedDatabaseId,
|
|
databases,
|
|
clearHighlights,
|
|
sessionSnap,
|
|
setActiveUtilityTab,
|
|
]
|
|
)
|
|
|
|
return { executeExplainQuery, isExplainExecuting }
|
|
}
|