@@ -3,6 +3,18 @@ import { DEFAULT_SYSTEM_SCHEMAS } from './constants'
3
3
import { functionsSql } from './sql'
4
4
import { PostgresMetaResult , PostgresFunction } from './types'
5
5
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
+
6
18
export default class PostgresMetaFunctions {
7
19
query : ( sql : string ) => Promise < PostgresMetaResult < any > >
8
20
@@ -53,31 +65,7 @@ export default class PostgresMetaFunctions {
53
65
return { data : data [ 0 ] , error }
54
66
}
55
67
} 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 } )
81
69
const { data, error } = await this . query ( sql )
82
70
if ( error ) {
83
71
return { data, error }
@@ -106,32 +94,18 @@ export default class PostgresMetaFunctions {
106
94
behavior = 'VOLATILE' ,
107
95
security_definer = false ,
108
96
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
+ } )
135
109
const { error } = await this . query ( sql )
136
110
if ( error ) {
137
111
return { data : null , error }
@@ -144,36 +118,78 @@ export default class PostgresMetaFunctions {
144
118
{
145
119
name,
146
120
schema,
121
+ definition,
147
122
} : {
148
123
name ?: string
149
124
schema ?: string
125
+ definition ?: string
150
126
}
151
127
) : 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 }
155
131
}
156
132
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
+
157
147
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
161
151
} ) RENAME TO ${ ident ( name ) } ;`
162
152
: ''
163
153
164
154
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
168
158
} ) SET SCHEMA ${ ident ( schema ) } ;`
169
159
: ''
170
160
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 }
172
168
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
+ }
176
191
}
192
+
177
193
return await this . retrieve ( { id } )
178
194
}
179
195
@@ -196,6 +212,84 @@ export default class PostgresMetaFunctions {
196
212
}
197
213
return { data : func ! , error : null }
198
214
}
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
+ }
199
293
}
200
294
201
295
const enrichedFunctionsSql = `
0 commit comments