| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- 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<ReturnType<typeof createTestDatabase>>) => Promise<void>
- ) => {
- 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<Number>,
- "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<Number>,
- "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<Number>,
- "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<Number>,
- "identity_argument_types": "",
- "is_set_returning_function": true,
- "language": "sql",
- "name": "function_returning_set_of_rows",
- "return_type": "SETOF users",
- "return_type_id": Any<Number>,
- "return_type_relation_id": Any<Number>,
- "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)
- }
- )
|