diff --git a/apps/studio/components/interfaces/Auth/Hooks/FunctionSelector.tsx b/apps/studio/components/interfaces/Auth/Hooks/FunctionSelector.tsx
index e2db6e5bea3..f0d598dda3e 100644
--- a/apps/studio/components/interfaces/Auth/Hooks/FunctionSelector.tsx
+++ b/apps/studio/components/interfaces/Auth/Hooks/FunctionSelector.tsx
@@ -71,9 +71,11 @@ const FunctionSelector = ({
Failed to load functions
+
Error: {error.message}
+
diff --git a/apps/studio/components/interfaces/Database/Functions/CreateFunction/index.tsx b/apps/studio/components/interfaces/Database/Functions/CreateFunction/index.tsx
index 57ef8bbf90e..c8db848536d 100644
--- a/apps/studio/components/interfaces/Database/Functions/CreateFunction/index.tsx
+++ b/apps/studio/components/interfaces/Database/Functions/CreateFunction/index.tsx
@@ -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,
diff --git a/apps/studio/components/interfaces/Database/Functions/DeleteFunction.tsx b/apps/studio/components/interfaces/Database/Functions/DeleteFunction.tsx
index 0120b0caf11..057e373ec2f 100644
--- a/apps/studio/components/interfaces/Database/Functions/DeleteFunction.tsx
+++ b/apps/studio/components/interfaces/Database/Functions/DeleteFunction.tsx
@@ -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={
<>
This will delete the function{' '}
diff --git a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx
index 8fe6fe053fd..a2bcebb700a 100644
--- a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx
+++ b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx
@@ -86,7 +86,7 @@ const FunctionsList = ({
})
if (isLoading) return
- if (isError)
+ if (isError) return
return (
<>
diff --git a/apps/studio/components/ui/CodeEditor/Providers/PgSQLCompletionProvider.ts b/apps/studio/components/ui/CodeEditor/Providers/PgSQLCompletionProvider.ts
index 599c67c4e04..f7bbbe4bce4 100644
--- a/apps/studio/components/ui/CodeEditor/Providers/PgSQLCompletionProvider.ts
+++ b/apps/studio/components/ui/CodeEditor/Providers/PgSQLCompletionProvider.ts
@@ -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) {
items.push({
label: x.name,
kind: monaco.languages.CompletionItemKind.Function,
- detail: x.result_type,
- documentation: x.description,
+ detail: x.return_type,
insertText: x.name,
})
})
diff --git a/apps/studio/components/ui/CodeEditor/Providers/PgSQLSignatureHelpProvider.ts b/apps/studio/components/ui/CodeEditor/Providers/PgSQLSignatureHelpProvider.ts
index 829206810a6..03df0fc3d4c 100644
--- a/apps/studio/components/ui/CodeEditor/Providers/PgSQLSignatureHelpProvider.ts
+++ b/apps/studio/components/ui/CodeEditor/Providers/PgSQLSignatureHelpProvider.ts
@@ -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 }
}),
})
diff --git a/apps/studio/data/database-functions/database-functions-create-mutation.ts b/apps/studio/data/database-functions/database-functions-create-mutation.ts
index 4481062a688..60553f8a86b 100644
--- a/apps/studio/data/database-functions/database-functions-create-mutation.ts
+++ b/apps/studio/data/database-functions/database-functions-create-mutation.ts
@@ -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
- }
+ payload: z.infer
}
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
}
type DatabaseFunctionCreateData = Awaited>
@@ -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) {
diff --git a/apps/studio/data/database-functions/database-functions-delete-mutation.ts b/apps/studio/data/database-functions/database-functions-delete-mutation.ts
index 02714fa3f36..c37e9a973cb 100644
--- a/apps/studio/data/database-functions/database-functions-delete-mutation.ts
+++ b/apps/studio/data/database-functions/database-functions-delete-mutation.ts
@@ -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
}
type DatabaseFunctionDeleteData = Awaited>
@@ -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) {
diff --git a/apps/studio/data/database-functions/database-functions-query.ts b/apps/studio/data/database-functions/database-functions-query.ts
index c0729e02cb4..a6e7148ca79 100644
--- a/apps/studio/data/database-functions/database-functions-query.ts
+++ b/apps/studio/data/database-functions/database-functions-query.ts
@@ -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
-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>
+export type DatabaseFunctionsData = z.infer
export type DatabaseFunctionsError = ResponseError
-export const useDatabaseFunctionsQuery = (
+export const useDatabaseFunctionsQuery = <
+ TData extends DatabaseFunctionsData = DatabaseFunctionsData,
+>(
{ projectRef, connectionString }: DatabaseFunctionsVariables,
- {
- enabled = true,
- ...options
- }: UseQueryOptions = {}
-) =>
- useQuery(
- databaseFunctionsKeys.list(projectRef),
- ({ signal }) => getDatabaseFunctions({ projectRef, connectionString }, signal),
+ options: UseQueryOptions = {}
+) => {
+ return useExecuteSqlQuery(
{
- enabled: enabled && typeof projectRef !== 'undefined',
+ projectRef,
+ connectionString,
+ sql: pgMetaFunctionsList.sql,
+ queryKey: ['functions-list'],
+ },
+ {
+ select(data) {
+ return (data as any)?.result ?? []
+ },
...options,
}
)
+}
diff --git a/apps/studio/data/database-functions/database-functions-update-mutation.ts b/apps/studio/data/database-functions/database-functions-update-mutation.ts
index 953f036c3e5..134d8ff7e81 100644
--- a/apps/studio/data/database-functions/database-functions-update-mutation.ts
+++ b/apps/studio/data/database-functions/database-functions-update-mutation.ts
@@ -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
}
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
}
type DatabaseFunctionUpdateData = Awaited>
@@ -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) {
diff --git a/apps/studio/data/database-functions/keys.ts b/apps/studio/data/database-functions/keys.ts
deleted file mode 100644
index 108e8567eee..00000000000
--- a/apps/studio/data/database-functions/keys.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const databaseFunctionsKeys = {
- list: (projectRef: string | undefined) => ['projects', projectRef, 'database-functions'] as const,
-}
diff --git a/apps/studio/data/database-roles/database-roles-query.ts b/apps/studio/data/database-roles/database-roles-query.ts
index e347af6f236..9a5569b07ae 100644
--- a/apps/studio/data/database-roles/database-roles-query.ts
+++ b/apps/studio/data/database-roles/database-roles-query.ts
@@ -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
const pgMetaRolesList = pgMeta.roles.list()
export type DatabaseRolesData = z.infer
-export type DatabaseRolesError = unknown
+export type DatabaseRolesError = ExecuteSqlError
export const useDatabaseRolesQuery = (
{ projectRef, connectionString }: DatabaseRolesVariables,
diff --git a/apps/studio/data/database/constraints-query.ts b/apps/studio/data/database/constraints-query.ts
index 2c7db7134e7..ccf0ae456da 100644
--- a/apps/studio/data/database/constraints-query.ts
+++ b/apps/studio/data/database/constraints-query.ts
@@ -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 = (
{ projectRef, connectionString, schema, table }: TableConstraintsVariables,
diff --git a/apps/studio/data/database/database-size-query.ts b/apps/studio/data/database/database-size-query.ts
index 3676d47365d..4a51c4b5e19 100644
--- a/apps/studio/data/database/database-size-query.ts
+++ b/apps/studio/data/database/database-size-query.ts
@@ -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 = (
{ projectRef, connectionString }: DatabaseSizeVariables,
diff --git a/apps/studio/data/database/entity-definitions-query.ts b/apps/studio/data/database/entity-definitions-query.ts
index 9ee7702057f..92ceb7e5069 100644
--- a/apps/studio/data/database/entity-definitions-query.ts
+++ b/apps/studio/data/database/entity-definitions-query.ts
@@ -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,
diff --git a/apps/studio/data/database/foreign-key-constraints-query.ts b/apps/studio/data/database/foreign-key-constraints-query.ts
index 064bf0fa07b..3c348b11ec2 100644
--- a/apps/studio/data/database/foreign-key-constraints-query.ts
+++ b/apps/studio/data/database/foreign-key-constraints-query.ts
@@ -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,
diff --git a/apps/studio/data/database/functions-query.ts b/apps/studio/data/database/functions-query.ts
deleted file mode 100644
index e4607d7b878..00000000000
--- a/apps/studio/data/database/functions-query.ts
+++ /dev/null
@@ -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 = (
- { projectRef, connectionString }: FunctionsVariables,
- options: UseQueryOptions = {}
-) => {
- 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,
- }
- )
-}
diff --git a/apps/studio/data/database/indexes-query.ts b/apps/studio/data/database/indexes-query.ts
index e45702479cc..c6f066caa36 100644
--- a/apps/studio/data/database/indexes-query.ts
+++ b/apps/studio/data/database/indexes-query.ts
@@ -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 = (
{ schema, projectRef, connectionString }: IndexesVariables,
diff --git a/apps/studio/data/database/keywords-query.ts b/apps/studio/data/database/keywords-query.ts
index 3ada32f254c..7cfc9aa483f 100644
--- a/apps/studio/data/database/keywords-query.ts
+++ b/apps/studio/data/database/keywords-query.ts
@@ -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 = (
{ projectRef, connectionString }: KeywordsVariables,
diff --git a/apps/studio/data/database/max-connections-query.ts b/apps/studio/data/database/max-connections-query.ts
index 876780b3df3..87a51ea04ac 100644
--- a/apps/studio/data/database/max-connections-query.ts
+++ b/apps/studio/data/database/max-connections-query.ts
@@ -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 = (
{ projectRef, connectionString }: MaxConnectionsVariables,
diff --git a/apps/studio/data/database/migrations-query.ts b/apps/studio/data/database/migrations-query.ts
index 619cb0fdd41..aff79487eca 100644
--- a/apps/studio/data/database/migrations-query.ts
+++ b/apps/studio/data/database/migrations-query.ts
@@ -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 = (
{ projectRef, connectionString }: MigrationsVariables,
@@ -36,7 +36,7 @@ export const useMigrationsQuery = {
+ handleError: (error) => {
if (
error.message.includes('relation "supabase_migrations.schema_migrations" does not exist')
) {
diff --git a/apps/studio/data/database/schemas-query.ts b/apps/studio/data/database/schemas-query.ts
index e93928dabc1..911f8fc88fa 100644
--- a/apps/studio/data/database/schemas-query.ts
+++ b/apps/studio/data/database/schemas-query.ts
@@ -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
const pgMetaSchemasList = pgMeta.schemas.list()
export type SchemasData = z.infer
-export type SchemasError = unknown
+export type SchemasError = ExecuteSqlError
export const useSchemasQuery = (
{ projectRef, connectionString }: SchemasVariables,
diff --git a/apps/studio/data/database/table-columns-query.ts b/apps/studio/data/database/table-columns-query.ts
index d1cae3c3159..b680423d76c 100644
--- a/apps/studio/data/database/table-columns-query.ts
+++ b/apps/studio/data/database/table-columns-query.ts
@@ -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 = (
{ projectRef, connectionString, table, schema }: TableColumnsVariables,
diff --git a/apps/studio/data/database/table-definition-query.ts b/apps/studio/data/database/table-definition-query.ts
index 3e3ed730b4e..9f29d08c6a7 100644
--- a/apps/studio/data/database/table-definition-query.ts
+++ b/apps/studio/data/database/table-definition-query.ts
@@ -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 = (
{ projectRef, connectionString, schema, name }: TableDefinitionVariables,
diff --git a/apps/studio/data/database/view-definition-query.ts b/apps/studio/data/database/view-definition-query.ts
index 39e3b96317b..6fb09d538fc 100644
--- a/apps/studio/data/database/view-definition-query.ts
+++ b/apps/studio/data/database/view-definition-query.ts
@@ -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 = (
{ projectRef, connectionString, schema, name }: ViewDefinitionVariables,
diff --git a/apps/studio/data/entity-types/entity-type-query.ts b/apps/studio/data/entity-types/entity-type-query.ts
index fd421fdd095..c650159ce52 100644
--- a/apps/studio/data/entity-types/entity-type-query.ts
+++ b/apps/studio/data/entity-types/entity-type-query.ts
@@ -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 = (
{ projectRef, connectionString, id, ...args }: EntityTypeVariables,
diff --git a/apps/studio/data/fdw/fdws-query.ts b/apps/studio/data/fdw/fdws-query.ts
index 8851d54d6ee..2d40500870b 100644
--- a/apps/studio/data/fdw/fdws-query.ts
+++ b/apps/studio/data/fdw/fdws-query.ts
@@ -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 = (
{ projectRef, connectionString }: FDWsVariables,
diff --git a/apps/studio/data/read-replicas/replica-lag-query.ts b/apps/studio/data/read-replicas/replica-lag-query.ts
index 423f7142754..9585e9eba42 100644
--- a/apps/studio/data/read-replicas/replica-lag-query.ts
+++ b/apps/studio/data/read-replicas/replica-lag-query.ts
@@ -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 = (
{ projectRef, connectionString, id }: ReplicationLagVariables,
diff --git a/apps/studio/data/sql/execute-sql-query.ts b/apps/studio/data/sql/execute-sql-query.ts
index b3aec849476..0d5bdbb4322 100644
--- a/apps/studio/data/sql/execute-sql-query.ts
+++ b/apps/studio/data/sql/execute-sql-query.ts
@@ -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>
-export type ExecuteSqlError = unknown
+export type ExecuteSqlError = ResponseError
export const useExecuteSqlQuery = (
{
diff --git a/apps/studio/data/table-rows/table-rows-count-query.ts b/apps/studio/data/table-rows/table-rows-count-query.ts
index 8f5c3e271e1..a95a7cd99b2 100644
--- a/apps/studio/data/table-rows/table-rows-count-query.ts
+++ b/apps/studio/data/table-rows/table-rows-count-query.ts
@@ -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 = (
{
diff --git a/apps/studio/data/table-rows/table-rows-query.ts b/apps/studio/data/table-rows/table-rows-query.ts
index cccb503f6d8..d04933191c9 100644
--- a/apps/studio/data/table-rows/table-rows-query.ts
+++ b/apps/studio/data/table-rows/table-rows-query.ts
@@ -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 = (
{ projectRef, connectionString, queryKey, table, impersonatedRole, ...args }: TableRowsVariables,
diff --git a/apps/studio/data/vault/vault-secrets-query.ts b/apps/studio/data/vault/vault-secrets-query.ts
index 8ac2377ee97..ff3bf827be8 100644
--- a/apps/studio/data/vault/vault-secrets-query.ts
+++ b/apps/studio/data/vault/vault-secrets-query.ts
@@ -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 = (
{ projectRef, connectionString }: VaultSecretsVariables,
diff --git a/apps/studio/pages/project/[ref]/database/functions.tsx b/apps/studio/pages/project/[ref]/database/functions.tsx
index 168eb7932e5..2883de5f38f 100644
--- a/apps/studio/pages/project/[ref]/database/functions.tsx
+++ b/apps/studio/pages/project/[ref]/database/functions.tsx
@@ -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()
+ const [selectedFunction, setSelectedFunction] = useState()
const [showCreateFunctionForm, setShowCreateFunctionForm] = useState(false)
const [showDeleteFunctionForm, setShowDeleteFunctionForm] = useState(false)
diff --git a/apps/studio/pages/project/[ref]/sql/[id].tsx b/apps/studio/pages/project/[ref]/sql/[id].tsx
index 87f616494ce..0c56b6b5f3a 100644
--- a/apps/studio/pages/project/[ref]/sql/[id].tsx
+++ b/apps/studio/pages/project/[ref]/sql/[id].tsx
@@ -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(() => {
diff --git a/packages/pg-meta/src/helpers.ts b/packages/pg-meta/src/helpers.ts
new file mode 100644
index 00000000000..4eadf84b438
--- /dev/null
+++ b/packages/pg-meta/src/helpers.ts
@@ -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 ''
+}
diff --git a/packages/pg-meta/src/index.ts b/packages/pg-meta/src/index.ts
index aaadfe36832..60761df8f85 100644
--- a/packages/pg-meta/src/index.ts
+++ b/packages/pg-meta/src/index.ts
@@ -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,
}
diff --git a/packages/pg-meta/src/pg-meta-functions.ts b/packages/pg-meta/src/pg-meta-functions.ts
new file mode 100644
index 00000000000..d2ffa10d6bb
--- /dev/null
+++ b/packages/pg-meta/src/pg-meta-functions.ts
@@ -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
+
+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
+
+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
+
+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
+
+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(),
+ }
+}
diff --git a/packages/pg-meta/src/sql/functions.ts b/packages/pg-meta/src/sql/functions.ts
new file mode 100644
index 00000000000..2afff047fc4
--- /dev/null
+++ b/packages/pg-meta/src/sql/functions.ts
@@ -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
+`