LCOV - code coverage report
Current view: top level - src/bin/pg_dump - dumputils.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13devel Lines: 222 355 62.5 %
Date: 2019-11-15 22:06:47 Functions: 11 11 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * Utility routines for SQL dumping
       4             :  *
       5             :  * Basically this is stuff that is useful in both pg_dump and pg_dumpall.
       6             :  *
       7             :  *
       8             :  * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
       9             :  * Portions Copyright (c) 1994, Regents of the University of California
      10             :  *
      11             :  * src/bin/pg_dump/dumputils.c
      12             :  *
      13             :  *-------------------------------------------------------------------------
      14             :  */
      15             : #include "postgres_fe.h"
      16             : 
      17             : #include <ctype.h>
      18             : 
      19             : #include "dumputils.h"
      20             : #include "fe_utils/string_utils.h"
      21             : 
      22             : 
      23             : static bool parseAclItem(const char *item, const char *type,
      24             :                          const char *name, const char *subname, int remoteVersion,
      25             :                          PQExpBuffer grantee, PQExpBuffer grantor,
      26             :                          PQExpBuffer privs, PQExpBuffer privswgo);
      27             : static char *copyAclUserName(PQExpBuffer output, char *input);
      28             : static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
      29             :                    const char *subname);
      30             : 
      31             : 
      32             : /*
      33             :  * Build GRANT/REVOKE command(s) for an object.
      34             :  *
      35             :  *  name: the object name, in the form to use in the commands (already quoted)
      36             :  *  subname: the sub-object name, if any (already quoted); NULL if none
      37             :  *  nspname: the namespace the object is in (NULL if none); not pre-quoted
      38             :  *  type: the object type (as seen in GRANT command: must be one of
      39             :  *      TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
      40             :  *      FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT)
      41             :  *  acls: the ACL string fetched from the database
      42             :  *  racls: the ACL string of any initial-but-now-revoked privileges
      43             :  *  owner: username of object owner (will be passed through fmtId); can be
      44             :  *      NULL or empty string to indicate "no owner known"
      45             :  *  prefix: string to prefix to each generated command; typically empty
      46             :  *  remoteVersion: version of database
      47             :  *
      48             :  * Returns true if okay, false if could not parse the acl string.
      49             :  * The resulting commands (if any) are appended to the contents of 'sql'.
      50             :  *
      51             :  * Note: when processing a default ACL, prefix is "ALTER DEFAULT PRIVILEGES "
      52             :  * or something similar, and name is an empty string.
      53             :  *
      54             :  * Note: beware of passing a fmtId() result directly as 'name' or 'subname',
      55             :  * since this routine uses fmtId() internally.
      56             :  */
      57             : bool
      58        2426 : buildACLCommands(const char *name, const char *subname, const char *nspname,
      59             :                  const char *type, const char *acls, const char *racls,
      60             :                  const char *owner, const char *prefix, int remoteVersion,
      61             :                  PQExpBuffer sql)
      62             : {
      63        2426 :     bool        ok = true;
      64        2426 :     char      **aclitems = NULL;
      65        2426 :     char      **raclitems = NULL;
      66        2426 :     int         naclitems = 0;
      67        2426 :     int         nraclitems = 0;
      68             :     int         i;
      69             :     PQExpBuffer grantee,
      70             :                 grantor,
      71             :                 privs,
      72             :                 privswgo;
      73             :     PQExpBuffer firstsql,
      74             :                 secondsql;
      75        2426 :     bool        found_owner_privs = false;
      76             : 
      77        2426 :     if (strlen(acls) == 0 && strlen(racls) == 0)
      78         242 :         return true;            /* object has default permissions */
      79             : 
      80             :     /* treat empty-string owner same as NULL */
      81        2184 :     if (owner && *owner == '\0')
      82           0 :         owner = NULL;
      83             : 
      84        2184 :     if (strlen(acls) != 0)
      85             :     {
      86        1998 :         if (!parsePGArray(acls, &aclitems, &naclitems))
      87             :         {
      88           0 :             if (aclitems)
      89           0 :                 free(aclitems);
      90           0 :             return false;
      91             :         }
      92             :     }
      93             : 
      94        2184 :     if (strlen(racls) != 0)
      95             :     {
      96         352 :         if (!parsePGArray(racls, &raclitems, &nraclitems))
      97             :         {
      98           0 :             if (aclitems)
      99           0 :                 free(aclitems);
     100           0 :             if (raclitems)
     101           0 :                 free(raclitems);
     102           0 :             return false;
     103             :         }
     104             :     }
     105             : 
     106        2184 :     grantee = createPQExpBuffer();
     107        2184 :     grantor = createPQExpBuffer();
     108        2184 :     privs = createPQExpBuffer();
     109        2184 :     privswgo = createPQExpBuffer();
     110             : 
     111             :     /*
     112             :      * At the end, these two will be pasted together to form the result.
     113             :      *
     114             :      * For older systems we use these to ensure that the owner privileges go
     115             :      * before the other ones, as a GRANT could create the default entry for
     116             :      * the object, which generally includes all rights for the owner. In more
     117             :      * recent versions we normally handle this because the owner rights come
     118             :      * first in the ACLs, but older versions might have them after the PUBLIC
     119             :      * privileges.
     120             :      *
     121             :      * For 9.6 and later systems, much of this changes.  With 9.6, we check
     122             :      * the default privileges for the objects at dump time and create two sets
     123             :      * of ACLs- "racls" which are the ACLs to REVOKE from the object (as the
     124             :      * object may have initial privileges on it, along with any default ACLs
     125             :      * which are not part of the current set of privileges), and regular
     126             :      * "acls", which are the ACLs to GRANT to the object.  We handle the
     127             :      * REVOKEs first, followed by the GRANTs.
     128             :      */
     129        2184 :     firstsql = createPQExpBuffer();
     130        2184 :     secondsql = createPQExpBuffer();
     131             : 
     132             :     /*
     133             :      * For pre-9.6 systems, we always start with REVOKE ALL FROM PUBLIC, as we
     134             :      * don't wish to make any assumptions about what the default ACLs are, and
     135             :      * we do not collect them during the dump phase (and racls will always be
     136             :      * the empty set, see above).
     137             :      *
     138             :      * For 9.6 and later, if any revoke ACLs have been provided, then include
     139             :      * them in 'firstsql'.
     140             :      *
     141             :      * Revoke ACLs happen when an object starts out life with a set of
     142             :      * privileges (eg: GRANT SELECT ON pg_class TO PUBLIC;) and the user has
     143             :      * decided to revoke those rights.  Since those objects come into being
     144             :      * with those default privileges, we have to revoke them to match what the
     145             :      * current state of affairs is.  Note that we only started explicitly
     146             :      * tracking such initial rights in 9.6, and prior to that all initial
     147             :      * rights are actually handled by the simple 'REVOKE ALL .. FROM PUBLIC'
     148             :      * case, for initdb-created objects.  Prior to 9.6, we didn't handle
     149             :      * extensions correctly, but we do now by tracking their initial
     150             :      * privileges, in the same way we track initdb initial privileges, see
     151             :      * pg_init_privs.
     152             :      */
     153        2184 :     if (remoteVersion < 90600)
     154             :     {
     155             :         Assert(nraclitems == 0);
     156             : 
     157           0 :         appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
     158           0 :         if (subname)
     159           0 :             appendPQExpBuffer(firstsql, "(%s)", subname);
     160           0 :         appendPQExpBuffer(firstsql, " ON %s ", type);
     161           0 :         if (nspname && *nspname)
     162           0 :             appendPQExpBuffer(firstsql, "%s.", fmtId(nspname));
     163           0 :         appendPQExpBuffer(firstsql, "%s FROM PUBLIC;\n", name);
     164             :     }
     165             :     else
     166             :     {
     167             :         /* Scan individual REVOKE ACL items */
     168        2536 :         for (i = 0; i < nraclitems; i++)
     169             :         {
     170         352 :             if (!parseAclItem(raclitems[i], type, name, subname, remoteVersion,
     171             :                               grantee, grantor, privs, privswgo))
     172             :             {
     173           0 :                 ok = false;
     174           0 :                 break;
     175             :             }
     176             : 
     177         352 :             if (privs->len > 0 || privswgo->len > 0)
     178             :             {
     179         352 :                 if (privs->len > 0)
     180             :                 {
     181         352 :                     appendPQExpBuffer(firstsql, "%sREVOKE %s ON %s ",
     182             :                                       prefix, privs->data, type);
     183         352 :                     if (nspname && *nspname)
     184         136 :                         appendPQExpBuffer(firstsql, "%s.", fmtId(nspname));
     185         352 :                     appendPQExpBuffer(firstsql, "%s FROM ", name);
     186         352 :                     if (grantee->len == 0)
     187         242 :                         appendPQExpBufferStr(firstsql, "PUBLIC;\n");
     188         110 :                     else if (strncmp(grantee->data, "group ",
     189             :                                      strlen("group ")) == 0)
     190           0 :                         appendPQExpBuffer(firstsql, "GROUP %s;\n",
     191           0 :                                           fmtId(grantee->data + strlen("group ")));
     192             :                     else
     193         110 :                         appendPQExpBuffer(firstsql, "%s;\n",
     194         110 :                                           fmtId(grantee->data));
     195             :                 }
     196         352 :                 if (privswgo->len > 0)
     197             :                 {
     198           0 :                     appendPQExpBuffer(firstsql,
     199             :                                       "%sREVOKE GRANT OPTION FOR %s ON %s ",
     200             :                                       prefix, privswgo->data, type);
     201           0 :                     if (nspname && *nspname)
     202           0 :                         appendPQExpBuffer(firstsql, "%s.", fmtId(nspname));
     203           0 :                     appendPQExpBuffer(firstsql, "%s FROM ", name);
     204           0 :                     if (grantee->len == 0)
     205           0 :                         appendPQExpBufferStr(firstsql, "PUBLIC");
     206           0 :                     else if (strncmp(grantee->data, "group ",
     207             :                                      strlen("group ")) == 0)
     208           0 :                         appendPQExpBuffer(firstsql, "GROUP %s",
     209           0 :                                           fmtId(grantee->data + strlen("group ")));
     210             :                     else
     211           0 :                         appendPQExpBufferStr(firstsql, fmtId(grantee->data));
     212             :                 }
     213             :             }
     214             :         }
     215             :     }
     216             : 
     217             :     /*
     218             :      * We still need some hacking though to cover the case where new default
     219             :      * public privileges are added in new versions: the REVOKE ALL will revoke
     220             :      * them, leading to behavior different from what the old version had,
     221             :      * which is generally not what's wanted.  So add back default privs if the
     222             :      * source database is too old to have had that particular priv.
     223             :      */
     224        2184 :     if (remoteVersion < 80200 && strcmp(type, "DATABASE") == 0)
     225             :     {
     226             :         /* database CONNECT priv didn't exist before 8.2 */
     227           0 :         appendPQExpBuffer(firstsql, "%sGRANT CONNECT ON %s %s TO PUBLIC;\n",
     228             :                           prefix, type, name);
     229             :     }
     230             : 
     231             :     /* Scan individual ACL items */
     232        4184 :     for (i = 0; i < naclitems; i++)
     233             :     {
     234        2000 :         if (!parseAclItem(aclitems[i], type, name, subname, remoteVersion,
     235             :                           grantee, grantor, privs, privswgo))
     236             :         {
     237           0 :             ok = false;
     238           0 :             break;
     239             :         }
     240             : 
     241        2000 :         if (grantor->len == 0 && owner)
     242           0 :             printfPQExpBuffer(grantor, "%s", owner);
     243             : 
     244        2000 :         if (privs->len > 0 || privswgo->len > 0)
     245             :         {
     246             :             /*
     247             :              * Prior to 9.6, we had to handle owner privileges in a special
     248             :              * manner by first REVOKE'ing the rights and then GRANT'ing them
     249             :              * after.  With 9.6 and above, what we need to REVOKE and what we
     250             :              * need to GRANT is figured out when we dump and stashed into
     251             :              * "racls" and "acls", respectively.  See above.
     252             :              */
     253        2000 :             if (remoteVersion < 90600 && owner
     254           0 :                 && strcmp(grantee->data, owner) == 0
     255           0 :                 && strcmp(grantor->data, owner) == 0)
     256             :             {
     257           0 :                 found_owner_privs = true;
     258             : 
     259             :                 /*
     260             :                  * For the owner, the default privilege level is ALL WITH
     261             :                  * GRANT OPTION.
     262             :                  */
     263           0 :                 if (strcmp(privswgo->data, "ALL") != 0)
     264             :                 {
     265           0 :                     appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
     266           0 :                     if (subname)
     267           0 :                         appendPQExpBuffer(firstsql, "(%s)", subname);
     268           0 :                     appendPQExpBuffer(firstsql, " ON %s ", type);
     269           0 :                     if (nspname && *nspname)
     270           0 :                         appendPQExpBuffer(firstsql, "%s.", fmtId(nspname));
     271           0 :                     appendPQExpBuffer(firstsql, "%s FROM %s;\n",
     272           0 :                                       name, fmtId(grantee->data));
     273           0 :                     if (privs->len > 0)
     274             :                     {
     275           0 :                         appendPQExpBuffer(firstsql,
     276             :                                           "%sGRANT %s ON %s ",
     277             :                                           prefix, privs->data, type);
     278           0 :                         if (nspname && *nspname)
     279           0 :                             appendPQExpBuffer(firstsql, "%s.", fmtId(nspname));
     280           0 :                         appendPQExpBuffer(firstsql,
     281             :                                           "%s TO %s;\n",
     282           0 :                                           name, fmtId(grantee->data));
     283             :                     }
     284           0 :                     if (privswgo->len > 0)
     285             :                     {
     286           0 :                         appendPQExpBuffer(firstsql,
     287             :                                           "%sGRANT %s ON %s ",
     288             :                                           prefix, privswgo->data, type);
     289           0 :                         if (nspname && *nspname)
     290           0 :                             appendPQExpBuffer(firstsql, "%s.", fmtId(nspname));
     291           0 :                         appendPQExpBuffer(firstsql,
     292             :                                           "%s TO %s WITH GRANT OPTION;\n",
     293           0 :                                           name, fmtId(grantee->data));
     294             :                     }
     295             :                 }
     296             :             }
     297             :             else
     298             :             {
     299             :                 /*
     300             :                  * For systems prior to 9.6, we can assume we are starting
     301             :                  * from no privs at this point.
     302             :                  *
     303             :                  * For 9.6 and above, at this point we have issued REVOKE
     304             :                  * statements for all initial and default privileges which are
     305             :                  * no longer present on the object (as they were passed in as
     306             :                  * 'racls') and we can simply GRANT the rights which are in
     307             :                  * 'acls'.
     308             :                  */
     309        2000 :                 if (grantor->len > 0
     310        2000 :                     && (!owner || strcmp(owner, grantor->data) != 0))
     311           0 :                     appendPQExpBuffer(secondsql, "SET SESSION AUTHORIZATION %s;\n",
     312           0 :                                       fmtId(grantor->data));
     313             : 
     314        2000 :                 if (privs->len > 0)
     315             :                 {
     316        2000 :                     appendPQExpBuffer(secondsql, "%sGRANT %s ON %s ",
     317             :                                       prefix, privs->data, type);
     318        2000 :                     if (nspname && *nspname)
     319        1706 :                         appendPQExpBuffer(secondsql, "%s.", fmtId(nspname));
     320        2000 :                     appendPQExpBuffer(secondsql, "%s TO ", name);
     321        2000 :                     if (grantee->len == 0)
     322        1282 :                         appendPQExpBufferStr(secondsql, "PUBLIC;\n");
     323         718 :                     else if (strncmp(grantee->data, "group ",
     324             :                                      strlen("group ")) == 0)
     325           0 :                         appendPQExpBuffer(secondsql, "GROUP %s;\n",
     326           0 :                                           fmtId(grantee->data + strlen("group ")));
     327             :                     else
     328         718 :                         appendPQExpBuffer(secondsql, "%s;\n", fmtId(grantee->data));
     329             :                 }
     330        2000 :                 if (privswgo->len > 0)
     331             :                 {
     332           0 :                     appendPQExpBuffer(secondsql, "%sGRANT %s ON %s ",
     333             :                                       prefix, privswgo->data, type);
     334           0 :                     if (nspname && *nspname)
     335           0 :                         appendPQExpBuffer(secondsql, "%s.", fmtId(nspname));
     336           0 :                     appendPQExpBuffer(secondsql, "%s TO ", name);
     337           0 :                     if (grantee->len == 0)
     338           0 :                         appendPQExpBufferStr(secondsql, "PUBLIC");
     339           0 :                     else if (strncmp(grantee->data, "group ",
     340             :                                      strlen("group ")) == 0)
     341           0 :                         appendPQExpBuffer(secondsql, "GROUP %s",
     342           0 :                                           fmtId(grantee->data + strlen("group ")));
     343             :                     else
     344           0 :                         appendPQExpBufferStr(secondsql, fmtId(grantee->data));
     345           0 :                     appendPQExpBufferStr(secondsql, " WITH GRANT OPTION;\n");
     346             :                 }
     347             : 
     348        2000 :                 if (grantor->len > 0
     349        2000 :                     && (!owner || strcmp(owner, grantor->data) != 0))
     350           0 :                     appendPQExpBufferStr(secondsql, "RESET SESSION AUTHORIZATION;\n");
     351             :             }
     352             :         }
     353             :     }
     354             : 
     355             :     /*
     356             :      * For systems prior to 9.6, if we didn't find any owner privs, the owner
     357             :      * must have revoked 'em all.
     358             :      *
     359             :      * For 9.6 and above, we handle this through the 'racls'.  See above.
     360             :      */
     361        2184 :     if (remoteVersion < 90600 && !found_owner_privs && owner)
     362             :     {
     363           0 :         appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
     364           0 :         if (subname)
     365           0 :             appendPQExpBuffer(firstsql, "(%s)", subname);
     366           0 :         appendPQExpBuffer(firstsql, " ON %s ", type);
     367           0 :         if (nspname && *nspname)
     368           0 :             appendPQExpBuffer(firstsql, "%s.", fmtId(nspname));
     369           0 :         appendPQExpBuffer(firstsql, "%s FROM %s;\n",
     370             :                           name, fmtId(owner));
     371             :     }
     372             : 
     373        2184 :     destroyPQExpBuffer(grantee);
     374        2184 :     destroyPQExpBuffer(grantor);
     375        2184 :     destroyPQExpBuffer(privs);
     376        2184 :     destroyPQExpBuffer(privswgo);
     377             : 
     378        2184 :     appendPQExpBuffer(sql, "%s%s", firstsql->data, secondsql->data);
     379        2184 :     destroyPQExpBuffer(firstsql);
     380        2184 :     destroyPQExpBuffer(secondsql);
     381             : 
     382        2184 :     if (aclitems)
     383        1998 :         free(aclitems);
     384             : 
     385        2184 :     if (raclitems)
     386         352 :         free(raclitems);
     387             : 
     388        2184 :     return ok;
     389             : }
     390             : 
     391             : /*
     392             :  * Build ALTER DEFAULT PRIVILEGES command(s) for single pg_default_acl entry.
     393             :  *
     394             :  *  type: the object type (TABLES, FUNCTIONS, etc)
     395             :  *  nspname: schema name, or NULL for global default privileges
     396             :  *  acls: the ACL string fetched from the database
     397             :  *  owner: username of privileges owner (will be passed through fmtId)
     398             :  *  remoteVersion: version of database
     399             :  *
     400             :  * Returns true if okay, false if could not parse the acl string.
     401             :  * The resulting commands (if any) are appended to the contents of 'sql'.
     402             :  */
     403             : bool
     404         122 : buildDefaultACLCommands(const char *type, const char *nspname,
     405             :                         const char *acls, const char *racls,
     406             :                         const char *initacls, const char *initracls,
     407             :                         const char *owner,
     408             :                         int remoteVersion,
     409             :                         PQExpBuffer sql)
     410             : {
     411             :     PQExpBuffer prefix;
     412             : 
     413         122 :     prefix = createPQExpBuffer();
     414             : 
     415             :     /*
     416             :      * We incorporate the target role directly into the command, rather than
     417             :      * playing around with SET ROLE or anything like that.  This is so that a
     418             :      * permissions error leads to nothing happening, rather than changing
     419             :      * default privileges for the wrong user.
     420             :      */
     421         122 :     appendPQExpBuffer(prefix, "ALTER DEFAULT PRIVILEGES FOR ROLE %s ",
     422             :                       fmtId(owner));
     423         122 :     if (nspname)
     424          42 :         appendPQExpBuffer(prefix, "IN SCHEMA %s ", fmtId(nspname));
     425             : 
     426         122 :     if (strlen(initacls) != 0 || strlen(initracls) != 0)
     427             :     {
     428           0 :         appendPQExpBufferStr(sql, "SELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\n");
     429           0 :         if (!buildACLCommands("", NULL, NULL, type,
     430             :                               initacls, initracls, owner,
     431           0 :                               prefix->data, remoteVersion, sql))
     432             :         {
     433           0 :             destroyPQExpBuffer(prefix);
     434           0 :             return false;
     435             :         }
     436           0 :         appendPQExpBufferStr(sql, "SELECT pg_catalog.binary_upgrade_set_record_init_privs(false);\n");
     437             :     }
     438             : 
     439         122 :     if (!buildACLCommands("", NULL, NULL, type,
     440             :                           acls, racls, owner,
     441         122 :                           prefix->data, remoteVersion, sql))
     442             :     {
     443           0 :         destroyPQExpBuffer(prefix);
     444           0 :         return false;
     445             :     }
     446             : 
     447         122 :     destroyPQExpBuffer(prefix);
     448             : 
     449         122 :     return true;
     450             : }
     451             : 
     452             : /*
     453             :  * This will parse an aclitem string, having the general form
     454             :  *      username=privilegecodes/grantor
     455             :  * or
     456             :  *      group groupname=privilegecodes/grantor
     457             :  * (the "group" case occurs only with servers before 8.1).
     458             :  *
     459             :  * Returns true on success, false on parse error.  On success, the components
     460             :  * of the string are returned in the PQExpBuffer parameters.
     461             :  *
     462             :  * The returned grantee string will be the dequoted username or groupname
     463             :  * (preceded with "group " in the latter case).  Note that a grant to PUBLIC
     464             :  * is represented by an empty grantee string.  The returned grantor is the
     465             :  * dequoted grantor name.  Privilege characters are decoded and split between
     466             :  * privileges with grant option (privswgo) and without (privs).
     467             :  *
     468             :  * Note: for cross-version compatibility, it's important to use ALL to
     469             :  * represent the privilege sets whenever appropriate.
     470             :  */
     471             : static bool
     472        2352 : parseAclItem(const char *item, const char *type,
     473             :              const char *name, const char *subname, int remoteVersion,
     474             :              PQExpBuffer grantee, PQExpBuffer grantor,
     475             :              PQExpBuffer privs, PQExpBuffer privswgo)
     476             : {
     477             :     char       *buf;
     478        2352 :     bool        all_with_go = true;
     479        2352 :     bool        all_without_go = true;
     480             :     char       *eqpos;
     481             :     char       *slpos;
     482             :     char       *pos;
     483             : 
     484        2352 :     buf = pg_strdup(item);
     485             : 
     486             :     /* user or group name is string up to = */
     487        2352 :     eqpos = copyAclUserName(grantee, buf);
     488        2352 :     if (*eqpos != '=')
     489             :     {
     490           0 :         pg_free(buf);
     491           0 :         return false;
     492             :     }
     493             : 
     494             :     /* grantor should appear after / */
     495        2352 :     slpos = strchr(eqpos + 1, '/');
     496        2352 :     if (slpos)
     497             :     {
     498        2352 :         *slpos++ = '\0';
     499        2352 :         slpos = copyAclUserName(grantor, slpos);
     500        2352 :         if (*slpos != '\0')
     501             :         {
     502           0 :             pg_free(buf);
     503           0 :             return false;
     504             :         }
     505             :     }
     506             :     else
     507             :     {
     508           0 :         pg_free(buf);
     509           0 :         return false;
     510             :     }
     511             : 
     512             :     /* privilege codes */
     513             : #define CONVERT_PRIV(code, keywd) \
     514             : do { \
     515             :     if ((pos = strchr(eqpos + 1, code))) \
     516             :     { \
     517             :         if (*(pos + 1) == '*') \
     518             :         { \
     519             :             AddAcl(privswgo, keywd, subname); \
     520             :             all_without_go = false; \
     521             :         } \
     522             :         else \
     523             :         { \
     524             :             AddAcl(privs, keywd, subname); \
     525             :             all_with_go = false; \
     526             :         } \
     527             :     } \
     528             :     else \
     529             :         all_with_go = all_without_go = false; \
     530             : } while (0)
     531             : 
     532        2352 :     resetPQExpBuffer(privs);
     533        2352 :     resetPQExpBuffer(privswgo);
     534             : 
     535        3116 :     if (strcmp(type, "TABLE") == 0 || strcmp(type, "SEQUENCE") == 0 ||
     536        1364 :         strcmp(type, "TABLES") == 0 || strcmp(type, "SEQUENCES") == 0)
     537             :     {
     538        1752 :         CONVERT_PRIV('r', "SELECT");
     539             : 
     540        3472 :         if (strcmp(type, "SEQUENCE") == 0 ||
     541        1720 :             strcmp(type, "SEQUENCES") == 0)
     542             :             /* sequence only */
     543          32 :             CONVERT_PRIV('U', "USAGE");
     544             :         else
     545             :         {
     546             :             /* table only */
     547        1720 :             CONVERT_PRIV('a', "INSERT");
     548        1720 :             CONVERT_PRIV('x', "REFERENCES");
     549             :             /* rest are not applicable to columns */
     550        1720 :             if (subname == NULL)
     551             :             {
     552         402 :                 CONVERT_PRIV('d', "DELETE");
     553         402 :                 CONVERT_PRIV('t', "TRIGGER");
     554         402 :                 if (remoteVersion >= 80400)
     555         402 :                     CONVERT_PRIV('D', "TRUNCATE");
     556             :             }
     557             :         }
     558             : 
     559             :         /* UPDATE */
     560        1752 :         CONVERT_PRIV('w', "UPDATE");
     561             :     }
     562        1116 :     else if (strcmp(type, "FUNCTION") == 0 ||
     563         516 :              strcmp(type, "FUNCTIONS") == 0)
     564         124 :         CONVERT_PRIV('X', "EXECUTE");
     565         952 :     else if (strcmp(type, "PROCEDURE") == 0 ||
     566         476 :              strcmp(type, "PROCEDURES") == 0)
     567           0 :         CONVERT_PRIV('X', "EXECUTE");
     568         476 :     else if (strcmp(type, "LANGUAGE") == 0)
     569          50 :         CONVERT_PRIV('U', "USAGE");
     570         728 :     else if (strcmp(type, "SCHEMA") == 0 ||
     571         302 :              strcmp(type, "SCHEMAS") == 0)
     572             :     {
     573         124 :         CONVERT_PRIV('C', "CREATE");
     574         124 :         CONVERT_PRIV('U', "USAGE");
     575             :     }
     576         302 :     else if (strcmp(type, "DATABASE") == 0)
     577             :     {
     578          10 :         CONVERT_PRIV('C', "CREATE");
     579          10 :         CONVERT_PRIV('c', "CONNECT");
     580          10 :         CONVERT_PRIV('T', "TEMPORARY");
     581             :     }
     582         292 :     else if (strcmp(type, "TABLESPACE") == 0)
     583           0 :         CONVERT_PRIV('C', "CREATE");
     584         414 :     else if (strcmp(type, "TYPE") == 0 ||
     585         122 :              strcmp(type, "TYPES") == 0)
     586         170 :         CONVERT_PRIV('U', "USAGE");
     587         122 :     else if (strcmp(type, "FOREIGN DATA WRAPPER") == 0)
     588          40 :         CONVERT_PRIV('U', "USAGE");
     589          82 :     else if (strcmp(type, "FOREIGN SERVER") == 0)
     590          40 :         CONVERT_PRIV('U', "USAGE");
     591          42 :     else if (strcmp(type, "FOREIGN TABLE") == 0)
     592           0 :         CONVERT_PRIV('r', "SELECT");
     593          42 :     else if (strcmp(type, "LARGE OBJECT") == 0)
     594             :     {
     595          42 :         CONVERT_PRIV('r', "SELECT");
     596          42 :         CONVERT_PRIV('w', "UPDATE");
     597             :     }
     598             :     else
     599           0 :         abort();
     600             : 
     601             : #undef CONVERT_PRIV
     602             : 
     603        2352 :     if (all_with_go)
     604             :     {
     605           0 :         resetPQExpBuffer(privs);
     606           0 :         printfPQExpBuffer(privswgo, "ALL");
     607           0 :         if (subname)
     608           0 :             appendPQExpBuffer(privswgo, "(%s)", subname);
     609             :     }
     610        2352 :     else if (all_without_go)
     611             :     {
     612         588 :         resetPQExpBuffer(privswgo);
     613         588 :         printfPQExpBuffer(privs, "ALL");
     614         588 :         if (subname)
     615           0 :             appendPQExpBuffer(privs, "(%s)", subname);
     616             :     }
     617             : 
     618        2352 :     pg_free(buf);
     619             : 
     620        2352 :     return true;
     621             : }
     622             : 
     623             : /*
     624             :  * Transfer a user or group name starting at *input into the output buffer,
     625             :  * dequoting if needed.  Returns a pointer to just past the input name.
     626             :  * The name is taken to end at an unquoted '=' or end of string.
     627             :  */
     628             : static char *
     629        4704 : copyAclUserName(PQExpBuffer output, char *input)
     630             : {
     631        4704 :     resetPQExpBuffer(output);
     632             : 
     633       49044 :     while (*input && *input != '=')
     634             :     {
     635             :         /*
     636             :          * If user name isn't quoted, then just add it to the output buffer
     637             :          */
     638       39636 :         if (*input != '"')
     639       39636 :             appendPQExpBufferChar(output, *input++);
     640             :         else
     641             :         {
     642             :             /* Otherwise, it's a quoted username */
     643           0 :             input++;
     644             :             /* Loop until we come across an unescaped quote */
     645           0 :             while (!(*input == '"' && *(input + 1) != '"'))
     646             :             {
     647           0 :                 if (*input == '\0')
     648           0 :                     return input;   /* really a syntax error... */
     649             : 
     650             :                 /*
     651             :                  * Quoting convention is to escape " as "".  Keep this code in
     652             :                  * sync with putid() in backend's acl.c.
     653             :                  */
     654           0 :                 if (*input == '"' && *(input + 1) == '"')
     655           0 :                     input++;
     656           0 :                 appendPQExpBufferChar(output, *input++);
     657             :             }
     658           0 :             input++;
     659             :         }
     660             :     }
     661        4704 :     return input;
     662             : }
     663             : 
     664             : /*
     665             :  * Append a privilege keyword to a keyword list, inserting comma if needed.
     666             :  */
     667             : static void
     668        3130 : AddAcl(PQExpBuffer aclbuf, const char *keyword, const char *subname)
     669             : {
     670        3130 :     if (aclbuf->len > 0)
     671         778 :         appendPQExpBufferChar(aclbuf, ',');
     672        3130 :     appendPQExpBufferStr(aclbuf, keyword);
     673        3130 :     if (subname)
     674        1318 :         appendPQExpBuffer(aclbuf, "(%s)", subname);
     675        3130 : }
     676             : 
     677             : 
     678             : /*
     679             :  * buildShSecLabelQuery
     680             :  *
     681             :  * Build a query to retrieve security labels for a shared object.
     682             :  * The object is identified by its OID plus the name of the catalog
     683             :  * it can be found in (e.g., "pg_database" for database names).
     684             :  * The query is appended to "sql".  (We don't execute it here so as to
     685             :  * keep this file free of assumptions about how to deal with SQL errors.)
     686             :  */
     687             : void
     688         164 : buildShSecLabelQuery(PGconn *conn, const char *catalog_name, Oid objectId,
     689             :                      PQExpBuffer sql)
     690             : {
     691         164 :     appendPQExpBuffer(sql,
     692             :                       "SELECT provider, label FROM pg_catalog.pg_shseclabel "
     693             :                       "WHERE classoid = 'pg_catalog.%s'::pg_catalog.regclass "
     694             :                       "AND objoid = '%u'", catalog_name, objectId);
     695         164 : }
     696             : 
     697             : /*
     698             :  * emitShSecLabels
     699             :  *
     700             :  * Construct SECURITY LABEL commands using the data retrieved by the query
     701             :  * generated by buildShSecLabelQuery, and append them to "buffer".
     702             :  * Here, the target object is identified by its type name (e.g. "DATABASE")
     703             :  * and its name (not pre-quoted).
     704             :  */
     705             : void
     706         164 : emitShSecLabels(PGconn *conn, PGresult *res, PQExpBuffer buffer,
     707             :                 const char *objtype, const char *objname)
     708             : {
     709             :     int         i;
     710             : 
     711         164 :     for (i = 0; i < PQntuples(res); i++)
     712             :     {
     713           0 :         char       *provider = PQgetvalue(res, i, 0);
     714           0 :         char       *label = PQgetvalue(res, i, 1);
     715             : 
     716             :         /* must use fmtId result before calling it again */
     717           0 :         appendPQExpBuffer(buffer,
     718             :                           "SECURITY LABEL FOR %s ON %s",
     719             :                           fmtId(provider), objtype);
     720           0 :         appendPQExpBuffer(buffer,
     721             :                           " %s IS ",
     722             :                           fmtId(objname));
     723           0 :         appendStringLiteralConn(buffer, label, conn);
     724           0 :         appendPQExpBufferStr(buffer, ";\n");
     725             :     }
     726         164 : }
     727             : 
     728             : /*
     729             :  * buildACLQueries
     730             :  *
     731             :  * Build the subqueries to extract out the correct set of ACLs to be
     732             :  * GRANT'd and REVOKE'd for the specific kind of object, accounting for any
     733             :  * initial privileges (from pg_init_privs) and based on if we are in binary
     734             :  * upgrade mode or not.
     735             :  *
     736             :  * Also builds subqueries to extract out the set of ACLs to go from the object
     737             :  * default privileges to the privileges in pg_init_privs, if we are in binary
     738             :  * upgrade mode, so that those privileges can be set up and recorded in the new
     739             :  * cluster before the regular privileges are added on top of those.
     740             :  */
     741             : void
     742        2158 : buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
     743             :                 PQExpBuffer init_acl_subquery, PQExpBuffer init_racl_subquery,
     744             :                 const char *acl_column, const char *acl_owner,
     745             :                 const char *obj_kind, bool binary_upgrade)
     746             : {
     747             :     /*
     748             :      * To get the delta from what the permissions were at creation time
     749             :      * (either initdb or CREATE EXTENSION) vs. what they are now, we have to
     750             :      * look at two things:
     751             :      *
     752             :      * What privileges have been added, which we calculate by extracting all
     753             :      * the current privileges (using the set of default privileges for the
     754             :      * object type if current privileges are NULL) and then removing those
     755             :      * which existed at creation time (again, using the set of default
     756             :      * privileges for the object type if there were no creation time
     757             :      * privileges).
     758             :      *
     759             :      * What privileges have been removed, which we calculate by extracting the
     760             :      * privileges as they were at creation time (or the default privileges, as
     761             :      * above), and then removing the current privileges (or the default
     762             :      * privileges, if current privileges are NULL).
     763             :      *
     764             :      * As a good cross-check, both directions of these checks should result in
     765             :      * the empty set if both the current ACL and the initial privs are NULL
     766             :      * (meaning, in practice, that the default ACLs were there at init time
     767             :      * and is what the current privileges are).
     768             :      *
     769             :      * We always perform this delta on all ACLs and expect that by the time
     770             :      * these are run the initial privileges will be in place, even in a binary
     771             :      * upgrade situation (see below).
     772             :      *
     773             :      * Finally, the order in which privileges are in the ACL string (the order
     774             :      * they been GRANT'd in, which the backend maintains) must be preserved to
     775             :      * ensure that GRANTs WITH GRANT OPTION and subsequent GRANTs based on
     776             :      * those are dumped in the correct order.
     777             :      */
     778        2158 :     printfPQExpBuffer(acl_subquery,
     779             :                       "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM "
     780             :                       "(SELECT acl, row_n FROM "
     781             :                       "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) "
     782             :                       "WITH ORDINALITY AS perm(acl,row_n) "
     783             :                       "WHERE NOT EXISTS ( "
     784             :                       "SELECT 1 FROM "
     785             :                       "pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault(%s,%s))) "
     786             :                       "AS init(init_acl) WHERE acl = init_acl)) as foo)",
     787             :                       acl_column,
     788             :                       obj_kind,
     789             :                       acl_owner,
     790             :                       obj_kind,
     791             :                       acl_owner);
     792             : 
     793        2158 :     printfPQExpBuffer(racl_subquery,
     794             :                       "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM "
     795             :                       "(SELECT acl, row_n FROM "
     796             :                       "pg_catalog.unnest(coalesce(pip.initprivs,pg_catalog.acldefault(%s,%s))) "
     797             :                       "WITH ORDINALITY AS initp(acl,row_n) "
     798             :                       "WHERE NOT EXISTS ( "
     799             :                       "SELECT 1 FROM "
     800             :                       "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) "
     801             :                       "AS permp(orig_acl) WHERE acl = orig_acl)) as foo)",
     802             :                       obj_kind,
     803             :                       acl_owner,
     804             :                       acl_column,
     805             :                       obj_kind,
     806             :                       acl_owner);
     807             : 
     808             :     /*
     809             :      * In binary upgrade mode we don't run the extension script but instead
     810             :      * dump out the objects independently and then recreate them.  To preserve
     811             :      * the initial privileges which were set on extension objects, we need to
     812             :      * grab the set of GRANT and REVOKE commands necessary to get from the
     813             :      * default privileges of an object to the initial privileges as recorded
     814             :      * in pg_init_privs.
     815             :      *
     816             :      * These will then be run ahead of the regular ACL commands, which were
     817             :      * calculated using the queries above, inside of a block which sets a flag
     818             :      * to indicate that the backend should record the results of these GRANT
     819             :      * and REVOKE statements into pg_init_privs.  This is how we preserve the
     820             :      * contents of that catalog across binary upgrades.
     821             :      */
     822        2158 :     if (binary_upgrade)
     823             :     {
     824         218 :         printfPQExpBuffer(init_acl_subquery,
     825             :                           "CASE WHEN privtype = 'e' THEN "
     826             :                           "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM "
     827             :                           "(SELECT acl, row_n FROM pg_catalog.unnest(pip.initprivs) "
     828             :                           "WITH ORDINALITY AS initp(acl,row_n) "
     829             :                           "WHERE NOT EXISTS ( "
     830             :                           "SELECT 1 FROM "
     831             :                           "pg_catalog.unnest(pg_catalog.acldefault(%s,%s)) "
     832             :                           "AS privm(orig_acl) WHERE acl = orig_acl)) as foo) END",
     833             :                           obj_kind,
     834             :                           acl_owner);
     835             : 
     836         218 :         printfPQExpBuffer(init_racl_subquery,
     837             :                           "CASE WHEN privtype = 'e' THEN "
     838             :                           "(SELECT pg_catalog.array_agg(acl) FROM "
     839             :                           "(SELECT acl, row_n FROM "
     840             :                           "pg_catalog.unnest(pg_catalog.acldefault(%s,%s)) "
     841             :                           "WITH ORDINALITY AS privp(acl,row_n) "
     842             :                           "WHERE NOT EXISTS ( "
     843             :                           "SELECT 1 FROM pg_catalog.unnest(pip.initprivs) "
     844             :                           "AS initp(init_acl) WHERE acl = init_acl)) as foo) END",
     845             :                           obj_kind,
     846             :                           acl_owner);
     847             :     }
     848             :     else
     849             :     {
     850        1940 :         printfPQExpBuffer(init_acl_subquery, "NULL");
     851        1940 :         printfPQExpBuffer(init_racl_subquery, "NULL");
     852             :     }
     853        2158 : }
     854             : 
     855             : /*
     856             :  * Detect whether the given GUC variable is of GUC_LIST_QUOTE type.
     857             :  *
     858             :  * It'd be better if we could inquire this directly from the backend; but even
     859             :  * if there were a function for that, it could only tell us about variables
     860             :  * currently known to guc.c, so that it'd be unsafe for extensions to declare
     861             :  * GUC_LIST_QUOTE variables anyway.  Lacking a solution for that, it doesn't
     862             :  * seem worth the work to do more than have this list, which must be kept in
     863             :  * sync with the variables actually marked GUC_LIST_QUOTE in guc.c.
     864             :  */
     865             : bool
     866          78 : variable_is_guc_list_quote(const char *name)
     867             : {
     868         156 :     if (pg_strcasecmp(name, "temp_tablespaces") == 0 ||
     869         156 :         pg_strcasecmp(name, "session_preload_libraries") == 0 ||
     870         156 :         pg_strcasecmp(name, "shared_preload_libraries") == 0 ||
     871         150 :         pg_strcasecmp(name, "local_preload_libraries") == 0 ||
     872          72 :         pg_strcasecmp(name, "search_path") == 0)
     873          12 :         return true;
     874             :     else
     875          66 :         return false;
     876             : }
     877             : 
     878             : /*
     879             :  * SplitGUCList --- parse a string containing identifiers or file names
     880             :  *
     881             :  * This is used to split the value of a GUC_LIST_QUOTE GUC variable, without
     882             :  * presuming whether the elements will be taken as identifiers or file names.
     883             :  * See comparable code in src/backend/utils/adt/varlena.c.
     884             :  *
     885             :  * Inputs:
     886             :  *  rawstring: the input string; must be overwritable!  On return, it's
     887             :  *             been modified to contain the separated identifiers.
     888             :  *  separator: the separator punctuation expected between identifiers
     889             :  *             (typically '.' or ',').  Whitespace may also appear around
     890             :  *             identifiers.
     891             :  * Outputs:
     892             :  *  namelist: receives a malloc'd, null-terminated array of pointers to
     893             :  *            identifiers within rawstring.  Caller should free this
     894             :  *            even on error return.
     895             :  *
     896             :  * Returns true if okay, false if there is a syntax error in the string.
     897             :  */
     898             : bool
     899          12 : SplitGUCList(char *rawstring, char separator,
     900             :              char ***namelist)
     901             : {
     902          12 :     char       *nextp = rawstring;
     903          12 :     bool        done = false;
     904             :     char      **nextptr;
     905             : 
     906             :     /*
     907             :      * Since we disallow empty identifiers, this is a conservative
     908             :      * overestimate of the number of pointers we could need.  Allow one for
     909             :      * list terminator.
     910             :      */
     911          12 :     *namelist = nextptr = (char **)
     912          12 :         pg_malloc((strlen(rawstring) / 2 + 2) * sizeof(char *));
     913          12 :     *nextptr = NULL;
     914             : 
     915          24 :     while (isspace((unsigned char) *nextp))
     916           0 :         nextp++;                /* skip leading whitespace */
     917             : 
     918          12 :     if (*nextp == '\0')
     919           0 :         return true;            /* allow empty string */
     920             : 
     921             :     /* At the top of the loop, we are at start of a new identifier. */
     922             :     do
     923             :     {
     924             :         char       *curname;
     925             :         char       *endp;
     926             : 
     927          30 :         if (*nextp == '"')
     928             :         {
     929             :             /* Quoted name --- collapse quote-quote pairs */
     930          24 :             curname = nextp + 1;
     931             :             for (;;)
     932             :             {
     933          36 :                 endp = strchr(nextp + 1, '"');
     934          30 :                 if (endp == NULL)
     935           0 :                     return false;   /* mismatched quotes */
     936          30 :                 if (endp[1] != '"')
     937          24 :                     break;      /* found end of quoted name */
     938             :                 /* Collapse adjacent quotes into one quote, and look again */
     939           6 :                 memmove(endp, endp + 1, strlen(endp));
     940           6 :                 nextp = endp;
     941             :             }
     942             :             /* endp now points at the terminating quote */
     943          24 :             nextp = endp + 1;
     944             :         }
     945             :         else
     946             :         {
     947             :             /* Unquoted name --- extends to separator or whitespace */
     948           6 :             curname = nextp;
     949         132 :             while (*nextp && *nextp != separator &&
     950          60 :                    !isspace((unsigned char) *nextp))
     951          60 :                 nextp++;
     952           6 :             endp = nextp;
     953           6 :             if (curname == nextp)
     954           0 :                 return false;   /* empty unquoted name not allowed */
     955             :         }
     956             : 
     957          60 :         while (isspace((unsigned char) *nextp))
     958           0 :             nextp++;            /* skip trailing whitespace */
     959             : 
     960          30 :         if (*nextp == separator)
     961             :         {
     962          18 :             nextp++;
     963          54 :             while (isspace((unsigned char) *nextp))
     964          18 :                 nextp++;        /* skip leading whitespace for next */
     965             :             /* we expect another name, so done remains false */
     966             :         }
     967          12 :         else if (*nextp == '\0')
     968          12 :             done = true;
     969             :         else
     970           0 :             return false;       /* invalid syntax */
     971             : 
     972             :         /* Now safe to overwrite separator with a null */
     973          30 :         *endp = '\0';
     974             : 
     975             :         /*
     976             :          * Finished isolating current name --- add it to output array
     977             :          */
     978          30 :         *nextptr++ = curname;
     979             : 
     980             :         /* Loop back if we didn't reach end of string */
     981          30 :     } while (!done);
     982             : 
     983          12 :     *nextptr = NULL;
     984          12 :     return true;
     985             : }
     986             : 
     987             : /*
     988             :  * Helper function for dumping "ALTER DATABASE/ROLE SET ..." commands.
     989             :  *
     990             :  * Parse the contents of configitem (a "name=value" string), wrap it in
     991             :  * a complete ALTER command, and append it to buf.
     992             :  *
     993             :  * type is DATABASE or ROLE, and name is the name of the database or role.
     994             :  * If we need an "IN" clause, type2 and name2 similarly define what to put
     995             :  * there; otherwise they should be NULL.
     996             :  * conn is used only to determine string-literal quoting conventions.
     997             :  */
     998             : void
     999          36 : makeAlterConfigCommand(PGconn *conn, const char *configitem,
    1000             :                        const char *type, const char *name,
    1001             :                        const char *type2, const char *name2,
    1002             :                        PQExpBuffer buf)
    1003             : {
    1004             :     char       *mine;
    1005             :     char       *pos;
    1006             : 
    1007             :     /* Parse the configitem.  If we can't find an "=", silently do nothing. */
    1008          36 :     mine = pg_strdup(configitem);
    1009          36 :     pos = strchr(mine, '=');
    1010          36 :     if (pos == NULL)
    1011             :     {
    1012           0 :         pg_free(mine);
    1013           0 :         return;
    1014             :     }
    1015          36 :     *pos++ = '\0';
    1016             : 
    1017             :     /* Build the command, with suitable quoting for everything. */
    1018          36 :     appendPQExpBuffer(buf, "ALTER %s %s ", type, fmtId(name));
    1019          36 :     if (type2 != NULL && name2 != NULL)
    1020           0 :         appendPQExpBuffer(buf, "IN %s %s ", type2, fmtId(name2));
    1021          36 :     appendPQExpBuffer(buf, "SET %s TO ", fmtId(mine));
    1022             : 
    1023             :     /*
    1024             :      * Variables that are marked GUC_LIST_QUOTE were already fully quoted by
    1025             :      * flatten_set_variable_args() before they were put into the setconfig
    1026             :      * array.  However, because the quoting rules used there aren't exactly
    1027             :      * like SQL's, we have to break the list value apart and then quote the
    1028             :      * elements as string literals.  (The elements may be double-quoted as-is,
    1029             :      * but we can't just feed them to the SQL parser; it would do the wrong
    1030             :      * thing with elements that are zero-length or longer than NAMEDATALEN.)
    1031             :      *
    1032             :      * Variables that are not so marked should just be emitted as simple
    1033             :      * string literals.  If the variable is not known to
    1034             :      * variable_is_guc_list_quote(), we'll do that; this makes it unsafe to
    1035             :      * use GUC_LIST_QUOTE for extension variables.
    1036             :      */
    1037          36 :     if (variable_is_guc_list_quote(mine))
    1038             :     {
    1039             :         char      **namelist;
    1040             :         char      **nameptr;
    1041             : 
    1042             :         /* Parse string into list of identifiers */
    1043             :         /* this shouldn't fail really */
    1044           0 :         if (SplitGUCList(pos, ',', &namelist))
    1045             :         {
    1046           0 :             for (nameptr = namelist; *nameptr; nameptr++)
    1047             :             {
    1048           0 :                 if (nameptr != namelist)
    1049           0 :                     appendPQExpBufferStr(buf, ", ");
    1050           0 :                 appendStringLiteralConn(buf, *nameptr, conn);
    1051             :             }
    1052             :         }
    1053           0 :         pg_free(namelist);
    1054             :     }
    1055             :     else
    1056          36 :         appendStringLiteralConn(buf, pos, conn);
    1057             : 
    1058          36 :     appendPQExpBufferStr(buf, ";\n");
    1059             : 
    1060          36 :     pg_free(mine);
    1061             : }

Generated by: LCOV version 1.13