/src/postgres/src/backend/commands/aggregatecmds.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * aggregatecmds.c |
4 | | * |
5 | | * Routines for aggregate-manipulation commands |
6 | | * |
7 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
8 | | * Portions Copyright (c) 1994, Regents of the University of California |
9 | | * |
10 | | * |
11 | | * IDENTIFICATION |
12 | | * src/backend/commands/aggregatecmds.c |
13 | | * |
14 | | * DESCRIPTION |
15 | | * The "DefineAggregate" routine takes the parse tree and picks out the |
16 | | * appropriate arguments/flags, passing the results to the |
17 | | * "AggregateCreate" routine (in src/backend/catalog), which does the |
18 | | * actual catalog-munging. DefineAggregate also verifies the permission of |
19 | | * the user to execute the command. |
20 | | * |
21 | | *------------------------------------------------------------------------- |
22 | | */ |
23 | | #include "postgres.h" |
24 | | |
25 | | #include "catalog/namespace.h" |
26 | | #include "catalog/pg_aggregate.h" |
27 | | #include "catalog/pg_namespace.h" |
28 | | #include "catalog/pg_proc.h" |
29 | | #include "catalog/pg_type.h" |
30 | | #include "commands/defrem.h" |
31 | | #include "miscadmin.h" |
32 | | #include "parser/parse_type.h" |
33 | | #include "utils/acl.h" |
34 | | #include "utils/builtins.h" |
35 | | #include "utils/lsyscache.h" |
36 | | |
37 | | |
38 | | static char extractModify(DefElem *defel); |
39 | | |
40 | | |
41 | | /* |
42 | | * DefineAggregate |
43 | | * |
44 | | * "oldstyle" signals the old (pre-8.2) style where the aggregate input type |
45 | | * is specified by a BASETYPE element in the parameters. Otherwise, |
46 | | * "args" is a pair, whose first element is a list of FunctionParameter structs |
47 | | * defining the agg's arguments (both direct and aggregated), and whose second |
48 | | * element is an Integer node with the number of direct args, or -1 if this |
49 | | * isn't an ordered-set aggregate. |
50 | | * "parameters" is a list of DefElem representing the agg's definition clauses. |
51 | | */ |
52 | | ObjectAddress |
53 | | DefineAggregate(ParseState *pstate, |
54 | | List *name, |
55 | | List *args, |
56 | | bool oldstyle, |
57 | | List *parameters, |
58 | | bool replace) |
59 | 0 | { |
60 | 0 | char *aggName; |
61 | 0 | Oid aggNamespace; |
62 | 0 | AclResult aclresult; |
63 | 0 | char aggKind = AGGKIND_NORMAL; |
64 | 0 | List *transfuncName = NIL; |
65 | 0 | List *finalfuncName = NIL; |
66 | 0 | List *combinefuncName = NIL; |
67 | 0 | List *serialfuncName = NIL; |
68 | 0 | List *deserialfuncName = NIL; |
69 | 0 | List *mtransfuncName = NIL; |
70 | 0 | List *minvtransfuncName = NIL; |
71 | 0 | List *mfinalfuncName = NIL; |
72 | 0 | bool finalfuncExtraArgs = false; |
73 | 0 | bool mfinalfuncExtraArgs = false; |
74 | 0 | char finalfuncModify = 0; |
75 | 0 | char mfinalfuncModify = 0; |
76 | 0 | List *sortoperatorName = NIL; |
77 | 0 | List *supportfuncName = NIL; |
78 | 0 | TypeName *baseType = NULL; |
79 | 0 | TypeName *transType = NULL; |
80 | 0 | TypeName *mtransType = NULL; |
81 | 0 | int32 transSpace = 0; |
82 | 0 | int32 mtransSpace = 0; |
83 | 0 | char *initval = NULL; |
84 | 0 | char *minitval = NULL; |
85 | 0 | char *parallel = NULL; |
86 | 0 | int numArgs; |
87 | 0 | int numDirectArgs = 0; |
88 | 0 | oidvector *parameterTypes; |
89 | 0 | ArrayType *allParameterTypes; |
90 | 0 | ArrayType *parameterModes; |
91 | 0 | ArrayType *parameterNames; |
92 | 0 | List *parameterDefaults; |
93 | 0 | Oid variadicArgType; |
94 | 0 | Oid transTypeId; |
95 | 0 | Oid mtransTypeId = InvalidOid; |
96 | 0 | char transTypeType; |
97 | 0 | char mtransTypeType = 0; |
98 | 0 | char proparallel = PROPARALLEL_UNSAFE; |
99 | 0 | ListCell *pl; |
100 | | |
101 | | /* Convert list of names to a name and namespace */ |
102 | 0 | aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName); |
103 | | |
104 | | /* Check we have creation rights in target namespace */ |
105 | 0 | aclresult = object_aclcheck(NamespaceRelationId, aggNamespace, GetUserId(), ACL_CREATE); |
106 | 0 | if (aclresult != ACLCHECK_OK) |
107 | 0 | aclcheck_error(aclresult, OBJECT_SCHEMA, |
108 | 0 | get_namespace_name(aggNamespace)); |
109 | | |
110 | | /* Deconstruct the output of the aggr_args grammar production */ |
111 | 0 | if (!oldstyle) |
112 | 0 | { |
113 | 0 | Assert(list_length(args) == 2); |
114 | 0 | numDirectArgs = intVal(lsecond(args)); |
115 | 0 | if (numDirectArgs >= 0) |
116 | 0 | aggKind = AGGKIND_ORDERED_SET; |
117 | 0 | else |
118 | 0 | numDirectArgs = 0; |
119 | 0 | args = linitial_node(List, args); |
120 | 0 | } |
121 | | |
122 | | /* Examine aggregate's definition clauses */ |
123 | 0 | foreach(pl, parameters) |
124 | 0 | { |
125 | 0 | DefElem *defel = lfirst_node(DefElem, pl); |
126 | | |
127 | | /* |
128 | | * sfunc1, stype1, and initcond1 are accepted as obsolete spellings |
129 | | * for sfunc, stype, initcond. |
130 | | */ |
131 | 0 | if (strcmp(defel->defname, "sfunc") == 0) |
132 | 0 | transfuncName = defGetQualifiedName(defel); |
133 | 0 | else if (strcmp(defel->defname, "sfunc1") == 0) |
134 | 0 | transfuncName = defGetQualifiedName(defel); |
135 | 0 | else if (strcmp(defel->defname, "finalfunc") == 0) |
136 | 0 | finalfuncName = defGetQualifiedName(defel); |
137 | 0 | else if (strcmp(defel->defname, "combinefunc") == 0) |
138 | 0 | combinefuncName = defGetQualifiedName(defel); |
139 | 0 | else if (strcmp(defel->defname, "serialfunc") == 0) |
140 | 0 | serialfuncName = defGetQualifiedName(defel); |
141 | 0 | else if (strcmp(defel->defname, "deserialfunc") == 0) |
142 | 0 | deserialfuncName = defGetQualifiedName(defel); |
143 | 0 | else if (strcmp(defel->defname, "msfunc") == 0) |
144 | 0 | mtransfuncName = defGetQualifiedName(defel); |
145 | 0 | else if (strcmp(defel->defname, "minvfunc") == 0) |
146 | 0 | minvtransfuncName = defGetQualifiedName(defel); |
147 | 0 | else if (strcmp(defel->defname, "mfinalfunc") == 0) |
148 | 0 | mfinalfuncName = defGetQualifiedName(defel); |
149 | 0 | else if (strcmp(defel->defname, "finalfunc_extra") == 0) |
150 | 0 | finalfuncExtraArgs = defGetBoolean(defel); |
151 | 0 | else if (strcmp(defel->defname, "mfinalfunc_extra") == 0) |
152 | 0 | mfinalfuncExtraArgs = defGetBoolean(defel); |
153 | 0 | else if (strcmp(defel->defname, "finalfunc_modify") == 0) |
154 | 0 | finalfuncModify = extractModify(defel); |
155 | 0 | else if (strcmp(defel->defname, "mfinalfunc_modify") == 0) |
156 | 0 | mfinalfuncModify = extractModify(defel); |
157 | 0 | else if (strcmp(defel->defname, "sortop") == 0) |
158 | 0 | sortoperatorName = defGetQualifiedName(defel); |
159 | 0 | else if (strcmp(defel->defname, "support") == 0) |
160 | 0 | supportfuncName = defGetQualifiedName(defel); |
161 | 0 | else if (strcmp(defel->defname, "basetype") == 0) |
162 | 0 | baseType = defGetTypeName(defel); |
163 | 0 | else if (strcmp(defel->defname, "hypothetical") == 0) |
164 | 0 | { |
165 | 0 | if (defGetBoolean(defel)) |
166 | 0 | { |
167 | 0 | if (aggKind == AGGKIND_NORMAL) |
168 | 0 | ereport(ERROR, |
169 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
170 | 0 | errmsg("only ordered-set aggregates can be hypothetical"))); |
171 | 0 | aggKind = AGGKIND_HYPOTHETICAL; |
172 | 0 | } |
173 | 0 | } |
174 | 0 | else if (strcmp(defel->defname, "stype") == 0) |
175 | 0 | transType = defGetTypeName(defel); |
176 | 0 | else if (strcmp(defel->defname, "stype1") == 0) |
177 | 0 | transType = defGetTypeName(defel); |
178 | 0 | else if (strcmp(defel->defname, "sspace") == 0) |
179 | 0 | transSpace = defGetInt32(defel); |
180 | 0 | else if (strcmp(defel->defname, "mstype") == 0) |
181 | 0 | mtransType = defGetTypeName(defel); |
182 | 0 | else if (strcmp(defel->defname, "msspace") == 0) |
183 | 0 | mtransSpace = defGetInt32(defel); |
184 | 0 | else if (strcmp(defel->defname, "initcond") == 0) |
185 | 0 | initval = defGetString(defel); |
186 | 0 | else if (strcmp(defel->defname, "initcond1") == 0) |
187 | 0 | initval = defGetString(defel); |
188 | 0 | else if (strcmp(defel->defname, "minitcond") == 0) |
189 | 0 | minitval = defGetString(defel); |
190 | 0 | else if (strcmp(defel->defname, "parallel") == 0) |
191 | 0 | parallel = defGetString(defel); |
192 | 0 | else |
193 | 0 | ereport(WARNING, |
194 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
195 | 0 | errmsg("aggregate attribute \"%s\" not recognized", |
196 | 0 | defel->defname))); |
197 | 0 | } |
198 | | |
199 | | /* |
200 | | * make sure we have our required definitions |
201 | | */ |
202 | 0 | if (transType == NULL) |
203 | 0 | ereport(ERROR, |
204 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
205 | 0 | errmsg("aggregate stype must be specified"))); |
206 | 0 | if (transfuncName == NIL) |
207 | 0 | ereport(ERROR, |
208 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
209 | 0 | errmsg("aggregate sfunc must be specified"))); |
210 | | |
211 | | /* |
212 | | * if mtransType is given, mtransfuncName and minvtransfuncName must be as |
213 | | * well; if not, then none of the moving-aggregate options should have |
214 | | * been given. |
215 | | */ |
216 | 0 | if (mtransType != NULL) |
217 | 0 | { |
218 | 0 | if (mtransfuncName == NIL) |
219 | 0 | ereport(ERROR, |
220 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
221 | 0 | errmsg("aggregate msfunc must be specified when mstype is specified"))); |
222 | 0 | if (minvtransfuncName == NIL) |
223 | 0 | ereport(ERROR, |
224 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
225 | 0 | errmsg("aggregate minvfunc must be specified when mstype is specified"))); |
226 | 0 | } |
227 | 0 | else |
228 | 0 | { |
229 | 0 | if (mtransfuncName != NIL) |
230 | 0 | ereport(ERROR, |
231 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
232 | 0 | errmsg("aggregate msfunc must not be specified without mstype"))); |
233 | 0 | if (minvtransfuncName != NIL) |
234 | 0 | ereport(ERROR, |
235 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
236 | 0 | errmsg("aggregate minvfunc must not be specified without mstype"))); |
237 | 0 | if (mfinalfuncName != NIL) |
238 | 0 | ereport(ERROR, |
239 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
240 | 0 | errmsg("aggregate mfinalfunc must not be specified without mstype"))); |
241 | 0 | if (mtransSpace != 0) |
242 | 0 | ereport(ERROR, |
243 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
244 | 0 | errmsg("aggregate msspace must not be specified without mstype"))); |
245 | 0 | if (minitval != NULL) |
246 | 0 | ereport(ERROR, |
247 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
248 | 0 | errmsg("aggregate minitcond must not be specified without mstype"))); |
249 | 0 | } |
250 | | |
251 | | /* |
252 | | * Default values for modify flags can only be determined once we know the |
253 | | * aggKind. |
254 | | */ |
255 | 0 | if (finalfuncModify == 0) |
256 | 0 | finalfuncModify = (aggKind == AGGKIND_NORMAL) ? AGGMODIFY_READ_ONLY : AGGMODIFY_READ_WRITE; |
257 | 0 | if (mfinalfuncModify == 0) |
258 | 0 | mfinalfuncModify = (aggKind == AGGKIND_NORMAL) ? AGGMODIFY_READ_ONLY : AGGMODIFY_READ_WRITE; |
259 | | |
260 | | /* |
261 | | * look up the aggregate's input datatype(s). |
262 | | */ |
263 | 0 | if (oldstyle) |
264 | 0 | { |
265 | | /* |
266 | | * Old style: use basetype parameter. This supports aggregates of |
267 | | * zero or one input, with input type ANY meaning zero inputs. |
268 | | * |
269 | | * Historically we allowed the command to look like basetype = 'ANY' |
270 | | * so we must do a case-insensitive comparison for the name ANY. Ugh. |
271 | | */ |
272 | 0 | Oid aggArgTypes[1]; |
273 | |
|
274 | 0 | if (baseType == NULL) |
275 | 0 | ereport(ERROR, |
276 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
277 | 0 | errmsg("aggregate input type must be specified"))); |
278 | | |
279 | 0 | if (pg_strcasecmp(TypeNameToString(baseType), "ANY") == 0) |
280 | 0 | { |
281 | 0 | numArgs = 0; |
282 | 0 | aggArgTypes[0] = InvalidOid; |
283 | 0 | } |
284 | 0 | else |
285 | 0 | { |
286 | 0 | numArgs = 1; |
287 | 0 | aggArgTypes[0] = typenameTypeId(NULL, baseType); |
288 | 0 | } |
289 | 0 | parameterTypes = buildoidvector(aggArgTypes, numArgs); |
290 | 0 | allParameterTypes = NULL; |
291 | 0 | parameterModes = NULL; |
292 | 0 | parameterNames = NULL; |
293 | 0 | parameterDefaults = NIL; |
294 | 0 | variadicArgType = InvalidOid; |
295 | 0 | } |
296 | 0 | else |
297 | 0 | { |
298 | | /* |
299 | | * New style: args is a list of FunctionParameters (possibly zero of |
300 | | * 'em). We share functioncmds.c's code for processing them. |
301 | | */ |
302 | 0 | Oid requiredResultType; |
303 | |
|
304 | 0 | if (baseType != NULL) |
305 | 0 | ereport(ERROR, |
306 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
307 | 0 | errmsg("basetype is redundant with aggregate input type specification"))); |
308 | | |
309 | 0 | numArgs = list_length(args); |
310 | 0 | interpret_function_parameter_list(pstate, |
311 | 0 | args, |
312 | 0 | InvalidOid, |
313 | 0 | OBJECT_AGGREGATE, |
314 | 0 | ¶meterTypes, |
315 | 0 | NULL, |
316 | 0 | &allParameterTypes, |
317 | 0 | ¶meterModes, |
318 | 0 | ¶meterNames, |
319 | 0 | NULL, |
320 | 0 | ¶meterDefaults, |
321 | 0 | &variadicArgType, |
322 | 0 | &requiredResultType); |
323 | | /* Parameter defaults are not currently allowed by the grammar */ |
324 | 0 | Assert(parameterDefaults == NIL); |
325 | | /* There shouldn't have been any OUT parameters, either */ |
326 | 0 | Assert(requiredResultType == InvalidOid); |
327 | 0 | } |
328 | | |
329 | | /* |
330 | | * look up the aggregate's transtype. |
331 | | * |
332 | | * transtype can't be a pseudo-type, since we need to be able to store |
333 | | * values of the transtype. However, we can allow polymorphic transtype |
334 | | * in some cases (AggregateCreate will check). Also, we allow "internal" |
335 | | * for functions that want to pass pointers to private data structures; |
336 | | * but allow that only to superusers, since you could crash the system (or |
337 | | * worse) by connecting up incompatible internal-using functions in an |
338 | | * aggregate. |
339 | | */ |
340 | 0 | transTypeId = typenameTypeId(NULL, transType); |
341 | 0 | transTypeType = get_typtype(transTypeId); |
342 | 0 | if (transTypeType == TYPTYPE_PSEUDO && |
343 | 0 | !IsPolymorphicType(transTypeId)) |
344 | 0 | { |
345 | 0 | if (transTypeId == INTERNALOID && superuser()) |
346 | 0 | /* okay */ ; |
347 | 0 | else |
348 | 0 | ereport(ERROR, |
349 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
350 | 0 | errmsg("aggregate transition data type cannot be %s", |
351 | 0 | format_type_be(transTypeId)))); |
352 | 0 | } |
353 | | |
354 | 0 | if (serialfuncName && deserialfuncName) |
355 | 0 | { |
356 | | /* |
357 | | * Serialization is only needed/allowed for transtype INTERNAL. |
358 | | */ |
359 | 0 | if (transTypeId != INTERNALOID) |
360 | 0 | ereport(ERROR, |
361 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
362 | 0 | errmsg("serialization functions may be specified only when the aggregate transition data type is %s", |
363 | 0 | format_type_be(INTERNALOID)))); |
364 | 0 | } |
365 | 0 | else if (serialfuncName || deserialfuncName) |
366 | 0 | { |
367 | | /* |
368 | | * Cannot specify one function without the other. |
369 | | */ |
370 | 0 | ereport(ERROR, |
371 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
372 | 0 | errmsg("must specify both or neither of serialization and deserialization functions"))); |
373 | 0 | } |
374 | | |
375 | | /* |
376 | | * If a moving-aggregate transtype is specified, look that up. Same |
377 | | * restrictions as for transtype. |
378 | | */ |
379 | 0 | if (mtransType) |
380 | 0 | { |
381 | 0 | mtransTypeId = typenameTypeId(NULL, mtransType); |
382 | 0 | mtransTypeType = get_typtype(mtransTypeId); |
383 | 0 | if (mtransTypeType == TYPTYPE_PSEUDO && |
384 | 0 | !IsPolymorphicType(mtransTypeId)) |
385 | 0 | { |
386 | 0 | if (mtransTypeId == INTERNALOID && superuser()) |
387 | 0 | /* okay */ ; |
388 | 0 | else |
389 | 0 | ereport(ERROR, |
390 | 0 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
391 | 0 | errmsg("aggregate transition data type cannot be %s", |
392 | 0 | format_type_be(mtransTypeId)))); |
393 | 0 | } |
394 | 0 | } |
395 | | |
396 | | /* |
397 | | * If we have an initval, and it's not for a pseudotype (particularly a |
398 | | * polymorphic type), make sure it's acceptable to the type's input |
399 | | * function. We will store the initval as text, because the input |
400 | | * function isn't necessarily immutable (consider "now" for timestamp), |
401 | | * and we want to use the runtime not creation-time interpretation of the |
402 | | * value. However, if it's an incorrect value it seems much more |
403 | | * user-friendly to complain at CREATE AGGREGATE time. |
404 | | */ |
405 | 0 | if (initval && transTypeType != TYPTYPE_PSEUDO) |
406 | 0 | { |
407 | 0 | Oid typinput, |
408 | 0 | typioparam; |
409 | |
|
410 | 0 | getTypeInputInfo(transTypeId, &typinput, &typioparam); |
411 | 0 | (void) OidInputFunctionCall(typinput, initval, typioparam, -1); |
412 | 0 | } |
413 | | |
414 | | /* |
415 | | * Likewise for moving-aggregate initval. |
416 | | */ |
417 | 0 | if (minitval && mtransTypeType != TYPTYPE_PSEUDO) |
418 | 0 | { |
419 | 0 | Oid typinput, |
420 | 0 | typioparam; |
421 | |
|
422 | 0 | getTypeInputInfo(mtransTypeId, &typinput, &typioparam); |
423 | 0 | (void) OidInputFunctionCall(typinput, minitval, typioparam, -1); |
424 | 0 | } |
425 | |
|
426 | 0 | if (parallel) |
427 | 0 | { |
428 | 0 | if (strcmp(parallel, "safe") == 0) |
429 | 0 | proparallel = PROPARALLEL_SAFE; |
430 | 0 | else if (strcmp(parallel, "restricted") == 0) |
431 | 0 | proparallel = PROPARALLEL_RESTRICTED; |
432 | 0 | else if (strcmp(parallel, "unsafe") == 0) |
433 | 0 | proparallel = PROPARALLEL_UNSAFE; |
434 | 0 | else |
435 | 0 | ereport(ERROR, |
436 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
437 | 0 | errmsg("parameter \"parallel\" must be SAFE, RESTRICTED, or UNSAFE"))); |
438 | 0 | } |
439 | | |
440 | | /* |
441 | | * Most of the argument-checking is done inside of AggregateCreate |
442 | | */ |
443 | 0 | return AggregateCreate(aggName, /* aggregate name */ |
444 | 0 | aggNamespace, /* namespace */ |
445 | 0 | replace, |
446 | 0 | aggKind, |
447 | 0 | numArgs, |
448 | 0 | numDirectArgs, |
449 | 0 | parameterTypes, |
450 | 0 | PointerGetDatum(allParameterTypes), |
451 | 0 | PointerGetDatum(parameterModes), |
452 | 0 | PointerGetDatum(parameterNames), |
453 | 0 | parameterDefaults, |
454 | 0 | variadicArgType, |
455 | 0 | transfuncName, /* step function name */ |
456 | 0 | finalfuncName, /* final function name */ |
457 | 0 | combinefuncName, /* combine function name */ |
458 | 0 | serialfuncName, /* serial function name */ |
459 | 0 | deserialfuncName, /* deserial function name */ |
460 | 0 | mtransfuncName, /* fwd trans function name */ |
461 | 0 | minvtransfuncName, /* inv trans function name */ |
462 | 0 | mfinalfuncName, /* final function name */ |
463 | 0 | finalfuncExtraArgs, |
464 | 0 | mfinalfuncExtraArgs, |
465 | 0 | finalfuncModify, |
466 | 0 | mfinalfuncModify, |
467 | 0 | sortoperatorName, /* sort operator name */ |
468 | 0 | supportfuncName, /* planner support func name */ |
469 | 0 | transTypeId, /* transition data type */ |
470 | 0 | transSpace, /* transition space */ |
471 | 0 | mtransTypeId, /* transition data type */ |
472 | 0 | mtransSpace, /* transition space */ |
473 | 0 | initval, /* initial condition */ |
474 | 0 | minitval, /* initial condition */ |
475 | 0 | proparallel); /* parallel safe? */ |
476 | 0 | } |
477 | | |
478 | | /* |
479 | | * Convert the string form of [m]finalfunc_modify to the catalog representation |
480 | | */ |
481 | | static char |
482 | | extractModify(DefElem *defel) |
483 | 0 | { |
484 | 0 | char *val = defGetString(defel); |
485 | |
|
486 | 0 | if (strcmp(val, "read_only") == 0) |
487 | 0 | return AGGMODIFY_READ_ONLY; |
488 | 0 | if (strcmp(val, "shareable") == 0) |
489 | 0 | return AGGMODIFY_SHAREABLE; |
490 | 0 | if (strcmp(val, "read_write") == 0) |
491 | 0 | return AGGMODIFY_READ_WRITE; |
492 | 0 | ereport(ERROR, |
493 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
494 | 0 | errmsg("parameter \"%s\" must be READ_ONLY, SHAREABLE, or READ_WRITE", |
495 | 0 | defel->defname))); |
496 | 0 | return 0; /* keep compiler quiet */ |
497 | 0 | } |