LCOV - code coverage report
Current view: top level - src/backend/commands - dropcmds.c (source / functions) Hit Total Coverage
Test: PostgreSQL 16beta1 Lines: 191 229 83.4 %
Date: 2023-05-30 16:15:03 Functions: 5 5 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * dropcmds.c
       4             :  *    handle various "DROP" operations
       5             :  *
       6             :  * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
       7             :  * Portions Copyright (c) 1994, Regents of the University of California
       8             :  *
       9             :  *
      10             :  * IDENTIFICATION
      11             :  *    src/backend/commands/dropcmds.c
      12             :  *
      13             :  *-------------------------------------------------------------------------
      14             :  */
      15             : #include "postgres.h"
      16             : 
      17             : #include "access/htup_details.h"
      18             : #include "access/table.h"
      19             : #include "access/xact.h"
      20             : #include "catalog/dependency.h"
      21             : #include "catalog/namespace.h"
      22             : #include "catalog/objectaddress.h"
      23             : #include "catalog/pg_class.h"
      24             : #include "catalog/pg_namespace.h"
      25             : #include "catalog/pg_proc.h"
      26             : #include "commands/defrem.h"
      27             : #include "miscadmin.h"
      28             : #include "nodes/makefuncs.h"
      29             : #include "parser/parse_type.h"
      30             : #include "utils/acl.h"
      31             : #include "utils/builtins.h"
      32             : #include "utils/lsyscache.h"
      33             : #include "utils/syscache.h"
      34             : 
      35             : 
      36             : static void does_not_exist_skipping(ObjectType objtype,
      37             :                                     Node *object);
      38             : static bool owningrel_does_not_exist_skipping(List *object,
      39             :                                               const char **msg, char **name);
      40             : static bool schema_does_not_exist_skipping(List *object,
      41             :                                            const char **msg, char **name);
      42             : static bool type_in_list_does_not_exist_skipping(List *typenames,
      43             :                                                  const char **msg, char **name);
      44             : 
      45             : 
      46             : /*
      47             :  * Drop one or more objects.
      48             :  *
      49             :  * We don't currently handle all object types here.  Relations, for example,
      50             :  * require special handling, because (for example) indexes have additional
      51             :  * locking requirements.
      52             :  *
      53             :  * We look up all the objects first, and then delete them in a single
      54             :  * performMultipleDeletions() call.  This avoids unnecessary DROP RESTRICT
      55             :  * errors if there are dependencies between them.
      56             :  */
      57             : void
      58        7752 : RemoveObjects(DropStmt *stmt)
      59             : {
      60             :     ObjectAddresses *objects;
      61             :     ListCell   *cell1;
      62             : 
      63        7752 :     objects = new_object_addresses();
      64             : 
      65       15244 :     foreach(cell1, stmt->objects)
      66             :     {
      67             :         ObjectAddress address;
      68        8000 :         Node       *object = lfirst(cell1);
      69        8000 :         Relation    relation = NULL;
      70             :         Oid         namespaceId;
      71             : 
      72             :         /* Get an ObjectAddress for the object. */
      73        8000 :         address = get_object_address(stmt->removeType,
      74             :                                      object,
      75             :                                      &relation,
      76             :                                      AccessExclusiveLock,
      77        8000 :                                      stmt->missing_ok);
      78             : 
      79             :         /*
      80             :          * Issue NOTICE if supplied object was not found.  Note this is only
      81             :          * relevant in the missing_ok case, because otherwise
      82             :          * get_object_address would have thrown an error.
      83             :          */
      84        7570 :         if (!OidIsValid(address.objectId))
      85             :         {
      86             :             Assert(stmt->missing_ok);
      87         384 :             does_not_exist_skipping(stmt->removeType, object);
      88         384 :             continue;
      89             :         }
      90             : 
      91             :         /*
      92             :          * Although COMMENT ON FUNCTION, SECURITY LABEL ON FUNCTION, etc. are
      93             :          * happy to operate on an aggregate as on any other function, we have
      94             :          * historically not allowed this for DROP FUNCTION.
      95             :          */
      96        7186 :         if (stmt->removeType == OBJECT_FUNCTION)
      97             :         {
      98        3180 :             if (get_func_prokind(address.objectId) == PROKIND_AGGREGATE)
      99           0 :                 ereport(ERROR,
     100             :                         (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     101             :                          errmsg("\"%s\" is an aggregate function",
     102             :                                 NameListToString(castNode(ObjectWithArgs, object)->objname)),
     103             :                          errhint("Use DROP AGGREGATE to drop aggregate functions.")));
     104             :         }
     105             : 
     106             :         /* Check permissions. */
     107        7186 :         namespaceId = get_object_namespace(&address);
     108        7186 :         if (!OidIsValid(namespaceId) ||
     109        4926 :             !object_ownercheck(NamespaceRelationId, namespaceId, GetUserId()))
     110        2410 :             check_object_ownership(GetUserId(), stmt->removeType, address,
     111             :                                    object, relation);
     112             : 
     113             :         /*
     114             :          * Make note if a temporary namespace has been accessed in this
     115             :          * transaction.
     116             :          */
     117        7108 :         if (OidIsValid(namespaceId) && isTempNamespace(namespaceId))
     118         256 :             MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
     119             : 
     120             :         /* Release any relcache reference count, but keep lock until commit. */
     121        7108 :         if (relation)
     122         966 :             table_close(relation, NoLock);
     123             : 
     124        7108 :         add_exact_object_address(&address, objects);
     125             :     }
     126             : 
     127             :     /* Here we really delete them. */
     128        7244 :     performMultipleDeletions(objects, stmt->behavior, 0);
     129             : 
     130        7094 :     free_object_addresses(objects);
     131        7094 : }
     132             : 
     133             : /*
     134             :  * owningrel_does_not_exist_skipping
     135             :  *      Subroutine for RemoveObjects
     136             :  *
     137             :  * After determining that a specification for a rule or trigger returns that
     138             :  * the specified object does not exist, test whether its owning relation, and
     139             :  * its schema, exist or not; if they do, return false --- the trigger or rule
     140             :  * itself is missing instead.  If the owning relation or its schema do not
     141             :  * exist, fill the error message format string and name, and return true.
     142             :  */
     143             : static bool
     144          48 : owningrel_does_not_exist_skipping(List *object, const char **msg, char **name)
     145             : {
     146             :     List       *parent_object;
     147             :     RangeVar   *parent_rel;
     148             : 
     149          48 :     parent_object = list_copy_head(object, list_length(object) - 1);
     150             : 
     151          48 :     if (schema_does_not_exist_skipping(parent_object, msg, name))
     152          24 :         return true;
     153             : 
     154          24 :     parent_rel = makeRangeVarFromNameList(parent_object);
     155             : 
     156          24 :     if (!OidIsValid(RangeVarGetRelid(parent_rel, NoLock, true)))
     157             :     {
     158          12 :         *msg = gettext_noop("relation \"%s\" does not exist, skipping");
     159          12 :         *name = NameListToString(parent_object);
     160             : 
     161          12 :         return true;
     162             :     }
     163             : 
     164          12 :     return false;
     165             : }
     166             : 
     167             : /*
     168             :  * schema_does_not_exist_skipping
     169             :  *      Subroutine for RemoveObjects
     170             :  *
     171             :  * After determining that a specification for a schema-qualifiable object
     172             :  * refers to an object that does not exist, test whether the specified schema
     173             :  * exists or not.  If no schema was specified, or if the schema does exist,
     174             :  * return false -- the object itself is missing instead.  If the specified
     175             :  * schema does not exist, fill the error message format string and the
     176             :  * specified schema name, and return true.
     177             :  */
     178             : static bool
     179         350 : schema_does_not_exist_skipping(List *object, const char **msg, char **name)
     180             : {
     181             :     RangeVar   *rel;
     182             : 
     183         350 :     rel = makeRangeVarFromNameList(object);
     184             : 
     185         488 :     if (rel->schemaname != NULL &&
     186         138 :         !OidIsValid(LookupNamespaceNoError(rel->schemaname)))
     187             :     {
     188         138 :         *msg = gettext_noop("schema \"%s\" does not exist, skipping");
     189         138 :         *name = rel->schemaname;
     190             : 
     191         138 :         return true;
     192             :     }
     193             : 
     194         212 :     return false;
     195             : }
     196             : 
     197             : /*
     198             :  * type_in_list_does_not_exist_skipping
     199             :  *      Subroutine for RemoveObjects
     200             :  *
     201             :  * After determining that a specification for a function, cast, aggregate or
     202             :  * operator returns that the specified object does not exist, test whether the
     203             :  * involved datatypes, and their schemas, exist or not; if they do, return
     204             :  * false --- the original object itself is missing instead.  If the datatypes
     205             :  * or schemas do not exist, fill the error message format string and the
     206             :  * missing name, and return true.
     207             :  *
     208             :  * First parameter is a list of TypeNames.
     209             :  */
     210             : static bool
     211         142 : type_in_list_does_not_exist_skipping(List *typenames, const char **msg,
     212             :                                      char **name)
     213             : {
     214             :     ListCell   *l;
     215             : 
     216         212 :     foreach(l, typenames)
     217             :     {
     218         138 :         TypeName   *typeName = lfirst_node(TypeName, l);
     219             : 
     220         138 :         if (typeName != NULL)
     221             :         {
     222         132 :             if (!OidIsValid(LookupTypeNameOid(NULL, typeName, true)))
     223             :             {
     224             :                 /* type doesn't exist, try to find why */
     225          68 :                 if (schema_does_not_exist_skipping(typeName->names, msg, name))
     226          68 :                     return true;
     227             : 
     228          32 :                 *msg = gettext_noop("type \"%s\" does not exist, skipping");
     229          32 :                 *name = TypeNameToString(typeName);
     230             : 
     231          32 :                 return true;
     232             :             }
     233             :         }
     234             :     }
     235             : 
     236          74 :     return false;
     237             : }
     238             : 
     239             : /*
     240             :  * does_not_exist_skipping
     241             :  *      Subroutine for RemoveObjects
     242             :  *
     243             :  * Generate a NOTICE stating that the named object was not found, and is
     244             :  * being skipped.  This is only relevant when "IF EXISTS" is used; otherwise,
     245             :  * get_object_address() in RemoveObjects would have thrown an ERROR.
     246             :  */
     247             : static void
     248         384 : does_not_exist_skipping(ObjectType objtype, Node *object)
     249             : {
     250         384 :     const char *msg = NULL;
     251         384 :     char       *name = NULL;
     252         384 :     char       *args = NULL;
     253             : 
     254         384 :     switch (objtype)
     255             :     {
     256           6 :         case OBJECT_ACCESS_METHOD:
     257           6 :             msg = gettext_noop("access method \"%s\" does not exist, skipping");
     258           6 :             name = strVal(object);
     259           6 :             break;
     260          26 :         case OBJECT_TYPE:
     261             :         case OBJECT_DOMAIN:
     262             :             {
     263          26 :                 TypeName   *typ = castNode(TypeName, object);
     264             : 
     265          26 :                 if (!schema_does_not_exist_skipping(typ->names, &msg, &name))
     266             :                 {
     267          14 :                     msg = gettext_noop("type \"%s\" does not exist, skipping");
     268          14 :                     name = TypeNameToString(typ);
     269             :                 }
     270             :             }
     271          26 :             break;
     272          18 :         case OBJECT_COLLATION:
     273          18 :             if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
     274             :             {
     275          12 :                 msg = gettext_noop("collation \"%s\" does not exist, skipping");
     276          12 :                 name = NameListToString(castNode(List, object));
     277             :             }
     278          18 :             break;
     279          12 :         case OBJECT_CONVERSION:
     280          12 :             if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
     281             :             {
     282           6 :                 msg = gettext_noop("conversion \"%s\" does not exist, skipping");
     283           6 :                 name = NameListToString(castNode(List, object));
     284             :             }
     285          12 :             break;
     286          12 :         case OBJECT_SCHEMA:
     287          12 :             msg = gettext_noop("schema \"%s\" does not exist, skipping");
     288          12 :             name = strVal(object);
     289          12 :             break;
     290           0 :         case OBJECT_STATISTIC_EXT:
     291           0 :             if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
     292             :             {
     293           0 :                 msg = gettext_noop("statistics object \"%s\" does not exist, skipping");
     294           0 :                 name = NameListToString(castNode(List, object));
     295             :             }
     296           0 :             break;
     297          12 :         case OBJECT_TSPARSER:
     298          12 :             if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
     299             :             {
     300           6 :                 msg = gettext_noop("text search parser \"%s\" does not exist, skipping");
     301           6 :                 name = NameListToString(castNode(List, object));
     302             :             }
     303          12 :             break;
     304          12 :         case OBJECT_TSDICTIONARY:
     305          12 :             if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
     306             :             {
     307           6 :                 msg = gettext_noop("text search dictionary \"%s\" does not exist, skipping");
     308           6 :                 name = NameListToString(castNode(List, object));
     309             :             }
     310          12 :             break;
     311          12 :         case OBJECT_TSTEMPLATE:
     312          12 :             if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
     313             :             {
     314           6 :                 msg = gettext_noop("text search template \"%s\" does not exist, skipping");
     315           6 :                 name = NameListToString(castNode(List, object));
     316             :             }
     317          12 :             break;
     318          12 :         case OBJECT_TSCONFIGURATION:
     319          12 :             if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
     320             :             {
     321           6 :                 msg = gettext_noop("text search configuration \"%s\" does not exist, skipping");
     322           6 :                 name = NameListToString(castNode(List, object));
     323             :             }
     324          12 :             break;
     325          12 :         case OBJECT_EXTENSION:
     326          12 :             msg = gettext_noop("extension \"%s\" does not exist, skipping");
     327          12 :             name = strVal(object);
     328          12 :             break;
     329          46 :         case OBJECT_FUNCTION:
     330             :             {
     331          46 :                 ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
     332             : 
     333          46 :                 if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
     334          40 :                     !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name))
     335             :                 {
     336          28 :                     msg = gettext_noop("function %s(%s) does not exist, skipping");
     337          28 :                     name = NameListToString(owa->objname);
     338          28 :                     args = TypeNameListToString(owa->objargs);
     339             :                 }
     340          46 :                 break;
     341             :             }
     342           0 :         case OBJECT_PROCEDURE:
     343             :             {
     344           0 :                 ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
     345             : 
     346           0 :                 if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
     347           0 :                     !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name))
     348             :                 {
     349           0 :                     msg = gettext_noop("procedure %s(%s) does not exist, skipping");
     350           0 :                     name = NameListToString(owa->objname);
     351           0 :                     args = TypeNameListToString(owa->objargs);
     352             :                 }
     353           0 :                 break;
     354             :             }
     355           0 :         case OBJECT_ROUTINE:
     356             :             {
     357           0 :                 ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
     358             : 
     359           0 :                 if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
     360           0 :                     !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name))
     361             :                 {
     362           0 :                     msg = gettext_noop("routine %s(%s) does not exist, skipping");
     363           0 :                     name = NameListToString(owa->objname);
     364           0 :                     args = TypeNameListToString(owa->objargs);
     365             :                 }
     366           0 :                 break;
     367             :             }
     368          30 :         case OBJECT_AGGREGATE:
     369             :             {
     370          30 :                 ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
     371             : 
     372          30 :                 if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
     373          24 :                     !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name))
     374             :                 {
     375          12 :                     msg = gettext_noop("aggregate %s(%s) does not exist, skipping");
     376          12 :                     name = NameListToString(owa->objname);
     377          12 :                     args = TypeNameListToString(owa->objargs);
     378             :                 }
     379          30 :                 break;
     380             :             }
     381          30 :         case OBJECT_OPERATOR:
     382             :             {
     383          30 :                 ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
     384             : 
     385          30 :                 if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
     386          24 :                     !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name))
     387             :                 {
     388           6 :                     msg = gettext_noop("operator %s does not exist, skipping");
     389           6 :                     name = NameListToString(owa->objname);
     390             :                 }
     391          30 :                 break;
     392             :             }
     393           6 :         case OBJECT_LANGUAGE:
     394           6 :             msg = gettext_noop("language \"%s\" does not exist, skipping");
     395           6 :             name = strVal(object);
     396           6 :             break;
     397          30 :         case OBJECT_CAST:
     398             :             {
     399          30 :                 if (!type_in_list_does_not_exist_skipping(list_make1(linitial(castNode(List, object))), &msg, &name) &&
     400          18 :                     !type_in_list_does_not_exist_skipping(list_make1(lsecond(castNode(List, object))), &msg, &name))
     401             :                 {
     402             :                     /* XXX quote or no quote? */
     403           6 :                     msg = gettext_noop("cast from type %s to type %s does not exist, skipping");
     404           6 :                     name = TypeNameToString(linitial_node(TypeName, castNode(List, object)));
     405           6 :                     args = TypeNameToString(lsecond_node(TypeName, castNode(List, object)));
     406             :                 }
     407             :             }
     408          30 :             break;
     409           6 :         case OBJECT_TRANSFORM:
     410           6 :             if (!type_in_list_does_not_exist_skipping(list_make1(linitial(castNode(List, object))), &msg, &name))
     411             :             {
     412           4 :                 msg = gettext_noop("transform for type %s language \"%s\" does not exist, skipping");
     413           4 :                 name = TypeNameToString(linitial_node(TypeName, castNode(List, object)));
     414           4 :                 args = strVal(lsecond(castNode(List, object)));
     415             :             }
     416           6 :             break;
     417          24 :         case OBJECT_TRIGGER:
     418          24 :             if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name))
     419             :             {
     420           6 :                 msg = gettext_noop("trigger \"%s\" for relation \"%s\" does not exist, skipping");
     421           6 :                 name = strVal(llast(castNode(List, object)));
     422           6 :                 args = NameListToString(list_copy_head(castNode(List, object),
     423           6 :                                                        list_length(castNode(List, object)) - 1));
     424             :             }
     425          24 :             break;
     426           0 :         case OBJECT_POLICY:
     427           0 :             if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name))
     428             :             {
     429           0 :                 msg = gettext_noop("policy \"%s\" for relation \"%s\" does not exist, skipping");
     430           0 :                 name = strVal(llast(castNode(List, object)));
     431           0 :                 args = NameListToString(list_copy_head(castNode(List, object),
     432           0 :                                                        list_length(castNode(List, object)) - 1));
     433             :             }
     434           0 :             break;
     435           6 :         case OBJECT_EVENT_TRIGGER:
     436           6 :             msg = gettext_noop("event trigger \"%s\" does not exist, skipping");
     437           6 :             name = strVal(object);
     438           6 :             break;
     439          24 :         case OBJECT_RULE:
     440          24 :             if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name))
     441             :             {
     442           6 :                 msg = gettext_noop("rule \"%s\" for relation \"%s\" does not exist, skipping");
     443           6 :                 name = strVal(llast(castNode(List, object)));
     444           6 :                 args = NameListToString(list_copy_head(castNode(List, object),
     445           6 :                                                        list_length(castNode(List, object)) - 1));
     446             :             }
     447          24 :             break;
     448          12 :         case OBJECT_FDW:
     449          12 :             msg = gettext_noop("foreign-data wrapper \"%s\" does not exist, skipping");
     450          12 :             name = strVal(object);
     451          12 :             break;
     452          12 :         case OBJECT_FOREIGN_SERVER:
     453          12 :             msg = gettext_noop("server \"%s\" does not exist, skipping");
     454          12 :             name = strVal(object);
     455          12 :             break;
     456          12 :         case OBJECT_OPCLASS:
     457             :             {
     458          12 :                 List       *opcname = list_copy_tail(castNode(List, object), 1);
     459             : 
     460          12 :                 if (!schema_does_not_exist_skipping(opcname, &msg, &name))
     461             :                 {
     462           6 :                     msg = gettext_noop("operator class \"%s\" does not exist for access method \"%s\", skipping");
     463           6 :                     name = NameListToString(opcname);
     464           6 :                     args = strVal(linitial(castNode(List, object)));
     465             :                 }
     466             :             }
     467          12 :             break;
     468          12 :         case OBJECT_OPFAMILY:
     469             :             {
     470          12 :                 List       *opfname = list_copy_tail(castNode(List, object), 1);
     471             : 
     472          12 :                 if (!schema_does_not_exist_skipping(opfname, &msg, &name))
     473             :                 {
     474           6 :                     msg = gettext_noop("operator family \"%s\" does not exist for access method \"%s\", skipping");
     475           6 :                     name = NameListToString(opfname);
     476           6 :                     args = strVal(linitial(castNode(List, object)));
     477             :                 }
     478             :             }
     479          12 :             break;
     480           0 :         case OBJECT_PUBLICATION:
     481           0 :             msg = gettext_noop("publication \"%s\" does not exist, skipping");
     482           0 :             name = strVal(object);
     483           0 :             break;
     484             : 
     485           0 :         case OBJECT_COLUMN:
     486             :         case OBJECT_DATABASE:
     487             :         case OBJECT_FOREIGN_TABLE:
     488             :         case OBJECT_INDEX:
     489             :         case OBJECT_MATVIEW:
     490             :         case OBJECT_ROLE:
     491             :         case OBJECT_SEQUENCE:
     492             :         case OBJECT_SUBSCRIPTION:
     493             :         case OBJECT_TABLE:
     494             :         case OBJECT_TABLESPACE:
     495             :         case OBJECT_VIEW:
     496             : 
     497             :             /*
     498             :              * These are handled elsewhere, so if someone gets here the code
     499             :              * is probably wrong or should be revisited.
     500             :              */
     501           0 :             elog(ERROR, "unsupported object type: %d", (int) objtype);
     502             :             break;
     503             : 
     504           0 :         case OBJECT_AMOP:
     505             :         case OBJECT_AMPROC:
     506             :         case OBJECT_ATTRIBUTE:
     507             :         case OBJECT_DEFAULT:
     508             :         case OBJECT_DEFACL:
     509             :         case OBJECT_DOMCONSTRAINT:
     510             :         case OBJECT_LARGEOBJECT:
     511             :         case OBJECT_PARAMETER_ACL:
     512             :         case OBJECT_PUBLICATION_NAMESPACE:
     513             :         case OBJECT_PUBLICATION_REL:
     514             :         case OBJECT_TABCONSTRAINT:
     515             :         case OBJECT_USER_MAPPING:
     516             :             /* These are currently not used or needed. */
     517           0 :             elog(ERROR, "unsupported object type: %d", (int) objtype);
     518             :             break;
     519             : 
     520             :             /* no default, to let compiler warn about missing case */
     521             :     }
     522         384 :     if (!msg)
     523           0 :         elog(ERROR, "unrecognized object type: %d", (int) objtype);
     524             : 
     525         384 :     if (!args)
     526         310 :         ereport(NOTICE, (errmsg(msg, name)));
     527             :     else
     528          74 :         ereport(NOTICE, (errmsg(msg, name, args)));
     529         384 : }

Generated by: LCOV version 1.14