mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 09:59:03 +08:00
## What - add a second notebook-level confirmation for destructive SQL before forced batch execution - reuse the shared SQL safety detector for stored and live cell SQL - cover destructive confirmation, cancellation, non-destructive mutations, and live SQL ## Testing - pnpm --filter studio exec vitest run components/interfaces/Explorer/ExplorerNotebookTab.utils.test.ts components/interfaces/Explorer/__tests__/ExplorerNotebookTab.test.tsx --coverage.enabled=false - pnpm --filter studio exec eslint components/interfaces/Explorer/ExplorerNotebookTab.tsx components/interfaces/Explorer/ExplorerNotebookTab.utils.ts apps/studio/components/interfaces/Explorer/ExplorerNotebookTab.utils.test.ts apps/studio/components/interfaces/Explorer/__tests__/ExplorerNotebookTab.test.tsx Closes FE-4284 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Improvements** * Simplified notebook execution into a single confirmation step for mutating queries. * Destructive queries, including operations such as `DROP` or `TRUNCATE`, are clearly marked with a **Destructive** badge. * The confirmation dialog lists affected queries and lets you proceed or cancel. * Detection uses the latest SQL from the editor and ignores destructive keywords in comments. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
63 lines
2.0 KiB
TypeScript
63 lines
2.0 KiB
TypeScript
import { type Snapshot } from 'valtio'
|
|
|
|
import { type Cell } from '@/data/content/notebooks/notebook-schema'
|
|
import { removeCommentsFromSql } from '@/lib/helpers'
|
|
|
|
const MUTATING_STATEMENT_REGEX =
|
|
/^\s*(insert|update|delete|create|alter|drop|truncate|grant|revoke|merge)\b/i
|
|
|
|
export type QueryCellSummary = { id: string; title: string }
|
|
|
|
type FindQueryCellsArgs = {
|
|
cells: readonly Snapshot<Cell>[]
|
|
/**
|
|
* Lets a caller also check each cell's live editor buffer as well. The store
|
|
* only updates on a Monaco blur commit, which fires asynchronously, so a scan
|
|
* against the store alone can miss SQL typed just before the run click.
|
|
*/
|
|
getLiveSql?: (cellId: string) => string | undefined
|
|
}
|
|
|
|
type SqlMatchers = Record<string, (sql: string) => boolean>
|
|
|
|
/**
|
|
* Finds database cells that match one or more SQL predicates in a single pass.
|
|
*/
|
|
export function findQueryCellsMatchingSql<T extends SqlMatchers>({
|
|
cells,
|
|
getLiveSql,
|
|
matchers,
|
|
}: FindQueryCellsArgs & {
|
|
matchers: T
|
|
}): Record<keyof T, QueryCellSummary[]> {
|
|
const matchingCells = {} as Record<keyof T, QueryCellSummary[]>
|
|
for (const name in matchers) {
|
|
matchingCells[name] = []
|
|
}
|
|
|
|
cells.forEach((cell) => {
|
|
if (cell._tag !== 'database_cell') return
|
|
|
|
const sql = [cell.unchecked_sql, getLiveSql?.(cell._id)].filter(
|
|
(value): value is string => value !== undefined
|
|
)
|
|
const summary = { id: cell._id, title: cell.title ?? 'Untitled query' }
|
|
|
|
for (const name in matchers) {
|
|
if (sql.some(matchers[name])) matchingCells[name].push(summary)
|
|
}
|
|
})
|
|
|
|
return matchingCells
|
|
}
|
|
|
|
/**
|
|
* Whether `sql` contains any statement that writes to data or schema, as opposed to a
|
|
* read-only query. Checked per `;`-delimited statement so a mutating statement anywhere
|
|
* in a multi-statement cell is caught, not just when it leads.
|
|
*/
|
|
export function isMutatingSql(sql: string): boolean {
|
|
const cleanedSql = removeCommentsFromSql(sql)
|
|
return cleanedSql.split(';').some((statement) => MUTATING_STATEMENT_REGEX.test(statement))
|
|
}
|