import { afterAll, expect, test } from 'vitest' import pgMeta, { safeSql } from '../src/index' import type { PGFunction, PGSavedFunction } from '../src/pg-meta-functions' import { cleanupRoot, createTestDatabase } from './db/utils' // Test fixtures originate from `executeQuery` results that match the // API/database boundary; brand the raw-SQL fields so they satisfy // `update`/`remove` parameter types. const asSavedFunction = (fn: PGFunction): PGSavedFunction => fn as unknown as PGSavedFunction afterAll(async () => { await cleanupRoot() }) const withTestDatabase = ( name: string, fn: (db: Awaited>) => Promise ) => { test(name, async () => { const db = await createTestDatabase() try { await fn(db) } finally { await db.cleanup() } }) } withTestDatabase('list functions', async ({ executeQuery }) => { const { sql, zod } = pgMeta.functions.list() const res = zod.parse(await executeQuery(sql)) // Test for the 'add' function created in init.sql const addFunction = res.find(({ name }) => name === 'add') expect(addFunction).toMatchInlineSnapshot( { id: expect.any(Number) }, ` { "args": [ { "has_default": false, "mode": "in", "name": "", "type_id": 23, }, { "has_default": false, "mode": "in", "name": "", "type_id": 23, }, ], "argument_types": "integer, integer", "behavior": "IMMUTABLE", "complete_statement": "CREATE OR REPLACE FUNCTION public.add(integer, integer) RETURNS integer LANGUAGE sql IMMUTABLE STRICT AS $function$select $1 + $2;$function$ ", "config_params": null, "definition": "select $1 + $2;", "id": Any, "identity_argument_types": "integer, integer", "is_set_returning_function": false, "language": "sql", "name": "add", "return_type": "integer", "return_type_id": 23, "return_type_relation_id": null, "schema": "public", "security_definer": false, } ` ) }) withTestDatabase('list functions with included schemas', async ({ executeQuery }) => { const { sql, zod } = pgMeta.functions.list({ includedSchemas: ['public'], }) const res = zod.parse(await executeQuery(sql)) expect(res.length).toBeGreaterThan(0) res.forEach((func) => { expect(func.schema).toBe('public') }) }) withTestDatabase('list functions with excluded schemas', async ({ executeQuery }) => { const { sql, zod } = pgMeta.functions.list({ excludedSchemas: ['public'], }) const res = zod.parse(await executeQuery(sql)) res.forEach((func) => { expect(func.schema).not.toBe('public') }) }) withTestDatabase( 'list functions with excluded schemas and include System Schemas', async ({ executeQuery }) => { const { sql, zod } = pgMeta.functions.list({ excludedSchemas: ['public'], includeSystemSchemas: true, }) const res = zod.parse(await executeQuery(sql)) expect(res.length).toBeGreaterThan(0) res.forEach((func) => { expect(func.schema).not.toBe('public') }) } ) withTestDatabase('retrieve, create, update, delete', async ({ executeQuery }) => { // Create function const { sql: createSql } = pgMeta.functions.create({ name: 'test_func', schema: 'public', args: [safeSql`a int2`, safeSql`b int2`], definition: 'select a + b', return_type: safeSql`integer`, language: 'sql', behavior: 'STABLE', security_definer: true, config_params: { search_path: safeSql`hooks, auth`, role: safeSql`postgres` }, }) await executeQuery(createSql) // // Retrieve function const { sql: retrieveSql, zod: retrieveZod } = pgMeta.functions.retrieve({ name: 'test_func', schema: 'public', args: ['a int2', 'b int2'], }) const retrieve = await executeQuery(retrieveSql) const res = retrieveZod.parse(retrieve[0]) const functionId = res!.id expect({ data: res, error: null }).toMatchInlineSnapshot( { data: { id: expect.any(Number) } }, ` { "data": { "args": [ { "has_default": false, "mode": "in", "name": "a", "type_id": 21, }, { "has_default": false, "mode": "in", "name": "b", "type_id": 21, }, ], "argument_types": "a smallint, b smallint", "behavior": "STABLE", "complete_statement": "CREATE OR REPLACE FUNCTION public.test_func(a smallint, b smallint) RETURNS integer LANGUAGE sql STABLE SECURITY DEFINER SET search_path TO 'hooks', 'auth' SET role TO 'postgres' AS $function$select a + b$function$ ", "config_params": { "role": "postgres", "search_path": "hooks, auth", }, "definition": "select a + b", "id": Any, "identity_argument_types": "a smallint, b smallint", "is_set_returning_function": false, "language": "sql", "name": "test_func", "return_type": "integer", "return_type_id": 23, "return_type_relation_id": null, "schema": "public", "security_definer": true, }, "error": null, } ` ) // create test_schema to move the function into: const { sql: createSchemaSql } = pgMeta.schemas.create({ name: 'test_schema' }) await executeQuery(createSchemaSql) const { sql: updateSql } = pgMeta.functions.update(asSavedFunction(res!), { name: 'test_func_renamed', schema: 'test_schema', definition: 'select b - a', }) await executeQuery(updateSql) const { sql: retrieveRenamedSql } = pgMeta.functions.retrieve({ id: functionId }) const retrieveRenamed = await executeQuery(retrieveRenamedSql) const resUpdated = retrieveZod.parse(retrieveRenamed[0]) expect({ data: resUpdated, error: null }).toMatchInlineSnapshot( { data: { id: expect.any(Number) } }, ` { "data": { "args": [ { "has_default": false, "mode": "in", "name": "a", "type_id": 21, }, { "has_default": false, "mode": "in", "name": "b", "type_id": 21, }, ], "argument_types": "a smallint, b smallint", "behavior": "STABLE", "complete_statement": "CREATE OR REPLACE FUNCTION test_schema.test_func_renamed(a smallint, b smallint) RETURNS integer LANGUAGE sql STABLE SECURITY DEFINER SET role TO 'postgres' SET search_path TO 'hooks', 'auth' AS $function$select b - a$function$ ", "config_params": { "role": "postgres", "search_path": "hooks, auth", }, "definition": "select b - a", "id": Any, "identity_argument_types": "a smallint, b smallint", "is_set_returning_function": false, "language": "sql", "name": "test_func_renamed", "return_type": "integer", "return_type_id": 23, "return_type_relation_id": null, "schema": "test_schema", "security_definer": true, }, "error": null, } ` ) // Remove function const { sql: removeSql } = pgMeta.functions.remove(asSavedFunction(resUpdated!)) await executeQuery(removeSql) // Verify function is removed const { sql: verifyRemoveSql } = pgMeta.functions.retrieve({ id: functionId }) const result = await executeQuery(verifyRemoveSql) expect(result).toHaveLength(0) }) withTestDatabase('retrieve set-returning function', async ({ executeQuery }) => { // Retrieve function const { sql: retrieveSql, zod: retrieveZod } = pgMeta.functions.retrieve({ schema: 'public', name: 'function_returning_set_of_rows', args: [], }) const retrieve = await executeQuery(retrieveSql) const res = retrieveZod.parse(retrieve[0]) expect(res).toMatchInlineSnapshot( { id: expect.any(Number), return_type_id: expect.any(Number), return_type_relation_id: expect.any(Number), }, ` { "args": [], "argument_types": "", "behavior": "STABLE", "complete_statement": "CREATE OR REPLACE FUNCTION public.function_returning_set_of_rows() RETURNS SETOF users LANGUAGE sql STABLE AS $function$ select * from public.users; $function$ ", "config_params": null, "definition": " select * from public.users; ", "id": Any, "identity_argument_types": "", "is_set_returning_function": true, "language": "sql", "name": "function_returning_set_of_rows", "return_type": "SETOF users", "return_type_id": Any, "return_type_relation_id": Any, "schema": "public", "security_definer": false, } ` ) }) withTestDatabase('create function with various config_params values', async ({ executeQuery }) => { // Set initial application_name for consistent testing await executeQuery("SET application_name = 'current-app-name'") const { sql: createSql1 } = pgMeta.functions.create({ name: 'test_func_config_1', schema: 'public', definition: 'select 1', return_type: safeSql`integer`, language: 'sql', config_params: { search_path: safeSql`''`, // Quoted empty string application_name: 'FROM CURRENT', // Special syntax: SET param FROM CURRENT work_mem: safeSql`'8MB'`, // Regular syntax: SET param TO value }, }) await executeQuery(createSql1) // Verify the function was created correctly const { sql: retrieveSql1, zod: retrieveZod } = pgMeta.functions.retrieve({ name: 'test_func_config_1', schema: 'public', args: [], }) const result1 = retrieveZod.parse((await executeQuery(retrieveSql1))[0]) expect(result1).toBeDefined() expect(result1!.config_params).toEqual({ search_path: '""', application_name: 'current-app-name', work_mem: '8MB', }) // Clean up const { sql: removeSql1 } = pgMeta.functions.remove(asSavedFunction(result1!)) await executeQuery(removeSql1) }) withTestDatabase( 'create function with namespaced custom GUC config_params', async ({ executeQuery }) => { const { sql: createSql } = pgMeta.functions.create({ name: 'test_func_namespaced_guc', schema: 'public', definition: 'select 1', return_type: safeSql`integer`, language: 'sql', config_params: { 'app.jwt_secret': safeSql`'top-secret'`, }, }) await executeQuery(createSql) const { sql: retrieveSql, zod: retrieveZod } = pgMeta.functions.retrieve({ name: 'test_func_namespaced_guc', schema: 'public', args: [], }) const result = retrieveZod.parse((await executeQuery(retrieveSql))[0]) expect(result).toBeDefined() expect(result!.config_params).toEqual({ 'app.jwt_secret': 'top-secret', }) const { sql: removeSql } = pgMeta.functions.remove(asSavedFunction(result!)) await executeQuery(removeSql) } )