Hi,

+ <sect1 id="catalog-pg-publication-rel">
+  <title><structname>pg_publication_rel</structname></title>
+
+  <indexterm zone="catalog-pg-publication-rel">
+   <primary>pg_publication_rel</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_publication_rel</structname> catalog contains
+   mapping between tables and publications in the database. This is many to
+   many mapping.
+  </para>

I wonder if we shouldn't abstract this a bit away from relations to
allow other objects to be exported to. Could structure it a bit more
like pg_depend.


+ALTER PUBLICATION <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] 
<replaceable class="PARAMETER">option</replaceable> [ ... ] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can 
be:</phrase>
+
+      PuBLISH_INSERT | NOPuBLISH_INSERT
+    | PuBLISH_UPDATE | NOPuBLISH_UPDATE
+    | PuBLISH_DELETE | NOPuBLISH_DELETE

That's odd casing.


+   <varlistentry>
+    <term><literal>PuBLISH_INSERT</literal></term>
+    <term><literal>NOPuBLISH_INSERT</literal></term>
+    <term><literal>PuBLISH_UPDATE</literal></term>
+    <term><literal>NOPuBLISH_UPDATE</literal></term>
+    <term><literal>PuBLISH_DELETE</literal></term>
+    <term><literal>NOPuBLISH_DELETE</literal></term>

More odd casing.

+   <varlistentry>
+    <term><literal>FOR TABLE</literal></term>
+    <listitem>
+     <para>
+      Specifies optional list of tables to add to the publication.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>FOR TABLE ALL IN SCHEMA</literal></term>
+    <listitem>
+     <para>
+      Specifies optional schema for which all logged tables will be added to
+      publication.
+     </para>
+    </listitem>
+   </varlistentry>

"FOR TABLE ALL IN SCHEMA" sounds weird.


+  <para>
+   This operation does not reserve any resources on the server. It only
+   defines grouping and filtering logic for future subscribers.
+  </para>

That's strictly speaking not true, maybe rephrase a bit?

+/*
+ * Check if relation can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_relation(Relation targetrel)
+{
+       /* Must be table */
+       if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("only tables can be added to 
publication"),
+                                errdetail("%s is not a table",
+                                                  
RelationGetRelationName(targetrel))));
+
+       /* Can't be system table */
+       if (IsCatalogRelation(targetrel))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("only user tables can be added to 
publication"),
+                                errdetail("%s is a system table",
+                                                  
RelationGetRelationName(targetrel))));
+
+       /* UNLOGGED and TEMP relations cannot be part of publication. */
+       if (!RelationNeedsWAL(targetrel))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("UNLOGGED and TEMP relations cannot be 
replicated")));
+}

This probably means we need a check in the ALTER TABLE ... SET UNLOGGED
path.


+/*
+ * Returns if relation represented by oid and Form_pg_class entry
+ * is publishable.
+ *
+ * Does same checks as the above, but does not need relation to be opened
+ * and also does not throw errors.
+ */
+static bool
+is_publishable_class(Oid relid, Form_pg_class reltuple)
+{
+       return reltuple->relkind == RELKIND_RELATION &&
+               !IsCatalogClass(relid, reltuple) &&
+               reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
+               /* XXX needed to exclude information_schema tables */
+               relid >= FirstNormalObjectId;
+}

Shouldn't that be IsCatalogRelation() instead?


+CREATE VIEW pg_publication_tables AS
+    SELECT
+        P.pubname AS pubname,
+        N.nspname AS schemaname,
+        C.relname AS tablename
+    FROM pg_publication P, pg_class C
+         JOIN pg_namespace N ON (N.oid = C.relnamespace)
+    WHERE C.relkind = 'r'
+      AND C.oid IN (SELECT relid FROM pg_get_publication_tables(P.pubname));

That's going to be quite inefficient if you filter by table... Might be
better to do that via the underlying table.


+/*
+ * Create new publication.
+ * TODO ACL check
+ */

Hm?

+ObjectAddress
+CreatePublication(CreatePublicationStmt *stmt)
+{
+       check_replication_permissions();

+
+/*
+ * Drop publication by OID
+ */
+void
+DropPublicationById(Oid pubid)
+
+/*
+ * Remove relation from publication by mapping OID.
+ */
+void
+RemovePublicationRelById(Oid proid)
+{

Permission checks?

+}

Hm. Neither of these does dependency checking, wonder if that can be
argued to be problematic.


+/*
+ * Gather Relations based o provided by RangeVar list.
+ * The gathered tables are locked in ShareUpdateExclusiveLock mode.
+ */

s/o/on/.  Not sure if gather is the best name.

+static List *
+GatherTableList(List *tables)


+/*
+ * Close all relations in the list.
+ */
+static void
+CloseTables(List *rels)

Shouldn't that be CloseTableList() based on the preceding function's naming?

+
+/*
+ * Add listed tables to the publication.
+ */
+static void
+PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
+                                        AlterPublicationStmt *stmt)
+{
+       ListCell           *lc;
+
+       Assert(!stmt || !stmt->for_all_tables);
+
+       foreach(lc, rels)
+       {
+               Relation        rel = (Relation) lfirst(lc);
+               ObjectAddress   obj;
+
+               obj = publication_add_relation(pubid, rel, if_not_exists);
+               if (stmt)
+                       EventTriggerCollectSimpleCommand(obj, 
InvalidObjectAddress,
+                                                                               
         (Node *) stmt);
+       }
+}
+
+/*
+ * Remove listed tables to the publication.
+ */

