Re: pg_dump versus rules, once again

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: pg_dump versus rules, once again
Дата
Msg-id 29254.1479351649@sss.pgh.pa.us
обсуждение исходный текст
Ответ на pg_dump versus rules, once again  (Tom Lane <tgl@sss.pgh.pa.us>)
Ответы Re: pg_dump versus rules, once again
Список pgsql-hackers
I wrote:
> We've talked before about replacing this _RETURN-rule business with
> CREATE OR REPLACE VIEW, ie the idea would be for pg_dump to first emit
> a dummy view with the right column names/types, say
> CREATE VIEW vv AS SELECT null::int AS f1, null::text AS f2;
> and then later when it's safe, emit CREATE OR REPLACE VIEW with the view's
> real query.

Here's a proposed patch for that.  It turns out there are some other
kluges that can be gotten rid of while we're at it: no longer need the
idea of reloptions being attached to a rule, for instance.

The changes in pg_backup_archiver.c would have to be back-patched
into all versions supporting --if-exists, so that they don't fail
on dump archives produced by patched versions.  We could possibly
put the rest only into HEAD; I remain of two minds about that.

            regards, tom lane

diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index b938d79..b89bd99 100644
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
*************** RestoreArchive(Archive *AHX)
*** 521,527 ****
                           * knows how to do it, without depending on
                           * te->dropStmt; use that.  For other objects we need
                           * to parse the command.
-                          *
                           */
                          if (strncmp(te->desc, "BLOB", 4) == 0)
                          {
--- 521,526 ----
*************** RestoreArchive(Archive *AHX)
*** 529,538 ****
                          }
                          else
                          {
-                             char        buffer[40];
-                             char       *mark;
                              char       *dropStmt = pg_strdup(te->dropStmt);
!                             char       *dropStmtPtr = dropStmt;
                              PQExpBuffer ftStmt = createPQExpBuffer();

                              /*
--- 528,535 ----
                          }
                          else
                          {
                              char       *dropStmt = pg_strdup(te->dropStmt);
!                             char       *dropStmtOrig = dropStmt;
                              PQExpBuffer ftStmt = createPQExpBuffer();

                              /*
*************** RestoreArchive(Archive *AHX)
*** 549,566 ****
                              /*
                               * ALTER TABLE..ALTER COLUMN..DROP DEFAULT does
                               * not support the IF EXISTS clause, and therefore
!                              * we simply emit the original command for such
!                              * objects. For other objects, we need to extract
!                              * the first part of the DROP which includes the
!                              * object type. Most of the time this matches
                               * te->desc, so search for that; however for the
                               * different kinds of CONSTRAINTs, we know to
                               * search for hardcoded "DROP CONSTRAINT" instead.
                               */
!                             if (strcmp(te->desc, "DEFAULT") == 0)
                                  appendPQExpBufferStr(ftStmt, dropStmt);
                              else
                              {
                                  if (strcmp(te->desc, "CONSTRAINT") == 0 ||
                                   strcmp(te->desc, "CHECK CONSTRAINT") == 0 ||
                                      strcmp(te->desc, "FK CONSTRAINT") == 0)
--- 546,573 ----
                              /*
                               * ALTER TABLE..ALTER COLUMN..DROP DEFAULT does
                               * not support the IF EXISTS clause, and therefore
!                              * we simply emit the original command for DEFAULT
!                              * objects (modulo the adjustment made above).
!                              *
!                              * If we used CREATE OR REPLACE VIEW as a means of
!                              * quasi-dropping an ON SELECT rule, that should
!                              * be emitted unchanged as well.
!                              *
!                              * For other object types, we need to extract the
!                              * first part of the DROP which includes the
!                              * object type.  Most of the time this matches
                               * te->desc, so search for that; however for the
                               * different kinds of CONSTRAINTs, we know to
                               * search for hardcoded "DROP CONSTRAINT" instead.
                               */
!                             if (strcmp(te->desc, "DEFAULT") == 0 ||
!                                 strncmp(dropStmt, "CREATE OR REPLACE VIEW", 22) == 0)
                                  appendPQExpBufferStr(ftStmt, dropStmt);
                              else
                              {
+                                 char        buffer[40];
+                                 char       *mark;
+
                                  if (strcmp(te->desc, "CONSTRAINT") == 0 ||
                                   strcmp(te->desc, "CHECK CONSTRAINT") == 0 ||
                                      strcmp(te->desc, "FK CONSTRAINT") == 0)
*************** RestoreArchive(Archive *AHX)
*** 570,588 ****
                                               te->desc);

                                  mark = strstr(dropStmt, buffer);
-                                 Assert(mark != NULL);

!                                 *mark = '\0';
!                                 appendPQExpBuffer(ftStmt, "%s%s IF EXISTS%s",
!                                                   dropStmt, buffer,
!                                                   mark + strlen(buffer));
                              }

                              ahprintf(AH, "%s", ftStmt->data);

                              destroyPQExpBuffer(ftStmt);
!
!                             pg_free(dropStmtPtr);
                          }
                      }
                  }
