mirror of
https://github.com/supabase/supabase.git
synced 2026-10-19 08:09:11 +03:00
chore: pg-meta functions (#26881)
* start pg-meta functions * pg meta create function * pg meta database functions update and delete * fix ts * update execute sql error type * update execute sql error type * remove duplicate database functions query
This commit is contained in:
1 parent
2310b9a63b
commit
b4b67d22f3
38 files changed
+608
-242
No files matched your search
@@ -71,9 +71,11 @@ const FunctionSelector = ({
|
||||
<AlertTitle_Shadcn_ className="text-xs text-amber-900">
|
||||
Failed to load functions
|
||||
</AlertTitle_Shadcn_>
|
||||
|
||||
<AlertDescription_Shadcn_ className="text-xs mb-2">
|
||||
Error: {error.message}
|
||||
</AlertDescription_Shadcn_>
|
||||
|
||||
<Button type="default" size="tiny" onClick={() => refetch()}>
|
||||
Reload functions
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { PostgresFunction } from '@supabase/postgres-meta'
|
||||
import { isEmpty, isNull, keyBy, mapValues, partition } from 'lodash'
|
||||
import { Plus, Trash } from 'lucide-react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
@@ -12,6 +11,7 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte
|
||||
import SchemaSelector from 'components/ui/SchemaSelector'
|
||||
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
|
||||
import { useDatabaseFunctionCreateMutation } from 'data/database-functions/database-functions-create-mutation'
|
||||
import { DatabaseFunction } from 'data/database-functions/database-functions-query'
|
||||
import { useDatabaseFunctionUpdateMutation } from 'data/database-functions/database-functions-update-mutation'
|
||||
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
|
||||
import type { FormSchema } from 'types'
|
||||
@@ -50,7 +50,7 @@ import { FunctionEditor } from './FunctionEditor'
|
||||
const FORM_ID = 'create-function-sidepanel'
|
||||
|
||||
interface CreateFunctionProps {
|
||||
func?: PostgresFunction
|
||||
func?: DatabaseFunction
|
||||
visible: boolean
|
||||
setVisible: (value: boolean) => void
|
||||
}
|
||||
@@ -103,7 +103,7 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
|
||||
if (isEditing) {
|
||||
updateDatabaseFunction(
|
||||
{
|
||||
id: func.id,
|
||||
func,
|
||||
projectRef: project.ref,
|
||||
connectionString: project.connectionString,
|
||||
payload,
|
||||
|
||||
@@ -3,16 +3,17 @@ import toast from 'react-hot-toast'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import TextConfirmModal from 'ui-patterns/Dialogs/TextConfirmModal'
|
||||
import { useDatabaseFunctionDeleteMutation } from 'data/database-functions/database-functions-delete-mutation'
|
||||
import { DatabaseFunction } from 'data/database-functions/database-functions-query'
|
||||
|
||||
interface DeleteFunctionProps {
|
||||
func?: any
|
||||
func?: DatabaseFunction
|
||||
visible: boolean
|
||||
setVisible: (value: boolean) => void
|
||||
}
|
||||
|
||||
const DeleteFunction = ({ func, visible, setVisible }: DeleteFunctionProps) => {
|
||||
const { project } = useProjectContext()
|
||||
const { id, name, schema } = func ?? {}
|
||||
const { name, schema } = func ?? {}
|
||||
|
||||
const { mutate: deleteDatabaseFunction, isLoading } = useDatabaseFunctionDeleteMutation({
|
||||
onSuccess: () => {
|
||||
@@ -22,11 +23,11 @@ const DeleteFunction = ({ func, visible, setVisible }: DeleteFunctionProps) => {
|
||||
})
|
||||
|
||||
async function handleDelete() {
|
||||
if (!id) return console.error('Function ID is require')
|
||||
if (!func) return console.error('Function is required')
|
||||
if (!project) return console.error('Project is required')
|
||||
|
||||
deleteDatabaseFunction({
|
||||
id,
|
||||
func,
|
||||
projectRef: project.ref,
|
||||
connectionString: project.connectionString,
|
||||
})
|
||||
@@ -43,7 +44,7 @@ const DeleteFunction = ({ func, visible, setVisible }: DeleteFunctionProps) => {
|
||||
loading={isLoading}
|
||||
confirmLabel={`Delete function ${name}`}
|
||||
confirmPlaceholder="Type in name of function"
|
||||
confirmString={name}
|
||||
confirmString={name ?? 'Unknown'}
|
||||
text={
|
||||
<>
|
||||
<span>This will delete the function</span>{' '}
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ const FunctionsList = ({
|
||||
})
|
||||
|
||||
if (isLoading) return <GenericSkeletonLoader />
|
||||
if (isError) <AlertError error={error} subject="Failed to retrieve database functions" />
|
||||
if (isError) return <AlertError error={error} subject="Failed to retrieve database functions" />
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { RefObject } from 'react'
|
||||
import BackwardIterator from './BackwardIterator'
|
||||
import type { DatabaseFunction } from 'data/database-functions/database-functions-query'
|
||||
import type { Schema } from 'data/database/schemas-query'
|
||||
import type { TableColumn } from 'data/database/table-columns-query'
|
||||
import type { DatabaseFunction } from 'data/database/functions-query'
|
||||
import type { RefObject } from 'react'
|
||||
import BackwardIterator from './BackwardIterator'
|
||||
|
||||
// [Joshen] Needs to be fixed
|
||||
|
||||
@@ -200,8 +200,7 @@ function defaultScenarioSuggestions(monaco: any, pgInfoRef: RefObject<any>) {
|
||||
items.push({
|
||||
label: x.name,
|
||||
kind: monaco.languages.CompletionItemKind.Function,
|
||||
detail: x.result_type,
|
||||
documentation: x.description,
|
||||
detail: x.return_type,
|
||||
insertText: x.name,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { RefObject } from 'react'
|
||||
import BackwardIterator from './BackwardIterator'
|
||||
import type { DatabaseFunction } from 'data/database-functions/database-functions-query'
|
||||
|
||||
// [Joshen] Needs to be fixed
|
||||
|
||||
@@ -17,8 +18,8 @@ export default function getPgsqlSignatureHelpProvider(monaco: any, pgInfoRef: Re
|
||||
let ident = iterator.readIdent()
|
||||
if (!ident || ident.match(/^\".*?\"$/)) return null
|
||||
|
||||
let fn = pgInfoRef.current.functions.find(
|
||||
(f: any) => f.name.toLocaleLowerCase() === ident.toLocaleLowerCase()
|
||||
let fn = (pgInfoRef.current.functions as DatabaseFunction[]).find(
|
||||
(f) => f.name.toLocaleLowerCase() === ident.toLocaleLowerCase()
|
||||
)
|
||||
if (!fn) return null
|
||||
if (!fn.args || fn.args.length < paramCount) return null
|
||||
@@ -27,10 +28,9 @@ export default function getPgsqlSignatureHelpProvider(monaco: any, pgInfoRef: Re
|
||||
const activeParameter = Math.min(paramCount, fn.args.length - 1)
|
||||
const signatures = []
|
||||
signatures.push({
|
||||
label: `${fn.name}( ${fn.args.join(' , ')} )`,
|
||||
documentation: fn.description,
|
||||
parameters: fn.args.map((v: any) => {
|
||||
return { label: v }
|
||||
label: `${fn.name}(${fn.argument_types})`,
|
||||
parameters: fn.args.map((v) => {
|
||||
return { label: v.name }
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { handleError, post } from 'data/fetchers'
|
||||
import type { Dictionary, ResponseError } from 'types'
|
||||
import { databaseFunctionsKeys } from './keys'
|
||||
import pgMeta from '@supabase/pg-meta'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import type { ResponseError } from 'types'
|
||||
import { sqlKeys } from 'data/sql/keys'
|
||||
|
||||
export type DatabaseFunctionCreateVariables = {
|
||||
projectRef: string
|
||||
connectionString?: string
|
||||
payload: {
|
||||
name: string
|
||||
schema: string
|
||||
args: string[]
|
||||
behavior: string // 'VOLATILE' | 'STABLE' | 'IMMUTABLE'
|
||||
definition: string
|
||||
language: string
|
||||
return_type: string
|
||||
security_definer: boolean
|
||||
config_params?: Dictionary<string>
|
||||
}
|
||||
payload: z.infer<typeof pgMeta.functions.pgFunctionCreateZod>
|
||||
}
|
||||
|
||||
export async function createDatabaseFunction({
|
||||
@@ -26,20 +18,16 @@ export async function createDatabaseFunction({
|
||||
connectionString,
|
||||
payload,
|
||||
}: DatabaseFunctionCreateVariables) {
|
||||
let headers = new Headers()
|
||||
if (connectionString) headers.set('x-connection-encrypted', connectionString)
|
||||
const { sql, zod } = pgMeta.functions.create(payload)
|
||||
|
||||
const { data, error } = await post('/platform/pg-meta/{ref}/functions', {
|
||||
params: {
|
||||
path: { ref: projectRef },
|
||||
},
|
||||
// @ts-ignore API codegen is typed wrongly, i suspect its using the body for edge functions instead
|
||||
body: payload,
|
||||
headers,
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['functions', 'create'],
|
||||
})
|
||||
|
||||
if (error) handleError(error)
|
||||
return data
|
||||
return result as z.infer<typeof zod>
|
||||
}
|
||||
|
||||
type DatabaseFunctionCreateData = Awaited<ReturnType<typeof createDatabaseFunction>>
|
||||
@@ -59,7 +47,7 @@ export const useDatabaseFunctionCreateMutation = ({
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
const { projectRef } = variables
|
||||
await queryClient.invalidateQueries(databaseFunctionsKeys.list(projectRef))
|
||||
await queryClient.invalidateQueries(sqlKeys.query(projectRef, ['functions-list']))
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { del, handleError } from 'data/fetchers'
|
||||
import pgMeta from '@supabase/pg-meta'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { sqlKeys } from 'data/sql/keys'
|
||||
import type { ResponseError } from 'types'
|
||||
import { databaseFunctionsKeys } from './keys'
|
||||
import { DatabaseFunction } from './database-functions-query'
|
||||
|
||||
export type DatabaseFunctionDeleteVariables = {
|
||||
projectRef: string
|
||||
connectionString?: string
|
||||
id: number
|
||||
func: DatabaseFunction
|
||||
}
|
||||
|
||||
export async function deleteDatabaseFunction({
|
||||
projectRef,
|
||||
connectionString,
|
||||
id,
|
||||
func,
|
||||
}: DatabaseFunctionDeleteVariables) {
|
||||
let headers = new Headers()
|
||||
if (connectionString) headers.set('x-connection-encrypted', connectionString)
|
||||
const { sql, zod } = pgMeta.functions.remove(func)
|
||||
|
||||
const { data, error } = await del('/platform/pg-meta/{ref}/functions', {
|
||||
params: {
|
||||
header: { 'x-connection-encrypted': connectionString! },
|
||||
path: { ref: projectRef },
|
||||
query: { id },
|
||||
},
|
||||
headers,
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['functions', 'delete', func.id.toString()],
|
||||
})
|
||||
|
||||
if (error) handleError(error)
|
||||
return data
|
||||
return result as z.infer<typeof zod>
|
||||
}
|
||||
|
||||
type DatabaseFunctionDeleteData = Awaited<ReturnType<typeof deleteDatabaseFunction>>
|
||||
@@ -49,7 +48,7 @@ export const useDatabaseFunctionDeleteMutation = ({
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
const { projectRef } = variables
|
||||
await queryClient.invalidateQueries(databaseFunctionsKeys.list(projectRef))
|
||||
await queryClient.invalidateQueries(sqlKeys.query(projectRef, ['functions-list']))
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
|
||||
@@ -1,52 +1,40 @@
|
||||
import { UseQueryOptions, useQuery } from '@tanstack/react-query'
|
||||
import type { components } from 'data/api'
|
||||
import { get, handleError } from 'data/fetchers'
|
||||
import pgMeta from '@supabase/pg-meta'
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import type { ExecuteSqlData, ExecuteSqlError } from 'data/sql/execute-sql-query'
|
||||
import { useExecuteSqlQuery } from 'data/sql/execute-sql-query'
|
||||
import type { ResponseError } from 'types'
|
||||
import { databaseFunctionsKeys } from './keys'
|
||||
import { z } from 'zod'
|
||||
|
||||
export type DatabaseFunctionsVariables = {
|
||||
projectRef?: string
|
||||
connectionString?: string
|
||||
}
|
||||
|
||||
export type DatabaseFunction = components['schemas']['PostgresFunction']
|
||||
export type DatabaseFunction = z.infer<typeof pgMeta.functions.pgFunctionZod>
|
||||
|
||||
export async function getDatabaseFunctions(
|
||||
{ projectRef, connectionString }: DatabaseFunctionsVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!projectRef) throw new Error('projectRef is required')
|
||||
const pgMetaFunctionsList = pgMeta.functions.list()
|
||||
|
||||
let headers = new Headers()
|
||||
if (connectionString) headers.set('x-connection-encrypted', connectionString)
|
||||
|
||||
const { data, error } = await get('/platform/pg-meta/{ref}/functions', {
|
||||
// @ts-ignore [Joshen] Temp, seems like API codegen is wrong
|
||||
params: { path: { ref: projectRef } },
|
||||
headers,
|
||||
signal,
|
||||
})
|
||||
|
||||
if (error) handleError(error)
|
||||
// [Joshen] API codegen is wrong, its matching Edge functions type to database functions
|
||||
return data as unknown as DatabaseFunction[]
|
||||
}
|
||||
|
||||
export type DatabaseFunctionsData = Awaited<ReturnType<typeof getDatabaseFunctions>>
|
||||
export type DatabaseFunctionsData = z.infer<typeof pgMetaFunctionsList.zod>
|
||||
export type DatabaseFunctionsError = ResponseError
|
||||
|
||||
export const useDatabaseFunctionsQuery = <TData = DatabaseFunctionsData>(
|
||||
export const useDatabaseFunctionsQuery = <
|
||||
TData extends DatabaseFunctionsData = DatabaseFunctionsData,
|
||||
>(
|
||||
{ projectRef, connectionString }: DatabaseFunctionsVariables,
|
||||
{
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseQueryOptions<DatabaseFunctionsData, DatabaseFunctionsError, TData> = {}
|
||||
) =>
|
||||
useQuery<DatabaseFunctionsData, DatabaseFunctionsError, TData>(
|
||||
databaseFunctionsKeys.list(projectRef),
|
||||
({ signal }) => getDatabaseFunctions({ projectRef, connectionString }, signal),
|
||||
options: UseQueryOptions<ExecuteSqlData, ExecuteSqlError, TData> = {}
|
||||
) => {
|
||||
return useExecuteSqlQuery(
|
||||
{
|
||||
enabled: enabled && typeof projectRef !== 'undefined',
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql: pgMetaFunctionsList.sql,
|
||||
queryKey: ['functions-list'],
|
||||
},
|
||||
{
|
||||
select(data) {
|
||||
return (data as any)?.result ?? []
|
||||
},
|
||||
...options,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,39 +1,36 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { z } from 'zod'
|
||||
|
||||
import type { components } from 'data/api'
|
||||
import { handleError, patch } from 'data/fetchers'
|
||||
import pgMeta from '@supabase/pg-meta'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { sqlKeys } from 'data/sql/keys'
|
||||
import type { ResponseError } from 'types'
|
||||
import { databaseFunctionsKeys } from './keys'
|
||||
import type { DatabaseFunction } from './database-functions-query'
|
||||
|
||||
export type DatabaseFunctionUpdateVariables = {
|
||||
projectRef: string
|
||||
connectionString?: string
|
||||
id: number
|
||||
payload: components['schemas']['UpdateFunctionBody']
|
||||
func: DatabaseFunction
|
||||
payload: z.infer<typeof pgMeta.functions.pgFunctionCreateZod>
|
||||
}
|
||||
|
||||
export async function updateDatabaseFunction({
|
||||
projectRef,
|
||||
connectionString,
|
||||
id,
|
||||
func,
|
||||
payload,
|
||||
}: DatabaseFunctionUpdateVariables) {
|
||||
let headers = new Headers()
|
||||
if (connectionString) headers.set('x-connection-encrypted', connectionString)
|
||||
const { sql, zod } = pgMeta.functions.update(func, payload)
|
||||
|
||||
const { data, error } = await patch('/platform/pg-meta/{ref}/functions', {
|
||||
params: {
|
||||
header: { 'x-connection-encrypted': connectionString! },
|
||||
path: { ref: projectRef },
|
||||
query: { id },
|
||||
},
|
||||
body: payload,
|
||||
headers,
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['functions', 'update', func.id.toString()],
|
||||
})
|
||||
|
||||
if (error) handleError(error)
|
||||
return data
|
||||
return result as z.infer<typeof zod>
|
||||
}
|
||||
|
||||
type DatabaseFunctionUpdateData = Awaited<ReturnType<typeof updateDatabaseFunction>>
|
||||
@@ -53,7 +50,7 @@ export const useDatabaseFunctionUpdateMutation = ({
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
const { projectRef } = variables
|
||||
await queryClient.invalidateQueries(databaseFunctionsKeys.list(projectRef))
|
||||
await queryClient.invalidateQueries(sqlKeys.query(projectRef, ['functions-list']))
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export const databaseFunctionsKeys = {
|
||||
list: (projectRef: string | undefined) => ['projects', projectRef, 'database-functions'] as const,
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import pgMeta from '@supabase/pg-meta'
|
||||
import { QueryClient, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from 'data/sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from 'data/sql/execute-sql-query'
|
||||
import { sqlKeys } from 'data/sql/keys'
|
||||
|
||||
export type DatabaseRolesVariables = {
|
||||
@@ -15,7 +15,7 @@ export type PgRole = z.infer<typeof pgMeta.roles.zod>
|
||||
const pgMetaRolesList = pgMeta.roles.list()
|
||||
|
||||
export type DatabaseRolesData = z.infer<typeof pgMetaRolesList.zod>
|
||||
export type DatabaseRolesError = unknown
|
||||
export type DatabaseRolesError = ExecuteSqlError
|
||||
|
||||
export const useDatabaseRolesQuery = <TData = DatabaseRolesData>(
|
||||
{ projectRef, connectionString }: DatabaseRolesVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
|
||||
type GetTableConstraintsVariables = {
|
||||
schema?: string
|
||||
@@ -45,7 +45,7 @@ export type TableConstraintsVariables = GetTableConstraintsVariables & {
|
||||
}
|
||||
|
||||
export type TableConstraintsData = Constraint[]
|
||||
export type TableConstraintsError = unknown
|
||||
export type TableConstraintsError = ExecuteSqlError
|
||||
|
||||
export const useTableConstraintsQuery = <TData extends TableConstraintsData = TableConstraintsData>(
|
||||
{ projectRef, connectionString, schema, table }: TableConstraintsVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
|
||||
export const getDatabaseSizeQuery = () => {
|
||||
const sql = /* SQL */ `
|
||||
@@ -15,7 +15,7 @@ export type DatabaseSizeVariables = {
|
||||
}
|
||||
|
||||
export type DatabaseSizeData = { result: { db_size: number }[] }
|
||||
export type DatabaseSizeError = unknown
|
||||
export type DatabaseSizeError = ExecuteSqlError
|
||||
|
||||
export const useDatabaseSizeQuery = <TData extends DatabaseSizeData = DatabaseSizeData>(
|
||||
{ projectRef, connectionString }: DatabaseSizeVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { CREATE_PG_GET_TABLEDEF_SQL } from './database-query-constants'
|
||||
|
||||
export const getEntityDefinitionsQuery = (limit = 100) => {
|
||||
@@ -82,7 +82,7 @@ export type EntityDefinitionsVariables = {
|
||||
|
||||
type EntityDefinition = { id: number; sql: string }
|
||||
export type EntityDefinitionsData = EntityDefinition[]
|
||||
export type EntityDefinitionsError = unknown
|
||||
export type EntityDefinitionsError = ExecuteSqlError
|
||||
|
||||
export const useEntityDefinitionsQuery = <
|
||||
TData extends EntityDefinitionsData = EntityDefinitionsData,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
|
||||
type GetForeignKeyConstraintsVariables = {
|
||||
schema?: string
|
||||
@@ -94,7 +94,7 @@ export type ForeignKeyConstraintsVariables = GetForeignKeyConstraintsVariables &
|
||||
}
|
||||
|
||||
export type ForeignKeyConstraintsData = ForeignKeyConstraint[]
|
||||
export type ForeignKeyConstraintsError = unknown
|
||||
export type ForeignKeyConstraintsError = ExecuteSqlError
|
||||
|
||||
export const useForeignKeyConstraintsQuery = <
|
||||
TData extends ForeignKeyConstraintsData = ForeignKeyConstraintsData,
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
|
||||
export type DatabaseFunction = {
|
||||
schema: string
|
||||
name: string
|
||||
description: string
|
||||
result_type: any
|
||||
argument_types: any
|
||||
args?: string[]
|
||||
}
|
||||
|
||||
export const getFunctionsQuery = () => {
|
||||
const sql = /* SQL */ `
|
||||
SELECT n.nspname as "schema",
|
||||
p.proname as "name",
|
||||
d.description,
|
||||
pg_catalog.pg_get_function_result(p.oid) as "result_type",
|
||||
pg_catalog.pg_get_function_arguments(p.oid) as "argument_types",
|
||||
CASE
|
||||
WHEN p.prokind = 'a' THEN 'agg'
|
||||
WHEN p.prokind = 'w' THEN 'window'
|
||||
WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger'
|
||||
ELSE 'normal'
|
||||
END as "type"
|
||||
FROM pg_catalog.pg_proc p
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
||||
LEFT JOIN pg_catalog.pg_description d ON p.oid = d.objoid
|
||||
WHERE n.nspname = 'public'
|
||||
ORDER BY 1, 2, 4;
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export type FunctionsVariables = {
|
||||
projectRef?: string
|
||||
connectionString?: string
|
||||
}
|
||||
|
||||
export type FunctionsData = { result: DatabaseFunction[] }
|
||||
export type FunctionsError = unknown
|
||||
|
||||
export const useFunctionsQuery = <TData extends FunctionsData = FunctionsData>(
|
||||
{ projectRef, connectionString }: FunctionsVariables,
|
||||
options: UseQueryOptions<ExecuteSqlData, FunctionsError, TData> = {}
|
||||
) => {
|
||||
return useExecuteSqlQuery(
|
||||
{
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql: getFunctionsQuery(),
|
||||
queryKey: ['functions'],
|
||||
},
|
||||
{
|
||||
select: (data) => {
|
||||
return {
|
||||
result: data.result.map((x: DatabaseFunction) => {
|
||||
const args = x.argument_types
|
||||
.split(',')
|
||||
.filter((a: any) => a)
|
||||
.map((a: any) => a.trim())
|
||||
|
||||
return { ...x, args }
|
||||
}),
|
||||
} as any
|
||||
},
|
||||
...options,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import type { ResponseError } from 'types'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
|
||||
// [Joshen] Future refactor to move to pg-meta once available
|
||||
|
||||
@@ -35,7 +34,7 @@ export type IndexesVariables = getIndexesDefinition & {
|
||||
}
|
||||
|
||||
export type IndexesData = { result: DatabaseIndex[] }
|
||||
export type IndexesError = unknown
|
||||
export type IndexesError = ExecuteSqlError
|
||||
|
||||
export const useIndexesQuery = <TData extends IndexesData = IndexesData>(
|
||||
{ schema, projectRef, connectionString }: IndexesVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
|
||||
export type DatabaseKeyword = { word: string }
|
||||
|
||||
@@ -17,7 +17,7 @@ export type KeywordsVariables = {
|
||||
}
|
||||
|
||||
export type KeywordsData = { result: string[] }
|
||||
export type KeywordsError = unknown
|
||||
export type KeywordsError = ExecuteSqlError
|
||||
|
||||
export const useKeywordsQuery = <TData extends KeywordsData = KeywordsData>(
|
||||
{ projectRef, connectionString }: KeywordsVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
|
||||
export const getMaxConnectionsQuery = () => {
|
||||
const sql = /* SQL */ `show max_connections`
|
||||
@@ -15,7 +15,7 @@ export type MaxConnectionsVariables = {
|
||||
}
|
||||
|
||||
export type MaxConnectionsData = { maxConnections: number }
|
||||
export type MaxConnectionsError = unknown
|
||||
export type MaxConnectionsError = ExecuteSqlError
|
||||
|
||||
export const useMaxConnectionsQuery = <TData extends MaxConnectionsData = MaxConnectionsData>(
|
||||
{ projectRef, connectionString }: MaxConnectionsVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
|
||||
export type DatabaseMigration = {
|
||||
version: string
|
||||
@@ -24,7 +24,7 @@ export type MigrationsVariables = {
|
||||
}
|
||||
|
||||
export type MigrationsData = { result: DatabaseMigration[] }
|
||||
export type MigrationsError = unknown
|
||||
export type MigrationsError = ExecuteSqlError
|
||||
|
||||
export const useMigrationsQuery = <TData extends MigrationsData = MigrationsData>(
|
||||
{ projectRef, connectionString }: MigrationsVariables,
|
||||
@@ -36,7 +36,7 @@ export const useMigrationsQuery = <TData extends MigrationsData = MigrationsData
|
||||
connectionString,
|
||||
sql: getMigrationsQuery(),
|
||||
queryKey: ['migrations'],
|
||||
handleError: (error: { code: number; message: string; requestId: string }) => {
|
||||
handleError: (error) => {
|
||||
if (
|
||||
error.message.includes('relation "supabase_migrations.schema_migrations" does not exist')
|
||||
) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import pgMeta from '@supabase/pg-meta'
|
||||
import { QueryClient, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from 'data/sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from 'data/sql/execute-sql-query'
|
||||
import { sqlKeys } from 'data/sql/keys'
|
||||
|
||||
export type SchemasVariables = {
|
||||
@@ -15,7 +15,7 @@ export type Schema = z.infer<typeof pgMeta.schemas.zod>
|
||||
const pgMetaSchemasList = pgMeta.schemas.list()
|
||||
|
||||
export type SchemasData = z.infer<typeof pgMetaSchemasList.zod>
|
||||
export type SchemasError = unknown
|
||||
export type SchemasError = ExecuteSqlError
|
||||
|
||||
export const useSchemasQuery = <TData = SchemasData>(
|
||||
{ projectRef, connectionString }: SchemasVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
|
||||
export type TableColumn = {
|
||||
schemaname: string
|
||||
@@ -92,7 +92,7 @@ export type TableColumnsVariables = {
|
||||
}
|
||||
|
||||
export type TableColumnsData = { result: TableColumn[] }
|
||||
export type TableColumnsError = unknown
|
||||
export type TableColumnsError = ExecuteSqlError
|
||||
|
||||
export const useTableColumnsQuery = <TData extends TableColumnsData = TableColumnsData>(
|
||||
{ projectRef, connectionString, table, schema }: TableColumnsVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { CREATE_PG_GET_TABLEDEF_SQL } from './database-query-constants'
|
||||
|
||||
type GetTableDefinition = {
|
||||
@@ -29,7 +29,7 @@ export type TableDefinitionVariables = GetTableDefinition & {
|
||||
}
|
||||
|
||||
export type TableDefinitionData = string
|
||||
export type TableDefinitionError = unknown
|
||||
export type TableDefinitionError = ExecuteSqlError
|
||||
|
||||
export const useTableDefinitionQuery = <TData extends TableDefinitionData = TableDefinitionData>(
|
||||
{ projectRef, connectionString, schema, name }: TableDefinitionVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
|
||||
type GetViewDefinition = {
|
||||
schema?: string
|
||||
@@ -20,7 +20,7 @@ export type ViewDefinitionVariables = GetViewDefinition & {
|
||||
}
|
||||
|
||||
export type ViewDefinitionData = string
|
||||
export type ViewDefinitionError = unknown
|
||||
export type ViewDefinitionError = ExecuteSqlError
|
||||
|
||||
export const useViewDefinitionQuery = <TData extends ViewDefinitionData = ViewDefinitionData>(
|
||||
{ projectRef, connectionString, schema, name }: ViewDefinitionVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ENTITY_TYPE } from './entity-type-constants'
|
||||
|
||||
type EntityTypeArgs = {
|
||||
@@ -53,7 +53,7 @@ export type EntityTypeVariables = EntityTypeArgs & {
|
||||
}
|
||||
|
||||
export type EntityTypeData = EntityType
|
||||
export type EntityTypeError = unknown
|
||||
export type EntityTypeError = ExecuteSqlError
|
||||
|
||||
export const useEntityTypeQuery = <TData extends EntityTypeData = EntityTypeData>(
|
||||
{ projectRef, connectionString, id, ...args }: EntityTypeVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
|
||||
export const getFDWsSql = () => {
|
||||
const sql = /* SQL */ `
|
||||
@@ -72,7 +72,7 @@ export type FDWsVariables = {
|
||||
}
|
||||
|
||||
export type FDWsData = FDWsResponse
|
||||
export type FDWsError = unknown
|
||||
export type FDWsError = ExecuteSqlError
|
||||
|
||||
export const useFDWsQuery = <TData extends FDWsData = FDWsData>(
|
||||
{ projectRef, connectionString }: FDWsVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
|
||||
export const replicationLagQuery = () => {
|
||||
const sql = /* SQL */ `
|
||||
@@ -21,7 +21,7 @@ export type ReplicationLagVariables = {
|
||||
}
|
||||
|
||||
export type ReplicationLagData = number
|
||||
export type ReplicationLagError = unknown
|
||||
export type ReplicationLagError = ExecuteSqlError
|
||||
|
||||
export const useReplicationLagQuery = <TData extends ReplicationLagData = ReplicationLagData>(
|
||||
{ projectRef, connectionString, id }: ReplicationLagVariables,
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { QueryClient, QueryKey, useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
|
||||
import { post, handleError as handleErrorFetchers } from 'data/fetchers'
|
||||
import { handleError as handleErrorFetchers, post } from 'data/fetchers'
|
||||
import {
|
||||
ROLE_IMPERSONATION_NO_RESULTS,
|
||||
ROLE_IMPERSONATION_SQL_LINE_COUNT,
|
||||
} from 'lib/role-impersonation'
|
||||
import type { ResponseError } from 'types'
|
||||
import { sqlKeys } from './keys'
|
||||
|
||||
export type Error = { code: number; message: string; requestId: string }
|
||||
|
||||
export type ExecuteSqlVariables = {
|
||||
projectRef?: string
|
||||
connectionString?: string
|
||||
sql: string
|
||||
queryKey?: QueryKey
|
||||
handleError?: (error: { code: number; message: string; requestId: string }) => any
|
||||
handleError?: (error: ResponseError) => { result: any }
|
||||
isRoleImpersonationEnabled?: boolean
|
||||
}
|
||||
|
||||
@@ -85,7 +84,7 @@ export async function executeSql(
|
||||
}
|
||||
|
||||
if (handleError !== undefined) return handleError(error as any)
|
||||
else handleErrorFetchers(error as any)
|
||||
else handleErrorFetchers(error)
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -100,7 +99,7 @@ export async function executeSql(
|
||||
}
|
||||
|
||||
export type ExecuteSqlData = Awaited<ReturnType<typeof executeSql>>
|
||||
export type ExecuteSqlError = unknown
|
||||
export type ExecuteSqlError = ResponseError
|
||||
|
||||
export const useExecuteSqlQuery = <TData = ExecuteSqlData>(
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { QueryKey, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { Filter, Query, SupaTable } from 'components/grid'
|
||||
import { ImpersonationRole, wrapWithRoleImpersonation } from 'lib/role-impersonation'
|
||||
import { useIsRoleImpersonationEnabled } from 'state/role-impersonation-state'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { formatFilterValue } from './utils'
|
||||
|
||||
type GetTableRowsCountArgs = {
|
||||
@@ -43,7 +43,7 @@ export type TableRowsCountVariables = GetTableRowsCountArgs & {
|
||||
}
|
||||
|
||||
export type TableRowsCountData = TableRowsCount
|
||||
export type TableRowsCountError = unknown
|
||||
export type TableRowsCountError = ExecuteSqlError
|
||||
|
||||
export const useTableRowsCountQuery = <TData extends TableRowsCountData = TableRowsCountData>(
|
||||
{
|
||||
|
||||
@@ -13,7 +13,12 @@ import {
|
||||
wrapWithRoleImpersonation,
|
||||
} from 'lib/role-impersonation'
|
||||
import { useIsRoleImpersonationEnabled } from 'state/role-impersonation-state'
|
||||
import { ExecuteSqlData, executeSql, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import {
|
||||
ExecuteSqlData,
|
||||
ExecuteSqlError,
|
||||
executeSql,
|
||||
useExecuteSqlQuery,
|
||||
} from '../sql/execute-sql-query'
|
||||
import { getPagination } from '../utils/pagination'
|
||||
import { formatFilterValue } from './utils'
|
||||
|
||||
@@ -155,7 +160,7 @@ export type TableRowsVariables = GetTableRowsArgs & {
|
||||
}
|
||||
|
||||
export type TableRowsData = TableRows
|
||||
export type TableRowsError = unknown
|
||||
export type TableRowsError = ExecuteSqlError
|
||||
|
||||
export const useTableRowsQuery = <TData extends TableRowsData = TableRowsData>(
|
||||
{ projectRef, connectionString, queryKey, table, impersonatedRole, ...args }: TableRowsVariables,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { Query } from 'components/grid/query/Query'
|
||||
import type { VaultSecret } from 'types'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
|
||||
import { vaultSecretsKeys } from './keys'
|
||||
|
||||
export const getVaultSecretsQuery = () => {
|
||||
@@ -19,7 +19,7 @@ export type VaultSecretsVariables = {
|
||||
}
|
||||
|
||||
export type VaultSecretsData = VaultSecret[]
|
||||
export type VaultSecretsError = unknown
|
||||
export type VaultSecretsError = ExecuteSqlError
|
||||
|
||||
export const useVaultSecretsQuery = <TData extends VaultSecretsData = VaultSecretsData>(
|
||||
{ projectRef, connectionString }: VaultSecretsVariables,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { PostgresFunction } from '@supabase/postgres-meta'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useState } from 'react'
|
||||
|
||||
@@ -8,11 +7,12 @@ import { DatabaseLayout } from 'components/layouts'
|
||||
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
|
||||
import { FormHeader } from 'components/ui/Forms'
|
||||
import NoPermission from 'components/ui/NoPermission'
|
||||
import { DatabaseFunction } from 'data/database-functions/database-functions-query'
|
||||
import { useCheckPermissions, usePermissionsLoaded } from 'hooks'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
|
||||
const FunctionsPage: NextPageWithLayout = () => {
|
||||
const [selectedFunction, setSelectedFunction] = useState<PostgresFunction | undefined>()
|
||||
const [selectedFunction, setSelectedFunction] = useState<DatabaseFunction | undefined>()
|
||||
const [showCreateFunctionForm, setShowCreateFunctionForm] = useState(false)
|
||||
const [showDeleteFunctionForm, setShowDeleteFunctionForm] = useState(false)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useParams } from 'common'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
import { useFunctionsQuery } from 'data/database/functions-query'
|
||||
import { useDatabaseFunctionsQuery } from 'data/database-functions/database-functions-query'
|
||||
import { useKeywordsQuery } from 'data/database/keywords-query'
|
||||
import { useSchemasQuery } from 'data/database/schemas-query'
|
||||
import { useTableColumnsQuery } from 'data/database/table-columns-query'
|
||||
@@ -59,7 +59,7 @@ const SqlEditor: NextPageWithLayout = () => {
|
||||
},
|
||||
{ enabled: intellisenseEnabled }
|
||||
)
|
||||
const { data: functions, isSuccess: isFunctionsSuccess } = useFunctionsQuery(
|
||||
const { data: functions, isSuccess: isFunctionsSuccess } = useDatabaseFunctionsQuery(
|
||||
{
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
@@ -99,7 +99,7 @@ const SqlEditor: NextPageWithLayout = () => {
|
||||
pgInfoRef.current.tableColumns = tableColumns?.result
|
||||
pgInfoRef.current.schemas = schemas
|
||||
pgInfoRef.current.keywords = keywords?.result
|
||||
pgInfoRef.current.functions = functions?.result
|
||||
pgInfoRef.current.functions = functions
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { literal } from 'pg-format'
|
||||
|
||||
export function filterByList(include?: string[], exclude?: string[], defaultExclude?: string[]) {
|
||||
if (defaultExclude) {
|
||||
exclude = defaultExclude.concat(exclude ?? [])
|
||||
}
|
||||
if (include?.length) {
|
||||
return `IN (${include.map(literal).join(',')})`
|
||||
} else if (exclude?.length) {
|
||||
return `NOT IN (${exclude.map(literal).join(',')})`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import roles from './pg-meta-roles'
|
||||
import schemas from './pg-meta-schemas'
|
||||
import * as functions from './pg-meta-functions'
|
||||
|
||||
export default {
|
||||
roles,
|
||||
schemas,
|
||||
functions,
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
import { ident, literal } from 'pg-format'
|
||||
import { z } from 'zod'
|
||||
import { DEFAULT_SYSTEM_SCHEMAS } from './constants'
|
||||
import { filterByList } from './helpers'
|
||||
import { FUNCTIONS_SQL } from './sql/functions'
|
||||
|
||||
export const pgFunctionZod = z.object({
|
||||
id: z.number(),
|
||||
schema: z.string(),
|
||||
name: z.string(),
|
||||
language: z.string(),
|
||||
definition: z.string(),
|
||||
complete_statement: z.string(),
|
||||
args: z.array(
|
||||
z.object({
|
||||
mode: z.union([
|
||||
z.literal('in'),
|
||||
z.literal('out'),
|
||||
z.literal('inout'),
|
||||
z.literal('variadic'),
|
||||
z.literal('table'),
|
||||
]),
|
||||
name: z.string(),
|
||||
type_id: z.number(),
|
||||
has_default: z.boolean(),
|
||||
})
|
||||
),
|
||||
argument_types: z.string(),
|
||||
identity_argument_types: z.string(),
|
||||
return_type_id: z.number(),
|
||||
return_type: z.string(),
|
||||
return_type_relation_id: z.union([z.number(), z.null()]),
|
||||
is_set_returning_function: z.boolean(),
|
||||
behavior: z.union([z.literal('IMMUTABLE'), z.literal('STABLE'), z.literal('VOLATILE')]),
|
||||
security_definer: z.boolean(),
|
||||
config_params: z.union([z.record(z.string(), z.string()), z.null()]),
|
||||
})
|
||||
|
||||
export const pgFunctionArrayZod = z.array(pgFunctionZod)
|
||||
export const pgFunctionOptionalZod = z.optional(pgFunctionZod)
|
||||
|
||||
export type PGFunction = z.infer<typeof pgFunctionZod>
|
||||
|
||||
export function list({
|
||||
includeSystemSchemas = false,
|
||||
includedSchemas,
|
||||
excludedSchemas,
|
||||
limit,
|
||||
offset,
|
||||
}: {
|
||||
includeSystemSchemas?: boolean
|
||||
includedSchemas?: string[]
|
||||
excludedSchemas?: string[]
|
||||
limit?: number
|
||||
offset?: number
|
||||
} = {}): {
|
||||
sql: string
|
||||
zod: typeof pgFunctionArrayZod
|
||||
} {
|
||||
let sql = /* SQL */ `
|
||||
with f as (
|
||||
${FUNCTIONS_SQL}
|
||||
)
|
||||
select
|
||||
f.*
|
||||
from f
|
||||
`
|
||||
const filter = filterByList(
|
||||
includedSchemas,
|
||||
excludedSchemas,
|
||||
!includeSystemSchemas ? DEFAULT_SYSTEM_SCHEMAS : undefined
|
||||
)
|
||||
if (filter) {
|
||||
sql += ` where schema ${filter}`
|
||||
}
|
||||
if (limit) {
|
||||
sql = `${sql} limit ${limit}`
|
||||
}
|
||||
if (offset) {
|
||||
sql = `${sql} offset ${offset}`
|
||||
}
|
||||
|
||||
return {
|
||||
sql,
|
||||
zod: pgFunctionArrayZod,
|
||||
}
|
||||
}
|
||||
|
||||
export function retrieve({ id }: { id: number }): {
|
||||
sql: string
|
||||
zod: typeof pgFunctionOptionalZod
|
||||
}
|
||||
export function retrieve({
|
||||
name,
|
||||
schema,
|
||||
args,
|
||||
}: {
|
||||
name: string
|
||||
schema: string
|
||||
args: string[]
|
||||
}): {
|
||||
sql: string
|
||||
zod: typeof pgFunctionOptionalZod
|
||||
}
|
||||
export function retrieve({
|
||||
id,
|
||||
name,
|
||||
schema = 'public',
|
||||
args = [],
|
||||
}: {
|
||||
id?: number
|
||||
name?: string
|
||||
schema?: string
|
||||
args?: string[]
|
||||
}): {
|
||||
sql: string
|
||||
zod: typeof pgFunctionOptionalZod
|
||||
} {
|
||||
if (id) {
|
||||
const sql = /* SQL */ `
|
||||
with f as (
|
||||
${FUNCTIONS_SQL}
|
||||
)
|
||||
select
|
||||
f.*
|
||||
from f where id = ${literal(id)};`
|
||||
|
||||
return {
|
||||
sql,
|
||||
zod: pgFunctionOptionalZod,
|
||||
}
|
||||
} else if (name && schema && args) {
|
||||
const sql = /* SQL */ `with f as (
|
||||
${FUNCTIONS_SQL}
|
||||
)
|
||||
select
|
||||
f.*
|
||||
from f join pg_proc as p on id = p.oid where schema = ${literal(
|
||||
schema
|
||||
)} and name = ${literal(name)} and p.proargtypes::text = ${
|
||||
args.length
|
||||
? /* SQL */ `(
|
||||
select string_agg(type_oid::text, ' ') from (
|
||||
select (
|
||||
split_args.arr[
|
||||
array_length(
|
||||
split_args.arr,
|
||||
1
|
||||
)
|
||||
]::regtype::oid
|
||||
) as type_oid from (
|
||||
select string_to_array(
|
||||
unnest(
|
||||
array[${args.map(literal)}]
|
||||
),
|
||||
' '
|
||||
) as arr
|
||||
) as split_args
|
||||
) args
|
||||
)`
|
||||
: literal('')
|
||||
}`
|
||||
|
||||
return {
|
||||
sql,
|
||||
zod: pgFunctionOptionalZod,
|
||||
}
|
||||
} else {
|
||||
throw new Error('Must provide either id or name and schema')
|
||||
}
|
||||
}
|
||||
|
||||
export const pgFunctionCreateZod = z.object({
|
||||
name: z.string(),
|
||||
definition: z.string(),
|
||||
args: z.array(z.string()).optional(),
|
||||
behavior: z.enum(['IMMUTABLE', 'STABLE', 'VOLATILE']).optional(),
|
||||
config_params: z.record(z.string(), z.string()).optional(),
|
||||
schema: z.string().optional(),
|
||||
language: z.string().optional(),
|
||||
return_type: z.string().optional(),
|
||||
security_definer: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export type PGFunctionCreate = z.infer<typeof pgFunctionCreateZod>
|
||||
|
||||
function _generateCreateFunctionSql(
|
||||
{
|
||||
name,
|
||||
schema,
|
||||
args,
|
||||
definition,
|
||||
return_type,
|
||||
language,
|
||||
behavior,
|
||||
security_definer,
|
||||
config_params,
|
||||
}: PGFunctionCreate,
|
||||
{ replace = false } = {}
|
||||
): string {
|
||||
return /* SQL */ `
|
||||
CREATE ${replace ? 'OR REPLACE' : ''} FUNCTION ${ident(schema!)}.${ident(name!)}(${
|
||||
args?.join(', ') || ''
|
||||
})
|
||||
RETURNS ${return_type}
|
||||
AS ${literal(definition)}
|
||||
LANGUAGE ${language}
|
||||
${behavior}
|
||||
CALLED ON NULL INPUT
|
||||
${security_definer ? 'SECURITY DEFINER' : 'SECURITY INVOKER'}
|
||||
${
|
||||
config_params
|
||||
? Object.entries(config_params)
|
||||
.map(
|
||||
([param, value]: string[]) =>
|
||||
`SET ${param} ${value[0] === 'FROM CURRENT' ? 'FROM CURRENT' : 'TO ' + value}`
|
||||
)
|
||||
.join('\n')
|
||||
: ''
|
||||
};
|
||||
`
|
||||
}
|
||||
|
||||
export function create({
|
||||
name,
|
||||
schema = 'public',
|
||||
args = [],
|
||||
definition,
|
||||
return_type = 'void',
|
||||
language = 'sql',
|
||||
behavior = 'VOLATILE',
|
||||
security_definer = false,
|
||||
config_params = {},
|
||||
}: PGFunctionCreate) {
|
||||
const sql = _generateCreateFunctionSql({
|
||||
name,
|
||||
schema,
|
||||
args,
|
||||
definition,
|
||||
return_type,
|
||||
language,
|
||||
behavior,
|
||||
security_definer,
|
||||
config_params,
|
||||
})
|
||||
|
||||
return {
|
||||
sql,
|
||||
zod: z.void(),
|
||||
}
|
||||
}
|
||||
|
||||
export const pgFunctionUpdateZod = z.object({
|
||||
name: z.string().optional(),
|
||||
schema: z.string().optional(),
|
||||
definition: z.string().optional(),
|
||||
})
|
||||
|
||||
export type PGFunctionUpdate = z.infer<typeof pgFunctionUpdateZod>
|
||||
|
||||
export function update(currentFunc: PGFunction, { name, schema, definition }: PGFunctionUpdate) {
|
||||
const args = currentFunc.argument_types.split(', ')
|
||||
const identityArgs = currentFunc.identity_argument_types
|
||||
|
||||
const updateDefinitionSql =
|
||||
typeof definition === 'string'
|
||||
? _generateCreateFunctionSql(
|
||||
{
|
||||
...currentFunc!,
|
||||
definition,
|
||||
args,
|
||||
config_params: currentFunc!.config_params ?? {},
|
||||
},
|
||||
{ replace: true }
|
||||
)
|
||||
: ''
|
||||
|
||||
const updateNameSql =
|
||||
name && name !== currentFunc!.name
|
||||
? `ALTER FUNCTION ${ident(currentFunc!.schema)}.${ident(
|
||||
currentFunc!.name
|
||||
)}(${identityArgs}) RENAME TO ${ident(name)};`
|
||||
: ''
|
||||
|
||||
const updateSchemaSql =
|
||||
schema && schema !== currentFunc!.schema
|
||||
? `ALTER FUNCTION ${ident(currentFunc!.schema)}.${ident(
|
||||
name || currentFunc!.name
|
||||
)}(${identityArgs}) SET SCHEMA ${ident(schema)};`
|
||||
: ''
|
||||
|
||||
const sql = /* SQL */ `
|
||||
DO LANGUAGE plpgsql $$
|
||||
BEGIN
|
||||
IF ${typeof definition === 'string' ? 'TRUE' : 'FALSE'} THEN
|
||||
${updateDefinitionSql}
|
||||
|
||||
IF (
|
||||
SELECT id
|
||||
FROM (${FUNCTIONS_SQL}) AS f
|
||||
WHERE f.schema = ${literal(currentFunc!.schema)}
|
||||
AND f.name = ${literal(currentFunc!.name)}
|
||||
AND f.identity_argument_types = ${literal(identityArgs)}
|
||||
) != ${currentFunc.id} THEN
|
||||
RAISE EXCEPTION 'Cannot find function "${currentFunc!.schema}"."${
|
||||
currentFunc!.name
|
||||
}"(${identityArgs})';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
${updateNameSql}
|
||||
|
||||
${updateSchemaSql}
|
||||
END;
|
||||
$$;
|
||||
`
|
||||
|
||||
return {
|
||||
sql,
|
||||
zod: z.void(),
|
||||
}
|
||||
}
|
||||
|
||||
export const pgFunctionDeleteZod = z.object({
|
||||
cascade: z.boolean().default(false).optional(),
|
||||
})
|
||||
|
||||
export type PGFunctionDelete = z.infer<typeof pgFunctionDeleteZod>
|
||||
|
||||
export function remove(func: PGFunction, { cascade = false }: PGFunctionDelete = {}) {
|
||||
const sql = /* SQL */ `DROP FUNCTION ${ident(func.schema)}.${ident(func.name)}
|
||||
(${func.identity_argument_types})
|
||||
${cascade ? 'CASCADE' : 'RESTRICT'};`
|
||||
|
||||
return {
|
||||
sql,
|
||||
zod: z.void(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
export const FUNCTIONS_SQL = /* SQL */ `
|
||||
-- CTE with sane arg_modes, arg_names, and arg_types.
|
||||
-- All three are always of the same length.
|
||||
-- All three include all args, including OUT and TABLE args.
|
||||
with functions as (
|
||||
select
|
||||
*,
|
||||
-- proargmodes is null when all arg modes are IN
|
||||
coalesce(
|
||||
p.proargmodes,
|
||||
array_fill('i'::text, array[cardinality(coalesce(p.proallargtypes, p.proargtypes))])
|
||||
) as arg_modes,
|
||||
-- proargnames is null when all args are unnamed
|
||||
coalesce(
|
||||
p.proargnames,
|
||||
array_fill(''::text, array[cardinality(coalesce(p.proallargtypes, p.proargtypes))])
|
||||
) as arg_names,
|
||||
-- proallargtypes is null when all arg modes are IN
|
||||
coalesce(p.proallargtypes, p.proargtypes) as arg_types,
|
||||
array_cat(
|
||||
array_fill(false, array[pronargs - pronargdefaults]),
|
||||
array_fill(true, array[pronargdefaults])) as arg_has_defaults
|
||||
from
|
||||
pg_proc as p
|
||||
where
|
||||
p.prokind = 'f'
|
||||
)
|
||||
select
|
||||
f.oid::int8 as id,
|
||||
n.nspname as schema,
|
||||
f.proname as name,
|
||||
l.lanname as language,
|
||||
case
|
||||
when l.lanname = 'internal' then ''
|
||||
else f.prosrc
|
||||
end as definition,
|
||||
case
|
||||
when l.lanname = 'internal' then f.prosrc
|
||||
else pg_get_functiondef(f.oid)
|
||||
end as complete_statement,
|
||||
coalesce(f_args.args, '[]') as args,
|
||||
pg_get_function_arguments(f.oid) as argument_types,
|
||||
pg_get_function_identity_arguments(f.oid) as identity_argument_types,
|
||||
f.prorettype::int8 as return_type_id,
|
||||
pg_get_function_result(f.oid) as return_type,
|
||||
nullif(rt.typrelid::int8, 0) as return_type_relation_id,
|
||||
f.proretset as is_set_returning_function,
|
||||
case
|
||||
when f.provolatile = 'i' then 'IMMUTABLE'
|
||||
when f.provolatile = 's' then 'STABLE'
|
||||
when f.provolatile = 'v' then 'VOLATILE'
|
||||
end as behavior,
|
||||
f.prosecdef as security_definer,
|
||||
f_config.config_params as config_params
|
||||
from
|
||||
functions f
|
||||
left join pg_namespace n on f.pronamespace = n.oid
|
||||
left join pg_language l on f.prolang = l.oid
|
||||
left join pg_type rt on rt.oid = f.prorettype
|
||||
left join (
|
||||
select
|
||||
oid,
|
||||
jsonb_object_agg(param, value) filter (where param is not null) as config_params
|
||||
from
|
||||
(
|
||||
select
|
||||
oid,
|
||||
(string_to_array(unnest(proconfig), '='))[1] as param,
|
||||
(string_to_array(unnest(proconfig), '='))[2] as value
|
||||
from
|
||||
functions
|
||||
) as t
|
||||
group by
|
||||
oid
|
||||
) f_config on f_config.oid = f.oid
|
||||
left join (
|
||||
select
|
||||
oid,
|
||||
jsonb_agg(jsonb_build_object(
|
||||
'mode', t2.mode,
|
||||
'name', name,
|
||||
'type_id', type_id,
|
||||
'has_default', has_default
|
||||
)) as args
|
||||
from
|
||||
(
|
||||
select
|
||||
oid,
|
||||
unnest(arg_modes) as mode,
|
||||
unnest(arg_names) as name,
|
||||
unnest(arg_types)::int8 as type_id,
|
||||
unnest(arg_has_defaults) as has_default
|
||||
from
|
||||
functions
|
||||
) as t1,
|
||||
lateral (
|
||||
select
|
||||
case
|
||||
when t1.mode = 'i' then 'in'
|
||||
when t1.mode = 'o' then 'out'
|
||||
when t1.mode = 'b' then 'inout'
|
||||
when t1.mode = 'v' then 'variadic'
|
||||
else 'table'
|
||||
end as mode
|
||||
) as t2
|
||||
group by
|
||||
t1.oid
|
||||
) f_args on f_args.oid = f.oid
|
||||
`
|
||||
Reference in new issue
Block a user