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