--- 577,604 ----
                                               te->desc);

                                  mark = strstr(dropStmt, buffer);

!                                 if (mark)
!                                 {
!                                     *mark = '\0';
!                                     appendPQExpBuffer(ftStmt, "%s%s IF EXISTS%s",
!                                                       dropStmt, buffer,
!                                                       mark + strlen(buffer));
!                                 }
!                                 else
!                                 {
!                                     /* complain and emit unmodified command */
!                                     write_msg(modulename,
!                                               "WARNING: could not find where to insert IF EXISTS in statement
\"%s\"\n",
!                                               dropStmtOrig);
!                                     appendPQExpBufferStr(ftStmt, dropStmt);
!                                 }
                              }

                              ahprintf(AH, "%s", ftStmt->data);

                              destroyPQExpBuffer(ftStmt);
!                             pg_free(dropStmtOrig);
                          }
                      }
                  }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ee1f673..9f59f53 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** getTables(Archive *fout, int *numTables)
*** 5484,5490 ****
              tblinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL;

          tblinfo[i].interesting = tblinfo[i].dobj.dump ? true : false;
!
          tblinfo[i].postponed_def = false;        /* might get set during sort */

          /*
--- 5486,5492 ----
              tblinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL;

          tblinfo[i].interesting = tblinfo[i].dobj.dump ? true : false;
!         tblinfo[i].dummy_view = false;    /* might get set during sort */
          tblinfo[i].postponed_def = false;        /* might get set during sort */

          /*
*************** getRules(Archive *fout, int *numRules)
*** 6205,6220 ****
          }
          else
              ruleinfo[i].separate = true;
-
-         /*
-          * If we're forced to break a dependency loop by dumping a view as a
-          * table and separate _RETURN rule, we'll move the view's reloptions
-          * to the rule.  (This is necessary because tables and views have
-          * different valid reloptions, so we can't apply the options until the
-          * backend knows it's a view.)  Otherwise the rule's reloptions stay
-          * NULL.
-          */
-         ruleinfo[i].reloptions = NULL;
      }

      PQclear(res);
--- 6207,6212 ----
*************** createViewAsClause(Archive *fout, TableI
*** 13976,13981 ****
--- 13968,14021 ----
  }

  /*
+  * Create a dummy AS clause for a view.  This is used when the real view
+  * definition has to be postponed because of circular dependencies.
+  * We must duplicate the view's external properties -- column names and types
+  * (including collation) -- so that it works for subsequent references.
+  *
+  * This returns a new buffer which must be freed by the caller.
+  */
+ static PQExpBuffer
+ createDummyViewAsClause(Archive *fout, TableInfo *tbinfo)
+ {
+     PQExpBuffer result = createPQExpBuffer();
+     int            j;
+
+     appendPQExpBufferStr(result, "SELECT");
+
+     for (j = 0; j < tbinfo->numatts; j++)
+     {
+         if (j > 0)
+             appendPQExpBufferChar(result, ',');
+         appendPQExpBufferStr(result, "\n    ");
+
+         appendPQExpBuffer(result, "NULL::%s", tbinfo->atttypnames[j]);
+
+         /*
+          * Must add collation if not default for the type, because CREATE OR
+          * REPLACE VIEW won't change it
+          */
+         if (OidIsValid(tbinfo->attcollation[j]))
+         {
+             CollInfo   *coll;
+
+             coll = findCollationByOid(tbinfo->attcollation[j]);
+             if (coll)
+             {
+                 /* always schema-qualify, don't try to be smart */
+                 appendPQExpBuffer(result, " COLLATE %s.",
+                                   fmtId(coll->dobj.namespace->dobj.name));
+                 appendPQExpBufferStr(result, fmtId(coll->dobj.name));
+             }
+         }
+
+         appendPQExpBuffer(result, " AS %s", fmtId(tbinfo->attnames[j]));
+     }
+
+     return result;
+ }
+
+ /*
   * dumpTableSchema
   *      write the declaration (not data) of one user-defined table or view
   */
