From 116faefcdad1b8de611a5ce90ce1dcb6d9abcbea Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Wed, 6 May 2026 12:21:48 -0400 Subject: [PATCH] studio: convert more executeSql callers to SafeSqlFragment (#45645) ## Summary - Converts ~27 `executeSql` call sites in `apps/studio/data/**` to build SQL through `safeSql` / `ident` / `literal` / `keyword` / `joinSqlFragments` instead of raw template-string interpolation. - Tightens the `useDatabaseCronJobCreateMutation` and `useDatabaseEventTriggerCreateMutation` `sql`/`query` parameter types from `string` to `SafeSqlFragment` (callers already produce one). - Updates `getDeleteEnumeratedTypeSQL` in `packages/pg-meta` to return `SafeSqlFragment`. - Fixes a bug noticed while testing where Queues integration does not correctly handle queues with uppercase names. ## Pages to manually test - Integrations > Cron Jobs - Integrations > Queues - Database > Triggers > Event Triggers - Database > Indexes - Reports > Query Performance - Storage ## Summary by CodeRabbit ## Release Notes * **Bug Fixes** * Queue lookups now correctly handle case-insensitive queue names. * Queue table references are now properly managed and consistently applied throughout the queue management interface. * Improved queue name display normalization in the user interface. * **Chores** * Enhanced SQL query safety across the database layer through parameterized query construction and safer templating approaches. --- .../CreateQueueSheet.schema.ts | 4 +- .../Integrations/Queues/QueueCells.tsx | 3 +- .../Integrations/Queues/QueuePage.tsx | 5 +- .../Integrations/Queues/QueueTab.tsx | 6 ++- .../Integrations/Queues/Queues.utils.tsx | 18 +++++-- .../Integrations/Queues/QueuesRows.tsx | 3 +- .../Integrations/Queues/QueuesSettings.tsx | 21 ++++++-- .../Queues/SingleQueue/QueueSettings.tsx | 9 ++-- .../data/config/disk-breakdown-query.ts | 3 +- .../database-cron-job-query.ts | 5 +- .../database-cron-job-run-mutation.ts | 9 ++-- .../database-cron-jobs-count-query.ts | 3 +- .../database-cron-jobs-create-mutation.ts | 3 +- .../database-cron-jobs-delete-mutation.ts | 3 +- .../database-cron-jobs-runs-infinite-query.ts | 11 ++-- .../database-cron-jobs-toggle-mutation.ts | 3 +- .../database-cron-timezone-query.ts | 3 +- .../database-event-trigger-create-mutation.ts | 3 +- .../database-event-triggers-query.ts | 3 +- .../database-indexes/index-create-mutation.ts | 12 ++--- .../database-indexes/index-delete-mutation.ts | 4 +- .../stripe/sync-state-query.ts | 3 +- ...atabase-queue-messages-archive-mutation.ts | 4 +- ...database-queue-messages-delete-mutation.ts | 4 +- .../database-queue-messages-infinite-query.ts | 50 ++++++++++++------- .../database-queue-messages-read-mutation.ts | 4 +- .../database-queue-messages-send-mutation.ts | 4 +- .../database-queues-create-mutation.ts | 21 +++++--- .../database-queues-delete-mutation.ts | 4 +- .../database-queues-metrics-query.ts | 21 ++++---- .../database-queues-purge-mutation.ts | 4 +- .../retrieve-index-from-select-query.ts | 7 +-- .../database/supamonitor-enabled-query.ts | 3 +- ...blic-buckets-with-select-policies-query.ts | 4 +- apps/studio/data/tables/table-names-query.ts | 5 +- .../pg-meta/src/sql/studio/database/types.ts | 2 +- 36 files changed, 173 insertions(+), 101 deletions(-) diff --git a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/CreateQueueSheet.schema.ts b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/CreateQueueSheet.schema.ts index e4ff0c93fd0..1499776e72d 100644 --- a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/CreateQueueSheet.schema.ts +++ b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/CreateQueueSheet.schema.ts @@ -1,6 +1,6 @@ import z from 'zod' -import { QueryNameSchema } from '../Queues.utils' +import { QueueNameSchema } from '../Queues.utils' const normalQueueSchema = z.object({ type: z.literal('basic'), @@ -17,7 +17,7 @@ const unloggedQueueSchema = z.object({ }) export const FormSchema = z.object({ - name: QueryNameSchema, + name: QueueNameSchema, enableRls: z.boolean(), values: z.discriminatedUnion('type', [ normalQueueSchema, diff --git a/apps/studio/components/interfaces/Integrations/Queues/QueueCells.tsx b/apps/studio/components/interfaces/Integrations/Queues/QueueCells.tsx index c72ea63e238..7deaf9eb4a3 100644 --- a/apps/studio/components/interfaces/Integrations/Queues/QueueCells.tsx +++ b/apps/studio/components/interfaces/Integrations/Queues/QueueCells.tsx @@ -1,6 +1,7 @@ import dayjs from 'dayjs' import { Check, Loader2, X } from 'lucide-react' +import { pgmqQueueTable } from './Queues.utils' import { useQueuesMetricsQuery } from '@/data/database-queues/database-queues-metrics-query' import { PostgresQueue } from '@/data/database-queues/database-queues-query' import { useTablesQuery } from '@/data/tables/tables-query' @@ -43,7 +44,7 @@ export const QueueRLSCell = ({ queue }: QueueCellProps) => { schema: 'pgmq', }) - const queueTable = queueTables?.find((x) => x.name === `q_${queue.queue_name}`) + const queueTable = queueTables?.find((x) => x.name === pgmqQueueTable(queue.queue_name)) const isRlsEnabled = !!queueTable?.rls_enabled return ( diff --git a/apps/studio/components/interfaces/Integrations/Queues/QueuePage.tsx b/apps/studio/components/interfaces/Integrations/Queues/QueuePage.tsx index a6b4c122472..aebef928b7b 100644 --- a/apps/studio/components/interfaces/Integrations/Queues/QueuePage.tsx +++ b/apps/studio/components/interfaces/Integrations/Queues/QueuePage.tsx @@ -34,7 +34,10 @@ export const QueuePage = () => { connectionString: project?.connectionString, }) - const currentQueue = queues?.find((queue) => queue.queue_name === childId) + // pgmq is case-insensitive when storing queue names — compare lowercased to be safe + const currentQueue = queues?.find( + (queue) => queue.queue_name.toLowerCase() === childId?.toLowerCase() + ) const pageTitle = childLabel || childId || 'Queue' diff --git a/apps/studio/components/interfaces/Integrations/Queues/QueueTab.tsx b/apps/studio/components/interfaces/Integrations/Queues/QueueTab.tsx index fd8178dd12b..37c29c8d191 100644 --- a/apps/studio/components/interfaces/Integrations/Queues/QueueTab.tsx +++ b/apps/studio/components/interfaces/Integrations/Queues/QueueTab.tsx @@ -16,6 +16,7 @@ import { import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' +import { pgmqQueueTable } from './Queues.utils' import { DeleteQueue } from '@/components/interfaces/Integrations/Queues/SingleQueue/DeleteQueue' import { PurgeQueue } from '@/components/interfaces/Integrations/Queues/SingleQueue/PurgeQueue' import { QUEUE_MESSAGE_TYPE } from '@/components/interfaces/Integrations/Queues/SingleQueue/Queue.utils' @@ -51,7 +52,8 @@ export const QueueTab = () => { connectionString: project?.connectionString, schema: 'pgmq', }) - const queueTable = tables?.find((x) => x.name === `q_${queueName}`) + const queueRelname = queueName ? pgmqQueueTable(queueName) : undefined + const queueTable = tables?.find((x) => x.name === queueRelname) const isRlsEnabled = queueTable?.rls_enabled ?? false const { data: policies } = useDatabasePoliciesQuery({ @@ -59,7 +61,7 @@ export const QueueTab = () => { connectionString: project?.connectionString, schema: 'pgmq', }) - const queuePolicies = (policies ?? []).filter((policy) => policy.table === `q_${queueName}`) + const queuePolicies = (policies ?? []).filter((policy) => policy.table === queueRelname) const { data: isExposed } = useQueuesExposePostgrestStatusQuery({ projectRef: project?.ref, diff --git a/apps/studio/components/interfaces/Integrations/Queues/Queues.utils.tsx b/apps/studio/components/interfaces/Integrations/Queues/Queues.utils.tsx index a3fbea3a28a..8540a7398d2 100644 --- a/apps/studio/components/interfaces/Integrations/Queues/Queues.utils.tsx +++ b/apps/studio/components/interfaces/Integrations/Queues/Queues.utils.tsx @@ -109,7 +109,11 @@ export const prepareQueuesForDataGrid = (queues: PostgresQueue[]): QueueWithMetr })) } -export const QueryNameSchema = z +// pgmq stores queue names as-provided in pgmq.meta and lowercases them only when +// building the underlying q_/a_ relations, so uppercase queue names are fine — +// the user-facing name is preserved and table-name lookups go through +// pgmqQueueTable / pgmqArchiveTable which lowercase to match pgmq's behavior. +export const QueueNameSchema = z .string() .trim() .min(1, 'Please provide a name for your queue') @@ -119,9 +123,13 @@ export const QueryNameSchema = z 'Name must contain only alphanumeric characters, underscores, and hyphens' ) -/** - * Checks if the queue name is valid. Returns a boolean. - */ export const isQueueNameValid = (queueName: string) => { - return QueryNameSchema.safeParse(queueName).success + return QueueNameSchema.safeParse(queueName).success } + +// pgmq.format_table_name() lowercases its input, so the actual relations in the +// pgmq schema are always q_/a_ followed by the lowercased queue name — even when +// pgmq.meta stores the original casing. Use these helpers anywhere a queue name +// is mapped to a relname. +export const pgmqQueueTable = (queueName: string) => `q_${queueName.toLowerCase()}` +export const pgmqArchiveTable = (queueName: string) => `a_${queueName.toLowerCase()}` diff --git a/apps/studio/components/interfaces/Integrations/Queues/QueuesRows.tsx b/apps/studio/components/interfaces/Integrations/Queues/QueuesRows.tsx index edc6916bf09..b5aa5e781e4 100644 --- a/apps/studio/components/interfaces/Integrations/Queues/QueuesRows.tsx +++ b/apps/studio/components/interfaces/Integrations/Queues/QueuesRows.tsx @@ -3,6 +3,7 @@ import { includes, sortBy } from 'lodash' import { Check, ChevronRight, Loader2, X } from 'lucide-react' import { useRouter } from 'next/router' +import { pgmqQueueTable } from './Queues.utils' import Table from '@/components/to-be-cleaned/Table' import { useQueuesMetricsQuery } from '@/data/database-queues/database-queues-metrics-query' import { PostgresQueue } from '@/data/database-queues/database-queues-query' @@ -24,7 +25,7 @@ const QueueRow = ({ queue }: { queue: PostgresQueue }) => { connectionString: selectedProject?.connectionString, schema: 'pgmq', }) - const queueTable = queueTables?.find((x) => x.name === `q_${queue.queue_name}`) + const queueTable = queueTables?.find((x) => x.name === pgmqQueueTable(queue.queue_name)) const isRlsEnabled = !!queueTable?.rls_enabled const { data: metrics, isPending: isLoading } = useQueuesMetricsQuery( diff --git a/apps/studio/components/interfaces/Integrations/Queues/QueuesSettings.tsx b/apps/studio/components/interfaces/Integrations/Queues/QueuesSettings.tsx index c226d6a2ead..ef25daa356d 100644 --- a/apps/studio/components/interfaces/Integrations/Queues/QueuesSettings.tsx +++ b/apps/studio/components/interfaces/Integrations/Queues/QueuesSettings.tsx @@ -21,6 +21,7 @@ import { InlineLink } from '@/components/ui/InlineLink' import { useProjectPostgrestConfigQuery } from '@/data/config/project-postgrest-config-query' import { useProjectPostgrestConfigUpdateMutation } from '@/data/config/project-postgrest-config-update-mutation' import { useQueuesExposePostgrestStatusQuery } from '@/data/database-queues/database-queues-expose-postgrest-status-query' +import { useQueuesQuery } from '@/data/database-queues/database-queues-query' import { useDatabaseQueueToggleExposeMutation } from '@/data/database-queues/database-queues-toggle-postgrest-mutation' import { useDatabaseQueuesVersionQuery } from '@/data/database-queues/database-queues-version-query' import { useTableUpdateMutation } from '@/data/tables/table-update-mutation' @@ -56,6 +57,18 @@ export const QueuesSettings = () => { const tablesWithoutRLS = queueTables?.filter((x) => x.name.startsWith('q_') && !x.rls_enabled) ?? [] + // pgmq lowercases queue names when building q_/a_ relations, but pgmq.meta keeps + // the original casing. Look up each relname in list_queues() so we render the + // user-provided name rather than the lowercased relname slice. + const { data: queues } = useQueuesQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + const queueDisplayName = (relname: string) => { + const stripped = relname.slice(2) + return queues?.find((q) => q.queue_name.toLowerCase() === stripped)?.queue_name ?? stripped + } + const { data: config, error: configError } = useProjectPostgrestConfigQuery({ projectRef: project?.ref, }) @@ -259,7 +272,9 @@ export const QueuesSettings = () => { {tablesWithoutRLS.map((x) => { return (
  • - {x.name.slice(2)} + + {queueDisplayName(x.name)} +
  • ) })} @@ -272,7 +287,7 @@ export const QueuesSettings = () => { > Enable RLS on{' '} {tablesWithoutRLS.length === 1 - ? tablesWithoutRLS[0].name.slice(2) + ? queueDisplayName(tablesWithoutRLS[0].name) : `${tablesWithoutRLS.length} queues`} @@ -354,7 +369,7 @@ export const QueuesSettings = () => { {tablesWithoutRLS.map((x) => { return (
  • - {x.name.slice(2)} + {queueDisplayName(x.name)}
  • ) })} diff --git a/apps/studio/components/interfaces/Integrations/Queues/SingleQueue/QueueSettings.tsx b/apps/studio/components/interfaces/Integrations/Queues/SingleQueue/QueueSettings.tsx index af634743e83..451ec22cd4b 100644 --- a/apps/studio/components/interfaces/Integrations/Queues/SingleQueue/QueueSettings.tsx +++ b/apps/studio/components/interfaces/Integrations/Queues/SingleQueue/QueueSettings.tsx @@ -28,6 +28,7 @@ import { import { Admonition } from 'ui-patterns/admonition' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' +import { pgmqArchiveTable, pgmqQueueTable } from '../Queues.utils' import { getQueueFunctionsMapping } from './Queue.utils' import AlertError from '@/components/ui/AlertError' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' @@ -83,15 +84,17 @@ export const QueueSettings = ({}: QueueSettingsProps) => { connectionString: project?.connectionString, schema: 'pgmq', }) - const queueTable = queueTables?.find((x) => x.name === `q_${name}`) - const archiveTable = queueTables?.find((x) => x.name === `a_${name}`) + const queueRelname = name ? pgmqQueueTable(name) : undefined + const archiveRelname = name ? pgmqArchiveTable(name) : undefined + const queueTable = queueTables?.find((x) => x.name === queueRelname) + const archiveTable = queueTables?.find((x) => x.name === archiveRelname) const { data: allTablePrivileges, isSuccess: isSuccessPrivileges } = useTablePrivilegesQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const queuePrivileges = allTablePrivileges?.find( - (x) => x.schema === 'pgmq' && x.name === `q_${name}` + (x) => x.schema === 'pgmq' && x.name === queueRelname ) const { mutateAsync: grantPrivilege } = useTablePrivilegesGrantMutation() diff --git a/apps/studio/data/config/disk-breakdown-query.ts b/apps/studio/data/config/disk-breakdown-query.ts index ebfbc28521f..df51ee0f0b0 100644 --- a/apps/studio/data/config/disk-breakdown-query.ts +++ b/apps/studio/data/config/disk-breakdown-query.ts @@ -1,3 +1,4 @@ +import { safeSql } from '@supabase/pg-meta/src/pg-format' import { useQuery } from '@tanstack/react-query' import { configKeys } from './keys' @@ -25,7 +26,7 @@ export async function getDiskBreakdown( { projectRef, connectionString, - sql: ` + sql: safeSql` SELECT ( SELECT diff --git a/apps/studio/data/database-cron-jobs/database-cron-job-query.ts b/apps/studio/data/database-cron-jobs/database-cron-job-query.ts index e83bdf00109..368ec52407e 100644 --- a/apps/studio/data/database-cron-jobs/database-cron-job-query.ts +++ b/apps/studio/data/database-cron-jobs/database-cron-job-query.ts @@ -1,3 +1,4 @@ +import { literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useQuery } from '@tanstack/react-query' import { CronJob } from './database-cron-jobs-infinite-query' @@ -24,8 +25,8 @@ export async function getDatabaseCronJob({ projectRef, connectionString, sql: !!id - ? `SELECT * FROM cron.job where jobid = ${id};` - : `SELECT * FROM cron.job where jobname = '${name}';`, + ? safeSql`SELECT * FROM cron.job where jobid = ${literal(id)};` + : safeSql`SELECT * FROM cron.job where jobname = ${literal(name)};`, queryKey: ['cron-job', id], }) diff --git a/apps/studio/data/database-cron-jobs/database-cron-job-run-mutation.ts b/apps/studio/data/database-cron-jobs/database-cron-job-run-mutation.ts index 3846630be03..a9200798503 100644 --- a/apps/studio/data/database-cron-jobs/database-cron-job-run-mutation.ts +++ b/apps/studio/data/database-cron-jobs/database-cron-job-run-mutation.ts @@ -1,3 +1,4 @@ +import { literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useMutation } from '@tanstack/react-query' import { toast } from 'sonner' @@ -22,15 +23,13 @@ export async function runDatabaseCronJobCommand({ const { result } = await executeSql({ projectRef, connectionString, - sql: ` -DO $$ + sql: safeSql`DO $$ DECLARE job_command text; BEGIN - select command into job_command from cron.job where jobid = ${jobId}; + select command into job_command from cron.job where jobid = ${literal(jobId)}; EXECUTE job_command; -END $$; -`.trim(), +END $$;`, queryKey: databaseCronJobsKeys.create(), }) diff --git a/apps/studio/data/database-cron-jobs/database-cron-jobs-count-query.ts b/apps/studio/data/database-cron-jobs/database-cron-jobs-count-query.ts index 49270d0cc4c..9fcb6d0bdca 100644 --- a/apps/studio/data/database-cron-jobs/database-cron-jobs-count-query.ts +++ b/apps/studio/data/database-cron-jobs/database-cron-jobs-count-query.ts @@ -1,3 +1,4 @@ +import { safeSql } from '@supabase/pg-meta/src/pg-format' import { useQuery } from '@tanstack/react-query' import { databaseCronJobsKeys } from './keys' @@ -9,7 +10,7 @@ type DatabaseCronJobsCountVariables = { connectionString?: string | null } -const cronJobCountSql = `select count(jobid) from cron.job;`.trim() +const cronJobCountSql = safeSql`select count(jobid) from cron.job;` export async function getDatabaseCronJobsCount({ projectRef, diff --git a/apps/studio/data/database-cron-jobs/database-cron-jobs-create-mutation.ts b/apps/studio/data/database-cron-jobs/database-cron-jobs-create-mutation.ts index 22a5b49989c..941d4df8181 100644 --- a/apps/studio/data/database-cron-jobs/database-cron-jobs-create-mutation.ts +++ b/apps/studio/data/database-cron-jobs/database-cron-jobs-create-mutation.ts @@ -1,3 +1,4 @@ +import type { SafeSqlFragment } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' @@ -8,7 +9,7 @@ import type { ResponseError, UseCustomMutationOptions } from '@/types' export type DatabaseCronJobCreateVariables = { projectRef: string connectionString?: string | null - query: string + query: SafeSqlFragment searchTerm?: string identifier?: string | number } diff --git a/apps/studio/data/database-cron-jobs/database-cron-jobs-delete-mutation.ts b/apps/studio/data/database-cron-jobs/database-cron-jobs-delete-mutation.ts index 986c84e2187..eda2c0674a6 100644 --- a/apps/studio/data/database-cron-jobs/database-cron-jobs-delete-mutation.ts +++ b/apps/studio/data/database-cron-jobs/database-cron-jobs-delete-mutation.ts @@ -1,3 +1,4 @@ +import { literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' @@ -20,7 +21,7 @@ export async function deleteDatabaseCronJob({ const { result } = await executeSql({ projectRef, connectionString, - sql: `SELECT cron.unschedule(${jobId});`, + sql: safeSql`SELECT cron.unschedule(${literal(jobId)});`, queryKey: databaseCronJobsKeys.delete(), }) diff --git a/apps/studio/data/database-cron-jobs/database-cron-jobs-runs-infinite-query.ts b/apps/studio/data/database-cron-jobs/database-cron-jobs-runs-infinite-query.ts index 82719814776..02338e053a4 100644 --- a/apps/studio/data/database-cron-jobs/database-cron-jobs-runs-infinite-query.ts +++ b/apps/studio/data/database-cron-jobs/database-cron-jobs-runs-infinite-query.ts @@ -1,3 +1,4 @@ +import { literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query' import { last } from 'lodash' @@ -37,13 +38,15 @@ export async function getDatabaseCronJobRuns({ // Use runid for ordering and pagination since it's the primary key (indexed) // and preserves chronological order (auto-incrementing) - let query = ` + const afterRunIdClause = + typeof afterRunId === 'number' ? safeSql`AND runid < ${literal(afterRunId)}` : safeSql`` + const query = safeSql` SELECT * FROM cron.job_run_details WHERE - jobid = '${jobId}' - ${typeof afterRunId === 'number' ? `AND runid < '${afterRunId}'` : ''} + jobid = ${literal(jobId)} + ${afterRunIdClause} ORDER BY runid DESC - LIMIT ${CRON_JOB_RUNS_PAGE_SIZE}` + LIMIT ${literal(CRON_JOB_RUNS_PAGE_SIZE)}` const { result } = await executeSql({ projectRef, diff --git a/apps/studio/data/database-cron-jobs/database-cron-jobs-toggle-mutation.ts b/apps/studio/data/database-cron-jobs/database-cron-jobs-toggle-mutation.ts index b091d159181..3e5bc7751b0 100644 --- a/apps/studio/data/database-cron-jobs/database-cron-jobs-toggle-mutation.ts +++ b/apps/studio/data/database-cron-jobs/database-cron-jobs-toggle-mutation.ts @@ -1,3 +1,4 @@ +import { literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' @@ -22,7 +23,7 @@ export async function toggleDatabaseCronJob({ const { result } = await executeSql({ projectRef, connectionString, - sql: `select cron.alter_job(job_id := ${jobId}, active := ${active});`, + sql: safeSql`select cron.alter_job(job_id := ${literal(jobId)}, active := ${literal(active)});`, queryKey: databaseCronJobsKeys.alter(), }) diff --git a/apps/studio/data/database-cron-jobs/database-cron-timezone-query.ts b/apps/studio/data/database-cron-jobs/database-cron-timezone-query.ts index a85a0e5521b..2d0f518780d 100644 --- a/apps/studio/data/database-cron-jobs/database-cron-timezone-query.ts +++ b/apps/studio/data/database-cron-jobs/database-cron-timezone-query.ts @@ -1,3 +1,4 @@ +import { safeSql } from '@supabase/pg-meta/src/pg-format' import { useQuery } from '@tanstack/react-query' import { databaseCronJobsKeys } from './keys' @@ -18,7 +19,7 @@ export async function getDatabaseCronTimezone({ const { result } = await executeSql({ projectRef, connectionString, - sql: `select setting from pg_settings where name = 'cron.timezone';`, + sql: safeSql`select setting from pg_settings where name = 'cron.timezone';`, }) return result[0].setting } diff --git a/apps/studio/data/database-event-triggers/database-event-trigger-create-mutation.ts b/apps/studio/data/database-event-triggers/database-event-trigger-create-mutation.ts index b193ea1a593..2740821cb02 100644 --- a/apps/studio/data/database-event-triggers/database-event-trigger-create-mutation.ts +++ b/apps/studio/data/database-event-triggers/database-event-trigger-create-mutation.ts @@ -1,3 +1,4 @@ +import type { SafeSqlFragment } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' @@ -8,7 +9,7 @@ import type { ResponseError, UseCustomMutationOptions } from '@/types' export type DatabaseEventTriggerCreateVariables = { projectRef: string connectionString?: string | null - sql: string + sql: SafeSqlFragment } export async function createDatabaseEventTrigger({ diff --git a/apps/studio/data/database-event-triggers/database-event-triggers-query.ts b/apps/studio/data/database-event-triggers/database-event-triggers-query.ts index 9f34a1873ab..2660952375f 100644 --- a/apps/studio/data/database-event-triggers/database-event-triggers-query.ts +++ b/apps/studio/data/database-event-triggers/database-event-triggers-query.ts @@ -1,3 +1,4 @@ +import { safeSql } from '@supabase/pg-meta/src/pg-format' import { useQuery } from '@tanstack/react-query' import { databaseEventTriggerKeys } from './keys' @@ -22,7 +23,7 @@ export type DatabaseEventTrigger = { function_definition: string | null } -const EVENT_TRIGGERS_SQL = ` +const EVENT_TRIGGERS_SQL = safeSql` select evt.oid, evt.evtname as name, diff --git a/apps/studio/data/database-indexes/index-create-mutation.ts b/apps/studio/data/database-indexes/index-create-mutation.ts index 199749b9d78..08888d88298 100644 --- a/apps/studio/data/database-indexes/index-create-mutation.ts +++ b/apps/studio/data/database-indexes/index-create-mutation.ts @@ -1,4 +1,4 @@ -import { ident } from '@supabase/pg-meta/src/pg-format' +import { ident, joinSqlFragments, keyword, safeSql } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' @@ -24,11 +24,11 @@ export async function createDatabaseIndex({ }: DatabaseIndexCreateVariables) { const { schema, entity, type, columns } = payload - const sql = ` - CREATE INDEX ON ${ident(schema)}.${ident(entity)} USING ${type} (${columns - .map((column) => ident(column)) - .join(', ')}); - `.trim() + const columnList = joinSqlFragments( + columns.map((column) => ident(column)), + ', ' + ) + const sql = safeSql`CREATE INDEX ON ${ident(schema)}.${ident(entity)} USING ${keyword(type)} (${columnList});` const { result } = await executeSql({ projectRef, diff --git a/apps/studio/data/database-indexes/index-delete-mutation.ts b/apps/studio/data/database-indexes/index-delete-mutation.ts index 4f2ff441b4b..5cb809613ae 100644 --- a/apps/studio/data/database-indexes/index-delete-mutation.ts +++ b/apps/studio/data/database-indexes/index-delete-mutation.ts @@ -1,4 +1,4 @@ -import { ident } from '@supabase/pg-meta/src/pg-format' +import { ident, safeSql } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' @@ -19,7 +19,7 @@ export async function deleteDatabaseIndex({ name, schema, }: DatabaseIndexDeleteVariables) { - const sql = `drop index if exists ${ident(schema)}.${ident(name)}` + const sql = safeSql`drop index if exists ${ident(schema)}.${ident(name)}` const { result } = await executeSql({ projectRef, diff --git a/apps/studio/data/database-integrations/stripe/sync-state-query.ts b/apps/studio/data/database-integrations/stripe/sync-state-query.ts index 226e304d1f2..9dcd5e5325f 100644 --- a/apps/studio/data/database-integrations/stripe/sync-state-query.ts +++ b/apps/studio/data/database-integrations/stripe/sync-state-query.ts @@ -1,3 +1,4 @@ +import { safeSql } from '@supabase/pg-meta/src/pg-format' import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query' import { z } from 'zod' @@ -30,7 +31,7 @@ export async function getStripeSyncState( { projectRef, connectionString, - sql: ` + sql: safeSql` SELECT started_at, closed_at, status FROM stripe.sync_runs WHERE status != 'pending' ORDER BY started_at DESC LIMIT 1; `, queryKey: stripeSyncKeys.syncState(projectRef), diff --git a/apps/studio/data/database-queues/database-queue-messages-archive-mutation.ts b/apps/studio/data/database-queues/database-queue-messages-archive-mutation.ts index e9cee99445c..590ea9e10f5 100644 --- a/apps/studio/data/database-queues/database-queue-messages-archive-mutation.ts +++ b/apps/studio/data/database-queues/database-queue-messages-archive-mutation.ts @@ -1,4 +1,4 @@ -import { literal } from '@supabase/pg-meta/src/pg-format' +import { literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' @@ -28,7 +28,7 @@ export async function archiveDatabaseQueueMessage({ const { result } = await executeSql({ projectRef, connectionString, - sql: `SELECT * FROM pgmq.archive(${literal(queueName)}, ${literal(messageId)})`, + sql: safeSql`SELECT * FROM pgmq.archive(${literal(queueName)}, ${literal(messageId)})`, queryKey: databaseQueuesKeys.create(), }) diff --git a/apps/studio/data/database-queues/database-queue-messages-delete-mutation.ts b/apps/studio/data/database-queues/database-queue-messages-delete-mutation.ts index 31efddebde8..fcf4b9c0d91 100644 --- a/apps/studio/data/database-queues/database-queue-messages-delete-mutation.ts +++ b/apps/studio/data/database-queues/database-queue-messages-delete-mutation.ts @@ -1,4 +1,4 @@ -import { literal } from '@supabase/pg-meta/src/pg-format' +import { literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' @@ -29,7 +29,7 @@ export async function deleteDatabaseQueueMessage({ const { result } = await executeSql({ projectRef, connectionString, - sql: `SELECT * FROM pgmq.delete(${literal(queueName)}, ${literal(messageId)})`, + sql: safeSql`SELECT * FROM pgmq.delete(${literal(queueName)}, ${literal(messageId)})`, queryKey: databaseQueuesKeys.create(), }) diff --git a/apps/studio/data/database-queues/database-queue-messages-infinite-query.ts b/apps/studio/data/database-queues/database-queue-messages-infinite-query.ts index f96e55089e9..f49e1ed338e 100644 --- a/apps/studio/data/database-queues/database-queue-messages-infinite-query.ts +++ b/apps/studio/data/database-queues/database-queue-messages-infinite-query.ts @@ -1,10 +1,14 @@ -import { literal } from '@supabase/pg-meta/src/pg-format' +import { ident, literal, safeSql, type SafeSqlFragment } from '@supabase/pg-meta/src/pg-format' import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query' import dayjs from 'dayjs' import { last } from 'lodash' import { databaseQueuesKeys } from './keys' -import { isQueueNameValid } from '@/components/interfaces/Integrations/Queues/Queues.utils' +import { + isQueueNameValid, + pgmqArchiveTable, + pgmqQueueTable, +} from '@/components/interfaces/Integrations/Queues/Queues.utils' import { QUEUE_MESSAGE_TYPE } from '@/components/interfaces/Integrations/Queues/SingleQueue/Queue.utils' import { executeSql } from '@/data/sql/execute-sql-query' import { DATE_FORMAT } from '@/lib/constants' @@ -47,34 +51,46 @@ export async function getDatabaseQueue({ } // handles when scheduled and available are deselected - let queueQuery = `` + const queueTable = safeSql`${ident('pgmq')}.${ident(pgmqQueueTable(queueName))}` + const archivedTable = safeSql`${ident('pgmq')}.${ident(pgmqArchiveTable(queueName))}` + const nowLiteral = literal(dayjs(new Date()).format(DATE_FORMAT)) + + let queueQuery: SafeSqlFragment | null = null if (status.includes('available') && status.includes('scheduled')) { - queueQuery = `SELECT msg_id, enqueued_at, read_ct, vt, message, NULL as archived_at FROM "pgmq"."q_${queueName}"` + queueQuery = safeSql`SELECT msg_id, enqueued_at, read_ct, vt, message, NULL as archived_at FROM ${queueTable}` } else if (status.includes('available') && !status.includes('scheduled')) { - queueQuery = `SELECT msg_id, enqueued_at, read_ct, vt, message, NULL as archived_at FROM "pgmq"."q_${queueName}" WHERE vt < '${dayjs(new Date()).format(DATE_FORMAT)}'` + queueQuery = safeSql`SELECT msg_id, enqueued_at, read_ct, vt, message, NULL as archived_at FROM ${queueTable} WHERE vt < ${nowLiteral}` } else if (!status.includes('available') && status.includes('scheduled')) { - queueQuery = `SELECT msg_id, enqueued_at, read_ct, vt, message, NULL as archived_at FROM "pgmq"."q_${queueName}" WHERE vt > '${dayjs(new Date()).format(DATE_FORMAT)}'` + queueQuery = safeSql`SELECT msg_id, enqueued_at, read_ct, vt, message, NULL as archived_at FROM ${queueTable} WHERE vt > ${nowLiteral}` } - let archivedQuery = `` - if (status.includes('archived')) { - archivedQuery = `SELECT msg_id, enqueued_at, read_ct, vt, message, archived_at FROM "pgmq"."a_${queueName}"` - } + const archivedQuery = status.includes('archived') + ? safeSql`SELECT msg_id, enqueued_at, read_ct, vt, message, archived_at FROM ${archivedTable}` + : null - let query = `SELECT + const unionParts = [queueQuery, archivedQuery].filter( + (part): part is SafeSqlFragment => part !== null + ) + const unionFragment = unionParts.reduce( + (acc, part, index) => (index === 0 ? part : safeSql`${acc} UNION ALL ${part}`), + safeSql`` + ) + + const whereClause = afterTimestamp + ? safeSql` WHERE enqueued_at > ${literal(afterTimestamp)}` + : safeSql`` + + const sql = safeSql`SELECT * FROM ( - ${[queueQuery, archivedQuery].filter(Boolean).join(' UNION ALL ')} - ) AS combined` - if (afterTimestamp) { - query += ` WHERE enqueued_at > ${literal(afterTimestamp)}` - } + ${unionFragment} + ) AS combined${whereClause} order by enqueued_at LIMIT ${literal(QUEUE_MESSAGES_PAGE_SIZE)}` const { result } = await executeSql({ projectRef, connectionString, - sql: `${query} order by enqueued_at LIMIT ${QUEUE_MESSAGES_PAGE_SIZE}`, + sql, }) return result as DatabaseQueueData } diff --git a/apps/studio/data/database-queues/database-queue-messages-read-mutation.ts b/apps/studio/data/database-queues/database-queue-messages-read-mutation.ts index f51bca283b7..7c72f35bc0d 100644 --- a/apps/studio/data/database-queues/database-queue-messages-read-mutation.ts +++ b/apps/studio/data/database-queues/database-queue-messages-read-mutation.ts @@ -1,4 +1,4 @@ -import { literal } from '@supabase/pg-meta/src/pg-format' +import { literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' @@ -31,7 +31,7 @@ export async function readDatabaseQueueMessage({ const { result } = await executeSql({ projectRef, connectionString, - sql: `select * from pgmq.set_vt(${literal(queueName)}, ${literal(messageId)}, ${literal(duration)})`, + sql: safeSql`select * from pgmq.set_vt(${literal(queueName)}, ${literal(messageId)}, ${literal(duration)})`, queryKey: databaseQueuesKeys.create(), }) diff --git a/apps/studio/data/database-queues/database-queue-messages-send-mutation.ts b/apps/studio/data/database-queues/database-queue-messages-send-mutation.ts index 4b181760e44..a3f6218fba2 100644 --- a/apps/studio/data/database-queues/database-queue-messages-send-mutation.ts +++ b/apps/studio/data/database-queues/database-queue-messages-send-mutation.ts @@ -1,4 +1,4 @@ -import { literal } from '@supabase/pg-meta/src/pg-format' +import { literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' @@ -31,7 +31,7 @@ export async function sendDatabaseQueueMessage({ const { result } = await executeSql({ projectRef, connectionString, - sql: `select * from pgmq.send(${literal(queueName)}, ${literal(payload)}, ${literal(delay)})`, + sql: safeSql`select * from pgmq.send(${literal(queueName)}, ${literal(payload)}, ${literal(delay)})`, queryKey: databaseQueuesKeys.create(), }) diff --git a/apps/studio/data/database-queues/database-queues-create-mutation.ts b/apps/studio/data/database-queues/database-queues-create-mutation.ts index c5c02394c3d..e26120ca0ae 100644 --- a/apps/studio/data/database-queues/database-queues-create-mutation.ts +++ b/apps/studio/data/database-queues/database-queues-create-mutation.ts @@ -1,9 +1,12 @@ -import { ident, literal } from '@supabase/pg-meta/src/pg-format' +import { ident, literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' import { databaseQueuesKeys } from './keys' -import { isQueueNameValid } from '@/components/interfaces/Integrations/Queues/Queues.utils' +import { + isQueueNameValid, + pgmqQueueTable, +} from '@/components/interfaces/Integrations/Queues/Queues.utils' import { executeSql } from '@/data/sql/execute-sql-query' import { tableKeys } from '@/data/tables/keys' import type { ResponseError, UseCustomMutationOptions } from '@/types' @@ -36,17 +39,21 @@ export async function createDatabaseQueue({ const { partitionInterval, retentionInterval } = configuration ?? {} - const query = + const createFragment = type === 'partitioned' - ? `select from pgmq.create_partitioned(${literal(name)}, ${literal(partitionInterval)}, ${literal(retentionInterval)});` + ? safeSql`select from pgmq.create_partitioned(${literal(name)}, ${literal(partitionInterval)}, ${literal(retentionInterval)});` : type === 'unlogged' - ? `SELECT pgmq.create_unlogged(${literal(name)});` - : `SELECT pgmq.create(${literal(name)});` + ? safeSql`SELECT pgmq.create_unlogged(${literal(name)});` + : safeSql`SELECT pgmq.create(${literal(name)});` + + const rlsFragment = enableRls + ? safeSql` alter table ${ident('pgmq')}.${ident(pgmqQueueTable(name))} enable row level security;` + : safeSql`` const { result } = await executeSql({ projectRef, connectionString, - sql: `${query} ${enableRls ? `alter table pgmq.${ident('q_' + name)} enable row level security;` : ''}`.trim(), + sql: safeSql`${createFragment}${rlsFragment}`, queryKey: databaseQueuesKeys.create(), }) diff --git a/apps/studio/data/database-queues/database-queues-delete-mutation.ts b/apps/studio/data/database-queues/database-queues-delete-mutation.ts index 97bac6182e2..38fc436080b 100644 --- a/apps/studio/data/database-queues/database-queues-delete-mutation.ts +++ b/apps/studio/data/database-queues/database-queues-delete-mutation.ts @@ -1,4 +1,4 @@ -import { literal } from '@supabase/pg-meta/src/pg-format' +import { literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' @@ -27,7 +27,7 @@ export async function deleteDatabaseQueue({ const { result } = await executeSql({ projectRef, connectionString, - sql: `select * from pgmq.drop_queue(${literal(queueName)});`, + sql: safeSql`select * from pgmq.drop_queue(${literal(queueName)});`, queryKey: databaseQueuesKeys.delete(queueName), }) diff --git a/apps/studio/data/database-queues/database-queues-metrics-query.ts b/apps/studio/data/database-queues/database-queues-metrics-query.ts index 82dbe4c5f0f..a41a6586a71 100644 --- a/apps/studio/data/database-queues/database-queues-metrics-query.ts +++ b/apps/studio/data/database-queues/database-queues-metrics-query.ts @@ -1,8 +1,11 @@ -import { ident, literal } from '@supabase/pg-meta/src/pg-format' +import { ident, literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useQuery } from '@tanstack/react-query' import { databaseQueuesKeys } from './keys' -import { isQueueNameValid } from '@/components/interfaces/Integrations/Queues/Queues.utils' +import { + isQueueNameValid, + pgmqQueueTable, +} from '@/components/interfaces/Integrations/Queues/Queues.utils' import { executeSql } from '@/data/sql/execute-sql-query' import type { ResponseError, UseCustomQueryOptions } from '@/types' @@ -18,27 +21,25 @@ export type PostgresQueueMetric = { method: 'estimated' | 'precise' } -const preciseMetricsSqlQuery = (queueName: string) => { - return ` +const preciseMetricsSqlQuery = (queueName: string) => + safeSql` set local statement_timeout = '1s'; SELECT COUNT(*) AS row_count FROM - "pgmq".${ident('q_' + queueName)}; + ${ident('pgmq')}.${ident(pgmqQueueTable(queueName))}; ` -} -const estimateMetricsSqlQuery = (queueName: string) => { - return ` +const estimateMetricsSqlQuery = (queueName: string) => + safeSql` select reltuples::bigint as estimated_rows from pg_class where - relname = ${literal('q_' + queueName)} + relname = ${literal(pgmqQueueTable(queueName))} and relnamespace = 'pgmq'::regnamespace; ` -} export async function getDatabaseQueuesMetrics({ projectRef, diff --git a/apps/studio/data/database-queues/database-queues-purge-mutation.ts b/apps/studio/data/database-queues/database-queues-purge-mutation.ts index 4d42427dd34..dc9aa97ae69 100644 --- a/apps/studio/data/database-queues/database-queues-purge-mutation.ts +++ b/apps/studio/data/database-queues/database-queues-purge-mutation.ts @@ -1,4 +1,4 @@ -import { literal } from '@supabase/pg-meta/src/pg-format' +import { literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' @@ -27,7 +27,7 @@ export async function purgeDatabaseQueue({ const { result } = await executeSql({ projectRef, connectionString, - sql: `select * from pgmq.purge_queue(${literal(queueName)});`, + sql: safeSql`select * from pgmq.purge_queue(${literal(queueName)});`, queryKey: databaseQueuesKeys.purge(queueName), }) diff --git a/apps/studio/data/database/retrieve-index-from-select-query.ts b/apps/studio/data/database/retrieve-index-from-select-query.ts index 8ed2108728e..419ecd6e323 100644 --- a/apps/studio/data/database/retrieve-index-from-select-query.ts +++ b/apps/studio/data/database/retrieve-index-from-select-query.ts @@ -1,3 +1,4 @@ +import { joinSqlFragments, literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useQuery } from '@tanstack/react-query' import { databaseKeys } from './keys' @@ -31,7 +32,7 @@ export async function getInvolvedIndexesInSelectQuery({ projectRef, connectionString, queryKey: ['involved-indexes-explain-query'], - sql: /* sql */ ` + sql: safeSql` create or replace function pg_temp.explain_query(query text) returns jsonb language plpgsql as $$ @@ -101,7 +102,7 @@ export async function getInvolvedIndexesInSelectQuery({ end; $$; - select pg_temp.explain_query('${query}') as plans; + select pg_temp.explain_query(${literal(query)}) as plans; `, }) @@ -113,7 +114,7 @@ export async function getInvolvedIndexesInSelectQuery({ projectRef, connectionString, queryKey: ['involved-indexes-names'], - sql: `select schemaname as schema, tablename as table, indexname as name from pg_indexes where indexname in (${involvedIndexes.map((name) => `'${name}'`).join(', ')});`, + sql: safeSql`select schemaname as schema, tablename as table, indexname as name from pg_indexes where indexname in (${joinSqlFragments(involvedIndexes.map(literal), ', ')});`, }) return indexResult as GetInvolvedIndexesFromSelectQueryResponse[] diff --git a/apps/studio/data/database/supamonitor-enabled-query.ts b/apps/studio/data/database/supamonitor-enabled-query.ts index e0670f12213..ed1046325cf 100644 --- a/apps/studio/data/database/supamonitor-enabled-query.ts +++ b/apps/studio/data/database/supamonitor-enabled-query.ts @@ -1,3 +1,4 @@ +import { safeSql } from '@supabase/pg-meta/src/pg-format' import { useQuery } from '@tanstack/react-query' import { databaseKeys } from './keys' @@ -18,7 +19,7 @@ export async function getSupamonitorEnabled({ const { result } = await executeSql<{ libraries: string }[]>({ projectRef, connectionString, - sql: `SELECT current_setting('shared_preload_libraries', true) AS libraries`, + sql: safeSql`SELECT current_setting('shared_preload_libraries', true) AS libraries`, }) const libraries = result[0]?.libraries ?? '' diff --git a/apps/studio/data/storage/public-buckets-with-select-policies-query.ts b/apps/studio/data/storage/public-buckets-with-select-policies-query.ts index fa6cee9c404..3df8fb7a3f4 100644 --- a/apps/studio/data/storage/public-buckets-with-select-policies-query.ts +++ b/apps/studio/data/storage/public-buckets-with-select-policies-query.ts @@ -1,4 +1,4 @@ -import { literal } from '@supabase/pg-meta/src/pg-format' +import { literal, safeSql } from '@supabase/pg-meta/src/pg-format' import { useQuery } from '@tanstack/react-query' import { storageKeys } from './keys' @@ -36,7 +36,7 @@ async function getPublicBucketsWithSelectPolicies({ const { result } = await executeSql({ projectRef, connectionString, - sql: ` + sql: safeSql` SELECT b.id AS bucket_id, b.name AS bucket_name, p.policyname FROM storage.buckets b JOIN pg_policies p diff --git a/apps/studio/data/tables/table-names-query.ts b/apps/studio/data/tables/table-names-query.ts index a55053bf4e6..6bbbd0f226e 100644 --- a/apps/studio/data/tables/table-names-query.ts +++ b/apps/studio/data/tables/table-names-query.ts @@ -1,3 +1,4 @@ +import { safeSql } from '@supabase/pg-meta/src/pg-format' import { useQuery } from '@tanstack/react-query' import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query' @@ -15,7 +16,7 @@ export type TableNamesVariables = { connectionString?: string | null } -const TABLE_NAMES_SQL = /* sql */ ` +const TABLE_NAMES_SQL = safeSql` select c.oid::int8 as id, nc.nspname as schema, @@ -31,7 +32,7 @@ where c.relkind in ('r', 'p') or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES') ) order by nc.nspname, c.relname -`.trim() +` export async function getTableNames( { projectRef, connectionString }: TableNamesVariables, diff --git a/packages/pg-meta/src/sql/studio/database/types.ts b/packages/pg-meta/src/sql/studio/database/types.ts index 2aea7a941d8..74f756bc519 100644 --- a/packages/pg-meta/src/sql/studio/database/types.ts +++ b/packages/pg-meta/src/sql/studio/database/types.ts @@ -22,7 +22,7 @@ export const getCreateEnumeratedTypeSQL = ({ } export const getDeleteEnumeratedTypeSQL = ({ schema, name }: { schema: string; name: string }) => { - return `drop type if exists ${ident(schema)}.${ident(name)}` + return safeSql`drop type if exists ${ident(schema)}.${ident(name)}` } export const getUpdateEnumeratedTypeSQL = ({