s/to/from/

+static void
+PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+{
+       ObjectAddress   obj;
+       ListCell           *lc;
+       Oid                             prid;
+
+       foreach(lc, rels)
+       {
+               Relation        rel = (Relation) lfirst(lc);
+               Oid                     relid = RelationGetRelid(rel);
+
+               prid = GetSysCacheOid2(PUBLICATIONRELMAP, 
ObjectIdGetDatum(relid),
+                                                          
ObjectIdGetDatum(pubid));
+               if (!OidIsValid(prid))
+               {
+                       if (missing_ok)
+                               continue;
+
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                        errmsg("relation \"%s\" is not part of 
the publication",
+                                                       
RelationGetRelationName(rel))));
+               }
+
+               ObjectAddressSet(obj, PublicationRelRelationId, prid);
+               performDeletion(&obj, DROP_CASCADE, 0);
+       }
+}


/*
+ * Check if command can be executed with current replica identity.
+ */
+static void
+CheckCmdReplicaIdentity(Relation rel, CmdType cmd)
+{
+       PublicationActions *pubactions;
+
+       /* We only need to do checks for UPDATE and DELETE. */
+       if (cmd != CMD_UPDATE && cmd != CMD_DELETE)
+               return;
+
+       /* If relation has replica identity we are always good. */
+       if (rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL ||
+               OidIsValid(RelationGetReplicaIndex(rel)))
+               return;
+
+       /*
+        * This is either UPDATE OR DELETE and there is no replica identity.
+        *
+        * Check if the table publishes UPDATES or DELETES.
+        */
+       pubactions = GetRelationPublicationActions(rel);
+       if (pubactions->pubupdate || pubactions->pubdelete)

I think that leads to spurious errors. Consider a DELETE with a
publication that replicates updates but not deletes.

+               ereport(ERROR,
+                               
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                errmsg("cannot update table \"%s\" because it 
does not have replica identity and publishes updates",
+                                               RelationGetRelationName(rel)),
+                                errhint("To enable updating the table, provide 
set REPLICA IDENTITY using ALTER TABLE.")));
+}

"provide set"


+publication_opt_item:
+                       IDENT
+                               {
+                                       /*
+                                        * We handle identifiers that aren't 
parser keywords with
+                                        * the following special-case codes, to 
avoid bloating the
+                                        * size of the main parser.
+                                        */
+                                       if (strcmp($1, "publish_insert") == 0)
+                                               $$ = 
makeDefElem("publish_insert",
+                                                                               
 (Node *)makeInteger(TRUE), @1);
+                                       else if (strcmp($1, "nopublish_insert") 
== 0)
+                                               $$ = 
makeDefElem("publish_insert",
+                                                                               
 (Node *)makeInteger(FALSE), @1);
+                                       else if (strcmp($1, "publish_update") 
== 0)
+                                               $$ = 
makeDefElem("publish_update",
+                                                                               
 (Node *)makeInteger(TRUE), @1);
+                                       else if (strcmp($1, "nopublish_update") 
== 0)
+                                               $$ = 
makeDefElem("publish_update",
+                                                                               
 (Node *)makeInteger(FALSE), @1);
+                                       else if (strcmp($1, "publish_delete") 
== 0)
+                                               $$ = 
makeDefElem("publish_delete",
+                                                                               
 (Node *)makeInteger(TRUE), @1);
+                                       else if (strcmp($1, "nopublish_delete") 
== 0)
+                                               $$ = 
makeDefElem("publish_delete",
+                                                                               
 (Node *)makeInteger(FALSE), @1);
+                                       else
+                                               ereport(ERROR,
+                                                               
(errcode(ERRCODE_SYNTAX_ERROR),
+                                                                
errmsg("unrecognized publication option \"%s\"", $1),
+                                                                        
parser_errposition(@1)));
+                               }
+               ;

I still would very much like to move this outside of gram.y and just use
IDENTs here. Like how COPY options are handled.


+/*
+ * Get publication actions for list of publication oids.
+ */
+struct PublicationActions *
+GetRelationPublicationActions(Relation relation)

API description and function name/parameters don't quite match.



+CATALOG(pg_publication,6104)
+{
+       NameData        pubname;                        /* name of the 
publication */
+
+       /*
+        * indicates that this is special publication which should encompass
+        * all tables in the database (except for the unlogged and temp ones)
+        */
+       bool            puballtables;
+
+       /* true if inserts are published */
+       bool            pubinsert;
+
+       /* true if updates are published */
+       bool            pubupdate;
+
+       /* true if deletes are published */
+       bool            pubdelete;
+
+} FormData_pg_publication;

Shouldn't this have an owner?   I also wonder if we want an easier to
extend form of pubinsert/update/delete (say to add pubddl, pubtruncate,
pub ... without changing the schema).


+/* ----------------
+ *             pg_publication_rel definition.  cpp turns this into
+ *             typedef struct FormData_pg_publication_rel
+ *
+ * ----------------
+ */
+#define PublicationRelRelationId                               6106
+
+CATALOG(pg_publication_rel,6106)
+{
+       Oid             prpubid;                                /* Oid of the 
publication */
+       Oid             prrelid;                                /* Oid of the 
relation */
+} FormData_pg_publication_rel;

To me it seems like a good idea to have objclassid/objsubid here.


Regards,

Andres


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to