*************** dumpTableSchema(Archive *fout, TableInfo
*** 14008,14013 ****
--- 14048,14057 ----
      {
          PQExpBuffer result;

+         /*
+          * Note: keep this code in sync with the is_view case in dumpRule()
+          */
+
          reltypename = "VIEW";

          /*
*************** dumpTableSchema(Archive *fout, TableInfo
*** 14024,14040 ****
                                               tbinfo->dobj.catId.oid, false);

          appendPQExpBuffer(q, "CREATE VIEW %s", fmtId(tbinfo->dobj.name));
!         if (nonemptyReloptions(tbinfo->reloptions))
          {
!             appendPQExpBufferStr(q, " WITH (");
!             appendReloptionsArrayAH(q, tbinfo->reloptions, "", fout);
!             appendPQExpBufferChar(q, ')');
          }
-         result = createViewAsClause(fout, tbinfo);
          appendPQExpBuffer(q, " AS\n%s", result->data);
          destroyPQExpBuffer(result);

!         if (tbinfo->checkoption != NULL)
              appendPQExpBuffer(q, "\n  WITH %s CHECK OPTION", tbinfo->checkoption);
          appendPQExpBufferStr(q, ";\n");

--- 14068,14089 ----
                                               tbinfo->dobj.catId.oid, false);

          appendPQExpBuffer(q, "CREATE VIEW %s", fmtId(tbinfo->dobj.name));
!         if (tbinfo->dummy_view)
!             result = createDummyViewAsClause(fout, tbinfo);
!         else
          {
!             if (nonemptyReloptions(tbinfo->reloptions))
!             {
!                 appendPQExpBufferStr(q, " WITH (");
!                 appendReloptionsArrayAH(q, tbinfo->reloptions, "", fout);
!                 appendPQExpBufferChar(q, ')');
!             }
!             result = createViewAsClause(fout, tbinfo);
          }
          appendPQExpBuffer(q, " AS\n%s", result->data);
          destroyPQExpBuffer(result);

!         if (tbinfo->checkoption != NULL && !tbinfo->dummy_view)
              appendPQExpBuffer(q, "\n  WITH %s CHECK OPTION", tbinfo->checkoption);
          appendPQExpBufferStr(q, ";\n");

*************** dumpRule(Archive *fout, RuleInfo *rinfo)
*** 15646,15651 ****
--- 15695,15701 ----
  {
      DumpOptions *dopt = fout->dopt;
      TableInfo  *tbinfo = rinfo->ruletable;
+     bool        is_view;
      PQExpBuffer query;
      PQExpBuffer cmd;
      PQExpBuffer delcmd;
*************** dumpRule(Archive *fout, RuleInfo *rinfo)
*** 15665,15670 ****
--- 15715,15726 ----
          return;

      /*
+      * If it's an ON SELECT rule, we want to print it as a view definition,
+      * instead of a rule.
+      */
+     is_view = (rinfo->ev_type == '1' && rinfo->is_instead);
+
+     /*
       * Make sure we are in proper schema.
       */
      selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
*************** dumpRule(Archive *fout, RuleInfo *rinfo)
*** 15674,15693 ****
      delcmd = createPQExpBuffer();
      labelq = createPQExpBuffer();

!     appendPQExpBuffer(query,
!       "SELECT pg_catalog.pg_get_ruledef('%u'::pg_catalog.oid) AS definition",
!                       rinfo->dobj.catId.oid);
!
!     res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
!
!     if (PQntuples(res) != 1)
      {
!         write_msg(NULL, "query to get rule \"%s\" for table \"%s\" failed: wrong number of rows returned\n",
!                   rinfo->dobj.name, tbinfo->dobj.name);
!         exit_nicely(1);
      }

!     printfPQExpBuffer(cmd, "%s\n", PQgetvalue(res, 0, 0));

      /*
       * Add the command to alter the rules replication firing semantics if it
--- 15730,15779 ----
      delcmd = createPQExpBuffer();
      labelq = createPQExpBuffer();

!     if (is_view)
      {
!         PQExpBuffer result;
!
!         /*
!          * We need OR REPLACE here because we'll be replacing a dummy view.
!          * Otherwise this should look largely like the regular view dump code.
!          */
!         appendPQExpBuffer(cmd, "CREATE OR REPLACE VIEW %s",
!                           fmtId(tbinfo->dobj.name));
!         if (nonemptyReloptions(tbinfo->reloptions))
!         {
!             appendPQExpBufferStr(cmd, " WITH (");
!             appendReloptionsArrayAH(cmd, tbinfo->reloptions, "", fout);
!             appendPQExpBufferChar(cmd, ')');
!         }
!         result = createViewAsClause(fout, tbinfo);
!         appendPQExpBuffer(cmd, " AS\n%s", result->data);
!         destroyPQExpBuffer(result);
!         if (tbinfo->checkoption != NULL)
!             appendPQExpBuffer(cmd, "\n  WITH %s CHECK OPTION",
!                               tbinfo->checkoption);
!         appendPQExpBufferStr(cmd, ";\n");
      }
+     else
+     {
+         /* In the rule case, just print pg_get_ruledef's result verbatim */
+         appendPQExpBuffer(query,
+                     "SELECT pg_catalog.pg_get_ruledef('%u'::pg_catalog.oid)",
+                           rinfo->dobj.catId.oid);

!         res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
!
!         if (PQntuples(res) != 1)
!         {
!             write_msg(NULL, "query to get rule \"%s\" for table \"%s\" failed: wrong number of rows returned\n",
!                       rinfo->dobj.name, tbinfo->dobj.name);
!             exit_nicely(1);
!         }
!
!         printfPQExpBuffer(cmd, "%s\n", PQgetvalue(res, 0, 0));
!
!         PQclear(res);
!     }

      /*
       * Add the command to alter the rules replication firing semantics if it
*************** dumpRule(Archive *fout, RuleInfo *rinfo)
*** 15714,15738 ****
      }

      /*
!      * Apply view's reloptions when its ON SELECT rule is separate.
       */
!     if (nonemptyReloptions(rinfo->reloptions))
      {
!         appendPQExpBuffer(cmd, "ALTER VIEW %s SET (",
                            fmtId(tbinfo->dobj.name));
-         appendReloptionsArrayAH(cmd, rinfo->reloptions, "", fout);
-         appendPQExpBufferStr(cmd, ");\n");
      }
-
-     /*
-      * DROP must be fully qualified in case same name appears in pg_catalog
-      */
-     appendPQExpBuffer(delcmd, "DROP RULE %s ",
-                       fmtId(rinfo->dobj.name));
-     appendPQExpBuffer(delcmd, "ON %s.",
-                       fmtId(tbinfo->dobj.namespace->dobj.name));
-     appendPQExpBuffer(delcmd, "%s;\n",
-                       fmtId(tbinfo->dobj.name));

      appendPQExpBuffer(labelq, "RULE %s",
                        fmtId(rinfo->dobj.name));
--- 15800,15833 ----
      }

      /*
!      * DROP must be fully qualified in case same name appears in pg_catalog
       */
