@@ -3,6 +3,18 @@ import { DEFAULT_SYSTEM_SCHEMAS } from './constants'
33import { functionsSql } from './sql'
44import { PostgresMetaResult , PostgresFunction } from './types'
55
6+ type FunctionInputs = {
7+ name : string
8+ definition : string
9+ args ?: string [ ]
10+ behavior ?: 'IMMUTABLE' | 'STABLE' | 'VOLATILE'
11+ config_params ?: { [ key : string ] : string }
12+ schema ?: string
13+ language ?: string
14+ return_type ?: string
15+ security_definer ?: boolean
16+ }
17+
618export default class PostgresMetaFunctions {
719 query : ( sql : string ) => Promise < PostgresMetaResult < any > >
820
@@ -53,31 +65,7 @@ export default class PostgresMetaFunctions {
5365 return { data : data [ 0 ] , error }
5466 }
5567 } else if ( name && schema && args ) {
56- const sql = `${ enrichedFunctionsSql } JOIN pg_proc AS p ON id = p.oid WHERE schema = ${ literal (
57- schema
58- ) } AND name = ${ literal ( name ) } AND p.proargtypes::text = ${
59- args . length
60- ? `(
61- SELECT STRING_AGG(type_oid::text, ' ') FROM (
62- SELECT (
63- split_args.arr[
64- array_length(
65- split_args.arr,
66- 1
67- )
68- ]::regtype::oid
69- ) AS type_oid FROM (
70- SELECT STRING_TO_ARRAY(
71- UNNEST(
72- ARRAY[${ args . map ( literal ) } ]
73- ),
74- ' '
75- ) AS arr
76- ) AS split_args
77- ) args
78- );`
79- : literal ( '' )
80- } `
68+ const sql = this . generateRetrieveFunctionSql ( { name, schema, args } )
8169 const { data, error } = await this . query ( sql )
8270 if ( error ) {
8371 return { data, error }
@@ -106,32 +94,18 @@ export default class PostgresMetaFunctions {
10694 behavior = 'VOLATILE' ,
10795 security_definer = false ,
10896 config_params = { } ,
109- } : {
110- name : string
111- schema ?: string
112- args ?: string [ ]
113- definition : string
114- return_type ?: string
115- language ?: string
116- behavior ?: 'IMMUTABLE' | 'STABLE' | 'VOLATILE'
117- security_definer ?: boolean
118- config_params ?: { [ key : string ] : string }
119- } ) : Promise < PostgresMetaResult < PostgresFunction > > {
120- const sql = `
121- CREATE FUNCTION ${ ident ( schema ) } .${ ident ( name ) } (${ args . join ( ', ' ) } )
122- RETURNS ${ return_type }
123- AS ${ literal ( definition ) }
124- LANGUAGE ${ language }
125- ${ behavior }
126- ${ security_definer ? 'SECURITY DEFINER' : 'SECURITY INVOKER' }
127- ${ Object . entries ( config_params )
128- . map (
129- ( [ param , value ] ) =>
130- `SET ${ param } ${ value [ 0 ] === 'FROM CURRENT' ? 'FROM CURRENT' : 'TO ' + value } `
131- )
132- . join ( '\n' ) }
133- RETURNS NULL ON NULL INPUT;
134- `
97+ } : FunctionInputs ) : Promise < PostgresMetaResult < PostgresFunction > > {
98+ const sql = this . generateCreateFunctionSql ( {
99+ name,
100+ schema,
101+ args,
102+ definition,
103+ return_type,
104+ language,
105+ behavior,
106+ security_definer,
107+ config_params,
108+ } )
135109 const { error } = await this . query ( sql )
136110 if ( error ) {
137111 return { data : null , error }
@@ -144,36 +118,78 @@ export default class PostgresMetaFunctions {
144118 {
145119 name,
146120 schema,
121+ definition,
147122 } : {
148123 name ?: string
149124 schema ?: string
125+ definition ?: string
150126 }
151127 ) : Promise < PostgresMetaResult < PostgresFunction > > {
152- const { data : old , error : retrieveError } = await this . retrieve ( { id } )
153- if ( retrieveError ) {
154- return { data : null , error : retrieveError }
128+ const { data : currentFunc , error } = await this . retrieve ( { id } )
129+ if ( error ) {
130+ return { data : null , error }
155131 }
156132
133+ const updateDefinitionSql = typeof definition === 'string' ? this . generateCreateFunctionSql (
134+ { ...currentFunc ! , definition } ,
135+ { replace : true }
136+ ) : ''
137+
138+ const retrieveFunctionSql = this . generateRetrieveFunctionSql (
139+ {
140+ schema : currentFunc ! . schema ,
141+ name : currentFunc ! . name ,
142+ args : currentFunc ! . argument_types . split ( ', ' ) ,
143+ } ,
144+ { terminateCommand : false }
145+ )
146+
157147 const updateNameSql =
158- name && name !== old ! . name
159- ? `ALTER FUNCTION ${ ident ( old ! . schema ) } .${ ident ( old ! . name ) } (${
160- old ! . argument_types
148+ name && name !== currentFunc ! . name
149+ ? `ALTER FUNCTION ${ ident ( currentFunc ! . schema ) } .${ ident ( currentFunc ! . name ) } (${
150+ currentFunc ! . argument_types
161151 } ) RENAME TO ${ ident ( name ) } ;`
162152 : ''
163153
164154 const updateSchemaSql =
165- schema && schema !== old ! . schema
166- ? `ALTER FUNCTION ${ ident ( old ! . schema ) } .${ ident ( name || old ! . name ) } (${
167- old ! . argument_types
155+ schema && schema !== currentFunc ! . schema
156+ ? `ALTER FUNCTION ${ ident ( currentFunc ! . schema ) } .${ ident ( name || currentFunc ! . name ) } (${
157+ currentFunc ! . argument_types
168158 } ) SET SCHEMA ${ ident ( schema ) } ;`
169159 : ''
170160
171- const sql = `BEGIN; ${ updateNameSql } ${ updateSchemaSql } COMMIT;`
161+ const sql = `
162+ DO LANGUAGE plpgsql $$
163+ DECLARE
164+ function record;
165+ BEGIN
166+ IF ${ typeof definition === 'string' ? 'TRUE' : 'FALSE' } THEN
167+ ${ updateDefinitionSql }
172168
173- const { error } = await this . query ( sql )
174- if ( error ) {
175- return { data : null , error }
169+ ${ retrieveFunctionSql } INTO function;
170+
171+ IF function.id != ${ id } THEN
172+ RAISE EXCEPTION 'Cannot find function "${ currentFunc ! . schema } "."${ currentFunc ! . name } "(${
173+ currentFunc ! . argument_types
174+ } )';
175+ END IF;
176+ END IF;
177+
178+ ${ updateNameSql }
179+
180+ ${ updateSchemaSql }
181+ END;
182+ $$;
183+ `
184+
185+ {
186+ const { error } = await this . query ( sql )
187+
188+ if ( error ) {
189+ return { data : null , error }
190+ }
176191 }
192+
177193 return await this . retrieve ( { id } )
178194 }
179195
@@ -196,6 +212,84 @@ export default class PostgresMetaFunctions {
196212 }
197213 return { data : func ! , error : null }
198214 }
215+
216+ private generateCreateFunctionSql (
217+ {
218+ name,
219+ schema,
220+ args,
221+ argument_types,
222+ definition,
223+ return_type,
224+ language,
225+ behavior,
226+ security_definer,
227+ config_params,
228+ } : Partial < Omit < FunctionInputs , 'config_params' > & PostgresFunction > ,
229+ { replace = false , terminateCommand = true } = { }
230+ ) : string {
231+ return `
232+ CREATE ${ replace ? 'OR REPLACE' : '' } FUNCTION ${ ident ( schema ! ) } .${ ident ( name ! ) } (${
233+ argument_types || args ?. join ( ', ' ) || ''
234+ } )
235+ RETURNS ${ return_type }
236+ AS ${ literal ( definition ) }
237+ LANGUAGE ${ language }
238+ ${ behavior }
239+ CALLED ON NULL INPUT
240+ ${ security_definer ? 'SECURITY DEFINER' : 'SECURITY INVOKER' }
241+ ${
242+ config_params
243+ ? Object . entries ( config_params )
244+ . map (
245+ ( [ param , value ] ) =>
246+ `SET ${ param } ${ value [ 0 ] === 'FROM CURRENT' ? 'FROM CURRENT' : 'TO ' + value } `
247+ )
248+ . join ( '\n' )
249+ : ''
250+ }
251+ ${ terminateCommand ? ';' : '' }
252+ `
253+ }
254+
255+ private generateRetrieveFunctionSql (
256+ {
257+ schema,
258+ name,
259+ args,
260+ } : {
261+ schema : string
262+ name : string
263+ args : string [ ]
264+ } ,
265+ { terminateCommand = true } = { }
266+ ) : string {
267+ return `${ enrichedFunctionsSql } JOIN pg_proc AS p ON id = p.oid WHERE schema = ${ literal (
268+ schema
269+ ) } AND name = ${ literal ( name ) } AND p.proargtypes::text = ${
270+ args . length
271+ ? `(
272+ SELECT STRING_AGG(type_oid::text, ' ') FROM (
273+ SELECT (
274+ split_args.arr[
275+ array_length(
276+ split_args.arr,
277+ 1
278+ )
279+ ]::regtype::oid
280+ ) AS type_oid FROM (
281+ SELECT STRING_TO_ARRAY(
282+ UNNEST(
283+ ARRAY[${ args . map ( literal ) } ]
284+ ),
285+ ' '
286+ ) AS arr
287+ ) AS split_args
288+ ) args
289+ ) ${ terminateCommand ? ';' : '' } `
290+ : literal ( '' )
291+ } `
292+ }
199293}
200294
201295const enrichedFunctionsSql = `
0 commit comments