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 => { 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 }