!     if (is_view)
      {
!         /*
!          * We can't DROP a view's ON SELECT rule.  Instead, use CREATE OR
!          * REPLACE VIEW to replace the rule with something with minimal
!          * dependencies.
!          */
!         PQExpBuffer result;
!
!         appendPQExpBuffer(delcmd, "CREATE OR REPLACE VIEW %s.",
!                           fmtId(tbinfo->dobj.namespace->dobj.name));
!         appendPQExpBuffer(delcmd, "%s",
!                           fmtId(tbinfo->dobj.name));
!         result = createDummyViewAsClause(fout, tbinfo);
!         appendPQExpBuffer(delcmd, " AS\n%s;\n", result->data);
!         destroyPQExpBuffer(result);
!     }
!     else
!     {
!         appendPQExpBuffer(delcmd, "DROP RULE %s ",
!                           fmtId(rinfo->dobj.name));
!         appendPQExpBuffer(delcmd, "ON %s.",
!                           fmtId(tbinfo->dobj.namespace->dobj.name));
!         appendPQExpBuffer(delcmd, "%s;\n",
                            fmtId(tbinfo->dobj.name));
      }

      appendPQExpBuffer(labelq, "RULE %s",
                        fmtId(rinfo->dobj.name));
*************** dumpRule(Archive *fout, RuleInfo *rinfo)
*** 15759,15766 ****
                      tbinfo->rolname,
                      rinfo->dobj.catId, 0, rinfo->dobj.dumpId);

