Coverage Report

Created: 2026-10-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
                      &parameterTypes,
315
0
                      NULL,
316
0
                      &allParameterTypes,
317
0
                      &parameterModes,
318
0
                      &parameterNames,
319
0
                      NULL,
320
0
                      &parameterDefaults,
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
}