Install extensions using update scripts (was Re: Remove superuser() checks from pgstattuple) - Mailing list pgsql-hackers

From Tom Lane
Subject Install extensions using update scripts (was Re: Remove superuser() checks from pgstattuple)
Date
Msg-id 25703.1473128649@sss.pgh.pa.us
Whole thread Raw
In response to Re: Remove superuser() checks from pgstattuple  (Andres Freund <andres@anarazel.de>)
Responses Re: Install extensions using update scripts (was Re: Remove superuser() checks from pgstattuple)  (Andres Freund <andres@anarazel.de>)
List pgsql-hackers
Andres Freund <andres@anarazel.de> writes:
> On September 4, 2016 6:33:30 PM PDT, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I think nearly all of the
>> infrastructure for this is already there in extension.c.

> Yes, it doesn't sound very hard...

I poked at this a bit, and indeed it's not that hard.  Attached is a
proposed patch (code-complete, I think, but no docs as yet).
Aside from touching CREATE EXTENSION itself, this modifies the
pg_available_extension_versions view to show versions that are installable
on the basis of update chains.  I think pg_extension_update_paths() needs
no changes, though.

Ordinarily I'd be willing to stick this on the queue for the next
commitfest, but it seems like we ought to try to get it pushed now
so that Stephen can make use of the feature for his superuser changes.
Thoughts?

            regards, tom lane

diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index fa861e6..4d82527 100644
*** a/src/backend/commands/extension.c
--- b/src/backend/commands/extension.c
*************** typedef struct ExtensionVersionInfo
*** 100,105 ****
--- 100,106 ----
  static List *find_update_path(List *evi_list,
                   ExtensionVersionInfo *evi_start,
                   ExtensionVersionInfo *evi_target,
+                  bool reject_indirect,
                   bool reinitialize);
  static void get_available_versions_for_extension(ExtensionControlFile *pcontrol,
                                       Tuplestorestate *tupstore,
*************** identify_update_path(ExtensionControlFil
*** 1071,1077 ****
      evi_target = get_ext_ver_info(newVersion, &evi_list);

      /* Find shortest path */
!     result = find_update_path(evi_list, evi_start, evi_target, false);

      if (result == NIL)
          ereport(ERROR,
--- 1072,1078 ----
      evi_target = get_ext_ver_info(newVersion, &evi_list);

      /* Find shortest path */
!     result = find_update_path(evi_list, evi_start, evi_target, false, false);

      if (result == NIL)
          ereport(ERROR,
*************** identify_update_path(ExtensionControlFil
*** 1086,1091 ****
--- 1087,1095 ----
   * Apply Dijkstra's algorithm to find the shortest path from evi_start to
   * evi_target.
   *
+  * If reject_indirect is true, ignore paths that go through installable
+  * versions (presumably, caller will consider starting from such versions).
+  *
   * If reinitialize is false, assume the ExtensionVersionInfo list has not
   * been used for this before, and the initialization done by get_ext_ver_info
   * is still good.
*************** static List *
*** 1097,1102 ****
--- 1101,1107 ----
  find_update_path(List *evi_list,
                   ExtensionVersionInfo *evi_start,
                   ExtensionVersionInfo *evi_target,
+                  bool reject_indirect,
                   bool reinitialize)
  {
      List       *result;
*************** find_update_path(List *evi_list,
*** 1105,1110 ****
--- 1110,1117 ----

      /* Caller error if start == target */
      Assert(evi_start != evi_target);
+     /* Caller error if reject_indirect and target is installable */
+     Assert(!(reject_indirect && evi_target->installable));

      if (reinitialize)
      {
*************** find_update_path(List *evi_list,
*** 1131,1136 ****
--- 1138,1146 ----
              ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc);
              int            newdist;

+             /* if reject_indirect, treat installable versions as unreachable */
+             if (reject_indirect && evi2->installable)
+                 continue;
              newdist = evi->distance + 1;
              if (newdist < evi2->distance)
              {
*************** find_update_path(List *evi_list,
*** 1167,1172 ****
--- 1177,1239 ----
  }

  /*
+  * Given a target version that is not directly installable, find the
+  * best installation sequence starting from a directly-installable version.
+  *
+  * evi_list: previously-collected version update graph
+  * evi_target: member of that list that we want to reach
+  *
+  * Returns the best starting-point version, or NULL if there is none.
+  * On success, *best_path is set to the path from the start point.
+  *
+  * If there's more than one possible start point, prefer shorter update paths,
+  * and break any ties arbitrarily on the basis of strcmp'ing the starting
+  * versions' names.
+  */
+ static ExtensionVersionInfo *
+ find_install_path(List *evi_list, ExtensionVersionInfo *evi_target,
+                   List **best_path)
+ {
+     ExtensionVersionInfo *evi_start = NULL;
+     ListCell   *lc;
+
+     /* Target should not be installable */
+     Assert(!evi_target->installable);
+
+     *best_path = NIL;
+
+     /* Consider all installable versions as start points */
+     foreach(lc, evi_list)
+     {
+         ExtensionVersionInfo *evi1 = (ExtensionVersionInfo *) lfirst(lc);
+         List       *path;
+
+         if (!evi1->installable)
+             continue;
+
+         /*
+          * Find shortest path from evi1 to evi_target; but no need to consider
+          * paths going through other installable versions.
+          */
+         path = find_update_path(evi_list, evi1, evi_target, true, true);
+         if (path == NIL)
+             continue;
+
+         /* Remember best path */
+         if (evi_start == NULL ||
+             list_length(path) < list_length(*best_path) ||
+             (list_length(path) == list_length(*best_path) &&
+              strcmp(evi_start->name, evi1->name) < 0))
+         {
+             evi_start = evi1;
+             *best_path = path;
+         }
+     }
+
+     return evi_start;
+ }
+
+ /*
   * CREATE EXTENSION worker
   *
   * When CASCADE is specified, CreateExtensionInternal() recurses if required
*************** CreateExtensionInternal(CreateExtensionS
*** 1264,1274 ****
      check_valid_version_name(versionName);

      /*
!      * Determine the (unpackaged) version to update from, if any, and then
!      * figure out what sequence of update scripts we need to apply.
       */
      if (d_old_version && d_old_version->arg)
      {
          oldVersionName = strVal(d_old_version->arg);
          check_valid_version_name(oldVersionName);

--- 1331,1348 ----
      check_valid_version_name(versionName);

      /*
!      * Figure out which script(s) we need to run to install the desired
!      * version of the extension.  If we do not have a script that directly
!      * does what is needed, we try to find a sequence of update scripts that
!      * will get us there.
       */
      if (d_old_version && d_old_version->arg)
      {
+         /*
+          * "FROM old_version" was specified, indicating that we're trying to
+          * update from some unpackaged version of the extension.  Locate a
+          * series of update scripts that will do it.
+          */
          oldVersionName = strVal(d_old_version->arg);
          check_valid_version_name(oldVersionName);

*************** CreateExtensionInternal(CreateExtensionS
*** 1304,1311 ****
      }
      else
      {
          oldVersionName = NULL;
!         updateVersions = NIL;
      }

      /*
--- 1378,1438 ----
      }
      else
      {
+         /*
+          * No FROM, so we're installing from scratch.  If there is an install
+          * script for the desired version, we only need to run that one.
+          */
+         char       *filename;
+         struct stat fst;
+
          oldVersionName = NULL;
!
!         filename = get_extension_script_filename(pcontrol, NULL, versionName);
!         if (stat(filename, &fst) == 0)
!         {
!             /* Easy, no extra scripts */
!             updateVersions = NIL;
!         }
!         else
!         {
!             /* Look for best way to install this version */
!             List       *evi_list;
!             ExtensionVersionInfo *evi_target;
!
!             /* Extract the version update graph from the script directory */
!             evi_list = get_ext_ver_list(pcontrol);
!
!             /* Identify the target version */
!             evi_target = get_ext_ver_info(versionName, &evi_list);
!
!             /*
!              * We don't expect it to be installable, but maybe somebody added
!              * a suitable script right after our stat() test.
!              */
!             if (evi_target->installable)
!             {
!                 /* Easy, no extra scripts */
!                 updateVersions = NIL;
!             }
!             else
!             {
!                 /* Identify best path to reach target */
!                 ExtensionVersionInfo *evi_start;
!
!                 evi_start = find_install_path(evi_list, evi_target,
!                                               &updateVersions);
!
!                 /* Fail if no path ... */
!                 if (evi_start == NULL)
!                     ereport(ERROR,
!                             (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
!                              errmsg("extension \"%s\" has no installation script for version \"%s\"",
!                                     pcontrol->name, versionName)));
!
!                 /* Otherwise, install best starting point and then upgrade */
!                 versionName = evi_start->name;
!             }
!         }
      }

      /*
*************** get_available_versions_for_extension(Ext
*** 1910,1952 ****
                                       Tuplestorestate *tupstore,
                                       TupleDesc tupdesc)
  {
!     int            extnamelen = strlen(pcontrol->name);
!     char       *location;
!     DIR           *dir;
!     struct dirent *de;

!     location = get_extension_script_directory(pcontrol);
!     dir = AllocateDir(location);
!     /* Note this will fail if script directory doesn't exist */
!     while ((de = ReadDir(dir, location)) != NULL)
      {
          ExtensionControlFile *control;
-         char       *vername;
          Datum        values[7];
          bool        nulls[7];

!         /* must be a .sql file ... */
!         if (!is_extension_script_filename(de->d_name))
!             continue;
!
!         /* ... matching extension name followed by separator */
!         if (strncmp(de->d_name, pcontrol->name, extnamelen) != 0 ||
!             de->d_name[extnamelen] != '-' ||
!             de->d_name[extnamelen + 1] != '-')
!             continue;
!
!         /* extract version name from 'extname--something.sql' filename */
!         vername = pstrdup(de->d_name + extnamelen + 2);
!         *strrchr(vername, '.') = '\0';
!
!         /* ignore it if it's an update script */
!         if (strstr(vername, "--"))
              continue;

          /*
           * Fetch parameters for specific version (pcontrol is not changed)
           */
!         control = read_extension_aux_control_file(pcontrol, vername);

          memset(values, 0, sizeof(values));
          memset(nulls, 0, sizeof(nulls));
--- 2037,2064 ----
                                       Tuplestorestate *tupstore,
                                       TupleDesc tupdesc)
  {
!     List       *evi_list;
!     ListCell   *lc;

!     /* Extract the version update graph from the script directory */
!     evi_list = get_ext_ver_list(pcontrol);
!
!     /* For each installable version ... */
!     foreach(lc, evi_list)
      {
+         ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
          ExtensionControlFile *control;
          Datum        values[7];
          bool        nulls[7];
+         ListCell   *lc2;

!         if (!evi->installable)
              continue;

          /*
           * Fetch parameters for specific version (pcontrol is not changed)
           */
!         control = read_extension_aux_control_file(pcontrol, evi->name);

          memset(values, 0, sizeof(values));
          memset(nulls, 0, sizeof(nulls));
*************** get_available_versions_for_extension(Ext
*** 1955,1961 ****
          values[0] = DirectFunctionCall1(namein,
                                          CStringGetDatum(control->name));
          /* version */
!         values[1] = CStringGetTextDatum(vername);
          /* superuser */
          values[2] = BoolGetDatum(control->superuser);
          /* relocatable */
--- 2067,2073 ----
          values[0] = DirectFunctionCall1(namein,
                                          CStringGetDatum(control->name));
          /* version */
!         values[1] = CStringGetTextDatum(evi->name);
          /* superuser */
          values[2] = BoolGetDatum(control->superuser);
          /* relocatable */
*************** get_available_versions_for_extension(Ext
*** 1998,2006 ****
              values[6] = CStringGetTextDatum(control->comment);

          tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-     }

!     FreeDir(dir);
  }

  /*
--- 2110,2137 ----
              values[6] = CStringGetTextDatum(control->comment);

          tuplestore_putvalues(tupstore, tupdesc, values, nulls);

!         /*
!          * Find all non-directly-installable versions that would be installed
!          * starting from this version, and report them with the same
!          * control-file parameters.
!          */
!         foreach(lc2, evi_list)
!         {
!             ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc2);
!             List       *best_path;
!
!             if (evi2->installable)
!                 continue;
!             if (find_install_path(evi_list, evi2, &best_path) == evi)
!             {
!                 /* replace only the version column */
!                 values[1] = CStringGetTextDatum(evi2->name);
!
!                 tuplestore_putvalues(tupstore, tupdesc, values, nulls);
!             }
!         }
!     }
  }

  /*
*************** pg_extension_update_paths(PG_FUNCTION_AR
*** 2072,2078 ****
                  continue;

              /* Find shortest path from evi1 to evi2 */
!             path = find_update_path(evi_list, evi1, evi2, true);

              /* Emit result row */
              memset(values, 0, sizeof(values));
--- 2203,2209 ----
                  continue;

              /* Find shortest path from evi1 to evi2 */
!             path = find_update_path(evi_list, evi1, evi2, false, true);

              /* Emit result row */
              memset(values, 0, sizeof(values));

pgsql-hackers by date:

Previous
From: Amit Langote
Date:
Subject: Comment Typo
Next
From: Andres Freund
Date:
Subject: Re: Install extensions using update scripts (was Re: Remove superuser() checks from pgstattuple)