-     PQclear(res);
-
      free(tag);
      destroyPQExpBuffer(query);
      destroyPQExpBuffer(cmd);
--- 15854,15859 ----
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 642c4d5..f3e5977 100644
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
*************** typedef struct _tableInfo
*** 287,292 ****
--- 287,293 ----
      int            relpages;        /* table's size in pages (from pg_class) */

      bool        interesting;    /* true if need to collect more data */
+     bool        dummy_view;        /* view's real definition must be postponed */
      bool        postponed_def;    /* matview must be postponed into post-data */

      /*
*************** typedef struct _ruleInfo
*** 364,371 ****
      char        ev_enabled;
      bool        separate;        /* TRUE if must dump as separate item */
      /* separate is always true for non-ON SELECT rules */
-     char       *reloptions;        /* options specified by WITH (...) */
-     /* reloptions is only set if we need to dump the options with the rule */
  } RuleInfo;

  typedef struct _triggerInfo
--- 365,370 ----
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 5b96334..9966423 100644
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
*************** repairViewRuleLoop(DumpableObject *viewo
*** 786,791 ****
--- 786,792 ----
  {
      /* remove rule's dependency on view */
      removeObjectDependency(ruleobj, viewobj->dumpId);
+     /* flags on the two objects are already set correctly for this case */
  }

  /*
*************** repairViewRuleMultiLoop(DumpableObject *
*** 805,831 ****
  {
      TableInfo  *viewinfo = (TableInfo *) viewobj;
      RuleInfo   *ruleinfo = (RuleInfo *) ruleobj;
-     int            i;

      /* remove view's dependency on rule */
      removeObjectDependency(viewobj, ruleobj->dumpId);
!     /* pretend view is a plain table and dump it that way */
!     viewinfo->relkind = 'r';    /* RELKIND_RELATION */
      /* mark rule as needing its own dump */
      ruleinfo->separate = true;
-     /* move any reloptions from view to rule */
-     if (viewinfo->reloptions)
-     {
-         ruleinfo->reloptions = viewinfo->reloptions;
-         viewinfo->reloptions = NULL;
-     }
      /* put back rule's dependency on view */
      addObjectDependency(ruleobj, viewobj->dumpId);
      /* now that rule is separate, it must be post-data */
      addObjectDependency(ruleobj, postDataBoundId);
-     /* also, any triggers on the view must be dumped after the rule */
-     for (i = 0; i < viewinfo->numTriggers; i++)
-         addObjectDependency(&(viewinfo->triggers[i].dobj), ruleobj->dumpId);
  }

  /*
--- 806,822 ----
  {
      TableInfo  *viewinfo = (TableInfo *) viewobj;
      RuleInfo   *ruleinfo = (RuleInfo *) ruleobj;

      /* remove view's dependency on rule */
      removeObjectDependency(viewobj, ruleobj->dumpId);
!     /* mark view to be printed with a dummy definition */
!     viewinfo->dummy_view = true;
      /* mark rule as needing its own dump */
      ruleinfo->separate = true;
      /* put back rule's dependency on view */
      addObjectDependency(ruleobj, viewobj->dumpId);
      /* now that rule is separate, it must be post-data */
      addObjectDependency(ruleobj, postDataBoundId);
  }

  /*

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Kyotaro HORIGUCHI
Дата:
Сообщение: Re: Document how to set up TAP tests for Perl 5.8.8
Следующее
От: Robert Haas
Дата:
Сообщение: Re: pg_dump versus rules, once again