diff --git a/build/script/opengauss_release_list_centos_single b/build/script/opengauss_release_list_centos_single index 357c47ab5..096910759 100644 --- a/build/script/opengauss_release_list_centos_single +++ b/build/script/opengauss_release_list_centos_single @@ -749,6 +749,7 @@ ./lib/postgresql/pg_upgrade_support.so ./lib/postgresql/java/pljava.jar ./lib/postgresql/postgres_fdw.so +./lib/postgresql/pgoutput.so ./lib/libpljava.so ./lib/libpq.a ./lib/libpq.so diff --git a/build/script/opengauss_release_list_euleros_aarch64_single b/build/script/opengauss_release_list_euleros_aarch64_single index f0f057806..b6eb88459 100644 --- a/build/script/opengauss_release_list_euleros_aarch64_single +++ b/build/script/opengauss_release_list_euleros_aarch64_single @@ -749,6 +749,7 @@ ./lib/postgresql/pg_upgrade_support.so ./lib/postgresql/java/pljava.jar ./lib/postgresql/postgres_fdw.so +./lib/postgresql/pgoutput.so ./lib/libpljava.so ./lib/libpq.a ./lib/libpq.so diff --git a/build/script/opengauss_release_list_euleros_single b/build/script/opengauss_release_list_euleros_single index 06e76cc91..b41c405e9 100644 --- a/build/script/opengauss_release_list_euleros_single +++ b/build/script/opengauss_release_list_euleros_single @@ -749,6 +749,7 @@ ./lib/postgresql/pg_upgrade_support.so ./lib/postgresql/java/pljava.jar ./lib/postgresql/postgres_fdw.so +./lib/postgresql/pgoutput.so ./lib/libpljava.so ./lib/libpq.a ./lib/libpq.so diff --git a/build/script/opengauss_release_list_kylin_aarch64_single b/build/script/opengauss_release_list_kylin_aarch64_single index b04b5ef6d..1ac0593e0 100644 --- a/build/script/opengauss_release_list_kylin_aarch64_single +++ b/build/script/opengauss_release_list_kylin_aarch64_single @@ -771,6 +771,7 @@ ./lib/postgresql/pg_upgrade_support.so ./lib/postgresql/java/pljava.jar ./lib/postgresql/postgres_fdw.so +./lib/postgresql/pgoutput.so ./lib/libpljava.so ./lib/libpq.a ./lib/libpq.so diff --git a/build/script/opengauss_release_list_openeuler_aarch64_single b/build/script/opengauss_release_list_openeuler_aarch64_single index f0f057806..b6eb88459 100644 --- a/build/script/opengauss_release_list_openeuler_aarch64_single +++ b/build/script/opengauss_release_list_openeuler_aarch64_single @@ -749,6 +749,7 @@ ./lib/postgresql/pg_upgrade_support.so ./lib/postgresql/java/pljava.jar ./lib/postgresql/postgres_fdw.so +./lib/postgresql/pgoutput.so ./lib/libpljava.so ./lib/libpq.a ./lib/libpq.so diff --git a/build/script/opengauss_release_list_openeuler_single b/build/script/opengauss_release_list_openeuler_single index 357c47ab5..096910759 100644 --- a/build/script/opengauss_release_list_openeuler_single +++ b/build/script/opengauss_release_list_openeuler_single @@ -749,6 +749,7 @@ ./lib/postgresql/pg_upgrade_support.so ./lib/postgresql/java/pljava.jar ./lib/postgresql/postgres_fdw.so +./lib/postgresql/pgoutput.so ./lib/libpljava.so ./lib/libpq.a ./lib/libpq.so diff --git a/contrib/pg_xlogdump/CMakeLists.txt b/contrib/pg_xlogdump/CMakeLists.txt index c28f9807f..c470e4ec6 100644 --- a/contrib/pg_xlogdump/CMakeLists.txt +++ b/contrib/pg_xlogdump/CMakeLists.txt @@ -25,6 +25,7 @@ execute_process( COMMAND ln -fs ${PROJECT_SRC_DIR}/gausskernel/storage/access/transam/xlogreader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xlogreader.cpp COMMAND ln -fs ${PROJECT_SRC_DIR}/gausskernel/storage/access/rmgrdesc/uheapdesc.cpp ${CMAKE_CURRENT_SOURCE_DIR}/uheapdesc.cpp COMMAND ln -fs ${PROJECT_SRC_DIR}/gausskernel/storage/access/rmgrdesc/undologdesc.cpp ${CMAKE_CURRENT_SOURCE_DIR}/undologdesc.cpp + COMMAND ln -fs ${PROJECT_SRC_DIR}/gausskernel/storage/access/rmgrdesc/replorigindesc.cpp ${CMAKE_CURRENT_SOURCE_DIR}/replorigindesc.cpp ) AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} TGT_xlogdump_SRC) diff --git a/contrib/pg_xlogdump/rmgrdesc.cpp b/contrib/pg_xlogdump/rmgrdesc.cpp index 49df07d2f..eeb19b508 100644 --- a/contrib/pg_xlogdump/rmgrdesc.cpp +++ b/contrib/pg_xlogdump/rmgrdesc.cpp @@ -29,6 +29,7 @@ #include "commands/sequence.h" #include "commands/tablespace.h" #include "replication/slot.h" +#include "replication/origin.h" #ifdef PGXC #include "pgxc/barrier.h" #endif diff --git a/src/Makefile b/src/Makefile index 969fc46bb..b2db98596 100755 --- a/src/Makefile +++ b/src/Makefile @@ -41,6 +41,7 @@ SUBDIRS = \ common \ lib/elog \ gausskernel $(dirs_in_central_mode) \ + gausskernel/storage/replication/pgoutput/ \ bin \ common/backend/utils/mb/conversion_procs \ common/backend/snowball \ diff --git a/src/bin/gs_guc/cluster_guc.conf b/src/bin/gs_guc/cluster_guc.conf index 33fb7dddf..bde35ced6 100755 --- a/src/bin/gs_guc/cluster_guc.conf +++ b/src/bin/gs_guc/cluster_guc.conf @@ -644,6 +644,7 @@ segment_buffers|int|16,1073741823|kB|NULL| default_index_kind|int|0,2|NULL|NULL| undo_zone_count|int|0,1048576|NULL|NULL| enable_auto_clean_unique_sql|bool|0,0|NULL|NULL| +max_logical_replication_workers|int|0,262143|NULL|Maximum number of logical replication worker processes.| [cmserver] log_dir|string|0,0|NULL|NULL| log_file_size|int|0,2047|MB|NULL| diff --git a/src/bin/initdb/initdb.cpp b/src/bin/initdb/initdb.cpp index 05ae64e4a..3240166e5 100644 --- a/src/bin/initdb/initdb.cpp +++ b/src/bin/initdb/initdb.cpp @@ -3594,7 +3594,8 @@ int main(int argc, char* argv[]) "pg_llog/snapshots", "pg_llog/mappings", "pg_errorinfo", - "undo"}; + "undo", + "pg_logical"}; progname = get_progname(argv[0]); set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("gs_initdb")); diff --git a/src/bin/pg_dump/common.cpp b/src/bin/pg_dump/common.cpp index 245d13cf6..0777f59df 100644 --- a/src/bin/pg_dump/common.cpp +++ b/src/bin/pg_dump/common.cpp @@ -256,6 +256,21 @@ TableInfo* getSchemaData(Archive* fout, int* numTablesPtr) write_msg(NULL, "reading row level security policies\n"); getRlsPolicies(fout, tblinfo, numTables); + if (g_verbose) { + write_msg(NULL, "reading publications\n"); + } + getPublications(fout); + + if (g_verbose) { + write_msg(NULL, "reading publication membership\n"); + } + getPublicationTables(fout, tblinfo, numTables); + + if (g_verbose) { + write_msg(NULL, "reading subscriptions\n"); + } + getSubscriptions(fout); + *numTablesPtr = numTables; GS_FREE(inhinfo); return tblinfo; diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index 8c7ac8620..f583fa432 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -102,6 +102,8 @@ typedef struct _restoreOptions { int use_setsessauth; /* Use SET SESSION AUTHORIZATION commands * instead of OWNER TO */ int no_security_labels; /* Skip security label entries */ + int no_subscriptions; /* Skip subscription entries */ + int no_publications; /* Skip publication entries */ char* superuser; /* Username to use as superuser */ char* use_role; /* Issue SET ROLE to this */ char* rolepassword; /* password for use_role */ diff --git a/src/bin/pg_dump/pg_backup_archiver.cpp b/src/bin/pg_dump/pg_backup_archiver.cpp index 76f39e97e..cf3f3a46c 100644 --- a/src/bin/pg_dump/pg_backup_archiver.cpp +++ b/src/bin/pg_dump/pg_backup_archiver.cpp @@ -2520,6 +2520,16 @@ static teReqs _tocEntryRequired(TocEntry* te, teSection curSection, RestoreOptio if (ropt->no_security_labels && strcmp(te->desc, "SECURITY LABEL") == 0) return (teReqs)0; + /* If it's a subcription, maybe ignore it */ + if (ropt->no_subscriptions && strcmp(te->desc, "SUBSCRIPTION") == 0) { + return (teReqs)0; + } + + /* If it's a publication, maybe ignore it */ + if (ropt->no_publications && strcmp(te->desc, "PUBLICATION") == 0) { + return (teReqs)0; + } + /* Ignore it if section is not to be dumped/restored */ switch (curSection) { case SECTION_PRE_DATA: @@ -3003,7 +3013,7 @@ static void _getObjectDescription(PQExpBuffer buf, TocEntry* te, ArchiveHandle* /* objects named by just a name */ if (strcmp(type, "DATABASE") == 0 || strcmp(type, "PROCEDURAL LANGUAGE") == 0 || strcmp(type, "SCHEMA") == 0 || strcmp(type, "DIRECTORY") == 0 || strcmp(type, "FOREIGN DATA WRAPPER") == 0 || strcmp(type, "SERVER") == 0 || - strcmp(type, "USER MAPPING") == 0) { + strcmp(type, "USER MAPPING") == 0 || strcmp(type, "PUBLICATION") == 0 || strcmp(type, "SUBSCRIPTION") == 0) { (void)appendPQExpBuffer(buf, "%s %s", type, fmtId(te->tag)); return; } @@ -3252,7 +3262,9 @@ static void _printTocEntry(ArchiveHandle* AH, TocEntry* te, RestoreOptions* ropt || strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 || strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0 || strcmp(te->desc, "FOREIGN DATA WRAPPER") == 0 - || strcmp(te->desc, "SERVER") == 0) { + || strcmp(te->desc, "SERVER") == 0 || + strcmp(te->desc, "PUBLICATION") == 0 || + strcmp(te->desc, "SUBSCRIPTION") == 0) { PQExpBuffer temp = createPQExpBuffer(); (void)appendPQExpBuffer(temp, "ALTER "); _getObjectDescription(temp, te, AH); diff --git a/src/bin/pg_dump/pg_dump.cpp b/src/bin/pg_dump/pg_dump.cpp index 6d124e433..afce2b3ae 100644 --- a/src/bin/pg_dump/pg_dump.cpp +++ b/src/bin/pg_dump/pg_dump.cpp @@ -228,7 +228,7 @@ static char connected_node_type = 'N'; /* 'N' -- NONE */ char* all_data_nodename_list = NULL; const uint32 USTORE_UPGRADE_VERSION = 92368; - +const uint32 SUBSCRIPTION_VERSION = 92425; #ifdef DUMPSYSLOG char* syslogpath = NULL; @@ -299,6 +299,8 @@ static bool is_encrypt = false; static bool could_encrypt = false; static int exclude_function = 0; static bool is_pipeline = false; +static int no_subscriptions = 0; +static int no_publications = 0; /* Used to count the number of -t input */ int gTableCount = 0; @@ -392,6 +394,9 @@ static void dumpForeignServer(Archive* fout, ForeignServerInfo* srvinfo); static void dumpUserMappings( Archive* fout, const char* servername, const char* nmspace, const char* owner, CatalogId catalogId, DumpId dumpId); static void dumpDefaultACL(Archive* fout, DefaultACLInfo* daclinfo); +static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo); +static void dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo); +static void dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo); static void dumpACL(Archive* fout, CatalogId objCatId, DumpId objDumpId, const char* type, const char* name, const char* subname, const char* tag, const char* nspname, const char* owner, const char* acls); @@ -554,8 +559,9 @@ int main(int argc, char** argv) {"section", required_argument, NULL, 5}, {"serializable-deferrable", no_argument, &serializable_deferrable, 1}, {"use-set-session-authorization", no_argument, &use_setsessauth, 1}, - {"no-security-labels", no_argument, &no_security_labels, 1}, + {"no-publications", no_argument, &no_publications, 1}, {"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1}, + {"no-subscriptions", no_argument, &no_subscriptions, 1}, {"include-alter-table", no_argument, &include_alter_table, 1}, {"exclude-self", no_argument, &exclude_self, 1}, {"include-depend-objs", no_argument, &include_depend_objs, 1}, @@ -1021,6 +1027,8 @@ int main(int argc, char** argv) ropt->noTablespace = outputNoTablespaces; ropt->disable_triggers = disable_triggers; ropt->use_setsessauth = use_setsessauth; + ropt->no_subscriptions = no_subscriptions; + ropt->no_publications = no_publications; if (compressLevel == -1) ropt->compression = 0; @@ -1704,7 +1712,10 @@ void help(const char* pchProgname) printf(_(" --exclude-table-data=TABLE do NOT dump data for the named table(s)\n")); printf(_(" --exclude-with do NOT dump WITH() of table(s)\n")); printf(_(" --inserts dump data as INSERT commands, rather than COPY\n")); + printf(_(" --no-create-subscription-slots do not create replication slots for subscriptions\n")); + printf(_(" --no-publications do not dump publications\n")); printf(_(" --no-security-labels do not dump security label assignments\n")); + printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --no-unlogged-table-data do not dump unlogged table data\n")); printf(_(" --include-alter-table dump the table delete column\n")); @@ -2296,6 +2307,18 @@ static void selectDumpableExtension(ExtensionInfo* extinfo) extinfo->dobj.dump = include_everything; } +/* + * selectDumpablePublicationTable: policy-setting subroutine + * Mark a publication table as to be dumped or not + * + * Publication tables have schemas, but those are ignored in decision making, + * because publications are only dumped when we are dumping everything. + */ +static void selectDumpablePublicationTable(DumpableObject *dobj) +{ + dobj->dump = include_everything; +} + /* * selectDumpableObject: policy-setting subroutine * Mark a generic dumpable object as to be dumped or not @@ -4017,6 +4040,437 @@ static int dumpBlobs(Archive* fout, void* arg) return 1; } +/* + * getPublications + * get information about publications + */ +void getPublications(Archive *fout) +{ + PQExpBuffer query; + PGresult *res; + PublicationInfo *pubinfo; + int i_tableoid; + int i_oid; + int i_pubname; + int i_rolname; + int i_puballtables; + int i_pubinsert; + int i_pubupdate; + int i_pubdelete; + int i, ntups; + + if (no_publications || GetVersionNum(fout) < SUBSCRIPTION_VERSION) { + return; + } + + query = createPQExpBuffer(); + + resetPQExpBuffer(query); + + /* Get the publications. */ + appendPQExpBuffer(query, + "SELECT p.tableoid, p.oid, p.pubname, " + "(%s p.pubowner) AS rolname, " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete " + "FROM pg_catalog.pg_publication p", + username_subquery); + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_pubname = PQfnumber(res, "pubname"); + i_rolname = PQfnumber(res, "rolname"); + i_puballtables = PQfnumber(res, "puballtables"); + i_pubinsert = PQfnumber(res, "pubinsert"); + i_pubupdate = PQfnumber(res, "pubupdate"); + i_pubdelete = PQfnumber(res, "pubdelete"); + + pubinfo = (PublicationInfo *)pg_malloc(ntups * sizeof(PublicationInfo)); + + for (i = 0; i < ntups; i++) { + pubinfo[i].dobj.objType = DO_PUBLICATION; + pubinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + pubinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&pubinfo[i].dobj); + pubinfo[i].dobj.name = gs_strdup(PQgetvalue(res, i, i_pubname)); + pubinfo[i].rolname = gs_strdup(PQgetvalue(res, i, i_rolname)); + pubinfo[i].puballtables = (strcmp(PQgetvalue(res, i, i_puballtables), "t") == 0); + pubinfo[i].pubinsert = (strcmp(PQgetvalue(res, i, i_pubinsert), "t") == 0); + pubinfo[i].pubupdate = (strcmp(PQgetvalue(res, i, i_pubupdate), "t") == 0); + pubinfo[i].pubdelete = (strcmp(PQgetvalue(res, i, i_pubdelete), "t") == 0); + + if (strlen(pubinfo[i].rolname) == 0) { + write_msg(NULL, "WARNING: owner of publication \"%s\" appears to be invalid\n", pubinfo[i].dobj.name); + } + + /* Decide whether we want to dump it */ + selectDumpableObject(&(pubinfo[i].dobj), fout); + } + PQclear(res); + + destroyPQExpBuffer(query); +} + +/* + * dumpPublication + * dump the definition of the given publication + */ +static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo) +{ + PQExpBuffer delq; + PQExpBuffer query; + PQExpBuffer labelq; + bool first = true; + + if (dataOnly || !pubinfo->dobj.dump) { + return; + } + + delq = createPQExpBuffer(); + query = createPQExpBuffer(); + labelq = createPQExpBuffer(); + + appendPQExpBuffer(delq, "DROP PUBLICATION %s;\n", fmtId(pubinfo->dobj.name)); + + appendPQExpBuffer(query, "CREATE PUBLICATION %s", fmtId(pubinfo->dobj.name)); + + appendPQExpBuffer(labelq, "PUBLICATION %s", fmtId(pubinfo->dobj.name)); + + if (pubinfo->puballtables) + appendPQExpBufferStr(query, " FOR ALL TABLES"); + + appendPQExpBufferStr(query, " WITH (publish = '"); + if (pubinfo->pubinsert) { + appendPQExpBufferStr(query, "insert"); + first = false; + } + + if (pubinfo->pubupdate) { + if (!first) { + appendPQExpBufferStr(query, ", "); + } + appendPQExpBufferStr(query, "update"); + first = false; + } + + if (pubinfo->pubdelete) { + if (!first) { + appendPQExpBufferStr(query, ", "); + } + appendPQExpBufferStr(query, "delete"); + first = false; + } + + appendPQExpBufferStr(query, "');\n"); + + ArchiveEntry(fout, pubinfo->dobj.catId, pubinfo->dobj.dumpId, pubinfo->dobj.name, NULL, NULL, pubinfo->rolname, + false, "PUBLICATION", SECTION_POST_DATA, query->data, delq->data, NULL, NULL, 0, NULL, NULL); + + dumpComment(fout, labelq->data, NULL, pubinfo->rolname, pubinfo->dobj.catId, 0, pubinfo->dobj.dumpId); + dumpSecLabel(fout, labelq->data, NULL, pubinfo->rolname, pubinfo->dobj.catId, 0, pubinfo->dobj.dumpId); + + destroyPQExpBuffer(delq); + destroyPQExpBuffer(query); +} + +/* + * getPublicationTables + * get information about publication membership for dumpable tables. + */ +void getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables) +{ + PQExpBuffer query; + PGresult *res; + PublicationRelInfo *pubrinfo; + int i_tableoid; + int i_oid; + int i_pubname; + int i, j, ntups; + + if (no_publications || GetVersionNum(fout) < SUBSCRIPTION_VERSION) { + return; + } + + query = createPQExpBuffer(); + + for (i = 0; i < numTables; i++) { + TableInfo *tbinfo = &tblinfo[i]; + + /* Only plain tables can be aded to publications. */ + if (tbinfo->relkind != RELKIND_RELATION) { + continue; + } + + if (g_verbose) { + write_msg(NULL, "reading publication membership for table \"%s.%s\"\n", tbinfo->dobj.nmspace->dobj.name, + tbinfo->dobj.name); + } + + resetPQExpBuffer(query); + + /* Get the publication memebership for the table. */ + appendPQExpBuffer(query, + "SELECT pr.tableoid, pr.oid, p.pubname " + "FROM pg_catalog.pg_publication_rel pr," + " pg_catalog.pg_publication p " + "WHERE pr.prrelid = '%u'" + " AND p.oid = pr.prpubid", + tbinfo->dobj.catId.oid); + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + if (ntups == 0) { + /* + * Table is not member of any publications. Clean up and return. + */ + PQclear(res); + continue; + } + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_pubname = PQfnumber(res, "pubname"); + + pubrinfo = (PublicationRelInfo *)pg_malloc(ntups * sizeof(PublicationRelInfo)); + for (j = 0; j < ntups; j++) { + pubrinfo[j].dobj.objType = DO_PUBLICATION_REL; + pubrinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid)); + pubrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid)); + AssignDumpId(&pubrinfo[j].dobj); + pubrinfo[j].dobj.nmspace = tbinfo->dobj.nmspace; + pubrinfo[j].dobj.name = tbinfo->dobj.name; + pubrinfo[j].pubname = gs_strdup(PQgetvalue(res, j, i_pubname)); + pubrinfo[j].pubtable = tbinfo; + + /* Decide whether we want to dump it */ + selectDumpablePublicationTable(&(pubrinfo[j].dobj)); + } + PQclear(res); + } + destroyPQExpBuffer(query); +} + +/* + * dumpPublicationTable + * dump the definition of the given publication table mapping + */ +static void dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo) +{ + TableInfo *tbinfo = pubrinfo->pubtable; + PQExpBuffer query; + PQExpBuffer tag; + + if (dataOnly || pubrinfo->dobj.dump) { + return; + } + + tag = createPQExpBuffer(); + appendPQExpBuffer(tag, "%s %s", pubrinfo->pubname, tbinfo->dobj.name); + + query = createPQExpBuffer(); + appendPQExpBuffer(query, "ALTER PUBLICATION %s ADD TABLE ONLY", fmtId(pubrinfo->pubname)); + appendPQExpBuffer(query, " %s;", fmtId(tbinfo->dobj.name)); + + /* + * There is no point in creating drop query as drop query as the drop + * is done by table drop. + */ + ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId, tag->data, tbinfo->dobj.nmspace->dobj.name, NULL, + "", false, "PUBLICATION TABLE", SECTION_POST_DATA, query->data, "", NULL, NULL, 0, NULL, NULL); + + destroyPQExpBuffer(tag); + destroyPQExpBuffer(query); +} + +/* + * Is the currently connected user a superuser? + */ +static bool is_superuser(Archive *fout) +{ + ArchiveHandle *AH = (ArchiveHandle *)fout; + const char *val; + + val = PQparameterStatus(AH->connection, "is_sysadmin"); + + if (val && strcmp(val, "on") == 0) { + return true; + } + + return false; +} + +/* + * getSubscriptions + * get information about subscriptions + */ +void getSubscriptions(Archive *fout) +{ + PQExpBuffer query; + PGresult *res; + SubscriptionInfo *subinfo; + int i_tableoid; + int i_oid; + int i_subname; + int i_rolname; + int i_subconninfo; + int i_subslotname; + int i_subsynccommit; + int i_subpublications; + int i, ntups; + + if (no_subscriptions || GetVersionNum(fout) < SUBSCRIPTION_VERSION) { + return; + } + + if (!is_superuser(fout)) { + int n; + + res = ExecuteSqlQuery(fout, + "SELECT count(*) FROM pg_subscription " + "WHERE subdbid = (SELECT oid FROM pg_catalog.pg_database" + " WHERE datname = current_database())", + PGRES_TUPLES_OK); + n = atoi(PQgetvalue(res, 0, 0)); + if (n > 0) { + write_msg(NULL, "WARNING: subscriptions not dumped because current user is not a superuser\n"); + } + PQclear(res); + return; + } + + query = createPQExpBuffer(); + + resetPQExpBuffer(query); + + /* Get the subscriptions in current database. */ + appendPQExpBuffer(query, + "SELECT s.tableoid, s.oid, s.subname," + "(%s s.subowner) AS rolname, " + " s.subconninfo, s.subslotname, s.subsynccommit, s.subpublications " + "FROM pg_catalog.pg_subscription s " + "WHERE s.subdbid = (SELECT oid FROM pg_catalog.pg_database" + " WHERE datname = current_database())", + username_subquery); + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_subname = PQfnumber(res, "subname"); + i_rolname = PQfnumber(res, "rolname"); + i_subconninfo = PQfnumber(res, "subconninfo"); + i_subslotname = PQfnumber(res, "subslotname"); + i_subsynccommit = PQfnumber(res, "subsynccommit"); + i_subpublications = PQfnumber(res, "subpublications"); + + subinfo = (SubscriptionInfo *)pg_malloc(ntups * sizeof(SubscriptionInfo)); + + for (i = 0; i < ntups; i++) { + subinfo[i].dobj.objType = DO_SUBSCRIPTION; + subinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + subinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&subinfo[i].dobj); + subinfo[i].dobj.name = gs_strdup(PQgetvalue(res, i, i_subname)); + subinfo[i].rolname = gs_strdup(PQgetvalue(res, i, i_rolname)); + subinfo[i].subconninfo = gs_strdup(PQgetvalue(res, i, i_subconninfo)); + if (PQgetisnull(res, i, i_subslotname)) { + subinfo[i].subslotname = NULL; + } else { + subinfo[i].subslotname = gs_strdup(PQgetvalue(res, i, i_subslotname)); + } + subinfo[i].subsynccommit = gs_strdup(PQgetvalue(res, i, i_subsynccommit)); + subinfo[i].subpublications = gs_strdup(PQgetvalue(res, i, i_subpublications)); + + if (strlen(subinfo[i].rolname) == 0) { + write_msg(NULL, "WARNING: owner of subscription \"%s\" appears to be invalid\n", subinfo[i].dobj.name); + } + + /* Decide whether we want to dump it */ + selectDumpableObject(&(subinfo[i].dobj), fout); + } + PQclear(res); + + destroyPQExpBuffer(query); +} + +/* + * dumpSubscription + * dump the definition of the given subscription + */ +static void dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo) +{ + PQExpBuffer delq; + PQExpBuffer query; + PQExpBuffer labelq; + PQExpBuffer publications; + char **pubnames = NULL; + int npubnames = 0; + int i; + + if (!subinfo->dobj.dump || dataOnly) { + return; + } + + delq = createPQExpBuffer(); + query = createPQExpBuffer(); + labelq = createPQExpBuffer(); + + appendPQExpBuffer(delq, "DROP SUBSCRIPTION %s;\n", fmtId(subinfo->dobj.name)); + + appendPQExpBuffer(query, "CREATE SUBSCRIPTION %s CONNECTION ", fmtId(subinfo->dobj.name)); + appendStringLiteralAH(query, subinfo->subconninfo, fout); + + /* Build list of quoted publications and append them to query. */ + if (!parsePGArray(subinfo->subpublications, &pubnames, &npubnames)) { + write_msg(NULL, "WARNING: could not parse subpublications array\n"); + if (pubnames) { + free(pubnames); + } + pubnames = NULL; + npubnames = 0; + } + + publications = createPQExpBuffer(); + for (i = 0; i < npubnames; i++) { + if (i > 0) { + appendPQExpBufferStr(publications, ", "); + } + + appendPQExpBufferStr(publications, fmtId(pubnames[i])); + } + + appendPQExpBuffer(query, " PUBLICATION %s WITH (enabled = false, slot_name = ", publications->data); + if (subinfo->subslotname) { + appendStringLiteralAH(query, subinfo->subslotname, fout); + } else { + appendPQExpBufferStr(query, "NONE"); + } + + if (strcmp(subinfo->subsynccommit, "off") != 0) { + appendPQExpBuffer(query, ", synchronous_commit = %s", fmtId(subinfo->subsynccommit)); + } + + appendPQExpBufferStr(query, ");\n"); + appendPQExpBuffer(labelq, "SUBSCRIPTION %s", fmtId(subinfo->dobj.name)); + ArchiveEntry(fout, subinfo->dobj.catId, subinfo->dobj.dumpId, subinfo->dobj.name, NULL, NULL, subinfo->rolname, + false, "SUBSCRIPTION", SECTION_POST_DATA, query->data, delq->data, NULL, NULL, 0, NULL, NULL); + + dumpComment(fout, labelq->data, NULL, subinfo->rolname, subinfo->dobj.catId, 0, subinfo->dobj.dumpId); + dumpSecLabel(fout, labelq->data, NULL, subinfo->rolname, subinfo->dobj.catId, 0, subinfo->dobj.dumpId); + + destroyPQExpBuffer(publications); + if (pubnames) { + free(pubnames); + } + + destroyPQExpBuffer(delq); + destroyPQExpBuffer(query); +} + static void binary_upgrade_set_type_oids_by_type_oid( Archive* fout, PQExpBuffer upgrade_buffer, Oid pg_type_oid, bool error_tbl) { @@ -10046,6 +10500,15 @@ static void dumpDumpableObject(Archive* fout, DumpableObject* dobj) case DO_RLSPOLICY: dumpRlsPolicy(fout, (RlsPolicyInfo*)dobj); break; + case DO_PUBLICATION: + dumpPublication(fout, (PublicationInfo *)dobj); + break; + case DO_PUBLICATION_REL: + dumpPublicationTable(fout, (PublicationRelInfo *)dobj); + break; + case DO_SUBSCRIPTION: + dumpSubscription(fout, (SubscriptionInfo *)dobj); + break; case DO_PRE_DATA_BOUNDARY: case DO_POST_DATA_BOUNDARY: /* never dumped, nothing to do */ @@ -20827,6 +21290,9 @@ static void addBoundaryDependencies(DumpableObject** dobjs, int numObjs, Dumpabl case DO_TRIGGER: case DO_DEFAULT_ACL: case DO_RLSPOLICY: + case DO_PUBLICATION: + case DO_PUBLICATION_REL: + case DO_SUBSCRIPTION: /* Post-data objects: must come after the post-data boundary */ if (dobj->objType == DO_INDEX && ((IndxInfo*)dobj)->indextable && ((IndxInfo*)dobj)->indextable->isMOT) { diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index bc5b1258a..03a32a2a6 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -89,7 +89,10 @@ typedef enum { DO_PRE_DATA_BOUNDARY, DO_POST_DATA_BOUNDARY, DO_FTBL_CONSTRAINT, /* dump informational constraint info of the HDFS foreign table, also used for MOT table */ - DO_RLSPOLICY /* dump row level security policy of table */ + DO_RLSPOLICY, /* dump row level security policy of table */ + DO_PUBLICATION, + DO_PUBLICATION_REL, + DO_SUBSCRIPTION } DumpableObjectType; typedef struct _dumpableObject { @@ -463,6 +466,40 @@ typedef struct _blobInfo { char* blobacl; } BlobInfo; +/* + * The PublicationInfo struct is used to represent publications. + */ +typedef struct _PublicationInfo { + DumpableObject dobj; + char *rolname; + bool puballtables; + bool pubinsert; + bool pubupdate; + bool pubdelete; +} PublicationInfo; + +/* + * The PublicationRelInfo struct is used to represent publication table + * mapping. + */ +typedef struct _PublicationRelInfo { + DumpableObject dobj; + TableInfo *pubtable; + char *pubname; +} PublicationRelInfo; + +/* + * The SubscriptionInfo struct is used to represent subscription. + */ +typedef struct _SubscriptionInfo { + DumpableObject dobj; + char *rolname; + char *subconninfo; + char *subslotname; + char *subsynccommit; + char *subpublications; +} SubscriptionInfo; + /* global decls */ extern bool force_quotes; /* double-quotes for identifiers flag */ extern bool g_verbose; /* verbose flag */ @@ -544,6 +581,9 @@ extern void getExtensionMembership(Archive* fout, ExtensionInfo extinfo[], int n extern void help(const char* progname); extern bool IsRbObject(Archive* fout, Oid classid, Oid objid, const char* objname); extern uint32 GetVersionNum(Archive* fout); +extern void getPublications(Archive *fout); +extern void getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables); +extern void getSubscriptions(Archive *fout); #ifdef GSDUMP_LLT void stopLLT(); diff --git a/src/bin/pg_dump/pg_dump_sort.cpp b/src/bin/pg_dump/pg_dump_sort.cpp index d91c407a0..002ce23e7 100644 --- a/src/bin/pg_dump/pg_dump_sort.cpp +++ b/src/bin/pg_dump/pg_dump_sort.cpp @@ -117,7 +117,10 @@ static const int newObjectTypePriority[] = { 22, /* DO_PRE_DATA_BOUNDARY */ 25, /* DO_POST_DATA_BOUNDARY */ 31, /* DO_FTBL_CONSTRAINT */ - 33 /* DO_RLSPOLICY */ + 33, /* DO_RLSPOLICY */ + 34, /* DO_PUBLICATION */ + 35, /* DO_PUBLICATION_REL */ + 36 /* DO_SUBSCRIPTION */ }; static DumpId postDataBoundId; @@ -1218,6 +1221,19 @@ static void describeDumpableObject(DumpableObject* obj, char* buf, int bufsize) nRet = snprintf_s( buf, bufsize, bufsize - 1, "ROW LEVEL SECURITY POLICY (ID %d OID %u)", obj->dumpId, obj->catId.oid); return; + case DO_PUBLICATION: + nRet = snprintf_s(buf, bufsize, bufsize - 1, "PUBLICATION (ID %d OID %u)", obj->dumpId, obj->catId.oid); + securec_check_ss_c(nRet, "\0", "\0"); + return; + case DO_PUBLICATION_REL: + nRet = snprintf_s(buf, bufsize, bufsize - 1, "PUBLICATION TABLE (ID %d OID %u)", + obj->dumpId, obj->catId.oid); + securec_check_ss_c(nRet, "\0", "\0"); + return; + case DO_SUBSCRIPTION: + nRet = snprintf_s(buf, bufsize, bufsize - 1, "SUBSCRIPTION (ID %d OID %u)", obj->dumpId, obj->catId.oid); + securec_check_ss_c(nRet, "\0", "\0"); + return; default: break; } diff --git a/src/bin/pg_dump/pg_dumpall.cpp b/src/bin/pg_dump/pg_dumpall.cpp index 1a8159354..440473d47 100644 --- a/src/bin/pg_dump/pg_dumpall.cpp +++ b/src/bin/pg_dump/pg_dumpall.cpp @@ -143,6 +143,8 @@ static bool dont_overwritefile = false; static bool dump_templatedb = false; static char* parallel_jobs = NULL; static bool is_pipeline = false; +static int no_subscriptions = 0; +static int no_publications = 0; GS_UCHAR init_rand[RANDOM_LEN + 1] = {0}; #define RAND_COUNT 100 @@ -226,7 +228,9 @@ int main(int argc, char* argv[]) {"with-key", required_argument, NULL, 7}, {"dont-overwrite-file", no_argument, NULL, 8}, {"use-set-session-authorization", no_argument, &use_setsessauth, 1}, + {"no-publications", no_argument, &no_publications, 1}, {"no-security-labels", no_argument, &no_security_labels, 1}, + {"no-subscriptions", no_argument, &no_subscriptions, 1}, {"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1}, {"include-alter-table", no_argument, &include_alter_table, 1}, #ifdef ENABLE_MULTIPLE_NODES @@ -1159,6 +1163,10 @@ static void validate_dumpall_options(char** argv) appendPQExpBuffer(pgdumpopts, " --no-security-labels"); if (no_unlogged_table_data) appendPQExpBuffer(pgdumpopts, " --no-unlogged-table-data"); + if (no_subscriptions) + appendPQExpBuffer(pgdumpopts, " --no-subscriptions"); + if (no_publications) + appendPQExpBuffer(pgdumpopts, " --no-publications"); #ifdef ENABLE_MULTIPLE_NODES if (include_nodes) @@ -1193,8 +1201,10 @@ void help(void) printf(_(" --disable-dollar-quoting disable dollar quoting, use SQL standard quoting\n")); printf(_(" --disable-triggers disable triggers during data-only restore\n")); printf(_(" --inserts dump data as INSERT commands, rather than COPY\n")); + printf(_(" --no-publications do not dump publications\n")); printf(_(" --no-security-labels do not dump security label assignments\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); + printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-unlogged-table-data do not dump unlogged table data\n")); printf(_(" --include-alter-table dump the table delete column\n")); printf(_(" --quote-all-identifiers quote all identifiers, even if not key words\n")); diff --git a/src/bin/pg_dump/pg_restore.cpp b/src/bin/pg_dump/pg_restore.cpp index e5625ebe2..658eea0c2 100644 --- a/src/bin/pg_dump/pg_restore.cpp +++ b/src/bin/pg_dump/pg_restore.cpp @@ -86,6 +86,8 @@ static char* passwd = NULL; static char* decrypt_key = NULL; static bool is_encrypt = false; static bool is_pipeline = false; +static int no_subscriptions = 0; +static int no_publications = 0; typedef struct option optType; #ifdef GSDUMP_LLT @@ -147,7 +149,9 @@ int main(int argc, char** argv) {"role", required_argument, NULL, 2}, {"section", required_argument, NULL, 3}, {"use-set-session-authorization", no_argument, &use_setsessauth, 1}, + {"no-publications", no_argument, &no_publications, 1}, {"no-security-labels", no_argument, &no_security_labels, 1}, + {"no-subscriptions", no_argument, &no_subscriptions, 1}, {"rolepassword", required_argument, NULL, 5}, {"with-key", required_argument, NULL, 6}, {"pipeline", no_argument, NULL, 7}, @@ -502,6 +506,8 @@ static void validate_restore_options(char** argv, RestoreOptions* opts) opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = use_setsessauth; opts->no_security_labels = no_security_labels; + opts->no_subscriptions = no_subscriptions; + opts->no_publications = no_publications; if (NULL != opts->formatName) { switch (opts->formatName[0]) { @@ -758,7 +764,9 @@ void usage(const char* pchProgname) printf(_(" --disable-triggers disable triggers during data-only restore\n")); printf(_(" --no-data-for-failed-tables do not restore data of tables that could not be\n" " created\n")); + printf(_(" --no-publications do not restore publications\n")); printf(_(" --no-security-labels do not restore security labels\n")); + printf(_(" --no-subscriptions do not restore subscriptions\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); printf(_(" --section=SECTION restore named section (pre-data, data, or post-data)\n")); printf(_(" --use-set-session-authorization use SET SESSION AUTHORIZATION commands instead of\n" diff --git a/src/bin/psql/tab-complete.cpp b/src/bin/psql/tab-complete.cpp index 823f2a76e..ff5ab798b 100644 --- a/src/bin/psql/tab-complete.cpp +++ b/src/bin/psql/tab-complete.cpp @@ -510,6 +510,7 @@ static const pgsql_thing_t words_after_create[] = { {"POLICY", NULL, NULL, 0}, {"PROCEDURAL", NULL, NULL, 0}, {"PROCEDURE", NULL, Query_for_list_of_procedures, 0}, + {"PUBLICATION", NULL, NULL, 0}, {"ROLE", Query_for_list_of_roles, NULL, 0}, {"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE " @@ -520,6 +521,7 @@ static const pgsql_thing_t words_after_create[] = { {"SCHEMA", Query_for_list_of_schemas, NULL, 0}, {"SEQUENCE", NULL, &Query_for_list_of_sequences, 0}, {"SERVER", Query_for_list_of_servers, NULL, 0}, + {"SUBSCRIPTION", NULL, NULL, 0}, {"TABLE", NULL, &Query_for_list_of_tables, 0}, {"TABLESPACE", Query_for_list_of_tablespaces, NULL, 0}, {"TEMP", NULL, NULL, THING_NO_DROP}, /* for CREATE TEMP TABLE ... */ @@ -807,8 +809,8 @@ static char** PsqlCompletion(const char *text, int start, int end) "AGGREGATE", "APP WORKLOAD GROUP", "APP WORKLOAD GROUP MAPPING", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION", "GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", - "MATERIALIZED VIEW", "OPERATOR", "POLICY", "PROCEDURE", "ROLE", "ROW LEVEL SECURITY POLICY", - "RULE", "SCHEMA", "SERVER", "SESSION", "SEQUENCE", "SYSTEM", "TABLE", "TABLESPACE", + "MATERIALIZED VIEW", "OPERATOR", "POLICY", "PROCEDURE", "PUBLICATION", "ROLE", "ROW LEVEL SECURITY POLICY", + "RULE", "SCHEMA", "SERVER", "SESSION", "SEQUENCE", "SUBSCRIPTION", "SYSTEM", "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", "USER", "USER MAPPING FOR", "VIEW", NULL }; @@ -835,6 +837,41 @@ static char** PsqlCompletion(const char *text, int start, int end) free(tmpBuf); } } + /* ALTER PUBLICATION ... */ + else if (pg_strcasecmp(PREV2_WD, "ALTER") == 0 && pg_strcasecmp(PREV_WD, "PUBLICATION") == 0) { + static const char * const listAlterPub[] = {"ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET", NULL}; + COMPLETE_WITH_LIST(listAlterPub); + } + /* ALTER PUBLICATION SET */ + else if (pg_strcasecmp(PREV4_WD, "ALTER") == 0 && pg_strcasecmp(PREV3_WD, "PUBLICATION") == 0 && + pg_strcasecmp(PREV_WD, "SET") == 0) { + static const char * const listAlterPubSet[] = {"(", "TABLE", NULL}; + COMPLETE_WITH_LIST(listAlterPubSet); + } + /* ALTER PUBLICATION SET ( */ + else if (pg_strcasecmp(PREV5_WD, "ALTER") == 0 && pg_strcasecmp(PREV4_WD, "PUBLICATION") == 0 && + pg_strcasecmp(PREV2_WD, "SET") == 0 && pg_strcasecmp(PREV_WD, "(") == 0) { + static const char * const listAlterPubSet2[] = {"publish", NULL}; + COMPLETE_WITH_LIST(listAlterPubSet2); + } + /* ALTER SUBSCRIPTION */ + else if (pg_strcasecmp(PREV2_WD, "ALTER") == 0 && pg_strcasecmp(PREV_WD, "SUBSCRIPTION") == 0) { + static const char * const listAlterSub[] = {"CONNECTION", "ENABLE", "DISABLE", "OWNER TO", "RENAME TO", + "SET", NULL}; + COMPLETE_WITH_LIST(listAlterSub); + } + /* ALTER SUBSCRIPTION SET */ + else if (pg_strcasecmp(PREV4_WD, "ALTER") == 0 && pg_strcasecmp(PREV3_WD, "SUBSCRIPTION") == 0 && + pg_strcasecmp(PREV_WD, "SET") == 0) { + static const char * const listAlterSubSet[] = {"(", "PUBLICATION", NULL}; + COMPLETE_WITH_LIST(listAlterSubSet); + } + /* ALTER SUBSCRIPTION SET ( */ + else if (pg_strcasecmp(PREV5_WD, "ALTER") == 0 && pg_strcasecmp(PREV4_WD, "SUBSCRIPTION") == 0 && + pg_strcasecmp(PREV2_WD, "SET") == 0 && pg_strcasecmp(PREV_WD, "(") == 0) { + static const char * const listAlterSubSet2[] = {"slot_name", "synchronous_commit", NULL}; + COMPLETE_WITH_LIST(listAlterSubSet2); + } /* ALTER SESSION */ else if (pg_strcasecmp(PREV2_WD, "ALTER") == 0 && pg_strcasecmp(PREV_WD, "SESSION") == 0) @@ -1867,6 +1904,27 @@ static char** PsqlCompletion(const char *text, int start, int end) COMPLETE_WITH_CONST("("); } + /* CREATE PUBLICATION */ + else if (pg_strcasecmp(PREV3_WD, "CREATE") == 0 && pg_strcasecmp(PREV2_WD, "PUBLICATION") == 0) { + static const char * const createPub[] = {"FOR TABLE", "FOR ALL TABLES", "WITH (", NULL}; + COMPLETE_WITH_LIST(createPub); + } + /* CREATE PUBLICATION FOR */ + else if (pg_strcasecmp(PREV4_WD, "CREATE") == 0 && pg_strcasecmp(PREV3_WD, "PUBLICATION") == 0 && + pg_strcasecmp(PREV_WD, "FOR") == 0) { + static const char * const createPub2[] = {"TABLE", "ALL TABLES", NULL}; + COMPLETE_WITH_LIST(createPub2); + } + /* CREATE PUBLICATION FOR TABLE , ... */ + else if (pg_strcasecmp(PREV5_WD, "CREATE") == 0 && pg_strcasecmp(PREV4_WD, "PUBLICATION") == 0 && + pg_strcasecmp(PREV2_WD, "FOR") == 0 && pg_strcasecmp(PREV_WD, "TABLE") == 0) { + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + } + /* CREATE SUBSCRIPTION */ + else if (pg_strcasecmp(PREV3_WD, "CREATE") == 0 && pg_strcasecmp(PREV2_WD, "SUBSCRIPTION") == 0) { + COMPLETE_WITH_CONST("CONNECTION"); + } + /* CREATE RULE */ /* Complete "CREATE RULE " with "AS" */ else if (pg_strcasecmp(PREV3_WD, "CREATE") == 0 && pg_strcasecmp(PREV2_WD, "RULE") == 0) @@ -2197,8 +2255,10 @@ static char** PsqlCompletion(const char *text, int start, int end) pg_strcasecmp(PREV2_WD, "CONVERSION") == 0 || pg_strcasecmp(PREV2_WD, "DOMAIN") == 0 || pg_strcasecmp(PREV2_WD, "EXTENSION") == 0 || pg_strcasecmp(PREV2_WD, "FUNCTION") == 0 || pg_strcasecmp(PREV2_WD, "INDEX") == 0 || pg_strcasecmp(PREV2_WD, "LANGUAGE") == 0 || + pg_strcasecmp(PREV2_WD, "PUBLICATION") == 0 || pg_strcasecmp(PREV2_WD, "SCHEMA") == 0 || pg_strcasecmp(PREV2_WD, "SEQUENCE") == 0 || - pg_strcasecmp(PREV2_WD, "SERVER") == 0 || pg_strcasecmp(PREV2_WD, "TABLE") == 0 || + pg_strcasecmp(PREV2_WD, "SERVER") == 0 || pg_strcasecmp(PREV2_WD, "SUBSCRIPTION") == 0 || + pg_strcasecmp(PREV2_WD, "TABLE") == 0 || pg_strcasecmp(PREV2_WD, "TYPE") == 0 || pg_strcasecmp(PREV2_WD, "VIEW") == 0 || pg_strcasecmp(PREV2_WD, "USER") == 0)) || (pg_strcasecmp(PREV4_WD, "DROP") == 0 && pg_strcasecmp(PREV3_WD, "AGGREGATE") == 0 && diff --git a/src/common/backend/catalog/CMakeLists.txt b/src/common/backend/catalog/CMakeLists.txt index 3cb44e664..e783da94d 100755 --- a/src/common/backend/catalog/CMakeLists.txt +++ b/src/common/backend/catalog/CMakeLists.txt @@ -14,7 +14,8 @@ set(POSTGRES_BKI_SRCS_S @gs_client_global_keys_args.h @pg_job.h @gs_asp.h @pg_job_proc.h @pg_extension_data_source.h @pg_statistic_ext.h @pg_object.h @pg_synonym.h @toasting.h @indexing.h @gs_obsscaninfo.h @pg_directory.h @pg_hashbucket.h @gs_global_chain.h @gs_global_config.h @pg_streaming_stream.h @pg_streaming_cont_query.h @pg_streaming_reaper_status.h @gs_matview.h @gs_matview_dependency.h @pgxc_slice.h -@gs_opt_model.h @pg_recyclebin.h @pg_snapshot.h @gs_model.h @gs_package.h" +@gs_opt_model.h @pg_recyclebin.h @pg_snapshot.h @gs_model.h @gs_package.h +@pg_replication_origin.h @pg_publication.h @pg_publication_rel.h @pg_subscription.h" ) string(REPLACE "@" "${PROJECT_SRC_DIR}/include/catalog/" POSTGRES_BKI_SRCS ${POSTGRES_BKI_SRCS_S}) diff --git a/src/common/backend/catalog/Makefile b/src/common/backend/catalog/Makefile index 221f2c95c..4be4eb793 100644 --- a/src/common/backend/catalog/Makefile +++ b/src/common/backend/catalog/Makefile @@ -23,7 +23,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ pg_operator.o gs_package.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o pg_synonym.o\ pg_type.o pgxc_class.o storage.o storage_gtt.o toasting.o pg_job.o pg_partition.o\ pg_hashbucket.o cstore_ctlg.o dfsstore_ctlg.o pg_builtin_proc.o streaming_stream.o\ - gs_matview.o pgxc_slice.o + gs_matview.o pgxc_slice.o pg_publication.o pg_subscription.o BKIFILES = postgres.bki postgres.description postgres.shdescription @@ -58,7 +58,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ toasting.h indexing.h gs_obsscaninfo.h pg_directory.h pg_hashbucket.h gs_global_chain.h gs_global_config.h\ pg_streaming_stream.h pg_streaming_cont_query.h pg_streaming_reaper_status.h gs_matview.h\ gs_matview_dependency.h pgxc_slice.h gs_opt_model.h gs_model.h\ - pg_recyclebin.h pg_snapshot.h \ + pg_recyclebin.h pg_snapshot.h pg_replication_origin.h pg_publication.h pg_publication_rel.h pg_subscription.h\ ) # location of Catalog.pm diff --git a/src/common/backend/catalog/aclchk.cpp b/src/common/backend/catalog/aclchk.cpp index 6fbfeb0c8..d4514bb4f 100644 --- a/src/common/backend/catalog/aclchk.cpp +++ b/src/common/backend/catalog/aclchk.cpp @@ -46,7 +46,9 @@ #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_proc.h" +#include "catalog/pg_publication.h" #include "catalog/gs_package.h" +#include "catalog/pg_subscription.h" #include "catalog/pg_synonym.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_type.h" @@ -4725,6 +4727,10 @@ static const char* const not_owner_msg[MAX_ACL_KIND] = { gettext_noop("must be owner of column encryption key %s"), /* ACL_KIND_GLOBAL_SETTING */ gettext_noop("must be owner of client master key %s"), + /* ACL_KIND_PUBLICATION */ + gettext_noop("must be owner of publication %s"), + /* ACL_KIND_SUBSCRIPTION */ + gettext_noop("must be owner of subscription %s"), }; void aclcheck_error(AclResult aclerr, AclObjectKind objectkind, const char* objectname) @@ -6940,6 +6946,54 @@ bool pg_synonym_ownercheck(Oid synOid, Oid roleId) return has_privs_of_role(roleId, ownerId); } +/* + * Ownership check for an publication (specified by OID). + */ +bool pg_publication_ownercheck(Oid pub_oid, Oid roleid) +{ + HeapTuple tuple; + Oid ownerId; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return true; + + tuple = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pub_oid)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("publication with OID %u does not exist", pub_oid))); + + ownerId = ((Form_pg_publication)GETSTRUCT(tuple))->pubowner; + + ReleaseSysCache(tuple); + + return has_privs_of_role(roleid, ownerId); +} + +/* + * Ownership check for an subscription (specified by OID). + */ +bool pg_subscription_ownercheck(Oid sub_oid, Oid roleid) +{ + HeapTuple tuple; + Oid ownerId; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) { + return true; + } + + tuple = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(sub_oid)); + if (!HeapTupleIsValid(tuple)) { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("subscription with OID %u does not exist", sub_oid))); + } + + ownerId = ((Form_pg_subscription)GETSTRUCT(tuple))->subowner; + + ReleaseSysCache(tuple); + + return has_privs_of_role(roleid, ownerId); +} + /* * Check whether specified role has CREATEROLE privilege (or is a superuser) * @@ -6957,8 +7011,9 @@ bool has_createrole_privilege(Oid roleid) HeapTuple utup = NULL; /* Superusers bypass all permission checking. */ - if (superuser_arg(roleid)) + if (superuser_arg(roleid)) { return true; + } utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); if (HeapTupleIsValid(utup)) { diff --git a/src/common/backend/catalog/builtin_funcs.ini b/src/common/backend/catalog/builtin_funcs.ini index 1d973063c..466a9e6a7 100755 --- a/src/common/backend/catalog/builtin_funcs.ini +++ b/src/common/backend/catalog/builtin_funcs.ini @@ -7463,6 +7463,10 @@ "pg_get_keywords", 1, AddBuiltinFunc(_0(1686), _1("pg_get_keywords"), _2(0), _3(true), _4(true), _5(pg_get_keywords), _6(2249), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(10), _11(400), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(0), _21(3, 25, 18, 25), _22(3, 'o', 'o', 'o'), _23(3, "word", "catcode", "catdesc"), _24(NULL), _25("pg_get_keywords"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false)) ), + AddFuncGroup( + "pg_get_publication_tables", 1, + AddBuiltinFunc(_0(2801), _1("pg_get_publication_tables"), _2(1), _3(true), _4(true), _5(pg_get_publication_tables), _6(26), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(1000), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(1, 25), _21(2, 25, 26), _22(2, 'i', 'o'), _23(2, "pubname", "relid"), _24(NULL), _25("pg_get_publication_tables"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("get OIDs of tables in a publication"), _34('f'), _35(NULL), _36(0), _37(false)) + ), AddFuncGroup( "pg_get_replication_slot_name", 1, AddBuiltinFunc(_0(6003), _1("pg_get_replication_slot_name"), _2(0), _3(true), _4(false), _5(pg_get_replication_slot_name), _6(25), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(0), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_get_replication_slot_name"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false)) @@ -7942,6 +7946,50 @@ "pg_stat_get_db_blocks_hit", 1, AddBuiltinFunc(_0(1945), _1("pg_stat_get_db_blocks_hit"), _2(1), _3(true), _4(false), _5(pg_stat_get_db_blocks_hit), _6(20), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(1, 26), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_stat_get_db_blocks_hit"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false)) ), + AddFuncGroup( + "pg_replication_origin_advance", 1, + AddBuiltinFunc(_0(2634), _1("pg_replication_origin_advance"), _2(2), _3(true), _4(false), _5(pg_replication_origin_advance), _6(2278), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(2, 25, 25), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_replication_origin_advance"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("advance replication itentifier to specific location"), _34('f'), _35(NULL), _36(0), _37(false)) + ), + AddFuncGroup( + "pg_replication_origin_create", 1, + AddBuiltinFunc(_0(2635), _1("pg_replication_origin_create"), _2(1), _3(true), _4(false), _5(pg_replication_origin_create), _6(26), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(1, 25), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_replication_origin_create"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("create a replication origin"), _34('f'), _35(NULL), _36(0), _37(false)) + ), + AddFuncGroup( + "pg_replication_origin_drop", 1, + AddBuiltinFunc(_0(2636), _1("pg_replication_origin_drop"), _2(1), _3(true), _4(false), _5(pg_replication_origin_drop), _6(2278), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(1, 25), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_replication_origin_drop"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("drop replication origin identified by its name"), _34('f'), _35(NULL), _36(0), _37(false)) + ), + AddFuncGroup( + "pg_replication_origin_oid", 1, + AddBuiltinFunc(_0(2637), _1("pg_replication_origin_oid"), _2(1), _3(true), _4(false), _5(pg_replication_origin_oid), _6(26), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(1, 25), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_replication_origin_oid"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("translate the replication origin's name to its id"), _34('f'), _35(NULL), _36(0), _37(false)) + ), + AddFuncGroup( + "pg_replication_origin_progress", 1, + AddBuiltinFunc(_0(2638), _1("pg_replication_origin_progress"), _2(2), _3(true), _4(false), _5(pg_replication_origin_progress), _6(25), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(2, 25, 16), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_replication_origin_progress"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("get an individual replication origin's replication progress"), _34('f'), _35(NULL), _36(0), _37(false)) + ), + AddFuncGroup( + "pg_replication_origin_session_is_setup", 1, + AddBuiltinFunc(_0(2639), _1("pg_replication_origin_session_is_setup"), _2(0), _3(true), _4(false), _5(pg_replication_origin_session_is_setup), _6(16), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(0), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_replication_origin_session_is_setup"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("is a replication origin configured in this session"), _34('f'), _35(NULL), _36(0), _37(false)) + ), + AddFuncGroup( + "pg_replication_origin_session_progress", 1, + AddBuiltinFunc(_0(2640), _1("pg_replication_origin_session_progress"), _2(1), _3(true), _4(false), _5(pg_replication_origin_session_progress), _6(25), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(1, 16), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_replication_origin_session_progress"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("get the replication progress of the current session"), _34('f'), _35(NULL), _36(0), _37(false)) + ), + AddFuncGroup( + "pg_replication_origin_session_reset", 1, + AddBuiltinFunc(_0(2750), _1("pg_replication_origin_session_reset"), _2(0), _3(true), _4(false), _5(pg_replication_origin_session_reset), _6(2278), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(0), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_replication_origin_session_reset"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("teardown configured replication progress tracking"), _34('f'), _35(NULL), _36(0), _37(false)) + ), + AddFuncGroup( + "pg_replication_origin_session_setup", 1, + AddBuiltinFunc(_0(2751), _1("pg_replication_origin_session_setup"), _2(1), _3(true), _4(false), _5(pg_replication_origin_session_setup), _6(2278), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(1, 25), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_replication_origin_session_setup"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("configure session to maintain replication progress tracking for the passed in origin"), _34('f'), _35(NULL), _36(0), _37(false)) + ), + AddFuncGroup( + "pg_replication_origin_xact_reset", 1, + AddBuiltinFunc(_0(2752), _1("pg_replication_origin_xact_reset"), _2(0), _3(true), _4(false), _5(pg_replication_origin_xact_reset), _6(2278), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(0), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_replication_origin_xact_reset"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("reset the transaction's origin lsn and timestamp"), _34('f'), _35(NULL), _36(0), _37(false)) + ), + AddFuncGroup( + "pg_replication_origin_xact_setup", 1, + AddBuiltinFunc(_0(2799), _1("pg_replication_origin_xact_setup"), _2(2), _3(true), _4(false), _5(pg_replication_origin_xact_setup), _6(2278), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(2, 25, 1184), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_replication_origin_xact_setup"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("setup the transaction's origin lsn and timestamp"), _34('f'), _35(NULL), _36(0), _37(false)) + ), AddFuncGroup( "pg_stat_get_db_conflict_all", 1, AddBuiltinFunc(_0(3070), _1("pg_stat_get_db_conflict_all"), _2(1), _3(true), _4(false), _5(pg_stat_get_db_conflict_all), _6(20), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(1, 26), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_stat_get_db_conflict_all"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false)) @@ -7974,6 +8022,10 @@ "pg_stat_get_db_cu_hdd_sync", 1, AddBuiltinFunc(_0(3488), _1("pg_stat_get_db_cu_hdd_sync"), _2(1), _3(true), _4(false), _5(pg_stat_get_db_cu_hdd_sync), _6(20), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(1, 26), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_stat_get_db_cu_hdd_sync"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false)) ), + AddFuncGroup( + "pg_show_replication_origin_status", 1, + AddBuiltinFunc(_0(2800), _1("pg_show_replication_origin_status"), _2(0), _3(false), _4(true), _5(pg_show_replication_origin_status), _6(2249), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(100), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(0), _21(4, 26, 25, 25, 25), _22(4, 'o', 'o', 'o', 'o'), _23(4, "local_id", "external_id", "remote_lsn", "local_lsn"), _24(NULL), _25("pg_show_replication_origin_status"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("get progress for all replication origins"), _34('f'), _35(NULL), _36(0), _37(false)) + ), AddFuncGroup( "pg_stat_get_db_cu_mem_hit", 1, AddBuiltinFunc(_0(3485), _1("pg_stat_get_db_cu_mem_hit"), _2(1), _3(true), _4(false), _5(pg_stat_get_db_cu_mem_hit), _6(20), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(1, 26), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_stat_get_db_cu_mem_hit"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false)) @@ -8369,6 +8421,10 @@ "pg_table_is_visible", 1, AddBuiltinFunc(_0(2079), _1("pg_table_is_visible"), _2(1), _3(true), _4(false), _5(pg_table_is_visible), _6(16), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(1, 26), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_table_is_visible"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false)) ), + AddFuncGroup( + "pg_stat_get_subscription", 1, + AddBuiltinFunc(_0(2802), _1("pg_stat_get_subscription"), _2(1), _3(false), _4(false), _5(pg_stat_get_subscription), _6(2249), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('s'), _19(0), _20(1, 26), _21(8, 26, 26, 23, 25, 1184, 1184, 25, 1184), _22(8, 'i', 'o', 'o', 'o', 'o', 'o', 'o', 'o'), _23(8, "subid", "subid", "pid", "received_lsn", "last_msg_send_time", "last_msg_receipt_time", "latest_end_lsn", "latest_end_time"), _24(NULL), _25("pg_stat_get_subscription"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33("statistics: information about subscription"), _34('f'), _35(NULL), _36(0), _37(false)) + ), AddFuncGroup( "pg_table_size", 1, AddBuiltinFunc(_0(2997), _1("pg_table_size"), _2(1), _3(true), _4(false), _5(pg_table_size), _6(20), _7(PG_CATALOG_NAMESPACE), _8(BOOTSTRAP_SUPERUSERID), _9(INTERNALlanguageId), _10(1), _11(0), _12(0), _13(0), _14(false), _15(false), _16(false), _17(false), _18('v'), _19(0), _20(1, 2205), _21(NULL), _22(NULL), _23(NULL), _24(NULL), _25("pg_table_size"), _26(NULL), _27(NULL), _28(NULL), _29(0), _30(false), _31(NULL), _32(false), _33(NULL), _34('f'), _35(NULL), _36(0), _37(false)) diff --git a/src/common/backend/catalog/catalog.cpp b/src/common/backend/catalog/catalog.cpp index 42da0b6c2..f35035f91 100644 --- a/src/common/backend/catalog/catalog.cpp +++ b/src/common/backend/catalog/catalog.cpp @@ -39,9 +39,11 @@ #include "catalog/pg_pltemplate.h" #include "catalog/pg_db_role_setting.h" #include "catalog/pg_recyclebin.h" +#include "catalog/pg_replication_origin.h" #include "catalog/pg_shdepend.h" #include "catalog/pg_shdescription.h" #include "catalog/pg_shseclabel.h" +#include "catalog/pg_subscription.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_auth_history.h" #include "catalog/pg_user_status.h" @@ -819,7 +821,8 @@ bool IsSharedRelation(Oid relationId) relationId == GsGlobalConfigRelationId || #endif relationId == DbRoleSettingRelationId || relationId == PgJobRelationId || relationId == PgJobProcRelationId || - relationId == DataSourceRelationId || relationId == GSObsScanInfoRelationId) + relationId == DataSourceRelationId || relationId == GSObsScanInfoRelationId || + relationId == SubscriptionRelationId || relationId == ReplicationOriginRelationId) return true; /* These are their indexes (see indexing.h) */ if (relationId == AuthIdRolnameIndexId || relationId == AuthIdOidIndexId || relationId == AuthMemRoleMemIndexId || @@ -843,7 +846,9 @@ bool IsSharedRelation(Oid relationId) relationId == DbRoleSettingDatidRolidIndexId || /* Add job system table indexs */ relationId == PgJobOidIndexId || relationId == PgJobIdIndexId || relationId == PgJobProcOidIndexId || - relationId == PgJobProcIdIndexId || relationId == DataSourceOidIndexId || relationId == DataSourceNameIndexId) + relationId == PgJobProcIdIndexId || relationId == DataSourceOidIndexId || relationId == DataSourceNameIndexId || + relationId == SubscriptionObjectIndexId || relationId == SubscriptionNameIndexId || + relationId == ReplicationOriginIdentIndex || relationId == ReplicationOriginNameIndex) return true; /* These are their toast tables and toast indexes (see toasting.h) */ if (relationId == PgShdescriptionToastTable || relationId == PgShdescriptionToastIndex || diff --git a/src/common/backend/catalog/dependency.cpp b/src/common/backend/catalog/dependency.cpp index 1396895ee..ee6bfcc4b 100644 --- a/src/common/backend/catalog/dependency.cpp +++ b/src/common/backend/catalog/dependency.cpp @@ -52,9 +52,12 @@ #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_proc.h" +#include "catalog/pg_publication.h" +#include "catalog/pg_publication_rel.h" #include "catalog/gs_package.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_rlspolicy.h" +#include "catalog/pg_subscription.h" #include "catalog/pg_synonym.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_trigger.h" @@ -85,6 +88,7 @@ #include "commands/extension.h" #include "commands/matview.h" #include "commands/proclang.h" +#include "commands/publicationcmds.h" #include "commands/schemacmds.h" #include "commands/seclabel.h" #include "commands/sec_rls_cmds.h" @@ -146,7 +150,10 @@ static const Oid object_classes[MAX_OCLASS] = { DefaultAclRelationId, /* OCLASS_DEFACL */ ExtensionRelationId, /* OCLASS_EXTENSION */ PgDirectoryRelationId, /* OCLASS_DIRECTORY */ - PgJobRelationId /* OCLASS_PG_JOB */ + PgJobRelationId, /* OCLASS_PG_JOB */ + PublicationRelationId, /* OCLASS_PUBLICATION */ + PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */ + SubscriptionRelationId /* OCLASS_SUBSCRIPTION */ #ifdef PGXC , PgxcClassRelationId /* OCLASS_PGXCCLASS */ @@ -1447,6 +1454,13 @@ static void doDeletion(const ObjectAddress* object, int flags) case OCLASS_GS_CL_PROC: remove_encrypted_proc_by_id(object->objectId); break; + case OCLASS_PUBLICATION: + RemovePublicationById(object->objectId); + break; + + case OCLASS_PUBLICATION_REL: + RemovePublicationRelById(object->objectId); + break; default: ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized object class: %u", object->classId))); @@ -2451,6 +2465,14 @@ ObjectClass getObjectClass(const ObjectAddress* object) return OCLASS_DB4AI_MODEL; case ClientLogicProcId: return OCLASS_GS_CL_PROC; + case PublicationRelationId: + return OCLASS_PUBLICATION; + + case PublicationRelRelationId: + return OCLASS_PUBLICATION_REL; + + case SubscriptionRelationId: + return OCLASS_SUBSCRIPTION; default: break; } @@ -3096,6 +3118,34 @@ char* getObjectDescription(const ObjectAddress* object) appendStringInfo(&buffer, _("synonym %s"), qualifiedSynname); break; } + case OCLASS_PUBLICATION: { + appendStringInfo(&buffer, _("publication %s"), get_publication_name(object->objectId, false)); + break; + } + + case OCLASS_PUBLICATION_REL: { + HeapTuple tup; + char *pubname; + Form_pg_publication_rel prform; + + tup = SearchSysCache1(PUBLICATIONREL, ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) { + elog(ERROR, "cache lookup failed for publication table %u", object->objectId); + } + + prform = (Form_pg_publication_rel)GETSTRUCT(tup); + pubname = get_publication_name(prform->prpubid, false); + + appendStringInfo(&buffer, _("publication table %s in publication %s"), get_rel_name(prform->prrelid), + pubname); + ReleaseSysCache(tup); + break; + } + + case OCLASS_SUBSCRIPTION: { + appendStringInfo(&buffer, _("subscription %s"), get_subscription_name(object->objectId, false)); + break; + } default: appendStringInfo( diff --git a/src/common/backend/catalog/namespace.cpp b/src/common/backend/catalog/namespace.cpp index 743511b5a..2dd87be35 100644 --- a/src/common/backend/catalog/namespace.cpp +++ b/src/common/backend/catalog/namespace.cpp @@ -2933,7 +2933,7 @@ Oid LookupNamespaceNoError(const char* nspname) * * Returns the namespace OID. Raises ereport if any problem. */ -Oid LookupExplicitNamespace(const char* nspname) +Oid LookupExplicitNamespace(const char* nspname, bool missing_ok) { Oid namespaceId; AclResult aclresult; @@ -2966,7 +2966,10 @@ Oid LookupExplicitNamespace(const char* nspname) */ } - namespaceId = get_namespace_oid(nspname, false); + namespaceId = get_namespace_oid(nspname, missing_ok); + if (missing_ok && !OidIsValid(namespaceId)) { + return InvalidOid; + } if (!(u_sess->analyze_cxt.is_under_analyze || (IS_PGXC_DATANODE && IsConnFromCoord())) || u_sess->exec_cxt.is_exec_trigger_func) { diff --git a/src/common/backend/catalog/objectaddress.cpp b/src/common/backend/catalog/objectaddress.cpp index ce33338be..9582aa887 100644 --- a/src/common/backend/catalog/objectaddress.cpp +++ b/src/common/backend/catalog/objectaddress.cpp @@ -38,8 +38,11 @@ #include "catalog/pg_opfamily.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" +#include "catalog/pg_publication.h" +#include "catalog/pg_publication_rel.h" #include "catalog/gs_package.h" #include "catalog/pg_rewrite.h" +#include "catalog/pg_subscription.h" #include "catalog/pg_rlspolicy.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_trigger.h" @@ -135,7 +138,19 @@ static THR_LOCAL const ObjectPropertyType ObjectProperty[] = {{CastRelationId, C Anum_pg_ts_template_tmplnamespace, }, {TypeRelationId, TypeOidIndexId, TYPEOID, Anum_pg_type_typnamespace}, - {RlsPolicyRelationId, PgRlspolicyOidIndex, -1, InvalidAttrNumber}}; + {RlsPolicyRelationId, PgRlspolicyOidIndex, -1, InvalidAttrNumber}, + { + PublicationRelationId, + PublicationObjectIndexId, + PUBLICATIONOID, + InvalidAttrNumber + }, + { + SubscriptionRelationId, + SubscriptionObjectIndexId, + SUBSCRIPTIONOID, + InvalidAttrNumber + }}; static ObjectAddress get_object_address_unqualified(ObjectType objtype, List* qualname, bool missing_ok); static ObjectAddress get_relation_by_qualified_name( @@ -146,6 +161,9 @@ static ObjectAddress get_object_address_attribute( static ObjectAddress get_object_address_type(ObjectType objtype, List* objname, bool missing_ok); static ObjectAddress get_object_address_opcf(ObjectType objtype, List* objname, List* objargs, bool missing_ok); static const ObjectPropertyType* get_object_property_data(Oid class_id); +static ObjectAddress get_object_address_publication_rel(List *objname, List *objargs, + Relation *relation, bool missing_ok); + /* * Translate an object name and arguments (as passed by the parser) to an @@ -219,6 +237,8 @@ ObjectAddress get_object_address( case OBJECT_FDW: case OBJECT_FOREIGN_SERVER: case OBJECT_DATA_SOURCE: + case OBJECT_PUBLICATION: + case OBJECT_SUBSCRIPTION: address = get_object_address_unqualified(objtype, objname, missing_ok); break; case OBJECT_TYPE: @@ -315,6 +335,8 @@ ObjectAddress get_object_address( case OBJECT_DIRECTORY: address = get_object_address_unqualified(objtype, objname, missing_ok); break; + case OBJECT_PUBLICATION_REL: + address = get_object_address_publication_rel(objname, objargs, &relation, missing_ok); default: ereport( ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized objtype: %d", (int)objtype))); @@ -441,6 +463,12 @@ static ObjectAddress get_object_address_unqualified(ObjectType objtype, List* qu case OBJECT_DIRECTORY: msg = gettext_noop("directory name cannot be qualified"); break; + case OBJECT_PUBLICATION: + msg = gettext_noop("publication name cannot be qualified"); + break; + case OBJECT_SUBSCRIPTION: + msg = gettext_noop("subscription name cannot be qualified"); + break; default: ereport( ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized objtype: %d", (int)objtype))); @@ -510,6 +538,16 @@ static ObjectAddress get_object_address_unqualified(ObjectType objtype, List* qu address.objectId = get_directory_oid(name, missing_ok); address.objectSubId = 0; break; + case OBJECT_PUBLICATION: + address.classId = PublicationRelationId; + address.objectId = get_publication_oid(name, missing_ok); + address.objectSubId = 0; + break; + case OBJECT_SUBSCRIPTION: + address.classId = SubscriptionRelationId; + address.objectId = get_subscription_oid(name, missing_ok); + address.objectSubId = 0; + break; default: ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized objtype: %d", (int)objtype))); /* placate compiler, which doesn't know elog won't return */ @@ -772,6 +810,47 @@ static ObjectAddress get_object_address_type(ObjectType objtype, List* objname, return address; } +/* + * Find the ObjectAddress for a publication relation. The objname parameter + * is the relation name; objargs contains the publication name. + */ +static ObjectAddress get_object_address_publication_rel(List *objname, List *objargs, Relation *relation, + bool missing_ok) +{ + ObjectAddress address; + char *pubname; + Publication *pub; + + address.classId = PublicationRelRelationId; + address.objectId = InvalidOid; + address.objectSubId = InvalidOid; + + *relation = relation_openrv_extended(makeRangeVarFromNameList(objname), AccessShareLock, missing_ok); + if (!relation) + return address; + + /* fetch publication name from input list */ + pubname = strVal(linitial(objargs)); + + /* Now look up the pg_publication tuple */ + pub = GetPublicationByName(pubname, missing_ok); + if (!pub) + return address; + + /* Find the publication relation mapping in syscache. */ + address.objectId = + GetSysCacheOid2(PUBLICATIONRELMAP, ObjectIdGetDatum(RelationGetRelid(*relation)), ObjectIdGetDatum(pub->oid)); + if (!OidIsValid(address.objectId)) { + if (!missing_ok) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("publication relation \"%s\" in publication \"%s\" does not exist", + RelationGetRelationName(*relation), pubname))); + return address; + } + + return address; +} + /* * Find the ObjectAddress for an opclass or opfamily. */ @@ -970,6 +1049,16 @@ void check_object_ownership( if (!pg_directory_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DIRECTORY, NameListToString(objname)); break; + case OBJECT_PUBLICATION: + if (!pg_publication_ownercheck(address.objectId, roleid)) { + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PUBLICATION, NameListToString(objname)); + } + break; + case OBJECT_SUBSCRIPTION: + if (!pg_subscription_ownercheck(address.objectId, roleid)) { + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_SUBSCRIPTION, NameListToString(objname)); + } + break; default: ereport( ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized object type: %d", (int)objtype))); diff --git a/src/common/backend/catalog/pg_publication.cpp b/src/common/backend/catalog/pg_publication.cpp new file mode 100644 index 000000000..d3a85932d --- /dev/null +++ b/src/common/backend/catalog/pg_publication.cpp @@ -0,0 +1,426 @@ +/* ------------------------------------------------------------------------- + * + * pg_publication.c + * publication C API manipulation + * + * Copyright (c) 2016, PostgreSQL Global Development Group + * + * IDENTIFICATION + * pg_publication.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "funcapi.h" +#include "miscadmin.h" + +#include "access/genam.h" +#include "access/hash.h" +#include "access/heapam.h" +#include "access/htup.h" +#include "access/xact.h" +#include "access/tableam.h" + +#include "catalog/catalog.h" +#include "catalog/dependency.h" +#include "catalog/index.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/objectaccess.h" +#include "catalog/objectaddress.h" +#include "catalog/pg_type.h" +#include "catalog/pg_publication_rel.h" +#include "catalog/pg_publication.h" + +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +/* + * 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("\"%s\" is not a table", RelationGetRelationName(targetrel)), + errdetail("Only tables can be added to publications."))); + + /* Can't be system table */ + if (IsCatalogRelation(targetrel)) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" is a system table", RelationGetRelationName(targetrel)), + errdetail("System tables cannot be added to publications."))); + + /* UNLOGGED and TEMP relations cannot be part of publication. */ + if (!RelationIsPermanent(targetrel)) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("table \"%s\" cannot be replicated", RelationGetRelationName(targetrel)), + errdetail("Temporary and unlogged relations cannot be replicated."))); +} + +/* + * Get publication using oid + * + * The Publication struct and its data are palloced here. + */ +static Publication *GetPublication(Oid pubid) +{ + HeapTuple tup; + Publication *pub; + Form_pg_publication pubform; + + tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for publication %u", pubid); + + pubform = (Form_pg_publication)GETSTRUCT(tup); + + pub = (Publication *)palloc(sizeof(Publication)); + pub->oid = pubid; + pub->name = pstrdup(NameStr(pubform->pubname)); + pub->alltables = pubform->puballtables; + pub->pubactions.pubinsert = pubform->pubinsert; + pub->pubactions.pubupdate = pubform->pubupdate; + pub->pubactions.pubdelete = pubform->pubdelete; + + ReleaseSysCache(tup); + + return pub; +} + +/* + * 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 && + /* + * Also exclude any tables created as part of initdb. This mainly + * affects the preinstalled information_schema. + * Note that IsCatalogClass() only checks for these inside pg_catalog + * and toast schemas. + */ + relid >= FirstNormalObjectId; +} + +/* + * Insert new publication / relation mapping. + */ +ObjectAddress publication_add_relation(Oid pubid, Relation targetrel, bool if_not_exists) +{ + Relation rel; + HeapTuple tup; + Datum values[Natts_pg_publication_rel]; + bool nulls[Natts_pg_publication_rel]; + Oid relid = RelationGetRelid(targetrel); + Oid prrelid; + Publication *pub = GetPublication(pubid); + ObjectAddress myself, referenced; + int rc; + + myself.classId = InvalidOid; + myself.objectId = InvalidOid; + myself.objectSubId = 0; + rel = heap_open(PublicationRelRelationId, RowExclusiveLock); + + /* + * Check for duplicates. Note that this does not really prevent + * duplicates, it's here just to provide nicer error message in common + * case. The real protection is the unique key on the catalog. + */ + if (SearchSysCacheExists2(PUBLICATIONRELMAP, ObjectIdGetDatum(relid), ObjectIdGetDatum(pubid))) { + heap_close(rel, RowExclusiveLock); + + if (if_not_exists) + return myself; + + ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg( + "relation \"%s\" is already member of publication \"%s\"", RelationGetRelationName(targetrel), pub->name))); + } + + check_publication_add_relation(targetrel); + + /* Form a tuple. */ + rc = memset_s(values, sizeof(values), 0, sizeof(values)); + securec_check(rc, "", ""); + rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls)); + securec_check(rc, "", ""); + + values[Anum_pg_publication_rel_prpubid - 1] = ObjectIdGetDatum(pubid); + values[Anum_pg_publication_rel_prrelid - 1] = ObjectIdGetDatum(relid); + + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + + /* Insert tuple into catalog. */ + prrelid = simple_heap_insert(rel, tup); + CatalogUpdateIndexes(rel, tup); + heap_freetuple(tup); + + myself.classId = PublicationRelRelationId; + myself.objectId = prrelid; + myself.objectSubId = 0; + + /* Add dependency on the publication */ + referenced.classId = PublicationRelationId; + referenced.objectId = pubid; + referenced.objectSubId = 0; + + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + + /* Add dependency on the relation */ + referenced.classId = RelationRelationId; + referenced.objectId = relid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + + /* Close the table. */ + heap_close(rel, RowExclusiveLock); + + /* Invalidate relcache so that publication info is rebuilt. */ + CacheInvalidateRelcache(targetrel); + + return myself; +} + + +/* + * Gets list of publication oids for a relation oid. + */ +List *GetRelationPublications(Oid relid) +{ + List *result = NIL; + CatCList *pubrellist; + int i; + + /* Find all publications associated with the relation. */ + pubrellist = SearchSysCacheList1(PUBLICATIONRELMAP, ObjectIdGetDatum(relid)); + for (i = 0; i < pubrellist->n_members; i++) { + HeapTuple tup = &pubrellist->members[i]->tuple; + Oid pubid = ((Form_pg_publication_rel)GETSTRUCT(tup))->prpubid; + + result = lappend_oid(result, pubid); + } + + ReleaseSysCacheList(pubrellist); + + return result; +} + +/* + * Gets list of relation oids for a publication. + * + * This should only be used for normal publications, the FOR ALL TABLES + * should use GetAllTablesPublicationRelations(). + */ +List *GetPublicationRelations(Oid pubid) +{ + List *result; + Relation pubrelsrel; + ScanKeyData scankey; + SysScanDesc scan; + HeapTuple tup; + + /* Find all publications associated with the relation. */ + pubrelsrel = heap_open(PublicationRelRelationId, AccessShareLock); + + ScanKeyInit(&scankey, Anum_pg_publication_rel_prpubid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(pubid)); + + scan = systable_beginscan(pubrelsrel, PublicationRelMapIndexId, true, NULL, 1, &scankey); + + result = NIL; + while (HeapTupleIsValid(tup = systable_getnext(scan))) { + Form_pg_publication_rel pubrel; + + pubrel = (Form_pg_publication_rel)GETSTRUCT(tup); + + result = lappend_oid(result, pubrel->prrelid); + } + + systable_endscan(scan); + heap_close(pubrelsrel, AccessShareLock); + + return result; +} + +/* + * Gets list of publication oids for publications marked as FOR ALL TABLES. + */ +List *GetAllTablesPublications(void) +{ + List *result; + Relation rel; + ScanKeyData scankey; + SysScanDesc scan; + HeapTuple tup; + + /* Find all publications that are marked as for all tables. */ + rel = heap_open(PublicationRelationId, AccessShareLock); + + ScanKeyInit(&scankey, Anum_pg_publication_puballtables, BTEqualStrategyNumber, F_BOOLEQ, BoolGetDatum(true)); + + scan = systable_beginscan(rel, InvalidOid, false, NULL, 1, &scankey); + + result = NIL; + while (HeapTupleIsValid(tup = systable_getnext(scan))) + result = lappend_oid(result, HeapTupleGetOid(tup)); + + systable_endscan(scan); + heap_close(rel, AccessShareLock); + + return result; +} + +/* + * Gets list of all relation published by FOR ALL TABLES publication(s). + */ +static List *GetAllTablesPublicationRelations(void) +{ + Relation classRel; + ScanKeyData key[1]; + TableScanDesc scan; + HeapTuple tuple; + List *result = NIL; + + classRel = heap_open(RelationRelationId, AccessShareLock); + + ScanKeyInit(&key[0], Anum_pg_class_relkind, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(RELKIND_RELATION)); + + scan = tableam_scan_begin(classRel, SnapshotNow, 1, key); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { + Oid relid = HeapTupleGetOid(tuple); + Form_pg_class relForm = (Form_pg_class)GETSTRUCT(tuple); + if (is_publishable_class(relid, relForm)) + result = lappend_oid(result, relid); + } + + heap_endscan(scan); + heap_close(classRel, AccessShareLock); + + return result; +} + +/* + * Get Publication using name. + */ +Publication *GetPublicationByName(const char *pubname, bool missing_ok) +{ + Oid oid; + + oid = get_publication_oid(pubname, missing_ok); + + return OidIsValid(oid) ? GetPublication(oid) : NULL; +} + +/* + * get_publication_oid - given a publication name, look up the OID + * + * If missing_ok is false, throw an error if name not found. If true, just + * return InvalidOid. + */ +Oid get_publication_oid(const char *pubname, bool missing_ok) +{ + Oid oid; + + oid = GetSysCacheOid1(PUBLICATIONNAME, CStringGetDatum(pubname)); + if (!OidIsValid(oid) && !missing_ok) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("publication \"%s\" does not exist", pubname))); + return oid; +} + +/* + * get_publication_name - given a publication Oid, look up the name + * + * If missing_ok is false, throw an error if name not found. If true, just + * return NULL. + */ +char *get_publication_name(Oid pubid, bool missing_ok) +{ + HeapTuple tup; + char *pubname; + Form_pg_publication pubform; + + tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid)); + if (!HeapTupleIsValid(tup)) { + if (!missing_ok) + elog(ERROR, "cache lookup failed for publication %u", pubid); + return NULL; + } + + pubform = (Form_pg_publication)GETSTRUCT(tup); + pubname = pstrdup(NameStr(pubform->pubname)); + + ReleaseSysCache(tup); + + return pubname; +} + +/* + * Returns Oids of tables in a publication. + */ +Datum pg_get_publication_tables(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + char *pubname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Publication *publication; + List *tables; + ListCell **lcp; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* switch to memory context appropriate for multiple function calls */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + publication = GetPublicationByName(pubname, false); + if (publication->alltables) + tables = GetAllTablesPublicationRelations(); + else + tables = GetPublicationRelations(publication->oid); + lcp = (ListCell **)palloc(sizeof(ListCell *)); + *lcp = list_head(tables); + funcctx->user_fctx = (void *)lcp; + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + lcp = (ListCell **)funcctx->user_fctx; + + while (*lcp != NULL) { + Oid relid = lfirst_oid(*lcp); + + *lcp = lnext(*lcp); + SRF_RETURN_NEXT(funcctx, ObjectIdGetDatum(relid)); + } + + SRF_RETURN_DONE(funcctx); +} + +/* + * Another variant of this, taking a Relation. + */ +bool is_publishable_relation(Relation rel) +{ + return is_publishable_class(RelationGetRelid(rel), rel->rd_rel); +} diff --git a/src/common/backend/catalog/pg_shdepend.cpp b/src/common/backend/catalog/pg_shdepend.cpp index c39463bb4..44829cf9d 100644 --- a/src/common/backend/catalog/pg_shdepend.cpp +++ b/src/common/backend/catalog/pg_shdepend.cpp @@ -36,7 +36,9 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_proc.h" +#include "catalog/pg_publication.h" #include "catalog/pg_shdepend.h" +#include "catalog/pg_subscription.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_ts_dict.h" #include "catalog/pg_type.h" @@ -46,7 +48,9 @@ #include "commands/defrem.h" #include "commands/extension.h" #include "commands/proclang.h" +#include "commands/publicationcmds.h" #include "commands/schemacmds.h" +#include "commands/subscriptioncmds.h" #include "commands/sec_rls_cmds.h" #include "commands/tablecmds.h" #include "commands/typecmds.h" @@ -1351,6 +1355,14 @@ void shdepReassignOwned(List* roleids, Oid newrole) AlterTSDictionaryOwner_oid(sdepForm->objid, newrole); break; + case PublicationRelationId: + AlterPublicationOwner_oid(sdepForm->objid, newrole); + break; + + case SubscriptionRelationId: + AlterSubscriptionOwner_oid(sdepForm->objid, newrole); + break; + default: ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unexpected classid %u", sdepForm->classid))); diff --git a/src/common/backend/catalog/pg_subscription.cpp b/src/common/backend/catalog/pg_subscription.cpp new file mode 100644 index 000000000..03e5f92cb --- /dev/null +++ b/src/common/backend/catalog/pg_subscription.cpp @@ -0,0 +1,196 @@ +/* ------------------------------------------------------------------------- + * + * pg_subscription.c + * replication subscriptions + * + * Copyright (c) 2016, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/catalog/pg_subscription.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "miscadmin.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup.h" + +#include "catalog/pg_type.h" +#include "catalog/pg_subscription.h" + +#include "nodes/makefuncs.h" + +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/syscache.h" + + +static List *textarray_to_stringlist(ArrayType *textarray); + +/* + * Fetch the subscription from the syscache. + */ +Subscription *GetSubscription(Oid subid, bool missing_ok) +{ + HeapTuple tup; + Subscription *sub; + Form_pg_subscription subform; + Datum datum; + bool isnull; + + tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid)); + if (!HeapTupleIsValid(tup)) { + if (missing_ok) { + return NULL; + } + elog(ERROR, "cache lookup failed for subscription %u", subid); + } + + subform = (Form_pg_subscription)GETSTRUCT(tup); + + sub = (Subscription *)palloc(sizeof(Subscription)); + sub->oid = subid; + sub->dbid = subform->subdbid; + sub->name = pstrdup(NameStr(subform->subname)); + sub->owner = subform->subowner; + sub->enabled = subform->subenabled; + + /* Get conninfo */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subconninfo, &isnull); + Assert(!isnull); + sub->conninfo = TextDatumGetCString(datum); + + /* Get slotname */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subslotname, &isnull); + if (!isnull) { + sub->slotname = pstrdup(NameStr(*DatumGetName(datum))); + } else { + sub->slotname = NULL; + } + + /* Get synccommit */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subsynccommit, &isnull); + Assert(!isnull); + sub->synccommit = TextDatumGetCString(datum); + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subpublications, &isnull); + Assert(!isnull); + sub->publications = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + ReleaseSysCache(tup); + + return sub; +} + +/* + * Return number of subscriptions defined in given database. + * Used by dropdb() to check if database can indeed be dropped. + */ +int CountDBSubscriptions(Oid dbid) +{ + int nsubs = 0; + Relation rel; + ScanKeyData scankey; + SysScanDesc scan; + HeapTuple tup; + + rel = heap_open(SubscriptionRelationId, RowExclusiveLock); + + ScanKeyInit(&scankey, Anum_pg_subscription_subdbid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(dbid)); + + scan = systable_beginscan(rel, InvalidOid, false, NULL, 1, &scankey); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + nsubs++; + + systable_endscan(scan); + + heap_close(rel, NoLock); + + return nsubs; +} + +/* + * Free memory allocated by subscription struct. + */ +void FreeSubscription(Subscription *sub) +{ + pfree(sub->name); + pfree(sub->conninfo); + if (sub->slotname) { + pfree(sub->slotname); + } + list_free_deep(sub->publications); + pfree(sub); +} + +/* + * get_subscription_oid - given a subscription name, look up the OID + * + * If missing_ok is false, throw an error if name not found. If true, just + * return InvalidOid. + */ +Oid get_subscription_oid(const char *subname, bool missing_ok) +{ + Oid oid; + + oid = GetSysCacheOid2(SUBSCRIPTIONNAME, u_sess->proc_cxt.MyDatabaseId, CStringGetDatum(subname)); + if (!OidIsValid(oid) && !missing_ok) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("subscription \"%s\" does not exist", subname))); + return oid; +} + +/* + * get_subscription_name - given a subscription OID, look up the name + * + * If missing_ok is false, throw an error if name not found. If true, just + * return NULL. + */ +char *get_subscription_name(Oid subid, bool missing_ok) +{ + HeapTuple tup; + char *subname; + Form_pg_subscription subform; + + tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid)); + if (!HeapTupleIsValid(tup)) { + if (!missing_ok) + elog(ERROR, "cache lookup failed for subscription %u", subid); + return NULL; + } + + subform = (Form_pg_subscription)GETSTRUCT(tup); + subname = pstrdup(NameStr(subform->subname)); + + ReleaseSysCache(tup); + + return subname; +} + +/* + * Convert text array to list of strings. + * + * Note: the resulting list of strings is pallocated here. + */ +static List *textarray_to_stringlist(ArrayType *textarray) +{ + Datum *elems; + int nelems, i; + List *res = NIL; + + deconstruct_array(textarray, TEXTOID, -1, false, 'i', &elems, NULL, &nelems); + + if (nelems == 0) + return NIL; + + for (i = 0; i < nelems; i++) + res = lappend(res, makeString(TextDatumGetCString(elems[i]))); + + return res; +} diff --git a/src/common/backend/catalog/system_views.sql b/src/common/backend/catalog/system_views.sql index 43907ffc3..846127232 100644 --- a/src/common/backend/catalog/system_views.sql +++ b/src/common/backend/catalog/system_views.sql @@ -3460,3 +3460,32 @@ create index statement_history_time_idx on pg_catalog.statement_history USING bt CREATE OR REPLACE VIEW PG_CATALOG.SYS_DUMMY AS (SELECT 'X'::TEXT AS DUMMY); GRANT SELECT ON TABLE SYS_DUMMY TO PUBLIC; + +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.oid IN (SELECT relid FROM pg_get_publication_tables(P.pubname)); + +CREATE VIEW pg_stat_subscription AS + SELECT + su.oid AS subid, + su.subname, + st.pid, + st.received_lsn, + st.last_msg_send_time, + st.last_msg_receipt_time, + st.latest_end_lsn, + st.latest_end_time + FROM pg_subscription su + LEFT JOIN pg_stat_get_subscription(NULL) st + ON (st.subid = su.oid); + +CREATE VIEW pg_replication_origin_status AS + SELECT * + FROM pg_show_replication_origin_status(); + +REVOKE ALL ON pg_replication_origin_status FROM public; \ No newline at end of file diff --git a/src/common/backend/nodes/copyfuncs.cpp b/src/common/backend/nodes/copyfuncs.cpp index 44bce0b47..465addc54 100644 --- a/src/common/backend/nodes/copyfuncs.cpp +++ b/src/common/backend/nodes/copyfuncs.cpp @@ -5995,6 +5995,64 @@ static CreateModelStmt* _copyCreateModelStmt(const CreateModelStmt* from){ return newnode; } +static CreatePublicationStmt *_copyCreatePublicationStmt(const CreatePublicationStmt *from) +{ + CreatePublicationStmt *newnode = makeNode(CreatePublicationStmt); + + COPY_STRING_FIELD(pubname); + COPY_NODE_FIELD(options); + COPY_NODE_FIELD(tables); + COPY_SCALAR_FIELD(for_all_tables); + + return newnode; +} + +static AlterPublicationStmt *_copyAlterPublicationStmt(const AlterPublicationStmt *from) +{ + AlterPublicationStmt *newnode = makeNode(AlterPublicationStmt); + + COPY_STRING_FIELD(pubname); + COPY_NODE_FIELD(options); + COPY_NODE_FIELD(tables); + COPY_SCALAR_FIELD(for_all_tables); + COPY_SCALAR_FIELD(tableAction); + + return newnode; +} + +static CreateSubscriptionStmt *_copyCreateSubscriptionStmt(const CreateSubscriptionStmt *from) +{ + CreateSubscriptionStmt *newnode = makeNode(CreateSubscriptionStmt); + + COPY_STRING_FIELD(subname); + COPY_STRING_FIELD(conninfo); + COPY_NODE_FIELD(publication); + COPY_NODE_FIELD(options); + + return newnode; +} + +static AlterSubscriptionStmt *_copyAlterSubscriptionStmt(const AlterSubscriptionStmt *from) +{ + AlterSubscriptionStmt *newnode = makeNode(AlterSubscriptionStmt); + + COPY_STRING_FIELD(subname); + COPY_NODE_FIELD(options); + + return newnode; +} + +static DropSubscriptionStmt *_copyDropSubscriptionStmt(const DropSubscriptionStmt *from) +{ + DropSubscriptionStmt *newnode = makeNode(DropSubscriptionStmt); + + COPY_STRING_FIELD(subname); + COPY_SCALAR_FIELD(missing_ok); + COPY_SCALAR_FIELD(behavior); + + return newnode; +} + /* **************************************************************** * pg_list.h copy functions * **************************************************************** @@ -7576,6 +7634,22 @@ void* copyObject(const void* from) retval = _copyShutdownStmt((ShutdownStmt*)from); break; + case T_CreatePublicationStmt: + retval = _copyCreatePublicationStmt((CreatePublicationStmt *)from); + break; + case T_AlterPublicationStmt: + retval = _copyAlterPublicationStmt((AlterPublicationStmt *)from); + break; + case T_CreateSubscriptionStmt: + retval = _copyCreateSubscriptionStmt((CreateSubscriptionStmt *)from); + break; + case T_AlterSubscriptionStmt: + retval = _copyAlterSubscriptionStmt((AlterSubscriptionStmt *)from); + break; + case T_DropSubscriptionStmt: + retval = _copyDropSubscriptionStmt((DropSubscriptionStmt *)from); + break; + default: ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized node type: %d", (int)nodeTag(from)))); diff --git a/src/common/backend/nodes/equalfuncs.cpp b/src/common/backend/nodes/equalfuncs.cpp index 8496b8881..c8d278433 100644 --- a/src/common/backend/nodes/equalfuncs.cpp +++ b/src/common/backend/nodes/equalfuncs.cpp @@ -2776,6 +2776,54 @@ static bool _equalRownum(Rownum* a, Rownum* b) return true; } +static bool _equalCreatePublicationStmt(const CreatePublicationStmt *a, const CreatePublicationStmt *b) +{ + COMPARE_STRING_FIELD(pubname); + COMPARE_NODE_FIELD(options); + COMPARE_NODE_FIELD(tables); + COMPARE_SCALAR_FIELD(for_all_tables); + + return true; +} + +static bool _equalAlterPublicationStmt(const AlterPublicationStmt *a, const AlterPublicationStmt *b) +{ + COMPARE_STRING_FIELD(pubname); + COMPARE_NODE_FIELD(options); + COMPARE_NODE_FIELD(tables); + COMPARE_SCALAR_FIELD(for_all_tables); + COMPARE_SCALAR_FIELD(tableAction); + + return true; +} + +static bool _equalCreateSubscriptionStmt(const CreateSubscriptionStmt *a, const CreateSubscriptionStmt *b) +{ + COMPARE_STRING_FIELD(subname); + COMPARE_STRING_FIELD(conninfo); + COMPARE_NODE_FIELD(publication); + COMPARE_NODE_FIELD(options); + + return true; +} + +static bool _equalAlterSubscriptionStmt(const AlterSubscriptionStmt *a, const AlterSubscriptionStmt *b) +{ + COMPARE_STRING_FIELD(subname); + COMPARE_NODE_FIELD(options); + + return true; +} + +static bool _equalDropSubscriptionStmt(const DropSubscriptionStmt *a, const DropSubscriptionStmt *b) +{ + COMPARE_STRING_FIELD(subname); + COMPARE_SCALAR_FIELD(missing_ok); + COMPARE_SCALAR_FIELD(behavior); + + return true; +} + /* * Stuff from pg_list.h */ @@ -3868,6 +3916,21 @@ bool equal(const void* a, const void* b) case T_Rownum: retval = _equalRownum((Rownum*)a, (Rownum*)b); break; + case T_CreatePublicationStmt: + retval = _equalCreatePublicationStmt((CreatePublicationStmt *)a, (CreatePublicationStmt *)b); + break; + case T_AlterPublicationStmt: + retval = _equalAlterPublicationStmt((AlterPublicationStmt *)a, (AlterPublicationStmt *)b); + break; + case T_CreateSubscriptionStmt: + retval = _equalCreateSubscriptionStmt((CreateSubscriptionStmt *)a, (CreateSubscriptionStmt *)b); + break; + case T_AlterSubscriptionStmt: + retval = _equalAlterSubscriptionStmt((AlterSubscriptionStmt *)a, (AlterSubscriptionStmt *)b); + break; + case T_DropSubscriptionStmt: + retval = _equalDropSubscriptionStmt((DropSubscriptionStmt *)a, (DropSubscriptionStmt *)b); + break; default: ereport(ERROR, diff --git a/src/common/backend/parser/analyze.cpp b/src/common/backend/parser/analyze.cpp index 9349ce9d4..9c2bd69e0 100644 --- a/src/common/backend/parser/analyze.cpp +++ b/src/common/backend/parser/analyze.cpp @@ -3414,7 +3414,7 @@ static List* transformUpdateTargetList(ParseState* pstate, List* qryTlist, List* ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("UPDATE target count mismatch --- internal error"))); } - setExtraUpdatedCols(pstate); + setExtraUpdatedCols(pstate->p_target_rangetblentry, pstate->p_target_relation->rd_att); return tlist; } diff --git a/src/common/backend/parser/gram.y b/src/common/backend/parser/gram.y index 92e32b378..046e9bc38 100644 --- a/src/common/backend/parser/gram.y +++ b/src/common/backend/parser/gram.y @@ -335,6 +335,8 @@ static Node *make_node_from_scanbuf(int start_pos, int end_pos, core_yyscan_t yy CreateAppWorkloadGroupMappingStmt AlterAppWorkloadGroupMappingStmt DropAppWorkloadGroupMappingStmt MergeStmt PurgeStmt CreateMatViewStmt RefreshMatViewStmt CreateWeakPasswordDictionaryStmt DropWeakPasswordDictionaryStmt + CreatePublicationStmt AlterPublicationStmt + CreateSubscriptionStmt AlterSubscriptionStmt DropSubscriptionStmt /* */ /* SNAPSHOTS */ @@ -458,12 +460,14 @@ static Node *make_node_from_scanbuf(int start_pos, int end_pos, core_yyscan_t yy opt_enum_val_list enum_val_list table_func_column_list create_generic_options alter_generic_options relation_expr_list dostmt_opt_list - merge_values_clause + merge_values_clause publication_name_list invoker_rights %type group_by_list %type group_by_item empty_grouping_set rollup_clause cube_clause %type grouping_sets_clause +%type opt_publication_for_tables publication_for_tables +%type publication_name_item %type opt_fdw_options fdw_options %type fdw_option @@ -802,7 +806,7 @@ static Node *make_node_from_scanbuf(int start_pos, int end_pos, core_yyscan_t yy /* PGXC_BEGIN */ PREFERRED PREFIX PRESERVE PREPARE PREPARED PRIMARY /* PGXC_END */ - PRIVATE PRIOR PRIVILEGES PRIVILEGE PROCEDURAL PROCEDURE PROFILE PUBLISH PURGE + PRIVATE PRIOR PRIVILEGES PRIVILEGE PROCEDURAL PROCEDURE PROFILE PUBLICATION PUBLISH PURGE QUERY QUOTE @@ -814,7 +818,7 @@ static Node *make_node_from_scanbuf(int start_pos, int end_pos, core_yyscan_t yy SAMPLE SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHIPPABLE SHOW SHUTDOWN SIMILAR SIMPLE SIZE SLICE SMALLDATETIME SMALLDATETIME_FORMAT_P SMALLINT SNAPSHOT SOME SOURCE_P SPACE SPILL SPLIT STABLE STANDALONE_P START - STATEMENT STATEMENT_ID STATISTICS STDIN STDOUT STORAGE STORE_P STORED STRATIFY STREAM STRICT_P STRIP_P SUBSTRING + STATEMENT STATEMENT_ID STATISTICS STDIN STDOUT STORAGE STORE_P STORED STRATIFY STREAM STRICT_P STRIP_P SUBSCRIPTION SUBSTRING SYMMETRIC SYNONYM SYSDATE SYSID SYSTEM_P SYS_REFCURSOR TABLE TABLES TABLESAMPLE TABLESPACE TARGET TEMP TEMPLATE TEMPORARY TEXT_P THAN THEN TIME TIME_FORMAT_P TIMECAPSULE TIMESTAMP TIMESTAMP_FORMAT_P TIMESTAMPDIFF TINYINT @@ -984,6 +988,7 @@ stmt : | AlterForeignTableStmt | AlterFunctionStmt | AlterProcedureStmt + | AlterPublicationStmt | AlterGroupStmt | AlterNodeGroupStmt | AlterNodeStmt @@ -993,6 +998,7 @@ stmt : | AlterResourcePoolStmt | AlterSeqStmt | AlterSchemaStmt + | AlterSubscriptionStmt | AlterTableStmt | AlterSystemStmt | AlterCompositeTypeStmt @@ -1044,6 +1050,7 @@ stmt : | CreateRlsPolicyStmt | CreatePLangStmt | CreateProcedureStmt + | CreatePublicationStmt | CreateKeyStmt | CreatePolicyLabelStmt | CreateWeakPasswordDictionaryStmt @@ -1060,6 +1067,7 @@ stmt : | CreateSchemaStmt | CreateSeqStmt | CreateStmt + | CreateSubscriptionStmt | CreateSynonymStmt | CreateTableSpaceStmt | CreateTrigStmt @@ -1093,6 +1101,7 @@ stmt : | DropResourcePoolStmt | DropRuleStmt | DropStmt + | DropSubscriptionStmt | DropSynonymStmt | DropTableSpaceStmt | DropTrigStmt @@ -9507,6 +9516,7 @@ drop_type: TABLE { $$ = OBJECT_TABLE; } | TEXT_P SEARCH CONFIGURATION { $$ = OBJECT_TSCONFIGURATION; } | CLIENT MASTER KEY { $$ = OBJECT_GLOBAL_SETTING; } | COLUMN ENCRYPTION KEY { $$ = OBJECT_COLUMN_SETTING; } + | PUBLICATION { $$ = OBJECT_PUBLICATION; } ; any_name_list: @@ -12376,6 +12386,24 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name n->missing_ok = false; $$ = (Node *)n; } + | ALTER PUBLICATION name RENAME TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_PUBLICATION; + n->object = list_make1(makeString($3)); + n->newname = $6; + n->missing_ok = false; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name RENAME TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_SUBSCRIPTION; + n->object = list_make1(makeString($3)); + n->newname = $6; + n->missing_ok = false; + $$ = (Node *)n; + } /* Rename Row Level Security Policy */ | ALTER RowLevelSecurityPolicyName ON qualified_name RENAME TO name { @@ -13250,6 +13278,209 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId n->newowner = $6; $$ = (Node *)n; } + | ALTER PUBLICATION name OWNER TO RoleId + { + AlterOwnerStmt *n = makeNode(AlterOwnerStmt); + n->objectType = OBJECT_PUBLICATION; + n->object = list_make1(makeString($3)); + n->newowner = $6; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name OWNER TO RoleId + { + AlterOwnerStmt *n = makeNode(AlterOwnerStmt); + n->objectType = OBJECT_SUBSCRIPTION; + n->object = list_make1(makeString($3)); + n->newowner = $6; + $$ = (Node *)n; + } + ; + +/***************************************************************************** + * + * CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ] + * + *****************************************************************************/ + +CreatePublicationStmt: + CREATE PUBLICATION name opt_publication_for_tables opt_definition + { + CreatePublicationStmt *n = makeNode(CreatePublicationStmt); + n->pubname = $3; + n->options = $5; + if ($4 != NULL) + { + /* FOR TABLE */ + if (IsA($4, List)) + n->tables = (List *)$4; + /* FOR ALL TABLES */ + else + n->for_all_tables = TRUE; + } + $$ = (Node *)n; + } + ; + +opt_publication_for_tables: + publication_for_tables { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + +publication_for_tables: + FOR TABLE relation_expr_list + { + $$ = (Node *) $3; + } + | FOR ALL TABLES + { + $$ = (Node *) makeInteger(TRUE); + } + ; + +/***************************************************************************** + * + * ALTER PUBLICATION name SET ( options ) + * + * ALTER PUBLICATION name ADD TABLE table [, table2] + * + * ALTER PUBLICATION name DROP TABLE table [, table2] + * + * ALTER PUBLICATION name SET TABLE table [, table2] + * + *****************************************************************************/ + +AlterPublicationStmt: + ALTER PUBLICATION name SET definition + { + AlterPublicationStmt *n = makeNode(AlterPublicationStmt); + n->pubname = $3; + n->options = $5; + $$ = (Node *)n; + } + | ALTER PUBLICATION name ADD_P TABLE relation_expr_list + { + AlterPublicationStmt *n = makeNode(AlterPublicationStmt); + n->pubname = $3; + n->tables = $6; + n->tableAction = DEFELEM_ADD; + $$ = (Node *)n; + } + | ALTER PUBLICATION name SET TABLE relation_expr_list + { + AlterPublicationStmt *n = makeNode(AlterPublicationStmt); + n->pubname = $3; + n->tables = $6; + n->tableAction = DEFELEM_SET; + $$ = (Node *)n; + } + | ALTER PUBLICATION name DROP TABLE relation_expr_list + { + AlterPublicationStmt *n = makeNode(AlterPublicationStmt); + n->pubname = $3; + n->tables = $6; + n->tableAction = DEFELEM_DROP; + $$ = (Node *)n; + } + ; + +/***************************************************************************** + * + * CREATE SUBSCRIPTION name ... + * + *****************************************************************************/ + +CreateSubscriptionStmt: + CREATE SUBSCRIPTION name CONNECTION Sconst PUBLICATION publication_name_list opt_definition + { + CreateSubscriptionStmt *n = + makeNode(CreateSubscriptionStmt); + n->subname = $3; + n->conninfo = $5; + n->publication = $7; + n->options = $8; + $$ = (Node *)n; + } + ; + +publication_name_list: + publication_name_item + { + $$ = list_make1($1); + } + | publication_name_list ',' publication_name_item + { + $$ = lappend($1, $3); + } + ; + +publication_name_item: + ColLabel { $$ = makeString($1); }; + +/***************************************************************************** + * + * ALTER SUBSCRIPTION name ... + * + *****************************************************************************/ + +AlterSubscriptionStmt: + ALTER SUBSCRIPTION name SET definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->subname = $3; + n->options = $5; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name CONNECTION Sconst + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->subname = $3; + n->options = list_make1(makeDefElem("conninfo", + (Node *)makeString($5))); + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name SET PUBLICATION publication_name_list + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->subname = $3; + n->options = list_make1(makeDefElem("publication", + (Node *)$6)); + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name ENABLE_P + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->subname = $3; + n->options = list_make1(makeDefElem("enabled", + (Node *)makeInteger(TRUE))); + $$ = (Node *)n; + } ; + +/***************************************************************************** + * + * DROP SUBSCRIPTION [ IF EXISTS ] name + * + *****************************************************************************/ + +DropSubscriptionStmt: DROP SUBSCRIPTION name opt_drop_behavior + { + DropSubscriptionStmt *n = makeNode(DropSubscriptionStmt); + n->subname = $3; + n->behavior = $4; + n->missing_ok = false; + $$ = (Node *) n; + } + | DROP SUBSCRIPTION IF_P EXISTS name opt_drop_behavior + { + DropSubscriptionStmt *n = makeNode(DropSubscriptionStmt); + n->subname = $5; + n->missing_ok = true; + n->behavior = $6; + $$ = (Node *) n; + } ; TypeOwner: RoleId { $$ = $1; } @@ -21632,6 +21863,7 @@ unreserved_keyword: | PRIVILEGES | PROCEDURAL | PROFILE + | PUBLICATION | PUBLISH | PURGE | QUERY @@ -21717,6 +21949,7 @@ unreserved_keyword: | STREAM | STRICT_P | STRIP_P + | SUBSCRIPTION | SYNONYM | SYS_REFCURSOR { $$ = "refcursor"; } | SYSID diff --git a/src/common/backend/parser/parse_merge.cpp b/src/common/backend/parser/parse_merge.cpp index ebd8c2b90..657ab7b31 100644 --- a/src/common/backend/parser/parse_merge.cpp +++ b/src/common/backend/parser/parse_merge.cpp @@ -640,11 +640,8 @@ void fix_merge_stmt_for_insert_update(ParseState* pstate, MergeStmt* stmt) fill_join_expr(pstate, stmt); } -void setExtraUpdatedCols(ParseState* pstate) +void setExtraUpdatedCols(RangeTblEntry* target_rte, TupleDesc tupdesc) { - TupleDesc tupdesc = pstate->p_target_relation->rd_att; - RangeTblEntry* target_rte = pstate->p_target_rangetblentry; - /* * Record in extraUpdatedCols generated columns referencing updated base * columns. @@ -748,7 +745,7 @@ static List* transformUpdateTargetList(ParseState* pstate, List* origTlist) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("UPDATE target count mismatch --- internal error"))); } - setExtraUpdatedCols(pstate); + setExtraUpdatedCols(pstate->p_target_rangetblentry, pstate->p_target_relation->rd_att); return tlist; } diff --git a/src/common/backend/utils/cache/inval.cpp b/src/common/backend/utils/cache/inval.cpp index b20f999f5..8844dfdc6 100644 --- a/src/common/backend/utils/cache/inval.cpp +++ b/src/common/backend/utils/cache/inval.cpp @@ -1412,3 +1412,43 @@ void CallSyscacheCallbacks(int cacheid, uint32 hashvalue) } } } + +/* + * PrepareInvalidationState + * Initialize inval lists for the current (sub)transaction. + */ +static void PrepareInvalidationState(void) +{ + TransInvalidationInfo *myInfo; + + if (u_sess->inval_cxt.transInvalInfo != NULL && + u_sess->inval_cxt.transInvalInfo->my_level == GetCurrentTransactionNestLevel()) + return; + + myInfo = + (TransInvalidationInfo *)MemoryContextAllocZero(u_sess->top_transaction_mem_cxt, sizeof(TransInvalidationInfo)); + myInfo->parent = u_sess->inval_cxt.transInvalInfo; + myInfo->my_level = GetCurrentTransactionNestLevel(); + + /* + * If there's any previous entry, this one should be for a deeper nesting + * level. + */ + Assert(u_sess->inval_cxt.transInvalInfo == NULL || myInfo->my_level > u_sess->inval_cxt.transInvalInfo->my_level); + + u_sess->inval_cxt.transInvalInfo = myInfo; +} + +/* + * CacheInvalidateRelcacheAll + * Register invalidation of the whole relcache at the end of command. + * + * This is used by alter publication as changes in publications may affect + * large number of tables. + */ +void CacheInvalidateRelcacheAll(void) +{ + PrepareInvalidationState(); + + RegisterRelcacheInvalidation(InvalidOid, InvalidOid); +} diff --git a/src/common/backend/utils/cache/relcache.cpp b/src/common/backend/utils/cache/relcache.cpp index 6676246f4..3fd8ef257 100644 --- a/src/common/backend/utils/cache/relcache.cpp +++ b/src/common/backend/utils/cache/relcache.cpp @@ -87,8 +87,11 @@ #include "catalog/pg_pltemplate.h" #include "catalog/pg_proc.h" #include "catalog/gs_package.h" +#include "catalog/pg_publication.h" +#include "catalog/pg_publication_rel.h" #include "catalog/pg_range.h" #include "catalog/pg_recyclebin.h" +#include "catalog/pg_replication_origin.h" #include "catalog/pg_resource_pool.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_rlspolicy.h" @@ -98,6 +101,7 @@ #include "catalog/pg_shseclabel.h" #include "catalog/pg_statistic.h" #include "catalog/pg_statistic_ext.h" +#include "catalog/pg_subscription.h" #include "catalog/pg_synonym.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_trigger.h" @@ -311,6 +315,13 @@ static const FormData_pg_attribute Desc_gs_opt_model[Natts_gs_opt_model] = {Sche static const FormData_pg_attribute Desc_gs_model_warehouse[Natts_gs_model_warehouse] = {Schema_gs_model_warehouse}; static const FormData_pg_attribute Desc_gs_package[Natts_gs_package] = {Schema_gs_package}; +static const FormData_pg_attribute Desc_pg_subscription[Natts_pg_subscription] = {Schema_pg_subscription}; +static const FormData_pg_attribute Desc_pg_publication[Natts_pg_publication] = {Schema_pg_publication}; +static const FormData_pg_attribute Desc_pg_publication_rel[Natts_pg_publication_rel] = {Schema_pg_publication_rel}; +static const FormData_pg_attribute Desc_pg_replication_origin[Natts_pg_replication_origin] = { + Schema_pg_replication_origin +}; + /* Please add to the array in ascending order of oid value */ static struct CatalogRelationBuildParam catalogBuildParam[CATALOG_NUM] = {{DefaultAclRelationId, "pg_default_acl", @@ -1120,6 +1131,42 @@ static struct CatalogRelationBuildParam catalogBuildParam[CATALOG_NUM] = {{Defau Desc_gs_opt_model, false, true}, + {SubscriptionRelationId, + "pg_subscription", + SubscriptionRelation_Rowtype_Id, + true, + true, + Natts_pg_subscription, + Desc_pg_subscription, + false, + true}, + {PublicationRelationId, + "pg_publication", + PublicationRelation_Rowtype_Id, + false, + true, + Natts_pg_publication, + Desc_pg_publication, + false, + true}, + {PublicationRelRelationId, + "pg_publication_rel", + PublicationRelRelationId_Rowtype_Id, + false, + true, + Natts_pg_publication_rel, + Desc_pg_publication_rel, + false, + true}, + {ReplicationOriginRelationId, + "pg_replication_origin", + ReplicationOriginRelationId_Rowtype_Id, + true, + false, + Natts_pg_replication_origin, + Desc_pg_replication_origin, + false, + true}, {PackageRelationId, "gs_package", PackageRelation_Rowtype_Id, @@ -3453,7 +3500,11 @@ static void RelationDestroyRelation(Relation relation, bool remember_tupdesc) list_free_ext(relation->rd_indexlist); bms_free_ext(relation->rd_indexattr); bms_free_ext(relation->rd_keyattr); + bms_free_ext(relation->rd_pkattr); bms_free_ext(relation->rd_idattr); + if (relation->rd_options) { + pfree_ext(relation->rd_pubactions); + } FreeTriggerDesc(relation->trigdesc); if (relation->rd_rlsdesc) { MemoryContextDelete(relation->rd_rlsdesc->rlsCxt); @@ -4166,6 +4217,7 @@ void AtEOXact_RelationCache(bool isCommit) list_free_ext(relation->rd_indexlist); relation->rd_indexlist = NIL; relation->rd_oidindex = InvalidOid; + relation->rd_pkindex = InvalidOid; relation->rd_indexvalid = 0; } } @@ -4243,6 +4295,7 @@ void AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid, SubTrans list_free_ext(relation->rd_indexlist); relation->rd_indexlist = NIL; relation->rd_oidindex = InvalidOid; + relation->rd_pkindex = InvalidOid; relation->rd_indexvalid = 0; } } @@ -5656,6 +5709,7 @@ List* RelationGetIndexList(Relation relation, bool inc_unused) /* Now save a copy of the completed list in the relcache entry. */ if (!inc_unused) { SaveCopyList(relation, result, oidIndex); + relation->rd_pkindex = pkeyIndex; } return result; @@ -5857,6 +5911,11 @@ void RelationSetIndexList(Relation relation, List* indexIds, Oid oidIndex) list_free_ext(relation->rd_indexlist); relation->rd_indexlist = indexIds; relation->rd_oidindex = oidIndex; + /* + * For the moment, assume the target rel hasn't got a pk or replica + * index. We'll load them on demand in the API that wraps access to them. + */ + relation->rd_pkindex = InvalidOid; relation->rd_indexvalid = 2; /* mark list as forced */ /* must flag that we have a forced index list */ u_sess->relcache_cxt.need_eoxact_work = true; @@ -5888,6 +5947,25 @@ Oid RelationGetOidIndex(Relation relation) return relation->rd_oidindex; } +/* + * RelationGetPrimaryKeyIndex -- get OID of the relation's primary key index + * + * Returns InvalidOid if there is no such index. + */ +Oid RelationGetPrimaryKeyIndex(Relation relation) +{ + List* ilist; + + if (relation->rd_indexvalid == 0) { + /* RelationGetIndexList does the heavy lifting. */ + ilist = RelationGetIndexList(relation); + list_free(ilist); + Assert(relation->rd_indexvalid != 0); + } + + return relation->rd_pkindex; +} + /* * RelationGetReplicaIndex -- get OID of the relation's replica identity index * If the table is partition table, return the replica identity index of parent table. @@ -6173,7 +6251,9 @@ Bitmapset* RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind att Bitmapset* uindexattrs = NULL; List* indexoidlist = NULL; ListCell* l = NULL; + Bitmapset *pkindexattrs; /* columns in the primary index */ Bitmapset* idindexattrs = NULL; /* columns in the the replica identity */ + Oid relpkindex; Oid relreplindex; MemoryContext oldcxt; @@ -6185,6 +6265,8 @@ Bitmapset* RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind att return bms_copy(relation->rd_indexattr); case INDEX_ATTR_BITMAP_KEY: return bms_copy(relation->rd_keyattr); + case INDEX_ATTR_BITMAP_PRIMARY_KEY: + return bms_copy(relation->rd_pkattr); case INDEX_ATTR_BITMAP_IDENTITY_KEY: return bms_copy(relation->rd_idattr); default: @@ -6206,12 +6288,14 @@ Bitmapset* RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind att return NULL; /* - * Copy the rd_replidindex value computed by RelationGetIndexList before - * proceeding. This is needed because a relcache flush could occur inside - * index_open below, resetting the fields managed by RelationGetIndexList. - * (The values we're computing will still be valid, assuming that caller - * has a sufficient lock on the relation.) - */ + * Copy the rd_pkindex and rd_replidindex value computed by + * RelationGetIndexList before proceeding. This is needed because a + * relcache flush could occur inside index_open below, resetting the + * fields managed by RelationGetIndexList. (The values we're computing + * will still be valid, assuming that caller has a sufficient lock on + * the relation.) + */ + relpkindex = relation->rd_pkindex; relreplindex = relation->rd_replidindex; /* @@ -6226,6 +6310,7 @@ Bitmapset* RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind att */ indexattrs = NULL; uindexattrs = NULL; + pkindexattrs = NULL; idindexattrs = NULL; foreach (l, indexoidlist) { Oid indexOid = lfirst_oid(l); @@ -6233,6 +6318,7 @@ Bitmapset* RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind att IndexInfo* indexInfo = NULL; int i; bool isKey = false; /* candidate key */ + bool isPK = false; /* primary key */ bool isIDKey = false; /* replica identity index */ indexDesc = index_open(indexOid, AccessShareLock); @@ -6242,6 +6328,8 @@ Bitmapset* RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind att /* Can this index be referenced by a foreign key? */ isKey = indexInfo->ii_Unique && indexInfo->ii_Expressions == NIL && indexInfo->ii_Predicate == NIL; + /* Is this a primary key? */ + isPK = (indexOid == relpkindex); /* Is this index the configured (or default) replica identity? */ isIDKey = (indexOid == relreplindex); @@ -6265,6 +6353,10 @@ Bitmapset* RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind att } if (isIDKey && i < indexInfo->ii_NumIndexKeyAttrs) idindexattrs = bms_add_member(idindexattrs, attrnum - FirstLowInvalidHeapAttributeNumber); + + if (isPK) { + pkindexattrs = bms_add_member(pkindexattrs, attrnum - FirstLowInvalidHeapAttributeNumber); + } } } @@ -6279,6 +6371,14 @@ Bitmapset* RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind att list_free_ext(indexoidlist); + /* Don't leak the old values of these bitmaps, if any */ + bms_free(relation->rd_indexattr); + relation->rd_indexattr = NULL; + bms_free(relation->rd_pkattr); + relation->rd_pkattr = NULL; + bms_free(relation->rd_idattr); + relation->rd_idattr = NULL; + /* * Now save copies of the bitmaps in the relcache entry. We intentionally * set rd_indexattr last, because that's the one that signals validity of @@ -6288,6 +6388,7 @@ Bitmapset* RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind att */ oldcxt = MemoryContextSwitchTo(u_sess->cache_mem_cxt); relation->rd_keyattr = bms_copy(uindexattrs); + relation->rd_pkattr = bms_copy(pkindexattrs); relation->rd_idattr = bms_copy(idindexattrs); relation->rd_indexattr = bms_copy(indexattrs); (void)MemoryContextSwitchTo(oldcxt); @@ -6298,6 +6399,8 @@ Bitmapset* RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind att return indexattrs; case INDEX_ATTR_BITMAP_KEY: return uindexattrs; + case INDEX_ATTR_BITMAP_PRIMARY_KEY: + return bms_copy(relation->rd_pkattr); case INDEX_ATTR_BITMAP_IDENTITY_KEY: return idindexattrs; default: @@ -6482,6 +6585,70 @@ void RelationGetExclusionInfo(Relation indexRelation, Oid** operators, Oid** pro (void)MemoryContextSwitchTo(oldcxt); } +/* + * Get publication actions for the given relation. + */ +struct PublicationActions* GetRelationPublicationActions(Relation relation) +{ + List* puboids; + ListCell* lc; + MemoryContext oldcxt; + int rc; + PublicationActions* pubactions = (PublicationActions*)palloc0(sizeof(PublicationActions)); + + if (relation->rd_pubactions) { + errno_t rcs = memcpy_s(pubactions, sizeof(PublicationActions), + relation->rd_pubactions, sizeof(PublicationActions)); + securec_check(rcs, "\0", "\0"); + return pubactions; + } + + /* Fetch the publication membership info. */ + puboids = GetRelationPublications(RelationGetRelid(relation)); + puboids = list_concat_unique_oid(puboids, GetAllTablesPublications()); + + foreach(lc, puboids) + { + Oid pubid = lfirst_oid(lc); + HeapTuple tup; + Form_pg_publication pubform; + + tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid)); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for publication %u", pubid); + + pubform = (Form_pg_publication) GETSTRUCT(tup); + + pubactions->pubinsert |= pubform->pubinsert; + pubactions->pubupdate |= pubform->pubupdate; + pubactions->pubdelete |= pubform->pubdelete; + + ReleaseSysCache(tup); + + /* + * If we know everything is replicated, there is no point to check + * for other publications. + */ + if (pubactions->pubinsert && pubactions->pubupdate && pubactions->pubdelete) + break; + } + + if (relation->rd_pubactions) { + pfree(relation->rd_pubactions); + relation->rd_pubactions = NULL; + } + + /* Now save copy of the actions in the relcache entry. */ + oldcxt = MemoryContextSwitchTo(u_sess->cache_mem_cxt); + relation->rd_pubactions = (PublicationActions*)palloc(sizeof(PublicationActions)); + rc = memcpy_s(relation->rd_pubactions, sizeof(PublicationActions), pubactions, sizeof(PublicationActions)); + securec_check(rc, "", ""); + MemoryContextSwitchTo(oldcxt); + + return pubactions; +} + /* * load_relcache_init_file, write_relcache_init_file * @@ -6854,9 +7021,12 @@ static bool load_relcache_init_file(bool shared) rel->rd_indexvalid = 0; rel->rd_indexlist = NIL; rel->rd_oidindex = InvalidOid; + rel->rd_pkindex = InvalidOid; rel->rd_indexattr = NULL; rel->rd_keyattr = NULL; + rel->rd_pkattr = NULL; rel->rd_idattr = NULL; + rel->rd_pubactions = NULL; rel->rd_createSubid = InvalidSubTransactionId; rel->rd_newRelfilenodeSubid = InvalidSubTransactionId; rel->rd_amcache = NULL; @@ -7811,4 +7981,30 @@ static void SetupPageCompressForRelation(Relation relation, PageCompressOpts* co compress_options->compressDiffConvert, preallocChunks, symbol, compressLevel, algorithm, chunkSize); } -} \ No newline at end of file +} + +char RelationGetRelReplident(Relation r) +{ + bool isNull = false; + char relreplident; + + HeapTuple tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(RelationGetRelid(r))); + if (!HeapTupleIsValid(tuple)) { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("cache lookup failed for relation %u", RelationGetRelid(r)))); + } + + Relation classRel = heap_open(RelationRelationId, AccessShareLock); + Datum datum = heap_getattr(tuple, Anum_pg_class_relreplident, RelationGetDescr(classRel), &isNull); + if (isNull) { + relreplident = REPLICA_IDENTITY_NOTHING; + } else { + relreplident = CharGetDatum(datum); + } + + heap_close(classRel, AccessShareLock); + ReleaseSysCache(tuple); + + return relreplident; +} diff --git a/src/common/backend/utils/cache/syscache.cpp b/src/common/backend/utils/cache/syscache.cpp index 4a453c923..ecc51b426 100644 --- a/src/common/backend/utils/cache/syscache.cpp +++ b/src/common/backend/utils/cache/syscache.cpp @@ -99,6 +99,9 @@ #include "utils/rel_gs.h" #include "utils/syscache.h" #include "catalog/pg_user_status.h" +#include "catalog/pg_publication.h" +#include "catalog/pg_publication_rel.h" +#include "catalog/pg_replication_origin.h" /* --------------------------------------------------------------------------- @@ -754,7 +757,73 @@ static const struct cachedesc cacheinfo[] = {{AggregateRelationId, /* AGGFNOID * PackageNameIndexId, 2, {Anum_gs_package_pkgname, Anum_gs_package_pkgnamespace, 0, 0}, - 2048} + 2048}, + {PublicationRelationId, /* PUBLICATIONNAME */ + PublicationNameIndexId, + 1, + { + Anum_pg_publication_pubname, + 0, + 0, + 0 + }, + 8 + }, + {PublicationRelationId, /* PUBLICATIONOID */ + PublicationObjectIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 8 + }, + {PublicationRelRelationId, /* PUBLICATIONREL */ + PublicationRelObjectIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 64 + }, + {PublicationRelRelationId, /* PUBLICATIONRELMAP */ + PublicationRelMapIndexId, + 2, + { + Anum_pg_publication_rel_prrelid, + Anum_pg_publication_rel_prpubid, + 0, + 0 + }, + 64 + }, + {SubscriptionRelationId, /* SUBSCRIPTIONNAME */ + SubscriptionNameIndexId, + 2, + { + Anum_pg_subscription_subdbid, + Anum_pg_subscription_subname, + 0, + 0 + }, + 4 + }, + {SubscriptionRelationId, /* SUBSCRIPTIONOID */ + SubscriptionObjectIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 4 + } }; int SysCacheSize = lengthof(cacheinfo); @@ -808,8 +877,13 @@ void InitCatalogCachePhase2(void) Assert(u_sess->syscache_cxt.CacheInitialized); - for (cacheId = 0; cacheId < SysCacheSize; cacheId++) + for (cacheId = 0; cacheId < SysCacheSize; cacheId++) { + if (t_thrd.proc->workingVersionNum < PUBLICATION_VERSION_NUM && + (cacheId == SUBSCRIPTIONNAME || cacheId == SUBSCRIPTIONOID)) { + continue; + } InitCatCachePhase2(u_sess->syscache_cxt.SysCache[cacheId], true); + } } /* diff --git a/src/common/backend/utils/init/globals.cpp b/src/common/backend/utils/init/globals.cpp index 15ecb65bb..22d9454b9 100644 --- a/src/common/backend/utils/init/globals.cpp +++ b/src/common/backend/utils/init/globals.cpp @@ -59,7 +59,7 @@ bool open_join_children = true; bool will_shutdown = false; /* hard-wired binary version number */ -const uint32 GRAND_VERSION_NUM = 92424; +const uint32 GRAND_VERSION_NUM = 92425; const uint32 HINT_ENHANCEMENT_VERSION_NUM = 92359; const uint32 MATVIEW_VERSION_NUM = 92213; @@ -96,6 +96,7 @@ const uint32 V5R2C00_BACKEND_VERSION_NUM = 92412; const uint32 ANALYZER_HOOK_VERSION_NUM = 92420; const uint32 SUPPORT_HASH_XLOG_VERSION_NUM = 92420; +const uint32 PUBLICATION_VERSION_NUM = 92425; const uint32 ENHANCED_TUPLE_LOCK_VERSION_NUM = 92423; diff --git a/src/common/backend/utils/init/miscinit.cpp b/src/common/backend/utils/init/miscinit.cpp index 9d3fedb23..3bd22a09e 100644 --- a/src/common/backend/utils/init/miscinit.cpp +++ b/src/common/backend/utils/init/miscinit.cpp @@ -70,6 +70,8 @@ #define DIRECTORY_LOCK_FILE "postmaster.pid" +#define INIT_SESSION_MAX_INT32_BUFF 20 + Alarm alarmItemTooManyDbUserConn[1] = {ALM_AI_Unknown, ALM_AS_Normal, 0, 0, 0, 0, {0}, {0}, NULL}; /* ---------------------------------------------------------------- @@ -789,7 +791,7 @@ bool has_rolvcadmin(Oid roleid) /* * Initialize user identity during normal backend startup */ -void InitializeSessionUserId(const char* rolename) +void InitializeSessionUserId(const char* rolename, Oid useroid) { HeapTuple roleTup; Form_pg_authid rform; @@ -814,12 +816,17 @@ void InitializeSessionUserId(const char* rolename) AssertState(false); ereport(FATAL, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Abnormal process. UserOid has been reseted. Current userOid[%u], reset username is %s.", - u_sess->misc_cxt.AuthenticatedUserId, rolename))); + errmsg("Abnormal process. UserOid has been reseted. Current userOid[%u], reset username is %s," + "useroid is %u", + u_sess->misc_cxt.AuthenticatedUserId, rolename, useroid))); } } - roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename)); + if (rolename != NULL) { + roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename)); + } else { + roleTup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(useroid)); + } #ifndef ENABLE_MULTIPLE_NODES /* @@ -841,6 +848,12 @@ void InitializeSessionUserId(const char* rolename) #endif if (!HeapTupleIsValid(roleTup)) { + char roleIdStr[INIT_SESSION_MAX_INT32_BUFF] = {0}; + if (rolename == NULL) { + int rc = sprintf_s(roleIdStr, INIT_SESSION_MAX_INT32_BUFF, "%u", useroid); + securec_check_ss(rc, "", ""); + rolename = roleIdStr; + } /* * Audit user login * it's unsafe to deal with plugins hooks as dynamic lib may be released @@ -862,6 +875,9 @@ void InitializeSessionUserId(const char* rolename) rform = (Form_pg_authid)GETSTRUCT(roleTup); roleid = HeapTupleGetOid(roleTup); + if (rolename == NULL) { + rolename = NameStr(rform->rolname); + } u_sess->misc_cxt.AuthenticatedUserId = roleid; u_sess->misc_cxt.AuthenticatedUserIsSuperuser = rform->rolsuper; diff --git a/src/common/backend/utils/init/postinit.cpp b/src/common/backend/utils/init/postinit.cpp index 189898fdd..f8046716e 100644 --- a/src/common/backend/utils/init/postinit.cpp +++ b/src/common/backend/utils/init/postinit.cpp @@ -1130,11 +1130,12 @@ PostgresInitializer::~PostgresInitializer() m_username = NULL; } -void PostgresInitializer::SetDatabaseAndUser(const char* in_dbname, Oid dboid, const char* username) +void PostgresInitializer::SetDatabaseAndUser(const char* in_dbname, Oid dboid, const char* username, Oid useroid) { m_indbname = in_dbname; m_dboid = dboid; m_username = username; + m_useroid = useroid; } void PostgresInitializer::InitFencedSysCache() @@ -1435,6 +1436,66 @@ void PostgresInitializer::InitCsnminSync() return; } +void PostgresInitializer::InitApplyLauncher() +{ + InitThread(); + + InitSysCache(); + + /* Initialize stats collection --- must happen before first xact */ + pgstat_initialize(); + + SetProcessExitCallback(); + + StartXact(); + + SetSuperUserAndDatabase(); + + CheckConnPermission(); + + SetDatabase(); + + LoadSysCache(); + + InitDatabase(); + + InitSettings(); + + FinishInit(); + + return; +} + +void PostgresInitializer::InitApplyWorker() +{ + InitThread(); + + InitSysCache(); + + /* Initialize stats collection --- must happen before first xact */ + pgstat_initialize(); + + SetProcessExitCallback(); + + StartXact(); + + InitUser(); + + CheckConnPermission(); + + SetDatabase(); + + LoadSysCache(); + + InitDatabase(); + + InitSettings(); + + FinishInit(); + + return; +} + void PostgresInitializer::InitUndoLauncher() { InitThread(); @@ -2072,7 +2133,7 @@ void PostgresInitializer::SetSuperUserAndDatabase() void PostgresInitializer::InitUser() { - InitializeSessionUserId(m_username); + InitializeSessionUserId(m_username, m_useroid); m_isSuperUser = superuser(); u_sess->misc_cxt.CurrentUserName = u_sess->proc_cxt.MyProcPort->user_name; #ifndef ENABLE_MULTIPLE_NODES diff --git a/src/common/backend/utils/misc/guc/guc_storage.cpp b/src/common/backend/utils/misc/guc/guc_storage.cpp index 50940a686..35b388d3a 100755 --- a/src/common/backend/utils/misc/guc/guc_storage.cpp +++ b/src/common/backend/utils/misc/guc/guc_storage.cpp @@ -2080,6 +2080,20 @@ static void InitStorageConfigureNamesInt() NULL, NULL}, + {{"max_logical_replication_workers", + PGC_POSTMASTER, + NODE_SINGLENODE, + REPLICATION, + gettext_noop("Maximum number of logical replication worker processes."), + NULL}, + &g_instance.attr.attr_storage.max_logical_replication_workers, + 4, + 0, + MAX_BACKENDS, + NULL, + NULL, + NULL}, + {{"recovery_time_target", PGC_SIGHUP, NODE_ALL, diff --git a/src/common/backend/utils/misc/postgresql_single.conf.sample b/src/common/backend/utils/misc/postgresql_single.conf.sample index f83f965e9..21f51958d 100644 --- a/src/common/backend/utils/misc/postgresql_single.conf.sample +++ b/src/common/backend/utils/misc/postgresql_single.conf.sample @@ -321,6 +321,7 @@ hot_standby = on # "on" allows queries during recovery #wal_receiver_buffer_size = 64MB # wal receiver buffer size #enable_xlog_prune = on # xlog keep for all standbys even through they are not connecting and donnot created replslot. #max_size_for_xlog_prune = 2147483647 # xlog keep for the wal size less than max_xlog_size when the enable_xlog_prune is on +#max_logical_replication_workers = 4 # Maximum number of logical replication worker processes. #------------------------------------------------------------------------------ # QUERY TUNING diff --git a/src/common/interfaces/libpq/frontend_parser/gram.y b/src/common/interfaces/libpq/frontend_parser/gram.y index 3fc76cb36..118dbc1a7 100755 --- a/src/common/interfaces/libpq/frontend_parser/gram.y +++ b/src/common/interfaces/libpq/frontend_parser/gram.y @@ -563,7 +563,7 @@ extern THR_LOCAL bool stmt_contains_operator_plus; /* PGXC_BEGIN */ PREFERRED PREFIX PRESERVE PREPARE PREPARED PRIMARY /* PGXC_END */ - PRIVATE PRIOR PRIVILEGES PRIVILEGE PROCEDURAL PROCEDURE PROFILE PUBLISH PURGE + PRIVATE PRIOR PRIVILEGES PRIVILEGE PROCEDURAL PROCEDURE PROFILE PUBLICATION PUBLISH PURGE QUERY QUOTE @@ -575,7 +575,7 @@ extern THR_LOCAL bool stmt_contains_operator_plus; SAMPLE SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHIPPABLE SHOW SHUTDOWN SIMILAR SIMPLE SIZE SLICE SMALLDATETIME SMALLDATETIME_FORMAT_P SMALLINT SNAPSHOT SOME SOURCE_P SPACE SPILL SPLIT STABLE STANDALONE_P START - STATEMENT STATEMENT_ID STATISTICS STDIN STDOUT STORAGE STORE_P STORED STRATIFY STREAM STRICT_P STRIP_P SUBSTRING + STATEMENT STATEMENT_ID STATISTICS STDIN STDOUT STORAGE STORE_P STORED STRATIFY STREAM STRICT_P STRIP_P SUBSCRIPTION SUBSTRING SYMMETRIC SYNONYM SYSDATE SYSID SYSTEM_P SYS_REFCURSOR TABLE TABLES TABLESAMPLE TABLESPACE TARGET TEMP TEMPLATE TEMPORARY TEXT_P THAN THEN TIME TIME_FORMAT_P TIMECAPSULE TIMESTAMP TIMESTAMP_FORMAT_P TIMESTAMPDIFF TINYINT @@ -11251,6 +11251,7 @@ unreserved_keyword: | PRIVILEGES | PROCEDURAL | PROFILE + | PUBLICATION | QUERY | QUOTE | RANDOMIZED @@ -11327,6 +11328,7 @@ unreserved_keyword: | STORED | STRICT_P | STRIP_P + | SUBSCRIPTION | SYNONYM | SYS_REFCURSOR { $$ = "refcursor"; } | SYSID diff --git a/src/common/port/cipher.cpp b/src/common/port/cipher.cpp index ac56d86e1..fd04012b4 100644 --- a/src/common/port/cipher.cpp +++ b/src/common/port/cipher.cpp @@ -465,7 +465,7 @@ static bool WriteContentToFile(const char* filename, const void* content, size_t static bool isModeExists(KeyMode mode) { if (mode != SERVER_MODE && mode != CLIENT_MODE && mode != USER_MAPPING_MODE && - mode != OBS_MODE && mode != SOURCE_MODE && mode != GDS_MODE) { + mode != OBS_MODE && mode != SOURCE_MODE && mode != GDS_MODE && mode != SUBSCRIPTION_MODE) { #ifndef ENABLE_LLT (void)fprintf(stderr, _("AK/SK encrypt/decrypt encounters invalid key mode.\n")); return false; @@ -829,6 +829,11 @@ void decode_cipher_files( securec_check_ss_c(ret, "\0", "\0"); ret = snprintf_s(randfile, MAXPGPATH, MAXPGPATH - 1, "%s/usermapping%s", datadir, RAN_KEY_FILE); securec_check_ss_c(ret, "\0", "\0"); + } else if (mode == SUBSCRIPTION_MODE) { + ret = snprintf_s(cipherkeyfile, MAXPGPATH, MAXPGPATH - 1, "%s/subscription%s", datadir, CIPHER_KEY_FILE); + securec_check_ss_c(ret, "\0", "\0"); + ret = snprintf_s(randfile, MAXPGPATH, MAXPGPATH - 1, "%s/subscription%s", datadir, RAN_KEY_FILE); + securec_check_ss_c(ret, "\0", "\0"); } /* * in client_mode,check with the user name is appointed.if so, read the files begin diff --git a/src/gausskernel/cbb/extension/connector/connector.cpp b/src/gausskernel/cbb/extension/connector/connector.cpp index 6de892d3b..78f848589 100644 --- a/src/gausskernel/cbb/extension/connector/connector.cpp +++ b/src/gausskernel/cbb/extension/connector/connector.cpp @@ -252,7 +252,7 @@ static void read_one_key_value(const char* key, const char* value) return; /* Decrypt username */ - decryptECString(value, plainuid, EC_CIPHER_TEXT_LENGTH); + decryptECString(value, plainuid, EC_CIPHER_TEXT_LENGTH, SOURCE_MODE); GetKeyValueByString(NAME_USERNAME, t_thrd.conn_cxt.value_username, plainuid, len); /* Clear buffer */ @@ -267,7 +267,7 @@ static void read_one_key_value(const char* key, const char* value) return; /* Decrypt password */ - decryptECString(value, plainpwd, EC_CIPHER_TEXT_LENGTH); + decryptECString(value, plainpwd, EC_CIPHER_TEXT_LENGTH, SOURCE_MODE); GetKeyValueByString(NAME_PASSWORD, t_thrd.conn_cxt.value_password, plainpwd, len); /* Clear buffer */ diff --git a/src/gausskernel/cbb/extension/foreign/foreign.cpp b/src/gausskernel/cbb/extension/foreign/foreign.cpp index b672a6f72..2fa31d313 100644 --- a/src/gausskernel/cbb/extension/foreign/foreign.cpp +++ b/src/gausskernel/cbb/extension/foreign/foreign.cpp @@ -344,48 +344,6 @@ bool isSpecifiedSrvTypeFromSrvName(const char* srvName, const char* SepcifiedTyp return isSpecifiedSrvType; } -static void DecryptOptions(List *options) -{ - if (options == NULL) { - return; - } - - ListCell *cell = NULL; - foreach(cell, options) { - DefElem* def = (DefElem*)lfirst(cell); - if (def->defname == NULL || def->arg == NULL || !IsA(def->arg, String)) { - continue; - } - - char *str = strVal(def->arg); - if (str == NULL || strlen(str) == 0) { - continue; - } - - for (int i = 0; i < g_sensitiveArrayLength; i++) { - if (pg_strcasecmp(def->defname, g_sensitiveOptionsArray[i]) == 0) { - char plainText[EC_CIPHER_TEXT_LENGTH] = {0}; - - /* - * If decryptECString return false, it means the stored values is not encrypted. - * This happened when user mapping was created in old gaussdb version. - */ - if (decryptECString(str, plainText, EC_CIPHER_TEXT_LENGTH, false)) { - pfree_ext(str); - pfree_ext(def->arg); - def->arg = (Node*)makeString(pstrdup(plainText)); - - /* Clear buffer */ - errno_t errCode = memset_s(plainText, EC_CIPHER_TEXT_LENGTH, 0, EC_CIPHER_TEXT_LENGTH); - securec_check(errCode, "\0", "\0"); - } - - break; - } - } - } -} - /* * GetUserMapping - look up the user mapping. * @@ -423,7 +381,7 @@ UserMapping* GetUserMapping(Oid userid, Oid serverid) um->options = untransformRelOptions(datum); } - DecryptOptions(um->options); + DecryptOptions(um->options, g_sensitiveOptionsArray, g_sensitiveArrayLength, USER_MAPPING_MODE); ReleaseSysCache(tp); diff --git a/src/gausskernel/cbb/utils/aes/cipherfn.cpp b/src/gausskernel/cbb/utils/aes/cipherfn.cpp index 93955fd91..d0b809a05 100644 --- a/src/gausskernel/cbb/utils/aes/cipherfn.cpp +++ b/src/gausskernel/cbb/utils/aes/cipherfn.cpp @@ -796,6 +796,21 @@ bool gs_decrypt_aes_speed( } } +static inline const char* GetCipherPrefix(KeyMode mode) +{ + switch (mode) { + case SOURCE_MODE: + return "datasource"; + case USER_MAPPING_MODE: + return "usermapping"; + case SUBSCRIPTION_MODE: + return "subscription"; + default: + ereport(ERROR, (errmsg("unknown key mode: %d", mode))); + return NULL; + } +} + /* * getECKeyString: * Get Extension Connector(EC) key string @@ -805,12 +820,13 @@ bool gs_decrypt_aes_speed( */ static GS_UCHAR* getECKeyString(KeyMode mode) { - Assert(mode == SOURCE_MODE || mode == USER_MAPPING_MODE); + Assert(mode == SOURCE_MODE || mode == USER_MAPPING_MODE || mode == SUBSCRIPTION_MODE); GS_UCHAR* plainkey = NULL; char* gshome = NULL; char cipherdir[MAXPGPATH] = {0}; char cipherfile[MAXPGPATH] = {0}; - const char *cipherPrefix = ((mode == SOURCE_MODE) ? "datasource" : "usermapping"); + const char *cipherPrefix = GetCipherPrefix(mode); + int ret = 0; /* @@ -865,9 +881,10 @@ static GS_UCHAR* getECKeyString(KeyMode mode) * @OUT dest_cipher_text: dest buffer to be filled with encrypted string, this buffer * should be given by caller * @IN dest_cipher_length: dest buffer length which is given by the caller + * @IN mode: key mode * @RETURN: void */ -void encryptECString(char* src_plain_text, char* dest_cipher_text, uint32 dest_cipher_length, bool isDataSource) +void encryptECString(char* src_plain_text, char* dest_cipher_text, uint32 dest_cipher_length, KeyMode mode) { GS_UINT32 ciphertextlen = 0; GS_UCHAR ciphertext[1024]; @@ -880,7 +897,7 @@ void encryptECString(char* src_plain_text, char* dest_cipher_text, uint32 dest_c } /* First, get encrypt key */ - cipherkey = getECKeyString(isDataSource ? SOURCE_MODE : USER_MAPPING_MODE); + cipherkey = getECKeyString(mode); /* Clear cipher buffer which will be used later */ ret = memset_s(ciphertext, sizeof(ciphertext), 0, sizeof(ciphertext)); @@ -963,10 +980,11 @@ void encryptECString(char* src_plain_text, char* dest_cipher_text, uint32 dest_c * @OUT dest_plain_text: dest buffer to be filled with plain text, this buffer * should be given by caller * @IN dest_plain_length: dest buffer length which is given by the caller + * @IN mode: key mode * @RETURN: bool, true if encrypt success, false if not */ bool decryptECString(const char* src_cipher_text, char* dest_plain_text, - uint32 dest_plain_length, bool isDataSource) + uint32 dest_plain_length, KeyMode mode) { GS_UCHAR* ciphertext = NULL; GS_UINT32 ciphertextlen = 0; @@ -980,7 +998,7 @@ bool decryptECString(const char* src_cipher_text, char* dest_plain_text, } /* Get key string */ - cipherkey = getECKeyString(isDataSource ? SOURCE_MODE : USER_MAPPING_MODE); + cipherkey = getECKeyString(mode); /* Step-1: Decode */ ciphertext = (GS_UCHAR*)(SEC_decodeBase64((char*)(src_cipher_text + strlen(EC_ENCRYPT_PREFIX)), &ciphertextlen)); @@ -1068,7 +1086,7 @@ bool IsECEncryptedString(const char* src_cipher_text) * @IN src_options: source options to be encrypted * @RETURN: void */ -void EncryptGenericOptions(List* options, const char** sensitiveOptionsArray, int arrayLength, bool isDataSource) +void EncryptGenericOptions(List* options, const char** sensitiveOptionsArray, int arrayLength, KeyMode mode) { int i; char* srcString = NULL; @@ -1102,14 +1120,14 @@ void EncryptGenericOptions(List* options, const char** sensitiveOptionsArray, in errmsg("Using probably encrypted option (prefix='encryptOpt') directly and it is not " "recommended."), errhint("The %s object can't be used if the option is not encrypted correctly.", - isDataSource ? "DATA SOURCE" : "USER MAPPING"))); + GetCipherPrefix(mode)))); isPrint = true; } break; } /* Encrypt the src string */ - encryptECString(srcString, encryptString, EC_CIPHER_TEXT_LENGTH, isDataSource); + encryptECString(srcString, encryptString, EC_CIPHER_TEXT_LENGTH, mode); /* Substitute the src */ def->arg = (Node*)makeString(pstrdup(encryptString)); @@ -1130,6 +1148,58 @@ void EncryptGenericOptions(List* options, const char** sensitiveOptionsArray, in } } +/* + * DecryptOptions: + * Decrypt generic options, before transformation + * + * @IN options: source options to be decrypted + * @IN sensitiveOptionsArray: options name to be decrypted + * @IN arrayLength: array length of sensitiveOptionsArray + * @IN mode: key mode + * @RETURN: void + */ +void DecryptOptions(List *options, const char** sensitiveOptionsArray, int arrayLength, KeyMode mode) +{ + if (options == NULL) { + return; + } + + ListCell *cell = NULL; + foreach(cell, options) { + DefElem* def = (DefElem*)lfirst(cell); + if (def->defname == NULL || def->arg == NULL || !IsA(def->arg, String)) { + continue; + } + + char *str = strVal(def->arg); + if (str == NULL || strlen(str) == 0) { + continue; + } + + for (int i = 0; i < arrayLength; i++) { + if (pg_strcasecmp(def->defname, sensitiveOptionsArray[i]) == 0) { + char plainText[EC_CIPHER_TEXT_LENGTH] = {0}; + + /* + * If decryptECString return false, it means the stored values is not encrypted. + * This happened when user mapping was created in old gaussdb version. + */ + if (decryptECString(str, plainText, EC_CIPHER_TEXT_LENGTH, mode)) { + pfree_ext(str); + pfree_ext(def->arg); + def->arg = (Node*)makeString(pstrdup(plainText)); + + /* Clear buffer */ + errno_t errCode = memset_s(plainText, EC_CIPHER_TEXT_LENGTH, 0, EC_CIPHER_TEXT_LENGTH); + securec_check(errCode, "\0", "\0"); + } + + break; + } + } + } +} + char int2hex(uint8 n) { char ret; diff --git a/src/gausskernel/optimizer/commands/Makefile b/src/gausskernel/optimizer/commands/Makefile index 7e7a53416..8b278e205 100644 --- a/src/gausskernel/optimizer/commands/Makefile +++ b/src/gausskernel/optimizer/commands/Makefile @@ -21,8 +21,8 @@ OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ dbcommands.o define.o discard.o dropcmds.o explain.o extension.o \ foreigncmds.o functioncmds.o \ indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \ - portalcmds.o prepare.o proclang.o packagecmds.o\ - schemacmds.o seclabel.o sec_rls_cmds.o sequence.o tablecmds.o tablespace.o trigger.o \ + portalcmds.o prepare.o proclang.o packagecmds.o publicationcmds.o\ + schemacmds.o seclabel.o sec_rls_cmds.o sequence.o subscriptioncmds.o tablecmds.o tablespace.o trigger.o \ tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \ variable.o verify.o view.o gds_stream.o obs_stream.o formatter.o datasourcecmds.o \ directory.o auto_explain.o shutdown.o diff --git a/src/gausskernel/optimizer/commands/alter.cpp b/src/gausskernel/optimizer/commands/alter.cpp index 146cfce4f..3b38927a1 100644 --- a/src/gausskernel/optimizer/commands/alter.cpp +++ b/src/gausskernel/optimizer/commands/alter.cpp @@ -22,6 +22,7 @@ #include "catalog/namespace.h" #include "catalog/pg_largeobject.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_subscription.h" #include "catalog/pg_synonym.h" #include "commands/alter.h" #include "commands/collationcmds.h" @@ -31,7 +32,9 @@ #include "commands/directory.h" #include "commands/extension.h" #include "commands/proclang.h" +#include "commands/publicationcmds.h" #include "commands/schemacmds.h" +#include "commands/subscriptioncmds.h" #include "commands/sec_rls_cmds.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" @@ -107,6 +110,14 @@ void ExecRenameStmt(RenameStmt* stmt) case OBJECT_PARTITION_INDEX: renamePartitionIndex(stmt); break; + + case OBJECT_PUBLICATION: + RenamePublication(stmt->object, stmt->newname); + break; + + case OBJECT_SUBSCRIPTION: + RenameSubscription(stmt->object, stmt->newname); + break; case OBJECT_RLSPOLICY: RenameRlsPolicy(stmt); @@ -581,6 +592,14 @@ void ExecAlterOwnerStmt(AlterOwnerStmt* stmt) AlterSynonymOwner(stmt->object, newowner); break; + case OBJECT_PUBLICATION: + AlterPublicationOwner(strVal(linitial(stmt->object)), newowner); + break; + + case OBJECT_SUBSCRIPTION: + AlterSubscriptionOwner(strVal(linitial(stmt->object)), newowner); + break; + default: ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), diff --git a/src/gausskernel/optimizer/commands/datasourcecmds.cpp b/src/gausskernel/optimizer/commands/datasourcecmds.cpp index 01f07ecd4..eb2f9f7ea 100644 --- a/src/gausskernel/optimizer/commands/datasourcecmds.cpp +++ b/src/gausskernel/optimizer/commands/datasourcecmds.cpp @@ -179,7 +179,7 @@ void CreateDataSource(CreateDataSourceStmt* stmt) check_source_generic_options(stmt->options); /* Encrypt sensitive options before any operations */ - EncryptGenericOptions(stmt->options, SensitiveOptionsArray, lengthof(SensitiveOptionsArray), true); + EncryptGenericOptions(stmt->options, SensitiveOptionsArray, lengthof(SensitiveOptionsArray), SOURCE_MODE); /* Add source options */ srcoptions = transformGenericOptions(DataSourceRelationId, PointerGetDatum(NULL), stmt->options, InvalidOid); @@ -291,7 +291,7 @@ void AlterDataSource(AlterDataSourceStmt* stmt) check_source_generic_options((const List*)stmt->options); /* Encrypt sensitive options before any operations */ - EncryptGenericOptions(stmt->options, SensitiveOptionsArray, lengthof(SensitiveOptionsArray), true); + EncryptGenericOptions(stmt->options, SensitiveOptionsArray, lengthof(SensitiveOptionsArray), SOURCE_MODE); /* Prepare the options array */ datum = transformGenericOptions(DataSourceRelationId, datum, stmt->options, InvalidOid); diff --git a/src/gausskernel/optimizer/commands/dbcommands.cpp b/src/gausskernel/optimizer/commands/dbcommands.cpp index 8133b173c..ffbae3b2d 100644 --- a/src/gausskernel/optimizer/commands/dbcommands.cpp +++ b/src/gausskernel/optimizer/commands/dbcommands.cpp @@ -42,6 +42,7 @@ #include "catalog/pg_job.h" #include "catalog/pg_proc.h" #include "catalog/pg_tablespace.h" +#include "catalog/pg_subscription.h" #include "catalog/pgxc_slice.h" #include "catalog/storage_xlog.h" #include "commands/comment.h" @@ -984,6 +985,7 @@ void dropdb(const char* dbname, bool missing_ok) HeapTuple tup; int notherbackends; int npreparedxacts; + int nsubscriptions; /* If we will return before reaching function end, please release this lock */ LWLockAcquire(DelayDDLLock, LW_SHARED); @@ -1070,7 +1072,20 @@ void dropdb(const char* dbname, bool missing_ok) "view: \"pg_stat_activity\".", dbname), errdetail_busy_db(notherbackends, npreparedxacts))); - /* Search need delete use-defined C fun library.*/ + /* + * Check if there are subscriptions defined in the target database. + * + * We can't drop them automatically because they might be holding + * resources in other databases/instances. + */ + if ((nsubscriptions = CountDBSubscriptions(db_id)) > 0) { + ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), + errmsg("database \"%s\" is being used by logical replication subscription", dbname), + errdetail_plural("There is %d subscription.", "There are %d subscriptions.", nsubscriptions, + nsubscriptions))); + } + + /* Search need delete use-defined C fun library. */ prepareDatabaseCFunLibrary(db_id); /* Relate to remove all job belong the database. */ diff --git a/src/gausskernel/optimizer/commands/define.cpp b/src/gausskernel/optimizer/commands/define.cpp index 897d0b02c..0bf523269 100644 --- a/src/gausskernel/optimizer/commands/define.cpp +++ b/src/gausskernel/optimizer/commands/define.cpp @@ -262,6 +262,33 @@ int defGetTypeLength(DefElem* def) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid argument for %s: \"%s\"", def->defname, defGetString(def)))); return 0; /* keep compiler quiet */ } + +/* + * Extract a list of string values (otherwise uninterpreted) from a DefElem. + */ +List *defGetStringList(DefElem *def) +{ + ListCell *cell; + + if (def->arg == NULL) { + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("%s requires a parameter", def->defname))); + } + if (nodeTag(def->arg) != T_List) { + ereport(ERROR, + (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized node type: %d", (int)nodeTag(def->arg)))); + } + + foreach (cell, (List *)def->arg) { + Node *str = (Node *)lfirst(cell); + + if (!IsA(str, String)) { + ereport(ERROR, (errmsg("unexpected node type in name list: %d", (int)nodeTag(str)))); + } + } + + return (List *)def->arg; +} + #endif /* !FRONTEND_PARSER */ /* @@ -296,3 +323,4 @@ List* defSetOption(List* options, const char* name, Node* value) return options; } + diff --git a/src/gausskernel/optimizer/commands/dropcmds.cpp b/src/gausskernel/optimizer/commands/dropcmds.cpp index 1278a02e0..37ca6aff4 100644 --- a/src/gausskernel/optimizer/commands/dropcmds.cpp +++ b/src/gausskernel/optimizer/commands/dropcmds.cpp @@ -376,6 +376,10 @@ static void does_not_exist_skipping(ObjectType objtype, List* objname, List* obj name = pstrdup(strVal(llast(objname))); args = NameListToString(list_truncate(list_copy(objname), list_length(objname) - 1)); break; + case OBJECT_PUBLICATION: + msg = gettext_noop("publication \"%s\" does not exist, skipping"); + name = NameListToString(objname); + break; default: pfree_ext(message->data); pfree_ext(message); diff --git a/src/gausskernel/optimizer/commands/foreigncmds.cpp b/src/gausskernel/optimizer/commands/foreigncmds.cpp index 66df41df2..7fa2a410a 100644 --- a/src/gausskernel/optimizer/commands/foreigncmds.cpp +++ b/src/gausskernel/optimizer/commands/foreigncmds.cpp @@ -1157,7 +1157,7 @@ void CreateUserMapping(CreateUserMappingStmt* stmt) values[Anum_pg_user_mapping_umuser - 1] = ObjectIdGetDatum(useId); values[Anum_pg_user_mapping_umserver - 1] = ObjectIdGetDatum(srv->serverid); - EncryptGenericOptions(stmt->options, g_sensitiveOptionsArray, g_sensitiveArrayLength, false); + EncryptGenericOptions(stmt->options, g_sensitiveOptionsArray, g_sensitiveArrayLength, USER_MAPPING_MODE); /* Add user options */ useoptions = @@ -1254,7 +1254,7 @@ void AlterUserMapping(AlterUserMappingStmt* stmt) if (isnull) datum = PointerGetDatum(NULL); - EncryptGenericOptions(stmt->options, g_sensitiveOptionsArray, g_sensitiveArrayLength, false); + EncryptGenericOptions(stmt->options, g_sensitiveOptionsArray, g_sensitiveArrayLength, USER_MAPPING_MODE); /* Prepare the options array */ datum = transformGenericOptions(UserMappingRelationId, datum, stmt->options, fdw->fdwvalidator); diff --git a/src/gausskernel/optimizer/commands/publicationcmds.cpp b/src/gausskernel/optimizer/commands/publicationcmds.cpp new file mode 100644 index 000000000..b069a7e3e --- /dev/null +++ b/src/gausskernel/optimizer/commands/publicationcmds.cpp @@ -0,0 +1,677 @@ +/* ------------------------------------------------------------------------- + * + * publicationcmds.c + * publication manipulation + * + * Copyright (c) 2016, PostgreSQL Global Development Group + * + * IDENTIFICATION + * publicationcmds.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "funcapi.h" +#include "miscadmin.h" + +#include "access/genam.h" +#include "access/hash.h" +#include "access/heapam.h" +#include "access/htup.h" +#include "access/xact.h" + +#include "catalog/catalog.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/objectaccess.h" +#include "catalog/objectaddress.h" +#include "catalog/pg_inherits_fn.h" +#include "catalog/pg_type.h" +#include "catalog/pg_publication.h" +#include "catalog/pg_publication_rel.h" +#include "catalog/dependency.h" + +#include "commands/dbcommands.h" +#include "commands/defrem.h" +#include "commands/publicationcmds.h" + +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" +#include "utils/acl.h" + + +/* Same as MAXNUMMESSAGES in sinvaladt.c */ +static const int MAX_RELCACHE_INVAL_MSGS = 4096; + +static List *OpenTableList(List *tables); +static void CloseTableList(List *rels); +static void PublicationAddTables(Oid pubid, List *rels, bool if_not_exists, AlterPublicationStmt *stmt); +static void PublicationDropTables(Oid pubid, List *rels, bool missing_ok); + +static void parse_publication_options(List *options, bool *publish_given, bool *publish_insert, bool *publish_update, + bool *publish_delete) +{ + ListCell *lc; + + *publish_given = false; + + /* Defaults are true */ + *publish_insert = true; + *publish_update = true; + *publish_delete = true; + + /* Parse options */ + foreach (lc, options) { + DefElem *defel = (DefElem *)lfirst(lc); + + if (strcmp(defel->defname, "publish") == 0) { + char *publish; + List *publish_list; + ListCell *lc; + + if (*publish_given) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + + /* + * If publish option was given only the explicitly listed actions + * should be published. + */ + *publish_insert = false; + *publish_update = false; + *publish_delete = false; + + *publish_given = true; + publish = defGetString(defel); + if (!SplitIdentifierString(publish, ',', &publish_list)) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid list syntax for \"publish\" option"))); + + /* Process the option list. */ + foreach (lc, publish_list) { + char *publish_opt = (char *)lfirst(lc); + + if (strcmp(publish_opt, "insert") == 0) + *publish_insert = true; + else if (strcmp(publish_opt, "update") == 0) + *publish_update = true; + else if (strcmp(publish_opt, "delete") == 0) + *publish_delete = true; + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), errmsg("unrecognized \"publish\" value: \"%s\"", publish_opt))); + } + } else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), errmsg("unrecognized publication parameter: %s", defel->defname))); + } +} + +/* + * Create new publication. + */ +ObjectAddress CreatePublication(CreatePublicationStmt *stmt) +{ + if (t_thrd.proc->workingVersionNum < PUBLICATION_VERSION_NUM) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Before GRAND VERSION NUM %u, we do not support publication.", PUBLICATION_VERSION_NUM))); + } + + Relation rel; + ObjectAddress myself; + Oid puboid; + bool nulls[Natts_pg_publication]; + Datum values[Natts_pg_publication]; + HeapTuple tup; + bool publish_given; + bool publish_insert; + bool publish_update; + bool publish_delete; + AclResult aclresult; + int rc; + + /* must have CREATE privilege on database */ + aclresult = pg_database_aclcheck(u_sess->proc_cxt.MyDatabaseId, GetUserId(), ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_DATABASE, get_database_name(u_sess->proc_cxt.MyDatabaseId)); + + /* FOR ALL TABLES requires superuser */ + if (stmt->for_all_tables && !superuser()) + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to create FOR ALL TABLES publication")))); + + rel = heap_open(PublicationRelationId, RowExclusiveLock); + + /* Check if name is used */ + puboid = GetSysCacheOid1(PUBLICATIONNAME, CStringGetDatum(stmt->pubname)); + if (OidIsValid(puboid)) { + ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("publication \"%s\" already exists", stmt->pubname))); + } + + /* Form a tuple. */ + rc = memset_s(values, sizeof(values), 0, sizeof(values)); + securec_check(rc, "", ""); + rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls)); + securec_check(rc, "", ""); + + values[Anum_pg_publication_pubname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->pubname)); + values[Anum_pg_publication_pubowner - 1] = ObjectIdGetDatum(GetUserId()); + + parse_publication_options(stmt->options, &publish_given, &publish_insert, &publish_update, &publish_delete); + + values[Anum_pg_publication_puballtables - 1] = BoolGetDatum(stmt->for_all_tables); + values[Anum_pg_publication_pubinsert - 1] = BoolGetDatum(publish_insert); + values[Anum_pg_publication_pubupdate - 1] = BoolGetDatum(publish_update); + values[Anum_pg_publication_pubdelete - 1] = BoolGetDatum(publish_delete); + + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + + /* Insert tuple into catalog. */ + puboid = simple_heap_insert(rel, tup); + CatalogUpdateIndexes(rel, tup); + heap_freetuple(tup); + + recordDependencyOnOwner(PublicationRelationId, puboid, GetUserId()); + + myself.classId = PublicationRelationId; + myself.objectId = puboid; + myself.objectSubId = 0; + + /* Make the changes visible. */ + CommandCounterIncrement(); + + if (stmt->tables) { + List *rels; + + Assert(list_length(stmt->tables) > 0); + + rels = OpenTableList(stmt->tables); + PublicationAddTables(puboid, rels, true, NULL); + CloseTableList(rels); + } else if (stmt->for_all_tables) { + /* Invalidate relcache so that publication info is rebuilt. */ + CacheInvalidateRelcacheAll(); + } + + heap_close(rel, RowExclusiveLock); + + InvokeObjectAccessHook(OAT_POST_CREATE, PublicationRelationId, puboid, 0, NULL); + + if (g_instance.attr.attr_storage.wal_level != WAL_LEVEL_LOGICAL) { + ereport(WARNING, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("wal_level is insufficient to publish logical changes"), + errhint("Set wal_level to logical before creating subscriptions."))); + } + + return myself; +} + +/* + * Change options of a publication. + */ +static void AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup) +{ + bool nulls[Natts_pg_publication]; + bool replaces[Natts_pg_publication]; + Datum values[Natts_pg_publication]; + bool publish_given; + bool publish_insert; + bool publish_update; + bool publish_delete; + int rc; + + parse_publication_options(stmt->options, &publish_given, &publish_insert, &publish_update, &publish_delete); + + /* Everything ok, form a new tuple. */ + rc = memset_s(values, sizeof(values), 0, sizeof(values)); + securec_check(rc, "", ""); + rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls)); + securec_check(rc, "", ""); + rc = memset_s(replaces, sizeof(replaces), false, sizeof(replaces)); + securec_check(rc, "", ""); + + if (publish_given) { + values[Anum_pg_publication_pubinsert - 1] = BoolGetDatum(publish_insert); + replaces[Anum_pg_publication_pubinsert - 1] = true; + + values[Anum_pg_publication_pubupdate - 1] = BoolGetDatum(publish_update); + replaces[Anum_pg_publication_pubupdate - 1] = true; + + values[Anum_pg_publication_pubdelete - 1] = BoolGetDatum(publish_delete); + replaces[Anum_pg_publication_pubdelete - 1] = true; + } + + tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces); + + /* Update the catalog. */ + simple_heap_update(rel, &tup->t_self, tup); + CatalogUpdateIndexes(rel, tup); + + CommandCounterIncrement(); + + /* Invalidate the relcache. */ + if (((Form_pg_publication)GETSTRUCT(tup))->puballtables) { + CacheInvalidateRelcacheAll(); + } else { + List *relids = GetPublicationRelations(HeapTupleGetOid(tup)); + + /* + * We don't want to send too many individual messages, at some point + * it's cheaper to just reset whole relcache. + */ + if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS) { + ListCell *lc; + + foreach (lc, relids) { + Oid relid = lfirst_oid(lc); + + CacheInvalidateRelcacheByRelid(relid); + } + } else { + CacheInvalidateRelcacheAll(); + } + } +} + +/* + * Add or remove table to/from publication. + */ +static void AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel, HeapTuple tup) +{ + Oid pubid = HeapTupleGetOid(tup); + List *rels = NIL; + Form_pg_publication pubform = (Form_pg_publication)GETSTRUCT(tup); + /* Check that user is allowed to manipulate the publication tables. */ + if (pubform->puballtables) + ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("publication \"%s\" is defined as FOR ALL TABLES", NameStr(pubform->pubname)), + errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications."))); + + Assert(list_length(stmt->tables) > 0); + + rels = OpenTableList(stmt->tables); + + if (stmt->tableAction == DEFELEM_ADD) + PublicationAddTables(pubid, rels, false, stmt); + else if (stmt->tableAction == DEFELEM_DROP) + PublicationDropTables(pubid, rels, false); + else { /* DEFELEM_SET */ + List *oldrelids = GetPublicationRelations(pubid); + List *delrels = NIL; + ListCell *oldlc; + + /* Calculate which relations to drop. */ + foreach (oldlc, oldrelids) { + Oid oldrelid = lfirst_oid(oldlc); + ListCell *newlc; + bool found = false; + + foreach (newlc, rels) { + Relation newrel = (Relation)lfirst(newlc); + if (RelationGetRelid(newrel) == oldrelid) { + found = true; + break; + } + } + + if (!found) { + Relation oldrel = heap_open(oldrelid, ShareUpdateExclusiveLock); + delrels = lappend(delrels, oldrel); + } + } + + /* And drop them. */ + PublicationDropTables(pubid, delrels, true); + + /* + * Don't bother calculating the difference for adding, we'll catch + * and skip existing ones when doing catalog update. + */ + PublicationAddTables(pubid, rels, true, stmt); + + CloseTableList(delrels); + } + + CloseTableList(rels); +} + +/* + * Alter the existing publication. + * + * This is dispatcher function for AlterPublicationOptions and + * AlterPublicationTables. + */ +void AlterPublication(AlterPublicationStmt *stmt) +{ + if (t_thrd.proc->workingVersionNum < PUBLICATION_VERSION_NUM) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Before GRAND VERSION NUM %u, we do not support publication.", PUBLICATION_VERSION_NUM))); + } + + Relation rel; + HeapTuple tup; + + rel = heap_open(PublicationRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy1(PUBLICATIONNAME, CStringGetDatum(stmt->pubname)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("publication \"%s\" does not exist", stmt->pubname))); + + /* must be owner */ + if (!pg_publication_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PUBLICATION, stmt->pubname); + + if (stmt->options) + AlterPublicationOptions(stmt, rel, tup); + else + AlterPublicationTables(stmt, rel, tup); + + /* Cleanup. */ + heap_freetuple(tup); + heap_close(rel, RowExclusiveLock); +} + +/* + * Remove the publication by mapping OID. + */ +void RemovePublicationById(Oid pubid) +{ + Relation rel; + HeapTuple tup; + Form_pg_publication pubform; + + rel = heap_open(PublicationRelationId, RowExclusiveLock); + + tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for publication %u", pubid); + + pubform = (Form_pg_publication)GETSTRUCT(tup); + /* Invalidate relcache so that publication info is rebuilt. */ + if (pubform->puballtables) { + CacheInvalidateRelcacheAll(); + } + + simple_heap_delete(rel, &tup->t_self); + ReleaseSysCache(tup); + heap_close(rel, RowExclusiveLock); +} + +/* + * Remove relation from publication by mapping OID. + */ +void RemovePublicationRelById(Oid proid) +{ + Relation rel; + HeapTuple tup; + Form_pg_publication_rel pubrel; + + rel = heap_open(PublicationRelRelationId, RowExclusiveLock); + tup = SearchSysCache1(PUBLICATIONREL, ObjectIdGetDatum(proid)); + if (!HeapTupleIsValid(tup)) { + elog(ERROR, "cache lookup failed for publication table %u", proid); + } + + pubrel = (Form_pg_publication_rel)GETSTRUCT(tup); + /* Invalidate relcache so that publication info is rebuilt. */ + CacheInvalidateRelcacheByRelid(pubrel->prrelid); + + simple_heap_delete(rel, &tup->t_self); + + ReleaseSysCache(tup); + + heap_close(rel, RowExclusiveLock); +} + +/* + * Open relations specified by a RangeVar list. + * The returned tables are locked in ShareUpdateExclusiveLock mode. + */ +static List *OpenTableList(List *tables) +{ + List *relids = NIL; + List *rels = NIL; + ListCell *lc; + + /* + * Open, share-lock, and check all the explicitly-specified relations + */ + foreach (lc, tables) { + RangeVar *rv = lfirst_node(RangeVar, lc); + Relation rel; + Oid myrelid; + + CHECK_FOR_INTERRUPTS(); + + rel = heap_openrv(rv, ShareUpdateExclusiveLock); + myrelid = RelationGetRelid(rel); + /* + * filter out duplicates when user specifies "foo, foo" + * Note that this algorithm is known to not be very efficient (O(N^2)) + * but given that it only works on list of tables given to us by user + * it's deemed acceptable. + */ + if (list_member_oid(relids, myrelid)) { + heap_close(rel, ShareUpdateExclusiveLock); + continue; + } + rels = lappend(rels, rel); + relids = lappend_oid(relids, myrelid); + } + + list_free(relids); + + return rels; +} + +/* + * Close all relations in the list. + */ +static void CloseTableList(List *rels) +{ + ListCell *lc; + + foreach (lc, rels) { + Relation rel = (Relation)lfirst(lc); + + heap_close(rel, NoLock); + } +} + +/* + * 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; + + /* Must be owner of the table or superuser. */ + if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(rel)); + + obj = publication_add_relation(pubid, rel, if_not_exists); + if (stmt) { + InvokeObjectAccessHook(OAT_POST_CREATE, PublicationRelRelationId, obj.objectId, 0, NULL); + } + } +} + +/* + * Remove listed tables from the publication. + */ +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)))); + } + + obj.classId = PublicationRelRelationId; + obj.objectId = prid; + obj.objectSubId = 0; + performDeletion(&obj, DROP_CASCADE, 0); + } +} + +/* + * Internal workhorse for changing a publication owner + */ +static void AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) +{ + Form_pg_publication form; + + form = (Form_pg_publication)GETSTRUCT(tup); + if (form->pubowner == newOwnerId) { + return; + } + + if (!superuser()) { + AclResult aclresult; + + /* Must be owner */ + if (!pg_publication_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PUBLICATION, NameStr(form->pubname)); + + /* Must be able to become new owner */ + check_is_member_of_role(GetUserId(), newOwnerId); + + /* New owner must have CREATE privilege on database */ + aclresult = pg_database_aclcheck(u_sess->proc_cxt.MyDatabaseId, newOwnerId, ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_DATABASE, get_database_name(u_sess->proc_cxt.MyDatabaseId)); + + if (form->puballtables && !superuser_arg(newOwnerId)) + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to change owner of publication \"%s\"", NameStr(form->pubname)), + errhint("The owner of a FOR ALL TABLES publication must be a superuser."))); + } + + form->pubowner = newOwnerId; + simple_heap_update(rel, &tup->t_self, tup); + CatalogUpdateIndexes(rel, tup); + + /* Update owner dependency reference */ + changeDependencyOnOwner(PublicationRelationId, HeapTupleGetOid(tup), newOwnerId); +} + +/* + * Change publication owner -- by name + */ +ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId) +{ + Oid subid; + HeapTuple tup; + Relation rel; + ObjectAddress address; + + rel = heap_open(PublicationRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy1(PUBLICATIONNAME, CStringGetDatum(name)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("publication \"%s\" does not exist", name))); + + subid = HeapTupleGetOid(tup); + + AlterPublicationOwner_internal(rel, tup, newOwnerId); + address.classId = PublicationRelationId; + address.objectId = subid; + address.objectSubId = 0; + + heap_freetuple(tup); + heap_close(rel, RowExclusiveLock); + + return address; +} + +/* + * Change publication owner -- by OID + */ +void AlterPublicationOwner_oid(Oid subid, Oid newOwnerId) +{ + HeapTuple tup; + Relation rel; + + rel = heap_open(PublicationRelationId, RowExclusiveLock); + tup = SearchSysCacheCopy1(PUBLICATIONOID, ObjectIdGetDatum(subid)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("publication with OID %u does not exist", subid))); + + AlterPublicationOwner_internal(rel, tup, newOwnerId); + + heap_freetuple(tup); + + heap_close(rel, RowExclusiveLock); +} + +/* + * Internal workhorse for rename a publication + */ +static void RenamePublicationInternal(Relation rel, HeapTuple tup, const char *newname) +{ + Form_pg_publication form = (Form_pg_publication)GETSTRUCT(tup); + + if (!superuser()) { + /* Must be owner */ + if (!pg_publication_ownercheck(HeapTupleGetOid(tup), GetUserId())) { + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PUBLICATION, NameStr(form->pubname)); + } + } + + namestrcpy(&(form->pubname), newname); + simple_heap_update(rel, &tup->t_self, tup); + CatalogUpdateIndexes(rel, tup); +} + +/* + * Rename Publication + */ +void RenamePublication(List *oldname, const char *newname) +{ + HeapTuple tup; + HeapTuple newtup; + Relation rel; + const char *pubname = strVal(linitial(oldname)); + + rel = heap_open(PublicationRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy1(PUBLICATIONNAME, CStringGetDatum(pubname)); + if (!HeapTupleIsValid(tup)) { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("Publication \"%s\" does not exist", pubname))); + } + + newtup = SearchSysCacheCopy1(PUBLICATIONNAME, CStringGetDatum(newname)); + if (HeapTupleIsValid(newtup)) { + ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("Publication \"%s\" has already exists", newname))); + } + + RenamePublicationInternal(rel, tup, newname); + + heap_freetuple(tup); + heap_close(rel, RowExclusiveLock); + + return; +} diff --git a/src/gausskernel/optimizer/commands/subscriptioncmds.cpp b/src/gausskernel/optimizer/commands/subscriptioncmds.cpp new file mode 100644 index 000000000..9c1118732 --- /dev/null +++ b/src/gausskernel/optimizer/commands/subscriptioncmds.cpp @@ -0,0 +1,857 @@ +/* ------------------------------------------------------------------------- + * + * subscriptioncmds.c + * subscription catalog manipulation functions + * + * Copyright (c) 2015, PostgreSQL Global Development Group + * + * IDENTIFICATION + * subscriptioncmds.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "miscadmin.h" + +#include "access/heapam.h" +#include "access/htup.h" +#include "access/xact.h" + +#include "catalog/indexing.h" +#include "catalog/objectaccess.h" +#include "catalog/objectaddress.h" +#include "catalog/pg_type.h" +#include "catalog/pg_subscription.h" +#include "catalog/dependency.h" + +#include "commands/defrem.h" +#include "commands/subscriptioncmds.h" + +#include "nodes/makefuncs.h" + +#include "replication/logicallauncher.h" +#include "replication/origin.h" +#include "replication/walreceiver.h" +#include "replication/worker_internal.h" + +#include "storage/lmgr.h" + +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/syscache.h" +#include "utils/array.h" +#include "utils/acl.h" + +/* + * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. + * + * Since not all options can be specified in both commands, this function + * will report an error on options if the target output pointer is NULL to + * accommodate that. + */ +static void parse_subscription_options(const List *options, char **conninfo, List **publications, bool *enabled_given, + bool *enabled, bool *slot_name_given, char **slot_name, char **synchronous_commit) +{ + ListCell *lc; + + if (conninfo) { + *conninfo = NULL; + } + if (publications) { + *publications = NIL; + } + if (enabled) { + *enabled_given = false; + } + if (slot_name) { + *slot_name_given = false; + *slot_name = NULL; + } + if (synchronous_commit) { + *synchronous_commit = NULL; + } + + /* Parse options */ + foreach (lc, options) { + DefElem *defel = (DefElem *)lfirst(lc); + + if (strcmp(defel->defname, "conninfo") == 0 && conninfo) { + if (*conninfo) { + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + + *conninfo = defGetString(defel); + } else if (strcmp(defel->defname, "publication") == 0 && publications) { + if (*publications) { + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + + *publications = defGetStringList(defel); + } else if (strcmp(defel->defname, "enabled") == 0 && enabled) { + if (*enabled_given) { + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + + *enabled_given = true; + *enabled = defGetBoolean(defel); + } else if (strcmp(defel->defname, "slot_name") == 0 && slot_name) { + if (*slot_name_given) { + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + + *slot_name_given = true; + *slot_name = defGetString(defel); + + /* Setting slot_name = NONE is treated as no slot name. */ + if (strcmp(*slot_name, "none") == 0) { + *slot_name = NULL; + } else { + ReplicationSlotValidateName(*slot_name, ERROR); + } + } else if (strcmp(defel->defname, "synchronous_commit") == 0 && synchronous_commit) { + if (*synchronous_commit) { + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + + *synchronous_commit = defGetString(defel); + + /* Test if the given value is valid for synchronous_commit GUC. */ + (void)set_config_option("synchronous_commit", *synchronous_commit, PGC_BACKEND, PGC_S_TEST, GUC_ACTION_SET, + false, 0, false); + } else { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), errmsg("unrecognized subscription parameter: %s", defel->defname))); + } + } + + /* + * Do additional checking for disallowed combination when + * slot_name = NONE was used. + */ + if (slot_name && *slot_name_given && !*slot_name) { + if (enabled && *enabled_given && *enabled) { + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("slot_name = NONE and enabled = true are mutually exclusive options"))); + } + + if (enabled && !*enabled_given && *enabled) { + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription with slot_name = NONE must also set enabled = false"))); + } + } +} + +/* + * Auxiliary function to build a text array out of a list of String nodes. + */ +static Datum publicationListToArray(List *publist) +{ + ArrayType *arr; + Datum *datums; + int j = 0; + ListCell *cell; + MemoryContext memcxt; + MemoryContext oldcxt; + + /* Create memory context for temporary allocations. */ + memcxt = AllocSetContextCreate(CurrentMemoryContext, "publicationListToArray to array", ALLOCSET_DEFAULT_SIZES); + oldcxt = MemoryContextSwitchTo(memcxt); + + datums = (Datum *)palloc(sizeof(Datum) * list_length(publist)); + foreach (cell, publist) { + char *name = strVal(lfirst(cell)); + ListCell *pcell; + + /* Check for duplicates. */ + foreach (pcell, publist) { + char *pname = strVal(lfirst(pcell)); + + if (pcell == cell) + break; + + if (strcmp(name, pname) == 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), errmsg("publication name \"%s\" used more than once", pname))); + } + + datums[j++] = CStringGetTextDatum(name); + } + + MemoryContextSwitchTo(oldcxt); + + arr = construct_array(datums, list_length(publist), TEXTOID, -1, false, 'i'); + MemoryContextDelete(memcxt); + + return PointerGetDatum(arr); +} + +/* + * connect publisher and create slot + */ +static void ConnectAndCreateSlot(char *conninfo, char *slotname) +{ + /* Try to connect to the publisher. */ + volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; + SpinLockAcquire(&walrcv->mutex); + walrcv->conn_target = REPCONNTARGET_PUBLICATION; + SpinLockRelease(&walrcv->mutex); + bool connectSuccess = (WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_connect(conninfo, NULL, slotname, -1); + if (!connectSuccess) { + ereport(ERROR, (errcode(ERRCODE_CONNECTION_FAILURE), errmsg("could not connect to the publisher"))); + } + + LibpqrcvConnectParam options; + int rc = memset_s(&options, sizeof(LibpqrcvConnectParam), 0, sizeof(LibpqrcvConnectParam)); + securec_check(rc, "", ""); + options.logical = true; + options.slotname = slotname; + PG_TRY(); + { + (WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_create_slot(&options); + ereport(NOTICE, (errmsg("created replication slot \"%s\" on publisher", slotname))); + } + PG_CATCH(); + { + /* Close the connection in case of failure. */ + (WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_disconnect(); + PG_RE_THROW(); + } + PG_END_TRY(); + + /* And we are done with the remote side. */ + (WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_disconnect(); +} + +/* + * Create new subscription. + */ +ObjectAddress CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel) +{ + if (t_thrd.proc->workingVersionNum < PUBLICATION_VERSION_NUM) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Before GRAND VERSION NUM %u, we do not support subscription.", PUBLICATION_VERSION_NUM))); + } + + Relation rel; + ObjectAddress myself; + Oid subid; + bool nulls[Natts_pg_subscription]; + Datum values[Natts_pg_subscription]; + Oid owner = GetUserId(); + HeapTuple tup; + bool enabled_given = false; + bool enabled = true; + char *synchronous_commit; + char *conninfo; + char *slotname; + bool slotname_given; + char originname[NAMEDATALEN]; + List *publications; + int rc; + + /* + * Parse and check options. + * Connection and publication should not be specified here. + */ + parse_subscription_options(stmt->options, NULL, NULL, &enabled_given, &enabled, &slotname_given, &slotname, + &synchronous_commit); + + /* + * Since creating a replication slot is not transactional, rolling back + * the transaction leaves the created replication slot. So we cannot run + * CREATE SUBSCRIPTION inside a transaction block if creating a + * replication slot. + */ + if (enabled) + PreventTransactionChain(isTopLevel, "CREATE SUBSCRIPTION ... WITH (enabled = true)"); + + if (!superuser()) + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to create subscriptions"))); + + rel = heap_open(SubscriptionRelationId, RowExclusiveLock); + + /* Check if name is used */ + subid = GetSysCacheOid2(SUBSCRIPTIONNAME, u_sess->proc_cxt.MyDatabaseId, CStringGetDatum(stmt->subname)); + if (OidIsValid(subid)) { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("subscription \"%s\" already exists", stmt->subname))); + } + + /* The default for synchronous_commit of subscriptions is off. */ + if (synchronous_commit == NULL) { + synchronous_commit = "off"; + } + + conninfo = stmt->conninfo; + publications = stmt->publication; + + /* Check the connection info string. */ + libpqrcv_check_conninfo(conninfo); + + /* Everything ok, form a new tuple. */ + rc = memset_s(values, sizeof(values), 0, sizeof(values)); + securec_check(rc, "", ""); + rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls)); + securec_check(rc, "", ""); + + values[Anum_pg_subscription_subdbid - 1] = ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId); + values[Anum_pg_subscription_subname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->subname)); + values[Anum_pg_subscription_subowner - 1] = ObjectIdGetDatum(owner); + values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(enabled); + + /* encrypt conninfo */ + List *conninfoList = ConninfoToDefList(stmt->conninfo); + /* Sensitive options for subscription, will be encrypted when saved to catalog. */ + const char* sensitiveOptionsArray[] = {"password"}; + const int sensitiveArrayLength = lengthof(sensitiveOptionsArray); + EncryptGenericOptions(conninfoList, sensitiveOptionsArray, sensitiveArrayLength, SUBSCRIPTION_MODE); + char *encryptConninfo = DefListToString(conninfoList); + + values[Anum_pg_subscription_subconninfo - 1] = CStringGetTextDatum(encryptConninfo); + + pfree_ext(conninfoList); + pfree_ext(encryptConninfo); + if (enabled) { + if (!slotname_given) { + slotname = stmt->subname; + } + values[Anum_pg_subscription_subslotname - 1] = DirectFunctionCall1(namein, CStringGetDatum(slotname)); + } else { + if (slotname_given && slotname) { + ereport(WARNING, (errmsg("When enabled=false, it is dangerous to set slot_name. " + "This will cause wal log accumulation on the publisher, " + "so slot_name will be forcibly set to NULL."))); + } + nulls[Anum_pg_subscription_subslotname - 1] = true; + } + values[Anum_pg_subscription_subsynccommit - 1] = CStringGetTextDatum(synchronous_commit); + values[Anum_pg_subscription_subpublications - 1] = publicationListToArray(publications); + + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + + /* Insert tuple into catalog. */ + subid = simple_heap_insert(rel, tup); + CatalogUpdateIndexes(rel, tup); + heap_freetuple(tup); + + recordDependencyOnOwner(SubscriptionRelationId, subid, owner); + + rc = sprintf_s(originname, sizeof(originname), "pg_%u", subid); + securec_check_ss(rc, "", ""); + replorigin_create(originname); + + /* + * If requested, create the replication slot on remote side for our + * newly created subscription. + */ + if (enabled) { + Assert(slotname); + ConnectAndCreateSlot(conninfo, slotname); + } + heap_close(rel, RowExclusiveLock); + + /* Don't wake up logical replication launcher unnecessarily */ + if (enabled) { + ApplyLauncherWakeupAtCommit(); + } + + myself.classId = SubscriptionRelationId; + myself.objectId = subid; + myself.objectSubId = 0; + + InvokeObjectAccessHook(OAT_POST_CREATE, SubscriptionRelationId, subid, 0, NULL); + + return myself; +} + +/* + * Alter the existing subscription. + */ +ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt) +{ + if (t_thrd.proc->workingVersionNum < PUBLICATION_VERSION_NUM) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Before GRAND VERSION NUM %u, we do not support subscription.", PUBLICATION_VERSION_NUM))); + } + + Relation rel; + ObjectAddress myself; + bool nulls[Natts_pg_subscription]; + bool replaces[Natts_pg_subscription]; + Datum values[Natts_pg_subscription]; + HeapTuple tup; + Oid subid; + bool enabled_given = false; + bool enabled; + char *synchronous_commit; + char *conninfo; + char *slot_name; + bool slotname_given; + List *publications; + Subscription *sub; + int rc; + + rel = heap_open(SubscriptionRelationId, RowExclusiveLock); + + /* Fetch the existing tuple. */ + tup = SearchSysCacheCopy2(SUBSCRIPTIONNAME, u_sess->proc_cxt.MyDatabaseId, CStringGetDatum(stmt->subname)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("subscription \"%s\" does not exist", stmt->subname))); + + /* must be owner */ + if (!pg_subscription_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_SUBSCRIPTION, stmt->subname); + + subid = HeapTupleGetOid(tup); + sub = GetSubscription(subid, false); + enabled = sub->enabled; + + /* Parse options. */ + parse_subscription_options(stmt->options, &conninfo, &publications, &enabled_given, &enabled, &slotname_given, + &slot_name, &synchronous_commit); + + /* Form a new tuple. */ + rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls)); + securec_check(rc, "", ""); + rc = memset_s(values, sizeof(values), 0, sizeof(values)); + securec_check(rc, "", ""); + rc = memset_s(replaces, sizeof(replaces), false, sizeof(replaces)); + securec_check(rc, "", ""); + + if (enabled_given && enabled != sub->enabled) { + values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(enabled); + replaces[Anum_pg_subscription_subenabled - 1] = true; + } + if (conninfo) { + /* Check the connection info string. */ + libpqrcv_check_conninfo(conninfo); + + /* encrypt conninfo */ + List *conninfoList = ConninfoToDefList(conninfo); + /* Sensitive options for subscription, will be encrypted when saved to catalog. */ + const char* sensitiveOptionsArray[] = {"password"}; + const int sensitiveArrayLength = lengthof(sensitiveOptionsArray); + EncryptGenericOptions(conninfoList, sensitiveOptionsArray, sensitiveArrayLength, SUBSCRIPTION_MODE); + char *encryptConninfo = DefListToString(conninfoList); + + values[Anum_pg_subscription_subconninfo - 1] = CStringGetTextDatum(encryptConninfo); + replaces[Anum_pg_subscription_subconninfo - 1] = true; + + pfree_ext(conninfoList); + pfree_ext(encryptConninfo); + } + if (slotname_given) { + if (sub->enabled && !slot_name) { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), errmsg("cannot set slot_name = NONE for enabled subscription"))); + } + + /* change to non-null value */ + if (slot_name) { + if (sub->enabled || (enabled_given && enabled)) { + values[Anum_pg_subscription_subslotname - 1] = DirectFunctionCall1(namein, CStringGetDatum(slot_name)); + } else { + ereport(ERROR, (errmsg("Currently enabled=false, cannot change slot_name to a non-null value."))); + } + } else { + /* change to NULL */ + + /* + * when enable this subscription but slot_name is not specified, + * it will be set to default value subname. + */ + if (!sub->enabled && enabled_given && enabled) { + values[Anum_pg_subscription_subslotname - 1] = DirectFunctionCall1(namein, CStringGetDatum(sub->name)); + } else { + nulls[Anum_pg_subscription_subslotname - 1] = true; + } + } + replaces[Anum_pg_subscription_subslotname - 1] = true; + } else if (!sub->enabled && enabled_given && enabled) { + values[Anum_pg_subscription_subslotname - 1] = DirectFunctionCall1(namein, CStringGetDatum(sub->name)); + replaces[Anum_pg_subscription_subslotname - 1] = true; + } + if (synchronous_commit) { + values[Anum_pg_subscription_subsynccommit - 1] = CStringGetTextDatum(synchronous_commit); + replaces[Anum_pg_subscription_subsynccommit - 1] = true; + } + if (publications != NIL) { + values[Anum_pg_subscription_subpublications - 1] = publicationListToArray(publications); + replaces[Anum_pg_subscription_subpublications - 1] = true; + } + + tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces); + + /* Update the catalog. */ + simple_heap_update(rel, &tup->t_self, tup); + CatalogUpdateIndexes(rel, tup); + + myself.classId = SubscriptionRelationId; + myself.objectId = subid; + myself.objectSubId = 0; + + /* Cleanup. */ + heap_freetuple(tup); + heap_close(rel, RowExclusiveLock); + + if (sub->enabled && !enabled) { + ereport(ERROR, (errmsg("If you want to deactivate this subscription, use DROP SUBSCRIPTION."))); + } + /* enable subscription */ + if (!sub->enabled && enabled) { + /* + * If slot_name is not specified or is empty, the default value is used; + * otherwise, the user-specified slot_name is used. + */ + char *temp_slotname = sub->name; + if (slotname_given && slot_name && *slot_name) { + temp_slotname = slot_name; + } + + /* if slot hasn't been created, then create it */ + if (!sub->slotname || !*(sub->slotname)) { + if (conninfo) { + ConnectAndCreateSlot(conninfo, temp_slotname); + } else { + /* Sensitive options for subscription, will be encrypted when saved to catalog. */ + const char* sensitiveOptionsArray[] = {"password"}; + const int sensitiveArrayLength = lengthof(sensitiveOptionsArray); + List *defList = ConninfoToDefList(sub->conninfo); + DecryptOptions(defList, sensitiveOptionsArray, sensitiveArrayLength, SUBSCRIPTION_MODE); + char *decryptConnInfo = DefListToString(defList); + + ConnectAndCreateSlot(decryptConnInfo, temp_slotname); + + pfree_ext(defList); + pfree_ext(decryptConnInfo); + } + } + ApplyLauncherWakeupAtCommit(); + } + + return myself; +} + +/* + * Drop a subscription + */ +void DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel) +{ + if (t_thrd.proc->workingVersionNum < PUBLICATION_VERSION_NUM) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Before GRAND VERSION NUM %u, we do not support subscription.", PUBLICATION_VERSION_NUM))); + } + + Relation rel; + ObjectAddress myself; + HeapTuple tup; + Oid subid; + Datum datum; + bool isnull; + char *subname; + char *conninfo; + char *slotname; + List *subWorkers; + ListCell *lc; + char originname[NAMEDATALEN]; + char *err = NULL; + StringInfoData cmd; + int rc; + + /* + * Lock pg_subscription with AccessExclusiveLock to ensure that the + * launcher doesn't restart new worker during dropping the subscription + */ + rel = heap_open(SubscriptionRelationId, AccessExclusiveLock); + + tup = SearchSysCache2(SUBSCRIPTIONNAME, u_sess->proc_cxt.MyDatabaseId, CStringGetDatum(stmt->subname)); + if (!HeapTupleIsValid(tup)) { + heap_close(rel, NoLock); + + if (!stmt->missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("subscription \"%s\" does not exist", stmt->subname))); + else + ereport(NOTICE, (errmsg("subscription \"%s\" does not exist, skipping", stmt->subname))); + + return; + } + + subid = HeapTupleGetOid(tup); + /* must be owner */ + if (!pg_subscription_ownercheck(subid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_SUBSCRIPTION, stmt->subname); + + /* DROP hook for the subscription being removed */ + InvokeObjectAccessHook(OAT_DROP, SubscriptionRelationId, subid, 0, NULL); + + /* + * Lock the subscription so nobody else can do anything with it + * (including the replication workers). + */ + LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock); + + /* Get subname */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subname, &isnull); + Assert(!isnull); + subname = pstrdup(NameStr(*DatumGetName(datum))); + + /* Get conninfo */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subconninfo, &isnull); + Assert(!isnull); + conninfo = TextDatumGetCString(datum); + + /* Get slotname */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subslotname, &isnull); + if (!isnull) { + slotname = pstrdup(NameStr(*DatumGetName(datum))); + } else { + slotname = NULL; + } + + /* + * Since dropping a replication slot is not transactional, the replication + * slot stays dropped even if the transaction rolls back. So we cannot + * run DROP SUBSCRIPTION inside a transaction block if dropping the + * replication slot. + * + * XXX The command name should really be something like "DROP SUBSCRIPTION + * of a subscription that is associated with a replication slot", but we + * don't have the proper facilities for that. + */ + if (slotname) { + PreventTransactionChain(isTopLevel, "DROP SUBSCRIPTION"); + } + + myself.classId = SubscriptionRelationId; + myself.objectId = subid; + myself.objectSubId = 0; + + /* Remove the tuple from catalog. */ + simple_heap_delete(rel, &tup->t_self); + + ReleaseSysCache(tup); + + /* Clean up dependencies */ + deleteSharedDependencyRecordsFor(SubscriptionRelationId, subid, 0); + + /* + * Stop all the subscription workers immediately. + * + * This is necessary if we are dropping the replication slot, so that the + * slot becomes accessible. + * + * It is also necessary if the subscription is disabled and was disabled + * in the same transaction. Then the workers haven't seen the disabling + * yet and will still be running, leading to hangs later when we want to + * drop the replication origin. If the subscription was disabled before + * this transaction, then there shouldn't be any workers left, so this + * won't make a difference. + * + * New workers won't be started because we hold an exclusive lock on the + * subscription till the end of the transaction. + */ + LWLockAcquire(LogicalRepWorkerLock, LW_SHARED); + subWorkers = logicalrep_workers_find(subid, false); + LWLockRelease(LogicalRepWorkerLock); + + foreach (lc, subWorkers) { + LogicalRepWorker *w = (LogicalRepWorker *)lfirst(lc); + logicalrep_worker_stop(w->subid); + } + list_free(subWorkers); + + /* Remove the origin tracking if exists. */ + rc = sprintf_s(originname, sizeof(originname), "pg_%u", subid); + securec_check_ss(rc, "", ""); + replorigin_drop_by_name(originname, true); + + /* If there is no slot associated with the subscription, we can finish here. */ + if (!slotname) { + heap_close(rel, NoLock); + return; + } + + /* + * Otherwise drop the replication slot at the publisher node using + * the replication connection. + */ + initStringInfo(&cmd); + appendStringInfo(&cmd, "DROP_REPLICATION_SLOT %s", quote_identifier(slotname)); + + volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; + SpinLockAcquire(&walrcv->mutex); + walrcv->conn_target = REPCONNTARGET_PUBLICATION; + SpinLockRelease(&walrcv->mutex); + + /* Sensitive options for subscription, will be encrypted when saved to catalog. */ + const char* sensitiveOptionsArray[] = {"password"}; + const int sensitiveArrayLength = lengthof(sensitiveOptionsArray); + List *defList = ConninfoToDefList(conninfo); + DecryptOptions(defList, sensitiveOptionsArray, sensitiveArrayLength, SUBSCRIPTION_MODE); + conninfo = DefListToString(defList); + + bool connectSuccess = (WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_connect(conninfo, NULL, subname, -1); + pfree_ext(defList); + pfree_ext(conninfo); + if (!connectSuccess) { + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_FAILURE), errmsg("could not connect to publisher when attempting to drop " + "the replication slot \"%s\". Use ALTER SUBSCRIPTION " + "... SET (slot_name = NONE) to disassociate the " + "subscription from the slot.", + slotname))); + } + + PG_TRY(); + { + if (!(WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_command(cmd.data, &err)) { + (WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_disconnect(); + ereport(ERROR, (errmsg("could not drop the replication slot \"%s\" on publisher", slotname), + errdetail("The error was: %s", err))); + } else { + ereport(NOTICE, (errmsg("dropped replication slot \"%s\" on publisher", slotname))); + } + } + PG_CATCH(); + { + /* Close the connection in case of failure */ + (WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_disconnect(); + PG_RE_THROW(); + } + PG_END_TRY(); + + (WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_disconnect(); + + pfree(cmd.data); + heap_close(rel, NoLock); +} + +/* + * Internal workhorse for changing a subscription owner + */ +static void AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) +{ + Form_pg_subscription form; + + form = (Form_pg_subscription)GETSTRUCT(tup); + if (form->subowner == newOwnerId) { + return; + } + + if (!pg_subscription_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_SUBSCRIPTION, NameStr(form->subname)); + + /* New owner must be a superuser */ + if (!superuser_arg(newOwnerId)) + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to change owner of subscription \"%s\"", NameStr(form->subname)), + errhint("The owner of a subscription must be a superuser."))); + + form->subowner = newOwnerId; + simple_heap_update(rel, &tup->t_self, tup); + CatalogUpdateIndexes(rel, tup); + + /* Update owner dependency reference */ + changeDependencyOnOwner(SubscriptionRelationId, HeapTupleGetOid(tup), newOwnerId); +} + +/* + * Change subscription owner -- by name + */ +ObjectAddress AlterSubscriptionOwner(const char *name, Oid newOwnerId) +{ + Oid subid; + HeapTuple tup; + Relation rel; + ObjectAddress address; + + rel = heap_open(SubscriptionRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy2(SUBSCRIPTIONNAME, u_sess->proc_cxt.MyDatabaseId, CStringGetDatum(name)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("subscription \"%s\" does not exist", name))); + + subid = HeapTupleGetOid(tup); + + AlterSubscriptionOwner_internal(rel, tup, newOwnerId); + + address.classId = SubscriptionRelationId; + address.objectId = subid; + address.objectSubId = 0; + + heap_freetuple(tup); + + heap_close(rel, RowExclusiveLock); + + return address; +} + +/* + * Change subscription owner -- by OID + */ +void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId) +{ + HeapTuple tup; + Relation rel; + + rel = heap_open(SubscriptionRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("subscription with OID %u does not exist", subid))); + + AlterSubscriptionOwner_internal(rel, tup, newOwnerId); + + heap_freetuple(tup); + + heap_close(rel, RowExclusiveLock); +} + +/* + * Internal workhorse for rename a Subscription + */ +static void RenameSubscriptionInternal(Relation rel, HeapTuple tup, const char *newname) +{ + Form_pg_subscription form = (Form_pg_subscription)GETSTRUCT(tup); + + if (!pg_subscription_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_SUBSCRIPTION, NameStr(form->subname)); + + namestrcpy(&(form->subname), newname); + simple_heap_update(rel, &tup->t_self, tup); + CatalogUpdateIndexes(rel, tup); +} + +/* + * Rename Subscription + */ +void RenameSubscription(List *oldname, const char *newname) +{ + HeapTuple tup; + HeapTuple newtup; + Relation rel; + const char *subname = strVal(linitial(oldname)); + + rel = heap_open(SubscriptionRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy2(SUBSCRIPTIONNAME, u_sess->proc_cxt.MyDatabaseId, CStringGetDatum(subname)); + if (!HeapTupleIsValid(tup)) { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("Subscription \"%s\" does not exist", subname))); + } + + newtup = SearchSysCacheCopy2(SUBSCRIPTIONNAME, u_sess->proc_cxt.MyDatabaseId, CStringGetDatum(newname)); + if (HeapTupleIsValid(newtup)) { + ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("Subscription \"%s\" has already exists", newname))); + } + + RenameSubscriptionInternal(rel, tup, newname); + + heap_freetuple(tup); + heap_close(rel, RowExclusiveLock); + + return; +} diff --git a/src/gausskernel/process/postmaster/postmaster.cpp b/src/gausskernel/process/postmaster/postmaster.cpp index 08734ad74..c96f24111 100644 --- a/src/gausskernel/process/postmaster/postmaster.cpp +++ b/src/gausskernel/process/postmaster/postmaster.cpp @@ -143,6 +143,8 @@ #include "replication/walsender_private.h" #include "replication/walreceiver.h" #include "replication/dcf_replication.h" +#include "replication/logicallauncher.h" +#include "replication/logicalworker.h" #include "postmaster/bgwriter.h" #include "postmaster/cbmwriter.h" #include "postmaster/startup.h" @@ -447,6 +449,7 @@ static void StartRbWorker(void); static void StartTxnSnapWorker(void); static void StartAutovacuumWorker(void); static void StartUndoWorker(void); +static void StartApplyWorker(void); static ThreadId StartCatchupWorker(void); static void InitPostmasterDeathWatchHandle(void); static void NotifyShutdown(void); @@ -2998,6 +3001,13 @@ static int ServerLoop(void) g_instance.pid_cxt.GlobalStatsPID = initialize_util_thread(GLOBALSTATS_THREAD); } +#ifndef ENABLE_MULTIPLE_NODES + if (u_sess->attr.attr_common.upgrade_mode == 0 && g_instance.pid_cxt.ApplyLauncerPID == 0 && + pmState == PM_RUN && !dummyStandbyMode) { + g_instance.pid_cxt.ApplyLauncerPID = initialize_util_thread(APPLY_LAUNCHER); + } +#endif + /* * If we have lost the job scheduler, try to start a new one. * @@ -4541,7 +4551,11 @@ static void SIGHUP_handler(SIGNAL_ARGS) if (g_instance.pid_cxt.UndoLauncherPID != 0) { signal_child(g_instance.pid_cxt.UndoLauncherPID, SIGHUP); } - +#ifndef ENABLE_MULTIPLE_NODES + if (g_instance.pid_cxt.ApplyLauncerPID != 0) { + signal_child(g_instance.pid_cxt.ApplyLauncerPID, SIGHUP); + } +#endif if (g_instance.pid_cxt.UndoRecyclerPID != 0) { signal_child(g_instance.pid_cxt.UndoRecyclerPID, SIGHUP); } @@ -4780,7 +4794,11 @@ static void pmdie(SIGNAL_ARGS) if (g_instance.pid_cxt.UndoLauncherPID != 0) { signal_child(g_instance.pid_cxt.UndoLauncherPID, SIGTERM); } - +#ifndef ENABLE_MULTIPLE_NODES + if (g_instance.pid_cxt.ApplyLauncerPID != 0) { + signal_child(g_instance.pid_cxt.ApplyLauncerPID, SIGTERM); + } +#endif if (g_instance.pid_cxt.GlobalStatsPID != 0) { signal_child(g_instance.pid_cxt.GlobalStatsPID, SIGTERM); } @@ -5036,7 +5054,10 @@ static void ProcessDemoteRequest(void) if (g_instance.pid_cxt.UndoLauncherPID != 0) signal_child(g_instance.pid_cxt.UndoLauncherPID, SIGTERM); - +#ifndef ENABLE_MULTIPLE_NODES + if (g_instance.pid_cxt.ApplyLauncerPID != 0) + signal_child(g_instance.pid_cxt.ApplyLauncerPID, SIGTERM); +#endif if (g_instance.pid_cxt.GlobalStatsPID != 0) signal_child(g_instance.pid_cxt.GlobalStatsPID, SIGTERM); @@ -5208,7 +5229,10 @@ static void ProcessDemoteRequest(void) if (g_instance.pid_cxt.UndoLauncherPID != 0) signal_child(g_instance.pid_cxt.UndoLauncherPID, SIGTERM); - +#ifndef ENABLE_MULTIPLE_NODES + if (g_instance.pid_cxt.ApplyLauncerPID != 0) + signal_child(g_instance.pid_cxt.ApplyLauncerPID, SIGTERM); +#endif if (g_instance.pid_cxt.GlobalStatsPID != 0) signal_child(g_instance.pid_cxt.GlobalStatsPID, SIGTERM); @@ -5456,6 +5480,13 @@ static void reaper(SIGNAL_ARGS) if (g_instance.pid_cxt.GlobalStatsPID == 0 && !dummyStandbyMode) g_instance.pid_cxt.GlobalStatsPID = initialize_util_thread(GLOBALSTATS_THREAD); +#ifndef ENABLE_MULTIPLE_NODES + if (u_sess->attr.attr_common.upgrade_mode == 0 && + g_instance.pid_cxt.ApplyLauncerPID == 0 && !dummyStandbyMode) { + g_instance.pid_cxt.ApplyLauncerPID = initialize_util_thread(APPLY_LAUNCHER); + } +#endif + if (XLogArchivingActive() && g_instance.pid_cxt.PgArchPID == 0 && !dummyStandbyMode && XLogArchiveCommandSet()) g_instance.pid_cxt.PgArchPID = pgarch_start(); @@ -6175,7 +6206,15 @@ static void reaper(SIGNAL_ARGS) LogChildExit(LOG, _("undo launcher process"), pid, exitstatus); continue; } +#ifndef ENABLE_MULTIPLE_NODES + if (pid == g_instance.pid_cxt.ApplyLauncerPID) { + g_instance.pid_cxt.ApplyLauncerPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + LogChildExit(LOG, _("apply launcher process"), pid, exitstatus); + continue; + } +#endif if (pid == g_instance.pid_cxt.GlobalStatsPID) { g_instance.pid_cxt.GlobalStatsPID = 0; @@ -6541,7 +6580,10 @@ static void PostmasterStateMachineReadOnly(void) if (g_instance.pid_cxt.UndoLauncherPID != 0) signal_child(g_instance.pid_cxt.UndoLauncherPID, SIGTERM); - +#ifndef ENABLE_MULTIPLE_NODES + if (g_instance.pid_cxt.ApplyLauncerPID != 0) + signal_child(g_instance.pid_cxt.ApplyLauncerPID, SIGTERM); +#endif if (g_instance.pid_cxt.UndoRecyclerPID != 0) signal_child(g_instance.pid_cxt.UndoRecyclerPID, SIGTERM); @@ -6621,7 +6663,10 @@ static void PostmasterStateMachine(void) #endif /* ENABLE_MULTIPLE_NODES */ g_instance.pid_cxt.UndoLauncherPID == 0 && g_instance.pid_cxt.UndoRecyclerPID == 0 && - g_instance.pid_cxt.GlobalStatsPID == 0 && + g_instance.pid_cxt.GlobalStatsPID == 0 && +#ifndef ENABLE_MULTIPLE_NODES + g_instance.pid_cxt.ApplyLauncerPID == 0 && +#endif IsAllPageWorkerExit() && IsAllBuildSenderExit()) { if (g_instance.fatal_error) { /* @@ -6781,6 +6826,9 @@ static void PostmasterStateMachine(void) Assert(g_instance.pid_cxt.UndoLauncherPID == 0); Assert(g_instance.pid_cxt.UndoRecyclerPID == 0); Assert(g_instance.pid_cxt.GlobalStatsPID == 0); +#ifndef ENABLE_MULTIPLE_NODES + Assert(g_instance.pid_cxt.ApplyLauncerPID == 0); +#endif Assert(IsAllPageWorkerExit() == true); Assert(IsAllBuildSenderExit() == true); /* syslogger is not considered here */ @@ -8152,6 +8200,13 @@ static void sigusr1_handler(SIGNAL_ARGS) StartUndoWorker(); } + /* should not start a worker in shutdown or demotion procedure */ + if (CheckPostmasterSignal(PMSIGNAL_START_APPLY_WORKER) && + g_instance.status == NoShutdown && + g_instance.demotion == NoDemote) { + StartApplyWorker(); + } + /* should not start a worker in shutdown or demotion procedure */ if (CheckPostmasterSignal(PMSIGNAL_START_CLEAN_STATEMENT) && g_instance.status == NoShutdown && g_instance.demotion == NoDemote) { @@ -8612,6 +8667,59 @@ static void StartAutovacuumWorker(void) } } +static void StartApplyWorker(void) +{ + Backend* bn = NULL; + + /* + * If not in condition to run a process, don't try, but handle it like a + * fork failure. This does not normally happen, since the signal is only + * supposed to be sent by apply launcher when it's OK to do it, but + * we have to check to avoid race-condition problems during DB state + * changes. + */ + if (canAcceptConnections(false) == CAC_OK) { + int slot = AssignPostmasterChildSlot(); + if (slot < 1) { + ereport(ERROR, (errmsg("no free slots in PMChildFlags array"))); + } + + bn = AssignFreeBackEnd(slot); + + if (bn != NULL) { + /* + * Compute the cancel key that will be assigned to this session. + * We probably don't need cancel keys for workers, but + * we'd better have something random in the field to prevent + * unfriendly people from sending cancels to them. + */ + GenerateCancelKey(false); + bn->cancel_key = t_thrd.proc_cxt.MyCancelKey; + + /* ApplyWorkers need a child slot */ + bn->child_slot = t_thrd.proc_cxt.MyPMChildSlot = slot; + bn->pid = initialize_util_thread(APPLY_WORKER, bn); + t_thrd.proc_cxt.MyPMChildSlot = 0; + if (bn->pid > 0) { + bn->is_autovacuum = false; + DLInitElem(&bn->elem, bn); + DLAddHead(g_instance.backend_list, &bn->elem); + /* all OK */ + return; + } + + /* + * fork failed, fall through to report -- actual error message was + * logged by initialize_util_thread + */ + (void)ReleasePostmasterChildSlot(bn->child_slot); + bn->pid = 0; + } else { + ereport(LOG, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); + } + } +} + static void StartUndoWorker(void) { Backend* bn = NULL; @@ -11643,6 +11751,22 @@ int GaussDbThreadMain(knl_thread_arg* arg) proc_exit(0); } break; + case APPLY_LAUNCHER: { + t_thrd.proc_cxt.MyPMChildSlot = AssignPostmasterChildSlot(); + if (t_thrd.proc_cxt.MyPMChildSlot == -1) { + return STATUS_ERROR; + } + InitProcessAndShareMemory(); + ApplyLauncherMain(); + proc_exit(0); + } break; + + case APPLY_WORKER: { + InitProcessAndShareMemory(); + ApplyWorkerMain(); + proc_exit(0); + } break; + case UNDO_LAUNCHER: { t_thrd.proc_cxt.MyPMChildSlot = AssignPostmasterChildSlot(); if (t_thrd.proc_cxt.MyPMChildSlot == -1) { @@ -11794,6 +11918,8 @@ static ThreadMetaData GaussdbThreadGate[] = { { GaussDbThreadMain, BARRIER_CREATOR, "barriercreator", "barrier creator" }, { GaussDbThreadMain, BGWORKER, "bgworker", "background worker" }, { GaussDbThreadMain, BARRIER_ARCH, "barrierarch", "barrier arch" }, + { GaussDbThreadMain, APPLY_LAUNCHER, "applylauncher", "apply launcher" }, + { GaussDbThreadMain, APPLY_WORKER, "applyworker", "apply worker" }, /* Keep the block in the end if it may be absent !!! */ #ifdef ENABLE_MULTIPLE_NODES { GaussDbThreadMain, TS_COMPACTION, "TScompaction", diff --git a/src/gausskernel/process/tcop/postgres.cpp b/src/gausskernel/process/tcop/postgres.cpp index 2a2f16c53..ab8763c0a 100755 --- a/src/gausskernel/process/tcop/postgres.cpp +++ b/src/gausskernel/process/tcop/postgres.cpp @@ -74,6 +74,8 @@ #include "postmaster/autovacuum.h" #include "postmaster/postmaster.h" #include "postmaster/snapcapturer.h" +#include "replication/logicallauncher.h" +#include "replication/logicalworker.h" #include "replication/dataqueue.h" #include "replication/datasender.h" #include "replication/walsender.h" @@ -6016,11 +6018,18 @@ void ProcessInterrupts(void) t_thrd.postgres_cxt.whereToSendOutput = DestNone; } } - if (IsAutoVacuumWorkerProcess()) + if (IsAutoVacuumWorkerProcess()) { ereport(FATAL, (errcode(ERRCODE_ADMIN_SHUTDOWN), errmsg("terminating autovacuum process due to administrator command"))); - else if (IsTxnSnapCapturerProcess()) { +#ifndef ENABLE_MULTIPLE_NODES + } else if (IsLogicalLauncher()) { + ereport(DEBUG1, (errmsg("logical replication launcher shutting down"))); + + /* The logical replication launcher can be stopped at any time. */ + proc_exit(0); +#endif + } else if (IsTxnSnapCapturerProcess()) { ereport(FATAL, (errcode(ERRCODE_ADMIN_SHUTDOWN), errmsg("terminating txnsnapcapturer process due to administrator command"))); diff --git a/src/gausskernel/process/tcop/utility.cpp b/src/gausskernel/process/tcop/utility.cpp index d314a1450..8dd8b4e49 100755 --- a/src/gausskernel/process/tcop/utility.cpp +++ b/src/gausskernel/process/tcop/utility.cpp @@ -57,10 +57,12 @@ #include "commands/portalcmds.h" #include "commands/prepare.h" #include "commands/proclang.h" +#include "commands/publicationcmds.h" #include "commands/schemacmds.h" #include "commands/seclabel.h" #include "commands/sec_rls_cmds.h" #include "commands/sequence.h" +#include "commands/subscriptioncmds.h" #include "commands/shutdown.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" @@ -485,6 +487,11 @@ static void check_xact_readonly(Node* parse_tree) case T_CreateClientLogicColumn: case T_CreatePackageStmt: case T_CreatePackageBodyStmt: + case T_CreatePublicationStmt: + case T_AlterPublicationStmt: + case T_CreateSubscriptionStmt: + case T_AlterSubscriptionStmt: + case T_DropSubscriptionStmt: PreventCommandIfReadOnly(CreateCommandTag(parse_tree)); break; case T_VacuumStmt: { @@ -6828,6 +6835,52 @@ void standard_ProcessUtility(Node* parse_tree, const char* query_string, ParamLi break; } + + case T_CreatePublicationStmt: +#ifdef ENABLE_MULTIPLE_NODES + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("openGauss does not support PUBLICATION yet"), + errdetail("The feature is not currently supported"))); +#endif + CreatePublication((CreatePublicationStmt *) parse_tree); + break; + case T_AlterPublicationStmt: +#ifdef ENABLE_MULTIPLE_NODES + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("openGauss does not support PUBLICATION yet"), + errdetail("The feature is not currently supported"))); +#endif + AlterPublication((AlterPublicationStmt *) parse_tree); + break; + case T_CreateSubscriptionStmt: +#ifdef ENABLE_MULTIPLE_NODES + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("openGauss does not support SUBSCRIPTION yet"), + errdetail("The feature is not currently supported"))); +#endif + CreateSubscription((CreateSubscriptionStmt *) parse_tree, is_top_level); + break; + case T_AlterSubscriptionStmt: +#ifdef ENABLE_MULTIPLE_NODES + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("openGauss does not support SUBSCRIPTION yet"), + errdetail("The feature is not currently supported"))); +#endif + AlterSubscription((AlterSubscriptionStmt *) parse_tree); + break; + case T_DropSubscriptionStmt: +#ifdef ENABLE_MULTIPLE_NODES + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("openGauss does not support SUBSCRIPTION yet"), + errdetail("The feature is not currently supported"))); +#endif + DropSubscription((DropSubscriptionStmt *) parse_tree, is_top_level); + break; default: { ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), @@ -7638,6 +7691,12 @@ static const char* AlterObjectTypeCommandTag(ObjectType obj_type) case OBJECT_SYNONYM: tag = "ALTER SYNONYM"; break; + case OBJECT_PUBLICATION: + tag = "ALTER PUBLICATION"; + break; + case OBJECT_SUBSCRIPTION: + tag = "ALTER SUBSCRIPTION"; + break; default: tag = "?\?\?"; break; @@ -8005,6 +8064,9 @@ const char* CreateCommandTag(Node* parse_tree) case OBJECT_COLUMN_SETTING: tag = "DROP COLUMN ENCRYPTION KEY"; break; + case OBJECT_PUBLICATION: + tag = "DROP PUBLICATION"; + break; default: tag = "?\?\?"; break; @@ -8645,6 +8707,25 @@ const char* CreateCommandTag(Node* parse_tree) case T_CreateModelStmt: tag = "CREATE MODEL"; break; + case T_CreatePublicationStmt: + tag = "CREATE PUBLICATION"; + break; + + case T_AlterPublicationStmt: + tag = "ALTER PUBLICATION"; + break; + + case T_CreateSubscriptionStmt: + tag = "CREATE SUBSCRIPTION"; + break; + + case T_AlterSubscriptionStmt: + tag = "ALTER SUBSCRIPTION"; + break; + + case T_DropSubscriptionStmt: + tag = "DROP SUBSCRIPTION"; + break; default: elog(WARNING, "unrecognized node type: %d", (int)nodeTag(parse_tree)); @@ -9382,6 +9463,11 @@ LogStmtLevel GetCommandLogLevel(Node* parse_tree) case T_CreateAppWorkloadGroupMappingStmt: case T_AlterAppWorkloadGroupMappingStmt: case T_DropAppWorkloadGroupMappingStmt: + case T_CreatePublicationStmt: + case T_AlterPublicationStmt: + case T_CreateSubscriptionStmt: + case T_AlterSubscriptionStmt: + case T_DropSubscriptionStmt: lev = LOGSTMT_DDL; break; diff --git a/src/gausskernel/process/threadpool/knl_session.cpp b/src/gausskernel/process/threadpool/knl_session.cpp index ef2eb7bf9..f5b170b3c 100755 --- a/src/gausskernel/process/threadpool/knl_session.cpp +++ b/src/gausskernel/process/threadpool/knl_session.cpp @@ -88,6 +88,16 @@ KnlUUstoreInit(knl_u_ustore_context *ustoreCxt) ustoreCxt->tdSlotWaitActive = false; } +static void KnlURepOriginInit(knl_u_rep_origin_context* repOriginCxt) +{ + repOriginCxt->curRepState = NULL; + repOriginCxt->originId = InvalidRepOriginId; + repOriginCxt->originLsn = InvalidXLogRecPtr; + repOriginCxt->originTs = 0; + repOriginCxt->repStatesShm = NULL; + repOriginCxt->registeredCleanup = false; +} + static void knl_u_analyze_init(knl_u_analyze_context* anl_cxt) { anl_cxt->is_under_analyze = false; @@ -1339,6 +1349,7 @@ void knl_session_init(knl_session_context* sess_cxt) knl_u_mot_init(&sess_cxt->mot_cxt); #endif KnlUUstoreInit(&sess_cxt->ustore_cxt); + KnlURepOriginInit(&sess_cxt->reporigin_cxt); MemoryContextSeal(sess_cxt->top_mem_cxt); } diff --git a/src/gausskernel/process/threadpool/knl_thread.cpp b/src/gausskernel/process/threadpool/knl_thread.cpp index b471d55e4..ec77e9064 100644 --- a/src/gausskernel/process/threadpool/knl_thread.cpp +++ b/src/gausskernel/process/threadpool/knl_thread.cpp @@ -428,6 +428,7 @@ static void knl_t_xlog_init(knl_t_xlog_context* xlog_cxt) xlog_cxt->curFileTLI = 0; xlog_cxt->ProcLastRecPtr = InvalidXLogRecPtr; xlog_cxt->XactLastRecEnd = InvalidXLogRecPtr; + xlog_cxt->XactLastCommitEnd = InvalidXLogRecPtr; xlog_cxt->RedoRecPtr = InvalidXLogRecPtr; xlog_cxt->doPageWrites = false; xlog_cxt->RedoStartLSN = InvalidXLogRecPtr; @@ -1019,6 +1020,41 @@ static void knl_t_autovacuum_init(knl_t_autovacuum_context* autovacuum_cxt) autovacuum_cxt->last_read = 0; } +static void KnlTApplyLauncherInit(knl_t_apply_launcher_context* applyLauncherCxt) +{ + applyLauncherCxt->got_SIGHUP = false; + applyLauncherCxt->got_SIGUSR2 = false; + applyLauncherCxt->got_SIGTERM = false; + applyLauncherCxt->onCommitLauncherWakeup = false; + applyLauncherCxt->applyLauncherShm = NULL; +} + +static void KnlTApplyWorkerInit(knl_t_apply_worker_context* applyWorkerCxt) +{ + applyWorkerCxt->got_SIGHUP = false; + applyWorkerCxt->got_SIGTERM = false; + applyWorkerCxt->lsnMapping.head.next = &applyWorkerCxt->lsnMapping.head; + applyWorkerCxt->lsnMapping.head.prev = &applyWorkerCxt->lsnMapping.head; + applyWorkerCxt->logicalRepRelMap = NULL; + applyWorkerCxt->sendTime = 0; + applyWorkerCxt->lastRecvpos = InvalidXLogRecPtr; + applyWorkerCxt->lastWritepos = InvalidXLogRecPtr; + applyWorkerCxt->lastFlushpos = InvalidXLogRecPtr; + applyWorkerCxt->mySubscription = NULL; + applyWorkerCxt->mySubscriptionValid = false; + applyWorkerCxt->inRemoteTransaction = false; + applyWorkerCxt->curWorker = NULL; + applyWorkerCxt->messageContext = NULL; + applyWorkerCxt->logicalRepRelMapContext = NULL; + applyWorkerCxt->applyContext = NULL; +} + +static void KnlTPublicationInit(knl_t_publication_context* publicationCxt) +{ + publicationCxt->publications_valid = false; + publicationCxt->RelationSyncCache = NULL; +} + static void KnlTUndolauncherInit(knl_t_undolauncher_context* undolauncherCxt) { undolauncherCxt->got_SIGHUP = false; @@ -1683,6 +1719,9 @@ void knl_thread_init(knl_thread_role role) knl_t_security_policy_init(&t_thrd.security_policy_cxt); knl_t_security_ledger_init(&t_thrd.security_ledger_cxt); knl_t_bgworker_init(&t_thrd.bgworker_cxt); + KnlTApplyLauncherInit(&t_thrd.applylauncher_cxt); + KnlTApplyWorkerInit(&t_thrd.applyworker_cxt); + KnlTPublicationInit(&t_thrd.publication_cxt); #ifdef ENABLE_MOT knl_t_mot_init(&t_thrd.mot_cxt); #endif diff --git a/src/gausskernel/process/threadpool/threadpool_worker.cpp b/src/gausskernel/process/threadpool/threadpool_worker.cpp index 8ed5b5215..6d1383bed 100644 --- a/src/gausskernel/process/threadpool/threadpool_worker.cpp +++ b/src/gausskernel/process/threadpool/threadpool_worker.cpp @@ -710,6 +710,9 @@ void ThreadPoolWorker::AddBackend(Backend* bn) static void init_session_share_memory() { TableSpaceUsageManager::Init(); +#ifndef ENABLE_MULTIPLE_NODES + ReplicationOriginShmemInit(); +#endif } static bool InitSession(knl_session_context* session) diff --git a/src/gausskernel/runtime/executor/Makefile b/src/gausskernel/runtime/executor/Makefile index 89f7b9bff..707004059 100644 --- a/src/gausskernel/runtime/executor/Makefile +++ b/src/gausskernel/runtime/executor/Makefile @@ -34,7 +34,7 @@ ifneq "$(MAKECMDGOALS)" "clean" endif OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \ - execProcnode.o execQual.o execScan.o execTuples.o \ + execProcnode.o execQual.o execReplication.o execScan.o execTuples.o \ execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \ nodeBitmapAnd.o nodeBitmapOr.o \ nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \ diff --git a/src/gausskernel/runtime/executor/execMain.cpp b/src/gausskernel/runtime/executor/execMain.cpp index eeaee0331..5e74de9fb 100755 --- a/src/gausskernel/runtime/executor/execMain.cpp +++ b/src/gausskernel/runtime/executor/execMain.cpp @@ -1531,7 +1531,7 @@ void CheckValidResultRel(Relation resultRel, CmdType operation) (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot change ledger relation \"%s\"", RelationGetRelationName(resultRel)))); } - /* OK */ + CheckCmdReplicaIdentity(resultRel, operation); break; case RELKIND_SEQUENCE: ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/gausskernel/runtime/executor/execReplication.cpp b/src/gausskernel/runtime/executor/execReplication.cpp new file mode 100644 index 000000000..a864f1ab0 --- /dev/null +++ b/src/gausskernel/runtime/executor/execReplication.cpp @@ -0,0 +1,850 @@ +/* ------------------------------------------------------------------------- + * + * execReplication.cpp + * miscellaneous executor routines for logical replication + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/gausskernel/runtime/executor/execReplication.cpp + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/relscan.h" +#include "access/transam.h" +#include "access/tableam.h" +#include "access/xact.h" +#include "commands/trigger.h" +#include "commands/cluster.h" +#include "catalog/pg_partition_fn.h" +#include "executor/executor.h" +#include "executor/node/nodeModifyTable.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_relation.h" +#include "parser/parsetree.h" +#include "storage/buf/bufmgr.h" +#include "storage/lmgr.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + +static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation idxrel, LockTupleMode lockmode, + TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelPart); +static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, TupleTableSlot *searchslot, + TupleTableSlot *outslot, FakeRelationPartition *fakeRelPart); + +/* + * Setup a ScanKey for a search in the relation 'rel' for a tuple 'key' that + * is setup to match 'rel' (*NOT* idxrel!). + * + * Returns whether any column contains NULLs. + * + * This is not generic routine, it expects the idxrel to be replication + * identity of a rel and meet all limitations associated with that. + */ +static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel, TupleTableSlot *searchslot) +{ + int attoff; + bool isnull; + Datum indclassDatum; + oidvector *opclass; + int2vector *indkey = &idxrel->rd_index->indkey; + bool hasnulls = false; + + indclassDatum = SysCacheGetAttr(INDEXRELID, idxrel->rd_indextuple, Anum_pg_index_indclass, &isnull); + Assert(!isnull); + opclass = (oidvector *)DatumGetPointer(indclassDatum); + + /* Build scankey for every attribute in the index. */ + for (attoff = 0; attoff < IndexRelationGetNumberOfKeyAttributes(idxrel); attoff++) { + Oid op; + Oid opfamily; + RegProcedure regop; + int pkattno = attoff + 1; + int mainattno = indkey->values[attoff]; + Oid optype = get_opclass_input_type(opclass->values[attoff]); + + /* + * Load the operator info. We need this to get the equality operator + * function for the scan key. + */ + opfamily = get_opclass_family(opclass->values[attoff]); + + op = get_opfamily_member(opfamily, optype, optype, BTEqualStrategyNumber); + if (!OidIsValid(op)) + elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", BTEqualStrategyNumber, optype, optype, opfamily); + + regop = get_opcode(op); + + /* Initialize the scankey. */ + ScanKeyInit(&skey[attoff], pkattno, BTEqualStrategyNumber, regop, searchslot->tts_values[mainattno - 1]); + skey[attoff].sk_collation = idxrel->rd_indcollation[attoff]; + + /* Check for null value. */ + if (searchslot->tts_isnull[mainattno - 1]) { + hasnulls = true; + skey[attoff].sk_flags |= SK_ISNULL; + } + } + + return hasnulls; +} + +/* Check tableam_tuple_lock result, and return if need to retry */ +static bool inline CheckTupleLockRes(TM_Result res) +{ + switch (res) { + case TM_Ok: + break; + case TM_Updated: + /* XXX: Improve handling here */ + ereport(LOG, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("concurrent update, retrying"))); + return true; + case TM_Invisible: + elog(ERROR, "attempted to lock invisible tuple"); + break; + default: + elog(ERROR, "unexpected heap_lock_tuple status: %u", res); + break; + } + return false; +} + +/* Check heap modify result */ +static void inline CheckTupleModifyRes(TM_Result res) +{ + switch (res) { + case TM_SelfModified: + /* Tuple was already updated in current command? */ + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple already updated by self"))); + break; + case TM_Ok: + break; + case TM_Updated: + case TM_Deleted: + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple concurrently updated"))); + break; + default: + ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("unrecognized tuple status: %u", res))); + break; + } +} + +static bool PartitionFindReplTupleByIndex(EState *estate, Relation rel, Relation idxrel, LockTupleMode lockmode, + TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelInfo) +{ + /* must be non-GPI index */ + Assert(!RelationIsGlobalIndex(idxrel)); +#ifdef NOT_USED + if (RelationIsSubPartitioned(rel)) { + fakeRelInfo->partList = RelationGetSubPartitionList(rel, RowExclusiveLock); + } else +#endif + { + fakeRelInfo->partList = relationGetPartitionList(rel, RowExclusiveLock); + } + + /* search the tuple in partition list one by one */ + ListCell *cell = NULL; + foreach (cell, fakeRelInfo->partList) { + Partition heapPart = (Partition)lfirst(cell); + Relation partionRel = partitionGetRelation(rel, heapPart); + /* Get index partition of this heap partition */ + Oid idxPartOid = getPartitionIndexOid(RelationGetRelid(idxrel), heapPart->pd_id); + Partition idxPart = partitionOpen(idxrel, idxPartOid, RowExclusiveLock); + Relation idxPartRel = partitionGetRelation(idxrel, idxPart); + + fakeRelInfo->partRel = partionRel; + fakeRelInfo->part = heapPart; + fakeRelInfo->partOid = heapPart->pd_id; + + if (RelationFindReplTupleByIndex(estate, rel, idxPartRel, lockmode, searchslot, outslot, fakeRelInfo)) { + /* Hit, release index resource, heap partition need to be used later, so don't release it */ + partitionClose(idxrel, idxPart, NoLock); + releaseDummyRelation(&idxPartRel); + /* caller shoud release partRel */ + fakeRelInfo->needRleaseDummyRel = true; + return true; + } + + /* didn't find tuple in current partition, release dummy relation and switch to next partition */ + releaseDummyRelation(&fakeRelInfo->partRel); + partitionClose(idxrel, idxPart, NoLock); + releaseDummyRelation(&idxPartRel); + } + + /* do not find tuple in any patition, close and return */ + releasePartitionList(rel, &fakeRelInfo->partList, NoLock); + return false; +} + +static bool PartitionFindReplTupleSeq(Relation rel, LockTupleMode lockmode, + TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelInfo) +{ +#ifdef NOT_USED + if (RelationIsSubPartitioned(rel)) { + fakeRelInfo->partList = RelationGetSubPartitionList(rel, RowExclusiveLock); + } else +#endif + { + fakeRelInfo->partList = relationGetPartitionList(rel, RowExclusiveLock); + } + + ListCell *cell = NULL; + foreach (cell, fakeRelInfo->partList) { + Partition heapPart = (Partition)lfirst(cell); + Relation partionRel = partitionGetRelation(rel, heapPart); + + fakeRelInfo->partRel = partionRel; + fakeRelInfo->part = heapPart; + fakeRelInfo->partOid = heapPart->pd_id; + + if (RelationFindReplTupleSeq(rel, lockmode, searchslot, outslot, fakeRelInfo)) { + /* caller shoud release partRel */ + fakeRelInfo->needRleaseDummyRel = true; + return true; + } + releaseDummyRelation(&fakeRelInfo->partRel); + } + + /* do not find tuple in any patition, close and return */ + releasePartitionList(rel, &fakeRelInfo->partList, NoLock); + return false; +} + +/* + * Search the relation 'rel' for tuple using the index or seq scan. + * + * If a matching tuple is found, lock it with lockmode, fill the slot with its + * contents, and return true. Return false otherwise. + * + * Caller should check and release fakeRelInfo->partList and fakeRelInfo->partRel + */ +bool RelationFindReplTuple(EState *estate, Relation rel, Oid idxoid, LockTupleMode lockmode, + TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelInfo) +{ + int rc; + bool found = false; + Relation idxrel = NULL; + + /* clear fake rel info */ + rc = memset_s(fakeRelInfo, sizeof(FakeRelationPartition), 0, sizeof(FakeRelationPartition)); + securec_check(rc, "", ""); + + if (OidIsValid(idxoid)) { + idxrel = index_open(idxoid, RowExclusiveLock); + } + + /* for non partitioned table, or partitioned table with GPI, use parent heap and index to do the scan */ + if (RelationIsNonpartitioned(rel) || (idxrel != NULL && RelationIsGlobalIndex(idxrel))) { + if (idxrel != NULL) { + found = RelationFindReplTupleByIndex(estate, rel, idxrel, lockmode, searchslot, outslot, fakeRelInfo); + index_close(idxrel, NoLock); + return found; + } else { + return RelationFindReplTupleSeq(rel, lockmode, searchslot, outslot, fakeRelInfo); + } + } + + /* scan with partition */ + if (idxrel != NULL) { + found = PartitionFindReplTupleByIndex(estate, rel, idxrel, lockmode, searchslot, outslot, fakeRelInfo); + index_close(idxrel, NoLock); + return found; + } else { + return PartitionFindReplTupleSeq(rel, lockmode, searchslot, outslot, fakeRelInfo); + } +} + +/* + * Search the relation 'rel' for tuple using the index. + * + * If a matching tuple is found, lock it with lockmode, fill the slot with its + * contents, and return true. Return false otherwise. + */ +static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation idxrel, LockTupleMode lockmode, + TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelPart) +{ + HeapTuple scantuple; + ScanKeyData skey[INDEX_MAX_KEYS]; + IndexScanDesc scan; + SnapshotData snap; + TransactionId xwait; + Relation targetRel = NULL; + bool found; + int rc; + bool isGpi = RelationIsGlobalIndex(idxrel); + /* + * For GPI and non-partition table, use parent heap relation to search the tuple, + * otherwise use partition relation + */ + if (isGpi || RelationIsNonpartitioned(rel)) { + targetRel = rel; + } else { + targetRel = fakeRelPart->partRel; + } + Assert(targetRel != NULL); + /* Start an index scan. */ + InitDirtySnapshot(snap); + scan = scan_handler_idx_beginscan(targetRel, idxrel, &snap, + IndexRelationGetNumberOfKeyAttributes(idxrel), 0); + /* refer to check_violation, we need to set isUpsert if we want to use dirty snapshot in UStore */ + scan->isUpsert = true; + + /* Build scan key. */ + build_replindex_scan_key(skey, targetRel, idxrel, searchslot); + +retry: + found = false; + + scan_handler_idx_rescan(scan, skey, IndexRelationGetNumberOfKeyAttributes(idxrel), NULL, 0); + + /* Try to find the tuple */ + if (RelationIsUstoreFormat(targetRel)) { + found = IndexGetnextSlot(scan, ForwardScanDirection, outslot); + } else { + if ((scantuple = scan_handler_idx_getnext(scan, ForwardScanDirection)) != NULL) { + found = true; + ExecStoreTuple(scantuple, outslot, InvalidBuffer, false); + } + } + + /* Found tuple, try to lock it in the lockmode. */ + if (found) { + outslot->tts_tuple = ExecMaterializeSlot(outslot); + xwait = TransactionIdIsValid(snap.xmin) ? snap.xmin : snap.xmax; + /* + * If the tuple is locked, wait for locking transaction to finish + * and retry. + */ + if (TransactionIdIsValid(xwait)) { + XactLockTableWait(xwait); + goto retry; + } + + Buffer buf; + TM_FailureData hufd; + TM_Result res; + Tuple locktup; + HeapTupleData heaplocktup; + UHeapTupleData UHeaplocktup; + struct { + UHeapDiskTupleData hdr; + char data[MaxPossibleUHeapTupleSize]; + } tbuf; + ItemPointer tid = tableam_tops_get_t_self(targetRel, outslot->tts_tuple); + + if (RelationIsUstoreFormat(targetRel)) { + ItemPointerCopy(tid, &UHeaplocktup.ctid); + rc = memset_s(&tbuf, sizeof(tbuf), 0, sizeof(tbuf)); + securec_check(rc, "\0", "\0"); + UHeaplocktup.disk_tuple = &tbuf.hdr; + locktup = &UHeaplocktup; + } else { + ItemPointerCopy(tid, &heaplocktup.t_self); + locktup = &heaplocktup; + } + + /* Get the target tuple's partition for GPI */ + if (isGpi) { + GetFakeRelAndPart(estate, rel, outslot, fakeRelPart); + targetRel = fakeRelPart->partRel; + } + + PushActiveSnapshot(GetLatestSnapshot()); + res = tableam_tuple_lock(targetRel, + locktup, &buf, GetCurrentCommandId(false), lockmode, false, &hufd, + false, false, /* don't follow updates */ + false, /* eval */ + GetLatestSnapshot(), tid, /* ItemPointer */ + false); /* is select for update */ + /* the tuple slot already has the buffer pinned */ + ReleaseBuffer(buf); + PopActiveSnapshot(); + + if (CheckTupleLockRes(res)) { + goto retry; + } + } + + scan_handler_idx_endscan(scan); + return found; +} + +/* + * Compare the tuple and slot and check if they have equal values. + */ +static bool tuple_equals_slot(TupleDesc desc, const Tuple tup, TupleTableSlot *slot, TypeCacheEntry **eq) +{ + Datum values[MaxTupleAttributeNumber]; + bool isnull[MaxTupleAttributeNumber]; + int attrnum; + Form_pg_attribute att; + + tableam_tops_deform_tuple(tup, desc, values, isnull); + + /* Check equality of the attributes. */ + for (attrnum = 0; attrnum < desc->natts; attrnum++) { + TypeCacheEntry *typentry; + /* + * If one value is NULL and other is not, then they are certainly not + * equal + */ + if (isnull[attrnum] != slot->tts_isnull[attrnum]) + return false; + + /* + * If both are NULL, they can be considered equal. + */ + if (isnull[attrnum]) + continue; + + att = desc->attrs[attrnum]; + typentry = eq[attrnum]; + if (typentry == NULL) { + typentry = lookup_type_cache(att->atttypid, TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", format_type_be(att->atttypid)))); + } + eq[attrnum] = typentry; + } + + if (!DatumGetBool(FunctionCall2Coll(&typentry->eq_opr_finfo, att->attcollation, values[attrnum], + slot->tts_values[attrnum]))) { + return false; + } + } + + return true; +} + +/* + * Search the relation 'rel' for tuple using the sequential scan. + * + * If a matching tuple is found, lock it with lockmode, fill the slot with its + * contents, and return true. Return false otherwise. + * + * Note that this stops on the first matching tuple. + * + * This can obviously be quite slow on tables that have more than few rows. + */ +static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, TupleTableSlot *searchslot, + TupleTableSlot *outslot, FakeRelationPartition *fakeRelPart) +{ + Tuple scantuple; + TableScanDesc scan; + SnapshotData snap; + TypeCacheEntry **eq; + TransactionId xwait; + bool found; + int rc; + Relation targetRel = fakeRelPart->partRel == NULL ? rel : fakeRelPart->partRel; + TupleDesc desc = RelationGetDescr(rel); + + Assert(equalTupleDescs(desc, outslot->tts_tupleDescriptor)); + eq = (TypeCacheEntry **)palloc0(sizeof(*eq) * outslot->tts_tupleDescriptor->natts); + + /* Start a heap scan. */ + InitDirtySnapshot(snap); + scan = scan_handler_tbl_beginscan(targetRel, &snap, 0, NULL, NULL); + +retry: + found = false; + + scan_handler_tbl_rescan(scan, NULL, targetRel); + + /* Try to find the tuple */ + while ((scantuple = scan_handler_tbl_getnext(scan, ForwardScanDirection, targetRel)) != NULL) { + if (!tuple_equals_slot(desc, scantuple, searchslot, eq)) + continue; + + found = true; + ExecStoreTuple(scantuple, outslot, InvalidBuffer, false); + outslot->tts_tuple = ExecMaterializeSlot(outslot); + + xwait = TransactionIdIsValid(snap.xmin) ? snap.xmin : snap.xmax; + /* + * If the tuple is locked, wait for locking transaction to finish + * and retry. + */ + if (TransactionIdIsValid(xwait)) { + XactLockTableWait(xwait); + goto retry; + } + + /* Found our tuple and it's not locked */ + break; + } + + /* Found tuple, try to lock it in the lockmode. */ + if (found) { + Buffer buf; + TM_FailureData hufd; + TM_Result res; + Tuple locktup = NULL; + HeapTupleData heaplocktup; + UHeapTupleData UHeaplocktup; + struct { + UHeapDiskTupleData hdr; + char data[MaxPossibleUHeapTupleSize]; + } tbuf; + ItemPointer tid = tableam_tops_get_t_self(rel, outslot->tts_tuple); + + if (RelationIsUstoreFormat(targetRel)) { + ItemPointerCopy(tid, &UHeaplocktup.ctid); + rc = memset_s(&tbuf, sizeof(tbuf), 0, sizeof(tbuf)); + securec_check(rc, "\0", "\0"); + UHeaplocktup.disk_tuple = &tbuf.hdr; + locktup = &UHeaplocktup; + } else { + ItemPointerCopy(tid, &heaplocktup.t_self); + locktup = &heaplocktup; + } + + PushActiveSnapshot(GetLatestSnapshot()); + res = tableam_tuple_lock(targetRel, locktup, &buf, GetCurrentCommandId(false), lockmode, false, &hufd, false, + false, /* don't follow updates */ + false, /* eval */ + GetLatestSnapshot(), tid, /* ItemPointer */ + false); /* is select for update */ + + /* the tuple slot already has the buffer pinned */ + ReleaseBuffer(buf); + PopActiveSnapshot(); + + if (CheckTupleLockRes(res)) { + goto retry; + } + } + + scan_handler_tbl_endscan(scan); + + return found; +} + +/* + * Insert tuple represented in the slot to the relation, update the indexes, + * and execute any constraints and per-row triggers. + * + * Caller is responsible for opening the indexes. + */ +void ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot, FakeRelationPartition *relAndPart) +{ + Tuple tuple; + ResultRelInfo *resultRelInfo = estate->es_result_relation_info; + Relation rel = resultRelInfo->ri_RelationDesc; + Relation targetRel = relAndPart->partRel == NULL ? rel : relAndPart->partRel; + + /* For now we support only tables. */ + Assert(rel->rd_rel->relkind == RELKIND_RELATION); + + CheckCmdReplicaIdentity(rel, CMD_INSERT); + + /* BEFORE ROW INSERT Triggers */ + if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_before_row) { + slot = ExecBRInsertTriggers(estate, resultRelInfo, slot); + if (slot == NULL) { + /* "do nothing" */ + return; + } + } + + List *recheckIndexes = NIL; + /* Materialize slot into a tuple that we can scribble upon. */ + tuple = tableam_tslot_get_tuple_from_slot(rel, slot); + tableam_tops_update_tuple_with_oid(targetRel, tuple, slot); + + /* Compute stored generated columns */ + if (rel->rd_att->constr && rel->rd_att->constr->has_generated_stored) { + ExecComputeStoredGenerated(resultRelInfo, estate, slot, tuple, CMD_INSERT); + tuple = slot->tts_tuple; + } + + /* Check the constraints of the tuple */ + if (rel->rd_att->constr) + ExecConstraints(resultRelInfo, slot, estate); + + /* OK, store the tuple and create index entries for it */ + (void)tableam_tuple_insert(targetRel, tuple, GetCurrentCommandId(true), 0, NULL); + if (resultRelInfo->ri_NumIndices > 0) { + ItemPointer pTSelf = tableam_tops_get_t_self(rel, tuple); + recheckIndexes = + ExecInsertIndexTuples(slot, pTSelf, estate, targetRel, relAndPart->part, InvalidBktId, NULL, NULL); + } + /* AFTER ROW INSERT Triggers */ + ExecARInsertTriggers(estate, resultRelInfo, relAndPart->partOid, InvalidBktId, (HeapTuple)tuple, recheckIndexes); + + list_free_ext(recheckIndexes); +} + +/* + * Find the searchslot tuple and update it with data in the slot, + * update the indexes, and execute any constraints and per-row triggers. + * + * Caller is responsible for opening the indexes. + */ +void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot *searchslot, TupleTableSlot *slot, + FakeRelationPartition *relAndPart) +{ + bool allowInplaceUpdate = true; + Tuple tuple = NULL; + ResultRelInfo *resultRelInfo = estate->es_result_relation_info; + Relation rel = resultRelInfo->ri_RelationDesc; + ItemPointer searchSlotTid = tableam_tops_get_t_self(rel, searchslot->tts_tuple); + + /* For now we support only tables. */ + Assert(rel->rd_rel->relkind == RELKIND_RELATION); + + CheckCmdReplicaIdentity(rel, CMD_UPDATE); + + if ((resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_update_after_row) || + resultRelInfo->ri_RelationDesc->rd_mlogoid) { + allowInplaceUpdate = false; + } + + /* BEFORE ROW UPDATE Triggers */ + if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_update_before_row) { + slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, relAndPart->partOid, InvalidBktId, NULL, + searchSlotTid, slot); + if (slot == NULL) { + /* "do nothing" */ + return; + } + } + + /* Materialize slot into a tuple that we can scribble upon. */ + tuple = tableam_tslot_get_tuple_from_slot(rel, slot); + List *recheckIndexes = NIL; + Bitmapset *modifiedIdxAttrs = NULL; + TupleTableSlot *oldslot = NULL; + bool updateIndexes = false; + bool rowMovement = false; + TM_FailureData tmfd; + FakeRelationPartition newTupleInfo; + TM_Result res; + Relation targetRelation = relAndPart->partRel == NULL ? rel : relAndPart->partRel; + Relation parentRelation = relAndPart->partRel == NULL ? NULL : rel; + + /* Compute stored generated columns */ + if (rel->rd_att->constr && rel->rd_att->constr->has_generated_stored) { + ExecComputeStoredGenerated(resultRelInfo, estate, slot, tuple, CMD_UPDATE); + tuple = slot->tts_tuple; + } + + /* Check the constraints of the tuple */ + if (rel->rd_att->constr) { + ExecConstraints(resultRelInfo, slot, estate); + } + + /* check whether there is a row movement for partition table */ + GetFakeRelAndPart(estate, rel, slot, &newTupleInfo); + if (newTupleInfo.partOid != InvalidOid && newTupleInfo.partOid != relAndPart->partOid) { + if (!rel->rd_rel->relrowmovement) { + ereport(ERROR, (errmodule(MOD_EXECUTOR), (errcode(ERRCODE_S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED), + errmsg("fail to update partitioned table \"%s\"", RelationGetRelationName(rel)), + errdetail("disable row movement")))); + } + rowMovement = true; + } + + CommandId cid = GetCurrentCommandId(true); + /* OK, update the tuple and index entries for it */ + if (!rowMovement) { + res = tableam_tuple_update(targetRelation, parentRelation, searchSlotTid, slot->tts_tuple, cid, + InvalidSnapshot, estate->es_snapshot, true, &oldslot, &tmfd, &updateIndexes, &modifiedIdxAttrs, + false, allowInplaceUpdate); + CheckTupleModifyRes(res); + + if (updateIndexes && resultRelInfo->ri_NumIndices > 0) { + ExecIndexTuplesState exec_index_tuples_state; + exec_index_tuples_state.estate = estate; + exec_index_tuples_state.targetPartRel = RELATION_IS_PARTITIONED(rel) ? targetRelation : NULL; + exec_index_tuples_state.p = relAndPart->part; + exec_index_tuples_state.conflict = NULL; + recheckIndexes = tableam_tops_exec_update_index_tuples(slot, oldslot, targetRelation, NULL, tuple, + searchSlotTid, exec_index_tuples_state, InvalidBktId, modifiedIdxAttrs); + } + } else { + /* rowMovement, delete origin tuple and insert new */ + Assert(relAndPart->partRel != NULL); + Assert(newTupleInfo.partRel != NULL); + res = tableam_tuple_delete(relAndPart->partRel, searchSlotTid, cid, InvalidSnapshot, + estate->es_snapshot, true, &oldslot, &tmfd); + CheckTupleModifyRes(res); + + ExecIndexTuplesState exec_index_tuples_state; + exec_index_tuples_state.estate = estate; + exec_index_tuples_state.targetPartRel = relAndPart->partRel; + exec_index_tuples_state.p = relAndPart->part; + exec_index_tuples_state.conflict = NULL; + tableam_tops_exec_delete_index_tuples(oldslot, relAndPart->partRel, NULL, searchSlotTid, + exec_index_tuples_state, modifiedIdxAttrs); + + /* Insert new tuple */ + (void)tableam_tuple_insert(newTupleInfo.partRel, tuple, cid, 0, NULL); + if (resultRelInfo->ri_NumIndices > 0) { + ItemPointer pTSelf = tableam_tops_get_t_self(rel, tuple); + recheckIndexes = ExecInsertIndexTuples(slot, pTSelf, estate, newTupleInfo.partRel, + newTupleInfo.part, InvalidBktId, NULL, NULL); + } + } + + if (oldslot) { + ExecDropSingleTupleTableSlot(oldslot); + } + + /* AFTER ROW UPDATE Triggers */ + ExecARUpdateTriggers(estate, resultRelInfo, relAndPart->partOid, InvalidBktId, relAndPart->partOid, + searchSlotTid, (HeapTuple)tuple, NULL, recheckIndexes); + + list_free(recheckIndexes); +} + +/* + * Find the searchslot tuple and delete it, and execute any constraints + * and per-row triggers. + * + * Caller is responsible for opening the indexes. + */ +void ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, TupleTableSlot *searchslot, + FakeRelationPartition *relAndPart) +{ + bool skip_tuple = false; + ResultRelInfo *resultRelInfo = estate->es_result_relation_info; + Relation rel = resultRelInfo->ri_RelationDesc; + ItemPointer tid = tableam_tops_get_t_self(rel, searchslot->tts_tuple); + + /* For now we support only tables. */ + Assert(rel->rd_rel->relkind == RELKIND_RELATION); + + CheckCmdReplicaIdentity(rel, CMD_DELETE); + + /* BEFORE ROW INSERT Triggers */ + if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_delete_before_row) { + skip_tuple = + !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo, relAndPart->partOid, InvalidBktId, NULL, tid); + if (skip_tuple) { + return; + } + } + + TupleTableSlot *oldslot = NULL; + Relation targetRel = relAndPart->partRel == NULL ? rel : relAndPart->partRel; + TM_FailureData tmfd; + + /* OK, delete the tuple */ + TM_Result res = tableam_tuple_delete(targetRel, tid, GetCurrentCommandId(true), InvalidSnapshot, + estate->es_snapshot, true, &oldslot, &tmfd); + CheckTupleModifyRes(res); + + Bitmapset *modifiedIdxAttrs = NULL; + ExecIndexTuplesState exec_index_tuples_state; + exec_index_tuples_state.estate = estate; + exec_index_tuples_state.targetPartRel = RELATION_IS_PARTITIONED(rel) ? relAndPart->partRel : NULL; + exec_index_tuples_state.p = relAndPart->part; + exec_index_tuples_state.conflict = NULL; + tableam_tops_exec_delete_index_tuples(oldslot, targetRel, NULL, tid, exec_index_tuples_state, modifiedIdxAttrs); + if (oldslot) { + ExecDropSingleTupleTableSlot(oldslot); + } + + /* AFTER ROW DELETE Triggers */ + ExecARDeleteTriggers(estate, resultRelInfo, relAndPart->partOid, InvalidBktId, NULL, tid); +} + +/* + * Check if command can be executed with current replica identity. + */ +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 (RelationGetRelReplident(rel) == 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 (cmd == CMD_UPDATE && pubactions->pubupdate) { + ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot update table \"%s\" because it does not have a replica identity and publishes updates", + RelationGetRelationName(rel)), + errhint("To enable updating the table, set REPLICA IDENTITY using ALTER TABLE."))); + } else if (cmd == CMD_DELETE && pubactions->pubdelete) { + ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot delete from table \"%s\" because it does not have a replica identity and publishes deletes", + RelationGetRelationName(rel)), + errhint("To enable deleting from the table, set REPLICA IDENTITY using ALTER TABLE."))); + } +} + +void GetFakeRelAndPart(EState *estate, Relation rel, TupleTableSlot *slot, FakeRelationPartition *relAndPart) +{ + relAndPart->partRel = NULL; + relAndPart->part = NULL; + relAndPart->partOid = InvalidOid; + + if (RelationIsNonpartitioned(rel)) { + return; + } + + Relation partRelation = NULL; + Partition partition = NULL; + Oid partitionOid; + Tuple tuple = tableam_tslot_get_tuple_from_slot(rel, slot); + switch (rel->rd_rel->parttype) { + case PARTTYPE_NON_PARTITIONED_RELATION: + case PARTTYPE_VALUE_PARTITIONED_RELATION: + break; + case PARTTYPE_PARTITIONED_RELATION: + partitionOid = heapTupleGetPartitionId(rel, tuple); + searchFakeReationForPartitionOid(estate->esfRelations, estate->es_query_cxt, rel, partitionOid, + partRelation, partition, RowExclusiveLock); + relAndPart->partRel = partRelation; + relAndPart->part = partition; + relAndPart->partOid = partitionOid; + break; +#ifdef NOT_USED + case PARTTYPE_SUBPARTITIONED_RELATION: { + Relation subPartRel = NULL; + Partition subPart = NULL; + Oid subPartOid; + partitionOid = heapTupleGetPartitionId(rel, tuple); + searchFakeReationForPartitionOid(estate->esfRelations, estate->es_query_cxt, rel, partitionOid, + partRelation, partition, RowExclusiveLock); + subPartOid = heapTupleGetPartitionId(partRelation, tuple); + searchFakeReationForPartitionOid(estate->esfRelations, estate->es_query_cxt, partRelation, subPartOid, + subPartRel, subPart, RowExclusiveLock); + + relAndPart->partRel = subPartRel; + relAndPart->part = subPart; + relAndPart->partOid = subPartOid; + break; + } +#endif + default: + ereport(ERROR, (errmodule(MOD_EXECUTOR), + (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("Unrecognized parttype as \"%c\" for relation \"%s\"", + rel->rd_rel->parttype, RelationGetRelationName(rel))))); + break; + } +} diff --git a/src/gausskernel/storage/access/rmgrdesc/Makefile b/src/gausskernel/storage/access/rmgrdesc/Makefile index 41dc8fcb3..d0972cfab 100644 --- a/src/gausskernel/storage/access/rmgrdesc/Makefile +++ b/src/gausskernel/storage/access/rmgrdesc/Makefile @@ -13,12 +13,12 @@ endif ifeq ($(enable_mot), yes) OBJS = barrierdesc.o clogdesc.o dbasedesc.o gindesc.o gistdesc.o \ hashdesc.o heapdesc.o motdesc.o mxactdesc.o nbtdesc.o relmapdesc.o \ - seqdesc.o smgrdesc.o spgdesc.o standbydesc.o tblspcdesc.o \ + replorigindesc.o seqdesc.o smgrdesc.o spgdesc.o standbydesc.o tblspcdesc.o \ xactdesc.o xlogdesc.o slotdesc.o undologdesc.o uheapdesc.o segpagedesc.o else OBJS = barrierdesc.o clogdesc.o dbasedesc.o gindesc.o gistdesc.o \ hashdesc.o heapdesc.o mxactdesc.o nbtdesc.o relmapdesc.o \ - seqdesc.o smgrdesc.o spgdesc.o standbydesc.o tblspcdesc.o \ + replorigindesc.o seqdesc.o smgrdesc.o spgdesc.o standbydesc.o tblspcdesc.o \ xactdesc.o xlogdesc.o slotdesc.o undologdesc.o uheapdesc.o segpagedesc.o endif diff --git a/src/gausskernel/storage/access/rmgrdesc/replorigindesc.cpp b/src/gausskernel/storage/access/rmgrdesc/replorigindesc.cpp new file mode 100644 index 000000000..d5c3783f6 --- /dev/null +++ b/src/gausskernel/storage/access/rmgrdesc/replorigindesc.cpp @@ -0,0 +1,41 @@ +/* ------------------------------------------------------------------------- + * + * replorigindesc.cpp + * rmgr descriptor routines for replication/logical/origin.cpp + * + * Portions Copyright (c) 2021, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/gausskernel/storage/access/rmgrdesc/replorigindesc.cpp + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "replication/origin.h" + +void replorigin_desc(StringInfo buf, XLogReaderState *record) +{ + char *rec = XLogRecGetData(record); + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + + switch (info) { + case XLOG_REPLORIGIN_SET: { + xl_replorigin_set *xlrec = (xl_replorigin_set *)rec; + + appendStringInfo(buf, "set %u; lsn %X/%X; force: %d", xlrec->node_id, + (uint32)(xlrec->remote_lsn >> BITS_PER_INT), (uint32)xlrec->remote_lsn, xlrec->force); + break; + } + case XLOG_REPLORIGIN_DROP: { + xl_replorigin_drop *xlrec = (xl_replorigin_drop *)rec; + + appendStringInfo(buf, "drop %u", xlrec->node_id); + break; + } + default: { + break; + } + } +} diff --git a/src/gausskernel/storage/access/rmgrdesc/xactdesc.cpp b/src/gausskernel/storage/access/rmgrdesc/xactdesc.cpp index 00fa8cbbe..950dda4e2 100644 --- a/src/gausskernel/storage/access/rmgrdesc/xactdesc.cpp +++ b/src/gausskernel/storage/access/rmgrdesc/xactdesc.cpp @@ -63,9 +63,10 @@ static void desc_library(StringInfo buf, char *filename, int nlibrary) } } -static void xact_desc_commit(StringInfo buf, xl_xact_commit *xlrec) +static void xact_desc_commit(StringInfo buf, xl_xact_commit *xlrec, RepOriginId origin_id) { int i; + int nsubxacts = xlrec->nsubxacts; TransactionId *subxacts = NULL; subxacts = (TransactionId *)&xlrec->xnodes[xlrec->nrels]; @@ -135,16 +136,25 @@ static void xact_desc_commit(StringInfo buf, xl_xact_commit *xlrec) SharedInvalidationMessage* msgs = (SharedInvalidationMessage*)&subxacts[xlrec->nsubxacts]; TransactionId* recentXmin = (TransactionId *)&(msgs[xlrec->nmsgs]); appendStringInfo(buf, "; RecentXmin:%lu", *recentXmin); + nsubxacts++; #endif if (xlrec->nlibrary > 0) { char *filename = NULL; filename = (char *)xlrec->xnodes + (xlrec->nrels * sizeof(ColFileNodeRel)) + - (xlrec->nsubxacts * sizeof(TransactionId)) + (xlrec->nmsgs * sizeof(SharedInvalidationMessage)); + (nsubxacts * sizeof(TransactionId)) + (xlrec->nmsgs * sizeof(SharedInvalidationMessage)); desc_library(buf, filename, xlrec->nlibrary); } + + if (xlrec->xinfo & XACT_HAS_ORIGIN) { + xl_xact_origin *origin = (xl_xact_origin *)GetRepOriginPtr((char *)xlrec->xnodes, xlrec->xinfo, + xlrec->nsubxacts, xlrec->nmsgs, xlrec->nrels, xlrec->nlibrary); + appendStringInfo(buf, "; origin: node %u, lsn %X/%X, at %s", origin_id, + (uint32)(origin->origin_lsn >> BITS_PER_INT), + (uint32)origin->origin_lsn, timestamptz_to_str(origin->origin_timestamp)); + } } static void xact_desc_commit_compact(StringInfo buf, xl_xact_commit_compact *xlrec) @@ -231,7 +241,7 @@ void xact_desc(StringInfo buf, XLogReaderState *record) xl_xact_commit *xlrec = (xl_xact_commit *)rec; appendStringInfo(buf, "XLOG_XACT_COMMIT commit: "); - xact_desc_commit(buf, xlrec); + xact_desc_commit(buf, xlrec, XLogRecGetOrigin(record)); } else if (info == XLOG_XACT_ABORT) { xl_xact_abort *xlrec = (xl_xact_abort *)rec; @@ -248,7 +258,7 @@ void xact_desc(StringInfo buf, XLogReaderState *record) xl_xact_commit_prepared *xlrec = (xl_xact_commit_prepared *)rec; appendStringInfo(buf, "commit prepared " XID_FMT ": ", xlrec->xid); - xact_desc_commit(buf, &xlrec->crec); + xact_desc_commit(buf, &xlrec->crec, XLogRecGetOrigin(record)); } else if (info == XLOG_XACT_ABORT_PREPARED) { xl_xact_abort_prepared *xlrec = (xl_xact_abort_prepared *)rec; diff --git a/src/gausskernel/storage/access/transam/twophase.cpp b/src/gausskernel/storage/access/transam/twophase.cpp index 312c24eab..293995fb9 100644 --- a/src/gausskernel/storage/access/transam/twophase.cpp +++ b/src/gausskernel/storage/access/transam/twophase.cpp @@ -163,7 +163,6 @@ int PendingPreparedXactsCount = 0; * typedef struct GlobalTransactionData *GlobalTransaction appears in * twophase.h */ -static int read_library(char *bufptr, int nlibrary); static void RecordTransactionCommitPrepared(TransactionId xid, int nchildren, TransactionId *children, int nrels, ColFileNodeRel *rels, int ninvalmsgs, SharedInvalidationMessage *invalmsgs, int nlibrary, char *librarys, int libraryLen, bool initfileinval); @@ -2768,31 +2767,6 @@ void FinishPreparedTransaction(const char *gid, bool isCommit) buf = NULL; } -/* - * @Description: Read library file length. - * @in bufptr: Library ptr head. - * @in nlibrary: Library number. - * @return: Library length. - */ -static int read_library(char *bufptr, int nlibrary) -{ - int nlib = nlibrary; - int over_length = 0; - char *ptr = bufptr; - - while (nlib > 0) { - int libraryLen = 0; - errno_t rc = memcpy_s(&libraryLen, sizeof(int), ptr, sizeof(int)); - securec_check_c(rc, "", ""); - - over_length += (sizeof(int) + libraryLen); - ptr += (sizeof(int) + libraryLen); - nlib--; - } - - return over_length; -} - /* * @Description: Parse library name from library, skip int byte. * @in library: Source string. diff --git a/src/gausskernel/storage/access/transam/xact.cpp b/src/gausskernel/storage/access/transam/xact.cpp index c6622e10e..a74a386bc 100755 --- a/src/gausskernel/storage/access/transam/xact.cpp +++ b/src/gausskernel/storage/access/transam/xact.cpp @@ -70,8 +70,11 @@ #include "replication/datasyncrep.h" #include "replication/datasender.h" #include "replication/dataqueue.h" +#include "replication/logical.h" +#include "replication/logicallauncher.h" #include "replication/walsender.h" #include "replication/syncrep.h" +#include "replication/origin.h" #include "storage/lmgr.h" #include "storage/predicate.h" #include "storage/procarray.h" @@ -1773,8 +1776,14 @@ static TransactionId RecordTransactionCommit(void) * invalidation messages, that's more extensible and degrades more * gracefully. Till then, it's just 20 bytes of overhead. */ +#ifdef ENABLE_MULTIPLE_NODES + bool hasOrigin = false; +#else + bool hasOrigin = u_sess->reporigin_cxt.originId != InvalidRepOriginId && + u_sess->reporigin_cxt.originId != DoNotReplicateId; +#endif if (nrels > 0 || nmsgs > 0 || RelcacheInitFileInval || t_thrd.xact_cxt.forceSyncCommit || - XLogLogicalInfoActive()) { + XLogLogicalInfoActive() || hasOrigin) { xl_xact_commit xlrec; /* Set flags required for recovery processing of commits. */ @@ -1788,6 +1797,9 @@ static TransactionId RecordTransactionCommit(void) xlrec.xinfo |= XACT_MOT_ENGINE_USED; } #endif + if (hasOrigin) { + xlrec.xinfo |= XACT_HAS_ORIGIN; + } xlrec.dbId = u_sess->proc_cxt.MyDatabaseId; xlrec.tsId = u_sess->proc_cxt.MyDatabaseTableSpace; @@ -1831,6 +1843,13 @@ static TransactionId RecordTransactionCommit(void) XLogRegisterData((char *)library_name, library_length); } + if (hasOrigin) { + xl_xact_origin origin; + origin.origin_lsn = u_sess->reporigin_cxt.originLsn; + origin.origin_timestamp = u_sess->reporigin_cxt.originTs; + XLogRegisterData((char*)&origin, sizeof(xl_xact_origin)); + } + /* we allow filtering by xacts */ XLogIncludeOrigin(); @@ -1845,6 +1864,10 @@ static TransactionId RecordTransactionCommit(void) LWLockRelease(DelayDDLLock); } + + if (hasOrigin) { + replorigin_session_advance(u_sess->reporigin_cxt.originLsn, t_thrd.xlog_cxt.XactLastRecEnd); + } } else { xl_xact_commit_compact xlrec; @@ -1964,6 +1987,9 @@ static TransactionId RecordTransactionCommit(void) /* Compute latestXid while we have the child XIDs handy */ latestXid = TransactionIdLatest(xid, nchildren, children); + /* remember end of last commit record */ + t_thrd.xlog_cxt.XactLastCommitEnd = t_thrd.xlog_cxt.XactLastRecEnd; + /* Reset XactLastRecEnd until the next transaction writes something */ t_thrd.xlog_cxt.XactLastRecEnd = 0; @@ -3206,6 +3232,8 @@ static void CommitTransaction(bool STP_commit) #endif AtEOXact_Snapshot(true); + AtEOXact_ApplyLauncher(true); + pgstat_report_xact_timestamp(0); t_thrd.utils_cxt.CurrentResourceOwner = NULL; @@ -4220,6 +4248,7 @@ static void AbortTransaction(bool PerfectRollback, bool STP_rollback) AtEOXact_ComboCid(); AtEOXact_HashTables(false); AtEOXact_PgStat(false); + AtEOXact_ApplyLauncher(false); #ifdef DEBUG_UHEAP AtEOXact_UHeapStats(); @@ -7438,7 +7467,7 @@ static void unlink_relfiles(_in_ ColFileNodeRel *xnodes, _in_ int nrels) static void xact_redo_commit_internal(TransactionId xid, XLogRecPtr lsn, TransactionId* sub_xids, int nsubxacts, SharedInvalidationMessage* inval_msgs, int nmsgs, ColFileNodeRel* xnodes, int nrels, int nlibrary, Oid dbId, Oid tsId, uint32 xinfo, - uint64 csn, TransactionId newStandbyXmin) + uint64 csn, TransactionId newStandbyXmin, RepOriginId originId) { TransactionId max_xid; XLogRecPtr globalDelayDDLLSN; @@ -7616,6 +7645,13 @@ static void xact_redo_commit_internal(TransactionId xid, XLogRecPtr lsn, Transac parseAndRemoveLibrary(filename, nlibrary); } + if (xinfo & XACT_HAS_ORIGIN) { + xl_xact_origin *origin = (xl_xact_origin *)GetRepOriginPtr((char*)xnodes, xinfo, nsubxacts, + nmsgs, nrels, nlibrary); + /* recover apply progress */ + replorigin_advance(originId, origin->origin_lsn, lsn, false, false); + } + /* * We issue an XLogFlush() for the same reason we emit ForceSyncCommit() * in normal operation. For example, in CREATE DATABASE, we copy all files @@ -7648,7 +7684,7 @@ static void xact_redo_commit_internal(TransactionId xid, XLogRecPtr lsn, Transac /* * Utility function to call xact_redo_commit_internal after breaking down xlrec */ -static void xact_redo_commit(xl_xact_commit *xlrec, TransactionId xid, XLogRecPtr lsn) +static void xact_redo_commit(xl_xact_commit *xlrec, TransactionId xid, XLogRecPtr lsn, RepOriginId originId) { TransactionId* subxacts = NULL; SharedInvalidationMessage* inval_msgs = NULL; @@ -7679,7 +7715,8 @@ static void xact_redo_commit(xl_xact_commit *xlrec, TransactionId xid, XLogRecPt xlrec->tsId, xlrec->xinfo, xlrec->csn, - newStandbyXmin); + newStandbyXmin, + originId); } /* @@ -7709,7 +7746,8 @@ static void xact_redo_commit_compact(xl_xact_commit_compact *xlrec, TransactionI InvalidOid, /* tsId */ 0, /* xinfo */ xlrec->csn, /* csn */ - globalXmin); /* recent_xmin */ + globalXmin, /* recent_xmin */ + InvalidRepOriginId); } /* @@ -7820,7 +7858,7 @@ void xact_redo(XLogReaderState *record) xact_redo_commit_compact(xlrec, XLogRecGetXid(record), lsn); } else if (info == XLOG_XACT_COMMIT) { xl_xact_commit *xlrec = (xl_xact_commit *)XLogRecGetData(record); - xact_redo_commit(xlrec, XLogRecGetXid(record), lsn); + xact_redo_commit(xlrec, XLogRecGetXid(record), lsn, XLogRecGetOrigin(record)); } else if (info == XLOG_XACT_ABORT || info == XLOG_XACT_ABORT_WITH_XID) { xl_xact_abort *xlrec = (xl_xact_abort *)XLogRecGetData(record); @@ -7843,7 +7881,7 @@ void xact_redo(XLogReaderState *record) } else if (info == XLOG_XACT_COMMIT_PREPARED) { xl_xact_commit_prepared *xlrec = (xl_xact_commit_prepared *)XLogRecGetData(record); - xact_redo_commit(&xlrec->crec, xlrec->xid, lsn); + xact_redo_commit(&xlrec->crec, xlrec->xid, lsn, XLogRecGetOrigin(record)); /* Delete TwoPhaseState gxact entry and/or 2PC file. */ (void)TWOPAHSE_LWLOCK_ACQUIRE(xlrec->xid, LW_EXCLUSIVE); diff --git a/src/gausskernel/storage/access/transam/xlog.cpp b/src/gausskernel/storage/access/transam/xlog.cpp index 5cfa31410..6e94ac217 100755 --- a/src/gausskernel/storage/access/transam/xlog.cpp +++ b/src/gausskernel/storage/access/transam/xlog.cpp @@ -83,6 +83,7 @@ #include "replication/datasender.h" #include "replication/dataqueue.h" #include "replication/dcf_data.h" +#include "replication/origin.h" #include "replication/reorderbuffer.h" #include "replication/replicainternal.h" #include "replication/slot.h" @@ -10674,6 +10675,13 @@ void StartupXLOG(void) * Perform end of recovery actions for any SLRUs that need it. */ StartupMultiXact(); + + /* + * Recover knowledge about replay progress of known replication partners. + */ +#ifndef ENABLE_MULTIPLE_NODES + StartupReplicationOrigin(); +#endif TrimCLOG(); /* Reload shared-memory state for prepared transactions */ @@ -12319,6 +12327,9 @@ static void CheckPointGuts(XLogRecPtr checkPointRedo, int flags, bool doFullChec * need wait pagewriter thread flush dirty page. */ CheckPointBuffers(flags, doFullCheckpoint); /* performs all required fsyncs */ +#ifndef ENABLE_MULTIPLE_NODES + CheckPointReplicationOrigin(); +#endif /* We deliberately delay 2PC checkpointing as long as possible */ CheckPointTwoPhase(checkPointRedo); undo::CheckPointUndoSystemMeta(checkPointRedo); diff --git a/src/gausskernel/storage/access/transam/xloginsert.cpp b/src/gausskernel/storage/access/transam/xloginsert.cpp index a840e42ea..01dd7234c 100755 --- a/src/gausskernel/storage/access/transam/xloginsert.cpp +++ b/src/gausskernel/storage/access/transam/xloginsert.cpp @@ -62,8 +62,11 @@ typedef struct registered_buffer { bool encrypt; } registered_buffer; +#define SizeOfXlogOrigin (sizeof(RepOriginId) + sizeof(char)) + #define HEADER_SCRATCH_SIZE \ - (SizeOfXLogRecord + MaxSizeOfXLogRecordBlockHeader * (XLR_MAX_BLOCK_ID + 1) + SizeOfXLogRecordDataHeaderLong) + (SizeOfXLogRecord + MaxSizeOfXLogRecordBlockHeader * (XLR_MAX_BLOCK_ID + 1) + \ + SizeOfXLogRecordDataHeaderLong + SizeOfXlogOrigin) static XLogRecData *XLogRecordAssemble(RmgrId rmid, uint8 info, XLogFPWInfo fpw_info, XLogRecPtr *fpw_lsn, bool isupgrade = false, int bucket_id = -1); @@ -903,11 +906,15 @@ static XLogRecData *XLogRecordAssemble(RmgrId rmid, uint8 info, XLogFPWInfo fpw_ XLOG_ASSEMBLE_ONE_ITEM(scratch, sizeof(XLogRecPtr), ®buf->lastLsn, remained_size); } +#ifndef ENABLE_MULTIPLE_NODES + int m_session_id = u_sess->reporigin_cxt.originId; +#else int m_session_id = u_sess->attr.attr_storage.replorigin_sesssion_origin; +#endif bool m_include_origin = t_thrd.xlog_cxt.include_origin; /* followed by the record's origin, if any */ - if (m_session_id == 0 && + if (m_session_id == InvalidRepOriginId && (u_sess->attr.attr_sql.enable_cluster_resize || t_thrd.role == AUTOVACUUM_WORKER)) { m_include_origin = true; m_session_id = 1; diff --git a/src/gausskernel/storage/access/transam/xlogreader.cpp b/src/gausskernel/storage/access/transam/xlogreader.cpp index be095a5b7..bcab3242e 100644 --- a/src/gausskernel/storage/access/transam/xlogreader.cpp +++ b/src/gausskernel/storage/access/transam/xlogreader.cpp @@ -1807,3 +1807,43 @@ void CloseXlogFile(void) } return; } + +/* + * @Description: Read library file length. + * @in bufptr: Library ptr head. + * @in nlibrary: Library number. + * @return: Library length. + */ +int read_library(char *bufptr, int nlibrary) +{ + int nlib = nlibrary; + int over_length = 0; + char *ptr = bufptr; + + while (nlib > 0) { + int libraryLen = 0; + errno_t rc = memcpy_s(&libraryLen, sizeof(int), ptr, sizeof(int)); + securec_check_c(rc, "", ""); + + over_length += (sizeof(int) + libraryLen); + ptr += (sizeof(int) + libraryLen); + nlib--; + } + + return over_length; +} + +char *GetRepOriginPtr(char *xnodes, uint64 xinfo, int nsubxacts, int nmsgs, int nrels, int nlibrary) +{ + if (!(xinfo & XACT_HAS_ORIGIN)) { + return NULL; + } +#ifndef ENABLE_MULTIPLE_NODES + /* One more recent_xmin for single node */ + nsubxacts++; +#endif + char *libPtr = xnodes + (nrels * sizeof(ColFileNodeRel)) + + (nsubxacts * sizeof(TransactionId)) + (nmsgs * sizeof(SharedInvalidationMessage)); + int libLen = read_library(libPtr, nlibrary); + return (libPtr + libLen); +} diff --git a/src/gausskernel/storage/access/transam/xlogutils.cpp b/src/gausskernel/storage/access/transam/xlogutils.cpp index c19ec6dfd..b3b4307c0 100644 --- a/src/gausskernel/storage/access/transam/xlogutils.cpp +++ b/src/gausskernel/storage/access/transam/xlogutils.cpp @@ -1463,3 +1463,4 @@ int read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int r /* number of valid bytes in the buffer */ return count; } + diff --git a/src/gausskernel/storage/ipc/ipci.cpp b/src/gausskernel/storage/ipc/ipci.cpp index c5076f830..4cd092ddd 100644 --- a/src/gausskernel/storage/ipc/ipci.cpp +++ b/src/gausskernel/storage/ipc/ipci.cpp @@ -54,6 +54,8 @@ #include "replication/datareceiver.h" #include "replication/datasender.h" #include "replication/dataqueue.h" +#include "replication/origin.h" +#include "replication/logicallauncher.h" #include "storage/buf/bufmgr.h" #include "storage/smgr/fd.h" #include "storage/ipc.h" @@ -114,6 +116,10 @@ Size ComputeTotalSizeOfShmem() size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE, sizeof(ShmemIndexEnt))); size = add_size(size, BufferShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); +#ifndef ENABLE_MULTIPLE_NODES + size = add_size(size, ReplicationOriginShmemSize()); + size = add_size(size, ApplyLauncherShmemSize()); +#endif size = add_size(size, LockShmemSize()); size = add_size(size, PredicateLockShmemSize()); size = add_size(size, ProcGlobalShmemSize()); @@ -332,6 +338,10 @@ void CreateSharedMemoryAndSemaphores(bool makePrivate, int port) RbCleanerShmemInit(); } ReplicationSlotsShmemInit(); +#ifndef ENABLE_MULTIPLE_NODES + ReplicationOriginShmemInit(); + ApplyLauncherShmemInit(); +#endif WalSndShmemInit(); /* * Set up WAL semaphores. This must be done after WalSndShmemInit(). diff --git a/src/gausskernel/storage/lmgr/lwlock.cpp b/src/gausskernel/storage/lmgr/lwlock.cpp index e11f69c95..2319847b2 100644 --- a/src/gausskernel/storage/lmgr/lwlock.cpp +++ b/src/gausskernel/storage/lmgr/lwlock.cpp @@ -181,7 +181,8 @@ static const char *BuiltinTrancheNames[] = { "WALInitSegment", "SegmentHeadPartitionLock", "TwoPhaseStatePartLock", - "RoleIdPartLock" + "RoleIdPartLock", + "ReplicationOriginLock" }; static void RegisterLWLockTranches(void); diff --git a/src/gausskernel/storage/lmgr/lwlocknames.txt b/src/gausskernel/storage/lmgr/lwlocknames.txt index 0a790771d..6edca4cd4 100755 --- a/src/gausskernel/storage/lmgr/lwlocknames.txt +++ b/src/gausskernel/storage/lmgr/lwlocknames.txt @@ -120,4 +120,6 @@ PldebugLock 110 HadrSwitchoverLock 111 TDEKeyCacheLock 112 RoleIdLock 113 -UniqueSqlEvictLock 114 \ No newline at end of file +UniqueSqlEvictLock 114 +ReplicationOriginLock 115 +LogicalRepWorkerLock 116 \ No newline at end of file diff --git a/src/gausskernel/storage/lmgr/proc.cpp b/src/gausskernel/storage/lmgr/proc.cpp index d3be94f16..c31d68fef 100755 --- a/src/gausskernel/storage/lmgr/proc.cpp +++ b/src/gausskernel/storage/lmgr/proc.cpp @@ -500,7 +500,7 @@ static inline void ReleaseChildSlot(void) IsJobAspProcess() || t_thrd.role == STREAMING_BACKEND || IsStatementFlushProcess() || IsJobSnapshotProcess() || t_thrd.postmaster_cxt.IsRPCWorkerThread || IsJobPercentileProcess() || t_thrd.role == ARCH || IsTxnSnapCapturerProcess() || IsTxnSnapWorkerProcess() || IsRbCleanerProcess() || IsRbWorkerProcess() || t_thrd.role == GLOBALSTATS_THREAD || - t_thrd.role == BARRIER_ARCH || t_thrd.role == BARRIER_CREATOR)) { + t_thrd.role == BARRIER_ARCH || t_thrd.role == BARRIER_CREATOR || t_thrd.role == APPLY_LAUNCHER)) { (void)ReleasePostmasterChildSlot(t_thrd.proc_cxt.MyPMChildSlot); } } diff --git a/src/gausskernel/storage/replication/CMakeLists.txt b/src/gausskernel/storage/replication/CMakeLists.txt index b4fe84e4f..eee30d260 100755 --- a/src/gausskernel/storage/replication/CMakeLists.txt +++ b/src/gausskernel/storage/replication/CMakeLists.txt @@ -61,6 +61,7 @@ set(TGT_replication_SRC ${CMAKE_CURRENT_SOURCE_DIR}/walsender.cpp ${CMAKE_CURRENT_SOURCE_DIR}/repl_gram.cpp ${CMAKE_CURRENT_SOURCE_DIR}/syncrep_gram.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/subscription_walreceiver.cpp ) set(TGT_replication_INC @@ -82,9 +83,11 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/heartbeat ${CMAKE_CURRENT_SOURCE_DIR}/dcf ${CMAKE_CURRENT_SOURCE_DIR}/logical + ${CMAKE_CURRENT_SOURCE_DIR}/pgoutput ) add_subdirectory(heartbeat) add_subdirectory(dcf) add_subdirectory(logical) +add_subdirectory(pgoutput) diff --git a/src/gausskernel/storage/replication/Makefile b/src/gausskernel/storage/replication/Makefile index 817d6ee71..5dc12f7ae 100644 --- a/src/gausskernel/storage/replication/Makefile +++ b/src/gausskernel/storage/replication/Makefile @@ -13,7 +13,7 @@ ifneq "$(MAKECMDGOALS)" "clean" endif endif endif -OBJS = walsender.o datasender.o walreceiverfuncs.o walreceiver.o walrcvwriter.o\ +OBJS = walsender.o datasender.o walreceiverfuncs.o walreceiver.o walrcvwriter.o subscription_walreceiver.o\ datareceiver.o datarcvwriter.o basebackup.o libpqwalreceiver.o obswalreceiver.o repl_gram.o\ syncrep.o dataqueue.o bcm.o datasyncrep.o catchup.o slot.o slotfuncs.o \ syncrep_gram.o heartbeat.o rto_statistic.o diff --git a/src/gausskernel/storage/replication/libpqwalreceiver.cpp b/src/gausskernel/storage/replication/libpqwalreceiver.cpp index ec9fdc6d2..e475256ac 100755 --- a/src/gausskernel/storage/replication/libpqwalreceiver.cpp +++ b/src/gausskernel/storage/replication/libpqwalreceiver.cpp @@ -53,6 +53,7 @@ static void ha_set_conn_channel(void); static void ha_add_disconnect_count(void); static void ha_set_rebuild_connerror(HaRebuildReason reason, WalRcvConnError connerror); static void ha_set_port_to_remote(PGconn *dummy_conn, int ha_port); +static char *stringlist_to_identifierstr(PGconn *conn, List *strings); extern void SetDataRcvDummyStandbySyncPercent(int percent); #define AmWalReceiverForDummyStandby() \ @@ -195,26 +196,341 @@ bool libpqrcv_connect_for_TLI(TimeLineID *timeLineID, char *conninfo) return true; } +void StartRemoteStreaming(const LibpqrcvConnectParam *options) +{ + Assert(t_thrd.libwalreceiver_cxt.streamConn != NULL); + StringInfoData cmd; + initStringInfo(&cmd); + + appendStringInfoString(&cmd, "START_REPLICATION"); + if (!t_thrd.walreceiver_cxt.AmWalReceiverForFailover && options->slotname != NULL) { + appendStringInfo(&cmd, " SLOT \"%s\"", options->slotname); + } + + if (options->logical) { + appendStringInfo(&cmd, " LOGICAL"); + } + + appendStringInfo(&cmd, " %X/%X", (uint32)(options->startpoint >> 32), (uint32)(options->startpoint)); + if (options->logical) { + appendStringInfoString(&cmd, " ("); + appendStringInfo(&cmd, "proto_version '%u'", options->protoVersion); + char *pubnames_str = + stringlist_to_identifierstr(t_thrd.libwalreceiver_cxt.streamConn, options->publicationNames); + appendStringInfo(&cmd, ", publication_names %s", + PQescapeLiteral(t_thrd.libwalreceiver_cxt.streamConn, pubnames_str, strlen(pubnames_str))); + appendStringInfoChar(&cmd, ')'); + pfree(pubnames_str); + } + + PGresult *res = libpqrcv_PQexec(cmd.data); + pfree(cmd.data); + if (PQresultStatus(res) != PGRES_COPY_BOTH) { + PQclear(res); + ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), errmsg("could not start WAL streaming: %s", + PQerrorMessage(t_thrd.libwalreceiver_cxt.streamConn)))); + } + PQclear(res); +} + +void CreateRemoteReplicationSlot(XLogRecPtr startpoint, const char* slotname, bool isLogical) +{ + Assert(t_thrd.libwalreceiver_cxt.streamConn != NULL); + char cmd[1024]; + int nRet = 0; + + if (isLogical) { + nRet = snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "CREATE_REPLICATION_SLOT \"%s\" LOGICAL pgoutput", + slotname); + } else { + nRet = snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "CREATE_REPLICATION_SLOT \"%s\" PHYSICAL %X/%X", slotname, + (uint32)(startpoint >> 32), (uint32)(startpoint)); + } + securec_check_ss(nRet, "", ""); + + PGresult *res = libpqrcv_PQexec(cmd); + if (PQresultStatus(res) != PGRES_TUPLES_OK) { + PQclear(res); + ereport(ERROR, + (errcode(ERRCODE_INVALID_STATUS), errmsg("could not create replication slot %s : %s", slotname, + PQerrorMessage(t_thrd.libwalreceiver_cxt.streamConn)))); + } + PQclear(res); +} + +/* checkRemote: don't check the result is checkRemote is false, just let remote do some init */ +void IdentifyRemoteSystem(bool checkRemote) +{ + Assert(t_thrd.libwalreceiver_cxt.streamConn != NULL); + char *remoteSysid = NULL; + char localSysid[32] = {0}; + TimeLineID remoteTli; + TimeLineID localTli; + volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; + PGresult *res = libpqrcv_PQexec("IDENTIFY_SYSTEM"); + if (PQresultStatus(res) != PGRES_TUPLES_OK) { + PQclear(res); + ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), + errmsg("could not receive database system identifier and timeline ID from " + "the remote server: %s", + PQerrorMessage(t_thrd.libwalreceiver_cxt.streamConn)))); + } + if (PQnfields(res) != 4 || PQntuples(res) != 1) { + int num_tuples = PQntuples(res); + int num_fields = PQnfields(res); + + PQclear(res); + ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), errmsg("invalid response from remote server"), + errdetail("Could not identify system: Got %d rows and %d fields, expected %d rows and %d or more fields.", + num_tuples, num_fields, 1, 4))); + } + + if (!checkRemote) { + PQclear(res); + return; + } + + remoteSysid = PQgetvalue(res, 0, 0); + remoteTli = pg_strtoint32(PQgetvalue(res, 0, 1)); + + /* + * Confirm that the system identifier of the primary is the same as ours. + */ + int nRet = snprintf_s(localSysid, sizeof(localSysid), sizeof(localSysid) - 1, UINT64_FORMAT, GetSystemIdentifier()); + securec_check_ss(nRet, "", ""); + + if (strcmp(remoteSysid, localSysid) != 0) { + if (dummyStandbyMode) { + /* delete local xlog. */ + ProcessWSRmXLog(); + if (g_instance.attr.attr_storage.enable_mix_replication) { + while (true) { + if (!ws_dummy_data_writer_use_file) { + CloseWSDataFileOnDummyStandby(); + break; + } else { + pg_usleep(100000); /* sleep 0.1 s */ + } + } + ProcessWSRmData(); + } + + // only walreceiver set standby_sysid.datareceiver do not set again. + int rc = memcpy_s(localSysid, sizeof(localSysid), remoteSysid, sizeof(localSysid)); + securec_check(rc, "", ""); + + sync_system_identifier = strtoul(remoteSysid, 0, 10); + + ereport(LOG, (errmsg("DummyStandby system identifier differs between the primary"), + errdetail("The primary's identifier is %s, the standby's identifier is %s.sync_system_identifier=%lu", + remoteSysid, localSysid, sync_system_identifier))); + } else { + remoteSysid = pstrdup(remoteSysid); + PQclear(res); + /* + * If the system id is different, + * then set error message in WalRcv and rebuild reason in HaShmData. + */ + SpinLockAcquire(&walrcv->mutex); + if (AmWalReceiverForDummyStandby()) { + walrcv->dummyStandbyConnectFailed = true; + } + SpinLockRelease(&walrcv->mutex); + ha_set_rebuild_connerror(SYSTEMID_REBUILD, REPL_INFO_ERROR); + if (!t_thrd.xlog_cxt.is_cascade_standby) { + ereport(ERROR, + (errcode(ERRCODE_INVALID_STATUS), + errmsg("database system identifier differs between the primary and standby"), + errdetail("The primary's identifier is %s, the standby's identifier is %s.", + remoteSysid, + localSysid))); + } else { + ereport(ERROR, + (errcode(ERRCODE_INVALID_STATUS), + errmsg("database system identifier differs between the standby and cascade standby"), + errdetail("The standby's identifier is %s, the cascade standby's identifier is %s.", + remoteSysid, + localSysid))); + } + } + } + + /* + * Confirm that the current timeline of the primary is the same as the + * recovery target timeline. + */ + if (dummyStandbyMode) { + localTli = remoteTli; + } else { + localTli = GetRecoveryTargetTLI(); + } + PQclear(res); + + if (t_thrd.walreceiver_cxt.AmWalReceiverForFailover) { + t_thrd.xlog_cxt.ThisTimeLineID = localTli; + } else { + if (remoteTli != localTli) { + /* + * If the timeline id different, + * then set error message in WalRcv and rebuild reason in HaShmData. + */ + ha_set_rebuild_connerror(TIMELINE_REBUILD, REPL_INFO_ERROR); + ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), + errmsg("timeline %u of the primary does not match recovery target timeline %u", remoteTli, + localTli))); + } + t_thrd.xlog_cxt.ThisTimeLineID = remoteTli; + } +} + +/* identify remote mode, should do this after connect success. */ +static ServerMode IdentifyRemoteMode() +{ + Assert(t_thrd.libwalreceiver_cxt.streamConn != NULL); + volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; + PGresult *res = libpqrcv_PQexec("IDENTIFY_MODE"); + ServerMode remoteMode = UNKNOWN_MODE; + if (PQresultStatus(res) != PGRES_TUPLES_OK) { + PQclear(res); + ereport(ERROR, + (errcode(ERRCODE_INVALID_STATUS), errmsg("could not receive the ongoing mode infomation from " + "the remote server: %s", + PQerrorMessage(t_thrd.libwalreceiver_cxt.streamConn)))); + } + if (PQnfields(res) != 1 || PQntuples(res) != 1) { + int num_tuples = PQntuples(res); + int num_fields = PQnfields(res); + + PQclear(res); + ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), errmsg("invalid response from remote server"), + errdetail("Expected 1 tuple with 1 fields, got %d tuples with %d fields.", num_tuples, + num_fields))); + } + remoteMode = (ServerMode)pg_strtoint32(PQgetvalue(res, 0, 0)); + if (!t_thrd.walreceiver_cxt.AmWalReceiverForFailover && remoteMode != PRIMARY_MODE && + /* remoteMode of cascade standby is a standby */ + !t_thrd.xlog_cxt.is_cascade_standby) { + PQclear(res); + + if (dummyStandbyMode) { + clean_failover_host_conninfo_for_dummy(); + } + + ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), + errmsg("the mode of the remote server must be primary, current is %s", + wal_get_role_string(remoteMode)))); + } + + if (t_thrd.postmaster_cxt.HaShmData->is_cascade_standby && remoteMode != STANDBY_MODE) { + PQclear(res); + + SpinLockAcquire(&walrcv->mutex); + walrcv->conn_errno = REPL_INFO_ERROR; + SpinLockRelease(&walrcv->mutex); + + ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("the mode of the remote server must be standby, current is %s", + wal_get_role_string(remoteMode, true)))); + } + + PQclear(res); + return remoteMode; +} + +/* identify remote version, should do this after connect success. */ +static int32 IdentifyRemoteVersion() +{ + Assert(t_thrd.libwalreceiver_cxt.streamConn != NULL); + const int versionFields = 3; + uint32 remoteSversion; + uint32 localSversion; + char *remotePversion = NULL; + char *localPversion = NULL; + uint32 remoteTerm; + uint32 localTerm; + volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; + + PGresult *res = libpqrcv_PQexec("IDENTIFY_VERSION"); + if (PQresultStatus(res) != PGRES_TUPLES_OK) { + PQclear(res); + ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), + errmsg("could not receive database system version and protocol version from " + "the remote server: %s", + PQerrorMessage(t_thrd.libwalreceiver_cxt.streamConn)))); + } + if (PQnfields(res) != versionFields || PQntuples(res) != 1) { + int ntuples = PQntuples(res); + int nfields = PQnfields(res); + + PQclear(res); + ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), errmsg("invalid response from remote server"), + errdetail("Expected 1 tuple with 3 fields, got %d tuples with %d fields.", ntuples, nfields))); + } + remoteSversion = pg_strtoint32(PQgetvalue(res, 0, 0)); + localSversion = PG_VERSION_NUM; + remotePversion = PQgetvalue(res, 0, 1); + localPversion = pstrdup(PG_PROTOCOL_VERSION); + remoteTerm = pg_strtoint32(PQgetvalue(res, 0, 2)); + localTerm = Max(g_instance.comm_cxt.localinfo_cxt.term_from_file, g_instance.comm_cxt.localinfo_cxt.term_from_xlog); + ereport(LOG, (errmsg("remote term[%u], local term[%u]", remoteTerm, localTerm))); + if (localPversion == NULL) { + PQclear(res); + if (t_thrd.role != APPLY_WORKER) { + ha_set_rebuild_connerror(VERSION_REBUILD, REPL_INFO_ERROR); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_STATUS), + errmsg("could not get the local protocal version, make sure the PG_PROTOCOL_VERSION is defined"))); + } + if (walrcv->conn_target != REPCONNTARGET_DUMMYSTANDBY && (localTerm == 0 || localTerm > remoteTerm)) { + PQclear(res); + ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), + errmsg("invalid local term or remote term smaller than local. remote term[%u], local term[%u]", + remoteTerm, localTerm))); + return false; + } + + /* + * If the version of the remote server is not the same as the local's, then set error + * message in WalRcv and rebuild reason in HaShmData + */ + if (remoteSversion != localSversion || strncmp(remotePversion, localPversion, strlen(PG_PROTOCOL_VERSION)) != 0) { + PQclear(res); + pfree_ext(localPversion); + if (t_thrd.role != APPLY_WORKER) { + ha_set_rebuild_connerror(VERSION_REBUILD, REPL_INFO_ERROR); + } + + if (remoteSversion != localSversion) { + ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), + errmsg("database system version is different between the remote and local"), + errdetail("The remote's system version is %u, the local's system version is %u.", + remoteSversion, localSversion))); + } else { + ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), + errmsg("the remote protocal version %s is not the same as the local protocal version %s.", + remotePversion, localPversion))); + } + } + + PQclear(res); + pfree_ext(localPversion); + return remoteTerm; +} + + /* * Establish the connection to the primary server for XLOG streaming */ bool libpqrcv_connect(char *conninfo, XLogRecPtr *startpoint, char *slotname, int channel_identifier) { char conninfoRepl[MAXCONNINFO + 75]; - char *remoteSysid = NULL; - char localSysid[32]; - TimeLineID remoteTli; - TimeLineID localTli; PGresult *res = NULL; char cmd[1024]; char *remoteRecCrc = NULL; pg_crc32 recCrc = 0; XLogRecPtr localRec; pg_crc32 localRecCrc = 0; - uint32 remoteSversion; - uint32 localSversion; - char *remotePversion = NULL; - char *localPversion = NULL; uint32 remoteTerm; uint32 localTerm; ServerMode remoteMode = UNKNOWN_MODE; @@ -228,7 +544,6 @@ bool libpqrcv_connect(char *conninfo, XLogRecPtr *startpoint, char *slotname, in char *remoteMaxLsnStr = NULL; char *remoteMaxLsnCrcStr = NULL; uint32 hi, lo; - const int versionFields = 3; /* * Connect using deliberately undocumented parameter: replication. The @@ -291,127 +606,16 @@ retry: ereport(LOG, (errmsg("Connected to remote server :%s success.", conninfo))); /* 2. identify version */ - res = libpqrcv_PQexec("IDENTIFY_VERSION"); - if (PQresultStatus(res) != PGRES_TUPLES_OK) { - PQclear(res); - ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), - errmsg("could not receive database system version and protocol version from " - "the remote server: %s", - PQerrorMessage(t_thrd.libwalreceiver_cxt.streamConn)))); - return false; - } - if (PQnfields(res) != versionFields || PQntuples(res) != 1) { - int ntuples = PQntuples(res); - int nfields = PQnfields(res); - - PQclear(res); - ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), errmsg("invalid response from remote server"), - errdetail("Expected 1 tuple with 3 fields, got %d tuples with %d fields.", ntuples, nfields))); - return false; - } - remoteSversion = pg_strtoint32(PQgetvalue(res, 0, 0)); - localSversion = PG_VERSION_NUM; - remotePversion = PQgetvalue(res, 0, 1); - localPversion = pstrdup(PG_PROTOCOL_VERSION); - remoteTerm = pg_strtoint32(PQgetvalue(res, 0, 2)); + remoteTerm = IdentifyRemoteVersion(); localTerm = Max(g_instance.comm_cxt.localinfo_cxt.term_from_file, g_instance.comm_cxt.localinfo_cxt.term_from_xlog); - ereport(LOG, (errmsg("remote term[%u], local term[%u]", remoteTerm, localTerm))); - if (localPversion == NULL) { - PQclear(res); - ha_set_rebuild_connerror(VERSION_REBUILD, REPL_INFO_ERROR); - ereport(ERROR, - (errcode(ERRCODE_INVALID_STATUS), - errmsg("could not get the local protocal version, make sure the PG_PROTOCOL_VERSION is defined"))); - return false; - } - if (walrcv->conn_target != REPCONNTARGET_DUMMYSTANDBY && (localTerm == 0 || localTerm > remoteTerm)) { - PQclear(res); - ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), - errmsg("invalid local term or remote term smaller than local. remote term[%u], local term[%u]", - remoteTerm, localTerm))); - return false; - } - - /* - * If the version of the remote server is not the same as the local's, then set error - * message in WalRcv and rebuild reason in HaShmData - */ - if (remoteSversion != localSversion || strncmp(remotePversion, localPversion, strlen(PG_PROTOCOL_VERSION)) != 0) { - PQclear(res); - ha_set_rebuild_connerror(VERSION_REBUILD, REPL_INFO_ERROR); - - if (remoteSversion != localSversion) { - ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), - errmsg("database system version is different between the remote and local"), - errdetail("The remote's system version is %u, the local's system version is %u.", - remoteSversion, localSversion))); - } else { - ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), - errmsg("the remote protocal version %s is not the same as the local protocal version %s.", - remotePversion, localPversion))); - } - - if (localPversion != NULL) { - pfree(localPversion); - localPversion = NULL; - } - return false; - } - - PQclear(res); /* If connect to primary or standby (for failover), check remote role */ if (!t_thrd.walreceiver_cxt.AmWalReceiverForFailover || t_thrd.walreceiver_cxt.AmWalReceiverForStandby) { /* Send query and get server mode of the remote server. */ - res = libpqrcv_PQexec("IDENTIFY_MODE"); - if (PQresultStatus(res) != PGRES_TUPLES_OK) { - PQclear(res); - ereport(ERROR, - (errcode(ERRCODE_INVALID_STATUS), errmsg("could not receive the ongoing mode infomation from " - "the remote server: %s", - PQerrorMessage(t_thrd.libwalreceiver_cxt.streamConn)))); + remoteMode = IdentifyRemoteMode(); + if (remoteMode == UNKNOWN_MODE) { return false; } - if (PQnfields(res) != 1 || PQntuples(res) != 1) { - int num_tuples = PQntuples(res); - int num_fields = PQnfields(res); - - PQclear(res); - ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), errmsg("invalid response from remote server"), - errdetail("Expected 1 tuple with 1 fields, got %d tuples with %d fields.", num_tuples, - num_fields))); - return false; - } - remoteMode = (ServerMode)pg_strtoint32(PQgetvalue(res, 0, 0)); - if (!t_thrd.walreceiver_cxt.AmWalReceiverForFailover && remoteMode != PRIMARY_MODE && - /* remoteMode of cascade standby is a standby */ - !t_thrd.xlog_cxt.is_cascade_standby) { - PQclear(res); - - if (dummyStandbyMode) { - clean_failover_host_conninfo_for_dummy(); - } - - ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), - errmsg("the mode of the remote server must be primary, current is %s", - wal_get_role_string(remoteMode)))); - return false; - } - - if (t_thrd.postmaster_cxt.HaShmData->is_cascade_standby && remoteMode != STANDBY_MODE) { - PQclear(res); - - SpinLockAcquire(&walrcv->mutex); - walrcv->conn_errno = REPL_INFO_ERROR; - SpinLockRelease(&walrcv->mutex); - - ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("the mode of the remote server must be standby, current is %s", - wal_get_role_string(remoteMode, true)))); - return false; - } - - PQclear(res); } #ifndef ENABLE_MULTIPLE_NODES @@ -424,122 +628,7 @@ retry: * Get the system identifier and timeline ID as a DataRow message from the * primary server. */ - res = libpqrcv_PQexec("IDENTIFY_SYSTEM"); - if (PQresultStatus(res) != PGRES_TUPLES_OK) { - PQclear(res); - ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), - errmsg("could not receive database system identifier and timeline ID from " - "the remote server: %s", - PQerrorMessage(t_thrd.libwalreceiver_cxt.streamConn)))); - return false; - } - if (PQnfields(res) != 4 || PQntuples(res) != 1) { - int num_tuples = PQntuples(res); - int num_fields = PQnfields(res); - - PQclear(res); - ereport( - ERROR, - (errcode(ERRCODE_INVALID_STATUS), errmsg("invalid response from remote server"), - errdetail("Could not identify system: Got %d rows and %d fields, expected %d rows and %d or more fields.", - num_tuples, num_fields, 1, 4))); - - return false; - } - remoteSysid = PQgetvalue(res, 0, 0); - remoteTli = pg_strtoint32(PQgetvalue(res, 0, 1)); - - /* - * Confirm that the system identifier of the primary is the same as ours. - */ - nRet = snprintf_s(localSysid, sizeof(localSysid), sizeof(localSysid) - 1, UINT64_FORMAT, GetSystemIdentifier()); - securec_check_ss(nRet, "", ""); - - if (strcmp(remoteSysid, localSysid) != 0) { - if (dummyStandbyMode) { - /* delete local xlog. */ - ProcessWSRmXLog(); - if (g_instance.attr.attr_storage.enable_mix_replication) { - while (true) { - if (!ws_dummy_data_writer_use_file) { - CloseWSDataFileOnDummyStandby(); - break; - } else { - pg_usleep(100000); /* sleep 0.1 s */ - } - } - ProcessWSRmData(); - } - - // only walreceiver set standby_sysid.datareceiver do not set again. - rc = memcpy_s(localSysid, sizeof(localSysid), remoteSysid, sizeof(localSysid)); - securec_check(rc, "", ""); - - sync_system_identifier = strtoul(remoteSysid, 0, 10); - - ereport( - LOG, - (errmsg("DummyStandby system identifier differs between the primary"), - errdetail("The primary's identifier is %s, the standby's identifier is %s.sync_system_identifier=%lu", - remoteSysid, localSysid, sync_system_identifier))); - } else { - remoteSysid = pstrdup(remoteSysid); - PQclear(res); - /* - * If the system id is different, - * then set error message in WalRcv and rebuild reason in HaShmData. - */ - SpinLockAcquire(&walrcv->mutex); - if (AmWalReceiverForDummyStandby()) { - walrcv->dummyStandbyConnectFailed = true; - } - SpinLockRelease(&walrcv->mutex); - ha_set_rebuild_connerror(SYSTEMID_REBUILD, REPL_INFO_ERROR); - if (!t_thrd.xlog_cxt.is_cascade_standby) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_STATUS), - errmsg("database system identifier differs between the primary and standby"), - errdetail("The primary's identifier is %s, the standby's identifier is %s.", - remoteSysid, - localSysid))); - } else { - ereport(ERROR, - (errcode(ERRCODE_INVALID_STATUS), - errmsg("database system identifier differs between the standby and cascade standby"), - errdetail("The standby's identifier is %s, the cascade standby's identifier is %s.", - remoteSysid, - localSysid))); - } - return false; - } - } - - /* - * Confirm that the current timeline of the primary is the same as the - * recovery target timeline. - */ - if (dummyStandbyMode) { - localTli = remoteTli; - } else { - localTli = GetRecoveryTargetTLI(); - } - PQclear(res); - - if (t_thrd.walreceiver_cxt.AmWalReceiverForFailover) { - t_thrd.xlog_cxt.ThisTimeLineID = localTli; - } else { - if (remoteTli != localTli) { - /* - * If the timeline id different, - * then set error message in WalRcv and rebuild reason in HaShmData. - */ - ha_set_rebuild_connerror(TIMELINE_REBUILD, REPL_INFO_ERROR); - ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), - errmsg("timeline %u of the primary does not match recovery target timeline %u", remoteTli, - localTli))); - } - t_thrd.xlog_cxt.ThisTimeLineID = remoteTli; - } + IdentifyRemoteSystem(true); if (dummyStandbyMode) { char msgBuf[XLOG_READER_MAX_MSGLENTH] = {0}; @@ -789,36 +878,17 @@ retry: /* Create replication slot if need */ if (!t_thrd.walreceiver_cxt.AmWalReceiverForFailover && slotname != NULL) { - nRet = snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "CREATE_REPLICATION_SLOT \"%s\" PHYSICAL %X/%X", slotname, - (uint32)(*startpoint >> 32), (uint32)(*startpoint)); - securec_check_ss(nRet, "", ""); - - res = libpqrcv_PQexec(cmd); - if (PQresultStatus(res) != PGRES_TUPLES_OK) { - PQclear(res); - ereport(ERROR, - (errcode(ERRCODE_INVALID_STATUS), errmsg("could not create replication slot %s : %s", slotname, - PQerrorMessage(t_thrd.libwalreceiver_cxt.streamConn)))); - } - PQclear(res); + CreateRemoteReplicationSlot(*startpoint, slotname, false); } /* Start streaming from the point requested by startup process */ - if (!t_thrd.walreceiver_cxt.AmWalReceiverForFailover && slotname != NULL) - nRet = snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "START_REPLICATION SLOT \"%s\" %X/%X", slotname, - (uint32)(*startpoint >> 32), (uint32)(*startpoint)); - else - nRet = snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "START_REPLICATION %X/%X", (uint32)(*startpoint >> 32), - (uint32)(*startpoint)); - securec_check_ss(nRet, "", ""); - - res = libpqrcv_PQexec(cmd); - if (PQresultStatus(res) != PGRES_COPY_BOTH) { - PQclear(res); - ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), errmsg("could not start WAL streaming: %s", - PQerrorMessage(t_thrd.libwalreceiver_cxt.streamConn)))); - } - PQclear(res); + LibpqrcvConnectParam options; + rc = memset_s(&options, sizeof(LibpqrcvConnectParam), 0, sizeof(LibpqrcvConnectParam)); + securec_check(rc, "", ""); + options.slotname = slotname; + options.startpoint = *startpoint; + options.logical = false; + StartRemoteStreaming(&options); ereport(LOG, (errmsg("streaming replication successfully connected to primary, the connection is %s, start from %X/%X ", @@ -980,6 +1050,18 @@ static PGresult *libpqrcv_PQexec(const char *query) return lastResult; } +void libpqrcv_check_conninfo(const char *conninfo) +{ + char *err = NULL; + + PQconninfoOption *opts = PQconninfoParse(conninfo, &err); + if (opts == NULL) { + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid connection string syntax: %s", err))); + } + + PQconninfoFree(opts); +} + /* * Disconnect connection to primary, if any. */ @@ -1061,7 +1143,9 @@ bool libpqrcv_receive(int timeout, unsigned char *type, char **buffer, int *len) retcode = MAKE_SQLSTATE(sqlstate[0], sqlstate[1], sqlstate[2], sqlstate[3], sqlstate[4]); } if (retcode == ERRCODE_UNDEFINED_FILE) { - ha_set_rebuild_connerror(WALSEGMENT_REBUILD, REPL_INFO_ERROR); + if (t_thrd.role != APPLY_WORKER) { + ha_set_rebuild_connerror(WALSEGMENT_REBUILD, REPL_INFO_ERROR); + } SpinLockAcquire(&walrcv->mutex); walrcv->ntries++; SpinLockRelease(&walrcv->mutex); @@ -1097,6 +1181,20 @@ void libpqrcv_send(const char *buffer, int nbytes) PQerrorMessage(t_thrd.libwalreceiver_cxt.streamConn)))); } +bool libpqrcv_command(const char *cmd, char **err) +{ + PGresult *res = libpqrcv_PQexec(cmd); + + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + PQclear(res); + *err = pstrdup(PQerrorMessage(t_thrd.libwalreceiver_cxt.streamConn)); + return false; + } + + PQclear(res); + return true; +} + void HaSetRebuildRepInfoError(HaRebuildReason reason) { if (NONE_REBUILD == reason) { @@ -1238,3 +1336,33 @@ static void ha_set_port_to_remote(PGconn *dummy_conn, int ha_port) PQclear(res); return; } + +/* + * Given a List of strings, return it as single comma separated + * string, quoting identifiers as needed. + * + * This is essentially the reverse of SplitIdentifierString. + * + * The caller should free the result. + */ +static char *stringlist_to_identifierstr(PGconn *conn, List *strings) +{ + ListCell *lc; + StringInfoData res; + bool first = true; + + initStringInfo(&res); + + foreach (lc, strings) { + char *val = strVal(lfirst(lc)); + + if (first) { + first = false; + } else { + appendStringInfoChar(&res, ','); + } + appendStringInfoString(&res, PQescapeIdentifier(conn, val, strlen(val))); + } + + return res.data; +} diff --git a/src/gausskernel/storage/replication/logical/Makefile b/src/gausskernel/storage/replication/logical/Makefile index 597c65bbb..0603a668d 100644 --- a/src/gausskernel/storage/replication/logical/Makefile +++ b/src/gausskernel/storage/replication/logical/Makefile @@ -6,6 +6,6 @@ include $(top_builddir)/src/Makefile.global override CPPFLAGS := -I$(srcdir) $(CPPFLAGS) -OBJS = decode.o logical.o logicalfuncs.o reorderbuffer.o snapbuild.o +OBJS = decode.o launcher.o logical.o logicalfuncs.o origin.o proto.o relation.o reorderbuffer.o snapbuild.o worker.o include $(top_srcdir)/src/gausskernel/common.mk diff --git a/src/gausskernel/storage/replication/logical/decode.cpp b/src/gausskernel/storage/replication/logical/decode.cpp index d555d3ece..ce9ed0789 100644 --- a/src/gausskernel/storage/replication/logical/decode.cpp +++ b/src/gausskernel/storage/replication/logical/decode.cpp @@ -39,6 +39,7 @@ #include "replication/decode.h" #include "replication/logical.h" #include "replication/reorderbuffer.h" +#include "replication/origin.h" #include "replication/snapbuild.h" #include "storage/standby.h" @@ -72,7 +73,7 @@ static void DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf static void DecodeUMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf); static void DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf, TransactionId xid, CommitSeqNo csn, Oid dboid, TimestampTz commit_time, int nsubxacts, TransactionId *sub_xids, int ninval_msgs, - SharedInvalidationMessage *msg); + SharedInvalidationMessage *msg, xl_xact_origin *origin); static void DecodeAbort(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, TransactionId *sub_xids, int nsubxacts); @@ -197,6 +198,7 @@ void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx, XLogReaderState * case RM_SPGIST_ID: case RM_SLOT_ID: case RM_BARRIER_ID: + case RM_REPLORIGIN_ID: break; case RM_HEAP3_ID: DecodeHeap3Op(ctx, &buf); @@ -285,14 +287,19 @@ static void DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) xl_xact_commit *xlrec = NULL; TransactionId *subxacts = NULL; SharedInvalidationMessage *invals = NULL; + xl_xact_origin *origin = NULL; xlrec = (xl_xact_commit *)buf->record_data; subxacts = (TransactionId *)&(xlrec->xnodes[xlrec->nrels]); invals = (SharedInvalidationMessage *)&(subxacts[xlrec->nsubxacts]); + if (xlrec->xinfo | XACT_HAS_ORIGIN) { + origin = (xl_xact_origin*)GetRepOriginPtr((char*)xlrec->xnodes, xlrec->xinfo, + xlrec->nsubxacts, xlrec->nmsgs, xlrec->nrels, xlrec->nlibrary); + } DecodeCommit(ctx, buf, XLogRecGetXid(r), xlrec->csn, xlrec->dbId, xlrec->xact_time, xlrec->nsubxacts, - subxacts, xlrec->nmsgs, invals); + subxacts, xlrec->nmsgs, invals, origin); break; } @@ -301,6 +308,7 @@ static void DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) xl_xact_commit *xlrec = NULL; TransactionId *subxacts = NULL; SharedInvalidationMessage *invals = NULL; + xl_xact_origin *origin = NULL; /* Prepared commits contain a normal commit record... */ prec = (xl_xact_commit_prepared *)buf->record_data; @@ -309,8 +317,13 @@ static void DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) subxacts = (TransactionId *)&(xlrec->xnodes[xlrec->nrels]); invals = (SharedInvalidationMessage *)&(subxacts[xlrec->nsubxacts]); + if (xlrec->xinfo | XACT_HAS_ORIGIN) { + origin = (xl_xact_origin*)GetRepOriginPtr((char*)xlrec->xnodes, xlrec->xinfo, + xlrec->nsubxacts, xlrec->nmsgs, xlrec->nrels, xlrec->nlibrary); + } + DecodeCommit(ctx, buf, prec->xid, xlrec->csn, xlrec->dbId, xlrec->xact_time, xlrec->nsubxacts, subxacts, - xlrec->nmsgs, invals); + xlrec->nmsgs, invals, origin); break; } @@ -320,7 +333,7 @@ static void DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) xlrec = (xl_xact_commit_compact *)buf->record_data; DecodeCommit(ctx, buf, XLogRecGetXid(r), xlrec->csn, InvalidOid, xlrec->xact_time, xlrec->nsubxacts, - xlrec->subxacts, 0, NULL); + xlrec->subxacts, 0, NULL, NULL); break; } case XLOG_XACT_ABORT: { @@ -663,10 +676,11 @@ static inline bool FilterByOrigin(LogicalDecodingContext *ctx, RepOriginId origi */ static void DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf, TransactionId xid, CommitSeqNo csn, Oid dboid, TimestampTz commit_time, int nsubxacts, TransactionId *sub_xids, int ninval_msgs, - SharedInvalidationMessage *msgs) + SharedInvalidationMessage *msgs, xl_xact_origin *origin) { int i; XLogRecPtr origin_id = XLogRecGetOrigin(buf->record); + XLogRecPtr origin_lsn = origin == NULL ? InvalidXLogRecPtr : origin->origin_lsn; /* * Process invalidation messages, even if we're not interested in the @@ -726,7 +740,7 @@ static void DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf, Tra } /* replay actions of all transaction + subtransactions in order */ - ReorderBufferCommit(ctx->reorder, xid, buf->origptr, buf->endptr, origin_id, csn, commit_time); + ReorderBufferCommit(ctx->reorder, xid, buf->origptr, buf->endptr, origin_id, origin_lsn, csn, commit_time); } /* diff --git a/src/include/catalog/pg_publication_namespace.h b/src/include/catalog/pg_publication_namespace.h new file mode 100644 index 000000000..8dfe3a922 --- /dev/null +++ b/src/include/catalog/pg_publication_namespace.h @@ -0,0 +1,47 @@ +/*------------------------------------------------------------------------- + * + * pg_publication_namespace.h + * definition of the system catalog for mappings between schemas and + * publications (pg_publication_namespace) + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_publication_namespace.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PUBLICATION_NAMESPACE_H +#define PG_PUBLICATION_NAMESPACE_H + +#include "catalog/genbki.h" +// #include "catalog/pg_publication_namespace_d.h" + + +/* ---------------- + * pg_publication_namespace definition. cpp turns this into + * typedef struct FormData_pg_publication_namespace + * ---------------- + */ +CATALOG(pg_publication_namespace,8901,PublicationNamespaceRelationId) +{ + Oid oid; /* oid */ + Oid pnpubid BKI_LOOKUP(pg_publication); /* Oid of the publication */ + Oid pnnspid BKI_LOOKUP(pg_namespace); /* Oid of the schema */ +} FormData_pg_publication_namespace; + +/* ---------------- + * Form_pg_publication_namespace corresponds to a pointer to a tuple with + * the format of pg_publication_namespace relation. + * ---------------- + */ +typedef FormData_pg_publication_namespace *Form_pg_publication_namespace; + +DECLARE_UNIQUE_INDEX_PKEY(pg_publication_namespace_oid_index, 8902, PublicationNamespaceObjectIndexId, on pg_publication_namespace using btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_publication_namespace_pnnspid_pnpubid_index, 8903, PublicationNamespacePnnspidPnpubidIndexId, on pg_publication_namespace using btree(pnnspid oid_ops, pnpubid oid_ops)); + +#endif /* PG_PUBLICATION_NAMESPACE_H */