mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 05:09:12 +03:00
* Fix unnecessary GET content request when writing a new SQL query * Gracefully handle landing on a snippet id that doesnt exist
204 lines
5.9 KiB
TypeScript
204 lines
5.9 KiB
TypeScript
import { removeCommentsFromSql } from 'lib/helpers'
|
|
import type { SnippetWithContent } from 'state/sql-editor-v2'
|
|
import type { SqlSnippets, UserContent } from 'types'
|
|
import {
|
|
NEW_SQL_SNIPPET_SKELETON,
|
|
destructiveSqlRegex,
|
|
sqlAiDisclaimerComment,
|
|
} from './SQLEditor.constants'
|
|
import { ContentDiff, DiffType } from './SQLEditor.types'
|
|
|
|
/**
|
|
* @deprecated
|
|
*/
|
|
export const createSqlSnippetSkeleton = ({
|
|
id,
|
|
name,
|
|
sql,
|
|
owner_id,
|
|
project_id,
|
|
}: {
|
|
id?: string
|
|
name?: string
|
|
sql?: string
|
|
owner_id?: number
|
|
project_id?: number
|
|
} = {}): UserContent<SqlSnippets.Content> => {
|
|
return {
|
|
...NEW_SQL_SNIPPET_SKELETON,
|
|
id,
|
|
...(name && { name }),
|
|
...(owner_id && { owner_id }),
|
|
...(project_id && { project_id }),
|
|
content: {
|
|
...NEW_SQL_SNIPPET_SKELETON.content,
|
|
content_id: id ?? '',
|
|
sql: sql ?? '',
|
|
},
|
|
}
|
|
}
|
|
|
|
export const createSqlSnippetSkeletonV2 = ({
|
|
id,
|
|
name,
|
|
sql,
|
|
owner_id,
|
|
project_id,
|
|
folder_id,
|
|
}: {
|
|
id: string
|
|
name: string
|
|
sql: string
|
|
owner_id: number
|
|
project_id: number
|
|
folder_id?: string
|
|
}): SnippetWithContent => {
|
|
return {
|
|
...NEW_SQL_SNIPPET_SKELETON,
|
|
id,
|
|
owner_id,
|
|
project_id,
|
|
name,
|
|
folder_id,
|
|
favorite: false,
|
|
inserted_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
content: {
|
|
...NEW_SQL_SNIPPET_SKELETON.content,
|
|
content_id: id ?? '',
|
|
sql: sql ?? '',
|
|
} as any,
|
|
isNotSavedInDatabaseYet: true,
|
|
}
|
|
}
|
|
|
|
export function getDiffTypeButtonLabel(diffType: DiffType) {
|
|
switch (diffType) {
|
|
case DiffType.Modification:
|
|
return 'Accept change'
|
|
case DiffType.Addition:
|
|
return 'Accept addition'
|
|
case DiffType.NewSnippet:
|
|
return 'Create new snippet'
|
|
default:
|
|
throw new Error(`Unknown diff type '${diffType}'`)
|
|
}
|
|
}
|
|
|
|
export function getDiffTypeDropdownLabel(diffType: DiffType) {
|
|
switch (diffType) {
|
|
case DiffType.Modification:
|
|
return 'Compare as change'
|
|
case DiffType.Addition:
|
|
return 'Compare as addition'
|
|
case DiffType.NewSnippet:
|
|
return 'Compare as new snippet'
|
|
default:
|
|
throw new Error(`Unknown diff type '${diffType}'`)
|
|
}
|
|
}
|
|
|
|
export function checkDestructiveQuery(sql: string) {
|
|
const cleanedSql = removeCommentsFromSql(sql)
|
|
return destructiveSqlRegex.some((regex) => regex.test(cleanedSql))
|
|
}
|
|
|
|
// Function to check for UPDATE queries without WHERE clause
|
|
export function isUpdateWithoutWhere(sql: string): boolean {
|
|
const updateWithoutWhereRegex =
|
|
/(?:^|;)\s*update\s+(?:"[\w.]+"\."[\w.]+"|[\w.]+)\s+set\s+[\w\W]+?(?!\s*where\s)/is
|
|
const updateStatements = sql
|
|
.split(';')
|
|
.filter((statement) => statement.trim().toLowerCase().startsWith('update'))
|
|
return updateStatements.some(
|
|
(statement) => updateWithoutWhereRegex.test(statement) && !/where\s/i.test(statement)
|
|
)
|
|
}
|
|
|
|
export const generateMigrationCliCommand = (id: string, name: string, isNpx = false) =>
|
|
`
|
|
${isNpx ? 'npx ' : ''}supabase snippets download ${id} |
|
|
${isNpx ? 'npx ' : ''}supabase migration new ${name}
|
|
`.trim()
|
|
|
|
export const generateSeedCliCommand = (id: string, isNpx = false) =>
|
|
`
|
|
${isNpx ? 'npx ' : ''}supabase snippets download ${id} >> \\
|
|
supabase/seed.sql
|
|
`.trim()
|
|
|
|
export const generateFileCliCommand = (id: string, name: string, isNpx = false) =>
|
|
`
|
|
${isNpx ? 'npx ' : ''}supabase snippets download ${id} > \\
|
|
${name}.sql
|
|
`.trim()
|
|
|
|
export const compareAsModification = (sqlDiff: ContentDiff) => {
|
|
const formattedModified = sqlDiff.modified.replace(sqlAiDisclaimerComment, '').trim()
|
|
|
|
return {
|
|
original: sqlDiff.original,
|
|
modified: `${formattedModified}`,
|
|
}
|
|
}
|
|
|
|
export const compareAsAddition = (sqlDiff: ContentDiff) => {
|
|
const formattedOriginal = sqlDiff.original.replace(sqlAiDisclaimerComment, '').trim()
|
|
const formattedModified = sqlDiff.modified.replace(sqlAiDisclaimerComment, '').trim()
|
|
const newModified = (formattedOriginal ? formattedOriginal + '\n\n' : '') + formattedModified
|
|
|
|
return {
|
|
original: sqlDiff.original,
|
|
modified: newModified,
|
|
}
|
|
}
|
|
|
|
export const compareAsNewSnippet = (sqlDiff: ContentDiff) => {
|
|
return {
|
|
original: '',
|
|
modified: sqlDiff.modified,
|
|
}
|
|
}
|
|
|
|
// [Joshen] Just FYI as well the checks here on whether to append limit is quite restricted
|
|
// This is to prevent dashboard from accidentally appending limit to the end of a query
|
|
// thats not supposed to have any, since there's too many cases to cover.
|
|
// We can however look into making this logic better in the future
|
|
// i.e It's harder to append the limit param, than just leaving the query as it is
|
|
// Otherwise we'd need a full on parser to do this properly
|
|
export const checkIfAppendLimitRequired = (sql: string, limit: number = 0) => {
|
|
// Remove lines and whitespaces to use for checking
|
|
const cleanedSql = sql.trim().replaceAll('\n', ' ').replaceAll(/\s+/g, ' ')
|
|
|
|
// Check how many queries
|
|
const regMatch = cleanedSql.matchAll(/[a-zA-Z]*[0-9]*[;]+/g)
|
|
const queries = new Array(...regMatch)
|
|
const indexSemiColon = cleanedSql.lastIndexOf(';')
|
|
const hasComments = cleanedSql.includes('--')
|
|
const hasMultipleQueries =
|
|
queries.length > 1 || (indexSemiColon > 0 && indexSemiColon !== cleanedSql.length - 1)
|
|
|
|
// Check if need to auto limit rows
|
|
const appendAutoLimit =
|
|
limit > 0 &&
|
|
!hasComments &&
|
|
!hasMultipleQueries &&
|
|
cleanedSql.toLowerCase().startsWith('select') &&
|
|
!cleanedSql.toLowerCase().match(/fetch\s+first/i) &&
|
|
!cleanedSql.match(/limit$/i) &&
|
|
!cleanedSql.match(/limit;$/i) &&
|
|
!cleanedSql.match(/limit [0-9]* offset [0-9]*[;]?$/i) &&
|
|
!cleanedSql.match(/limit [0-9]*[;]?$/i)
|
|
return { cleanedSql, appendAutoLimit }
|
|
}
|
|
|
|
export const suffixWithLimit = (sql: string, limit: number = 0) => {
|
|
const { cleanedSql, appendAutoLimit } = checkIfAppendLimitRequired(sql, limit)
|
|
const formattedSql = appendAutoLimit
|
|
? cleanedSql.endsWith(';')
|
|
? sql.replace(/[;]+$/, ` limit ${limit};`)
|
|
: `${sql} limit ${limit};`
|
|
: sql
|
|
return formattedSql
|
|
}
|