From 3404ca05119d9183f6473d4ba381f42fe3b4e304 Mon Sep 17 00:00:00 2001 From: Yogesh Sharma Date: Wed, 27 May 2026 18:37:41 -0400 Subject: [PATCH 1/6] Pull latest pg_dump and string_utils.c --- Makefile.in | 10 +- src/compat/14/pg_dump.patch | 2 +- src/compat/14/pg_dump/pg_backup_archiver.c | 41 +++++- src/compat/14/pg_dump/pg_backup_archiver.h | 10 ++ src/compat/14/pg_dump/pg_dump.c | 9 +- src/compat/14/pg_dump/pg_dump_sort.c | 16 ++- src/compat/15/pg_dump.patch | 2 +- src/compat/15/pg_dump/pg_backup_archiver.c | 41 +++++- src/compat/15/pg_dump/pg_backup_archiver.h | 10 ++ src/compat/15/pg_dump/pg_dump.c | 9 +- src/compat/15/pg_dump/pg_dump_sort.c | 16 ++- src/compat/16/pg_dump.patch | 2 +- src/compat/16/pg_dump/compress_gzip.c | 48 +++++-- src/compat/16/pg_dump/compress_io.c | 2 + src/compat/16/pg_dump/compress_io.h | 15 +- src/compat/16/pg_dump/compress_lz4.c | 102 +++++++++----- src/compat/16/pg_dump/compress_none.c | 29 ++-- src/compat/16/pg_dump/compress_zstd.c | 144 ++++++++++++-------- src/compat/16/pg_dump/pg_backup_archiver.c | 45 +++++- src/compat/16/pg_dump/pg_backup_directory.c | 52 ++----- src/compat/16/pg_dump/pg_dump.c | 9 +- src/compat/16/pg_dump/pg_dump_sort.c | 16 ++- src/compat/17/pg_dump.patch | 2 +- src/compat/17/pg_dump/compress_gzip.c | 48 +++++-- src/compat/17/pg_dump/compress_io.c | 2 + src/compat/17/pg_dump/compress_io.h | 15 +- src/compat/17/pg_dump/compress_lz4.c | 101 +++++++++----- src/compat/17/pg_dump/compress_none.c | 29 ++-- src/compat/17/pg_dump/compress_zstd.c | 144 ++++++++++++-------- src/compat/17/pg_dump/pg_backup_archiver.c | 45 +++++- src/compat/17/pg_dump/pg_backup_directory.c | 52 ++----- src/compat/17/pg_dump/pg_dump.c | 19 ++- src/compat/17/pg_dump/pg_dump_sort.c | 27 +++- src/compat/18/pg_dump.patch | 12 +- src/compat/18/pg_dump/compress_gzip.c | 48 +++++-- src/compat/18/pg_dump/compress_io.c | 2 + src/compat/18/pg_dump/compress_io.h | 15 +- src/compat/18/pg_dump/compress_lz4.c | 101 +++++++++----- src/compat/18/pg_dump/compress_none.c | 29 ++-- src/compat/18/pg_dump/compress_zstd.c | 144 ++++++++++++-------- src/compat/18/pg_dump/dumputils.c | 1 + src/compat/18/pg_dump/pg_backup_archiver.c | 53 +++++-- src/compat/18/pg_dump/pg_backup_archiver.h | 2 - src/compat/18/pg_dump/pg_backup_directory.c | 52 ++----- src/compat/18/pg_dump/pg_backup_tar.c | 25 ---- src/compat/18/pg_dump/pg_dump.c | 88 ++++++++---- src/compat/18/pg_dump/pg_dump_sort.c | 27 +++- 47 files changed, 1108 insertions(+), 605 deletions(-) diff --git a/Makefile.in b/Makefile.in index 9e97c88d..8881e8bb 100644 --- a/Makefile.in +++ b/Makefile.in @@ -19,6 +19,7 @@ EXTRA_CLEAN = src/pgactive_init_copy$(X) src/pgactive_init_copy.o include/pgacti test/regression.diffs test/regression.out \ pgactive_init_copy_postgres.log home tmp_test_* \ pgactive_dump$(X) $(pgactive_DUMP_OBJS) \ + src/*.o src/compat/*/pg_dump/*.o # When in development add -Werror. PG_CPPFLAGS = -I$(srcdir)/include -I$(srcdir)/src/$(pgactive_PGVERCOMPAT_INCDIR) -I$(libpq_srcdir) -Wall -Wmissing-prototypes -Wmissing-declarations $(EXTRA_CFLAGS) @@ -149,14 +150,14 @@ pgactive_DUMP_OBJS = $(pgactive_DUMP_DIR)/pg_dump.o \ $(pgactive_DUMP_DIR)/dumputils.o \ $(pgactive_DUMP_DIR)/compress_io.o -ifeq ($(shell test $(MAJORVERSION) -ge 18; echo $$?),0) - pgactive_DUMP_OBJS += $(pgactive_DUMP_DIR)/connectdb.o -endif - PG_CONFIG_LIBS = $(shell $(PG_CONFIG) --libs) pgactive_DUMP_LIBS = $(PG_CONFIG_LIBS) -lpgfeutils +ifeq ($(shell test $(MAJORVERSION) -ge 18; echo $$?),0) + pgactive_DUMP_OBJS += $(pgactive_DUMP_DIR)/connectdb.o +endif + ifneq ($(wildcard $(pgactive_DUMP_DIR)/compress_lz4.c),) pgactive_DUMP_OBJS += $(pgactive_DUMP_DIR)/compress_lz4.o endif @@ -171,6 +172,7 @@ endif ifneq ($(wildcard $(pgactive_DUMP_DIR)/compress_zstd.c),) pgactive_DUMP_OBJS += $(pgactive_DUMP_DIR)/compress_zstd.o + pgactive_DUMP_LIBS += -lzstd endif ifneq ($(wildcard $(pgactive_DUMP_DIR)/filter.c),) diff --git a/src/compat/14/pg_dump.patch b/src/compat/14/pg_dump.patch index 349bc4d6..b2e369c7 100644 --- a/src/compat/14/pg_dump.patch +++ b/src/compat/14/pg_dump.patch @@ -11,7 +11,7 @@ index 203acff..11de754 100644 /* various user-settable parameters */ bool schemaOnly; diff --git a/src/compat/14/pg_dump/pg_dump.c b/src/compat/14/pg_dump/pg_dump.c -index 0d5d796..8f80bf2 100644 +index 9bebf23..0321e96 100644 --- a/src/compat/14/pg_dump/pg_dump.c +++ b/src/compat/14/pg_dump/pg_dump.c @@ -372,6 +372,7 @@ main(int argc, char **argv) diff --git a/src/compat/14/pg_dump/pg_backup_archiver.c b/src/compat/14/pg_dump/pg_backup_archiver.c index 6f2cd8c2..c22dd8f3 100644 --- a/src/compat/14/pg_dump/pg_backup_archiver.c +++ b/src/compat/14/pg_dump/pg_backup_archiver.c @@ -2858,6 +2858,20 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) if (ropt->no_comments && strcmp(te->desc, "COMMENT") == 0) return 0; + /* + * If it's a comment on a publication or a subscription, maybe ignore it. + */ + if (strcmp(te->desc, "COMMENT") == 0) + { + if (ropt->no_publications && + strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) + return 0; + + if (ropt->no_subscriptions && + strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0) + return 0; + } + /* * If it's a publication or a table part of a publication, maybe ignore * it. @@ -2871,6 +2885,21 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) if (ropt->no_security_labels && strcmp(te->desc, "SECURITY LABEL") == 0) return 0; + /* + * If it's a security label on a publication or a subscription, maybe + * ignore it. + */ + if (strcmp(te->desc, "SECURITY LABEL") == 0) + { + if (ropt->no_publications && + strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) + return 0; + + if (ropt->no_subscriptions && + strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0) + return 0; + } + /* If it's a subscription, maybe ignore it */ if (ropt->no_subscriptions && strcmp(te->desc, "SUBSCRIPTION") == 0) return 0; @@ -3111,12 +3140,14 @@ _tocEntryRestorePass(TocEntry *te) return RESTORE_PASS_POST_ACL; /* - * Comments need to be emitted in the same pass as their parent objects. - * ACLs haven't got comments, and neither do matview data objects, but - * event triggers do. (Fortunately, event triggers haven't got ACLs, or - * we'd need yet another weird special case.) + * Comments and security labels need to be emitted in the same pass as + * their parent objects. ACLs haven't got comments and security labels, + * and neither do matview data objects, but event triggers do. + * (Fortunately, event triggers haven't got ACLs, or we'd need yet another + * weird special case.) */ - if (strcmp(te->desc, "COMMENT") == 0 && + if ((strcmp(te->desc, "COMMENT") == 0 || + strcmp(te->desc, "SECURITY LABEL") == 0) && strncmp(te->tag, "EVENT TRIGGER ", 14) == 0) return RESTORE_PASS_POST_ACL; diff --git a/src/compat/14/pg_dump/pg_backup_archiver.h b/src/compat/14/pg_dump/pg_backup_archiver.h index 91060944..ef4a6903 100644 --- a/src/compat/14/pg_dump/pg_backup_archiver.h +++ b/src/compat/14/pg_dump/pg_backup_archiver.h @@ -38,6 +38,16 @@ */ #ifdef HAVE_LIBZ #include + +/* + * We don't use the gzgetc() macro, because zlib's configuration logic is not + * robust enough to guarantee that the macro will have the same ideas about + * struct field layout as the library itself does; see for example + * https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=59711 + * Instead, #undef the macro and fall back to the underlying function. + */ +#undef gzgetc + #define GZCLOSE(fh) gzclose(fh) #define GZWRITE(p, s, n, fh) gzwrite(fh, p, (n) * (s)) #define GZREAD(p, s, n, fh) gzread(fh, p, (n) * (s)) diff --git a/src/compat/14/pg_dump/pg_dump.c b/src/compat/14/pg_dump/pg_dump.c index 8f80bf25..0321e967 100644 --- a/src/compat/14/pg_dump/pg_dump.c +++ b/src/compat/14/pg_dump/pg_dump.c @@ -15843,7 +15843,7 @@ collectSecLabels(Archive *fout, SecLabelItem **items) appendPQExpBufferStr(query, "SELECT label, provider, classoid, objoid, objsubid " - "FROM pg_catalog.pg_seclabel " + "FROM pg_catalog.pg_seclabels " "ORDER BY classoid, objoid, objsubid"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -17611,7 +17611,7 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) dumpComment(fout, conprefix->data, qtypname, tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, - coninfo->dobj.catId, 0, tyinfo->dobj.dumpId); + coninfo->dobj.catId, 0, coninfo->dobj.dumpId); destroyPQExpBuffer(conprefix); free(qtypname); } @@ -18333,6 +18333,11 @@ dumpEventTrigger(Archive *fout, const EventTriggerInfo *evtinfo) NULL, evtinfo->evtowner, evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId); + if (evtinfo->dobj.dump & DUMP_COMPONENT_SECLABEL) + dumpSecLabel(fout, "EVENT TRIGGER", qevtname, + NULL, evtinfo->evtowner, + evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId); + destroyPQExpBuffer(query); destroyPQExpBuffer(delqry); free(qevtname); diff --git a/src/compat/14/pg_dump/pg_dump_sort.c b/src/compat/14/pg_dump/pg_dump_sort.c index 168e4a3d..99841278 100644 --- a/src/compat/14/pg_dump/pg_dump_sort.c +++ b/src/compat/14/pg_dump/pg_dump_sort.c @@ -379,7 +379,8 @@ DOTypeNameCompare(const void *p1, const void *p2) if (cmpval != 0) return cmpval; } - else if (obj1->objType == DO_CONSTRAINT) + else if (obj1->objType == DO_CONSTRAINT || + obj1->objType == DO_FK_CONSTRAINT) { ConstraintInfo *robj1 = *(ConstraintInfo *const *) p1; ConstraintInfo *robj2 = *(ConstraintInfo *const *) p2; @@ -412,6 +413,19 @@ DOTypeNameCompare(const void *p1, const void *p2) return cmpval; } } + else if (obj1->objType == DO_DEFAULT_ACL) + { + DefaultACLInfo *daclobj1 = *(DefaultACLInfo *const *) p1; + DefaultACLInfo *daclobj2 = *(DefaultACLInfo *const *) p2; + + /* + * Sort by defaclrole, per pg_default_acl_role_nsp_obj_index. The + * (namespace, name) match (defaclnamespace, defaclobjtype). + */ + cmpval = strcmp(daclobj1->defaclrole, daclobj2->defaclrole); + if (cmpval != 0) + return cmpval; + } else if (obj1->objType == DO_PUBLICATION_REL) { PublicationRelInfo *probj1 = *(PublicationRelInfo *const *) p1; diff --git a/src/compat/15/pg_dump.patch b/src/compat/15/pg_dump.patch index 74fe0631..2b4c3514 100644 --- a/src/compat/15/pg_dump.patch +++ b/src/compat/15/pg_dump.patch @@ -11,7 +11,7 @@ index 7562e24..0c9866d 100644 /* various user-settable parameters */ bool schemaOnly; diff --git a/src/compat/15/pg_dump/pg_dump.c b/src/compat/15/pg_dump/pg_dump.c -index 8bf14bb..c8afec5 100644 +index 020a232..b15bbaf 100644 --- a/src/compat/15/pg_dump/pg_dump.c +++ b/src/compat/15/pg_dump/pg_dump.c @@ -385,6 +385,7 @@ main(int argc, char **argv) diff --git a/src/compat/15/pg_dump/pg_backup_archiver.c b/src/compat/15/pg_dump/pg_backup_archiver.c index b81788f7..29a41ca8 100644 --- a/src/compat/15/pg_dump/pg_backup_archiver.c +++ b/src/compat/15/pg_dump/pg_backup_archiver.c @@ -2875,6 +2875,20 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) if (ropt->no_comments && strcmp(te->desc, "COMMENT") == 0) return 0; + /* + * If it's a comment on a publication or a subscription, maybe ignore it. + */ + if (strcmp(te->desc, "COMMENT") == 0) + { + if (ropt->no_publications && + strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) + return 0; + + if (ropt->no_subscriptions && + strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0) + return 0; + } + /* * If it's a publication or a table part of a publication, maybe ignore * it. @@ -2889,6 +2903,21 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) if (ropt->no_security_labels && strcmp(te->desc, "SECURITY LABEL") == 0) return 0; + /* + * If it's a security label on a publication or a subscription, maybe + * ignore it. + */ + if (strcmp(te->desc, "SECURITY LABEL") == 0) + { + if (ropt->no_publications && + strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) + return 0; + + if (ropt->no_subscriptions && + strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0) + return 0; + } + /* If it's a subscription, maybe ignore it */ if (ropt->no_subscriptions && strcmp(te->desc, "SUBSCRIPTION") == 0) return 0; @@ -3129,12 +3158,14 @@ _tocEntryRestorePass(TocEntry *te) return RESTORE_PASS_POST_ACL; /* - * Comments need to be emitted in the same pass as their parent objects. - * ACLs haven't got comments, and neither do matview data objects, but - * event triggers do. (Fortunately, event triggers haven't got ACLs, or - * we'd need yet another weird special case.) + * Comments and security labels need to be emitted in the same pass as + * their parent objects. ACLs haven't got comments and security labels, + * and neither do matview data objects, but event triggers do. + * (Fortunately, event triggers haven't got ACLs, or we'd need yet another + * weird special case.) */ - if (strcmp(te->desc, "COMMENT") == 0 && + if ((strcmp(te->desc, "COMMENT") == 0 || + strcmp(te->desc, "SECURITY LABEL") == 0) && strncmp(te->tag, "EVENT TRIGGER ", 14) == 0) return RESTORE_PASS_POST_ACL; diff --git a/src/compat/15/pg_dump/pg_backup_archiver.h b/src/compat/15/pg_dump/pg_backup_archiver.h index 084cd87e..ae98b5a4 100644 --- a/src/compat/15/pg_dump/pg_backup_archiver.h +++ b/src/compat/15/pg_dump/pg_backup_archiver.h @@ -34,6 +34,16 @@ #ifdef HAVE_LIBZ #include + +/* + * We don't use the gzgetc() macro, because zlib's configuration logic is not + * robust enough to guarantee that the macro will have the same ideas about + * struct field layout as the library itself does; see for example + * https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=59711 + * Instead, #undef the macro and fall back to the underlying function. + */ +#undef gzgetc + #define GZCLOSE(fh) gzclose(fh) #define GZWRITE(p, s, n, fh) gzwrite(fh, p, (n) * (s)) #define GZREAD(p, s, n, fh) gzread(fh, p, (n) * (s)) diff --git a/src/compat/15/pg_dump/pg_dump.c b/src/compat/15/pg_dump/pg_dump.c index c8afec53..b15bbafe 100644 --- a/src/compat/15/pg_dump/pg_dump.c +++ b/src/compat/15/pg_dump/pg_dump.c @@ -15084,7 +15084,7 @@ collectSecLabels(Archive *fout) appendPQExpBufferStr(query, "SELECT label, provider, classoid, objoid, objsubid " - "FROM pg_catalog.pg_seclabel " + "FROM pg_catalog.pg_seclabels " "ORDER BY classoid, objoid, objsubid"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -16912,7 +16912,7 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) dumpComment(fout, conprefix->data, qtypname, tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, - coninfo->dobj.catId, 0, tyinfo->dobj.dumpId); + coninfo->dobj.catId, 0, coninfo->dobj.dumpId); destroyPQExpBuffer(conprefix); free(qtypname); } @@ -17603,6 +17603,11 @@ dumpEventTrigger(Archive *fout, const EventTriggerInfo *evtinfo) NULL, evtinfo->evtowner, evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId); + if (evtinfo->dobj.dump & DUMP_COMPONENT_SECLABEL) + dumpSecLabel(fout, "EVENT TRIGGER", qevtname, + NULL, evtinfo->evtowner, + evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId); + destroyPQExpBuffer(query); destroyPQExpBuffer(delqry); free(qevtname); diff --git a/src/compat/15/pg_dump/pg_dump_sort.c b/src/compat/15/pg_dump/pg_dump_sort.c index 09799c98..0066601b 100644 --- a/src/compat/15/pg_dump/pg_dump_sort.c +++ b/src/compat/15/pg_dump/pg_dump_sort.c @@ -381,7 +381,8 @@ DOTypeNameCompare(const void *p1, const void *p2) if (cmpval != 0) return cmpval; } - else if (obj1->objType == DO_CONSTRAINT) + else if (obj1->objType == DO_CONSTRAINT || + obj1->objType == DO_FK_CONSTRAINT) { ConstraintInfo *robj1 = *(ConstraintInfo *const *) p1; ConstraintInfo *robj2 = *(ConstraintInfo *const *) p2; @@ -414,6 +415,19 @@ DOTypeNameCompare(const void *p1, const void *p2) return cmpval; } } + else if (obj1->objType == DO_DEFAULT_ACL) + { + DefaultACLInfo *daclobj1 = *(DefaultACLInfo *const *) p1; + DefaultACLInfo *daclobj2 = *(DefaultACLInfo *const *) p2; + + /* + * Sort by defaclrole, per pg_default_acl_role_nsp_obj_index. The + * (namespace, name) match (defaclnamespace, defaclobjtype). + */ + cmpval = strcmp(daclobj1->defaclrole, daclobj2->defaclrole); + if (cmpval != 0) + return cmpval; + } else if (obj1->objType == DO_PUBLICATION_REL) { PublicationRelInfo *probj1 = *(PublicationRelInfo *const *) p1; diff --git a/src/compat/16/pg_dump.patch b/src/compat/16/pg_dump.patch index 001dcc27..b21073d8 100644 --- a/src/compat/16/pg_dump.patch +++ b/src/compat/16/pg_dump.patch @@ -11,7 +11,7 @@ index 558a8f0..85c1370 100644 /* various user-settable parameters */ bool schemaOnly; diff --git a/src/compat/16/pg_dump/pg_dump.c b/src/compat/16/pg_dump/pg_dump.c -index 829800e..f1fcdb6 100644 +index 597c828..0e3c093 100644 --- a/src/compat/16/pg_dump/pg_dump.c +++ b/src/compat/16/pg_dump/pg_dump.c @@ -400,6 +400,7 @@ main(int argc, char **argv) diff --git a/src/compat/16/pg_dump/compress_gzip.c b/src/compat/16/pg_dump/compress_gzip.c index 63dfd966..7c63f66c 100644 --- a/src/compat/16/pg_dump/compress_gzip.c +++ b/src/compat/16/pg_dump/compress_gzip.c @@ -20,6 +20,15 @@ #ifdef HAVE_LIBZ #include "zlib.h" +/* + * We don't use the gzgetc() macro, because zlib's configuration logic is not + * robust enough to guarantee that the macro will have the same ideas about + * struct field layout as the library itself does; see for example + * https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=59711 + * Instead, #undef the macro and fall back to the underlying function. + */ +#undef gzgetc + /*---------------------- * Compressor API *---------------------- @@ -251,34 +260,53 @@ InitCompressorGzip(CompressorState *cs, *---------------------- */ -static bool -Gzip_read(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) +static size_t +Gzip_read(void *ptr, size_t size, CompressFileHandle *CFH) { gzFile gzfp = (gzFile) CFH->private_data; int gzret; + /* Reading zero bytes must be a no-op */ + if (size == 0) + return 0; + gzret = gzread(gzfp, ptr, size); - if (gzret <= 0 && !gzeof(gzfp)) + + /* + * gzread returns zero on EOF as well as some error conditions, and less + * than zero on other error conditions, so we need to inspect for EOF on + * zero. + */ + if (gzret <= 0) { int errnum; - const char *errmsg = gzerror(gzfp, &errnum); + const char *errmsg; + + if (gzret == 0 && gzeof(gzfp)) + return 0; + + errmsg = gzerror(gzfp, &errnum); pg_fatal("could not read from input file: %s", errnum == Z_ERRNO ? strerror(errno) : errmsg); } - if (rsize) - *rsize = (size_t) gzret; - - return true; + return (size_t) gzret; } -static bool +static void Gzip_write(const void *ptr, size_t size, CompressFileHandle *CFH) { gzFile gzfp = (gzFile) CFH->private_data; + int errnum; + const char *errmsg; - return gzwrite(gzfp, ptr, size) > 0; + if (gzwrite(gzfp, ptr, size) != size) + { + errmsg = gzerror(gzfp, &errnum); + pg_fatal("could not write to file: %s", + errnum == Z_ERRNO ? strerror(errno) : errmsg); + } } static int diff --git a/src/compat/16/pg_dump/compress_io.c b/src/compat/16/pg_dump/compress_io.c index efedc53a..ec8148ca 100644 --- a/src/compat/16/pg_dump/compress_io.c +++ b/src/compat/16/pg_dump/compress_io.c @@ -270,6 +270,7 @@ InitDiscoverCompressFileHandle(const char *path, const char *mode) } CFH = InitCompressFileHandle(compression_spec); + errno = 0; if (!CFH->open_func(fname, -1, mode, CFH)) { free_keep_errno(CFH); @@ -290,6 +291,7 @@ EndCompressFileHandle(CompressFileHandle *CFH) { bool ret = false; + errno = 0; if (CFH->private_data) ret = CFH->close_func(CFH); diff --git a/src/compat/16/pg_dump/compress_io.h b/src/compat/16/pg_dump/compress_io.h index 621e03a5..63c8ff90 100644 --- a/src/compat/16/pg_dump/compress_io.h +++ b/src/compat/16/pg_dump/compress_io.h @@ -123,21 +123,22 @@ struct CompressFileHandle CompressFileHandle *CFH); /* - * Read 'size' bytes of data from the file and store them into 'ptr'. - * Optionally it will store the number of bytes read in 'rsize'. + * Read up to 'size' bytes of data from the file and store them into + * 'ptr'. * - * Returns true on success and throws an internal error otherwise. + * Returns number of bytes read (this might be less than 'size' if EOF was + * reached). Exits via pg_fatal for all error conditions. */ - bool (*read_func) (void *ptr, size_t size, size_t *rsize, + size_t (*read_func) (void *ptr, size_t size, CompressFileHandle *CFH); /* * Write 'size' bytes of data into the file from 'ptr'. * - * Returns true on success and false on error. + * Returns nothing, exits via pg_fatal for all error conditions. */ - bool (*write_func) (const void *ptr, size_t size, - struct CompressFileHandle *CFH); + void (*write_func) (const void *ptr, size_t size, + CompressFileHandle *CFH); /* * Read at most size - 1 characters from the compress file handle into diff --git a/src/compat/16/pg_dump/compress_lz4.c b/src/compat/16/pg_dump/compress_lz4.c index 52214b31..6f3bd2fd 100644 --- a/src/compat/16/pg_dump/compress_lz4.c +++ b/src/compat/16/pg_dump/compress_lz4.c @@ -12,6 +12,8 @@ *------------------------------------------------------------------------- */ #include "postgres_fe.h" +#include + #include "pg_backup_utils.h" #include "compress_lz4.h" @@ -358,7 +360,6 @@ LZ4Stream_init(LZ4State *state, int size, bool compressing) return true; state->compressing = compressing; - state->inited = true; /* When compressing, write LZ4 header to the output stream. */ if (state->compressing) @@ -367,6 +368,7 @@ LZ4Stream_init(LZ4State *state, int size, bool compressing) if (!LZ4State_compression_init(state)) return false; + errno = 0; if (fwrite(state->buffer, 1, state->compressedlen, state->fp) != state->compressedlen) { errno = (errno) ? errno : ENOSPC; @@ -390,6 +392,7 @@ LZ4Stream_init(LZ4State *state, int size, bool compressing) state->overflowlen = 0; } + state->inited = true; return true; } @@ -457,7 +460,11 @@ LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag) /* Lazy init */ if (!LZ4Stream_init(state, size, false /* decompressing */ )) + { + pg_log_error("unable to initialize LZ4 library: %s", + LZ4F_getErrorName(state->errcode)); return -1; + } /* No work needs to be done for a zero-sized output buffer */ if (size <= 0) @@ -484,7 +491,10 @@ LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag) rsize = fread(readbuf, 1, size, state->fp); if (rsize < size && !feof(state->fp)) + { + pg_log_error("could not read from input file: %m"); return -1; + } rp = (char *) readbuf; rend = (char *) readbuf + rsize; @@ -501,6 +511,8 @@ LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag) if (LZ4F_isError(status)) { state->errcode = status; + pg_log_error("could not read from input file: %s", + LZ4F_getErrorName(state->errcode)); return -1; } @@ -558,7 +570,7 @@ LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag) /* * Compress size bytes from ptr and write them to the stream. */ -static bool +static void LZ4Stream_write(const void *ptr, size_t size, CompressFileHandle *CFH) { LZ4State *state = (LZ4State *) CFH->private_data; @@ -567,7 +579,8 @@ LZ4Stream_write(const void *ptr, size_t size, CompressFileHandle *CFH) /* Lazy init */ if (!LZ4Stream_init(state, size, true)) - return false; + pg_fatal("unable to initialize LZ4 library: %s", + LZ4F_getErrorName(state->errcode)); while (remaining > 0) { @@ -578,28 +591,24 @@ LZ4Stream_write(const void *ptr, size_t size, CompressFileHandle *CFH) status = LZ4F_compressUpdate(state->ctx, state->buffer, state->buflen, ptr, chunk, NULL); if (LZ4F_isError(status)) - { - state->errcode = status; - return false; - } + pg_fatal("error during writing: %s", LZ4F_getErrorName(status)); + errno = 0; if (fwrite(state->buffer, 1, status, state->fp) != status) { errno = (errno) ? errno : ENOSPC; - return false; + pg_fatal("error during writing: %m"); } ptr = ((const char *) ptr) + chunk; } - - return true; } /* * fread() equivalent implementation for LZ4 compressed files. */ -static bool -LZ4Stream_read(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) +static size_t +LZ4Stream_read(void *ptr, size_t size, CompressFileHandle *CFH) { LZ4State *state = (LZ4State *) CFH->private_data; int ret; @@ -607,10 +616,7 @@ LZ4Stream_read(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) if ((ret = LZ4Stream_read_internal(state, ptr, size, false)) < 0) pg_fatal("could not read from input file: %s", LZ4Stream_get_error(CFH)); - if (rsize) - *rsize = (size_t) ret; - - return true; + return (size_t) ret; } /* @@ -643,11 +649,13 @@ LZ4Stream_gets(char *ptr, int size, CompressFileHandle *CFH) int ret; ret = LZ4Stream_read_internal(state, ptr, size - 1, true); - if (ret < 0 || (ret == 0 && !LZ4Stream_eof(CFH))) - pg_fatal("could not read from input file: %s", LZ4Stream_get_error(CFH)); - /* Done reading */ - if (ret == 0) + /* + * LZ4Stream_read_internal returning 0 or -1 means that it was either an + * EOF or an error, but gets_func is defined to return NULL in either case + * so we can treat both the same here. + */ + if (ret <= 0) return NULL; /* @@ -669,6 +677,8 @@ LZ4Stream_close(CompressFileHandle *CFH) FILE *fp; LZ4State *state = (LZ4State *) CFH->private_data; size_t status; + int ret; + bool success = true; fp = state->fp; if (state->inited) @@ -677,25 +687,39 @@ LZ4Stream_close(CompressFileHandle *CFH) { status = LZ4F_compressEnd(state->ctx, state->buffer, state->buflen, NULL); if (LZ4F_isError(status)) - pg_fatal("could not end compression: %s", - LZ4F_getErrorName(status)); - else if (fwrite(state->buffer, 1, status, state->fp) != status) { - errno = (errno) ? errno : ENOSPC; - WRITE_ERROR_EXIT; + pg_log_error("could not end compression: %s", + LZ4F_getErrorName(status)); + success = false; + } + else + { + errno = 0; + if (fwrite(state->buffer, 1, status, state->fp) != status) + { + errno = (errno) ? errno : ENOSPC; + pg_log_error("could not write to output file: %m"); + success = false; + } } status = LZ4F_freeCompressionContext(state->ctx); if (LZ4F_isError(status)) - pg_fatal("could not end compression: %s", - LZ4F_getErrorName(status)); + { + pg_log_error("could not end compression: %s", + LZ4F_getErrorName(status)); + success = false; + } } else { status = LZ4F_freeDecompressionContext(state->dtx); if (LZ4F_isError(status)) - pg_fatal("could not end decompression: %s", - LZ4F_getErrorName(status)); + { + pg_log_error("could not end decompression: %s", + LZ4F_getErrorName(status)); + success = false; + } pg_free(state->overflowbuf); } @@ -703,29 +727,35 @@ LZ4Stream_close(CompressFileHandle *CFH) } pg_free(state); + CFH->private_data = NULL; - return fclose(fp) == 0; + errno = 0; + ret = fclose(fp); + if (ret != 0) + { + pg_log_error("could not close file: %m"); + success = false; + } + + return success; } static bool LZ4Stream_open(const char *path, int fd, const char *mode, CompressFileHandle *CFH) { - FILE *fp; LZ4State *state = (LZ4State *) CFH->private_data; if (fd >= 0) - fp = fdopen(fd, mode); + state->fp = fdopen(dup(fd), mode); else - fp = fopen(path, mode); - if (fp == NULL) + state->fp = fopen(path, mode); + if (state->fp == NULL) { state->errcode = errno; return false; } - state->fp = fp; - return true; } diff --git a/src/compat/16/pg_dump/compress_none.c b/src/compat/16/pg_dump/compress_none.c index 736a7957..9effc7e4 100644 --- a/src/compat/16/pg_dump/compress_none.c +++ b/src/compat/16/pg_dump/compress_none.c @@ -83,36 +83,32 @@ InitCompressorNone(CompressorState *cs, * Private routines */ -static bool -read_none(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) +static size_t +read_none(void *ptr, size_t size, CompressFileHandle *CFH) { FILE *fp = (FILE *) CFH->private_data; size_t ret; - if (size == 0) - return true; - ret = fread(ptr, 1, size, fp); - if (ret != size && !feof(fp)) + if (ferror(fp)) pg_fatal("could not read from input file: %s", strerror(errno)); - if (rsize) - *rsize = ret; - - return true; + return ret; } -static bool +static void write_none(const void *ptr, size_t size, CompressFileHandle *CFH) { size_t ret; + errno = 0; ret = fwrite(ptr, 1, size, (FILE *) CFH->private_data); if (ret != size) - return false; - - return true; + { + errno = (errno) ? errno : ENOSPC; + pg_fatal("could not write to file: %m"); + } } static const char * @@ -154,7 +150,12 @@ close_none(CompressFileHandle *CFH) CFH->private_data = NULL; if (fp) + { + errno = 0; ret = fclose(fp); + if (ret != 0) + pg_log_error("could not close file: %m"); + } return ret == 0; } diff --git a/src/compat/16/pg_dump/compress_zstd.c b/src/compat/16/pg_dump/compress_zstd.c index 078be033..13d5a55a 100644 --- a/src/compat/16/pg_dump/compress_zstd.c +++ b/src/compat/16/pg_dump/compress_zstd.c @@ -13,6 +13,7 @@ */ #include "postgres_fe.h" +#include #include "pg_backup_utils.h" #include "compress_zstd.h" @@ -257,8 +258,8 @@ InitCompressorZstd(CompressorState *cs, * Compressed stream API */ -static bool -Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) +static size_t +Zstd_read_internal(void *ptr, size_t size, CompressFileHandle *CFH, bool exit_on_error) { ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data; ZSTD_inBuffer *input = &zstdcs->input; @@ -267,6 +268,22 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) size_t res, cnt; + /* + * If this is the first call to the reading function, initialize the + * required datastructures. + */ + if (zstdcs->dstream == NULL) + { + zstdcs->input.src = pg_malloc0(input_allocated_size); + zstdcs->dstream = ZSTD_createDStream(); + if (zstdcs->dstream == NULL) + { + if (exit_on_error) + pg_fatal("could not initialize compression library"); + return -1; + } + } + output->size = size; output->dst = ptr; output->pos = 0; @@ -291,6 +308,13 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) if (input->pos == input->size) { cnt = fread(unconstify(void *, input->src), 1, input_allocated_size, zstdcs->fp); + if (ferror(zstdcs->fp)) + { + if (exit_on_error) + pg_fatal("could not read from input file: %m"); + return -1; + } + input->size = cnt; Assert(cnt <= input_allocated_size); @@ -306,7 +330,11 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) res = ZSTD_decompressStream(zstdcs->dstream, output, input); if (ZSTD_isError(res)) - pg_fatal("could not decompress data: %s", ZSTD_getErrorName(res)); + { + if (exit_on_error) + pg_fatal("could not decompress data: %s", ZSTD_getErrorName(res)); + return -1; + } if (output->pos == output->size) break; /* No more room for output */ @@ -319,13 +347,10 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) break; /* We read all the data that fits */ } - if (rdsize != NULL) - *rdsize = output->pos; - - return true; + return output->pos; } -static bool +static void Zstd_write(const void *ptr, size_t size, CompressFileHandle *CFH) { ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data; @@ -338,41 +363,40 @@ Zstd_write(const void *ptr, size_t size, CompressFileHandle *CFH) input->size = size; input->pos = 0; + if (zstdcs->cstream == NULL) + { + zstdcs->output.size = ZSTD_CStreamOutSize(); + zstdcs->output.dst = pg_malloc0(zstdcs->output.size); + zstdcs->cstream = _ZstdCStreamParams(CFH->compression_spec); + if (zstdcs->cstream == NULL) + pg_fatal("could not initialize compression library"); + } + /* Consume all input, to be flushed later */ while (input->pos != input->size) { output->pos = 0; res = ZSTD_compressStream2(zstdcs->cstream, output, input, ZSTD_e_continue); if (ZSTD_isError(res)) - { - zstdcs->zstderror = ZSTD_getErrorName(res); - return false; - } + pg_fatal("could not write to file: %s", ZSTD_getErrorName(res)); + errno = 0; cnt = fwrite(output->dst, 1, output->pos, zstdcs->fp); if (cnt != output->pos) { - zstdcs->zstderror = strerror(errno); - return false; + errno = (errno) ? errno : ENOSPC; + pg_fatal("could not write to file: %m"); } } - - return size; } static int Zstd_getc(CompressFileHandle *CFH) { - ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data; - int ret; + unsigned char ret; - if (CFH->read_func(&ret, 1, NULL, CFH) != 1) - { - if (feof(zstdcs->fp)) - pg_fatal("could not read from input file: end of file"); - else - pg_fatal("could not read from input file: %m"); - } + if (CFH->read_func(&ret, 1, CFH) != 1) + pg_fatal("could not read from input file: end of file"); return ret; } @@ -389,11 +413,7 @@ Zstd_gets(char *buf, int len, CompressFileHandle *CFH) */ for (i = 0; i < len - 1; ++i) { - size_t readsz; - - if (!CFH->read_func(&buf[i], 1, &readsz, CFH)) - break; - if (readsz != 1) + if (Zstd_read_internal(&buf[i], 1, CFH, false) != 1) break; if (buf[i] == '\n') { @@ -405,10 +425,17 @@ Zstd_gets(char *buf, int len, CompressFileHandle *CFH) return i > 0 ? buf : NULL; } +static size_t +Zstd_read(void *ptr, size_t size, CompressFileHandle *CFH) +{ + return Zstd_read_internal(ptr, size, CFH, true); +} + static bool Zstd_close(CompressFileHandle *CFH) { ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data; + bool success = true; if (zstdcs->cstream) { @@ -425,14 +452,18 @@ Zstd_close(CompressFileHandle *CFH) if (ZSTD_isError(res)) { zstdcs->zstderror = ZSTD_getErrorName(res); - return false; + success = false; + break; } + errno = 0; cnt = fwrite(output->dst, 1, output->pos, zstdcs->fp); if (cnt != output->pos) { + errno = (errno) ? errno : ENOSPC; zstdcs->zstderror = strerror(errno); - return false; + success = false; + break; } if (res == 0) @@ -449,11 +480,16 @@ Zstd_close(CompressFileHandle *CFH) pg_free(unconstify(void *, zstdcs->input.src)); } + errno = 0; if (fclose(zstdcs->fp) != 0) - return false; + { + zstdcs->zstderror = strerror(errno); + success = false; + } pg_free(zstdcs); - return true; + CFH->private_data = NULL; + return success; } static bool @@ -471,35 +507,33 @@ Zstd_open(const char *path, int fd, const char *mode, FILE *fp; ZstdCompressorState *zstdcs; + /* + * Clear state storage to avoid having the fd point to non-NULL memory on + * error return. + */ + CFH->private_data = NULL; + + zstdcs = (ZstdCompressorState *) pg_malloc_extended(sizeof(*zstdcs), + MCXT_ALLOC_NO_OOM | MCXT_ALLOC_ZERO); + if (!zstdcs) + { + errno = ENOMEM; + return false; + } + if (fd >= 0) - fp = fdopen(fd, mode); + fp = fdopen(dup(fd), mode); else fp = fopen(path, mode); if (fp == NULL) + { + pg_free(zstdcs); return false; + } - zstdcs = (ZstdCompressorState *) pg_malloc0(sizeof(*zstdcs)); - CFH->private_data = zstdcs; zstdcs->fp = fp; - - if (mode[0] == 'r') - { - zstdcs->input.src = pg_malloc0(ZSTD_DStreamInSize()); - zstdcs->dstream = ZSTD_createDStream(); - if (zstdcs->dstream == NULL) - pg_fatal("could not initialize compression library"); - } - else if (mode[0] == 'w' || mode[0] == 'a') - { - zstdcs->output.size = ZSTD_CStreamOutSize(); - zstdcs->output.dst = pg_malloc0(zstdcs->output.size); - zstdcs->cstream = _ZstdCStreamParams(CFH->compression_spec); - if (zstdcs->cstream == NULL) - pg_fatal("could not initialize compression library"); - } - else - pg_fatal("unhandled mode \"%s\"", mode); + CFH->private_data = zstdcs; return true; } diff --git a/src/compat/16/pg_dump/pg_backup_archiver.c b/src/compat/16/pg_dump/pg_backup_archiver.c index 1c1675fa..74e3c564 100644 --- a/src/compat/16/pg_dump/pg_backup_archiver.c +++ b/src/compat/16/pg_dump/pg_backup_archiver.c @@ -1761,8 +1761,8 @@ ahwrite(const void *ptr, size_t size, size_t nmemb, ArchiveHandle *AH) { CompressFileHandle *CFH = (CompressFileHandle *) AH->OF; - if (CFH->write_func(ptr, size * nmemb, CFH)) - bytes_written = size * nmemb; + CFH->write_func(ptr, size * nmemb, CFH); + bytes_written = size * nmemb; } if (bytes_written != size * nmemb) @@ -2855,6 +2855,20 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) if (ropt->no_comments && strcmp(te->desc, "COMMENT") == 0) return 0; + /* + * If it's a comment on a publication or a subscription, maybe ignore it. + */ + if (strcmp(te->desc, "COMMENT") == 0) + { + if (ropt->no_publications && + strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) + return 0; + + if (ropt->no_subscriptions && + strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0) + return 0; + } + /* * If it's a publication or a table part of a publication, maybe ignore * it. @@ -2869,6 +2883,21 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) if (ropt->no_security_labels && strcmp(te->desc, "SECURITY LABEL") == 0) return 0; + /* + * If it's a security label on a publication or a subscription, maybe + * ignore it. + */ + if (strcmp(te->desc, "SECURITY LABEL") == 0) + { + if (ropt->no_publications && + strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) + return 0; + + if (ropt->no_subscriptions && + strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0) + return 0; + } + /* If it's a subscription, maybe ignore it */ if (ropt->no_subscriptions && strcmp(te->desc, "SUBSCRIPTION") == 0) return 0; @@ -3109,12 +3138,14 @@ _tocEntryRestorePass(TocEntry *te) return RESTORE_PASS_POST_ACL; /* - * Comments need to be emitted in the same pass as their parent objects. - * ACLs haven't got comments, and neither do matview data objects, but - * event triggers do. (Fortunately, event triggers haven't got ACLs, or - * we'd need yet another weird special case.) + * Comments and security labels need to be emitted in the same pass as + * their parent objects. ACLs haven't got comments and security labels, + * and neither do matview data objects, but event triggers do. + * (Fortunately, event triggers haven't got ACLs, or we'd need yet another + * weird special case.) */ - if (strcmp(te->desc, "COMMENT") == 0 && + if ((strcmp(te->desc, "COMMENT") == 0 || + strcmp(te->desc, "SECURITY LABEL") == 0) && strncmp(te->tag, "EVENT TRIGGER ", 14) == 0) return RESTORE_PASS_POST_ACL; diff --git a/src/compat/16/pg_dump/pg_backup_directory.c b/src/compat/16/pg_dump/pg_backup_directory.c index 7f2ac7c7..24bcbe8e 100644 --- a/src/compat/16/pg_dump/pg_backup_directory.c +++ b/src/compat/16/pg_dump/pg_backup_directory.c @@ -347,15 +347,9 @@ _WriteData(ArchiveHandle *AH, const void *data, size_t dLen) lclContext *ctx = (lclContext *) AH->formatData; CompressFileHandle *CFH = ctx->dataFH; - errno = 0; - if (dLen > 0 && !CFH->write_func(data, dLen, CFH)) - { - /* if write didn't set errno, assume problem is no disk space */ - if (errno == 0) - errno = ENOSPC; - pg_fatal("could not write to output file: %s", - CFH->get_error_func(CFH)); - } + if (dLen <= 0) + return; + CFH->write_func(data, dLen, CFH); } /* @@ -382,7 +376,7 @@ _EndData(ArchiveHandle *AH, TocEntry *te) static void _PrintFileData(ArchiveHandle *AH, char *filename) { - size_t cnt = 0; + size_t cnt; char *buf; size_t buflen; CompressFileHandle *CFH; @@ -397,7 +391,7 @@ _PrintFileData(ArchiveHandle *AH, char *filename) buflen = DEFAULT_IO_BUFFER_SIZE; buf = pg_malloc(buflen); - while (CFH->read_func(buf, buflen, &cnt, CFH) && cnt > 0) + while ((cnt = CFH->read_func(buf, buflen, CFH)) > 0) { ahwrite(buf, 1, cnt, AH); } @@ -490,16 +484,7 @@ _WriteByte(ArchiveHandle *AH, const int i) lclContext *ctx = (lclContext *) AH->formatData; CompressFileHandle *CFH = ctx->dataFH; - errno = 0; - if (!CFH->write_func(&c, 1, CFH)) - { - /* if write didn't set errno, assume problem is no disk space */ - if (errno == 0) - errno = ENOSPC; - pg_fatal("could not write to output file: %s", - CFH->get_error_func(CFH)); - } - + CFH->write_func(&c, 1, CFH); return 1; } @@ -528,15 +513,7 @@ _WriteBuf(ArchiveHandle *AH, const void *buf, size_t len) lclContext *ctx = (lclContext *) AH->formatData; CompressFileHandle *CFH = ctx->dataFH; - errno = 0; - if (!CFH->write_func(buf, len, CFH)) - { - /* if write didn't set errno, assume problem is no disk space */ - if (errno == 0) - errno = ENOSPC; - pg_fatal("could not write to output file: %s", - CFH->get_error_func(CFH)); - } + CFH->write_func(buf, len, CFH); } /* @@ -551,10 +528,10 @@ _ReadBuf(ArchiveHandle *AH, void *buf, size_t len) CompressFileHandle *CFH = ctx->dataFH; /* - * If there was an I/O error, we already exited in readF(), so here we - * exit on short reads. + * We do not expect a short read, so fail if we get one. The read_func + * already dealt with any outright I/O error. */ - if (!CFH->read_func(buf, len, NULL, CFH)) + if (CFH->read_func(buf, len, CFH) != len) pg_fatal("could not read from input file: end of file"); } @@ -696,14 +673,7 @@ _EndLO(ArchiveHandle *AH, TocEntry *te, Oid oid) /* register the LO in blobs.toc */ len = snprintf(buf, sizeof(buf), "%u blob_%u.dat\n", oid, oid); - if (!CFH->write_func(buf, len, CFH)) - { - /* if write didn't set errno, assume problem is no disk space */ - if (errno == 0) - errno = ENOSPC; - pg_fatal("could not write to LOs TOC file: %s", - CFH->get_error_func(CFH)); - } + CFH->write_func(buf, len, CFH); } /* diff --git a/src/compat/16/pg_dump/pg_dump.c b/src/compat/16/pg_dump/pg_dump.c index f1fcdb69..0e3c0931 100644 --- a/src/compat/16/pg_dump/pg_dump.c +++ b/src/compat/16/pg_dump/pg_dump.c @@ -15266,7 +15266,7 @@ collectSecLabels(Archive *fout) appendPQExpBufferStr(query, "SELECT label, provider, classoid, objoid, objsubid " - "FROM pg_catalog.pg_seclabel " + "FROM pg_catalog.pg_seclabels " "ORDER BY classoid, objoid, objsubid"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -17094,7 +17094,7 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) dumpComment(fout, conprefix->data, qtypname, tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, - coninfo->dobj.catId, 0, tyinfo->dobj.dumpId); + coninfo->dobj.catId, 0, coninfo->dobj.dumpId); destroyPQExpBuffer(conprefix); free(qtypname); } @@ -17785,6 +17785,11 @@ dumpEventTrigger(Archive *fout, const EventTriggerInfo *evtinfo) NULL, evtinfo->evtowner, evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId); + if (evtinfo->dobj.dump & DUMP_COMPONENT_SECLABEL) + dumpSecLabel(fout, "EVENT TRIGGER", qevtname, + NULL, evtinfo->evtowner, + evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId); + destroyPQExpBuffer(query); destroyPQExpBuffer(delqry); free(qevtname); diff --git a/src/compat/16/pg_dump/pg_dump_sort.c b/src/compat/16/pg_dump/pg_dump_sort.c index 3d3b91fa..44ba9bc3 100644 --- a/src/compat/16/pg_dump/pg_dump_sort.c +++ b/src/compat/16/pg_dump/pg_dump_sort.c @@ -381,7 +381,8 @@ DOTypeNameCompare(const void *p1, const void *p2) if (cmpval != 0) return cmpval; } - else if (obj1->objType == DO_CONSTRAINT) + else if (obj1->objType == DO_CONSTRAINT || + obj1->objType == DO_FK_CONSTRAINT) { ConstraintInfo *robj1 = *(ConstraintInfo *const *) p1; ConstraintInfo *robj2 = *(ConstraintInfo *const *) p2; @@ -414,6 +415,19 @@ DOTypeNameCompare(const void *p1, const void *p2) return cmpval; } } + else if (obj1->objType == DO_DEFAULT_ACL) + { + DefaultACLInfo *daclobj1 = *(DefaultACLInfo *const *) p1; + DefaultACLInfo *daclobj2 = *(DefaultACLInfo *const *) p2; + + /* + * Sort by defaclrole, per pg_default_acl_role_nsp_obj_index. The + * (namespace, name) match (defaclnamespace, defaclobjtype). + */ + cmpval = strcmp(daclobj1->defaclrole, daclobj2->defaclrole); + if (cmpval != 0) + return cmpval; + } else if (obj1->objType == DO_PUBLICATION_REL) { PublicationRelInfo *probj1 = *(PublicationRelInfo *const *) p1; diff --git a/src/compat/17/pg_dump.patch b/src/compat/17/pg_dump.patch index 2d3b5d3b..dcbe03c1 100644 --- a/src/compat/17/pg_dump.patch +++ b/src/compat/17/pg_dump.patch @@ -11,7 +11,7 @@ index 609635c..20abb98 100644 /* various user-settable parameters */ bool schemaOnly; diff --git a/src/compat/17/pg_dump/pg_dump.c b/src/compat/17/pg_dump/pg_dump.c -index 13139c9..9ccbe73 100644 +index e59d562..bcf13bb 100644 --- a/src/compat/17/pg_dump/pg_dump.c +++ b/src/compat/17/pg_dump/pg_dump.c @@ -415,6 +415,7 @@ main(int argc, char **argv) diff --git a/src/compat/17/pg_dump/compress_gzip.c b/src/compat/17/pg_dump/compress_gzip.c index 9e1b7c15..c327ba38 100644 --- a/src/compat/17/pg_dump/compress_gzip.c +++ b/src/compat/17/pg_dump/compress_gzip.c @@ -20,6 +20,15 @@ #ifdef HAVE_LIBZ #include "zlib.h" +/* + * We don't use the gzgetc() macro, because zlib's configuration logic is not + * robust enough to guarantee that the macro will have the same ideas about + * struct field layout as the library itself does; see for example + * https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=59711 + * Instead, #undef the macro and fall back to the underlying function. + */ +#undef gzgetc + /*---------------------- * Compressor API *---------------------- @@ -251,34 +260,53 @@ InitCompressorGzip(CompressorState *cs, *---------------------- */ -static bool -Gzip_read(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) +static size_t +Gzip_read(void *ptr, size_t size, CompressFileHandle *CFH) { gzFile gzfp = (gzFile) CFH->private_data; int gzret; + /* Reading zero bytes must be a no-op */ + if (size == 0) + return 0; + gzret = gzread(gzfp, ptr, size); - if (gzret <= 0 && !gzeof(gzfp)) + + /* + * gzread returns zero on EOF as well as some error conditions, and less + * than zero on other error conditions, so we need to inspect for EOF on + * zero. + */ + if (gzret <= 0) { int errnum; - const char *errmsg = gzerror(gzfp, &errnum); + const char *errmsg; + + if (gzret == 0 && gzeof(gzfp)) + return 0; + + errmsg = gzerror(gzfp, &errnum); pg_fatal("could not read from input file: %s", errnum == Z_ERRNO ? strerror(errno) : errmsg); } - if (rsize) - *rsize = (size_t) gzret; - - return true; + return (size_t) gzret; } -static bool +static void Gzip_write(const void *ptr, size_t size, CompressFileHandle *CFH) { gzFile gzfp = (gzFile) CFH->private_data; + int errnum; + const char *errmsg; - return gzwrite(gzfp, ptr, size) > 0; + if (gzwrite(gzfp, ptr, size) != size) + { + errmsg = gzerror(gzfp, &errnum); + pg_fatal("could not write to file: %s", + errnum == Z_ERRNO ? strerror(errno) : errmsg); + } } static int diff --git a/src/compat/17/pg_dump/compress_io.c b/src/compat/17/pg_dump/compress_io.c index e2edf8bf..1ee1a2fe 100644 --- a/src/compat/17/pg_dump/compress_io.c +++ b/src/compat/17/pg_dump/compress_io.c @@ -270,6 +270,7 @@ InitDiscoverCompressFileHandle(const char *path, const char *mode) } CFH = InitCompressFileHandle(compression_spec); + errno = 0; if (!CFH->open_func(fname, -1, mode, CFH)) { free_keep_errno(CFH); @@ -290,6 +291,7 @@ EndCompressFileHandle(CompressFileHandle *CFH) { bool ret = false; + errno = 0; if (CFH->private_data) ret = CFH->close_func(CFH); diff --git a/src/compat/17/pg_dump/compress_io.h b/src/compat/17/pg_dump/compress_io.h index bbbf1582..e9e68423 100644 --- a/src/compat/17/pg_dump/compress_io.h +++ b/src/compat/17/pg_dump/compress_io.h @@ -123,21 +123,22 @@ struct CompressFileHandle CompressFileHandle *CFH); /* - * Read 'size' bytes of data from the file and store them into 'ptr'. - * Optionally it will store the number of bytes read in 'rsize'. + * Read up to 'size' bytes of data from the file and store them into + * 'ptr'. * - * Returns true on success and throws an internal error otherwise. + * Returns number of bytes read (this might be less than 'size' if EOF was + * reached). Exits via pg_fatal for all error conditions. */ - bool (*read_func) (void *ptr, size_t size, size_t *rsize, + size_t (*read_func) (void *ptr, size_t size, CompressFileHandle *CFH); /* * Write 'size' bytes of data into the file from 'ptr'. * - * Returns true on success and false on error. + * Returns nothing, exits via pg_fatal for all error conditions. */ - bool (*write_func) (const void *ptr, size_t size, - struct CompressFileHandle *CFH); + void (*write_func) (const void *ptr, size_t size, + CompressFileHandle *CFH); /* * Read at most size - 1 characters from the compress file handle into diff --git a/src/compat/17/pg_dump/compress_lz4.c b/src/compat/17/pg_dump/compress_lz4.c index 7f72492b..634cb7ff 100644 --- a/src/compat/17/pg_dump/compress_lz4.c +++ b/src/compat/17/pg_dump/compress_lz4.c @@ -12,6 +12,7 @@ *------------------------------------------------------------------------- */ #include "postgres_fe.h" +#include #include "compress_lz4.h" #include "pg_backup_utils.h" @@ -358,7 +359,6 @@ LZ4Stream_init(LZ4State *state, int size, bool compressing) return true; state->compressing = compressing; - state->inited = true; /* When compressing, write LZ4 header to the output stream. */ if (state->compressing) @@ -367,6 +367,7 @@ LZ4Stream_init(LZ4State *state, int size, bool compressing) if (!LZ4State_compression_init(state)) return false; + errno = 0; if (fwrite(state->buffer, 1, state->compressedlen, state->fp) != state->compressedlen) { errno = (errno) ? errno : ENOSPC; @@ -390,6 +391,7 @@ LZ4Stream_init(LZ4State *state, int size, bool compressing) state->overflowlen = 0; } + state->inited = true; return true; } @@ -457,7 +459,11 @@ LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag) /* Lazy init */ if (!LZ4Stream_init(state, size, false /* decompressing */ )) + { + pg_log_error("unable to initialize LZ4 library: %s", + LZ4F_getErrorName(state->errcode)); return -1; + } /* No work needs to be done for a zero-sized output buffer */ if (size <= 0) @@ -484,7 +490,10 @@ LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag) rsize = fread(readbuf, 1, size, state->fp); if (rsize < size && !feof(state->fp)) + { + pg_log_error("could not read from input file: %m"); return -1; + } rp = (char *) readbuf; rend = (char *) readbuf + rsize; @@ -501,6 +510,8 @@ LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag) if (LZ4F_isError(status)) { state->errcode = status; + pg_log_error("could not read from input file: %s", + LZ4F_getErrorName(state->errcode)); return -1; } @@ -558,7 +569,7 @@ LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag) /* * Compress size bytes from ptr and write them to the stream. */ -static bool +static void LZ4Stream_write(const void *ptr, size_t size, CompressFileHandle *CFH) { LZ4State *state = (LZ4State *) CFH->private_data; @@ -567,7 +578,8 @@ LZ4Stream_write(const void *ptr, size_t size, CompressFileHandle *CFH) /* Lazy init */ if (!LZ4Stream_init(state, size, true)) - return false; + pg_fatal("unable to initialize LZ4 library: %s", + LZ4F_getErrorName(state->errcode)); while (remaining > 0) { @@ -578,28 +590,24 @@ LZ4Stream_write(const void *ptr, size_t size, CompressFileHandle *CFH) status = LZ4F_compressUpdate(state->ctx, state->buffer, state->buflen, ptr, chunk, NULL); if (LZ4F_isError(status)) - { - state->errcode = status; - return false; - } + pg_fatal("error during writing: %s", LZ4F_getErrorName(status)); + errno = 0; if (fwrite(state->buffer, 1, status, state->fp) != status) { errno = (errno) ? errno : ENOSPC; - return false; + pg_fatal("error during writing: %m"); } ptr = ((const char *) ptr) + chunk; } - - return true; } /* * fread() equivalent implementation for LZ4 compressed files. */ -static bool -LZ4Stream_read(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) +static size_t +LZ4Stream_read(void *ptr, size_t size, CompressFileHandle *CFH) { LZ4State *state = (LZ4State *) CFH->private_data; int ret; @@ -607,10 +615,7 @@ LZ4Stream_read(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) if ((ret = LZ4Stream_read_internal(state, ptr, size, false)) < 0) pg_fatal("could not read from input file: %s", LZ4Stream_get_error(CFH)); - if (rsize) - *rsize = (size_t) ret; - - return true; + return (size_t) ret; } /* @@ -643,11 +648,13 @@ LZ4Stream_gets(char *ptr, int size, CompressFileHandle *CFH) int ret; ret = LZ4Stream_read_internal(state, ptr, size - 1, true); - if (ret < 0 || (ret == 0 && !LZ4Stream_eof(CFH))) - pg_fatal("could not read from input file: %s", LZ4Stream_get_error(CFH)); - /* Done reading */ - if (ret == 0) + /* + * LZ4Stream_read_internal returning 0 or -1 means that it was either an + * EOF or an error, but gets_func is defined to return NULL in either case + * so we can treat both the same here. + */ + if (ret <= 0) return NULL; /* @@ -669,6 +676,8 @@ LZ4Stream_close(CompressFileHandle *CFH) FILE *fp; LZ4State *state = (LZ4State *) CFH->private_data; size_t status; + int ret; + bool success = true; fp = state->fp; if (state->inited) @@ -677,25 +686,39 @@ LZ4Stream_close(CompressFileHandle *CFH) { status = LZ4F_compressEnd(state->ctx, state->buffer, state->buflen, NULL); if (LZ4F_isError(status)) - pg_fatal("could not end compression: %s", - LZ4F_getErrorName(status)); - else if (fwrite(state->buffer, 1, status, state->fp) != status) { - errno = (errno) ? errno : ENOSPC; - WRITE_ERROR_EXIT; + pg_log_error("could not end compression: %s", + LZ4F_getErrorName(status)); + success = false; + } + else + { + errno = 0; + if (fwrite(state->buffer, 1, status, state->fp) != status) + { + errno = (errno) ? errno : ENOSPC; + pg_log_error("could not write to output file: %m"); + success = false; + } } status = LZ4F_freeCompressionContext(state->ctx); if (LZ4F_isError(status)) - pg_fatal("could not end compression: %s", - LZ4F_getErrorName(status)); + { + pg_log_error("could not end compression: %s", + LZ4F_getErrorName(status)); + success = false; + } } else { status = LZ4F_freeDecompressionContext(state->dtx); if (LZ4F_isError(status)) - pg_fatal("could not end decompression: %s", - LZ4F_getErrorName(status)); + { + pg_log_error("could not end decompression: %s", + LZ4F_getErrorName(status)); + success = false; + } pg_free(state->overflowbuf); } @@ -703,29 +726,35 @@ LZ4Stream_close(CompressFileHandle *CFH) } pg_free(state); + CFH->private_data = NULL; - return fclose(fp) == 0; + errno = 0; + ret = fclose(fp); + if (ret != 0) + { + pg_log_error("could not close file: %m"); + success = false; + } + + return success; } static bool LZ4Stream_open(const char *path, int fd, const char *mode, CompressFileHandle *CFH) { - FILE *fp; LZ4State *state = (LZ4State *) CFH->private_data; if (fd >= 0) - fp = fdopen(fd, mode); + state->fp = fdopen(dup(fd), mode); else - fp = fopen(path, mode); - if (fp == NULL) + state->fp = fopen(path, mode); + if (state->fp == NULL) { state->errcode = errno; return false; } - state->fp = fp; - return true; } diff --git a/src/compat/17/pg_dump/compress_none.c b/src/compat/17/pg_dump/compress_none.c index f3a524d8..35f4525f 100644 --- a/src/compat/17/pg_dump/compress_none.c +++ b/src/compat/17/pg_dump/compress_none.c @@ -83,35 +83,31 @@ InitCompressorNone(CompressorState *cs, * Private routines */ -static bool -read_none(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) +static size_t +read_none(void *ptr, size_t size, CompressFileHandle *CFH) { FILE *fp = (FILE *) CFH->private_data; size_t ret; - if (size == 0) - return true; - ret = fread(ptr, 1, size, fp); - if (ret != size && !feof(fp)) + if (ferror(fp)) pg_fatal("could not read from input file: %m"); - if (rsize) - *rsize = ret; - - return true; + return ret; } -static bool +static void write_none(const void *ptr, size_t size, CompressFileHandle *CFH) { size_t ret; + errno = 0; ret = fwrite(ptr, 1, size, (FILE *) CFH->private_data); if (ret != size) - return false; - - return true; + { + errno = (errno) ? errno : ENOSPC; + pg_fatal("could not write to file: %m"); + } } static const char * @@ -153,7 +149,12 @@ close_none(CompressFileHandle *CFH) CFH->private_data = NULL; if (fp) + { + errno = 0; ret = fclose(fp); + if (ret != 0) + pg_log_error("could not close file: %m"); + } return ret == 0; } diff --git a/src/compat/17/pg_dump/compress_zstd.c b/src/compat/17/pg_dump/compress_zstd.c index 02987d6b..492b28a0 100644 --- a/src/compat/17/pg_dump/compress_zstd.c +++ b/src/compat/17/pg_dump/compress_zstd.c @@ -13,6 +13,7 @@ */ #include "postgres_fe.h" +#include #include "compress_zstd.h" #include "pg_backup_utils.h" @@ -257,8 +258,8 @@ InitCompressorZstd(CompressorState *cs, * Compressed stream API */ -static bool -Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) +static size_t +Zstd_read_internal(void *ptr, size_t size, CompressFileHandle *CFH, bool exit_on_error) { ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data; ZSTD_inBuffer *input = &zstdcs->input; @@ -267,6 +268,22 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) size_t res, cnt; + /* + * If this is the first call to the reading function, initialize the + * required datastructures. + */ + if (zstdcs->dstream == NULL) + { + zstdcs->input.src = pg_malloc0(input_allocated_size); + zstdcs->dstream = ZSTD_createDStream(); + if (zstdcs->dstream == NULL) + { + if (exit_on_error) + pg_fatal("could not initialize compression library"); + return -1; + } + } + output->size = size; output->dst = ptr; output->pos = 0; @@ -291,6 +308,13 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) if (input->pos == input->size) { cnt = fread(unconstify(void *, input->src), 1, input_allocated_size, zstdcs->fp); + if (ferror(zstdcs->fp)) + { + if (exit_on_error) + pg_fatal("could not read from input file: %m"); + return -1; + } + input->size = cnt; Assert(cnt <= input_allocated_size); @@ -306,7 +330,11 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) res = ZSTD_decompressStream(zstdcs->dstream, output, input); if (ZSTD_isError(res)) - pg_fatal("could not decompress data: %s", ZSTD_getErrorName(res)); + { + if (exit_on_error) + pg_fatal("could not decompress data: %s", ZSTD_getErrorName(res)); + return -1; + } if (output->pos == output->size) break; /* No more room for output */ @@ -319,13 +347,10 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) break; /* We read all the data that fits */ } - if (rdsize != NULL) - *rdsize = output->pos; - - return true; + return output->pos; } -static bool +static void Zstd_write(const void *ptr, size_t size, CompressFileHandle *CFH) { ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data; @@ -338,41 +363,40 @@ Zstd_write(const void *ptr, size_t size, CompressFileHandle *CFH) input->size = size; input->pos = 0; + if (zstdcs->cstream == NULL) + { + zstdcs->output.size = ZSTD_CStreamOutSize(); + zstdcs->output.dst = pg_malloc0(zstdcs->output.size); + zstdcs->cstream = _ZstdCStreamParams(CFH->compression_spec); + if (zstdcs->cstream == NULL) + pg_fatal("could not initialize compression library"); + } + /* Consume all input, to be flushed later */ while (input->pos != input->size) { output->pos = 0; res = ZSTD_compressStream2(zstdcs->cstream, output, input, ZSTD_e_continue); if (ZSTD_isError(res)) - { - zstdcs->zstderror = ZSTD_getErrorName(res); - return false; - } + pg_fatal("could not write to file: %s", ZSTD_getErrorName(res)); + errno = 0; cnt = fwrite(output->dst, 1, output->pos, zstdcs->fp); if (cnt != output->pos) { - zstdcs->zstderror = strerror(errno); - return false; + errno = (errno) ? errno : ENOSPC; + pg_fatal("could not write to file: %m"); } } - - return size; } static int Zstd_getc(CompressFileHandle *CFH) { - ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data; - int ret; + unsigned char ret; - if (CFH->read_func(&ret, 1, NULL, CFH) != 1) - { - if (feof(zstdcs->fp)) - pg_fatal("could not read from input file: end of file"); - else - pg_fatal("could not read from input file: %m"); - } + if (CFH->read_func(&ret, 1, CFH) != 1) + pg_fatal("could not read from input file: end of file"); return ret; } @@ -389,11 +413,7 @@ Zstd_gets(char *buf, int len, CompressFileHandle *CFH) */ for (i = 0; i < len - 1; ++i) { - size_t readsz; - - if (!CFH->read_func(&buf[i], 1, &readsz, CFH)) - break; - if (readsz != 1) + if (Zstd_read_internal(&buf[i], 1, CFH, false) != 1) break; if (buf[i] == '\n') { @@ -405,10 +425,17 @@ Zstd_gets(char *buf, int len, CompressFileHandle *CFH) return i > 0 ? buf : NULL; } +static size_t +Zstd_read(void *ptr, size_t size, CompressFileHandle *CFH) +{ + return Zstd_read_internal(ptr, size, CFH, true); +} + static bool Zstd_close(CompressFileHandle *CFH) { ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data; + bool success = true; if (zstdcs->cstream) { @@ -425,14 +452,18 @@ Zstd_close(CompressFileHandle *CFH) if (ZSTD_isError(res)) { zstdcs->zstderror = ZSTD_getErrorName(res); - return false; + success = false; + break; } + errno = 0; cnt = fwrite(output->dst, 1, output->pos, zstdcs->fp); if (cnt != output->pos) { + errno = (errno) ? errno : ENOSPC; zstdcs->zstderror = strerror(errno); - return false; + success = false; + break; } if (res == 0) @@ -449,11 +480,16 @@ Zstd_close(CompressFileHandle *CFH) pg_free(unconstify(void *, zstdcs->input.src)); } + errno = 0; if (fclose(zstdcs->fp) != 0) - return false; + { + zstdcs->zstderror = strerror(errno); + success = false; + } pg_free(zstdcs); - return true; + CFH->private_data = NULL; + return success; } static bool @@ -471,35 +507,33 @@ Zstd_open(const char *path, int fd, const char *mode, FILE *fp; ZstdCompressorState *zstdcs; + /* + * Clear state storage to avoid having the fd point to non-NULL memory on + * error return. + */ + CFH->private_data = NULL; + + zstdcs = (ZstdCompressorState *) pg_malloc_extended(sizeof(*zstdcs), + MCXT_ALLOC_NO_OOM | MCXT_ALLOC_ZERO); + if (!zstdcs) + { + errno = ENOMEM; + return false; + } + if (fd >= 0) - fp = fdopen(fd, mode); + fp = fdopen(dup(fd), mode); else fp = fopen(path, mode); if (fp == NULL) + { + pg_free(zstdcs); return false; + } - zstdcs = (ZstdCompressorState *) pg_malloc0(sizeof(*zstdcs)); - CFH->private_data = zstdcs; zstdcs->fp = fp; - - if (mode[0] == 'r') - { - zstdcs->input.src = pg_malloc0(ZSTD_DStreamInSize()); - zstdcs->dstream = ZSTD_createDStream(); - if (zstdcs->dstream == NULL) - pg_fatal("could not initialize compression library"); - } - else if (mode[0] == 'w' || mode[0] == 'a') - { - zstdcs->output.size = ZSTD_CStreamOutSize(); - zstdcs->output.dst = pg_malloc0(zstdcs->output.size); - zstdcs->cstream = _ZstdCStreamParams(CFH->compression_spec); - if (zstdcs->cstream == NULL) - pg_fatal("could not initialize compression library"); - } - else - pg_fatal("unhandled mode \"%s\"", mode); + CFH->private_data = zstdcs; return true; } diff --git a/src/compat/17/pg_dump/pg_backup_archiver.c b/src/compat/17/pg_dump/pg_backup_archiver.c index 4717e6f6..758ee098 100644 --- a/src/compat/17/pg_dump/pg_backup_archiver.c +++ b/src/compat/17/pg_dump/pg_backup_archiver.c @@ -1861,8 +1861,8 @@ ahwrite(const void *ptr, size_t size, size_t nmemb, ArchiveHandle *AH) { CompressFileHandle *CFH = (CompressFileHandle *) AH->OF; - if (CFH->write_func(ptr, size * nmemb, CFH)) - bytes_written = size * nmemb; + CFH->write_func(ptr, size * nmemb, CFH); + bytes_written = size * nmemb; } if (bytes_written != size * nmemb) @@ -2960,6 +2960,20 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) if (ropt->no_comments && strcmp(te->desc, "COMMENT") == 0) return 0; + /* + * If it's a comment on a publication or a subscription, maybe ignore it. + */ + if (strcmp(te->desc, "COMMENT") == 0) + { + if (ropt->no_publications && + strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) + return 0; + + if (ropt->no_subscriptions && + strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0) + return 0; + } + /* * If it's a publication or a table part of a publication, maybe ignore * it. @@ -2974,6 +2988,21 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) if (ropt->no_security_labels && strcmp(te->desc, "SECURITY LABEL") == 0) return 0; + /* + * If it's a security label on a publication or a subscription, maybe + * ignore it. + */ + if (strcmp(te->desc, "SECURITY LABEL") == 0) + { + if (ropt->no_publications && + strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) + return 0; + + if (ropt->no_subscriptions && + strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0) + return 0; + } + /* If it's a subscription, maybe ignore it */ if (ropt->no_subscriptions && strcmp(te->desc, "SUBSCRIPTION") == 0) return 0; @@ -3216,12 +3245,14 @@ _tocEntryRestorePass(TocEntry *te) return RESTORE_PASS_POST_ACL; /* - * Comments need to be emitted in the same pass as their parent objects. - * ACLs haven't got comments, and neither do matview data objects, but - * event triggers do. (Fortunately, event triggers haven't got ACLs, or - * we'd need yet another weird special case.) + * Comments and security labels need to be emitted in the same pass as + * their parent objects. ACLs haven't got comments and security labels, + * and neither do matview data objects, but event triggers do. + * (Fortunately, event triggers haven't got ACLs, or we'd need yet another + * weird special case.) */ - if (strcmp(te->desc, "COMMENT") == 0 && + if ((strcmp(te->desc, "COMMENT") == 0 || + strcmp(te->desc, "SECURITY LABEL") == 0) && strncmp(te->tag, "EVENT TRIGGER ", 14) == 0) return RESTORE_PASS_POST_ACL; diff --git a/src/compat/17/pg_dump/pg_backup_directory.c b/src/compat/17/pg_dump/pg_backup_directory.c index 6b93b0dc..bbaac2b4 100644 --- a/src/compat/17/pg_dump/pg_backup_directory.c +++ b/src/compat/17/pg_dump/pg_backup_directory.c @@ -348,15 +348,9 @@ _WriteData(ArchiveHandle *AH, const void *data, size_t dLen) lclContext *ctx = (lclContext *) AH->formatData; CompressFileHandle *CFH = ctx->dataFH; - errno = 0; - if (dLen > 0 && !CFH->write_func(data, dLen, CFH)) - { - /* if write didn't set errno, assume problem is no disk space */ - if (errno == 0) - errno = ENOSPC; - pg_fatal("could not write to output file: %s", - CFH->get_error_func(CFH)); - } + if (dLen <= 0) + return; + CFH->write_func(data, dLen, CFH); } /* @@ -383,7 +377,7 @@ _EndData(ArchiveHandle *AH, TocEntry *te) static void _PrintFileData(ArchiveHandle *AH, char *filename) { - size_t cnt = 0; + size_t cnt; char *buf; size_t buflen; CompressFileHandle *CFH; @@ -398,7 +392,7 @@ _PrintFileData(ArchiveHandle *AH, char *filename) buflen = DEFAULT_IO_BUFFER_SIZE; buf = pg_malloc(buflen); - while (CFH->read_func(buf, buflen, &cnt, CFH) && cnt > 0) + while ((cnt = CFH->read_func(buf, buflen, CFH)) > 0) { ahwrite(buf, 1, cnt, AH); } @@ -502,16 +496,7 @@ _WriteByte(ArchiveHandle *AH, const int i) lclContext *ctx = (lclContext *) AH->formatData; CompressFileHandle *CFH = ctx->dataFH; - errno = 0; - if (!CFH->write_func(&c, 1, CFH)) - { - /* if write didn't set errno, assume problem is no disk space */ - if (errno == 0) - errno = ENOSPC; - pg_fatal("could not write to output file: %s", - CFH->get_error_func(CFH)); - } - + CFH->write_func(&c, 1, CFH); return 1; } @@ -540,15 +525,7 @@ _WriteBuf(ArchiveHandle *AH, const void *buf, size_t len) lclContext *ctx = (lclContext *) AH->formatData; CompressFileHandle *CFH = ctx->dataFH; - errno = 0; - if (!CFH->write_func(buf, len, CFH)) - { - /* if write didn't set errno, assume problem is no disk space */ - if (errno == 0) - errno = ENOSPC; - pg_fatal("could not write to output file: %s", - CFH->get_error_func(CFH)); - } + CFH->write_func(buf, len, CFH); } /* @@ -563,10 +540,10 @@ _ReadBuf(ArchiveHandle *AH, void *buf, size_t len) CompressFileHandle *CFH = ctx->dataFH; /* - * If there was an I/O error, we already exited in readF(), so here we - * exit on short reads. + * We do not expect a short read, so fail if we get one. The read_func + * already dealt with any outright I/O error. */ - if (!CFH->read_func(buf, len, NULL, CFH)) + if (CFH->read_func(buf, len, CFH) != len) pg_fatal("could not read from input file: end of file"); } @@ -709,14 +686,7 @@ _EndLO(ArchiveHandle *AH, TocEntry *te, Oid oid) /* register the LO in blobs_NNN.toc */ len = snprintf(buf, sizeof(buf), "%u blob_%u.dat\n", oid, oid); - if (!CFH->write_func(buf, len, CFH)) - { - /* if write didn't set errno, assume problem is no disk space */ - if (errno == 0) - errno = ENOSPC; - pg_fatal("could not write to LOs TOC file: %s", - CFH->get_error_func(CFH)); - } + CFH->write_func(buf, len, CFH); } /* diff --git a/src/compat/17/pg_dump/pg_dump.c b/src/compat/17/pg_dump/pg_dump.c index 9ccbe737..bcf13bb1 100644 --- a/src/compat/17/pg_dump/pg_dump.c +++ b/src/compat/17/pg_dump/pg_dump.c @@ -5058,7 +5058,9 @@ getSubscriptionTables(Archive *fout) subrinfo[i].dobj.catId.tableoid = relid; subrinfo[i].dobj.catId.oid = cur_srsubid; AssignDumpId(&subrinfo[i].dobj); - subrinfo[i].dobj.name = pg_strdup(subinfo->dobj.name); + subrinfo[i].dobj.namespace = tblinfo->dobj.namespace; + subrinfo[i].dobj.name = tblinfo->dobj.name; + subrinfo[i].subinfo = subinfo; subrinfo[i].tblinfo = tblinfo; subrinfo[i].srsubstate = PQgetvalue(res, i, i_srsubstate)[0]; if (PQgetisnull(res, i, i_srsublsn)) @@ -5066,8 +5068,6 @@ getSubscriptionTables(Archive *fout) else subrinfo[i].srsublsn = pg_strdup(PQgetvalue(res, i, i_srsublsn)); - subrinfo[i].subinfo = subinfo; - /* Decide whether we want to dump it */ selectDumpableObject(&(subrinfo[i].dobj), fout); } @@ -5095,7 +5095,7 @@ dumpSubscriptionTable(Archive *fout, const SubRelInfo *subrinfo) Assert(fout->dopt->binary_upgrade && fout->remoteVersion >= 170000); - tag = psprintf("%s %s", subinfo->dobj.name, subrinfo->dobj.name); + tag = psprintf("%s %s", subinfo->dobj.name, subrinfo->tblinfo->dobj.name); query = createPQExpBuffer(); @@ -5110,7 +5110,7 @@ dumpSubscriptionTable(Archive *fout, const SubRelInfo *subrinfo) "\n-- For binary upgrade, must preserve the subscriber table.\n"); appendPQExpBufferStr(query, "SELECT pg_catalog.binary_upgrade_add_sub_rel_state("); - appendStringLiteralAH(query, subrinfo->dobj.name, fout); + appendStringLiteralAH(query, subinfo->dobj.name, fout); appendPQExpBuffer(query, ", %u, '%c'", subrinfo->tblinfo->dobj.catId.oid, @@ -15648,7 +15648,7 @@ collectSecLabels(Archive *fout) appendPQExpBufferStr(query, "SELECT label, provider, classoid, objoid, objsubid " - "FROM pg_catalog.pg_seclabel " + "FROM pg_catalog.pg_seclabels " "ORDER BY classoid, objoid, objsubid"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -17548,7 +17548,7 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) dumpComment(fout, conprefix->data, qtypname, tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, - coninfo->dobj.catId, 0, tyinfo->dobj.dumpId); + coninfo->dobj.catId, 0, coninfo->dobj.dumpId); destroyPQExpBuffer(conprefix); free(qtypname); } @@ -18123,6 +18123,11 @@ dumpEventTrigger(Archive *fout, const EventTriggerInfo *evtinfo) NULL, evtinfo->evtowner, evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId); + if (evtinfo->dobj.dump & DUMP_COMPONENT_SECLABEL) + dumpSecLabel(fout, "EVENT TRIGGER", qevtname, + NULL, evtinfo->evtowner, + evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId); + destroyPQExpBuffer(query); destroyPQExpBuffer(delqry); free(qevtname); diff --git a/src/compat/17/pg_dump/pg_dump_sort.c b/src/compat/17/pg_dump/pg_dump_sort.c index ffaceb93..7618c1bd 100644 --- a/src/compat/17/pg_dump/pg_dump_sort.c +++ b/src/compat/17/pg_dump/pg_dump_sort.c @@ -384,7 +384,8 @@ DOTypeNameCompare(const void *p1, const void *p2) if (cmpval != 0) return cmpval; } - else if (obj1->objType == DO_CONSTRAINT) + else if (obj1->objType == DO_CONSTRAINT || + obj1->objType == DO_FK_CONSTRAINT) { ConstraintInfo *robj1 = *(ConstraintInfo *const *) p1; ConstraintInfo *robj2 = *(ConstraintInfo *const *) p2; @@ -417,6 +418,19 @@ DOTypeNameCompare(const void *p1, const void *p2) return cmpval; } } + else if (obj1->objType == DO_DEFAULT_ACL) + { + DefaultACLInfo *daclobj1 = *(DefaultACLInfo *const *) p1; + DefaultACLInfo *daclobj2 = *(DefaultACLInfo *const *) p2; + + /* + * Sort by defaclrole, per pg_default_acl_role_nsp_obj_index. The + * (namespace, name) match (defaclnamespace, defaclobjtype). + */ + cmpval = strcmp(daclobj1->defaclrole, daclobj2->defaclrole); + if (cmpval != 0) + return cmpval; + } else if (obj1->objType == DO_PUBLICATION_REL) { PublicationRelInfo *probj1 = *(PublicationRelInfo *const *) p1; @@ -439,6 +453,17 @@ DOTypeNameCompare(const void *p1, const void *p2) if (cmpval != 0) return cmpval; } + else if (obj1->objType == DO_SUBSCRIPTION_REL) + { + SubRelInfo *srobj1 = *(SubRelInfo *const *) p1; + SubRelInfo *srobj2 = *(SubRelInfo *const *) p2; + + /* Sort by subscription name, since (namespace, name) match the rel */ + cmpval = strcmp(srobj1->subinfo->dobj.name, + srobj2->subinfo->dobj.name); + if (cmpval != 0) + return cmpval; + } /* * Shouldn't get here except after catalog corruption, but if we do, sort diff --git a/src/compat/18/pg_dump.patch b/src/compat/18/pg_dump.patch index a0907050..069809dc 100644 --- a/src/compat/18/pg_dump.patch +++ b/src/compat/18/pg_dump.patch @@ -11,10 +11,10 @@ index d9041da..4dd06e8 100644 /* various user-settable parameters */ int dumpSections; /* bitmask of chosen sections */ diff --git a/src/compat/18/pg_dump/pg_dump.c b/src/compat/18/pg_dump/pg_dump.c -index 2b70da8..2fd19c6 100644 +index 8f6332d..5b024e1 100644 --- a/src/compat/18/pg_dump/pg_dump.c +++ b/src/compat/18/pg_dump/pg_dump.c -@@ -487,6 +487,7 @@ main(int argc, char **argv) +@@ -488,6 +488,7 @@ main(int argc, char **argv) */ {"attribute-inserts", no_argument, &dopt.column_inserts, 1}, {"binary-upgrade", no_argument, &dopt.binary_upgrade, 1}, @@ -22,7 +22,7 @@ index 2b70da8..2fd19c6 100644 {"column-inserts", no_argument, &dopt.column_inserts, 1}, {"disable-dollar-quoting", no_argument, &dopt.disable_dollar_quoting, 1}, {"disable-triggers", no_argument, &dopt.disable_triggers, 1}, -@@ -9948,6 +9949,8 @@ shouldPrintColumn(const DumpOptions *dopt, const TableInfo *tbinfo, int colno) +@@ -9965,6 +9966,8 @@ shouldPrintColumn(const DumpOptions *dopt, const TableInfo *tbinfo, int colno) { if (dopt->binary_upgrade) return true; @@ -31,7 +31,7 @@ index 2b70da8..2fd19c6 100644 if (tbinfo->attisdropped[colno]) return false; return (tbinfo->attislocal[colno] || tbinfo->ispartition); -@@ -15957,7 +15960,7 @@ dumpForeignServer(Archive *fout, const ForeignServerInfo *srvinfo) +@@ -15974,7 +15977,7 @@ dumpForeignServer(Archive *fout, const ForeignServerInfo *srvinfo) res = ExecuteSqlQueryForSingleRow(fout, query->data); fdwname = PQgetvalue(res, 0, 0); @@ -40,7 +40,7 @@ index 2b70da8..2fd19c6 100644 if (srvinfo->srvtype && strlen(srvinfo->srvtype) > 0) { appendPQExpBufferStr(q, " TYPE "); -@@ -16085,7 +16088,7 @@ dumpUserMappings(Archive *fout, +@@ -16102,7 +16105,7 @@ dumpUserMappings(Archive *fout, umoptions = PQgetvalue(res, i, i_umoptions); resetPQExpBuffer(q); @@ -49,7 +49,7 @@ index 2b70da8..2fd19c6 100644 appendPQExpBuffer(q, " SERVER %s", fmtId(servername)); if (umoptions && strlen(umoptions) > 0) -@@ -17593,6 +17596,35 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) +@@ -17613,6 +17616,35 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) } } diff --git a/src/compat/18/pg_dump/compress_gzip.c b/src/compat/18/pg_dump/compress_gzip.c index 5a30ebf9..be31209f 100644 --- a/src/compat/18/pg_dump/compress_gzip.c +++ b/src/compat/18/pg_dump/compress_gzip.c @@ -20,6 +20,15 @@ #ifdef HAVE_LIBZ #include +/* + * We don't use the gzgetc() macro, because zlib's configuration logic is not + * robust enough to guarantee that the macro will have the same ideas about + * struct field layout as the library itself does; see for example + * https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=59711 + * Instead, #undef the macro and fall back to the underlying function. + */ +#undef gzgetc + /*---------------------- * Compressor API *---------------------- @@ -251,34 +260,53 @@ InitCompressorGzip(CompressorState *cs, *---------------------- */ -static bool -Gzip_read(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) +static size_t +Gzip_read(void *ptr, size_t size, CompressFileHandle *CFH) { gzFile gzfp = (gzFile) CFH->private_data; int gzret; + /* Reading zero bytes must be a no-op */ + if (size == 0) + return 0; + gzret = gzread(gzfp, ptr, size); - if (gzret <= 0 && !gzeof(gzfp)) + + /* + * gzread returns zero on EOF as well as some error conditions, and less + * than zero on other error conditions, so we need to inspect for EOF on + * zero. + */ + if (gzret <= 0) { int errnum; - const char *errmsg = gzerror(gzfp, &errnum); + const char *errmsg; + + if (gzret == 0 && gzeof(gzfp)) + return 0; + + errmsg = gzerror(gzfp, &errnum); pg_fatal("could not read from input file: %s", errnum == Z_ERRNO ? strerror(errno) : errmsg); } - if (rsize) - *rsize = (size_t) gzret; - - return true; + return (size_t) gzret; } -static bool +static void Gzip_write(const void *ptr, size_t size, CompressFileHandle *CFH) { gzFile gzfp = (gzFile) CFH->private_data; + int errnum; + const char *errmsg; - return gzwrite(gzfp, ptr, size) > 0; + if (gzwrite(gzfp, ptr, size) != size) + { + errmsg = gzerror(gzfp, &errnum); + pg_fatal("could not write to file: %s", + errnum == Z_ERRNO ? strerror(errno) : errmsg); + } } static int diff --git a/src/compat/18/pg_dump/compress_io.c b/src/compat/18/pg_dump/compress_io.c index 8c3d9c91..9cadc6f2 100644 --- a/src/compat/18/pg_dump/compress_io.c +++ b/src/compat/18/pg_dump/compress_io.c @@ -269,6 +269,7 @@ InitDiscoverCompressFileHandle(const char *path, const char *mode) } CFH = InitCompressFileHandle(compression_spec); + errno = 0; if (!CFH->open_func(fname, -1, mode, CFH)) { free_keep_errno(CFH); @@ -289,6 +290,7 @@ EndCompressFileHandle(CompressFileHandle *CFH) { bool ret = false; + errno = 0; if (CFH->private_data) ret = CFH->close_func(CFH); diff --git a/src/compat/18/pg_dump/compress_io.h b/src/compat/18/pg_dump/compress_io.h index db9b3874..25a7bf09 100644 --- a/src/compat/18/pg_dump/compress_io.h +++ b/src/compat/18/pg_dump/compress_io.h @@ -123,21 +123,22 @@ struct CompressFileHandle CompressFileHandle *CFH); /* - * Read 'size' bytes of data from the file and store them into 'ptr'. - * Optionally it will store the number of bytes read in 'rsize'. + * Read up to 'size' bytes of data from the file and store them into + * 'ptr'. * - * Returns true on success and throws an internal error otherwise. + * Returns number of bytes read (this might be less than 'size' if EOF was + * reached). Exits via pg_fatal for all error conditions. */ - bool (*read_func) (void *ptr, size_t size, size_t *rsize, + size_t (*read_func) (void *ptr, size_t size, CompressFileHandle *CFH); /* * Write 'size' bytes of data into the file from 'ptr'. * - * Returns true on success and false on error. + * Returns nothing, exits via pg_fatal for all error conditions. */ - bool (*write_func) (const void *ptr, size_t size, - struct CompressFileHandle *CFH); + void (*write_func) (const void *ptr, size_t size, + CompressFileHandle *CFH); /* * Read at most size - 1 characters from the compress file handle into diff --git a/src/compat/18/pg_dump/compress_lz4.c b/src/compat/18/pg_dump/compress_lz4.c index e99f0cad..442ae4e2 100644 --- a/src/compat/18/pg_dump/compress_lz4.c +++ b/src/compat/18/pg_dump/compress_lz4.c @@ -12,6 +12,7 @@ *------------------------------------------------------------------------- */ #include "postgres_fe.h" +#include #include "compress_lz4.h" #include "pg_backup_utils.h" @@ -358,7 +359,6 @@ LZ4Stream_init(LZ4State *state, int size, bool compressing) return true; state->compressing = compressing; - state->inited = true; /* When compressing, write LZ4 header to the output stream. */ if (state->compressing) @@ -367,6 +367,7 @@ LZ4Stream_init(LZ4State *state, int size, bool compressing) if (!LZ4State_compression_init(state)) return false; + errno = 0; if (fwrite(state->buffer, 1, state->compressedlen, state->fp) != state->compressedlen) { errno = (errno) ? errno : ENOSPC; @@ -390,6 +391,7 @@ LZ4Stream_init(LZ4State *state, int size, bool compressing) state->overflowlen = 0; } + state->inited = true; return true; } @@ -457,7 +459,11 @@ LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag) /* Lazy init */ if (!LZ4Stream_init(state, size, false /* decompressing */ )) + { + pg_log_error("unable to initialize LZ4 library: %s", + LZ4F_getErrorName(state->errcode)); return -1; + } /* No work needs to be done for a zero-sized output buffer */ if (size <= 0) @@ -484,7 +490,10 @@ LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag) rsize = fread(readbuf, 1, size, state->fp); if (rsize < size && !feof(state->fp)) + { + pg_log_error("could not read from input file: %m"); return -1; + } rp = (char *) readbuf; rend = (char *) readbuf + rsize; @@ -501,6 +510,8 @@ LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag) if (LZ4F_isError(status)) { state->errcode = status; + pg_log_error("could not read from input file: %s", + LZ4F_getErrorName(state->errcode)); return -1; } @@ -558,7 +569,7 @@ LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag) /* * Compress size bytes from ptr and write them to the stream. */ -static bool +static void LZ4Stream_write(const void *ptr, size_t size, CompressFileHandle *CFH) { LZ4State *state = (LZ4State *) CFH->private_data; @@ -567,7 +578,8 @@ LZ4Stream_write(const void *ptr, size_t size, CompressFileHandle *CFH) /* Lazy init */ if (!LZ4Stream_init(state, size, true)) - return false; + pg_fatal("unable to initialize LZ4 library: %s", + LZ4F_getErrorName(state->errcode)); while (remaining > 0) { @@ -578,28 +590,24 @@ LZ4Stream_write(const void *ptr, size_t size, CompressFileHandle *CFH) status = LZ4F_compressUpdate(state->ctx, state->buffer, state->buflen, ptr, chunk, NULL); if (LZ4F_isError(status)) - { - state->errcode = status; - return false; - } + pg_fatal("error during writing: %s", LZ4F_getErrorName(status)); + errno = 0; if (fwrite(state->buffer, 1, status, state->fp) != status) { errno = (errno) ? errno : ENOSPC; - return false; + pg_fatal("error during writing: %m"); } ptr = ((const char *) ptr) + chunk; } - - return true; } /* * fread() equivalent implementation for LZ4 compressed files. */ -static bool -LZ4Stream_read(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) +static size_t +LZ4Stream_read(void *ptr, size_t size, CompressFileHandle *CFH) { LZ4State *state = (LZ4State *) CFH->private_data; int ret; @@ -607,10 +615,7 @@ LZ4Stream_read(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) if ((ret = LZ4Stream_read_internal(state, ptr, size, false)) < 0) pg_fatal("could not read from input file: %s", LZ4Stream_get_error(CFH)); - if (rsize) - *rsize = (size_t) ret; - - return true; + return (size_t) ret; } /* @@ -643,11 +648,13 @@ LZ4Stream_gets(char *ptr, int size, CompressFileHandle *CFH) int ret; ret = LZ4Stream_read_internal(state, ptr, size - 1, true); - if (ret < 0 || (ret == 0 && !LZ4Stream_eof(CFH))) - pg_fatal("could not read from input file: %s", LZ4Stream_get_error(CFH)); - /* Done reading */ - if (ret == 0) + /* + * LZ4Stream_read_internal returning 0 or -1 means that it was either an + * EOF or an error, but gets_func is defined to return NULL in either case + * so we can treat both the same here. + */ + if (ret <= 0) return NULL; /* @@ -669,6 +676,8 @@ LZ4Stream_close(CompressFileHandle *CFH) FILE *fp; LZ4State *state = (LZ4State *) CFH->private_data; size_t status; + int ret; + bool success = true; fp = state->fp; if (state->inited) @@ -677,25 +686,39 @@ LZ4Stream_close(CompressFileHandle *CFH) { status = LZ4F_compressEnd(state->ctx, state->buffer, state->buflen, NULL); if (LZ4F_isError(status)) - pg_fatal("could not end compression: %s", - LZ4F_getErrorName(status)); - else if (fwrite(state->buffer, 1, status, state->fp) != status) { - errno = (errno) ? errno : ENOSPC; - WRITE_ERROR_EXIT; + pg_log_error("could not end compression: %s", + LZ4F_getErrorName(status)); + success = false; + } + else + { + errno = 0; + if (fwrite(state->buffer, 1, status, state->fp) != status) + { + errno = (errno) ? errno : ENOSPC; + pg_log_error("could not write to output file: %m"); + success = false; + } } status = LZ4F_freeCompressionContext(state->ctx); if (LZ4F_isError(status)) - pg_fatal("could not end compression: %s", - LZ4F_getErrorName(status)); + { + pg_log_error("could not end compression: %s", + LZ4F_getErrorName(status)); + success = false; + } } else { status = LZ4F_freeDecompressionContext(state->dtx); if (LZ4F_isError(status)) - pg_fatal("could not end decompression: %s", - LZ4F_getErrorName(status)); + { + pg_log_error("could not end decompression: %s", + LZ4F_getErrorName(status)); + success = false; + } pg_free(state->overflowbuf); } @@ -703,29 +726,35 @@ LZ4Stream_close(CompressFileHandle *CFH) } pg_free(state); + CFH->private_data = NULL; - return fclose(fp) == 0; + errno = 0; + ret = fclose(fp); + if (ret != 0) + { + pg_log_error("could not close file: %m"); + success = false; + } + + return success; } static bool LZ4Stream_open(const char *path, int fd, const char *mode, CompressFileHandle *CFH) { - FILE *fp; LZ4State *state = (LZ4State *) CFH->private_data; if (fd >= 0) - fp = fdopen(fd, mode); + state->fp = fdopen(dup(fd), mode); else - fp = fopen(path, mode); - if (fp == NULL) + state->fp = fopen(path, mode); + if (state->fp == NULL) { state->errcode = errno; return false; } - state->fp = fp; - return true; } diff --git a/src/compat/18/pg_dump/compress_none.c b/src/compat/18/pg_dump/compress_none.c index 3fc89c99..4abb2e95 100644 --- a/src/compat/18/pg_dump/compress_none.c +++ b/src/compat/18/pg_dump/compress_none.c @@ -83,35 +83,31 @@ InitCompressorNone(CompressorState *cs, * Private routines */ -static bool -read_none(void *ptr, size_t size, size_t *rsize, CompressFileHandle *CFH) +static size_t +read_none(void *ptr, size_t size, CompressFileHandle *CFH) { FILE *fp = (FILE *) CFH->private_data; size_t ret; - if (size == 0) - return true; - ret = fread(ptr, 1, size, fp); - if (ret != size && !feof(fp)) + if (ferror(fp)) pg_fatal("could not read from input file: %m"); - if (rsize) - *rsize = ret; - - return true; + return ret; } -static bool +static void write_none(const void *ptr, size_t size, CompressFileHandle *CFH) { size_t ret; + errno = 0; ret = fwrite(ptr, 1, size, (FILE *) CFH->private_data); if (ret != size) - return false; - - return true; + { + errno = (errno) ? errno : ENOSPC; + pg_fatal("could not write to file: %m"); + } } static const char * @@ -153,7 +149,12 @@ close_none(CompressFileHandle *CFH) CFH->private_data = NULL; if (fp) + { + errno = 0; ret = fclose(fp); + if (ret != 0) + pg_log_error("could not close file: %m"); + } return ret == 0; } diff --git a/src/compat/18/pg_dump/compress_zstd.c b/src/compat/18/pg_dump/compress_zstd.c index cb595b10..e24d45e1 100644 --- a/src/compat/18/pg_dump/compress_zstd.c +++ b/src/compat/18/pg_dump/compress_zstd.c @@ -13,6 +13,7 @@ */ #include "postgres_fe.h" +#include #include "compress_zstd.h" #include "pg_backup_utils.h" @@ -258,8 +259,8 @@ InitCompressorZstd(CompressorState *cs, * Compressed stream API */ -static bool -Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) +static size_t +Zstd_read_internal(void *ptr, size_t size, CompressFileHandle *CFH, bool exit_on_error) { ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data; ZSTD_inBuffer *input = &zstdcs->input; @@ -268,6 +269,22 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) size_t res, cnt; + /* + * If this is the first call to the reading function, initialize the + * required datastructures. + */ + if (zstdcs->dstream == NULL) + { + zstdcs->input.src = pg_malloc0(input_allocated_size); + zstdcs->dstream = ZSTD_createDStream(); + if (zstdcs->dstream == NULL) + { + if (exit_on_error) + pg_fatal("could not initialize compression library"); + return -1; + } + } + output->size = size; output->dst = ptr; output->pos = 0; @@ -292,6 +309,13 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) if (input->pos == input->size) { cnt = fread(unconstify(void *, input->src), 1, input_allocated_size, zstdcs->fp); + if (ferror(zstdcs->fp)) + { + if (exit_on_error) + pg_fatal("could not read from input file: %m"); + return -1; + } + input->size = cnt; Assert(cnt <= input_allocated_size); @@ -307,7 +331,11 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) res = ZSTD_decompressStream(zstdcs->dstream, output, input); if (ZSTD_isError(res)) - pg_fatal("could not decompress data: %s", ZSTD_getErrorName(res)); + { + if (exit_on_error) + pg_fatal("could not decompress data: %s", ZSTD_getErrorName(res)); + return -1; + } if (output->pos == output->size) break; /* No more room for output */ @@ -320,13 +348,10 @@ Zstd_read(void *ptr, size_t size, size_t *rdsize, CompressFileHandle *CFH) break; /* We read all the data that fits */ } - if (rdsize != NULL) - *rdsize = output->pos; - - return true; + return output->pos; } -static bool +static void Zstd_write(const void *ptr, size_t size, CompressFileHandle *CFH) { ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data; @@ -339,41 +364,40 @@ Zstd_write(const void *ptr, size_t size, CompressFileHandle *CFH) input->size = size; input->pos = 0; + if (zstdcs->cstream == NULL) + { + zstdcs->output.size = ZSTD_CStreamOutSize(); + zstdcs->output.dst = pg_malloc0(zstdcs->output.size); + zstdcs->cstream = _ZstdCStreamParams(CFH->compression_spec); + if (zstdcs->cstream == NULL) + pg_fatal("could not initialize compression library"); + } + /* Consume all input, to be flushed later */ while (input->pos != input->size) { output->pos = 0; res = ZSTD_compressStream2(zstdcs->cstream, output, input, ZSTD_e_continue); if (ZSTD_isError(res)) - { - zstdcs->zstderror = ZSTD_getErrorName(res); - return false; - } + pg_fatal("could not write to file: %s", ZSTD_getErrorName(res)); + errno = 0; cnt = fwrite(output->dst, 1, output->pos, zstdcs->fp); if (cnt != output->pos) { - zstdcs->zstderror = strerror(errno); - return false; + errno = (errno) ? errno : ENOSPC; + pg_fatal("could not write to file: %m"); } } - - return size; } static int Zstd_getc(CompressFileHandle *CFH) { - ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data; - int ret; + unsigned char ret; - if (CFH->read_func(&ret, 1, NULL, CFH) != 1) - { - if (feof(zstdcs->fp)) - pg_fatal("could not read from input file: end of file"); - else - pg_fatal("could not read from input file: %m"); - } + if (CFH->read_func(&ret, 1, CFH) != 1) + pg_fatal("could not read from input file: end of file"); return ret; } @@ -390,11 +414,7 @@ Zstd_gets(char *buf, int len, CompressFileHandle *CFH) */ for (i = 0; i < len - 1; ++i) { - size_t readsz; - - if (!CFH->read_func(&buf[i], 1, &readsz, CFH)) - break; - if (readsz != 1) + if (Zstd_read_internal(&buf[i], 1, CFH, false) != 1) break; if (buf[i] == '\n') { @@ -406,10 +426,17 @@ Zstd_gets(char *buf, int len, CompressFileHandle *CFH) return i > 0 ? buf : NULL; } +static size_t +Zstd_read(void *ptr, size_t size, CompressFileHandle *CFH) +{ + return Zstd_read_internal(ptr, size, CFH, true); +} + static bool Zstd_close(CompressFileHandle *CFH) { ZstdCompressorState *zstdcs = (ZstdCompressorState *) CFH->private_data; + bool success = true; if (zstdcs->cstream) { @@ -426,14 +453,18 @@ Zstd_close(CompressFileHandle *CFH) if (ZSTD_isError(res)) { zstdcs->zstderror = ZSTD_getErrorName(res); - return false; + success = false; + break; } + errno = 0; cnt = fwrite(output->dst, 1, output->pos, zstdcs->fp); if (cnt != output->pos) { + errno = (errno) ? errno : ENOSPC; zstdcs->zstderror = strerror(errno); - return false; + success = false; + break; } if (res == 0) @@ -450,11 +481,16 @@ Zstd_close(CompressFileHandle *CFH) pg_free(unconstify(void *, zstdcs->input.src)); } + errno = 0; if (fclose(zstdcs->fp) != 0) - return false; + { + zstdcs->zstderror = strerror(errno); + success = false; + } pg_free(zstdcs); - return true; + CFH->private_data = NULL; + return success; } static bool @@ -472,35 +508,33 @@ Zstd_open(const char *path, int fd, const char *mode, FILE *fp; ZstdCompressorState *zstdcs; + /* + * Clear state storage to avoid having the fd point to non-NULL memory on + * error return. + */ + CFH->private_data = NULL; + + zstdcs = (ZstdCompressorState *) pg_malloc_extended(sizeof(*zstdcs), + MCXT_ALLOC_NO_OOM | MCXT_ALLOC_ZERO); + if (!zstdcs) + { + errno = ENOMEM; + return false; + } + if (fd >= 0) - fp = fdopen(fd, mode); + fp = fdopen(dup(fd), mode); else fp = fopen(path, mode); if (fp == NULL) + { + pg_free(zstdcs); return false; + } - zstdcs = (ZstdCompressorState *) pg_malloc0(sizeof(*zstdcs)); - CFH->private_data = zstdcs; zstdcs->fp = fp; - - if (mode[0] == 'r') - { - zstdcs->input.src = pg_malloc0(ZSTD_DStreamInSize()); - zstdcs->dstream = ZSTD_createDStream(); - if (zstdcs->dstream == NULL) - pg_fatal("could not initialize compression library"); - } - else if (mode[0] == 'w' || mode[0] == 'a') - { - zstdcs->output.size = ZSTD_CStreamOutSize(); - zstdcs->output.dst = pg_malloc0(zstdcs->output.size); - zstdcs->cstream = _ZstdCStreamParams(CFH->compression_spec); - if (zstdcs->cstream == NULL) - pg_fatal("could not initialize compression library"); - } - else - pg_fatal("unhandled mode \"%s\"", mode); + CFH->private_data = zstdcs; return true; } diff --git a/src/compat/18/pg_dump/dumputils.c b/src/compat/18/pg_dump/dumputils.c index 8945bdd4..dde16590 100644 --- a/src/compat/18/pg_dump/dumputils.c +++ b/src/compat/18/pg_dump/dumputils.c @@ -730,6 +730,7 @@ bool variable_is_guc_list_quote(const char *name) { if (pg_strcasecmp(name, "local_preload_libraries") == 0 || + pg_strcasecmp(name, "oauth_validator_libraries") == 0 || pg_strcasecmp(name, "search_path") == 0 || pg_strcasecmp(name, "session_preload_libraries") == 0 || pg_strcasecmp(name, "shared_preload_libraries") == 0 || diff --git a/src/compat/18/pg_dump/pg_backup_archiver.c b/src/compat/18/pg_dump/pg_backup_archiver.c index bb50a170..4293e20b 100644 --- a/src/compat/18/pg_dump/pg_backup_archiver.c +++ b/src/compat/18/pg_dump/pg_backup_archiver.c @@ -42,6 +42,7 @@ #include "pg_backup_archiver.h" #include "pg_backup_db.h" #include "pg_backup_utils.h" +#include "pgtar.h" #define TEXT_DUMP_HEADER "--\n-- PostgreSQL database dump\n--\n\n" #define TEXT_DUMPALL_HEADER "--\n-- PostgreSQL database cluster dump\n--\n\n" @@ -1881,8 +1882,8 @@ ahwrite(const void *ptr, size_t size, size_t nmemb, ArchiveHandle *AH) { CompressFileHandle *CFH = (CompressFileHandle *) AH->OF; - if (CFH->write_func(ptr, size * nmemb, CFH)) - bytes_written = size * nmemb; + CFH->write_func(ptr, size * nmemb, CFH); + bytes_written = size * nmemb; } if (bytes_written != size * nmemb) @@ -2349,7 +2350,7 @@ _discoverArchiveFormat(ArchiveHandle *AH) } if (!isValidTarHeader(AH->lookahead)) - pg_fatal("input file does not appear to be a valid archive"); + pg_fatal("input file does not appear to be a valid tar archive"); AH->format = archTar; } @@ -3033,6 +3034,25 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) strcmp(te->desc, "ROW SECURITY") == 0)) return 0; + /* + * If it's a comment on a policy, a publication, or a subscription, maybe + * ignore it. + */ + if (strcmp(te->desc, "COMMENT") == 0) + { + if (ropt->no_policies && + strncmp(te->tag, "POLICY", strlen("POLICY")) == 0) + return 0; + + if (ropt->no_publications && + strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) + return 0; + + if (ropt->no_subscriptions && + strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0) + return 0; + } + /* * If it's a publication or a table part of a publication, maybe ignore * it. @@ -3047,6 +3067,21 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) if (ropt->no_security_labels && strcmp(te->desc, "SECURITY LABEL") == 0) return 0; + /* + * If it's a security label on a publication or a subscription, maybe + * ignore it. + */ + if (strcmp(te->desc, "SECURITY LABEL") == 0) + { + if (ropt->no_publications && + strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0) + return 0; + + if (ropt->no_subscriptions && + strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0) + return 0; + } + /* If it's a subscription, maybe ignore it */ if (ropt->no_subscriptions && strcmp(te->desc, "SUBSCRIPTION") == 0) return 0; @@ -3291,12 +3326,14 @@ _tocEntryRestorePass(TocEntry *te) return RESTORE_PASS_POST_ACL; /* - * Comments need to be emitted in the same pass as their parent objects. - * ACLs haven't got comments, and neither do matview data objects, but - * event triggers do. (Fortunately, event triggers haven't got ACLs, or - * we'd need yet another weird special case.) + * Comments and security labels need to be emitted in the same pass as + * their parent objects. ACLs haven't got comments and security labels, + * and neither do matview data objects, but event triggers do. + * (Fortunately, event triggers haven't got ACLs, or we'd need yet another + * weird special case.) */ - if (strcmp(te->desc, "COMMENT") == 0 && + if ((strcmp(te->desc, "COMMENT") == 0 || + strcmp(te->desc, "SECURITY LABEL") == 0) && strncmp(te->tag, "EVENT TRIGGER ", 14) == 0) return RESTORE_PASS_POST_ACL; diff --git a/src/compat/18/pg_dump/pg_backup_archiver.h b/src/compat/18/pg_dump/pg_backup_archiver.h index 325b53fc..c01d4506 100644 --- a/src/compat/18/pg_dump/pg_backup_archiver.h +++ b/src/compat/18/pg_dump/pg_backup_archiver.h @@ -464,8 +464,6 @@ extern void InitArchiveFmt_Null(ArchiveHandle *AH); extern void InitArchiveFmt_Directory(ArchiveHandle *AH); extern void InitArchiveFmt_Tar(ArchiveHandle *AH); -extern bool isValidTarHeader(char *header); - extern void ReconnectToServer(ArchiveHandle *AH, const char *dbname); extern void IssueCommandPerBlob(ArchiveHandle *AH, TocEntry *te, const char *cmdBegin, const char *cmdEnd); diff --git a/src/compat/18/pg_dump/pg_backup_directory.c b/src/compat/18/pg_dump/pg_backup_directory.c index bc2a2fb4..94d401d8 100644 --- a/src/compat/18/pg_dump/pg_backup_directory.c +++ b/src/compat/18/pg_dump/pg_backup_directory.c @@ -316,15 +316,9 @@ _WriteData(ArchiveHandle *AH, const void *data, size_t dLen) lclContext *ctx = (lclContext *) AH->formatData; CompressFileHandle *CFH = ctx->dataFH; - errno = 0; - if (dLen > 0 && !CFH->write_func(data, dLen, CFH)) - { - /* if write didn't set errno, assume problem is no disk space */ - if (errno == 0) - errno = ENOSPC; - pg_fatal("could not write to output file: %s", - CFH->get_error_func(CFH)); - } + if (dLen <= 0) + return; + CFH->write_func(data, dLen, CFH); } /* @@ -351,7 +345,7 @@ _EndData(ArchiveHandle *AH, TocEntry *te) static void _PrintFileData(ArchiveHandle *AH, char *filename) { - size_t cnt = 0; + size_t cnt; char *buf; size_t buflen; CompressFileHandle *CFH; @@ -366,7 +360,7 @@ _PrintFileData(ArchiveHandle *AH, char *filename) buflen = DEFAULT_IO_BUFFER_SIZE; buf = pg_malloc(buflen); - while (CFH->read_func(buf, buflen, &cnt, CFH) && cnt > 0) + while ((cnt = CFH->read_func(buf, buflen, CFH)) > 0) { ahwrite(buf, 1, cnt, AH); } @@ -470,16 +464,7 @@ _WriteByte(ArchiveHandle *AH, const int i) lclContext *ctx = (lclContext *) AH->formatData; CompressFileHandle *CFH = ctx->dataFH; - errno = 0; - if (!CFH->write_func(&c, 1, CFH)) - { - /* if write didn't set errno, assume problem is no disk space */ - if (errno == 0) - errno = ENOSPC; - pg_fatal("could not write to output file: %s", - CFH->get_error_func(CFH)); - } - + CFH->write_func(&c, 1, CFH); return 1; } @@ -508,15 +493,7 @@ _WriteBuf(ArchiveHandle *AH, const void *buf, size_t len) lclContext *ctx = (lclContext *) AH->formatData; CompressFileHandle *CFH = ctx->dataFH; - errno = 0; - if (!CFH->write_func(buf, len, CFH)) - { - /* if write didn't set errno, assume problem is no disk space */ - if (errno == 0) - errno = ENOSPC; - pg_fatal("could not write to output file: %s", - CFH->get_error_func(CFH)); - } + CFH->write_func(buf, len, CFH); } /* @@ -531,10 +508,10 @@ _ReadBuf(ArchiveHandle *AH, void *buf, size_t len) CompressFileHandle *CFH = ctx->dataFH; /* - * If there was an I/O error, we already exited in readF(), so here we - * exit on short reads. + * We do not expect a short read, so fail if we get one. The read_func + * already dealt with any outright I/O error. */ - if (!CFH->read_func(buf, len, NULL, CFH)) + if (CFH->read_func(buf, len, CFH) != len) pg_fatal("could not read from input file: end of file"); } @@ -677,14 +654,7 @@ _EndLO(ArchiveHandle *AH, TocEntry *te, Oid oid) /* register the LO in blobs_NNN.toc */ len = snprintf(buf, sizeof(buf), "%u blob_%u.dat\n", oid, oid); - if (!CFH->write_func(buf, len, CFH)) - { - /* if write didn't set errno, assume problem is no disk space */ - if (errno == 0) - errno = ENOSPC; - pg_fatal("could not write to LOs TOC file: %s", - CFH->get_error_func(CFH)); - } + CFH->write_func(buf, len, CFH); } /* diff --git a/src/compat/18/pg_dump/pg_backup_tar.c b/src/compat/18/pg_dump/pg_backup_tar.c index b5ba3b46..ec42a2cb 100644 --- a/src/compat/18/pg_dump/pg_backup_tar.c +++ b/src/compat/18/pg_dump/pg_backup_tar.c @@ -984,31 +984,6 @@ tarPrintf(TAR_MEMBER *th, const char *fmt,...) return (int) cnt; } -bool -isValidTarHeader(char *header) -{ - int sum; - int chk = tarChecksum(header); - - sum = read_tar_number(&header[TAR_OFFSET_CHECKSUM], 8); - - if (sum != chk) - return false; - - /* POSIX tar format */ - if (memcmp(&header[TAR_OFFSET_MAGIC], "ustar\0", 6) == 0 && - memcmp(&header[TAR_OFFSET_VERSION], "00", 2) == 0) - return true; - /* GNU tar format */ - if (memcmp(&header[TAR_OFFSET_MAGIC], "ustar \0", 8) == 0) - return true; - /* not-quite-POSIX format written by pre-9.3 pg_dump */ - if (memcmp(&header[TAR_OFFSET_MAGIC], "ustar00\0", 8) == 0) - return true; - - return false; -} - /* Given the member, write the TAR header & copy the file */ static void _tarAddFile(ArchiveHandle *AH, TAR_MEMBER *th) diff --git a/src/compat/18/pg_dump/pg_dump.c b/src/compat/18/pg_dump/pg_dump.c index 2fd19c66..5b024e1e 100644 --- a/src/compat/18/pg_dump/pg_dump.c +++ b/src/compat/18/pg_dump/pg_dump.c @@ -135,6 +135,7 @@ typedef struct int64 cache; /* cache size */ int64 last_value; /* last value of sequence */ bool is_called; /* whether nextval advances before returning */ + bool null_seqtuple; /* did pg_get_sequence_data return nulls? */ } SequenceItem; typedef enum OidOptions @@ -824,23 +825,30 @@ main(int argc, char **argv) /* reject conflicting "-only" options */ if (data_only && schema_only) - pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together"); + pg_fatal("options %s and %s cannot be used together", + "-s/--schema-only", "-a/--data-only"); if (schema_only && statistics_only) - pg_fatal("options -s/--schema-only and --statistics-only cannot be used together"); + pg_fatal("options %s and %s cannot be used together", + "-s/--schema-only", "--statistics-only"); if (data_only && statistics_only) - pg_fatal("options -a/--data-only and --statistics-only cannot be used together"); + pg_fatal("options %s and %s cannot be used together", + "-a/--data-only", "--statistics-only"); /* reject conflicting "-only" and "no-" options */ if (data_only && no_data) - pg_fatal("options -a/--data-only and --no-data cannot be used together"); + pg_fatal("options %s and %s cannot be used together", + "-a/--data-only", "--no-data"); if (schema_only && no_schema) - pg_fatal("options -s/--schema-only and --no-schema cannot be used together"); + pg_fatal("options %s and %s cannot be used together", + "-s/--schema-only", "--no-schema"); if (statistics_only && no_statistics) - pg_fatal("options --statistics-only and --no-statistics cannot be used together"); + pg_fatal("options %s and %s cannot be used together", + "--statistics-only", "--no-statistics"); /* reject conflicting "no-" options */ if (with_statistics && no_statistics) - pg_fatal("options --statistics and --no-statistics cannot be used together"); + pg_fatal("options %s and %s cannot be used together", + "--statistics", "--no-statistics"); /* reject conflicting "-only" options */ if (data_only && with_statistics) @@ -851,16 +859,20 @@ main(int argc, char **argv) "-s/--schema-only", "--statistics"); if (schema_only && foreign_servers_include_patterns.head != NULL) - pg_fatal("options -s/--schema-only and --include-foreign-data cannot be used together"); + pg_fatal("options %s and %s cannot be used together", + "-s/--schema-only", "--include-foreign-data"); if (numWorkers > 1 && foreign_servers_include_patterns.head != NULL) - pg_fatal("option --include-foreign-data is not supported with parallel backup"); + pg_fatal("option %s is not supported with parallel backup", + "--include-foreign-data"); if (data_only && dopt.outputClean) - pg_fatal("options -c/--clean and -a/--data-only cannot be used together"); + pg_fatal("options %s and %s cannot be used together", + "-c/--clean", "-a/--data-only"); if (dopt.if_exists && !dopt.outputClean) - pg_fatal("option --if-exists requires option -c/--clean"); + pg_fatal("option %s requires option %s", + "--if-exists", "-c/--clean"); /* * Set derivative flags. Ambiguous or nonsensical combinations, e.g. @@ -880,7 +892,9 @@ main(int argc, char **argv) * --rows-per-insert were specified. */ if (dopt.do_nothing && dopt.dump_inserts == 0) - pg_fatal("option --on-conflict-do-nothing requires option --inserts, --rows-per-insert, or --column-inserts"); + pg_fatal("option %s requires option %s, %s, or %s", + "--on-conflict-do-nothing", + "--inserts", "--rows-per-insert", "--column-inserts"); /* Identify archive format to emit */ archiveFormat = parseArchiveFormat(format, &archiveMode); @@ -901,7 +915,8 @@ main(int argc, char **argv) pg_fatal("invalid restrict key"); } else if (dopt.restrict_key) - pg_fatal("option --restrict-key can only be used with --format=plain"); + pg_fatal("option %s can only be used with %s", + "--restrict-key", "--format=plain"); /* * Custom and directory formats are compressed by default with gzip when @@ -5219,7 +5234,9 @@ getSubscriptionTables(Archive *fout) subrinfo[i].dobj.catId.tableoid = relid; subrinfo[i].dobj.catId.oid = cur_srsubid; AssignDumpId(&subrinfo[i].dobj); - subrinfo[i].dobj.name = pg_strdup(subinfo->dobj.name); + subrinfo[i].dobj.namespace = tblinfo->dobj.namespace; + subrinfo[i].dobj.name = tblinfo->dobj.name; + subrinfo[i].subinfo = subinfo; subrinfo[i].tblinfo = tblinfo; subrinfo[i].srsubstate = PQgetvalue(res, i, i_srsubstate)[0]; if (PQgetisnull(res, i, i_srsublsn)) @@ -5227,8 +5244,6 @@ getSubscriptionTables(Archive *fout) else subrinfo[i].srsublsn = pg_strdup(PQgetvalue(res, i, i_srsublsn)); - subrinfo[i].subinfo = subinfo; - /* Decide whether we want to dump it */ selectDumpableObject(&(subrinfo[i].dobj), fout); } @@ -5256,7 +5271,7 @@ dumpSubscriptionTable(Archive *fout, const SubRelInfo *subrinfo) Assert(fout->dopt->binary_upgrade && fout->remoteVersion >= 170000); - tag = psprintf("%s %s", subinfo->dobj.name, subrinfo->dobj.name); + tag = psprintf("%s %s", subinfo->dobj.name, subrinfo->tblinfo->dobj.name); query = createPQExpBuffer(); @@ -5271,7 +5286,7 @@ dumpSubscriptionTable(Archive *fout, const SubRelInfo *subrinfo) "\n-- For binary upgrade, must preserve the subscriber table.\n"); appendPQExpBufferStr(query, "SELECT pg_catalog.binary_upgrade_add_sub_rel_state("); - appendStringLiteralAH(query, subrinfo->dobj.name, fout); + appendStringLiteralAH(query, subinfo->dobj.name, fout); appendPQExpBuffer(query, ", %u, '%c'", subrinfo->tblinfo->dobj.catId.oid, @@ -9180,8 +9195,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) * * We track in notnull_islocal whether the constraint was defined directly * in this table or via an ancestor, for binary upgrade. flagInhAttrs - * might modify this later; that routine is also in charge of determining - * the correct inhcount. + * might modify this later. */ if (fout->remoteVersion >= 180000) appendPQExpBufferStr(q, @@ -9198,7 +9212,10 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) "NULL AS notnull_comment,\n" "NULL AS notnull_invalidoid,\n" "false AS notnull_noinherit,\n" - "a.attislocal AS notnull_islocal,\n"); + "CASE WHEN a.attislocal THEN true\n" + " WHEN a.attnotnull AND NOT a.attislocal THEN true\n" + " ELSE false\n" + "END AS notnull_islocal,\n"); if (fout->remoteVersion >= 140000) appendPQExpBufferStr(q, @@ -9900,7 +9917,7 @@ determineNotNullFlags(Archive *fout, PGresult *res, int r, */ if ((dopt->binary_upgrade && !tbinfo->ispartition && - !tbinfo->notnull_islocal) || + !tbinfo->notnull_islocal[j]) || !PQgetisnull(res, r, i_notnull_comment)) { tbinfo->notnull_constrs[j] = @@ -16604,7 +16621,7 @@ collectSecLabels(Archive *fout) appendPQExpBufferStr(query, "SELECT label, provider, classoid, objoid, objsubid " - "FROM pg_catalog.pg_seclabel " + "FROM pg_catalog.pg_seclabels " "ORDER BY classoid, objoid, objsubid"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -17216,6 +17233,9 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) appendPQExpBuffer(q, "CONSTRAINT %s NOT NULL %s", tbinfo->notnull_constrs[j], fmtId(tbinfo->attnames[j])); + + if (tbinfo->notnull_noinh[j]) + appendPQExpBufferStr(q, " NO INHERIT"); } } @@ -18666,7 +18686,7 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) dumpComment(fout, conprefix->data, qtypname, tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, - coninfo->dobj.catId, 0, tyinfo->dobj.dumpId); + coninfo->dobj.catId, 0, coninfo->dobj.dumpId); destroyPQExpBuffer(conprefix); free(qtypname); } @@ -18801,6 +18821,7 @@ collectSequences(Archive *fout) sequences[i].cycled = (strcmp(PQgetvalue(res, i, 7), "t") == 0); sequences[i].last_value = strtoi64(PQgetvalue(res, i, 8), NULL, 10); sequences[i].is_called = (strcmp(PQgetvalue(res, i, 9), "t") == 0); + sequences[i].null_seqtuple = (PQgetisnull(res, i, 8) || PQgetisnull(res, i, 9)); } PQclear(res); @@ -19070,7 +19091,13 @@ dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo) TableInfo *tbinfo = tdinfo->tdtable; int64 last; bool called; - PQExpBuffer query = createPQExpBuffer(); + PQExpBuffer query; + + /* needn't bother if not dumping sequence data */ + if (!fout->dopt->dumpData && !fout->dopt->sequence_data) + return; + + query = createPQExpBuffer(); /* * For versions >= 18, the sequence information is gathered in the sorted @@ -19113,6 +19140,12 @@ dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo) entry = bsearch(&key, sequences, nsequences, sizeof(SequenceItem), SequenceItemCmp); + if (entry->null_seqtuple) + pg_fatal("failed to get data for sequence \"%s\"; user may lack " + "SELECT privilege on the sequence or the sequence may " + "have been concurrently dropped", + tbinfo->dobj.name); + last = entry->last_value; called = entry->is_called; } @@ -19343,6 +19376,11 @@ dumpEventTrigger(Archive *fout, const EventTriggerInfo *evtinfo) NULL, evtinfo->evtowner, evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId); + if (evtinfo->dobj.dump & DUMP_COMPONENT_SECLABEL) + dumpSecLabel(fout, "EVENT TRIGGER", qevtname, + NULL, evtinfo->evtowner, + evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId); + destroyPQExpBuffer(query); destroyPQExpBuffer(delqry); free(qevtname); diff --git a/src/compat/18/pg_dump/pg_dump_sort.c b/src/compat/18/pg_dump/pg_dump_sort.c index ff3aa024..297b4dfa 100644 --- a/src/compat/18/pg_dump/pg_dump_sort.c +++ b/src/compat/18/pg_dump/pg_dump_sort.c @@ -385,7 +385,8 @@ DOTypeNameCompare(const void *p1, const void *p2) if (cmpval != 0) return cmpval; } - else if (obj1->objType == DO_CONSTRAINT) + else if (obj1->objType == DO_CONSTRAINT || + obj1->objType == DO_FK_CONSTRAINT) { ConstraintInfo *robj1 = *(ConstraintInfo *const *) p1; ConstraintInfo *robj2 = *(ConstraintInfo *const *) p2; @@ -418,6 +419,19 @@ DOTypeNameCompare(const void *p1, const void *p2) return cmpval; } } + else if (obj1->objType == DO_DEFAULT_ACL) + { + DefaultACLInfo *daclobj1 = *(DefaultACLInfo *const *) p1; + DefaultACLInfo *daclobj2 = *(DefaultACLInfo *const *) p2; + + /* + * Sort by defaclrole, per pg_default_acl_role_nsp_obj_index. The + * (namespace, name) match (defaclnamespace, defaclobjtype). + */ + cmpval = strcmp(daclobj1->defaclrole, daclobj2->defaclrole); + if (cmpval != 0) + return cmpval; + } else if (obj1->objType == DO_PUBLICATION_REL) { PublicationRelInfo *probj1 = *(PublicationRelInfo *const *) p1; @@ -440,6 +454,17 @@ DOTypeNameCompare(const void *p1, const void *p2) if (cmpval != 0) return cmpval; } + else if (obj1->objType == DO_SUBSCRIPTION_REL) + { + SubRelInfo *srobj1 = *(SubRelInfo *const *) p1; + SubRelInfo *srobj2 = *(SubRelInfo *const *) p2; + + /* Sort by subscription name, since (namespace, name) match the rel */ + cmpval = strcmp(srobj1->subinfo->dobj.name, + srobj2->subinfo->dobj.name); + if (cmpval != 0) + return cmpval; + } /* * Shouldn't get here except after catalog corruption, but if we do, sort From 6f36bad9dc4512225cd14ca0c227363887ad1820 Mon Sep 17 00:00:00 2001 From: Yogesh Sharma Date: Wed, 27 May 2026 18:43:07 -0400 Subject: [PATCH 2/6] Convert SQLs to use params instead of strings REVOKE ALL from _pgactive_node_name_present_private for PUBLIC Add a new test to detect if any of the privatye functions are exposed to PUBLIC Don't start perdb wrokers if pgactive schema is present in DB but extension is not yet created. Add 2.1.8 sqls --- include/pgactive.h | 2 + include/pgactive_version.h.in | 4 +- pgactive--2.1.7--2.1.8.sql | 16 + pgactive--2.1.8.sql | 4025 +++++++++++++++++++++++++++++++++ pgactive.control | 2 +- src/pgactive.c | 20 +- src/pgactive_remotecalls.c | 10 +- src/pgactive_user_mapping.c | 31 +- test/t/059_misc.pl | 4 + 9 files changed, 4086 insertions(+), 28 deletions(-) create mode 100644 pgactive--2.1.7--2.1.8.sql create mode 100644 pgactive--2.1.8.sql diff --git a/include/pgactive.h b/include/pgactive.h index 3c576892..305ec41b 100644 --- a/include/pgactive.h +++ b/include/pgactive.h @@ -103,6 +103,8 @@ #define pgactive_SUPERVISOR_DBNAME "pgactive_supervisordb" +#define pgactive_SCHEMA_NAME "pgactive" + #define pgactive_LOGICAL_MSG_PREFIX "pgactive" #define pgactive_SECLABEL_PROVIDER "pgactive" diff --git a/include/pgactive_version.h.in b/include/pgactive_version.h.in index 0e7e3f5b..3ec3aada 100644 --- a/include/pgactive_version.h.in +++ b/include/pgactive_version.h.in @@ -1,5 +1,5 @@ -#define pgactive_VERSION "2.1.7" -#define pgactive_VERSION_NUM 20107 +#define pgactive_VERSION "2.1.8" +#define pgactive_VERSION_NUM 20108 #define pgactive_MIN_REMOTE_VERSION_NUM 20100 #define pgactive_VERSION_DATE "" #define pgactive_VERSION_GITHASH "" diff --git a/pgactive--2.1.7--2.1.8.sql b/pgactive--2.1.7--2.1.8.sql new file mode 100644 index 00000000..e6d847e0 --- /dev/null +++ b/pgactive--2.1.7--2.1.8.sql @@ -0,0 +1,16 @@ +/* pgactive--2.1.7--2.1.8.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pgactive UPDATE TO '2.1.8'" to load this file. \quit + +SET pgactive.skip_ddl_replication = true; +SET LOCAL search_path = pgactive; +-- Start Upgrade SQLs/Functions/Procedures + + + +REVOKE ALL ON FUNCTION _pgactive_node_name_present_private(text, text) FROM PUBLIC; + +-- Finish Upgrade SQLs/Functions/Procedures +RESET pgactive.skip_ddl_replication; +RESET search_path; diff --git a/pgactive--2.1.8.sql b/pgactive--2.1.8.sql new file mode 100644 index 00000000..10b930f3 --- /dev/null +++ b/pgactive--2.1.8.sql @@ -0,0 +1,4025 @@ +/* pgactive--2.1.8.sql */ + +-- Install script for pgactive 2.1.8 + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pgactive" to load this file. \quit + +--- We must be able to use exclusion constraints for global sequences among +--- other things. +-- SET pgactive.permit_unsafe_ddl_commands = true; is removed for now + +-- We don't want to replicate commands from in here +SET pgactive.skip_ddl_replication = true; + +CREATE SCHEMA pgactive; +GRANT USAGE ON SCHEMA pgactive TO public; + +-- Everything should assume the 'pgactive' prefix +SET LOCAL search_path = pgactive; + +CREATE FUNCTION pgactive_version() +RETURNS TEXT +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE FUNCTION pgactive_variant() +RETURNS TEXT +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE FUNCTION pgactive_get_stats ( + OUT rep_node_id oid, + OUT rilocalid oid, + OUT riremoteid text, + OUT nr_commit int8, + OUT nr_rollback int8, + OUT nr_insert int8, + OUT nr_insert_conflict int8, + OUT nr_update int8, + OUT nr_update_conflict int8, + OUT nr_delete int8, + OUT nr_delete_conflict int8, + OUT nr_disconnect int8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C; + +REVOKE ALL ON FUNCTION pgactive_get_stats() FROM PUBLIC; + +CREATE VIEW pgactive_stats AS SELECT * FROM pgactive_get_stats(); + +CREATE TYPE pgactive_conflict_type AS ENUM ( + 'insert_insert', + 'insert_update', + 'update_update', + 'update_delete', + 'delete_delete', + 'unhandled_tx_abort' +); + +COMMENT ON TYPE pgactive_conflict_type IS +'The nature of a pgactive apply conflict - concurrent updates (update_update), conflicting inserts, etc.'; + +CREATE TYPE pgactive_conflict_handler_action AS ENUM('IGNORE', 'ROW', 'SKIP'); + +CREATE TABLE pgactive_conflict_handlers ( + ch_name NAME NOT NULL, + ch_type pgactive.pgactive_conflict_type NOT NULL, + ch_reloid oid NOT NULL, + ch_fun text NOT NULL, + ch_timeframe INTERVAL, + PRIMARY KEY(ch_reloid, ch_name) +); +REVOKE ALL ON TABLE pgactive_conflict_handlers FROM PUBLIC; +SELECT pg_catalog.pg_extension_config_dump('pgactive_conflict_handlers', ''); + +CREATE INDEX pgactive_conflict_handlers_ch_type_reloid_idx + ON pgactive_conflict_handlers(ch_reloid, ch_type); + +CREATE FUNCTION pgactive_create_conflict_handler ( + ch_rel REGCLASS, + ch_name NAME, + ch_proc REGPROCEDURE, + ch_type pgactive.pgactive_conflict_type, + ch_timeframe INTERVAL DEFAULT NULL +) +RETURNS VOID +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE FUNCTION pgactive_drop_conflict_handler(ch_rel REGCLASS, ch_name NAME) +RETURNS VOID +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE TYPE pgactive_conflict_resolution AS ENUM ( + 'conflict_trigger_skip_change', + 'conflict_trigger_returned_tuple', + 'last_update_wins_keep_local', + 'last_update_wins_keep_remote', + 'apply_change', + 'skip_change', + 'unhandled_tx_abort' +); + +COMMENT ON TYPE pgactive_conflict_resolution IS +'Resolution of a pgactive conflict - if a conflict was resolved by a conflict trigger, by last-update-wins tests on commit timestamps, etc.'; + +CREATE SEQUENCE pgactive_conflict_history_id_seq; + +-- +-- pgactive_conflict_history records apply conflicts so they can be queried and +-- analysed by administrators. +-- +-- This must remain in sync with struct pgactiveApplyConflict and +-- pgactive_conflict_log_table(). +-- +CREATE TABLE pgactive_conflict_history ( + conflict_id bigint not null default nextval('pgactive_conflict_history_id_seq'), + local_node_sysid text not null, -- really uint64 but we don't have the type for it + PRIMARY KEY (local_node_sysid, conflict_id), + + local_conflict_xid xid not null, -- xid of conflicting apply tx + + -- lsn of local node at the time the conflict was detected + local_conflict_lsn pg_lsn not null, + local_conflict_time timestamptz not null, + object_schema text, + object_name text, + remote_node_sysid text not null, -- again, really uint64 + remote_txid xid not null, + remote_commit_time timestamptz not null, + remote_commit_lsn pg_lsn not null, + conflict_type pgactive_conflict_type not null, + conflict_resolution pgactive_conflict_resolution not null, + local_tuple json, + remote_tuple json, + local_tuple_xmin xid, + local_tuple_origin_sysid text, -- also really uint64 + + -- The following apply only for unhandled apply errors and correspond to + -- fields in ErrorData in elog.h . + error_message text, + error_sqlstate text CHECK (length(error_sqlstate) = 5), + error_querystring text, + error_cursorpos integer, + error_detail text, + error_hint text, + error_context text, + + -- schema and table in object_schema, object_name above + error_columnname text, + error_typename text, + error_constraintname text, + error_filename text, + error_lineno integer, + error_funcname text, + + remote_node_timeline oid, + remote_node_dboid oid, + local_tuple_origin_timeline oid, + local_tuple_origin_dboid oid, + local_commit_time timestamptz +); +REVOKE ALL ON TABLE pgactive_conflict_history FROM PUBLIC; +SELECT pg_catalog.pg_extension_config_dump('pgactive_conflict_history', 'WHERE false'); + +ALTER SEQUENCE pgactive_conflict_history_id_seq + OWNED BY pgactive_conflict_history.conflict_id; + +COMMENT ON TABLE pgactive_conflict_history IS 'Log of all conflicts in this pgactive group'; +COMMENT ON COLUMN pgactive_conflict_history.local_node_sysid IS 'sysid of the local node where the apply conflict occurred'; +COMMENT ON COLUMN pgactive_conflict_history.remote_node_sysid IS 'sysid of the remote node the conflicting transaction originated from'; +COMMENT ON COLUMN pgactive_conflict_history.object_schema IS 'Schema of the object involved in the conflict'; +COMMENT ON COLUMN pgactive_conflict_history.object_name IS 'Name of the object (table, etc.) involved in the conflict'; +COMMENT ON COLUMN pgactive_conflict_history.local_conflict_xid IS 'Transaction ID of the apply transaction that encountered the conflict'; +COMMENT ON COLUMN pgactive_conflict_history.local_conflict_lsn IS 'xlog position at the time the conflict occured on the applying node'; +COMMENT ON COLUMN pgactive_conflict_history.local_conflict_time IS 'The time the conflict was detected on the applying node'; +COMMENT ON COLUMN pgactive_conflict_history.remote_txid IS 'xid of the remote transaction involved in the conflict'; +COMMENT ON COLUMN pgactive_conflict_history.remote_commit_time IS 'The time the remote transaction involved in this conflict committed'; +COMMENT ON COLUMN pgactive_conflict_history.remote_commit_lsn IS 'LSN on remote node at which conflicting transaction committed'; +COMMENT ON COLUMN pgactive_conflict_history.conflict_type IS 'Nature of the conflict - insert/insert, update/delete, etc.'; +COMMENT ON COLUMN pgactive_conflict_history.local_tuple IS 'For DML conflicts, the conflicting tuple from the local DB (as json), if logged'; +COMMENT ON COLUMN pgactive_conflict_history.local_tuple_xmin IS 'If local_tuple is set, the xmin of the conflicting local tuple'; +COMMENT ON COLUMN pgactive_conflict_history.local_tuple_origin_sysid IS 'The node id for the true origin of the local tuple. Differs from local_node_sysid if the tuple was originally replicated from another node'; +COMMENT ON COLUMN pgactive_conflict_history.remote_tuple IS 'For DML conflicts, the conflicting tuple from the remote DB (as json), if logged'; +COMMENT ON COLUMN pgactive_conflict_history.conflict_resolution IS 'How the conflict was resolved/handled; see the enum definition'; +COMMENT ON COLUMN pgactive_conflict_history.error_message IS 'On apply error, the error message from ereport/elog. Other error fields match.'; +COMMENT ON COLUMN pgactive_conflict_history.local_commit_time IS 'The time the local transaction involved in this conflict committed'; + +-- The pgactive_nodes table tracks members of a pgactive group; it's only concerned with +-- one pgactive group so it only has to track enough to uniquely identify each +-- member node, which is the (sysid, timeline, dboid) tuple for that node. +-- +-- The sysid must be a numeric (or string) because PostgreSQL has no uint64 SQL +-- type. +-- +-- We don't exclude pgactive_nodes with pg_extension_config_dump because this is a +-- global table that's sync'd between nodes. +-- +CREATE TABLE pgactive_nodes ( + node_sysid text not null, -- Really a uint64 but we have no type for that + node_timeline oid not null, + node_dboid oid not null, -- This is an oid local to the node_sysid cluster + node_status "char" not null, + node_name text not null, + node_dsn text, + node_init_from_dsn text, + node_read_only boolean default false, + node_seq_id smallint, + primary key(node_sysid, node_timeline, node_dboid), + CHECK (node_status in ('b', 'i', 'c', 'o', 'r', 'k')) +); +REVOKE ALL ON TABLE pgactive_nodes FROM PUBLIC; + +-- pgactive.pgactive_nodes gets synced by pgactive_sync_nodes(), it shouldn't be dumped and +-- applied. +SELECT pg_catalog.pg_extension_config_dump('pgactive_nodes', 'WHERE false'); + +-- Add constrains ensuring node_names are unique and not null +CREATE UNIQUE INDEX pgactive_nodes_node_name ON pgactive_nodes(node_name); + +COMMENT ON TABLE pgactive_nodes IS 'All known nodes in this pgactive group'; +COMMENT ON COLUMN pgactive_nodes.node_sysid IS 'pgactive generated node identifier'; +COMMENT ON COLUMN pgactive_nodes.node_timeline IS 'Timeline ID of this node'; +COMMENT ON COLUMN pgactive_nodes.node_dboid IS 'Local database oid on the cluster (node_sysid, node_timeline)'; +COMMENT ON COLUMN pgactive_nodes.node_status IS 'Readiness of the node: [b]eginning setup, [i]nitializing, [c]atchup, creating [o]utbound slots, [r]eady, [k]illed. Doesn''t indicate connected/disconnected.'; + +CREATE TABLE pgactive_global_locks ( + locktype text NOT NULL, + + owning_sysid text NOT NULL, + owning_timeline oid NOT NULL, + owning_datid oid NOT NULL, + + owner_created_lock_at pg_lsn NOT NULL, + + acquired_sysid text NOT NULL, + acquired_timeline oid NOT NULL, + acquired_datid oid NOT NULL, + + acquired_lock_at pg_lsn, + + state text NOT NULL +); +REVOKE ALL ON TABLE pgactive_global_locks FROM PUBLIC; +SELECT pg_catalog.pg_extension_config_dump('pgactive_global_locks', ''); + +CREATE UNIQUE INDEX pgactive_global_locks_byowner +ON pgactive_global_locks(locktype, owning_sysid, owning_timeline, owning_datid); + +CREATE TABLE pgactive_queued_commands ( + lsn pg_lsn NOT NULL, + queued_at TIMESTAMP WITH TIME ZONE NOT NULL, + perpetrator TEXT NOT NULL, + command_tag TEXT NOT NULL, + command TEXT NOT NULL, + search_path TEXT DEFAULT '' +); +REVOKE ALL ON TABLE pgactive_queued_commands FROM PUBLIC; +SELECT pg_catalog.pg_extension_config_dump('pgactive_queued_commands', ''); + +CREATE FUNCTION pgactive_replicate_ddl_command(cmd TEXT) +RETURNS VOID +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE FUNCTION pgactive_truncate_trigger_add() +RETURNS event_trigger +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE FUNCTION pgactive_internal_create_truncate_trigger(regclass) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE FUNCTION pgactive_queue_truncate() +RETURNS TRIGGER +AS 'MODULE_PATHNAME', 'pgactive_queue_truncate' +LANGUAGE C; + +-- This type is tailored to use as input to get_object_address +CREATE TYPE dropped_object AS ( + objtype text, + objnames text[], + objargs text[] +); + +CREATE TABLE pgactive_queued_drops ( + lsn pg_lsn NOT NULL, + queued_at timestamptz NOT NULL, + dropped_objects pgactive.dropped_object[] NOT NULL +); +REVOKE ALL ON TABLE pgactive_queued_drops FROM PUBLIC; +SELECT pg_catalog.pg_extension_config_dump('pgactive_queued_drops', ''); + +CREATE FUNCTION pgactive_apply_pause() +RETURNS VOID +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE FUNCTION pgactive_apply_resume() +RETURNS VOID +AS 'MODULE_PATHNAME' +LANGUAGE C; + +--- Functions for manipulating/displaying replications sets +CREATE FUNCTION pgactive_get_table_replication_sets(relation regclass, OUT sets text[]) + VOLATILE + STRICT + LANGUAGE 'sql' + AS $$ + SELECT + ARRAY( + SELECT * + FROM json_array_elements_text(COALESCE(( + SELECT label::json->'sets' + FROM pg_seclabel + WHERE provider = 'pgactive' + AND classoid = 'pg_class'::regclass + AND objoid = $1::regclass + ), '["default"]')) + )|| '{all}'; + $$; + +CREATE TABLE pgactive_replication_set_config ( + set_name name PRIMARY KEY, + replicate_inserts bool NOT NULL DEFAULT true, + replicate_updates bool NOT NULL DEFAULT true, + replicate_deletes bool NOT NULL DEFAULT true +); +ALTER TABLE pgactive_replication_set_config SET (user_catalog_table = true); + +REVOKE ALL ON TABLE pgactive_replication_set_config FROM PUBLIC; + +-- Fix quoting for format() arguments by directly using regclass with %s +-- instead of %I +CREATE FUNCTION pgactive_set_table_replication_sets(p_relation regclass, p_sets text[]) + RETURNS void + VOLATILE + LANGUAGE 'plpgsql' +-- remove pgactive_permit_unsafe_commands and do not replace +-- by pgactive_skip_ddl_replication for now + SET search_path = '' + AS $$ +DECLARE + v_label json; + setting_value text; +BEGIN + -- emulate STRICT for p_relation parameter + IF p_relation IS NULL THEN + RETURN; + END IF; + + -- query current label + SELECT label::json INTO v_label + FROM pg_catalog.pg_seclabel + WHERE provider = 'pgactive' + AND classoid = 'pg_class'::regclass + AND objoid = p_relation; + + -- replace old 'sets' parameter with new value + SELECT json_object_agg(key, value) INTO v_label + FROM ( + SELECT key, value + FROM json_each(v_label) + WHERE key <> 'sets' + UNION ALL + SELECT + 'sets', to_json(p_sets) + WHERE p_sets IS NOT NULL + ) d; + + -- and now set the appropriate label + -- pgactive_replicate_ddl_command would fail if skip_ddl_replication is true + + SELECT setting INTO setting_value + FROM pg_settings + WHERE name = 'pgactive.skip_ddl_replication'; + + IF setting_value = 'on' or setting_value = 'true' THEN + PERFORM format('SECURITY LABEL FOR pgactive ON TABLE %s IS %L', p_relation, v_label); + ELSE + PERFORM pgactive.pgactive_replicate_ddl_command(format('SECURITY LABEL FOR pgactive ON TABLE %s IS %L', p_relation, v_label)); + END IF; +END; +$$; + +CREATE FUNCTION pgactive_get_local_nodeid ( + sysid OUT text, + timeline OUT oid, + dboid OUT oid) +RETURNS record +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE FUNCTION pgactive_version_num() +RETURNS integer +AS 'MODULE_PATHNAME' +LANGUAGE C; + +COMMENT ON FUNCTION pgactive_version_num() IS +'This pgactive version represented as (major)*10^4 + (minor)*10^2 + (revision). The subrevision is not included. So 0.8.0.1 is 800'; + +CREATE FUNCTION pgactive_min_remote_version_num() +RETURNS integer +AS 'MODULE_PATHNAME' +LANGUAGE C; + +COMMENT ON FUNCTION pgactive_min_remote_version_num() IS +'The oldest pgactive version that this pgactive extension can exchange data with'; + +CREATE FUNCTION _pgactive_get_node_info_private ( + local_dsn text, + remote_dsn text DEFAULT NULL, + sysid OUT text, + timeline OUT oid, + dboid OUT oid, + variant OUT text, + version OUT text, + version_num OUT integer, + min_remote_version_num OUT integer, + has_required_privs OUT boolean, + node_status OUT "char", + node_name OUT text, + dbname OUT text, + dbsize OUT int8, + indexessize OUT int8, + max_nodes OUT integer, + skip_ddl_replication OUT boolean, + cur_nodes OUT integer, + datcollate OUT text, + datctype OUT text) +RETURNS record +AS 'MODULE_PATHNAME','pgactive_get_node_info' +LANGUAGE C; + +REVOKE ALL ON FUNCTION _pgactive_get_node_info_private(text, text) FROM public; + +COMMENT ON FUNCTION _pgactive_get_node_info_private(text, text) IS +'Verify both replication and non-replication connections to the given dsn and get node info; when specified remote_dsn ask remote node to connect back to local node'; + +CREATE TABLE pgactive_connections ( + conn_sysid text not null, + conn_timeline oid not null, + conn_dboid oid not null, -- This is an oid local to the node_sysid cluster + + -- Wondering why there's no FOREIGN KEY to pgactive.pgactive_nodes? + -- + -- pgactive.pgactive_nodes won't be populated when the pgactive.pgactive_connections row gets + -- created on the local node. + + PRIMARY KEY(conn_sysid, conn_timeline, conn_dboid), + conn_dsn text not null, + conn_apply_delay integer + CHECK (conn_apply_delay >= 0), + conn_replication_sets text[] +); + +REVOKE ALL ON TABLE pgactive_connections FROM public; + +COMMENT ON TABLE pgactive_connections IS 'Connection information for nodes in the group. Don''t modify this directly, use the provided functions. One entry should exist per node in the group.'; +COMMENT ON COLUMN pgactive_connections.conn_sysid IS 'System identifer for the node this entry''s dsn refers to'; +COMMENT ON COLUMN pgactive_connections.conn_timeline IS 'System timeline ID for the node this entry''s dsn refers to'; +COMMENT ON COLUMN pgactive_connections.conn_dboid IS 'System database OID for the node this entry''s dsn refers to'; +COMMENT ON COLUMN pgactive_connections.conn_dsn IS 'A libpq-style connection string specifying how to make a connection to this node from other nodes'; +COMMENT ON COLUMN pgactive_connections.conn_apply_delay IS 'If set, milliseconds to wait before applying each transaction from the remote node. Mainly for debugging. If null, the global default applies.'; +COMMENT ON COLUMN pgactive_connections.conn_replication_sets IS 'Replication sets this connection should participate in, if non-default'; + +SELECT pg_catalog.pg_extension_config_dump('pgactive_connections', 'WHERE false'); + +CREATE FUNCTION pgactive_connections_changed() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C; + +REVOKE ALL ON FUNCTION pgactive_connections_changed() FROM public; + +COMMENT ON FUNCTION pgactive_connections_changed() IS +'Internal pgactive function, do not call directly'; + +-- +-- This is a helper for node_join, for internal use only. It's called on the +-- remote end by the init code when joining an existing group, to do the +-- remote-side setup. +-- +CREATE FUNCTION _pgactive_join_node_private ( + sysid text, timeline oid, dboid oid, + node_dsn text, + apply_delay integer, + replication_sets text[] + ) +RETURNS void LANGUAGE plpgsql VOLATILE +SET search_path = pgactive, pg_catalog +AS +$body$ +DECLARE + status "char"; +BEGIN + LOCK TABLE pgactive.pgactive_connections IN EXCLUSIVE MODE; + LOCK TABLE pg_catalog.pg_shseclabel IN EXCLUSIVE MODE; + + -- Assert that the joining node has a pgactive_nodes entry with state = i on this join-target node + SELECT INTO status + FROM pgactive.pgactive_nodes + WHERE node_sysid = sysid + AND node_timeline = timeline + AND node_dboid = dboid; + + IF NOT FOUND THEN + RAISE object_not_in_prerequisite_state + USING MESSAGE = format('pgactive.pgactive_nodes entry for (%s,%s,%s) not found', + sysid, timeline, dboid); + END IF; + + IF status <> 'i' THEN + RAISE object_not_in_prerequisite_state + USING MESSAGE = format('pgactive.pgactive_nodes entry for (%s,%s,%s) has unexpected status %L (expected ''i'')', + sysid, timeline, dboid, status); + END IF; + + -- Insert or Update the connection info on this node, which we must be + -- initing from. + -- No need to care about concurrency here as we hold EXCLUSIVE LOCK. + BEGIN + INSERT INTO pgactive.pgactive_connections + (conn_sysid, conn_timeline, conn_dboid, + conn_dsn, + conn_apply_delay, conn_replication_sets) + VALUES + (sysid, timeline, dboid, node_dsn, + CASE WHEN apply_delay = -1 THEN NULL ELSE apply_delay END, + replication_sets); + EXCEPTION WHEN unique_violation THEN + UPDATE pgactive.pgactive_connections + SET conn_dsn = node_dsn, + conn_apply_delay = CASE WHEN apply_delay = -1 THEN NULL ELSE apply_delay END, + conn_replication_sets = replication_sets + WHERE conn_sysid = sysid + AND conn_timeline = timeline + AND conn_dboid = dboid; + END; + + -- Schedule the apply worker launch for commit time + PERFORM pgactive.pgactive_connections_changed(); +END; +$body$; + +CREATE FUNCTION _pgactive_update_seclabel_private() +RETURNS void LANGUAGE plpgsql +SET search_path = pgactive, pg_catalog +-- SET pgactive.permit_unsafe_ddl_commands = on is removed for now +SET pgactive.skip_ddl_replication = on +-- SET pgactive.skip_ddl_locking = on is removed for now +AS $body$ +DECLARE + v_label json; +BEGIN + -- Update 'pgactive' parameter in the current label if there's one. (Right now, + -- there's not much point to this but later we'll be possibly have more + -- information in there.) + + -- First, select existing label + SELECT label::json INTO v_label + FROM pg_catalog.pg_shseclabel + WHERE provider = 'pgactive' + AND classoid = 'pg_database'::regclass + AND objoid = (SELECT oid FROM pg_database WHERE datname = current_database()); + + -- Then, replace 'pgactive' with 'pgactive'::true + SELECT json_object_agg(key, value) INTO v_label + FROM ( + SELECT key, value + FROM json_each(v_label) + WHERE key <> 'pgactive' + UNION ALL + SELECT 'pgactive', to_json(true) + ) d; + + -- And, set the newly computed label (It's safe to do this early, it won't + -- take effect until commit). + EXECUTE format('SECURITY LABEL FOR pgactive ON DATABASE %I IS %L', + current_database(), v_label); +END; +$body$; + +CREATE FUNCTION _pgactive_begin_join_private ( + caller text, + node_name text, + node_dsn text, + remote_dsn text, + remote_sysid OUT text, + remote_timeline OUT oid, + remote_dboid OUT oid, + bypass_collation_check boolean, + bypass_node_identifier_creation boolean, + bypass_user_tables_check boolean +) +RETURNS record LANGUAGE plpgsql VOLATILE +SET search_path = pgactive, pg_catalog +-- SET pgactive.permit_unsafe_ddl_commands = on is removed for now +SET pgactive.skip_ddl_replication = on +-- SET pgactive.skip_ddl_locking = on is removed for now +AS $body$ +DECLARE + localid RECORD; + localid_from_dsn RECORD; + remote_nodeinfo RECORD; + remote_nodeinfo_r RECORD; + cur_node RECORD; + local_max_node_value integer; + local_skip_ddl_replication_value boolean; + local_db_collation_info_r RECORD; + collation_errmsg text; + collation_hintmsg text; + data_dir text; + temp_dump_dir text; + same_file_system_mount_point boolean; + free_disk_space1 int8; + free_disk_space1_p text; + free_disk_space2 int8; + free_disk_space2_p text; + remote_dbsize_p text; +BEGIN + -- Only one tx can be adding connections + LOCK TABLE pgactive.pgactive_connections IN EXCLUSIVE MODE; + LOCK TABLE pgactive.pgactive_nodes IN EXCLUSIVE MODE; + LOCK TABLE pg_catalog.pg_shseclabel IN EXCLUSIVE MODE; + + -- Generate pgactive node identifier if asked + IF bypass_node_identifier_creation THEN + RAISE WARNING USING + MESSAGE = 'skipping creation of pgactive node identifier for this node', + HINT = 'The ''bypass_node_identifier_creation'' option is only available for pgactive_init_copy tool.'; + ELSE + PERFORM pgactive._pgactive_generate_node_identifier_private(); + END IF; + + SELECT sysid, timeline, dboid INTO localid + FROM pgactive.pgactive_get_local_nodeid(); + + RAISE LOG USING MESSAGE = format('node identity of node being created is (%s,%s,%s)', localid.sysid, localid.timeline, localid.dboid); + + -- If there's already an entry for ourselves in pgactive.pgactive_connections then we + -- know this node is part of an active pgactive group and cannot be joined to + -- another group. + PERFORM 1 FROM pgactive_connections + WHERE conn_sysid = localid.sysid + AND conn_timeline = localid.timeline + AND conn_dboid = localid.dboid; + + IF FOUND THEN + RAISE USING + MESSAGE = 'this node is already a member of a pgactive group', + HINT = 'Connect to the node you wish to add and run '||caller||' from it instead.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + -- Validate that the local connection is usable and matches the node + -- identity of the node we're running on. + -- + -- For pgactive this will NOT check the 'dsn' if 'node_dsn' gets supplied. + -- We don't know if 'dsn' is even valid for loopback connections and can't + -- assume it is. That'll get checked later by pgactive specific code. + -- + -- We'll get a null node name back at this point since we haven't inserted + -- our nodes record (and it wouldn't have committed yet if we had). + -- + SELECT * INTO localid_from_dsn + FROM _pgactive_get_node_info_private(node_dsn); + + IF localid_from_dsn.sysid <> localid.sysid + OR localid_from_dsn.timeline <> localid.timeline + OR localid_from_dsn.dboid <> localid.dboid + THEN + RAISE USING + MESSAGE = 'node identity for local dsn does not match current node', + DETAIL = format($$The dsn '%s' connects to a node with identity (%s,%s,%s) but the local node is (%s,%s,%s)$$, + node_dsn, localid_from_dsn.sysid, localid_from_dsn.timeline, + localid_from_dsn.dboid, localid.sysid, localid.timeline, localid.dboid), + HINT = 'The node_dsn parameter must refer to the node you''re running this function from.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF NOT localid_from_dsn.has_required_privs THEN + RAISE USING + MESSAGE = 'node_dsn does not have required rights', + DETAIL = format($$The dsn '%s' connects successfully but does not have required rights.$$, node_dsn), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF NOT bypass_user_tables_check THEN + PERFORM 1 FROM pg_class r + INNER JOIN pg_namespace n ON r.relnamespace = n.oid + WHERE n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema') + AND relkind = 'r' AND relpersistence = 'p'; + + IF FOUND THEN + RAISE USING + MESSAGE = 'database joining pgactive group has existing user tables', + HINT = 'Ensure no user tables in the database.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + END IF; + + -- Now interrogate the remote node, if specified, and sanity check its + -- connection too. The discovered node identity is returned if found. + -- + -- This will error out if there are issues with the remote node. + IF remote_dsn IS NOT NULL THEN + SELECT * INTO remote_nodeinfo + FROM _pgactive_get_node_info_private(remote_dsn); + + remote_sysid := remote_nodeinfo.sysid; + remote_timeline := remote_nodeinfo.timeline; + remote_dboid := remote_nodeinfo.dboid; + + IF NOT remote_nodeinfo.has_required_privs THEN + RAISE USING + MESSAGE = 'connection to remote node does not have required rights', + DETAIL = format($$The dsn '%s' connects successfully but does not have required rights.$$, remote_dsn), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF remote_nodeinfo.version_num < pgactive_min_remote_version_num() THEN + RAISE USING + MESSAGE = 'remote node''s pgactive version is too old', + DETAIL = format($$The dsn '%s' connects successfully but the remote node version %s is less than the required version %s.$$, + remote_dsn, remote_nodeinfo.version_num, pgactive_min_remote_version_num()), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF remote_nodeinfo.min_remote_version_num > pgactive_version_num() THEN + RAISE USING + MESSAGE = 'remote node''s pgactive version is too new or this node''s version is too old', + DETAIL = format($$The dsn '%s' connects successfully but the remote node version %s requires this node to run at least pgactive %s, not the current %s.$$, + remote_dsn, remote_nodeinfo.version_num, remote_nodeinfo.min_remote_version_num, + pgactive_min_remote_version_num()), + ERRCODE = 'object_not_in_prerequisite_state'; + + END IF; + + IF remote_nodeinfo.node_status IS NULL THEN + RAISE USING + MESSAGE = 'remote node does not appear to be a fully running pgactive node', + DETAIL = format($$The dsn '%s' connects successfully but the target node has no entry in pgactive.pgactive_nodes.$$, remote_dsn), + ERRCODE = 'object_not_in_prerequisite_state'; + ELSIF remote_nodeinfo.node_status IS DISTINCT FROM pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_READY') THEN + RAISE USING + MESSAGE = 'remote node does not appear to be a fully running pgactive node', + DETAIL = format($$The dsn '%s' connects successfully but the target node has pgactive.pgactive_nodes node_status=%s instead of expected 'r'.$$, remote_dsn, remote_nodeinfo.node_status), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT setting::integer INTO local_max_node_value FROM pg_settings + WHERE name = 'pgactive.max_nodes'; + + IF local_max_node_value <> remote_nodeinfo.max_nodes THEN + RAISE USING + MESSAGE = 'joining node and pgactive group have different values for pgactive.max_nodes parameter', + DETAIL = format('pgactive.max_nodes value for joining node is ''%s'' and remote node is ''%s''.', + local_max_node_value, remote_nodeinfo.max_nodes), + HINT = 'The parameter must be set to the same value on all pgactive members.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT setting FROM pg_settings + WHERE name = 'data_directory' INTO data_dir; + + SELECT pgactive.get_free_disk_space(data_dir) INTO free_disk_space1; + SELECT pg_size_pretty(free_disk_space1) INTO free_disk_space1_p; + SELECT pg_size_pretty(remote_nodeinfo.dbsize) INTO remote_dbsize_p; + + -- We estimate that postgres needs 20% more disk space as temporary + -- workspace while restoring database for running queries or building + -- indexes. Note that it is just an estimation, the actual disk space + -- needed depends on various factors. Hence we emit a warning to inform + -- early, not an error. + IF free_disk_space1 < (1.2 * remote_nodeinfo.dbsize) THEN + RAISE WARNING USING + MESSAGE = 'node might fail to join pgactive group as disk space is likely to be insufficient', + DETAIL = format('joining node data directory file system mount point has %s free disk space and remote database is %s in size.', + free_disk_space1_p, remote_dbsize_p), + HINT = 'Ensure enough free space on joining node file system.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT setting FROM pg_settings + WHERE name = 'pgactive.temp_dump_directory' INTO temp_dump_dir; + + SELECT pgactive.get_free_disk_space(temp_dump_dir) INTO free_disk_space2; + SELECT pg_size_pretty(free_disk_space2) INTO free_disk_space2_p; + + -- We estimate that pg_dump needs at least 50% of database size + -- excluding total size of indexes on the database. Note that it is + -- just an estimation, the actual disk space needed depends on various + -- factors. Hence we emit a warning to inform early, not an error. + IF free_disk_space2 < ((remote_nodeinfo.dbsize - remote_nodeinfo.indexessize)/2) THEN + RAISE WARNING USING + MESSAGE = 'node might fail to join pgactive group as disk space required to store temporary dump is likely to be insufficient', + DETAIL = format('pgactive.temp_dump_directory file system mount point has %s free disk space and remote database is %s in size.', + free_disk_space2_p, remote_dbsize_p), + HINT = 'Ensure enough free space on pgactive.temp_dump_directory file system.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT pgactive.check_file_system_mount_points(data_dir, temp_dump_dir) + INTO same_file_system_mount_point; + + IF same_file_system_mount_point THEN + IF free_disk_space1 < + ((1.2 * remote_nodeinfo.dbsize) + ((remote_nodeinfo.dbsize - remote_nodeinfo.indexessize)/2)) THEN + RAISE WARNING USING + MESSAGE = 'node might fail to join pgactive group as disk space required to store both remote database and temporary dump is likely to be insufficient', + HINT = 'Ensure enough free space on joining node file system.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + END IF; + + -- using pg_file_settings here as pgactive.skip_ddl_replication is SET to on when entering + -- the function. + SELECT COALESCE((SELECT setting::boolean + FROM pg_file_settings + WHERE name = 'pgactive.skip_ddl_replication' ORDER BY seqno DESC LIMIT 1), + true) INTO local_skip_ddl_replication_value; + + IF local_skip_ddl_replication_value <> remote_nodeinfo.skip_ddl_replication THEN + RAISE USING + MESSAGE = 'joining node and pgactive group have different values for pgactive.skip_ddl_replication parameter', + DETAIL = format('pgactive.skip_ddl_replication value for joining node is ''%s'' and remote node is ''%s''.', + local_skip_ddl_replication_value, remote_nodeinfo.skip_ddl_replication), + HINT = 'The parameter must be set to the same value on all pgactive members.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF local_max_node_value = remote_nodeinfo.cur_nodes THEN + RAISE USING + MESSAGE = 'cannot allow more than pgactive.max_nodes number of nodes in a pgactive group', + HINT = 'Increase pgactive.max_nodes parameter value on joining node as well as on all other pgactive members.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT datcollate, datctype FROM pg_database + WHERE datname = current_database() INTO local_db_collation_info_r; + + IF local_db_collation_info_r.datcollate <> remote_nodeinfo.datcollate OR + local_db_collation_info_r.datctype <> remote_nodeinfo.datctype THEN + + collation_errmsg := 'joining node and remote node have different database collation settings'; + collation_hintmsg := 'Use the same database collation settings for both nodes.'; + + IF bypass_collation_check THEN + RAISE WARNING USING + MESSAGE = collation_errmsg, + HINT = collation_hintmsg, + ERRCODE = 'object_not_in_prerequisite_state'; + ELSE + RAISE EXCEPTION USING + MESSAGE = collation_errmsg, + HINT = collation_hintmsg, + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + END IF; + END IF; + + -- Create local node record so the apply worker knows to start initializing + -- this node with pgactive_init_replica when it's started. + -- + -- pgactive_init_copy might've created a node entry in catchup mode already, in + -- which case we can skip this. + SELECT * FROM pgactive_nodes + WHERE node_sysid = localid.sysid + AND node_timeline = localid.timeline + AND node_dboid = localid.dboid + INTO cur_node; + + IF NOT FOUND THEN + INSERT INTO pgactive_nodes ( + node_name, + node_sysid, node_timeline, node_dboid, + node_status, node_dsn, node_init_from_dsn + ) VALUES ( + node_name, + localid.sysid, localid.timeline, localid.dboid, + pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_BEGINNING_INIT'), + node_dsn, remote_dsn + ); + ELSIF pgactive.pgactive_node_status_from_char(cur_node.node_status) = 'pgactive_NODE_STATUS_CATCHUP' THEN + RAISE DEBUG 'starting node join in pgactive_NODE_STATUS_CATCHUP'; + ELSE + RAISE USING + MESSAGE = 'a pgactive_nodes entry for this node already exists', + DETAIL = format('pgactive.pgactive_nodes entry for (%s,%s,%s) named ''%s'' with status %s exists.', + cur_node.node_sysid, cur_node.node_timeline, cur_node.node_dboid, + cur_node.node_name, pgactive.pgactive_node_status_from_char(cur_node.node_status)), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + PERFORM pgactive._pgactive_update_seclabel_private(); +END; +$body$; + +-- +-- The public interface for node join/addition, to be run to join a currently +-- unconnected node with a blank database to a pgactive group. +-- +CREATE FUNCTION pgactive_join_group ( + node_name text, + node_dsn text, + join_using_dsn text, + apply_delay integer DEFAULT NULL, + replication_sets text[] DEFAULT ARRAY['default'], + bypass_collation_check boolean DEFAULT false, + bypass_node_identifier_creation boolean DEFAULT false, + bypass_user_tables_check boolean DEFAULT false + ) +RETURNS void LANGUAGE plpgsql VOLATILE +SET search_path = pgactive, pg_catalog +-- SET pgactive.permit_unsafe_ddl_commands = on is removed for now +SET pgactive.skip_ddl_replication = on +-- SET pgactive.skip_ddl_locking = on is removed for now +AS $body$ +DECLARE + localid record; + connectback_nodeinfo record; + remoteinfo record; +BEGIN + + -- Prohibit enabling pgactive where pglogical is installed + IF ( + SELECT count(1) + FROM pg_extension + WHERE extname = 'pglogical' + ) > 0 + THEN + RAISE USING + MESSAGE = 'pgactive can''t be enabled because an external logical replication extension is installed', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = 'pgactive doesn''t allow a node to pull in changes from more than one logical replication sources'; + END IF; + + -- Prohibit enabling pgactive where a subscription exists + IF ( + SELECT count(1) + FROM pg_subscription + WHERE subdbid = (SELECT oid + FROM pg_database + WHERE datname = current_database() + ) + ) > 0 + THEN + RAISE USING + MESSAGE = 'pgactive can''t be enabled because a logical replication subscription is created', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = 'pgactive doesn''t allow a node to pull in changes from more than one logical replication sources'; + END IF; + + IF node_dsn IS NULL THEN + RAISE USING + MESSAGE = 'node_dsn can not be null', + ERRCODE = 'invalid_parameter_value'; + END IF; + + PERFORM pgactive._pgactive_begin_join_private( + caller := '', + node_name := node_name, + node_dsn := node_dsn, + remote_dsn := join_using_dsn, + bypass_collation_check := bypass_collation_check, + bypass_node_identifier_creation := bypass_node_identifier_creation, + bypass_user_tables_check := bypass_user_tables_check); + + SELECT sysid, timeline, dboid INTO localid + FROM pgactive.pgactive_get_local_nodeid(); + + -- Request additional connection tests to determine that the remote is + -- reachable for replication and non-replication mode and that the remote + -- can connect back to us via 'dsn' on non-replication and replication + -- modes. + -- + -- This cannot be checked for the first node since there's no peer to ask + -- for help. + IF join_using_dsn IS NOT NULL THEN + + SELECT * INTO connectback_nodeinfo + FROM pgactive._pgactive_get_node_info_private(node_dsn, join_using_dsn); + + -- The connectback must actually match our local node identity and must + -- provide a connection that has required rights. + IF NOT connectback_nodeinfo.has_required_privs THEN + RAISE USING + MESSAGE = 'node_dsn does not have required rights when connecting via remote node', + DETAIL = format($$The dsn '%s' connects successfully but does not have required rights.$$, dsn), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF (connectback_nodeinfo.sysid, connectback_nodeinfo.timeline, connectback_nodeinfo.dboid) + IS DISTINCT FROM + (localid.sysid, localid.timeline, localid.dboid) + AND + (connectback_nodeinfo.sysid, connectback_nodeinfo.timeline, connectback_nodeinfo.dboid) + IS DISTINCT FROM + (NULL, NULL, NULL) -- Returned by old versions' dummy functions + THEN + RAISE USING + MESSAGE = 'node identity for node_dsn does not match current node when connecting back via remote', + DETAIL = format($$The dsn '%s' connects to a node with identity (%s,%s,%s) but the local node is (%s,%s,%s).$$, + node_dsn, connectback_nodeinfo.sysid, connectback_nodeinfo.timeline, + connectback_nodeinfo.dboid, localid.sysid, localid.timeline, localid.dboid), + HINT = 'The ''node_dsn'' parameter must refer to the node you''re running this function from, from the perspective of the node pointed to by join_using_dsn.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + END IF; + + -- Null/empty checks are skipped, the underlying constraints on the table + -- will catch that for us. + INSERT INTO pgactive.pgactive_connections ( + conn_sysid, conn_timeline, conn_dboid, + conn_dsn, conn_apply_delay, conn_replication_sets + ) VALUES ( + localid.sysid, localid.timeline, localid.dboid, + node_dsn, apply_delay, replication_sets + ); + + -- Now ensure the per-db worker is started if it's not already running. + -- This won't actually take effect until commit time, it just adds a commit + -- hook to start the worker when we commit. + PERFORM pgactive.pgactive_connections_changed(); +END; +$body$; + +COMMENT ON FUNCTION pgactive_join_group(text, text, text, integer, text[], boolean, boolean, boolean) IS +'Join an existing pgactive group by connecting to a member node and copying its contents'; + +CREATE FUNCTION pgactive_create_group ( + node_name text, + node_dsn text, + apply_delay integer DEFAULT NULL, + replication_sets text[] DEFAULT ARRAY['default'] + ) +RETURNS void LANGUAGE plpgsql VOLATILE +SET search_path = pgactive, pg_catalog +-- SET pgactive.permit_unsafe_ddl_commands = on is removed for now +SET pgactive.skip_ddl_replication = on +-- SET pgactive.skip_ddl_locking = on is removed for now +AS $body$ +DECLARE + t record; +BEGIN + + -- Prohibit enabling pgactive where exclusion constraints exist + FOR t IN + SELECT n.nspname, r.relname, c.conname, c.contype + FROM pg_constraint c + INNER JOIN pg_namespace n ON c.connamespace = n.oid + INNER JOIN pg_class r ON c.conrelid = r.oid + INNER JOIN LATERAL unnest(pgactive.pgactive_get_table_replication_sets(c.conrelid)) rs(rsname) ON (rs.rsname = ANY(replication_sets)) + WHERE c.contype = 'x' + AND r.relpersistence = 'p' + AND r.relkind = 'r' + AND n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema') + LOOP + RAISE USING + MESSAGE = 'pgactive can''t be enabled because exclusion constraints exist on persistent tables that are not excluded from replication', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = format('Table %I.%I has exclusion constraint %I.', t.nspname, t.relname, t.conname), + HINT = 'Drop the exclusion constraint(s), change the table(s) to UNLOGGED if they don''t need to be replicated, or exclude the table(s) from the active replication set(s).'; + END LOOP; + + -- Warn users about secondary unique indexes + FOR t IN + SELECT n.nspname, r.relname, c.conname, c.contype + FROM pg_constraint c + INNER JOIN pg_namespace n ON c.connamespace = n.oid + INNER JOIN pg_class r ON c.conrelid = r.oid + INNER JOIN LATERAL unnest(pgactive.pgactive_get_table_replication_sets(c.conrelid)) rs(rsname) ON (rs.rsname = ANY(replication_sets)) + WHERE c.contype = 'u' + AND r.relpersistence = 'p' + AND r.relkind = 'r' + AND n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema') + LOOP + RAISE WARNING USING + MESSAGE = 'secondary unique constraint(s) exist on replicated table(s)', + DETAIL = format('Table %I.%I has secondary unique constraint %I. This may cause unhandled replication conflicts.', t.nspname, t.relname, t.conname), + HINT = 'Drop the secondary unique constraint(s), change the table(s) to UNLOGGED if they don''t need to be replicated, or exclude the table(s) from the active replication set(s).'; + END LOOP; + + -- Warn users about missing primary keys + FOR t IN + SELECT n.nspname, r.relname, c.conname + FROM pg_class r INNER JOIN pg_namespace n ON r.relnamespace = n.oid + LEFT OUTER JOIN pg_constraint c ON (c.conrelid = r.oid AND c.contype = 'p') + WHERE n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema') + AND relkind = 'r' + AND relpersistence = 'p' + AND c.oid IS NULL + LOOP + RAISE WARNING USING + MESSAGE = format('table %I.%I has no PRIMARY KEY', t.nspname, t.relname), + HINT = 'Tables without a PRIMARY KEY cannot be UPDATEd or DELETEd from, only INSERTed into. Add a PRIMARY KEY.'; + END LOOP; + + -- Create ON TRUNCATE triggers for pgactive on existing tables + -- See pgactive_truncate_trigger_add for the matching event trigger for tables + -- created after join. + -- + -- The triggers may be created already because the pgactive event trigger + -- runs when the pgactive extension is created, even if there's no active + -- pgactive connections yet, so tables created after the extension is created + -- will get the trigger already. So skip tables that have a tg named + -- 'truncate_trigger' calling proc 'pgactive.pgactive_queue_truncate'. + FOR t IN + SELECT r.oid AS relid + FROM pg_class r + INNER JOIN pg_namespace n ON (r.relnamespace = n.oid) + LEFT JOIN pg_trigger tg ON (r.oid = tg.tgrelid AND tgname = 'truncate_trigger') + LEFT JOIN pg_proc p ON (p.oid = tg.tgfoid AND p.proname = 'pgactive_queue_truncate') + LEFT JOIN pg_namespace pn ON (pn.oid = p.pronamespace AND pn.nspname = 'pgactive') + WHERE r.relpersistence = 'p' + AND r.relkind = 'r' + AND n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema') + AND tg.oid IS NULL AND p.oid IS NULL and pn.oid IS NULL + LOOP + -- We use a C function here because in addition to trigger creation + -- we must also mark it tgisinternal. + PERFORM pgactive.pgactive_internal_create_truncate_trigger(t.relid); + END LOOP; + + PERFORM pgactive.pgactive_join_group( + node_name := node_name, + node_dsn := node_dsn, + join_using_dsn := null, + apply_delay := apply_delay, + replication_sets := replication_sets, + bypass_user_tables_check := true); +END; +$body$; + +COMMENT ON FUNCTION pgactive_create_group(text, text, integer, text[]) IS +'Create a pgactive group, turning a stand-alone database into the first node in a pgactive group'; + +CREATE FUNCTION pgactive_detach_nodes(p_nodes text[]) +RETURNS void LANGUAGE plpgsql VOLATILE +SET search_path = pgactive, pg_catalog +-- SET pgactive.permit_unsafe_ddl_commands = on is removed for now +SET pgactive.skip_ddl_replication = on +-- SET pgactive.skip_ddl_locking = on is removed for now +AS $body$ +DECLARE + unknown_node_names text := NULL; + r record; +BEGIN + -- concurrency + LOCK TABLE pgactive.pgactive_connections IN EXCLUSIVE MODE; + LOCK TABLE pgactive.pgactive_nodes IN EXCLUSIVE MODE; + LOCK TABLE pg_catalog.pg_shseclabel IN EXCLUSIVE MODE; + + -- Ensure we're not running on the node being detached. + -- We can't safely ensure that the change gets replicated + -- to peer nodes before we cut off our local connections + -- if running on the node being detached. + -- + -- (This restriction can be lifted later if we add + -- multi-phase negotiated detach). + -- + IF pgactive.pgactive_get_local_node_name() = ANY(p_nodes) THEN + -- One exception is if we're the only live node + IF (SELECT count(node_status) FROM pgactive.pgactive_nodes WHERE node_status IN (pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_READY'))) > 1 THEN + RAISE USING + MESSAGE = 'cannot detach a node from its self', + DETAIL = 'Attempted to pgactive_detach_nodes(...) on node '||pgactive.pgactive_get_local_node_name()||' which is one of the nodes being detached.', + HINT = 'You must call call pgactive_detach_nodes on a node that is not being removed.', + ERRCODE = 'object_in_use'; + ELSE + RAISE WARNING USING + MESSAGE = 'detaching last node', + HINT = 'Marking last node as detached. To remove pgactive completely use pgactive.pgactive_remove(...).'; + END IF; + END IF; + + SELECT + string_agg(to_remove.remove_node_name, ', ') + FROM + pgactive.pgactive_nodes + RIGHT JOIN unnest(p_nodes) AS to_remove(remove_node_name) + ON (pgactive_nodes.node_name = to_remove.remove_node_name) + WHERE pgactive_nodes.node_name IS NULL + INTO unknown_node_names; + + IF unknown_node_names IS NOT NULL THEN + RAISE USING + MESSAGE = format('no node(s) named %s found', unknown_node_names), + ERRCODE = 'no_data_found'; + END IF; + + FOR r IN + SELECT + node_name, node_status + FROM + pgactive.pgactive_nodes + INNER JOIN unnest(p_nodes) AS to_remove(remove_node_name) + ON (pgactive_nodes.node_name = to_remove.remove_node_name) + WHERE pgactive_nodes.node_status <> pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_READY') + LOOP + IF r.node_status = pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_KILLED') THEN + RAISE INFO 'node %i is already detached, ignoring', r.node_name; + ELSE + RAISE WARNING 'node % is in state % not expected ''r'' (pgactive_NODE_STATUS_READY), attempting to remove anyway', + r.node_name, r.node_status; + END IF; + END LOOP; + + DELETE from pgactive.pgactive_connections + WHERE (conn_sysid, conn_timeline, conn_dboid) + in (select node_sysid, node_timeline, node_dboid FROM + pgactive.pgactive_nodes WHERE node_name = ANY(p_nodes)); + + UPDATE pgactive.pgactive_nodes + SET node_status = pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_KILLED') + WHERE node_name = ANY(p_nodes); + + -- Notify local perdb worker to kill nodes. + PERFORM pgactive.pgactive_connections_changed(); +END; +$body$; + +CREATE FUNCTION pgactive_wait_for_node_ready( + timeout integer DEFAULT 0, + progress_interval integer DEFAULT 60) +RETURNS void LANGUAGE plpgsql VOLATILE +AS $body$ +DECLARE + r1 record; + r2 record; + t_lp_cnt integer := 0; + p_lp_cnt integer := 0; + first_time boolean := true; + l_db_init_sz int8; + l_db_sz int8; + r_db text; + p_pct integer; + p_stime timestamp; + p_etime timestamp; + p_elapsed interval; +BEGIN + + IF timeout < 0 THEN + RAISE EXCEPTION '''timeout'' parameter must not be negative'; + END IF; + + IF progress_interval < 0 THEN + RAISE EXCEPTION '''progress_interval'' parameter must not be negative'; + END IF; + + IF current_setting('transaction_isolation') <> 'read committed' THEN + RAISE EXCEPTION 'can only wait for node join in an ISOLATION LEVEL READ COMMITTED transaction, not %', + current_setting('transaction_isolation'); + END IF; + + LOOP + SELECT * FROM pgactive.pgactive_nodes + WHERE (node_sysid, node_timeline, node_dboid) + = pgactive.pgactive_get_local_nodeid() + INTO r1; + + PERFORM pg_sleep(1); + + IF r1.node_status = 'r' THEN + IF progress_interval > 0 AND r2 IS NOT NULL THEN + p_etime := clock_timestamp(); + p_elapsed := p_etime - p_stime; + RAISE NOTICE + USING MESSAGE = format('successfully restored database ''%s'' from node %s in %s', + r2.dbname, r2.node_name, p_elapsed); + END IF; + EXIT; + END IF; + + IF timeout > 0 THEN + t_lp_cnt := t_lp_cnt + 1; + IF t_lp_cnt > timeout THEN + RAISE EXCEPTION 'node % cannot reach ready state within % seconds, current state is %', + r1.node_name, timeout, r1.node_status; + END IF; + END IF; + + IF progress_interval > 0 AND r1.node_init_from_dsn IS NOT NULL THEN + p_lp_cnt := p_lp_cnt + 1; + + IF first_time THEN + SELECT * FROM pgactive._pgactive_get_node_info_private(r1.node_init_from_dsn) + INTO r2; + SELECT pg_size_pretty(r2.dbsize) INTO r_db; + SELECT pg_database_size(r1.node_dboid) INTO l_db_init_sz; + p_stime := clock_timestamp(); + first_time := false; + END IF; + + IF p_lp_cnt > progress_interval THEN + SELECT pg_database_size(r1.node_dboid) INTO l_db_sz; + IF l_db_sz = 0 OR l_db_sz = l_db_init_sz THEN + RAISE NOTICE + USING MESSAGE = format('transferring of database ''%s'' (%s) from node %s in progress', + r2.dbname, r_db, r2.node_name); + ELSE + SELECT ROUND((l_db_sz::real/r2.dbsize::real) * 100.0) INTO p_pct; + RAISE NOTICE + USING MESSAGE = format('restoring database ''%s'', %s%% of %s complete', + r2.dbname, p_pct, r_db); + END IF; + p_lp_cnt := 0; + END IF; + END IF; + END LOOP; +END; +$body$; + +CREATE TYPE pgactive_sync_type AS ENUM ('none', 'full'); + +CREATE FUNCTION pgactive_parse_slot_name ( + slot_name name, + remote_sysid OUT text, + remote_timeline OUT oid, + remote_dboid OUT oid, + local_dboid OUT oid, + replication_name OUT name +) +RETURNS record +AS 'MODULE_PATHNAME','pgactive_parse_slot_name_sql' +LANGUAGE C STRICT IMMUTABLE; + +COMMENT ON FUNCTION pgactive_parse_slot_name(name) IS +'Parse a slot name from the pgactive plugin and report the embedded field values'; + +CREATE FUNCTION pgactive_format_slot_name ( + remote_sysid text, + remote_timeline oid, + remote_dboid oid, + local_dboid oid, + replication_name name DEFAULT '' +) +RETURNS name +AS 'MODULE_PATHNAME','pgactive_format_slot_name_sql' +LANGUAGE C STRICT IMMUTABLE; + +COMMENT ON FUNCTION pgactive_format_slot_name(text, oid, oid, oid, name) IS +'Format a pgactive slot name from node identity parameters'; + +CREATE FUNCTION pgactive_get_local_node_name() RETURNS text +LANGUAGE sql +AS $$ +SELECT node_name +FROM pgactive.pgactive_nodes n, + pgactive.pgactive_get_local_nodeid() i +WHERE n.node_sysid = i.sysid + AND n.node_timeline = i.timeline + AND n.node_dboid = i.dboid; +$$; + +COMMENT ON FUNCTION pgactive_get_local_node_name() IS +'Return the name from pgactive.pgactive_nodes for the local node, or null if no entry exists'; + +CREATE FUNCTION pgactive_is_apply_paused() +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE FUNCTION pgactive_set_node_read_only (node_name text, read_only boolean) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C VOLATILE; + +CREATE FUNCTION pgactive_get_workers_info ( + OUT sysid text, + OUT timeline oid, + OUT dboid oid, + OUT worker_type text, + OUT pid int4 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C VOLATILE STRICT; + +CREATE FUNCTION pgactive_terminate_workers(text, oid, oid, text) +RETURNS boolean +LANGUAGE SQL +AS $$ +SELECT pg_catalog.pg_terminate_backend(pid) FROM pgactive.pgactive_get_workers_info() +-- For per-db worker, we don't expect sysid and timeline, but rely on dboid. + WHERE CASE WHEN worker_type = 'per-db' THEN (dboid, worker_type) = ($3, $4) + ELSE (sysid, timeline, dboid, worker_type) = ($1, $2, $3, $4) END; +$$; + +CREATE FUNCTION pgactive_skip_changes( + from_sysid text, + from_timeline oid, + from_dboid oid, + upto_lsn pg_lsn) +RETURNS void +AS 'MODULE_PATHNAME','pgactive_skip_changes' +LANGUAGE C; + +CREATE FUNCTION pgactive_get_connection_replication_sets(target_node_name text) +RETURNS text[] +LANGUAGE plpgsql +AS $$ +DECLARE + sysid text; + timeline oid; + dboid oid; + replication_sets text[]; +BEGIN + SELECT node_sysid, node_timeline, node_dboid + FROM pgactive.pgactive_nodes + WHERE node_name = target_node_name + INTO sysid, timeline, dboid; + + IF NOT FOUND THEN + RAISE EXCEPTION 'no node with name % found in pgactive.pgactive_nodes',target_node_name; + END IF; + + IF ( + SELECT count(1) + FROM pgactive.pgactive_connections + WHERE conn_sysid = sysid + AND conn_timeline = timeline + AND conn_dboid = dboid + ) > 1 + THEN + RAISE WARNING 'there are node-specific override entries for node % in pgactive.pgactive_connections. Only the default connection''s replication sets will be returned.',node_name; + END IF; + + SELECT pgactive.pgactive_get_connection_replication_sets(sysid, timeline, dboid) INTO replication_sets; + RETURN replication_sets; +END; +$$; + +CREATE FUNCTION pgactive_set_connection_replication_sets ( + replication_sets text[], + target_node_name text +) +RETURNS void +LANGUAGE plpgsql +AS $$ +DECLARE + sysid text; + timeline oid; + dboid oid; +BEGIN + SELECT node_sysid, node_timeline, node_dboid + FROM pgactive.pgactive_nodes + WHERE node_name = target_node_name + INTO sysid, timeline, dboid; + + IF NOT FOUND THEN + RAISE EXCEPTION 'no node with name % found in pgactive.pgactive_nodes',target_node_name; + END IF; + + IF ( + SELECT count(1) + FROM pgactive.pgactive_connections + WHERE conn_sysid = sysid + AND conn_timeline = timeline + AND conn_dboid = dboid + ) > 1 + THEN + RAISE WARNING 'there are node-specific override entries for node % in pgactive.pgactive_connections. Only the default connection''s replication sets will be changed. Use the 6-argument form of this function to change others.',node_name; + END IF; + + PERFORM pgactive.pgactive_set_connection_replication_sets(replication_sets, sysid, timeline, dboid); +END; +$$; + +CREATE FUNCTION _pgactive_pause_worker_management_private(boolean) +RETURNS void +AS 'MODULE_PATHNAME','pgactive_pause_worker_management' +LANGUAGE C; + +COMMENT ON FUNCTION _pgactive_pause_worker_management_private(boolean) IS +'pgactive-internal function for test use only'; + +CREATE FUNCTION pgactive_parse_replident_name ( + replident text, + remote_sysid OUT text, + remote_timeline OUT oid, + remote_dboid OUT oid, + local_dboid OUT oid, + replication_name OUT name +) +RETURNS record +AS 'MODULE_PATHNAME','pgactive_parse_replident_name_sql' +LANGUAGE C STRICT IMMUTABLE; + +COMMENT ON FUNCTION pgactive_parse_replident_name(text) IS +'Parse a replication identifier name from the pgactive plugin and report the embedded field values'; + +CREATE FUNCTION pgactive_format_replident_name ( + remote_sysid text, + remote_timeline oid, + remote_dboid oid, + local_dboid oid, + replication_name name DEFAULT '' +) +RETURNS text +AS 'MODULE_PATHNAME','pgactive_format_replident_name_sql' +LANGUAGE C STRICT IMMUTABLE; + +COMMENT ON FUNCTION pgactive_format_replident_name(text, oid, oid, oid, name) IS +'Format a pgactive replication identifier name from node identity parameters'; + +CREATE FUNCTION _pgactive_destroy_temporary_dump_directories_private() +RETURNS void +AS 'MODULE_PATHNAME','pgactive_destroy_temporary_dump_directories' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION _pgactive_destroy_temporary_dump_directories_private() FROM public; + +COMMENT ON FUNCTION _pgactive_destroy_temporary_dump_directories_private() IS +'Remove temporary dump directories used for node initialization.'; + +-- Completely de-pgactive-ize a node. Updated to fix #281. +CREATE FUNCTION pgactive_remove ( + force boolean DEFAULT false) +RETURNS void +LANGUAGE plpgsql +-- SET pgactive.skip_ddl_locking = on is removed for now +-- SET pgactive.permit_unsafe_ddl_commands = on is removed for now +SET pgactive.skip_ddl_replication = on +SET search_path = 'pgactive,pg_catalog' +AS $$ +DECLARE + local_node_status "char"; + _seqschema name; + _seqname name; + _seqmax bigint; + _tableoid oid; + _truncate_tg record; +BEGIN + + SELECT node_status FROM pgactive.pgactive_nodes WHERE (node_sysid, node_timeline, node_dboid) = pgactive.pgactive_get_local_nodeid() + INTO local_node_status; + + IF NOT (local_node_status = 'k' OR local_node_status IS NULL) THEN + IF force THEN + RAISE WARNING 'forcing deletion of possibly active pgactive node'; + + UPDATE pgactive.pgactive_nodes + SET node_status = 'k' + WHERE (node_sysid, node_timeline, node_dboid) = pgactive.pgactive_get_local_nodeid(); + + PERFORM pgactive._pgactive_pause_worker_management_private(false); + + PERFORM pg_sleep(5); + + RAISE NOTICE 'node forced to detached state, now removing'; + ELSE + RAISE EXCEPTION 'this pgactive node might still be active, not removing'; + END IF; + END IF; + + RAISE NOTICE 'removing pgactive from node'; + + -- Strip the database security label + EXECUTE format('SECURITY LABEL FOR pgactive ON DATABASE %I IS NULL', current_database()); + + -- Suspend worker management, so when we terminate apply workers and + -- walsenders they won't get relaunched. + PERFORM pgactive._pgactive_pause_worker_management_private(true); + + -- Terminate WAL sender(s) associated with this database. + PERFORM pgactive.pgactive_terminate_workers(node_sysid, node_timeline, node_dboid, 'walsender') + FROM pgactive.pgactive_nodes + WHERE (node_sysid, node_timeline, node_dboid) <> pgactive.pgactive_get_local_nodeid(); + + -- Terminate apply worker(s) associated with this database. + PERFORM pgactive.pgactive_terminate_workers(node_sysid, node_timeline, node_dboid, 'apply') + FROM pgactive.pgactive_nodes + WHERE (node_sysid, node_timeline, node_dboid) <> pgactive.pgactive_get_local_nodeid(); + + -- Delete all connections and all nodes except the current one + DELETE FROM pgactive.pgactive_connections + WHERE (conn_sysid, conn_timeline, conn_dboid) <> pgactive.pgactive_get_local_nodeid(); + + DELETE FROM pgactive.pgactive_nodes + WHERE (node_sysid, node_timeline, node_dboid) <> pgactive.pgactive_get_local_nodeid(); + + -- Let the perdb worker resume work and figure out everything's + -- going away. + PERFORM pgactive._pgactive_pause_worker_management_private(false); + PERFORM pgactive.pgactive_connections_changed(); + + -- Give it a few seconds + PERFORM pg_sleep(2); + + -- Terminate per-db worker associated with this database. + PERFORM pgactive.pgactive_terminate_workers(sysid, timeline, dboid, 'per-db') + FROM pgactive.pgactive_get_local_nodeid(); + + -- Clear out the rest of pgactive_nodes and pgactive_connections + DELETE FROM pgactive.pgactive_nodes; + DELETE FROM pgactive.pgactive_connections; + + -- Drop peer replication slots for this DB + PERFORM pg_drop_replication_slot(slot_name) + FROM pg_catalog.pg_replication_slots, + pgactive.pgactive_parse_slot_name(slot_name) ps + WHERE ps.local_dboid = (select oid from pg_database where datname = current_database()) + AND plugin = 'pgactive'; + + -- and replication origins + PERFORM pg_replication_origin_drop(roname) + FROM pg_catalog.pg_replication_origin, + pgactive.pgactive_parse_replident_name(roname) pi + WHERE pi.local_dboid = (select oid from pg_database where datname = current_database()); + + -- Strip the security labels we use for replication sets from all the tables + FOR _tableoid IN + SELECT objoid + FROM pg_catalog.pg_seclabel + INNER JOIN pg_catalog.pg_class ON (pg_seclabel.objoid = pg_class.oid) + WHERE provider = 'pgactive' + AND classoid = 'pg_catalog.pg_class'::regclass + AND pg_class.relkind = 'r' + LOOP + -- regclass's text out adds quoting and schema qualification if needed + EXECUTE format('SECURITY LABEL FOR pgactive ON TABLE %s IS NULL', _tableoid::regclass); + END LOOP; + + -- Drop the on-truncate triggers. They'd otherwise get cascade-dropped when + -- the pgactive extension was dropped, but this way the system is clean. We can't + -- drop ones under the 'pgactive' schema. + FOR _truncate_tg IN + SELECT + n.nspname AS tgrelnsp, + c.relname AS tgrelname, + t.tgname AS tgname, + d.objid AS tgobjid, + d.refobjid AS tgrelid + FROM pg_depend d + INNER JOIN pg_class c ON (d.refclassid = 'pg_class'::regclass AND d.refobjid = c.oid) + INNER JOIN pg_namespace n ON (c.relnamespace = n.oid) + INNER JOIN pg_trigger t ON (d.classid = 'pg_trigger'::regclass and d.objid = t.oid) + INNER JOIN pg_depend d2 ON (d.classid = d2.classid AND d.objid = d2.objid) + WHERE tgname LIKE 'truncate_trigger_%' + AND d2.refclassid = 'pg_proc'::regclass + AND d2.refobjid = 'pgactive.pgactive_queue_truncate'::regproc + AND n.nspname <> 'pgactive' + LOOP + EXECUTE format('DROP TRIGGER %I ON %I.%I', + _truncate_tg.tgname, _truncate_tg.tgrelnsp, _truncate_tg.tgrelname); + + -- The trigger' dependency entry will be dangling because of how we dropped + -- it. + DELETE FROM pg_depend + WHERE classid = 'pg_trigger'::regclass AND + (objid = _truncate_tg.tgobjid + AND (refclassid = 'pg_proc'::regclass AND refobjid = 'pgactive.pgactive_queue_truncate'::regproc) + OR + (refclassid = 'pg_class'::regclass AND refobjid = _truncate_tg.tgrelid) + ); + + END LOOP; + + -- Delete the other detritus from the extension. The user should really drop it, + -- but we should try to restore a clean state anyway. + DELETE FROM pgactive.pgactive_queued_commands; + DELETE FROM pgactive.pgactive_queued_drops; + DELETE FROM pgactive.pgactive_global_locks; + DELETE FROM pgactive.pgactive_conflict_handlers; + DELETE FROM pgactive.pgactive_conflict_history; + DELETE FROM pgactive.pgactive_replication_set_config; + + PERFORM pgactive._pgactive_destroy_temporary_dump_directories_private(); + + -- We can't drop the pgactive extension, we just need to tell the user to do that. + RAISE NOTICE 'pgactive removed from this node. You can now DROP EXTENSION pgactive and, if this is the last pgactive node on this PostgreSQL instance, remove pgactive from shared_preload_libraries.'; +END; +$$; + +REVOKE ALL ON FUNCTION pgactive_remove(boolean) FROM public; + +COMMENT ON FUNCTION pgactive_remove(boolean) IS +'Remove all pgactive security labels, slots, replication origins, replication sets, etc from the local node.'; + +CREATE FUNCTION pgactive_is_active_in_db() +RETURNS boolean +AS 'MODULE_PATHNAME','pgactive_is_active_in_db' +LANGUAGE C; + +CREATE EVENT TRIGGER pgactive_truncate_trigger_add +ON ddl_command_end +EXECUTE PROCEDURE pgactive.pgactive_truncate_trigger_add(); + +ALTER EVENT TRIGGER pgactive_truncate_trigger_add ENABLE ALWAYS; + +-- Marking this immutable is technically a bit cheeky as we could add new +-- statuses. But for index use we need it, and it's safe since any unrecognised +-- entries will result in ERRORs and can thus never exist in an index. +CREATE FUNCTION pgactive_node_status_from_char("char") +RETURNS text +AS 'MODULE_PATHNAME','pgactive_node_status_from_char' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION pgactive_node_status_to_char(text) +RETURNS "char" +AS 'MODULE_PATHNAME','pgactive_node_status_to_char' +LANGUAGE C STRICT IMMUTABLE; + +-- pgactive doesn't like partial unique indexes. We'd really like an index like: +-- +-- CREATE UNIQUE INDEX ON pgactive.pgactive_nodes(node_seq_id) WHERE (node_status IN (pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_READY'))); +-- +-- But, the simple way we do updates to those catalogs doesn't support partial +-- or expression indexes. So no constraint enforces node ID uniqueness. + +CREATE FUNCTION pgactive_snowflake_id_nextval(regclass) +RETURNS bigint +AS 'MODULE_PATHNAME','pgactive_snowflake_id_nextval_oid' +LANGUAGE C STRICT VOLATILE; + +COMMENT ON FUNCTION pgactive_snowflake_id_nextval(regclass) IS +'Generate sequence values unique to this node using a local sequence as a seed'; + +-- For testing purposes we sometimes want to be able to override the timestamp +-- etc. +CREATE FUNCTION _pgactive_snowflake_id_nextval_private(regclass, bigint) +RETURNS bigint +AS 'MODULE_PATHNAME','pgactive_snowflake_id_nextval_oid' +LANGUAGE C STRICT VOLATILE; + +COMMENT ON FUNCTION _pgactive_snowflake_id_nextval_private(regclass, bigint) IS +'Function for pgactive testing only, do not use in application code'; + +CREATE FUNCTION pgactive_acquire_global_lock(lockmode text) +RETURNS void +AS 'MODULE_PATHNAME','pgactive_acquire_global_lock' +LANGUAGE C VOLATILE STRICT; + +REVOKE ALL ON FUNCTION pgactive_acquire_global_lock(text) FROM public; + +COMMENT ON FUNCTION pgactive_acquire_global_lock(text) IS +'Acquire pgactive global lock ("ddl lock") in specified mode'; + +CREATE FUNCTION pgactive_xact_replication_origin(xid) +RETURNS oid +AS 'MODULE_PATHNAME','pgactive_xact_replication_origin' +LANGUAGE C; + +REVOKE ALL ON FUNCTION pgactive_xact_replication_origin(xid) FROM public; + +COMMENT ON FUNCTION pgactive_xact_replication_origin(xid) IS +'Get replication origin id for a given transaction'; + +-- +-- When upgrading an existing cluster we must assign node sequence IDs. +-- +-- We can't do that safely during the upgrade script run since the changes +-- won't get replicated, so we have do it as a user-initiated post-upgrade +-- step. +-- +CREATE FUNCTION pgactive_assign_seq_ids_post_upgrade() +RETURNS void LANGUAGE plpgsql AS +$$ +DECLARE + errd text; + dofail boolean := false; + n record; +BEGIN + -- Refuse to run if it looks like there might be a dangling 'i' node or + -- something. + errd := 'One or more nodes have status other than expected pgactive_NODE_STATUS_READY (r) or pgactive_NODE_STATUS_KILLED (k): '; + FOR n IN + SELECT * FROM pgactive.pgactive_nodes WHERE node_status NOT IN ( + pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_READY'), + pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_KILLED') + ) + LOOP + errd := errd || 'node % has status % (%);', n.node_name, n.node_status; + dofail := true; + END LOOP; + IF dofail THEN + RAISE USING + MESSAGE = 'cannot upgrade pgactive extension because some nodes are not ready', + DETAIL = errd, + HINT = 'Make sure no nodes are joining or partially joined in pgactive.pgactive_nodes.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + -- if all nodes look sensible, generate sequence IDs, skipping over any + -- already-assigned values, and start counting from the lowest assigned + -- value. In theory there shouldn't be one, but we don't actively stop users + -- joining nodes when some other nodes have no node_seq_id, so there could + -- be... + UPDATE pgactive.pgactive_nodes + SET node_seq_id = seqid + FROM ( + SELECT + n2.node_sysid, n2.node_timeline, n2.node_dboid, + ( + row_number() + OVER (ORDER BY n2.node_sysid, n2.node_timeline, n2.node_dboid) + + + coalesce(( + SELECT max(n3.node_seq_id) + FROM pgactive.pgactive_nodes n3 + WHERE n3.node_status NOT IN (pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_KILLED')) + ), 0) + ) AS node_seq_id + FROM pgactive.pgactive_nodes n2 + WHERE n2.node_status NOT IN (pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_KILLED')) + AND n2.node_seq_id IS NULL + ) n(sysid, timeline, dboid, seqid) + WHERE (node_sysid, node_timeline, node_dboid) = + (sysid, timeline, dboid) + AND node_seq_id IS NULL; + +END; +$$; + +CREATE FUNCTION pgactive_get_global_locks_info ( + OUT owner_replorigin oid, + OUT owner_sysid text, + OUT owner_timeline oid, + OUT owner_dboid oid, + OUT lock_mode text, + OUT lock_state text, + OUT owner_local_pid integer, + /* rest is lower level diagnostic stuff */ + OUT lockcount integer, + OUT npeers integer, + OUT npeers_confirmed integer, + OUT npeers_declined integer, + OUT npeers_replayed integer, + OUT replay_upto pg_lsn) +RETURNS record +AS 'MODULE_PATHNAME', 'pgactive_get_global_locks_info' +LANGUAGE C VOLATILE; + +COMMENT ON FUNCTION pgactive_get_global_locks_info() IS +'Backing function for pgactive_global_locks_info view'; + +CREATE VIEW pgactive_global_locks_info AS +SELECT + owner_replorigin = 0 AS owner_is_my_node, + owner_sysid, owner_timeline, owner_dboid, + (SELECT node_name FROM pgactive.pgactive_nodes WHERE (node_sysid,node_timeline,node_dboid) = (owner_sysid, owner_timeline, owner_dboid)) AS owner_node_name, + lock_mode, lock_state, owner_local_pid, + coalesce(owner_local_pid = pg_backend_pid(),'f') AS owner_is_my_backend, + owner_replorigin, + lockcount, npeers, npeers_confirmed, npeers_declined, npeers_replayed, + replay_upto +FROM pgactive_get_global_locks_info(); + +COMMENT ON VIEW pgactive_global_locks_info IS +'Diagnostic information on pgactive global locking state, see manual'; + +CREATE FUNCTION pgactive_wait_for_slots_confirmed_flush_lsn(slotname name, target pg_lsn) +RETURNS void +AS 'MODULE_PATHNAME','pgactive_wait_for_slots_confirmed_flush_lsn' +LANGUAGE C; + +COMMENT ON FUNCTION pgactive_wait_for_slots_confirmed_flush_lsn(name,pg_lsn) IS +'Wait until slotname (or all slots, if null) has passed specified lsn (or current lsn, if null)'; + +CREATE FUNCTION pgactive_handle_rejoin() + RETURNS trigger AS +$$ +BEGIN +-- Don't insert any rows on the re-joining node with a 'k' status. +-- That way, duplicated keys on the primary key or node_name are avoided. + IF NEW.node_status = 'k' THEN + RETURN NULL; + +-- Adding a new node (could be the re-joining node) + ELSIF NEW.node_status = 'i' THEN +-- We must ensure the delete done below on the other nodes matches the primary +-- key on the re-joining node (so update the primary key accordingly). +-- That way the delete can be propagated safely on the re-joining node. + UPDATE pgactive.pgactive_nodes SET node_sysid = NEW.node_sysid + WHERE node_status = 'k' + AND node_timeline = NEW.node_timeline + AND node_dboid = NEW.node_dboid + AND node_name = NEW.node_name; +-- Delete the existing entry related to the re-joining node, so that it can be +-- re-inserted with the right status. + DELETE FROM pgactive.pgactive_nodes + WHERE node_status = 'k' + AND node_sysid = NEW.node_sysid + AND node_timeline = NEW.node_timeline + AND node_dboid = NEW.node_dboid; + END IF; + RETURN NEW; +END;$$ +LANGUAGE 'plpgsql'; + +CREATE TRIGGER pgactive_handle_rejoin_trigg +BEFORE INSERT +ON pgactive.pgactive_nodes +FOR EACH ROW +EXECUTE PROCEDURE pgactive_handle_rejoin(); + +CREATE FUNCTION _pgactive_generate_node_identifier_private() +RETURNS void +AS 'MODULE_PATHNAME','pgactive_generate_node_identifier' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION _pgactive_generate_node_identifier_private() FROM PUBLIC; + +COMMENT ON FUNCTION _pgactive_generate_node_identifier_private() +IS 'Generate pgactive node identifier and create its getter function'; + +CREATE FUNCTION pgactive_get_node_identifier() +RETURNS numeric +AS 'MODULE_PATHNAME','pgactive_get_node_identifier' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION pgactive_get_node_identifier() FROM PUBLIC; + +COMMENT ON FUNCTION pgactive_get_node_identifier() +IS 'Get pgactive node identifier'; + +CREATE FUNCTION pgactive_fdw_validator( + options text[], + catalog oid +) +RETURNS void +AS 'MODULE_PATHNAME', 'pgactive_fdw_validator' +LANGUAGE C STRICT; + +CREATE FOREIGN DATA WRAPPER pgactive_fdw VALIDATOR pgactive_fdw_validator; + +CREATE FUNCTION pgactive_conninfo_cmp( + conninfo1 text, + conninfo2 text +) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FUNCTION pgactive_update_node_conninfo ( + node_name_to_update text, + node_dsn_to_update text + ) +RETURNS void LANGUAGE plpgsql VOLATILE +SET search_path = pgactive, pg_catalog +AS $body$ +DECLARE + r record; + updated_rows int; -- a variable to store the row count +BEGIN + -- Only one tx can update node connection info + LOCK TABLE pgactive.pgactive_nodes IN EXCLUSIVE MODE; + LOCK TABLE pgactive.pgactive_connections IN EXCLUSIVE MODE; + + SELECT * FROM pgactive.pgactive_nodes WHERE node_name = node_name_to_update + INTO r; + + IF NOT FOUND THEN + RAISE EXCEPTION 'no node with name % found in pgactive.pgactive_nodes', + node_name_to_update; + END IF; + + -- Update node DSNs for all nodes that joined pgactive group using passed-in node. + UPDATE pgactive.pgactive_nodes SET node_init_from_dsn = node_dsn_to_update + WHERE node_init_from_dsn IS NOT NULL AND + pgactive.pgactive_conninfo_cmp(node_init_from_dsn, r.node_dsn) AND + node_status = pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_READY'); + + GET DIAGNOSTICS updated_rows = ROW_COUNT; + IF updated_rows = 0 THEN + RAISE EXCEPTION 'could not find any row in pgactive.pgactive_nodes to update node_init_from_dsn'; + END IF; + + -- Update node DSN for passed-in node. + UPDATE pgactive.pgactive_nodes SET node_dsn = node_dsn_to_update + WHERE node_name = node_name_to_update AND + node_status = pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_READY'); + + GET DIAGNOSTICS updated_rows = ROW_COUNT; + IF updated_rows = 0 THEN + RAISE EXCEPTION 'could not find any row in pgactive.pgactive_nodes to update node_dsn'; + END IF; + + -- Update node DSN for passed-in node in pgactive.pgactive_connections. + UPDATE pgactive.pgactive_connections SET conn_dsn = node_dsn_to_update + WHERE conn_sysid = r.node_sysid AND + conn_timeline = r.node_timeline AND + conn_dboid = r.node_dboid AND + conn_dsn = r.node_dsn; + + GET DIAGNOSTICS updated_rows = ROW_COUNT; + IF updated_rows = 0 THEN + RAISE EXCEPTION 'could not find any row in pgactive.pgactive_connections to update conn_dsn'; + END IF; +END; +$body$; + +REVOKE ALL ON FUNCTION pgactive_update_node_conninfo(text, text) FROM public; + +COMMENT ON FUNCTION pgactive_update_node_conninfo(text, text) IS +'Updates a node connection info across pgactive internal tables.'; + +CREATE FUNCTION get_last_applied_xact_info( + sysid text, + timeline oid, + dboid oid, + OUT last_applied_xact_id oid, + OUT last_applied_xact_committs timestamptz, + OUT last_applied_xact_at timestamptz +) +RETURNS record +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION get_last_applied_xact_info(text, oid, oid) FROM public; + +COMMENT ON FUNCTION get_last_applied_xact_info(text, oid, oid) IS +'Gets last applied transaction info of apply worker for a given node.'; + +CREATE FUNCTION get_replication_lag_info( + OUT slot_name name, + OUT last_sent_xact_id oid, + OUT last_sent_xact_committs timestamptz, + OUT last_sent_xact_at timestamptz, + OUT last_applied_xact_id oid, + OUT last_applied_xact_committs timestamptz, + OUT last_applied_xact_at timestamptz +) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C VOLATILE STRICT; + +REVOKE ALL ON FUNCTION get_replication_lag_info() FROM public; + +COMMENT ON FUNCTION get_replication_lag_info() IS +'Gets replication lag info.'; + +CREATE VIEW pgactive.pgactive_node_slots AS +SELECT n.node_name, + s.slot_name, s.restart_lsn AS slot_restart_lsn, s.confirmed_flush_lsn AS slot_confirmed_lsn, + s.active AS walsender_active, + s.active_pid AS walsender_pid, + r.sent_lsn, r.write_lsn, r.flush_lsn, r.replay_lsn, + l.last_sent_xact_id, + l.last_sent_xact_committs, + l.last_sent_xact_at, + l.last_applied_xact_id, + l.last_applied_xact_committs, + l.last_applied_xact_at +FROM + pg_catalog.pg_replication_slots s + CROSS JOIN LATERAL pgactive.pgactive_parse_slot_name(s.slot_name) ps(remote_sysid, remote_timeline, remote_dboid, local_dboid, replication_name) + INNER JOIN pgactive.pgactive_nodes n ON ((n.node_sysid = ps.remote_sysid) AND (n.node_timeline = ps.remote_timeline) AND (n.node_dboid = ps.remote_dboid)) + INNER JOIN pgactive.get_replication_lag_info() l ON (l.slot_name = s.slot_name) + LEFT JOIN pg_catalog.pg_stat_replication r ON (r.pid = s.active_pid) +WHERE ps.local_dboid = (select oid from pg_database where datname = current_database()) + AND s.plugin = 'pgactive'; + +CREATE FUNCTION get_free_disk_space( + path text, + OUT free_disk_space int8 +) +RETURNS bigint +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION get_free_disk_space(text) FROM public; + +COMMENT ON FUNCTION get_free_disk_space(text) IS +'Gets free disk space in bytes of filesystem to which given path is mounted.'; + +CREATE FUNCTION check_file_system_mount_points( + path1 text, + path2 text +) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION check_file_system_mount_points(text, text) FROM public; + +COMMENT ON FUNCTION check_file_system_mount_points(text, text) IS +'Checks if given paths are on same file system mount points.'; + +CREATE FUNCTION _pgactive_nid_shmem_reset_all_private() +RETURNS void +AS 'MODULE_PATHNAME','pgactive_nid_shmem_reset_all' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION _pgactive_nid_shmem_reset_all_private() FROM public; + +COMMENT ON FUNCTION _pgactive_nid_shmem_reset_all_private() IS +'Resets pgactive node identifier shared memory.'; + +CREATE FUNCTION has_required_privs() +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION has_required_privs() FROM public; + +COMMENT ON FUNCTION has_required_privs() IS +'Checks if current user has required privileges.'; + +-- RESET pgactive.permit_unsafe_ddl_commands; is removed for now +RESET pgactive.skip_ddl_replication; +RESET search_path; + +-- Upgrades from 2.1.0 to 2.1.1 + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION + +SET pgactive.skip_ddl_replication = true; +-- Everything should assume the 'pgactive' prefix +SET LOCAL search_path = pgactive; + +DROP FUNCTION get_last_applied_xact_info(text, oid, oid); + +CREATE FUNCTION pgactive_get_last_applied_xact_info( + sysid text, + timeline oid, + dboid oid, + OUT last_applied_xact_id oid, + OUT last_applied_xact_committs timestamptz, + OUT last_applied_xact_at timestamptz +) +RETURNS record +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +COMMENT ON FUNCTION pgactive_get_last_applied_xact_info(text, oid, oid) IS +'Gets last applied transaction info of apply worker for a given node.'; + +DROP VIEW pgactive.pgactive_node_slots; + +DROP FUNCTION get_replication_lag_info(); + +CREATE FUNCTION pgactive_get_replication_lag_info( + OUT slot_name name, + OUT last_sent_xact_id oid, + OUT last_sent_xact_committs timestamptz, + OUT last_sent_xact_at timestamptz, + OUT last_applied_xact_id oid, + OUT last_applied_xact_committs timestamptz, + OUT last_applied_xact_at timestamptz +) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C VOLATILE STRICT; + +COMMENT ON FUNCTION pgactive_get_replication_lag_info() IS +'Gets replication lag info.'; + +DROP FUNCTION get_free_disk_space(text); +CREATE FUNCTION _pgactive_get_free_disk_space( + path text, + OUT free_disk_space int8 +) +RETURNS bigint +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION _pgactive_get_free_disk_space(text) FROM public; + +COMMENT ON FUNCTION _pgactive_get_free_disk_space(text) IS +'Gets free disk space in bytes of filesystem to which given path is mounted.'; + +DROP FUNCTION check_file_system_mount_points(text, text); + +CREATE FUNCTION _pgactive_check_file_system_mount_points( + path1 text, + path2 text +) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION _pgactive_check_file_system_mount_points(text, text) FROM public; + +COMMENT ON FUNCTION _pgactive_check_file_system_mount_points(text, text) IS +'Checks if given paths are on same file system mount points.'; + +DROP FUNCTION has_required_privs(); + +CREATE FUNCTION _pgactive_has_required_privs() +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION _pgactive_has_required_privs() FROM public; + +COMMENT ON FUNCTION _pgactive_has_required_privs() IS +'Checks if current user has required privileges.'; + +CREATE FUNCTION has_required_privs() +RETURNS boolean +AS 'MODULE_PATHNAME','_pgactive_has_required_privs' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION has_required_privs() FROM public; + +CREATE VIEW pgactive.pgactive_node_slots AS +SELECT n.node_name, + s.slot_name, s.restart_lsn AS slot_restart_lsn, s.confirmed_flush_lsn AS slot_confirmed_lsn, + s.active AS walsender_active, + s.active_pid AS walsender_pid, + r.sent_lsn, r.write_lsn, r.flush_lsn, r.replay_lsn, + l.last_sent_xact_id, + l.last_sent_xact_committs, + l.last_sent_xact_at, + l.last_applied_xact_id, + l.last_applied_xact_committs, + l.last_applied_xact_at +FROM + pg_catalog.pg_replication_slots s + CROSS JOIN LATERAL pgactive.pgactive_parse_slot_name(s.slot_name) ps(remote_sysid, remote_timeline, remote_dboid, local_dboid, replication_name) + INNER JOIN pgactive.pgactive_nodes n ON ((n.node_sysid = ps.remote_sysid) AND (n.node_timeline = ps.remote_timeline) AND (n.node_dboid = ps.remote_dboid)) + INNER JOIN pgactive.pgactive_get_replication_lag_info() l ON (l.slot_name = s.slot_name) + LEFT JOIN pg_catalog.pg_stat_replication r ON (r.pid = s.active_pid) +WHERE ps.local_dboid = (select oid from pg_database where datname = current_database()) + AND s.plugin = 'pgactive'; + +DROP FUNCTION _pgactive_begin_join_private(text, text, text, text, boolean, boolean, boolean); +CREATE FUNCTION _pgactive_begin_join_private ( + caller text, + node_name text, + node_dsn text, + remote_dsn text, + remote_sysid OUT text, + remote_timeline OUT oid, + remote_dboid OUT oid, + bypass_collation_check boolean, + bypass_node_identifier_creation boolean, + bypass_user_tables_check boolean +) +RETURNS record LANGUAGE plpgsql VOLATILE +SET search_path = pgactive, pg_catalog +-- SET pgactive.permit_unsafe_ddl_commands = on is removed for now +SET pgactive.skip_ddl_replication = on +-- SET pgactive.skip_ddl_locking = on is removed for now +AS $body$ +DECLARE + localid RECORD; + localid_from_dsn RECORD; + remote_nodeinfo RECORD; + remote_nodeinfo_r RECORD; + cur_node RECORD; + local_max_node_value integer; + local_skip_ddl_replication_value boolean; + local_db_collation_info_r RECORD; + collation_errmsg text; + collation_hintmsg text; + data_dir text; + temp_dump_dir text; + same_file_system_mount_point boolean; + free_disk_space1 int8; + free_disk_space1_p text; + free_disk_space2 int8; + free_disk_space2_p text; + remote_dbsize_p text; +BEGIN + -- Only one tx can be adding connections + LOCK TABLE pgactive.pgactive_connections IN EXCLUSIVE MODE; + LOCK TABLE pgactive.pgactive_nodes IN EXCLUSIVE MODE; + LOCK TABLE pg_catalog.pg_shseclabel IN EXCLUSIVE MODE; + + -- Generate pgactive node identifier if asked + IF bypass_node_identifier_creation THEN + RAISE WARNING USING + MESSAGE = 'skipping creation of pgactive node identifier for this node', + HINT = 'The ''bypass_node_identifier_creation'' option is only available for pgactive_init_copy tool.'; + ELSE + PERFORM pgactive._pgactive_generate_node_identifier_private(); + END IF; + + SELECT sysid, timeline, dboid INTO localid + FROM pgactive.pgactive_get_local_nodeid(); + + RAISE LOG USING MESSAGE = format('node identity of node being created is (%s,%s,%s)', localid.sysid, localid.timeline, localid.dboid); + + -- If there's already an entry for ourselves in pgactive.pgactive_connections then we + -- know this node is part of an active pgactive group and cannot be joined to + -- another group. + PERFORM 1 FROM pgactive_connections + WHERE conn_sysid = localid.sysid + AND conn_timeline = localid.timeline + AND conn_dboid = localid.dboid; + + IF FOUND THEN + RAISE USING + MESSAGE = 'this node is already a member of a pgactive group', + HINT = 'Connect to the node you wish to add and run '||caller||' from it instead.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + -- Validate that the local connection is usable and matches the node + -- identity of the node we're running on. + -- + -- For pgactive this will NOT check the 'dsn' if 'node_dsn' gets supplied. + -- We don't know if 'dsn' is even valid for loopback connections and can't + -- assume it is. That'll get checked later by pgactive specific code. + -- + -- We'll get a null node name back at this point since we haven't inserted + -- our nodes record (and it wouldn't have committed yet if we had). + -- + SELECT * INTO localid_from_dsn + FROM _pgactive_get_node_info_private(node_dsn); + + IF localid_from_dsn.sysid <> localid.sysid + OR localid_from_dsn.timeline <> localid.timeline + OR localid_from_dsn.dboid <> localid.dboid + THEN + RAISE USING + MESSAGE = 'node identity for local dsn does not match current node', + DETAIL = format($$The dsn '%s' connects to a node with identity (%s,%s,%s) but the local node is (%s,%s,%s)$$, + node_dsn, localid_from_dsn.sysid, localid_from_dsn.timeline, + localid_from_dsn.dboid, localid.sysid, localid.timeline, localid.dboid), + HINT = 'The node_dsn parameter must refer to the node you''re running this function from.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF NOT localid_from_dsn.has_required_privs THEN + RAISE USING + MESSAGE = 'node_dsn does not have required rights', + DETAIL = format($$The dsn '%s' connects successfully but does not have required rights.$$, node_dsn), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF NOT bypass_user_tables_check THEN + PERFORM 1 FROM pg_class r + INNER JOIN pg_namespace n ON r.relnamespace = n.oid + WHERE n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema') + AND relkind = 'r' AND relpersistence = 'p'; + + IF FOUND THEN + RAISE USING + MESSAGE = 'database joining pgactive group has existing user tables', + HINT = 'Ensure no user tables in the database.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + END IF; + + -- Now interrogate the remote node, if specified, and sanity check its + -- connection too. The discovered node identity is returned if found. + -- + -- This will error out if there are issues with the remote node. + IF remote_dsn IS NOT NULL THEN + SELECT * INTO remote_nodeinfo + FROM _pgactive_get_node_info_private(remote_dsn); + + remote_sysid := remote_nodeinfo.sysid; + remote_timeline := remote_nodeinfo.timeline; + remote_dboid := remote_nodeinfo.dboid; + + IF NOT remote_nodeinfo.has_required_privs THEN + RAISE USING + MESSAGE = 'connection to remote node does not have required rights', + DETAIL = format($$The dsn '%s' connects successfully but does not have required rights.$$, remote_dsn), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF remote_nodeinfo.version_num < pgactive_min_remote_version_num() THEN + RAISE USING + MESSAGE = 'remote node''s pgactive version is too old', + DETAIL = format($$The dsn '%s' connects successfully but the remote node version %s is less than the required version %s.$$, + remote_dsn, remote_nodeinfo.version_num, pgactive_min_remote_version_num()), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF remote_nodeinfo.min_remote_version_num > pgactive_version_num() THEN + RAISE USING + MESSAGE = 'remote node''s pgactive version is too new or this node''s version is too old', + DETAIL = format($$The dsn '%s' connects successfully but the remote node version %s requires this node to run at least pgactive %s, not the current %s.$$, + remote_dsn, remote_nodeinfo.version_num, remote_nodeinfo.min_remote_version_num, + pgactive_min_remote_version_num()), + ERRCODE = 'object_not_in_prerequisite_state'; + + END IF; + + IF remote_nodeinfo.node_status IS NULL THEN + RAISE USING + MESSAGE = 'remote node does not appear to be a fully running pgactive node', + DETAIL = format($$The dsn '%s' connects successfully but the target node has no entry in pgactive.pgactive_nodes.$$, remote_dsn), + ERRCODE = 'object_not_in_prerequisite_state'; + ELSIF remote_nodeinfo.node_status IS DISTINCT FROM pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_READY') THEN + RAISE USING + MESSAGE = 'remote node does not appear to be a fully running pgactive node', + DETAIL = format($$The dsn '%s' connects successfully but the target node has pgactive.pgactive_nodes node_status=%s instead of expected 'r'.$$, remote_dsn, remote_nodeinfo.node_status), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT setting::integer INTO local_max_node_value FROM pg_settings + WHERE name = 'pgactive.max_nodes'; + + IF local_max_node_value <> remote_nodeinfo.max_nodes THEN + RAISE USING + MESSAGE = 'joining node and pgactive group have different values for pgactive.max_nodes parameter', + DETAIL = format('pgactive.max_nodes value for joining node is ''%s'' and remote node is ''%s''.', + local_max_node_value, remote_nodeinfo.max_nodes), + HINT = 'The parameter must be set to the same value on all pgactive members.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT setting FROM pg_settings + WHERE name = 'data_directory' INTO data_dir; + + SELECT pgactive._pgactive_get_free_disk_space(data_dir) INTO free_disk_space1; + SELECT pg_size_pretty(free_disk_space1) INTO free_disk_space1_p; + SELECT pg_size_pretty(remote_nodeinfo.dbsize) INTO remote_dbsize_p; + + -- We estimate that postgres needs 20% more disk space as temporary + -- workspace while restoring database for running queries or building + -- indexes. Note that it is just an estimation, the actual disk space + -- needed depends on various factors. Hence we emit a warning to inform + -- early, not an error. + IF free_disk_space1 < (1.2 * remote_nodeinfo.dbsize) THEN + RAISE WARNING USING + MESSAGE = 'node might fail to join pgactive group as disk space is likely to be insufficient', + DETAIL = format('joining node data directory file system mount point has %s free disk space and remote database is %s in size.', + free_disk_space1_p, remote_dbsize_p), + HINT = 'Ensure enough free space on joining node file system.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT setting FROM pg_settings + WHERE name = 'pgactive.temp_dump_directory' INTO temp_dump_dir; + + SELECT pgactive._pgactive_get_free_disk_space(temp_dump_dir) INTO free_disk_space2; + SELECT pg_size_pretty(free_disk_space2) INTO free_disk_space2_p; + + -- We estimate that pg_dump needs at least 50% of database size + -- excluding total size of indexes on the database. Note that it is + -- just an estimation, the actual disk space needed depends on various + -- factors. Hence we emit a warning to inform early, not an error. + IF free_disk_space2 < ((remote_nodeinfo.dbsize - remote_nodeinfo.indexessize)/2) THEN + RAISE WARNING USING + MESSAGE = 'node might fail to join pgactive group as disk space required to store temporary dump is likely to be insufficient', + DETAIL = format('pgactive.temp_dump_directory file system mount point has %s free disk space and remote database is %s in size.', + free_disk_space2_p, remote_dbsize_p), + HINT = 'Ensure enough free space on pgactive.temp_dump_directory file system.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT pgactive._pgactive_check_file_system_mount_points(data_dir, temp_dump_dir) + INTO same_file_system_mount_point; + + IF same_file_system_mount_point THEN + IF free_disk_space1 < + ((1.2 * remote_nodeinfo.dbsize) + ((remote_nodeinfo.dbsize - remote_nodeinfo.indexessize)/2)) THEN + RAISE WARNING USING + MESSAGE = 'node might fail to join pgactive group as disk space required to store both remote database and temporary dump is likely to be insufficient', + HINT = 'Ensure enough free space on joining node file system.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + END IF; + + -- using pg_file_settings here as pgactive.skip_ddl_replication is SET to on when entering + -- the function. + SELECT COALESCE((SELECT setting::boolean + FROM pg_file_settings + WHERE name = 'pgactive.skip_ddl_replication' ORDER BY seqno DESC LIMIT 1), + true) INTO local_skip_ddl_replication_value; + + IF local_skip_ddl_replication_value <> remote_nodeinfo.skip_ddl_replication THEN + RAISE USING + MESSAGE = 'joining node and pgactive group have different values for pgactive.skip_ddl_replication parameter', + DETAIL = format('pgactive.skip_ddl_replication value for joining node is ''%s'' and remote node is ''%s''.', + local_skip_ddl_replication_value, remote_nodeinfo.skip_ddl_replication), + HINT = 'The parameter must be set to the same value on all pgactive members.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF local_max_node_value = remote_nodeinfo.cur_nodes THEN + RAISE USING + MESSAGE = 'cannot allow more than pgactive.max_nodes number of nodes in a pgactive group', + HINT = 'Increase pgactive.max_nodes parameter value on joining node as well as on all other pgactive members.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT datcollate, datctype FROM pg_database + WHERE datname = current_database() INTO local_db_collation_info_r; + + IF local_db_collation_info_r.datcollate <> remote_nodeinfo.datcollate OR + local_db_collation_info_r.datctype <> remote_nodeinfo.datctype THEN + + collation_errmsg := 'joining node and remote node have different database collation settings'; + collation_hintmsg := 'Use the same database collation settings for both nodes.'; + + IF bypass_collation_check THEN + RAISE WARNING USING + MESSAGE = collation_errmsg, + HINT = collation_hintmsg, + ERRCODE = 'object_not_in_prerequisite_state'; + ELSE + RAISE EXCEPTION USING + MESSAGE = collation_errmsg, + HINT = collation_hintmsg, + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + END IF; + END IF; + + -- Create local node record so the apply worker knows to start initializing + -- this node with pgactive_init_replica when it's started. + -- + -- pgactive_init_copy might've created a node entry in catchup mode already, in + -- which case we can skip this. + SELECT * FROM pgactive_nodes + WHERE node_sysid = localid.sysid + AND node_timeline = localid.timeline + AND node_dboid = localid.dboid + INTO cur_node; + + IF NOT FOUND THEN + INSERT INTO pgactive_nodes ( + node_name, + node_sysid, node_timeline, node_dboid, + node_status, node_dsn, node_init_from_dsn + ) VALUES ( + node_name, + localid.sysid, localid.timeline, localid.dboid, + pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_BEGINNING_INIT'), + node_dsn, remote_dsn + ); + ELSIF pgactive.pgactive_node_status_from_char(cur_node.node_status) = 'pgactive_NODE_STATUS_CATCHUP' THEN + RAISE DEBUG 'starting node join in pgactive_NODE_STATUS_CATCHUP'; + ELSE + RAISE USING + MESSAGE = 'a pgactive_nodes entry for this node already exists', + DETAIL = format('pgactive.pgactive_nodes entry for (%s,%s,%s) named ''%s'' with status %s exists.', + cur_node.node_sysid, cur_node.node_timeline, cur_node.node_dboid, + cur_node.node_name, pgactive.pgactive_node_status_from_char(cur_node.node_status)), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + PERFORM pgactive._pgactive_update_seclabel_private(); +END; +$body$; + +REVOKE ALL ON FUNCTION _pgactive_begin_join_private(text, text, text, text, boolean, boolean, boolean) FROM public; +REVOKE ALL ON FUNCTION pgactive_variant() FROM public; +REVOKE ALL ON FUNCTION pgactive_get_stats() FROM PUBLIC; +REVOKE ALL ON FUNCTION pgactive_truncate_trigger_add() FROM public; +REVOKE ALL ON FUNCTION pgactive_internal_create_truncate_trigger(regclass) FROM public; +REVOKE ALL ON FUNCTION pgactive_queue_truncate() FROM public; +REVOKE ALL ON FUNCTION pgactive_apply_pause() FROM public; +REVOKE ALL ON FUNCTION pgactive_apply_resume() FROM public; +REVOKE ALL ON FUNCTION pgactive_get_local_nodeid() FROM public; +REVOKE ALL ON FUNCTION pgactive_version_num() FROM public; +REVOKE ALL ON FUNCTION pgactive_min_remote_version_num() FROM public; +REVOKE ALL ON FUNCTION _pgactive_join_node_private(text, oid, oid, text, integer, text[]) FROM public; +REVOKE ALL ON FUNCTION _pgactive_update_seclabel_private() FROM public; +REVOKE ALL ON FUNCTION pgactive_join_group(text, text, text, integer, text[], boolean, boolean, boolean) FROM public; +REVOKE ALL ON FUNCTION pgactive_create_group(text, text, integer, text[]) FROM public; +REVOKE ALL ON FUNCTION pgactive_detach_nodes(text[]) FROM public; +REVOKE ALL ON FUNCTION pgactive_wait_for_node_ready(integer, integer) FROM public; +REVOKE ALL ON FUNCTION pgactive_parse_slot_name(name) FROM public; +REVOKE ALL ON FUNCTION pgactive_format_slot_name(text, oid, oid, oid, name) FROM public; +REVOKE ALL ON FUNCTION pgactive_set_node_read_only(text, boolean) FROM public; +REVOKE ALL ON FUNCTION pgactive_terminate_workers(text, oid, oid, text) FROM public; +REVOKE ALL ON FUNCTION pgactive_skip_changes(text, oid, oid, pg_lsn) FROM public; +REVOKE ALL ON FUNCTION pgactive_get_connection_replication_sets(text) FROM public; +REVOKE ALL ON FUNCTION pgactive_set_connection_replication_sets(text[], text) FROM public; +REVOKE ALL ON FUNCTION _pgactive_pause_worker_management_private(boolean) FROM public; +REVOKE ALL ON FUNCTION pgactive_parse_replident_name(text) FROM public; +REVOKE ALL ON FUNCTION pgactive_format_replident_name(text, oid, oid, oid, name) FROM public; +REVOKE ALL ON FUNCTION pgactive_node_status_from_char("char") FROM public; +REVOKE ALL ON FUNCTION pgactive_node_status_to_char(text) FROM public; +REVOKE ALL ON FUNCTION _pgactive_snowflake_id_nextval_private(regclass, bigint) FROM public; +REVOKE ALL ON FUNCTION pgactive_assign_seq_ids_post_upgrade() FROM public; +REVOKE ALL ON FUNCTION pgactive_wait_for_slots_confirmed_flush_lsn(name,pg_lsn) FROM public; +REVOKE ALL ON FUNCTION pgactive_handle_rejoin() FROM public; +REVOKE ALL ON FUNCTION pgactive_get_node_identifier() FROM PUBLIC; +REVOKE ALL ON FUNCTION pgactive_fdw_validator(text[], oid) FROM PUBLIC; +REVOKE ALL ON FUNCTION pgactive_conninfo_cmp(text, text) FROM PUBLIC; + +RESET pgactive.skip_ddl_replication; +RESET search_path; + +-- Upgrades from 2.1.1 to 2.1.2 + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION + +-- Upgrades from 2.1.2 to 2.1.3 + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION + +SET pgactive.skip_ddl_replication = true; +-- Everything should assume the 'pgactive' prefix +SET LOCAL search_path = pgactive; + +DROP FUNCTION IF EXISTS has_required_privs(); + +CREATE FUNCTION has_required_privs() +RETURNS boolean +AS 'MODULE_PATHNAME','_pgactive_has_required_privs' +LANGUAGE C STRICT; + +REVOKE ALL ON FUNCTION has_required_privs() FROM public; + +COMMENT ON FUNCTION has_required_privs() IS +'Checks if current user has required privileges.'; + +CREATE OR REPLACE FUNCTION pgactive_get_connection_replication_sets( + sysid text, timeline oid, dboid oid, + origin_sysid text default '0', + origin_timeline oid default 0, + origin_dboid oid default 0 +) +RETURNS text[] +LANGUAGE plpgsql +AS $$ +DECLARE + found_sets text[]; +BEGIN + SELECT conn_replication_sets + FROM pgactive.pgactive_connections + WHERE conn_sysid = sysid + AND conn_timeline = timeline + AND conn_dboid = dboid + INTO found_sets; + + IF NOT FOUND THEN + RAISE EXCEPTION 'No pgactive.pgactive_connections entry found for node (%)', + sysid; + END IF; + + RETURN found_sets; +END; +$$; + +CREATE OR REPLACE FUNCTION pgactive_get_connection_replication_sets( + new_replication_sets text[], + sysid text, timeline oid, dboid oid, + origin_sysid text default '0', + origin_timeline oid default 0, + origin_dboid oid default 0 +) +RETURNS void +LANGUAGE plpgsql +AS $$ +BEGIN + UPDATE pgactive.pgactive_connections + SET conn_replication_sets = new_replication_sets + WHERE conn_sysid = sysid + AND conn_timeline = timeline + AND conn_dboid = dboid; + + IF NOT FOUND THEN + RAISE EXCEPTION 'No pgactive.pgactive_connections entry found for node (%)', + sysid; + END IF; + + -- The other nodes will notice the change when they replay the new tuple; we + -- only have to explicitly notify the local node. + PERFORM pgactive.pgactive_connections_changed(); +END; +$$; + +-- RESET pgactive.permit_unsafe_ddl_commands; is removed for now +RESET pgactive.skip_ddl_replication; +RESET search_path; + +-- Upgrades from 2.1.3 to 2.1.4 + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION + +SET pgactive.skip_ddl_replication = true; +-- Everything should assume the 'pgactive' prefix +SET LOCAL search_path = pgactive; + +-- Fix quoting for format() arguments by directly using regclass with %s +-- instead of %I +DROP FUNCTION pgactive_set_table_replication_sets(p_relation regclass, p_sets text[]); + +CREATE FUNCTION pgactive_set_table_replication_sets(p_relation regclass, exclude_table boolean) + RETURNS void + VOLATILE + LANGUAGE 'plpgsql' +-- remove pgactive_permit_unsafe_commands and do not replace +-- by pgactive_skip_ddl_replication for now + SET search_path = '' + AS $$ +DECLARE + v_label json; + setting_value text; + p_sets text[]; +BEGIN + -- emulate STRICT for p_relation parameter + IF p_relation IS NULL THEN + RETURN; + END IF; + + -- Prohibit if not exactly one node (as we may need to update pgactive_connections) + IF ( + SELECT count(1) + FROM pgactive.pgactive_nodes + WHERE node_status NOT IN (pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_KILLED')) + ) != 1 + THEN + RAISE USING + MESSAGE = 'pgactive can''t exclude or include table from replication', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = 'replication set exclude or include can only be performed after pgactive_create_group() and before pgactive_join_group()'; + END IF; + + IF (exclude_table) THEN + -- Prohibit exclude if include has been done + IF ( + SELECT count(1) + FROM pgactive.pgactive_connections + WHERE 'include_rs' = ANY(conn_replication_sets) + ) > 0 + THEN + RAISE USING + MESSAGE = 'pgactive can''t exclude table from replication', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = 'pgactive doesn''t allow exclude set setup when an include set has already been used'; + END IF; + p_sets := '{exclude_rs}'; + ELSE + -- Prohibit include if exclude has been done + IF ( + SELECT count(1) + FROM pg_seclabel + WHERE label like '%exclude_rs%' + ) > 0 + THEN + RAISE USING + MESSAGE = 'pgactive can''t include table from replication', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = 'pgactive doesn''t allow an include set setup when exclude set has already been used'; + END IF; + p_sets := '{include_rs}'; + END IF; + + -- query current label + SELECT label::json INTO v_label + FROM pg_catalog.pg_seclabel + WHERE provider = 'pgactive' + AND classoid = 'pg_class'::regclass + AND objoid = p_relation; + + -- replace old 'sets' parameter with new value + SELECT json_object_agg(key, value) INTO v_label + FROM ( + SELECT key, value + FROM json_each(v_label) + WHERE key <> 'sets' + UNION ALL + SELECT + 'sets', to_json(p_sets) + ) d; + + -- and now set the appropriate label + -- pgactive_replicate_ddl_command would fail if skip_ddl_replication is true + + SELECT setting INTO setting_value + FROM pg_settings + WHERE name = 'pgactive.skip_ddl_replication'; + + IF setting_value = 'on' or setting_value = 'true' THEN + IF v_label IS NOT NULL THEN + EXECUTE 'SECURITY LABEL FOR pgactive ON TABLE ' || p_relation || ' IS ' || pg_catalog.quote_literal(v_label); + ELSE + EXECUTE 'SECURITY LABEL FOR pgactive ON TABLE ' || p_relation || ' IS NULL'; + END IF; + ELSE + PERFORM pgactive.pgactive_replicate_ddl_command(format('SECURITY LABEL FOR pgactive ON TABLE %s IS %L', p_relation, v_label)); + END IF; + + IF (exclude_table IS FALSE) THEN + UPDATE pgactive.pgactive_connections SET conn_replication_sets = p_sets; + PERFORM pgactive.pgactive_connections_changed(); + END IF; +END; +$$; + + +CREATE OR REPLACE FUNCTION pgactive_exclude_table_replication_set(p_relation regclass) +RETURNS void +VOLATILE +LANGUAGE 'plpgsql' +-- remove pgactive_permit_unsafe_commands and do not replace +-- by pgactive_skip_ddl_replication for now +SET search_path = '' +AS $$ +BEGIN + PERFORM pgactive.pgactive_set_table_replication_sets(p_relation, true); +END; +$$; + + +CREATE OR REPLACE FUNCTION pgactive_include_table_replication_set(p_relation regclass) +RETURNS void +VOLATILE +LANGUAGE 'plpgsql' +-- remove pgactive_permit_unsafe_commands and do not replace +-- by pgactive_skip_ddl_replication for now +SET search_path = '' +AS $$ +BEGIN + PERFORM pgactive.pgactive_set_table_replication_sets(p_relation, false); +END; +$$; + +DROP FUNCTION pgactive_get_connection_replication_sets( + text[], + text, oid, oid, + text, + oid, + oid +); + +CREATE OR REPLACE FUNCTION pgactive_set_connection_replication_sets( + new_replication_sets text[], + sysid text, timeline oid, dboid oid, + origin_sysid text default '0', + origin_timeline oid default 0, + origin_dboid oid default 0 +) +RETURNS void +LANGUAGE plpgsql +AS $$ +BEGIN + -- Prohibit if not exactly one node (as we may need to update pgactive_connections) + IF ( + SELECT count(1) + FROM pgactive.pgactive_nodes + WHERE node_status NOT IN (pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_KILLED')) + ) != 1 + THEN + RAISE USING + MESSAGE = 'pgactive can''t set connection replication sets', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = 'set connection replication sets can only be performed after pgactive_create_group() and before pgactive_join_group()'; + END IF; + + -- Prohibit setting conn_replication_sets to non default + IF (new_replication_sets != '{default}') + THEN + RAISE USING + MESSAGE = 'pgactive can''t set connection replication sets to non default value', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = 'pgactive doesn''t allow to set connection replication sets but {default}'; + END IF; + + UPDATE pgactive.pgactive_connections + SET conn_replication_sets = new_replication_sets + WHERE conn_sysid = sysid + AND conn_timeline = timeline + AND conn_dboid = dboid; + + IF NOT FOUND THEN + IF origin_timeline <> '0' OR origin_timeline <> 0 OR origin_dboid <> 0 THEN + RAISE EXCEPTION 'No pgactive.pgactive_connections entry found from origin (%,%,%) to (%,%,%)', + origin_sysid, origin_timeline, origin_dboid, sysid, timeline, dboid; + ELSE + RAISE EXCEPTION 'No pgactive.pgactive_connections entry found for (%,%,%) with default origin (0,0,0)', + sysid, timeline, dboid; + END IF; + END IF; + + -- The other nodes will notice the change when they replay the new tuple; we + -- only have to explicitly notify the local node. + PERFORM pgactive.pgactive_connections_changed(); +END; +$$; + +DROP FUNCTION pgactive_get_workers_info(); +CREATE FUNCTION pgactive_get_workers_info ( + OUT sysid text, + OUT timeline oid, + OUT dboid oid, + OUT worker_type text, + OUT pid int4, + OUT unregistered boolean, + OUT last_error text, + OUT last_error_time timestamptz +) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C VOLATILE STRICT; + +DROP FUNCTION pgactive_terminate_workers(text, oid, oid, text); +CREATE OR REPLACE FUNCTION pgactive_terminate_workers(text, oid, oid, text) +RETURNS boolean +LANGUAGE SQL +AS $$ +SELECT pg_catalog.pg_terminate_backend(pid) FROM pgactive.pgactive_get_workers_info() +-- For per-db worker, we don't expect sysid and timeline, but rely on dboid. + WHERE unregistered = false AND + CASE WHEN worker_type = 'per-db' THEN (dboid, worker_type) = ($3, $4) + ELSE (sysid, timeline, dboid, worker_type) = ($1, $2, $3, $4) END; +$$; + +REVOKE ALL ON FUNCTION pgactive_set_connection_replication_sets(text[], text, oid, oid, text, oid, oid) FROM public; +REVOKE ALL ON FUNCTION pgactive_get_workers_info() FROM public; +REVOKE ALL ON FUNCTION pgactive_terminate_workers(text, oid, oid, text) FROM public; + +CREATE OR REPLACE FUNCTION pgactive_set_connection_replication_sets ( + replication_sets text[], + target_node_name text +) +RETURNS void +LANGUAGE plpgsql +AS $$ +DECLARE + sysid text; + timeline oid; + dboid oid; +BEGIN + -- Prohibit if not exactly one node (as we may need to update pgactive_connections) + IF ( + SELECT count(1) + FROM pgactive.pgactive_nodes + WHERE node_status NOT IN (pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_KILLED')) + ) != 1 + THEN + RAISE USING + MESSAGE = 'pgactive can''t set connection replication sets', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = 'set connection replication sets can only be performed after pgactive_create_group() and before pgactive_join_group()'; + END IF; + + -- Prohibit setting conn_replication_sets to non default + IF (replication_sets != '{default}') + THEN + RAISE USING + MESSAGE = 'pgactive can''t set connection replication sets to non default value', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = 'pgactive doesn''t allow to set connection replication sets but {default}'; + END IF; + + SELECT node_sysid, node_timeline, node_dboid + FROM pgactive.pgactive_nodes + WHERE node_name = target_node_name + INTO sysid, timeline, dboid; + + IF NOT FOUND THEN + RAISE EXCEPTION 'no node with name % found in pgactive.pgactive_nodes',target_node_name; + END IF; + + IF ( + SELECT count(1) + FROM pgactive.pgactive_connections + WHERE conn_sysid = sysid + AND conn_timeline = timeline + AND conn_dboid = dboid + ) > 1 + THEN + RAISE WARNING 'there are node-specific override entries for node % in pgactive.pgactive_connections. Only the default connection''s replication sets will be changed. Use the 6-argument form of this function to change others.',node_name; + END IF; + + PERFORM pgactive.pgactive_set_connection_replication_sets(replication_sets, sysid, timeline, dboid); +END; +$$; + +REVOKE ALL ON FUNCTION pgactive_set_connection_replication_sets(text[], text) FROM public; + +CREATE FUNCTION _pgactive_node_name_present_private ( + node_name text, + remote_dsn text) +RETURNS integer +AS 'MODULE_PATHNAME','pgactive_node_name_present' +LANGUAGE C; + +REVOKE ALL ON FUNCTION _pgactive_node_name_present_private(text, text) FROM PUBLIC; + +-- +-- The public interface for node join/addition, to be run to join a currently +-- unconnected node with a blank database to a pgactive group. +-- +DROP FUNCTION pgactive_join_group(text, text, text, integer, text[], boolean, boolean, boolean); + +CREATE FUNCTION pgactive.pgactive_join_group ( + node_name text, + node_dsn text, + join_using_dsn text, + apply_delay integer DEFAULT NULL, + replication_sets text[] DEFAULT ARRAY['default'], + bypass_collation_check boolean DEFAULT false, + bypass_node_identifier_creation boolean DEFAULT false, + bypass_user_tables_check boolean DEFAULT false, + data_only_node_init boolean DEFAULT false + ) +RETURNS void LANGUAGE plpgsql VOLATILE +SET search_path = pgactive, pg_catalog +-- SET pgactive.permit_unsafe_ddl_commands = on is removed for now +SET pgactive.skip_ddl_replication = on +-- SET pgactive.skip_ddl_locking = on is removed for now +AS $body$ +DECLARE + localid record; + connectback_nodeinfo record; + remoteinfo record; + contains_include_rs boolean; + current_dboid oid; +BEGIN + + contains_include_rs = false; + -- Prohibit enabling pgactive where pglogical is installed + IF ( + SELECT count(1) + FROM pg_extension + WHERE extname = 'pglogical' + ) > 0 + THEN + RAISE USING + MESSAGE = 'pgactive can''t be enabled because an external logical replication extension is installed', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = 'pgactive doesn''t allow a node to pull in changes from more than one logical replication sources'; + END IF; + + -- Prohibit enabling pgactive where a subscription exists + IF ( + SELECT count(1) + FROM pg_subscription + WHERE subdbid = (SELECT oid + FROM pg_database + WHERE datname = current_database() + ) + ) > 0 + THEN + RAISE USING + MESSAGE = 'pgactive can''t be enabled because a logical replication subscription is created', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = 'pgactive doesn''t allow a node to pull in changes from more than one logical replication sources'; + END IF; + + IF node_dsn IS NULL THEN + RAISE USING + MESSAGE = 'node_dsn can not be null', + ERRCODE = 'invalid_parameter_value'; + END IF; + + -- Prohibit enabling pgactive when there is an existing per-db worker. + SELECT oid FROM pg_database + WHERE datname = current_database() INTO current_dboid; + IF ( + SELECT count(1) + FROM pgactive.pgactive_get_workers_info() + WHERE worker_type = 'per-db' AND dboid = current_dboid + ) > 0 + THEN + RAISE USING + MESSAGE = 'pgactive can''t be enabled because there is an existing per-db worker for the current database', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + IF join_using_dsn IS NOT NULL and NOT bypass_node_identifier_creation THEN + IF ( + SELECT * + FROM pgactive._pgactive_node_name_present_private(node_name, join_using_dsn) + ) > 0 THEN + RAISE USING + MESSAGE = 'node_name already present on remote', + DETAIL = format($$Node name '%s' is already present on remote with node_status != 'k'.$$, node_name), + HINT = 'Either detach the node on remote or use a new node name.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + END IF; + PERFORM pgactive._pgactive_begin_join_private( + caller := '', + node_name := node_name, + node_dsn := node_dsn, + remote_dsn := join_using_dsn, + bypass_collation_check := bypass_collation_check, + bypass_node_identifier_creation := bypass_node_identifier_creation, + bypass_user_tables_check := bypass_user_tables_check, + data_only_node_init := data_only_node_init); + + SELECT sysid, timeline, dboid INTO localid + FROM pgactive.pgactive_get_local_nodeid(); + + -- Request additional connection tests to determine that the remote is + -- reachable for replication and non-replication mode and that the remote + -- can connect back to us via 'dsn' on non-replication and replication + -- modes. + -- + -- This cannot be checked for the first node since there's no peer to ask + -- for help. + IF join_using_dsn IS NOT NULL THEN + + SELECT * INTO connectback_nodeinfo + FROM pgactive._pgactive_get_node_info_private(node_dsn, join_using_dsn); + + -- The connectback must actually match our local node identity and must + -- provide a connection that has required rights. + IF NOT connectback_nodeinfo.has_required_privs THEN + RAISE USING + MESSAGE = 'node_dsn does not have required rights when connecting via remote node', + DETAIL = format($$The dsn '%s' connects successfully but does not have required rights.$$, dsn), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF (connectback_nodeinfo.sysid, connectback_nodeinfo.timeline, connectback_nodeinfo.dboid) + IS DISTINCT FROM + (localid.sysid, localid.timeline, localid.dboid) + AND + (connectback_nodeinfo.sysid, connectback_nodeinfo.timeline, connectback_nodeinfo.dboid) + IS DISTINCT FROM + (NULL, NULL, NULL) -- Returned by old versions' dummy functions + THEN + RAISE USING + MESSAGE = 'node identity for node_dsn does not match current node when connecting back via remote', + DETAIL = format($$The dsn '%s' connects to a node with identity (%s,%s,%s) but the local node is (%s,%s,%s).$$, + node_dsn, connectback_nodeinfo.sysid, connectback_nodeinfo.timeline, + connectback_nodeinfo.dboid, localid.sysid, localid.timeline, localid.dboid), + HINT = 'The ''node_dsn'' parameter must refer to the node you''re running this function from, from the perspective of the node pointed to by join_using_dsn.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT * INTO remoteinfo FROM + _pgactive_get_node_info_private(join_using_dsn); + + IF (remoteinfo.nb_include_rs > 0) THEN + contains_include_rs = true; + END IF; + + END IF; + + -- Null/empty checks are skipped, the underlying constraints on the table + -- will catch that for us. + INSERT INTO pgactive.pgactive_connections ( + conn_sysid, conn_timeline, conn_dboid, + conn_dsn, conn_apply_delay, conn_replication_sets + ) VALUES ( + localid.sysid, localid.timeline, localid.dboid, + node_dsn, apply_delay, replication_sets + ); + + IF (contains_include_rs) + THEN + UPDATE pgactive.pgactive_connections SET conn_replication_sets = '{include_rs}'; + END IF; + + -- Now ensure the per-db worker is started if it's not already running. + -- This won't actually take effect until commit time, it just adds a commit + -- hook to start the worker when we commit. + PERFORM pgactive.pgactive_connections_changed(); +END; +$body$; + +COMMENT ON FUNCTION pgactive.pgactive_join_group(text, text, text, integer, text[], boolean, boolean, boolean, boolean) IS +'Join an existing pgactive group by connecting to a member node and copying its contents'; + +REVOKE ALL ON FUNCTION pgactive.pgactive_join_group(text, text, text, integer, text[], boolean, boolean, boolean, boolean) FROM public; + +DROP FUNCTION _pgactive_get_node_info_private (text, text); + +CREATE FUNCTION _pgactive_get_node_info_private ( + local_dsn text, + remote_dsn text DEFAULT NULL, + sysid OUT text, + timeline OUT oid, + dboid OUT oid, + variant OUT text, + version OUT text, + version_num OUT integer, + min_remote_version_num OUT integer, + has_required_privs OUT boolean, + node_status OUT "char", + node_name OUT text, + dbname OUT text, + dbsize OUT int8, + indexessize OUT int8, + max_nodes OUT integer, + skip_ddl_replication OUT boolean, + nb_include_rs OUT integer, + cur_nodes OUT integer, + datcollate OUT text, + datctype OUT text) +RETURNS record +AS 'MODULE_PATHNAME','pgactive_get_node_info' +LANGUAGE C; + +REVOKE ALL ON FUNCTION _pgactive_get_node_info_private(text, text) FROM public; + +COMMENT ON FUNCTION _pgactive_get_node_info_private(text, text) IS +'Verify both replication and non-replication connections to the given dsn and get node info; when specified remote_dsn ask remote node to connect back to local node'; + +DROP FUNCTION _pgactive_begin_join_private(text, text, text, text, boolean, boolean, boolean); +CREATE FUNCTION _pgactive_begin_join_private ( + caller text, + node_name text, + node_dsn text, + remote_dsn text, + remote_sysid OUT text, + remote_timeline OUT oid, + remote_dboid OUT oid, + bypass_collation_check boolean, + bypass_node_identifier_creation boolean, + bypass_user_tables_check boolean, + data_only_node_init boolean +) +RETURNS record LANGUAGE plpgsql VOLATILE +SET search_path = pgactive, pg_catalog +-- SET pgactive.permit_unsafe_ddl_commands = on is removed for now +SET pgactive.skip_ddl_replication = on +-- SET pgactive.skip_ddl_locking = on is removed for now +AS $body$ +DECLARE + localid RECORD; + localid_from_dsn RECORD; + remote_nodeinfo RECORD; + remote_nodeinfo_r RECORD; + cur_node RECORD; + local_max_node_value integer; + local_skip_ddl_replication_value boolean; + local_db_collation_info_r RECORD; + collation_errmsg text; + collation_hintmsg text; + data_dir text; + temp_dump_dir text; + same_file_system_mount_point boolean; + free_disk_space1 int8; + free_disk_space1_p text; + free_disk_space2 int8; + free_disk_space2_p text; + remote_dbsize_p text; + current_dboid oid; +BEGIN + -- Only one tx can be adding connections + LOCK TABLE pgactive.pgactive_connections IN EXCLUSIVE MODE; + LOCK TABLE pgactive.pgactive_nodes IN EXCLUSIVE MODE; + LOCK TABLE pg_catalog.pg_shseclabel IN EXCLUSIVE MODE; + + -- Generate pgactive node identifier if asked + IF bypass_node_identifier_creation THEN + RAISE WARNING USING + MESSAGE = 'skipping creation of pgactive node identifier for this node', + HINT = 'The ''bypass_node_identifier_creation'' option is only available for pgactive_init_copy tool.'; + ELSE + PERFORM pgactive._pgactive_generate_node_identifier_private(); + END IF; + + SELECT sysid, timeline, dboid INTO localid + FROM pgactive.pgactive_get_local_nodeid(); + + RAISE LOG USING MESSAGE = format('node identity of node being created is (%s,%s,%s)', localid.sysid, localid.timeline, localid.dboid); + + -- If there's already an entry for ourselves in pgactive.pgactive_connections then we + -- know this node is part of an active pgactive group and cannot be joined to + -- another group. + PERFORM 1 FROM pgactive_connections + WHERE conn_sysid = localid.sysid + AND conn_timeline = localid.timeline + AND conn_dboid = localid.dboid; + + IF FOUND THEN + RAISE USING + MESSAGE = 'this node is already a member of a pgactive group', + HINT = 'Connect to the node you wish to add and run '||caller||' from it instead.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + -- Validate that the local connection is usable and matches the node + -- identity of the node we're running on. + -- + -- For pgactive this will NOT check the 'dsn' if 'node_dsn' gets supplied. + -- We don't know if 'dsn' is even valid for loopback connections and can't + -- assume it is. That'll get checked later by pgactive specific code. + -- + -- We'll get a null node name back at this point since we haven't inserted + -- our nodes record (and it wouldn't have committed yet if we had). + -- + SELECT * INTO localid_from_dsn + FROM _pgactive_get_node_info_private(node_dsn); + + IF localid_from_dsn.sysid <> localid.sysid + OR localid_from_dsn.timeline <> localid.timeline + OR localid_from_dsn.dboid <> localid.dboid + THEN + RAISE USING + MESSAGE = 'node identity for local dsn does not match current node', + DETAIL = format($$The dsn '%s' connects to a node with identity (%s,%s,%s) but the local node is (%s,%s,%s)$$, + node_dsn, localid_from_dsn.sysid, localid_from_dsn.timeline, + localid_from_dsn.dboid, localid.sysid, localid.timeline, localid.dboid), + HINT = 'The node_dsn parameter must refer to the node you''re running this function from.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF NOT localid_from_dsn.has_required_privs THEN + RAISE USING + MESSAGE = 'node_dsn does not have required rights', + DETAIL = format($$The dsn '%s' connects successfully but does not have required rights.$$, node_dsn), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF data_only_node_init THEN + bypass_user_tables_check := true; + END IF; + + IF NOT bypass_user_tables_check THEN + PERFORM 1 FROM pg_class r + INNER JOIN pg_namespace n ON r.relnamespace = n.oid + WHERE n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema') + AND relkind = 'r' AND relpersistence = 'p'; + + IF FOUND THEN + RAISE USING + MESSAGE = 'database joining pgactive group has existing user tables', + HINT = 'Ensure no user tables in the database.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + END IF; + + -- Now interrogate the remote node, if specified, and sanity check its + -- connection too. The discovered node identity is returned if found. + -- + -- This will error out if there are issues with the remote node. + IF remote_dsn IS NOT NULL THEN + SELECT * INTO remote_nodeinfo + FROM _pgactive_get_node_info_private(remote_dsn); + + remote_sysid := remote_nodeinfo.sysid; + remote_timeline := remote_nodeinfo.timeline; + remote_dboid := remote_nodeinfo.dboid; + + IF NOT remote_nodeinfo.has_required_privs THEN + RAISE USING + MESSAGE = 'connection to remote node does not have required rights', + DETAIL = format($$The dsn '%s' connects successfully but does not have required rights.$$, remote_dsn), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF remote_nodeinfo.version_num < pgactive_min_remote_version_num() THEN + RAISE USING + MESSAGE = 'remote node''s pgactive version is too old', + DETAIL = format($$The dsn '%s' connects successfully but the remote node version %s is less than the required version %s.$$, + remote_dsn, remote_nodeinfo.version_num, pgactive_min_remote_version_num()), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF remote_nodeinfo.min_remote_version_num > pgactive_version_num() THEN + RAISE USING + MESSAGE = 'remote node''s pgactive version is too new or this node''s version is too old', + DETAIL = format($$The dsn '%s' connects successfully but the remote node version %s requires this node to run at least pgactive %s, not the current %s.$$, + remote_dsn, remote_nodeinfo.version_num, remote_nodeinfo.min_remote_version_num, + pgactive_min_remote_version_num()), + ERRCODE = 'object_not_in_prerequisite_state'; + + END IF; + + IF remote_nodeinfo.node_status IS NULL THEN + RAISE USING + MESSAGE = 'remote node does not appear to be a fully running pgactive node', + DETAIL = format($$The dsn '%s' connects successfully but the target node has no entry in pgactive.pgactive_nodes.$$, remote_dsn), + ERRCODE = 'object_not_in_prerequisite_state'; + ELSIF remote_nodeinfo.node_status IS DISTINCT FROM pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_READY') THEN + RAISE USING + MESSAGE = 'remote node does not appear to be a fully running pgactive node', + DETAIL = format($$The dsn '%s' connects successfully but the target node has pgactive.pgactive_nodes node_status=%s instead of expected 'r'.$$, remote_dsn, remote_nodeinfo.node_status), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT setting::integer INTO local_max_node_value FROM pg_settings + WHERE name = 'pgactive.max_nodes'; + + IF local_max_node_value <> remote_nodeinfo.max_nodes THEN + RAISE USING + MESSAGE = 'joining node and pgactive group have different values for pgactive.max_nodes parameter', + DETAIL = format('pgactive.max_nodes value for joining node is ''%s'' and remote node is ''%s''.', + local_max_node_value, remote_nodeinfo.max_nodes), + HINT = 'The parameter must be set to the same value on all pgactive members.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT setting FROM pg_settings + WHERE name = 'data_directory' INTO data_dir; + + SELECT pgactive._pgactive_get_free_disk_space(data_dir) INTO free_disk_space1; + SELECT pg_size_pretty(free_disk_space1) INTO free_disk_space1_p; + SELECT pg_size_pretty(remote_nodeinfo.dbsize) INTO remote_dbsize_p; + + -- We estimate that postgres needs 20% more disk space as temporary + -- workspace while restoring database for running queries or building + -- indexes. Note that it is just an estimation, the actual disk space + -- needed depends on various factors. Hence we emit a warning to inform + -- early, not an error. + IF free_disk_space1 < (1.2 * remote_nodeinfo.dbsize) THEN + RAISE WARNING USING + MESSAGE = 'node might fail to join pgactive group as disk space is likely to be insufficient', + DETAIL = format('joining node data directory file system mount point has %s free disk space and remote database is %s in size.', + free_disk_space1_p, remote_dbsize_p), + HINT = 'Ensure enough free space on joining node file system.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT setting FROM pg_settings + WHERE name = 'pgactive.temp_dump_directory' INTO temp_dump_dir; + + SELECT pgactive._pgactive_get_free_disk_space(temp_dump_dir) INTO free_disk_space2; + SELECT pg_size_pretty(free_disk_space2) INTO free_disk_space2_p; + + -- We estimate that pg_dump needs at least 50% of database size + -- excluding total size of indexes on the database. Note that it is + -- just an estimation, the actual disk space needed depends on various + -- factors. Hence we emit a warning to inform early, not an error. + IF free_disk_space2 < ((remote_nodeinfo.dbsize - remote_nodeinfo.indexessize)/2) THEN + RAISE WARNING USING + MESSAGE = 'node might fail to join pgactive group as disk space required to store temporary dump is likely to be insufficient', + DETAIL = format('pgactive.temp_dump_directory file system mount point has %s free disk space and remote database is %s in size.', + free_disk_space2_p, remote_dbsize_p), + HINT = 'Ensure enough free space on pgactive.temp_dump_directory file system.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT pgactive._pgactive_check_file_system_mount_points(data_dir, temp_dump_dir) + INTO same_file_system_mount_point; + + IF same_file_system_mount_point THEN + IF free_disk_space1 < + ((1.2 * remote_nodeinfo.dbsize) + ((remote_nodeinfo.dbsize - remote_nodeinfo.indexessize)/2)) THEN + RAISE WARNING USING + MESSAGE = 'node might fail to join pgactive group as disk space required to store both remote database and temporary dump is likely to be insufficient', + HINT = 'Ensure enough free space on joining node file system.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + END IF; + + -- using pg_file_settings here as pgactive.skip_ddl_replication is SET to on when entering + -- the function. + SELECT COALESCE((SELECT setting::boolean + FROM pg_file_settings + WHERE name = 'pgactive.skip_ddl_replication' ORDER BY seqno DESC LIMIT 1), + true) INTO local_skip_ddl_replication_value; + + IF local_skip_ddl_replication_value <> remote_nodeinfo.skip_ddl_replication THEN + RAISE USING + MESSAGE = 'joining node and pgactive group have different values for pgactive.skip_ddl_replication parameter', + DETAIL = format('pgactive.skip_ddl_replication value for joining node is ''%s'' and remote node is ''%s''.', + local_skip_ddl_replication_value, remote_nodeinfo.skip_ddl_replication), + HINT = 'The parameter must be set to the same value on all pgactive members.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + IF local_max_node_value = remote_nodeinfo.cur_nodes THEN + RAISE USING + MESSAGE = 'cannot allow more than pgactive.max_nodes number of nodes in a pgactive group', + HINT = 'Increase pgactive.max_nodes parameter value on joining node as well as on all other pgactive members.', + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + SELECT datcollate, datctype FROM pg_database + WHERE datname = current_database() INTO local_db_collation_info_r; + + IF local_db_collation_info_r.datcollate <> remote_nodeinfo.datcollate OR + local_db_collation_info_r.datctype <> remote_nodeinfo.datctype THEN + + collation_errmsg := 'joining node and remote node have different database collation settings'; + collation_hintmsg := 'Use the same database collation settings for both nodes.'; + + IF bypass_collation_check THEN + RAISE WARNING USING + MESSAGE = collation_errmsg, + HINT = collation_hintmsg, + ERRCODE = 'object_not_in_prerequisite_state'; + ELSE + RAISE EXCEPTION USING + MESSAGE = collation_errmsg, + HINT = collation_hintmsg, + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + END IF; + END IF; + + + IF data_only_node_init THEN + SELECT oid FROM pg_database + WHERE datname = current_database() INTO current_dboid; + -- The per-db worker will reset data_only_node_init to false after the + -- pgactive_init_replica. + PERFORM _pgactive_set_data_only_node_init(current_dboid, true); + END IF; + + -- Create local node record so the apply worker knows to start initializing + -- this node with pgactive_init_replica when it's started. + -- + -- pgactive_init_copy might've created a node entry in catchup mode already, in + -- which case we can skip this. + SELECT * FROM pgactive_nodes + WHERE node_sysid = localid.sysid + AND node_timeline = localid.timeline + AND node_dboid = localid.dboid + INTO cur_node; + + IF NOT FOUND THEN + INSERT INTO pgactive_nodes ( + node_name, + node_sysid, node_timeline, node_dboid, + node_status, node_dsn, node_init_from_dsn + ) VALUES ( + node_name, + localid.sysid, localid.timeline, localid.dboid, + pgactive.pgactive_node_status_to_char('pgactive_NODE_STATUS_BEGINNING_INIT'), + node_dsn, remote_dsn + ); + ELSIF pgactive.pgactive_node_status_from_char(cur_node.node_status) = 'pgactive_NODE_STATUS_CATCHUP' THEN + RAISE DEBUG 'starting node join in pgactive_NODE_STATUS_CATCHUP'; + ELSE + RAISE USING + MESSAGE = 'a pgactive_nodes entry for this node already exists', + DETAIL = format('pgactive.pgactive_nodes entry for (%s,%s,%s) named ''%s'' with status %s exists.', + cur_node.node_sysid, cur_node.node_timeline, cur_node.node_dboid, + cur_node.node_name, pgactive.pgactive_node_status_from_char(cur_node.node_status)), + ERRCODE = 'object_not_in_prerequisite_state'; + END IF; + + PERFORM pgactive._pgactive_update_seclabel_private(); +END; +$body$; + +REVOKE ALL ON FUNCTION _pgactive_begin_join_private(text, text, text, text, boolean, boolean, boolean, boolean) FROM public; + +CREATE FUNCTION _pgactive_set_data_only_node_init(dboid oid, val boolean) +RETURNS VOID +AS 'MODULE_PATHNAME' +LANGUAGE C; + +REVOKE ALL ON FUNCTION _pgactive_set_data_only_node_init(oid, boolean) FROM public; + +CREATE FUNCTION pgactive_get_replication_set_tables(r_sets text[]) +RETURNS SETOF text +VOLATILE +STRICT +LANGUAGE 'sql' +AS $$ + SELECT DISTINCT objname + FROM pg_seclabels + WHERE provider = 'pgactive' + AND objtype = 'table' + AND EXISTS ( + SELECT 1 + FROM json_array_elements_text(label::json->'sets') AS elem + WHERE elem::text = ANY (r_sets) + ); +$$; + +REVOKE ALL ON FUNCTION pgactive_get_replication_set_tables(text[]) FROM public; + +-- Completely de-pgactive-ize a node. Updated to fix #281. +CREATE OR REPLACE FUNCTION pgactive_remove ( + force boolean DEFAULT false) +RETURNS void +LANGUAGE plpgsql +-- SET pgactive.skip_ddl_locking = on is removed for now +-- SET pgactive.permit_unsafe_ddl_commands = on is removed for now +SET pgactive.skip_ddl_replication = on +SET search_path = 'pgactive,pg_catalog' +AS $$ +DECLARE + local_node_status "char"; + _seqschema name; + _seqname name; + _seqmax bigint; + _tableoid oid; + _truncate_tg record; + current_dboid oid; +BEGIN + + SELECT node_status FROM pgactive.pgactive_nodes WHERE (node_sysid, node_timeline, node_dboid) = pgactive.pgactive_get_local_nodeid() + INTO local_node_status; + + IF NOT (local_node_status = 'k' OR local_node_status IS NULL) THEN + IF force THEN + RAISE WARNING 'forcing deletion of possibly active pgactive node'; + + UPDATE pgactive.pgactive_nodes + SET node_status = 'k' + WHERE (node_sysid, node_timeline, node_dboid) = pgactive.pgactive_get_local_nodeid(); + + PERFORM pgactive._pgactive_pause_worker_management_private(false); + + PERFORM pg_sleep(5); + + RAISE NOTICE 'node forced to detached state, now removing'; + ELSE + RAISE EXCEPTION 'this pgactive node might still be active, not removing'; + END IF; + END IF; + + RAISE NOTICE 'removing pgactive from node'; + + -- Strip the database security label + EXECUTE format('SECURITY LABEL FOR pgactive ON DATABASE %I IS NULL', current_database()); + + -- Suspend worker management, so when we terminate apply workers and + -- walsenders they won't get relaunched. + PERFORM pgactive._pgactive_pause_worker_management_private(true); + + -- Terminate WAL sender(s) associated with this database. + PERFORM pgactive.pgactive_terminate_workers(node_sysid, node_timeline, node_dboid, 'walsender') + FROM pgactive.pgactive_nodes + WHERE (node_sysid, node_timeline, node_dboid) <> pgactive.pgactive_get_local_nodeid(); + + -- Terminate apply worker(s) associated with this database. + PERFORM pgactive.pgactive_terminate_workers(node_sysid, node_timeline, node_dboid, 'apply') + FROM pgactive.pgactive_nodes + WHERE (node_sysid, node_timeline, node_dboid) <> pgactive.pgactive_get_local_nodeid(); + + -- Delete all connections and all nodes except the current one + DELETE FROM pgactive.pgactive_connections + WHERE (conn_sysid, conn_timeline, conn_dboid) <> pgactive.pgactive_get_local_nodeid(); + + DELETE FROM pgactive.pgactive_nodes + WHERE (node_sysid, node_timeline, node_dboid) <> pgactive.pgactive_get_local_nodeid(); + + -- Let the perdb worker resume work and figure out everything's + -- going away. + PERFORM pgactive._pgactive_pause_worker_management_private(false); + PERFORM pgactive.pgactive_connections_changed(); + + -- Give it a few seconds + PERFORM pg_sleep(2); + + -- Terminate per-db worker associated with this database. + SELECT oid FROM pg_database + WHERE datname = current_database() INTO current_dboid; + PERFORM pgactive.pgactive_terminate_perdb_worker(current_dboid); + + -- Poke supervisor to clear the per-db worker's shared memory slot. + PERFORM pgactive.pgactive_connections_changed(); + + -- Clear out the rest of pgactive_nodes and pgactive_connections + DELETE FROM pgactive.pgactive_nodes; + DELETE FROM pgactive.pgactive_connections; + + -- Drop peer replication slots for this DB + PERFORM pg_drop_replication_slot(slot_name) + FROM pg_catalog.pg_replication_slots, + pgactive.pgactive_parse_slot_name(slot_name) ps + WHERE ps.local_dboid = (select oid from pg_database where datname = current_database()) + AND plugin = 'pgactive'; + + -- and replication origins + PERFORM pg_replication_origin_drop(roname) + FROM pg_catalog.pg_replication_origin, + pgactive.pgactive_parse_replident_name(roname) pi + WHERE pi.local_dboid = (select oid from pg_database where datname = current_database()); + + -- Strip the security labels we use for replication sets from all the tables + FOR _tableoid IN + SELECT objoid + FROM pg_catalog.pg_seclabel + INNER JOIN pg_catalog.pg_class ON (pg_seclabel.objoid = pg_class.oid) + WHERE provider = 'pgactive' + AND classoid = 'pg_catalog.pg_class'::regclass + AND pg_class.relkind = 'r' + LOOP + -- regclass's text out adds quoting and schema qualification if needed + EXECUTE format('SECURITY LABEL FOR pgactive ON TABLE %s IS NULL', _tableoid::regclass); + END LOOP; + + -- Drop the on-truncate triggers. They'd otherwise get cascade-dropped when + -- the pgactive extension was dropped, but this way the system is clean. We can't + -- drop ones under the 'pgactive' schema. + FOR _truncate_tg IN + SELECT + n.nspname AS tgrelnsp, + c.relname AS tgrelname, + t.tgname AS tgname, + d.objid AS tgobjid, + d.refobjid AS tgrelid + FROM pg_depend d + INNER JOIN pg_class c ON (d.refclassid = 'pg_class'::regclass AND d.refobjid = c.oid) + INNER JOIN pg_namespace n ON (c.relnamespace = n.oid) + INNER JOIN pg_trigger t ON (d.classid = 'pg_trigger'::regclass and d.objid = t.oid) + INNER JOIN pg_depend d2 ON (d.classid = d2.classid AND d.objid = d2.objid) + WHERE tgname LIKE 'truncate_trigger_%' + AND d2.refclassid = 'pg_proc'::regclass + AND d2.refobjid = 'pgactive.pgactive_queue_truncate'::regproc + AND n.nspname <> 'pgactive' + LOOP + EXECUTE format('DROP TRIGGER %I ON %I.%I', + _truncate_tg.tgname, _truncate_tg.tgrelnsp, _truncate_tg.tgrelname); + + -- The trigger' dependency entry will be dangling because of how we dropped + -- it. + DELETE FROM pg_depend + WHERE classid = 'pg_trigger'::regclass AND + (objid = _truncate_tg.tgobjid + AND (refclassid = 'pg_proc'::regclass AND refobjid = 'pgactive.pgactive_queue_truncate'::regproc) + OR + (refclassid = 'pg_class'::regclass AND refobjid = _truncate_tg.tgrelid) + ); + + END LOOP; + + -- Delete the other detritus from the extension. The user should really drop it, + -- but we should try to restore a clean state anyway. + DELETE FROM pgactive.pgactive_queued_commands; + DELETE FROM pgactive.pgactive_queued_drops; + DELETE FROM pgactive.pgactive_global_locks; + DELETE FROM pgactive.pgactive_conflict_handlers; + DELETE FROM pgactive.pgactive_conflict_history; + DELETE FROM pgactive.pgactive_replication_set_config; + + PERFORM pgactive._pgactive_destroy_temporary_dump_directories_private(); + + -- We can't drop the pgactive extension, we just need to tell the user to do that. + RAISE NOTICE 'pgactive removed from this node. You can now DROP EXTENSION pgactive and, if this is the last pgactive node on this PostgreSQL instance, remove pgactive from shared_preload_libraries.'; +END; +$$; + +REVOKE ALL ON FUNCTION pgactive_remove(boolean) FROM public; + +COMMENT ON FUNCTION pgactive_remove(boolean) IS +'Remove all pgactive security labels, slots, replication origins, replication sets, etc from the local node.'; + +CREATE FUNCTION pgactive_terminate_perdb_worker(dboid oid) +RETURNS VOID +AS 'MODULE_PATHNAME' +LANGUAGE C VOLATILE STRICT; + +REVOKE ALL ON FUNCTION pgactive_terminate_perdb_worker(oid) FROM public; + +DROP FUNCTION pgactive_wait_for_node_ready(integer, integer); + +CREATE FUNCTION pgactive_wait_for_node_ready( + timeout integer DEFAULT 0, + progress_interval integer DEFAULT 60) +RETURNS void LANGUAGE plpgsql VOLATILE +AS $body$ +DECLARE + local_node record; + remote_node record; + t_lp_cnt integer := 0; + p_lp_cnt integer := 0; + w_lp_cnt integer; + l_db_init_sz int8; + l_db_sz int8; + r_db text; + p_pct integer; + sleep_sec integer; + worker_timeout integer; +BEGIN + + IF timeout < 0 THEN + RAISE EXCEPTION '''timeout'' parameter must not be 0'; + END IF; + + IF progress_interval <= 0 THEN + RAISE EXCEPTION '''progress_interval'' parameter must be > 0'; + END IF; + w_lp_cnt := 0; + sleep_sec := 5; + worker_timeout := 120; + LOOP + PERFORM pg_sleep( sleep_sec ); + PERFORM PID from pg_stat_activity where application_name = 'pgactive:supervisor'; + IF FOUND THEN + EXIT; + END IF; + IF w_lp_cnt > worker_timeout THEN + RAISE EXCEPTION 'pgactive supervisor is not running'; + ELSE + RAISE NOTICE 'waiting for pgactive supervisor to start %/%', w_lp_cnt, worker_timeout; + END IF; + w_lp_cnt := w_lp_cnt + sleep_sec; + END LOOP; + + IF current_setting('transaction_isolation') <> 'read committed' THEN + RAISE EXCEPTION 'can only wait for node join in an ISOLATION LEVEL READ COMMITTED transaction, not %', + current_setting('transaction_isolation'); + END IF; + + SELECT * FROM pgactive.pgactive_nodes + WHERE (node_sysid, node_timeline, node_dboid) = pgactive.pgactive_get_local_nodeid() + INTO local_node; + + IF local_node.node_init_from_dsn is NULL THEN + RAISE NOTICE 'checking status of pgactive.pgactive_create_group'; + ELSE + RAISE NOTICE 'checking status of pgactive.pgactive_join_group'; + SELECT * FROM pgactive._pgactive_get_node_info_private(local_node.node_init_from_dsn) + INTO remote_node; + SELECT pg_size_pretty(remote_node.dbsize) INTO r_db; + SELECT pg_database_size(local_node.node_dboid) INTO l_db_init_sz; + END IF; + w_lp_cnt := 0; + sleep_sec := 10; + worker_timeout := 300; + LOOP + SELECT * FROM pgactive.pgactive_nodes + WHERE (node_sysid, node_timeline, node_dboid) + = pgactive.pgactive_get_local_nodeid() + INTO local_node; + + IF local_node.node_status = 'r' THEN + IF remote_node IS NOT NULL THEN + RAISE NOTICE + USING MESSAGE = format('successfully joined the node and restored database ''%s'' from node %s', + remote_node.dbname, remote_node.node_name); + ELSE + RAISE NOTICE 'successfully created first node in pgactive group'; + END IF; + EXIT; + END IF; + + IF timeout > 0 THEN + t_lp_cnt := t_lp_cnt + sleep_sec; + IF t_lp_cnt > timeout THEN + RAISE EXCEPTION 'node % cannot reach ready state within % seconds, current state is %', + local_node.node_name, timeout, local_node.node_status; + END IF; + END IF; + + PERFORM pg_sleep( sleep_sec ); + w_lp_cnt := w_lp_cnt + sleep_sec; + IF w_lp_cnt > worker_timeout THEN + w_lp_cnt := 0; + PERFORM PID FROM pg_stat_activity where application_name = 'pgactive:'|| local_node.node_sysid ||':perdb'; + IF NOT FOUND THEN + RAISE EXCEPTION 'could not detect a running pgactive perdb worker, current node state is %', local_node.node_status + USING DETAIL = format( 'Either pgactive perdb worker exited due to an error or it did not start in %s seconds.', worker_timeout), + HINT = 'Please check PostgreSQL log file for more details.'; + END IF; + END IF; + + IF progress_interval > 0 AND local_node.node_init_from_dsn IS NOT NULL THEN + p_lp_cnt := p_lp_cnt + sleep_sec; + + IF p_lp_cnt > progress_interval THEN + SELECT pg_database_size(local_node.node_dboid) INTO l_db_sz; + IF l_db_sz = 0 OR l_db_sz = l_db_init_sz THEN + RAISE NOTICE + USING MESSAGE = format('transferring of database ''%s'' (%s) from node %s in progress', + remote_node.dbname, r_db, remote_node.node_name); + ELSE + SELECT (l_db_sz/remote_node.dbsize) * 100 INTO p_pct; + RAISE NOTICE + USING MESSAGE = format('restoring database ''%s'', %s%% of %s complete', + remote_node.dbname, p_pct, r_db); + END IF; + p_lp_cnt := 0; + END IF; + END IF; + END LOOP; +END; +$body$; + +REVOKE ALL ON FUNCTION pgactive_wait_for_node_ready(integer, integer) FROM public; + +DROP VIEW pgactive.pgactive_node_slots; + +DROP FUNCTION pgactive_get_replication_lag_info(); + +CREATE FUNCTION pgactive_get_replication_lag_info( + OUT node_name text, + OUT node_sysid text, + OUT application_name text, + OUT slot_name text, + OUT active boolean, + OUT active_pid integer, + OUT pending_wal_decoding bigint, + OUT pending_wal_to_apply bigint, + OUT restart_lsn pg_lsn, + OUT confirmed_flush_lsn pg_lsn, + OUT sent_lsn pg_lsn, + OUT write_lsn pg_lsn, + OUT flush_lsn pg_lsn, + OUT replay_lsn pg_lsn +) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C VOLATILE STRICT; + +COMMENT ON FUNCTION pgactive_get_replication_lag_info() IS +'Gets replication lag info.'; + +-- RESET pgactive.permit_unsafe_ddl_commands; is removed for now +RESET pgactive.skip_ddl_replication; +RESET search_path; + +-- Upgrades from 2.1.4 to 2.1.5 + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION + +SET pgactive.skip_ddl_replication = true; +SET LOCAL search_path = pgactive; +-- Start Upgrade SQLs/Functions/Procedures + + +-- Finish Upgrade SQLs/Functions/Procedures +RESET pgactive.skip_ddl_replication; +RESET search_path; + +-- Upgrades from 2.1.5 to 2.1.6 + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION + +SET pgactive.skip_ddl_replication = true; +SET LOCAL search_path = pgactive; +-- Start Upgrade SQLs/Functions/Procedures + + +-- Finish Upgrade SQLs/Functions/Procedures +RESET pgactive.skip_ddl_replication; +RESET search_path; + +-- Upgrades from 2.1.6 to 2.1.7 + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION + +SET pgactive.skip_ddl_replication = true; +SET LOCAL search_path = pgactive; +-- Start Upgrade SQLs/Functions/Procedures + +DROP FUNCTION pgactive_create_group (text, text, integer, text[]); + +CREATE FUNCTION pgactive_create_group ( + node_name text, + node_dsn text, + apply_delay integer DEFAULT NULL, + replication_sets text[] DEFAULT ARRAY['default'] + ) +RETURNS void LANGUAGE plpgsql VOLATILE +SET search_path = pgactive, pg_catalog +-- SET pgactive.permit_unsafe_ddl_commands = on is removed for now +SET pgactive.skip_ddl_replication = on +-- SET pgactive.skip_ddl_locking = on is removed for now +AS $body$ +DECLARE + t record; +BEGIN + + -- Prohibit enabling pgactive where exclusion constraints exist + FOR t IN + SELECT n.nspname, r.relname, c.conname, c.contype + FROM pg_constraint c + INNER JOIN pg_namespace n ON c.connamespace = n.oid + INNER JOIN pg_class r ON c.conrelid = r.oid + INNER JOIN LATERAL unnest(pgactive.pgactive_get_table_replication_sets(c.conrelid)) rs(rsname) ON (rs.rsname = ANY(replication_sets)) + WHERE c.contype = 'x' + AND r.relpersistence = 'p' + AND r.relkind = 'r' + AND n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema') + LOOP + RAISE USING + MESSAGE = 'pgactive can''t be enabled because exclusion constraints exist on persistent tables that are not excluded from replication', + ERRCODE = 'object_not_in_prerequisite_state', + DETAIL = format('Table %I.%I has exclusion constraint %I.', t.nspname, t.relname, t.conname), + HINT = 'Drop the exclusion constraint(s), change the table(s) to UNLOGGED if they don''t need to be replicated, or exclude the table(s) from the active replication set(s).'; + END LOOP; + + -- Warn users about missing primary keys and replica identity index + FOR t IN + SELECT n.nspname, r.relname, c.conname, c.contype + FROM pg_constraint c + INNER JOIN pg_namespace n ON c.connamespace = n.oid + INNER JOIN pg_class r ON c.conrelid = r.oid + INNER JOIN LATERAL unnest(pgactive.pgactive_get_table_replication_sets(c.conrelid)) rs(rsname) ON (rs.rsname = ANY(replication_sets)) + WHERE c.contype = 'u' + AND r.relpersistence = 'p' + AND r.relkind = 'r' + AND n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema') + LOOP + RAISE WARNING USING + MESSAGE = 'secondary unique constraint(s) exist on replicated table(s)', + DETAIL = format('Table %I.%I has secondary unique constraint %I. This may cause unhandled replication conflicts.', t.nspname, t.relname, t.conname), + HINT = 'Drop the secondary unique constraint(s), change the table(s) to UNLOGGED if they don''t need to be replicated, or exclude the table(s) from the active replication set(s).'; + END LOOP; + + -- Warn users about missing primary keys + FOR t IN + SELECT n.nspname, r.relname, c.conname + FROM pg_class r INNER JOIN pg_namespace n ON r.relnamespace = n.oid + LEFT OUTER JOIN pg_constraint c ON (c.conrelid = r.oid AND c.contype = 'p') + WHERE n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema') + AND relkind = 'r' + AND relpersistence = 'p' + AND c.oid IS NULL AND r.relreplident != 'i' + LOOP + RAISE WARNING USING + MESSAGE = format('table %I.%I has no PRIMARY KEY', t.nspname, t.relname), + HINT = 'Tables without a PRIMARY KEY and REPLICA IDENTITY INDEX cannot be UPDATED or DELETED from, only INSERTED into. Add a PRIMARY KEY or a REPLICA IDENTITY INDEX.'; + END LOOP; + + -- Create ON TRUNCATE triggers for pgactive on existing tables + -- See pgactive_truncate_trigger_add for the matching event trigger for tables + -- created after join. + -- + -- The triggers may be created already because the pgactive event trigger + -- runs when the pgactive extension is created, even if there's no active + -- pgactive connections yet, so tables created after the extension is created + -- will get the trigger already. So skip tables that have a tg named + -- 'truncate_trigger' calling proc 'pgactive.pgactive_queue_truncate'. + FOR t IN + SELECT r.oid AS relid + FROM pg_class r + INNER JOIN pg_namespace n ON (r.relnamespace = n.oid) + LEFT JOIN pg_trigger tg ON (r.oid = tg.tgrelid AND tgname = 'truncate_trigger') + LEFT JOIN pg_proc p ON (p.oid = tg.tgfoid AND p.proname = 'pgactive_queue_truncate') + LEFT JOIN pg_namespace pn ON (pn.oid = p.pronamespace AND pn.nspname = 'pgactive') + WHERE r.relpersistence = 'p' + AND r.relkind = 'r' + AND n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema') + AND tg.oid IS NULL AND p.oid IS NULL and pn.oid IS NULL + LOOP + -- We use a C function here because in addition to trigger creation + -- we must also mark it tgisinternal. + PERFORM pgactive.pgactive_internal_create_truncate_trigger(t.relid); + END LOOP; + + PERFORM pgactive.pgactive_join_group( + node_name := node_name, + node_dsn := node_dsn, + join_using_dsn := null, + apply_delay := apply_delay, + replication_sets := replication_sets, + bypass_user_tables_check := true); +END; +$body$; + +COMMENT ON FUNCTION pgactive_create_group(text, text, integer, text[]) IS +'Create a pgactive group, turning a stand-alone database into the first node in a pgactive group'; + +REVOKE ALL ON FUNCTION pgactive_create_group(text, text, integer, text[]) FROM public; + +-- Finish Upgrade SQLs/Functions/Procedures +RESET pgactive.skip_ddl_replication; +RESET search_path; diff --git a/pgactive.control b/pgactive.control index 4e3993a1..9087682d 100644 --- a/pgactive.control +++ b/pgactive.control @@ -1,6 +1,6 @@ # pgactive extension comment = 'Active-Active Replication Extension for PostgreSQL' -default_version = '2.1.7' +default_version = '2.1.8' module_pathname = '$libdir/pgactive' relocatable = false schema = pg_catalog diff --git a/src/pgactive.c b/src/pgactive.c index 6663a1db..e6c69e5a 100644 --- a/src/pgactive.c +++ b/src/pgactive.c @@ -556,6 +556,8 @@ pgactive_bgworker_init(uint32 worker_arg, pgactiveWorkerType worker_type) Oid dboid; pgactiveNodeId myid; char mystatus; + Oid pgactive_oid; + Oid schema_oid; #if PG_VERSION_NUM < 170000 Assert(IsBackgroundWorker); @@ -615,6 +617,22 @@ pgactive_bgworker_init(uint32 worker_arg, pgactiveWorkerType worker_type) pgactive_worker_slot->worker_pid = MyProcPid; pgactive_worker_slot->worker_proc = MyProc; + SetCurrentStatementStartTimestamp(); + StartTransactionCommand(); + PushActiveSnapshot(GetTransactionSnapshot()); + schema_oid = get_namespace_oid(pgactive_SCHEMA_NAME, true); + pgactive_oid = get_extension_oid("pgactive", true); + if (schema_oid != InvalidOid && pgactive_oid == InvalidOid) + { + elog(LOG, "pgactive schema is present but extension is not created. Cleanup and restart instance"); + LWLockRelease(pgactiveWorkerCtl->lock); + + pgactive_worker_unregister(); + pg_unreachable(); + } + PopActiveSnapshot(); + CommitTransactionCommand(); + /* Check if we decided to unregister this worker. */ if (!OidIsValid(find_pgactive_nid_getter_function())) { @@ -1315,7 +1333,7 @@ pgactive_maintain_schema(bool update_extensions) table_close(extrel, NoLock); /* setup initial queued_cmds OID */ - schema_oid = get_namespace_oid("pgactive", false); + schema_oid = get_namespace_oid(pgactive_SCHEMA_NAME, false); pgactiveSchemaOid = schema_oid; pgactiveNodesRelid = pgactive_lookup_relid("pgactive_nodes", schema_oid); diff --git a/src/pgactive_remotecalls.c b/src/pgactive_remotecalls.c index 017a1b7a..3a73f496 100644 --- a/src/pgactive_remotecalls.c +++ b/src/pgactive_remotecalls.c @@ -573,25 +573,24 @@ pgactive_node_name_present(PG_FUNCTION_ARGS) int is_present = 0; PGconn *conn; PGresult *res; - StringInfoData cmd; + const char *paramValues[1]; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) PG_RETURN_NULL(); node_name = text_to_cstring(PG_GETARG_TEXT_P(0)); + paramValues[0] = node_name; dsn = text_to_cstring(PG_GETARG_TEXT_P(1)); - initStringInfo(&cmd); - appendStringInfo(&cmd, "select count(1) from pgactive.pgactive_nodes where node_name = '%s' and node_status != 'k'", node_name); - conn = pgactive_connect_nonrepl(dsn, "pgactive", false, false); if (PQstatus(conn) != CONNECTION_OK) { elog(ERROR, "unable to connect to remote node node: %s", dsn); } - res = PQexec(conn, cmd.data); + res = PQexecParams(conn, "select count(1) from pgactive.pgactive_nodes where node_name = $1 and node_status != 'k'", + 1, NULL, paramValues, NULL, NULL, 0); if (PQresultStatus(res) != PGRES_TUPLES_OK) { @@ -608,7 +607,6 @@ pgactive_node_name_present(PG_FUNCTION_ARGS) is_present = atoi(PQgetvalue(res, 0, 0)); - pfree(cmd.data); PQclear(res); PQfinish(conn); PG_RETURN_INT32(is_present); diff --git a/src/pgactive_user_mapping.c b/src/pgactive_user_mapping.c index e18a90ec..bd71f1b1 100644 --- a/src/pgactive_user_mapping.c +++ b/src/pgactive_user_mapping.c @@ -208,9 +208,10 @@ get_connect_string(const char *usermappinginfo) const PQconninfoOption *options = NULL; char *umname = NULL; char *fsname = NULL; - StringInfoData cmd; Oid umuser; PQconninfoOption *opts = NULL; + Oid argtypes[] = { TEXTOID, TEXTOID }; + Datum args[2]; /* * First check if it's a valid connection string, if yes, do nothing @@ -247,18 +248,16 @@ get_connect_string(const char *usermappinginfo) StartTransactionCommand(); } - initStringInfo(&cmd); - appendStringInfo(&cmd, "SELECT pfs.srvname FROM pg_catalog.pg_foreign_server pfs " - "JOIN pg_catalog.pg_foreign_data_wrapper pfdw ON pfdw.oid = pfs.srvfdw " - "WHERE pfdw.fdwname ='pgactive_fdw' AND pfs.srvname = '%s';", - fsname); - if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); PushActiveSnapshot(GetTransactionSnapshot()); - if (SPI_execute(cmd.data, false, 0) != SPI_OK_SELECT) - elog(ERROR, "SPI_execute failed: %s", cmd.data); + args[0] = PointerGetDatum(cstring_to_text(fsname)); + if (SPI_execute_with_args("SELECT pfs.srvname FROM pg_catalog.pg_foreign_server pfs " + "JOIN pg_catalog.pg_foreign_data_wrapper pfdw ON pfdw.oid = pfs.srvfdw " + "WHERE pfdw.fdwname ='pgactive_fdw' AND pfs.srvname = $1;", + 1, argtypes, args, NULL, true, 1) != SPI_OK_SELECT) + elog(ERROR, "SPI_execute_with_args failed to query FDW"); if (SPI_processed != 1 || SPI_tuptable->tupdesc->natts != 1) { @@ -270,17 +269,15 @@ get_connect_string(const char *usermappinginfo) elog(ERROR, "SPI_finish failed"); PopActiveSnapshot(); - resetStringInfo(&cmd); - appendStringInfo(&cmd, "SELECT umuser FROM pg_catalog.pg_user_mappings " - "WHERE usename = '%s' AND srvname = '%s';", - umname, fsname); - if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); PushActiveSnapshot(GetTransactionSnapshot()); - if (SPI_execute(cmd.data, false, 0) != SPI_OK_SELECT) - elog(ERROR, "SPI_execute failed: %s", cmd.data); + args[0] = PointerGetDatum(cstring_to_text(umname)); + args[1] = PointerGetDatum(cstring_to_text(fsname)); + if (SPI_execute_with_args("SELECT umuser FROM pg_catalog.pg_user_mappings WHERE usename = $1 AND srvname = $2;", + 2, argtypes, args, NULL, true, 1) != SPI_OK_SELECT) + elog(ERROR, "SPI_execute_with_args failed to query pg_user_mappings for given mapping and fdw"); if (SPI_processed != 1 || SPI_tuptable->tupdesc->natts != 1) { @@ -288,8 +285,6 @@ get_connect_string(const char *usermappinginfo) (int) SPI_processed, SPI_tuptable->tupdesc->natts); } - pfree(cmd.data); - umuser = DatumGetObjectId( DirectFunctionCall1(oidin, CStringGetDatum(SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1)))); diff --git a/test/t/059_misc.pl b/test/t/059_misc.pl index ae2e3c9b..cce498e0 100644 --- a/test/t/059_misc.pl +++ b/test/t/059_misc.pl @@ -17,6 +17,10 @@ my $nodes = make_pgactive_group(2,'node_'); my ($node_0,$node_1) = @$nodes; +my $my_count = $node_0->safe_psql($pgactive_test_dbname, + q[SELECT COUNT(*) FROM information_schema.role_routine_grants WHERE grantee = 'PUBLIC' and routine_schema = 'pgactive' and routine_name ilike '%private%';]); +is($my_count, 0, 'Check that we are not exposing private functions to PUBLIC'); + $node_0->safe_psql($pgactive_test_dbname, q[CREATE TABLE fruits(id integer PRIMARY KEY, name varchar);]); $node_0->safe_psql($pgactive_test_dbname, From f7c147d18b44b5605517dc554d5c7308b05baf16 Mon Sep 17 00:00:00 2001 From: Yogesh Sharma Date: Thu, 28 May 2026 08:20:13 -0400 Subject: [PATCH 3/6] Run pgindent --- src/pgactive_remotecalls.c | 2 +- src/pgactive_user_mapping.c | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pgactive_remotecalls.c b/src/pgactive_remotecalls.c index 3a73f496..cfde11fa 100644 --- a/src/pgactive_remotecalls.c +++ b/src/pgactive_remotecalls.c @@ -590,7 +590,7 @@ pgactive_node_name_present(PG_FUNCTION_ARGS) } res = PQexecParams(conn, "select count(1) from pgactive.pgactive_nodes where node_name = $1 and node_status != 'k'", - 1, NULL, paramValues, NULL, NULL, 0); + 1, NULL, paramValues, NULL, NULL, 0); if (PQresultStatus(res) != PGRES_TUPLES_OK) { diff --git a/src/pgactive_user_mapping.c b/src/pgactive_user_mapping.c index bd71f1b1..6a63c258 100644 --- a/src/pgactive_user_mapping.c +++ b/src/pgactive_user_mapping.c @@ -210,8 +210,8 @@ get_connect_string(const char *usermappinginfo) char *fsname = NULL; Oid umuser; PQconninfoOption *opts = NULL; - Oid argtypes[] = { TEXTOID, TEXTOID }; - Datum args[2]; + Oid argtypes[] = {TEXTOID, TEXTOID}; + Datum args[2]; /* * First check if it's a valid connection string, if yes, do nothing @@ -254,9 +254,9 @@ get_connect_string(const char *usermappinginfo) args[0] = PointerGetDatum(cstring_to_text(fsname)); if (SPI_execute_with_args("SELECT pfs.srvname FROM pg_catalog.pg_foreign_server pfs " - "JOIN pg_catalog.pg_foreign_data_wrapper pfdw ON pfdw.oid = pfs.srvfdw " - "WHERE pfdw.fdwname ='pgactive_fdw' AND pfs.srvname = $1;", - 1, argtypes, args, NULL, true, 1) != SPI_OK_SELECT) + "JOIN pg_catalog.pg_foreign_data_wrapper pfdw ON pfdw.oid = pfs.srvfdw " + "WHERE pfdw.fdwname ='pgactive_fdw' AND pfs.srvname = $1;", + 1, argtypes, args, NULL, true, 1) != SPI_OK_SELECT) elog(ERROR, "SPI_execute_with_args failed to query FDW"); if (SPI_processed != 1 || SPI_tuptable->tupdesc->natts != 1) @@ -276,7 +276,7 @@ get_connect_string(const char *usermappinginfo) args[0] = PointerGetDatum(cstring_to_text(umname)); args[1] = PointerGetDatum(cstring_to_text(fsname)); if (SPI_execute_with_args("SELECT umuser FROM pg_catalog.pg_user_mappings WHERE usename = $1 AND srvname = $2;", - 2, argtypes, args, NULL, true, 1) != SPI_OK_SELECT) + 2, argtypes, args, NULL, true, 1) != SPI_OK_SELECT) elog(ERROR, "SPI_execute_with_args failed to query pg_user_mappings for given mapping and fdw"); if (SPI_processed != 1 || SPI_tuptable->tupdesc->natts != 1) From adb4c1b45196ce899849ec1c14cf726ce4c9ddc5 Mon Sep 17 00:00:00 2001 From: Yogesh Sharma Date: Thu, 28 May 2026 19:58:17 -0400 Subject: [PATCH 4/6] Update confure Fix tests to use events instead of random sleeps --- configure | 1526 +++++++++-------- test/t/010_pgactive_init_copy.pl | 4 +- test/t/011_pgactive_init_copy_extended.pl | 4 +- test/t/025_ddl_lock.pl | 6 +- test/t/038_apply_delay.pl | 3 +- test/t/045_unregister_workers_after_detach.pl | 75 +- test/t/047_verify_pgactive_guc_settings.pl | 3 +- test/t/059_misc.pl | 61 +- test/t/070_pgactive_ddl_lock_detach.pl | 3 +- test/t/utils/concurrent.pm | 17 +- test/t/utils/nodemanagement.pm | 75 +- 11 files changed, 964 insertions(+), 813 deletions(-) diff --git a/configure b/configure index d799f036..95572a87 100755 --- a/configure +++ b/configure @@ -1,11 +1,11 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for pgactive pgactive-next. +# Generated by GNU Autoconf 2.73 for pgactive pgactive-next. # -# Report bugs to . +# Report bugs to . # # -# Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, +# Copyright (C) 1992-1996, 1998-2017, 2020-2026 Free Software Foundation, # Inc. # # @@ -19,21 +19,21 @@ # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which - # is contrary to our usage. Disable this feature. + # contradicts POSIX and common usage. Disable this. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else $as_nop - case `(set -o) 2>/dev/null` in #( +else case e in #( + e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; +esac ;; esac fi @@ -105,13 +105,13 @@ IFS=$as_save_IFS ;; esac -# We did not find ourselves, most probably we were run as `sh COMMAND' +# We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then - printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + printf '%s\n' "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi @@ -133,30 +133,33 @@ case $- in # (((( *x* ) as_opts=-x ;; * ) as_opts= ;; esac -exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +case $# in # (( + 0) exec $CONFIG_SHELL $as_opts "$as_myself" ;; + *) exec $CONFIG_SHELL $as_opts "$as_myself" "$@" ;; +esac # Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed `exec'. -printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 +# out after a failed 'exec'. +printf '%s\n' "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then - as_bourne_compatible="as_nop=: -if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 + as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which - # is contrary to our usage. Disable this feature. + # contradicts POSIX and common usage. Disable this. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST -else \$as_nop - case \`(set -o) 2>/dev/null\` in #( +else case e in #( + e) case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; +esac ;; esac fi " @@ -174,8 +177,9 @@ as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ) then : -else \$as_nop - exitcode=1; echo positional parameters were not saved. +else case e in #( + e) exitcode=1; echo positional parameters were not saved. ;; +esac fi test x\$exitcode = x0 || exit 1 blah=\$(echo \$(echo blah)) @@ -189,14 +193,15 @@ test \$(( 1 + 1 )) = 2 || exit 1" if (eval "$as_required") 2>/dev/null then : as_have_required=yes -else $as_nop - as_have_required=no +else case e in #( + e) as_have_required=no ;; +esac fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null then : -else $as_nop - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +else case e in #( + e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do @@ -229,12 +234,13 @@ IFS=$as_save_IFS if $as_found then : -else $as_nop - if { test -f "$SHELL" || test -f "$SHELL.exe"; } && +else case e in #( + e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$SHELL as_have_required=yes -fi +fi ;; +esac fi @@ -254,29 +260,33 @@ case $- in # (((( *x* ) as_opts=-x ;; * ) as_opts= ;; esac -exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +case $# in # (( + 0) exec $CONFIG_SHELL $as_opts "$as_myself" ;; + *) exec $CONFIG_SHELL $as_opts "$as_myself" "$@" ;; +esac # Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed `exec'. -printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 +# out after a failed 'exec'. +printf '%s\n' "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno then : - printf "%s\n" "$0: This script requires a shell more modern than all" - printf "%s\n" "$0: the shells that I found on your system." + printf '%s\n' "$0: This script requires a shell more modern than all" + printf '%s\n' "$0: the shells that I found on your system." if test ${ZSH_VERSION+y} ; then - printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" - printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." + printf '%s\n' "$0: In particular, zsh $ZSH_VERSION has bugs and should" + printf '%s\n' "$0: be upgraded to zsh 4.3.4 or later." else - printf "%s\n" "$0: Please tell bug-autoconf@gnu.org and -$0: pgsql-bugs@postgresql.org about your system, including -$0: any error possibly output before this message. Then -$0: install a modern shell, or manually run the script -$0: under such a shell if you do have one." + printf '%s\n' "$0: Please tell bug-autoconf@gnu.org and +$0: https://github.com/aws/pgactive/issues/new/choose about +$0: your system, including any error possibly output before +$0: this message. Then install a modern shell, or manually +$0: run the script under such a shell if you do have one." fi exit 1 -fi +fi ;; +esac fi fi SHELL=${CONFIG_SHELL-/bin/sh} @@ -315,14 +325,6 @@ as_fn_exit () as_fn_set_status $1 exit $1 } # as_fn_exit -# as_fn_nop -# --------- -# Do nothing but, unlike ":", preserve the value of $?. -as_fn_nop () -{ - return $? -} -as_nop=as_fn_nop # as_fn_mkdir_p # ------------- @@ -337,7 +339,7 @@ as_fn_mkdir_p () as_dirs= while :; do case $as_dir in #( - *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *\'*) as_qdir=`printf '%s\n' "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" @@ -346,7 +348,7 @@ $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$as_dir" | +printf '%s\n' X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -391,11 +393,12 @@ then : { eval $1+=\$2 }' -else $as_nop - as_fn_append () +else case e in #( + e) as_fn_append () { eval $1=\$$1\$2 - } + } ;; +esac fi # as_fn_append # as_fn_arith ARG... @@ -409,21 +412,14 @@ then : { as_val=$(( $* )) }' -else $as_nop - as_fn_arith () +else case e in #( + e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` - } + } ;; +esac fi # as_fn_arith -# as_fn_nop -# --------- -# Do nothing but, unlike ":", preserve the value of $?. -as_fn_nop () -{ - return $? -} -as_nop=as_fn_nop # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- @@ -435,9 +431,9 @@ as_fn_error () as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi - printf "%s\n" "$as_me: error: $2" >&2 + printf '%s\n' "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error @@ -464,7 +460,7 @@ as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X/"$0" | +printf '%s\n' X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q @@ -497,6 +493,8 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits /[$]LINENO/= ' <$as_myself | sed ' + t clear + :clear s/[$]LINENO.*/&-/ t lineno b @@ -508,7 +506,7 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || - { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + { printf '%s\n' "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall @@ -522,30 +520,6 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits exit } - -# Determine whether it's possible to make 'echo' print without a newline. -# These variables are no longer used directly by Autoconf, but are AC_SUBSTed -# for compatibility with existing Makefiles. -ECHO_C= ECHO_N= ECHO_T= -case `echo -n x` in #((((( --n*) - case `echo 'xy\c'` in - *c*) ECHO_T=' ';; # ECHO_T is single tab character. - xy) ECHO_C='\c';; - *) echo `echo ksh88 bug on AIX 6.1` > /dev/null - ECHO_T=' ';; - esac;; -*) - ECHO_N='-n';; -esac - -# For backward compatibility with old third-party macros, we provide -# the shell variables $as_echo and $as_echo_n. New code should use -# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. -as_echo='printf %s\n' -as_echo_n='printf %s' - - rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file @@ -557,9 +531,9 @@ if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: - # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -pR'. + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then @@ -584,10 +558,12 @@ as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. -as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. -as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated test -n "$DJDIR" || exec 7<&0 /dev/null | sed 1q` # Initializations. # ac_default_prefix=/usr/local +ac_clean_CONFIG_STATUS= ac_clean_files= ac_config_libobj_dir=. LIBOBJS= @@ -634,13 +611,13 @@ pgactive_PG_MAJORVERSION PG_CONFIG SED configure_args +ECHO_T +ECHO_N +ECHO_C target_alias host_alias build_alias LIBS -ECHO_T -ECHO_N -ECHO_C DEFS mandir localedir @@ -796,9 +773,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: \`$ac_useropt'" + as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" @@ -822,9 +799,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: \`$ac_useropt'" + as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" @@ -1035,9 +1012,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: \`$ac_useropt'" + as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" @@ -1051,9 +1028,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: \`$ac_useropt'" + as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" @@ -1081,8 +1058,8 @@ do | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; - -*) as_fn_error $? "unrecognized option: \`$ac_option' -Try \`$0 --help' for more information" + -*) as_fn_error $? "unrecognized option: '$ac_option' +Try '$0 --help' for more information" ;; *=*) @@ -1090,16 +1067,16 @@ Try \`$0 --help' for more information" # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) - as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + as_fn_error $? "invalid variable name: '$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. - printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 + printf '%s\n' "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && - printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 + printf '%s\n' "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; @@ -1107,7 +1084,7 @@ Try \`$0 --help' for more information" done if test -n "$ac_prev"; then - ac_option=--`echo $ac_prev | sed 's/_/-/g'` + ac_option=--`printf '%s\n' $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi @@ -1115,7 +1092,7 @@ if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; - *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + *) printf '%s\n' "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi @@ -1140,7 +1117,7 @@ do as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done -# There might be people who depend on the old broken behavior: `$host' +# There might be people who depend on the old broken behavior: '$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias @@ -1179,7 +1156,7 @@ $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$as_myself" | +printf '%s\n' X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -1208,7 +1185,7 @@ if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi -ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` @@ -1236,7 +1213,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures pgactive pgactive-next to adapt to many kinds of systems. +'configure' configures pgactive pgactive-next to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1250,11 +1227,11 @@ Configuration: --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit - -q, --quiet, --silent do not print \`checking ...' messages + -q, --quiet, --silent do not print 'checking ...' messages --cache-file=FILE cache test results in FILE [disabled] - -C, --config-cache alias for \`--cache-file=config.cache' + -C, --config-cache alias for '--cache-file=config.cache' -n, --no-create do not create output files - --srcdir=DIR find the sources in DIR [configure dir or \`..'] + --srcdir=DIR find the sources in DIR [configure dir or '..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX @@ -1262,10 +1239,10 @@ Installation directories: --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] -By default, \`make install' will install all the files in -\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify -an installation prefix other than \`$ac_default_prefix' using \`--prefix', -for instance \`--prefix=\$HOME'. +By default, 'make install' will install all the files in +'$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify +an installation prefix other than '$ac_default_prefix' using '--prefix', +for instance '--prefix=\$HOME'. For better control, use the options below. @@ -1320,11 +1297,11 @@ Some influential environment variables: you have headers in a nonstandard directory PATH PATH for target PostgreSQL install pg_config -Use these variables to override the choices made by `configure' or to help +Use these variables to override the choices made by 'configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . -pgactive home page: . +pgactive home page: . _ACEOF ac_status=$? fi @@ -1340,9 +1317,9 @@ if test "$ac_init_help" = "recursive"; then case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) - ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` + ac_dir_suffix=/`printf '%s\n' "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + ac_top_builddir_sub=`printf '%s\n' "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; @@ -1379,7 +1356,7 @@ ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix echo && $SHELL "$ac_srcdir/configure" --help=recursive else - printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + printf '%s\n' "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done @@ -1389,9 +1366,9 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF pgactive configure pgactive-next -generated by GNU Autoconf 2.71 +generated by GNU Autoconf 2.73 -Copyright (C) 2021 Free Software Foundation, Inc. +Copyright (C) 2026 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. @@ -1417,7 +1394,7 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then @@ -1425,18 +1402,19 @@ printf "%s\n" "$ac_try_echo"; } >&5 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext then : ac_retval=0 -else $as_nop - printf "%s\n" "$as_me: failed program was:" >&5 +else case e in #( + e) printf '%s\n' "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1 + ac_retval=1 ;; +esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval @@ -1450,13 +1428,13 @@ fi ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> @@ -1464,14 +1442,16 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" -else $as_nop - eval "$3=no" +else case e in #( + e) eval "$3=no" ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi eval ac_res=\$$3 - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -printf "%s\n" "$ac_res" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf '%s\n' "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_compile @@ -1480,7 +1460,7 @@ for ac_arg do case $ac_arg in *\'*) - ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + ac_arg=`printf '%s\n' "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append ac_configure_args_raw " '$ac_arg'" done @@ -1492,7 +1472,7 @@ case $ac_configure_args_raw in ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. ac_unsafe_a="$ac_unsafe_z#~" ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" - ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; + ac_configure_args_raw=` printf '%s\n' "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; esac cat >config.log <<_ACEOF @@ -1500,7 +1480,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by pgactive $as_me pgactive-next, which was -generated by GNU Autoconf 2.71. Invocation command line was +generated by GNU Autoconf 2.73. Invocation command line was $ $0$ac_configure_args_raw @@ -1540,7 +1520,7 @@ do */) ;; *) as_dir=$as_dir/ ;; esac - printf "%s\n" "PATH: $as_dir" + printf '%s\n' "PATH: $as_dir" done IFS=$as_save_IFS @@ -1575,7 +1555,7 @@ do | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) - ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + ac_arg=`printf '%s\n' "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; @@ -1604,31 +1584,22 @@ done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} -# When interrupted or exit'd, cleanup temporary files, and complete -# config.log. We remove comments because anyway the quotes in there -# would cause problems or look ugly. -# WARNING: Use '\'' to represent an apostrophe within the trap. -# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. -trap 'exit_status=$? - # Sanitize IFS. - IFS=" "" $as_nl" - # Save into config.log some information that might help in debugging. - { - echo - - printf "%s\n" "## ---------------- ## -## Cache variables. ## -## ---------------- ##" - echo - # The following way of writing the cache mishandles newlines in values, +# Dump the cache to stdout. It can be in a pipe (this is a requirement). +ac_cache_dump () +{ + # The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. ( - for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( - *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + *_cv_*) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +printf '%s\n' "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( @@ -1637,67 +1608,95 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} esac ;; esac done + (set) 2>&1 | - case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) + # 'set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ - "s/'\''/'\''\\\\'\'''\''/g; - s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) + # 'set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) - echo +} + +# Print debugging info to stdout. +ac_dump_debugging_info () +{ + echo + + printf '%s\n' "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + ac_cache_dump + echo - printf "%s\n" "## ----------------- ## + printf '%s\n' "## ----------------- ## ## Output variables. ## ## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'*) ac_val=`printf '%s\n' "$ac_val" | sed "s/'/'\\\\\\\\''/g"`;; + esac + printf '%s\n' "$ac_var='$ac_val'" + done | sort + echo + + if test -n "$ac_subst_files"; then + printf '%s\n' "## ------------------- ## +## File substitutions. ## +## ------------------- ##" echo - for ac_var in $ac_subst_vars + for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in - *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + *\'*) ac_val=`printf '%s\n' "$ac_val" | sed "s/'/'\\\\\\\\''/g"`;; esac - printf "%s\n" "$ac_var='\''$ac_val'\''" + printf '%s\n' "$ac_var='$ac_val'" done | sort echo + fi - if test -n "$ac_subst_files"; then - printf "%s\n" "## ------------------- ## -## File substitutions. ## -## ------------------- ##" - echo - for ac_var in $ac_subst_files - do - eval ac_val=\$$ac_var - case $ac_val in - *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; - esac - printf "%s\n" "$ac_var='\''$ac_val'\''" - done | sort - echo - fi - - if test -s confdefs.h; then - printf "%s\n" "## ----------- ## + if test -s confdefs.h; then + printf '%s\n' "## ----------- ## ## confdefs.h. ## ## ----------- ##" - echo - cat confdefs.h - echo - fi - test "$ac_signal" != 0 && - printf "%s\n" "$as_me: caught signal $ac_signal" - printf "%s\n" "$as_me: exit $exit_status" - } >&5 - rm -f core *.core core.conftest.* && + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + printf '%s\n' "$as_me: caught signal $ac_signal" + printf '%s\n' "$as_me: exit $exit_status" +} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. +ac_exit_trap () +{ + exit_status= + # Sanitize IFS. + IFS=" "" $as_nl" + # Save into config.log some information that might help in debugging. + ac_dump_debugging_info >&5 + eval "rm -f $ac_clean_CONFIG_STATUS core *.core core.conftest.*" && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status -' 0 +} + +trap 'ac_exit_trap $?' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done @@ -1706,21 +1705,21 @@ ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h -printf "%s\n" "/* confdefs.h */" > confdefs.h +printf '%s\n' "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. -printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h +printf '%s\n' "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h -printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h +printf '%s\n' "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h -printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h +printf '%s\n' "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h -printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h +printf '%s\n' "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h -printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h +printf '%s\n' "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h -printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h +printf '%s\n' "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h # Let the site file select an alternate cache file if it wants to. @@ -1742,14 +1741,14 @@ do ac_site_file=./$ac_site_file ;; esac if test -f "$ac_site_file" && test -r "$ac_site_file"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 -printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +printf '%s\n' "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ - || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + || { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } fi done @@ -1757,27 +1756,120 @@ if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 -printf "%s\n" "$as_me: loading cache $cache_file" >&6;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +printf '%s\n' "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 -printf "%s\n" "$as_me: creating cache $cache_file" >&6;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +printf '%s\n' "$as_me: creating cache $cache_file" >&6;} >$cache_file fi +# Test code for whether the C compiler supports C23 (global declarations) +ac_c_conftest_c23_globals=' +/* Does the compiler advertise conformance to C17 or earlier? + Although GCC 14 does not do that, even with -std=gnu23, + it is close enough, and defines __STDC_VERSION == 202000L. */ +#if !defined __STDC_VERSION__ || __STDC_VERSION__ <= 201710L +# error "Compiler advertises conformance to C17 or earlier" +#endif + +// Check alignas. +char alignas (double) c23_aligned_as_double; +char alignas (0) c23_no_special_alignment; +extern char c23_aligned_as_int; +char alignas (0) alignas (int) c23_aligned_as_int; + +// Check alignof. +enum +{ + c23_int_alignment = alignof (int), + c23_int_array_alignment = alignof (int[100]), + c23_char_alignment = alignof (char) +}; +static_assert (0 < -alignof (int), "alignof is signed"); + +int function_with_unnamed_parameter (int) { return 0; } + +void c23_noreturn (); + +/* Test parsing of string and char UTF-8 literals (including hex escapes). + The parens pacify GCC 15. */ +bool use_u8 = (!sizeof u8"\xFF") == (!u8'\''x'\''); + +bool check_that_bool_works = true | false | !nullptr; +#if !true +# error "true does not work in #if" +#endif +#if false +#elifdef __STDC_VERSION__ +#else +# error "#elifdef does not work" +#endif + +#ifndef __has_c_attribute +# error "__has_c_attribute not defined" +#endif + +#ifndef __has_include +# error "__has_include not defined" +#endif + +#define LPAREN() ( +#define FORTY_TWO(x) 42 +#define VA_OPT_TEST(r, x, ...) __VA_OPT__ (FORTY_TWO r x)) +static_assert (VA_OPT_TEST (LPAREN (), 0, <:-) == 42); + +static_assert (0b101010 == 42); +static_assert (0B101010 == 42); +static_assert (0xDEAD'\''BEEF == 3'\''735'\''928'\''559); +static_assert (0.500'\''000'\''000 == 0.5); + +enum unsignedish : unsigned int { uione = 1 }; +static_assert (0 < -uione); + +#include +constexpr nullptr_t null_pointer = nullptr; + +static typeof (1 + 1L) two () { return 2; } +static long int three () { return 3; } +' + +# Test code for whether the C compiler supports C23 (body of main). +ac_c_conftest_c23_main=' + { + label_before_declaration: + int arr[10] = {}; + if (arr[0]) + goto label_before_declaration; + if (!arr[0]) + goto label_at_end_of_block; + label_at_end_of_block: + } + ok |= !null_pointer; + ok |= two != three; +' + +# Test code for whether the C compiler supports C23 (complete). +ac_c_conftest_c23_program="${ac_c_conftest_c23_globals} + +int +main (int, char **) +{ + int ok = 0; + ${ac_c_conftest_c23_main} + return ok; +} +" + # Test code for whether the C compiler supports C89 (global declarations) ac_c_conftest_c89_globals=' -/* Does the compiler advertise C89 conformance? - Do not test the value of __STDC__, because some compilers set it to 0 - while being otherwise adequately conformant. */ -#if !defined __STDC__ -# error "Compiler does not advertise C89 conformance" -#endif +/* Do not test the value of __STDC__, because some compilers define it to 0 + or do not define it, while otherwise adequately conforming. */ #include #include @@ -1785,9 +1877,7 @@ struct stat; /* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ struct buf { int x; }; struct buf * (*rcsopen) (struct buf *, struct stat *, int); -static char *e (p, i) - char **p; - int i; +static char *e (char **p, int i) { return p[i]; } @@ -1801,6 +1891,21 @@ static char *f (char * (*g) (char **, int), char **p, ...) return s; } +/* C89 style stringification. */ +#define noexpand_stringify(a) #a +const char *stringified = noexpand_stringify(arbitrary+token=sequence); + +/* C89 style token pasting. Exercises some of the corner cases that + e.g. old MSVC gets wrong, but not very hard. */ +#define noexpand_concat(a,b) a##b +#define expand_concat(a,b) noexpand_concat(a,b) +extern int vA; +extern int vbee; +#define aye A +#define bee B +int *pvA = &expand_concat(v,aye); +int *pvbee = &noexpand_concat(v,bee); + /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not \xHH hex character constants. These do not provoke an error unfortunately, instead are silently treated @@ -1828,20 +1933,24 @@ ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); # Test code for whether the C compiler supports C99 (global declarations) ac_c_conftest_c99_globals=' -// Does the compiler advertise C99 conformance? +/* Does the compiler advertise C99 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L # error "Compiler does not advertise C99 conformance" #endif +// See if C++-style comments work. + #include extern int puts (const char *); extern int printf (const char *, ...); extern int dprintf (int, const char *, ...); extern void *malloc (size_t); +extern void free (void *); // Check varargs macros. These examples are taken from C99 6.10.3.5. // dprintf is used instead of fprintf to avoid needing to declare -// FILE and stderr. +// FILE and stderr, and "aND" is used instead of "and" to work around +// GCC bug 40564 which is irrelevant here. #define debug(...) dprintf (2, __VA_ARGS__) #define showlist(...) puts (#__VA_ARGS__) #define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) @@ -1852,7 +1961,7 @@ test_varargs_macros (void) int y = 5678; debug ("Flag"); debug ("X = %d\n", x); - showlist (The first, second, and third items.); + showlist (The first, second, aND third items.); report (x>y, "x is %d but y is %d", x, y); } @@ -1887,7 +1996,6 @@ typedef const char *ccp; static inline int test_restrict (ccp restrict text) { - // See if C++-style comments work. // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) @@ -1941,18 +2049,20 @@ ac_c_conftest_c99_main=' // Check restrict. if (test_restrict ("String literal") == 0) success = true; - char *restrict newvar = "Another string"; + const char *restrict newvar = "Another string"; // Check varargs. success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); test_varargs_macros (); // Check flexible array members. - struct incomplete_array *ia = - malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10)); + static struct incomplete_array *volatile incomplete_array_pointer; + struct incomplete_array *ia = incomplete_array_pointer; ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; + // Work around memory leak warnings. + free (ia); // Check named initializers. struct named_init ni = { @@ -1963,18 +2073,17 @@ ac_c_conftest_c99_main=' ni.number = 58; - int dynamic_array[ni.number]; - dynamic_array[0] = argv[0][0]; - dynamic_array[ni.number - 1] = 543; + // Do not test for VLAs, as some otherwise-conforming compilers lack them. + // C code should instead use __STDC_NO_VLA__; see Autoconf manual. // work around unused variable warnings ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' - || dynamic_array[ni.number - 1] != 543); + || ni.number != 58); ' # Test code for whether the C compiler supports C11 (global declarations) ac_c_conftest_c11_globals=' -// Does the compiler advertise C11 conformance? +/* Does the compiler advertise C11 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L # error "Compiler does not advertise C11 conformance" #endif @@ -2088,38 +2197,44 @@ for ac_var in $ac_precious_vars; do eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 -printf "%s\n" "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 +printf '%s\n' "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 -printf "%s\n" "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 +printf '%s\n' "$as_me: error: '$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. - ac_old_val_w=`echo x $ac_old_val` - ac_new_val_w=`echo x $ac_new_val` + ac_old_val_w= + for ac_val in x $ac_old_val; do + ac_old_val_w="$ac_old_val_w $ac_val" + done + ac_new_val_w= + for ac_val in x $ac_new_val; do + ac_new_val_w="$ac_new_val_w $ac_val" + done if test "$ac_old_val_w" != "$ac_new_val_w"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 -printf "%s\n" "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 +printf '%s\n' "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 -printf "%s\n" "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 +printf '%s\n' "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 -printf "%s\n" "$as_me: former value: \`$ac_old_val'" >&2;} - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 -printf "%s\n" "$as_me: current value: \`$ac_new_val'" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 +printf '%s\n' "$as_me: former value: '$ac_old_val'" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 +printf '%s\n' "$as_me: current value: '$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in - *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *\'*) ac_arg=$ac_var=`printf '%s\n' "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in @@ -2129,17 +2244,34 @@ printf "%s\n" "$as_me: current value: \`$ac_new_val'" >&2;} fi done if $ac_cache_corrupted; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 -printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} - as_fn_error $? "run \`${MAKE-make} distclean' and/or \`rm $cache_file' + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +printf '%s\n' "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## + +# Determine whether it's possible to make 'echo' print without a newline. +# These variables are no longer used directly by Autoconf, but are AC_SUBSTed +# for compatibility with existing Makefiles. +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' @@ -2156,18 +2288,19 @@ configure_args=$ac_configure_args if test ${enable_pgactive+y} then : enableval=$enable_pgactive; -else $as_nop - enable_pgactive=auto +else case e in #( + e) enable_pgactive=auto ;; +esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 printf %s "checking for a sed that does not truncate output... " >&6; } if test ${ac_cv_path_SED+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ +else case e in #( + e) ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ for ac_i in 1 2 3 4 5 6 7; do ac_script="$ac_script$as_nl$ac_script" done @@ -2192,9 +2325,10 @@ do as_fn_executable_p "$ac_path_SED" || continue # Check for GNU ac_path_SED and select it if it is found. # Check for GNU $ac_path_SED -case `"$ac_path_SED" --version 2>&1` in +case `"$ac_path_SED" --version 2>&1` in #( *GNU*) ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; +#( *) ac_count=0 printf %s 0123456789 >"conftest.in" @@ -2203,7 +2337,7 @@ case `"$ac_path_SED" --version 2>&1` in cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" - printf "%s\n" '' >> "conftest.nl" + printf '%s\n' '' >> "conftest.nl" "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val @@ -2229,10 +2363,11 @@ IFS=$as_save_IFS else ac_cv_path_SED=$SED fi - + ;; +esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 -printf "%s\n" "$ac_cv_path_SED" >&6; } +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 +printf '%s\n' "$ac_cv_path_SED" >&6; } SED="$ac_cv_path_SED" rm -f conftest.sed @@ -2242,13 +2377,13 @@ printf "%s\n" "$ac_cv_path_SED" >&6; } if test -z "$PG_CONFIG"; then # Extract the first word of "pg_config", so it can be a program name with args. set dummy pg_config; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_PG_CONFIG+y} then : printf %s "(cached) " >&6 -else $as_nop - case $PG_CONFIG in +else case e in #( + e) case $PG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PG_CONFIG="$PG_CONFIG" # Let the user override the test with a path. ;; @@ -2265,7 +2400,7 @@ do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_PG_CONFIG="$as_dir$ac_word$ac_exec_ext" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done @@ -2273,15 +2408,16 @@ done IFS=$as_save_IFS ;; +esac ;; esac fi PG_CONFIG=$ac_cv_path_PG_CONFIG if test -n "$PG_CONFIG"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PG_CONFIG" >&5 -printf "%s\n" "$PG_CONFIG" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $PG_CONFIG" >&5 +printf '%s\n' "$PG_CONFIG" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } fi @@ -2299,8 +2435,8 @@ if test -z "$version_num"; then as_fn_error $? "could not detect the PostgreSQL version, wrong or broken pg_config?" "$LINENO" 5 fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: building against PostgreSQL $version_num" >&5 -printf "%s\n" "$as_me: building against PostgreSQL $version_num" >&6;} +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: building against PostgreSQL $version_num" >&5 +printf '%s\n' "$as_me: building against PostgreSQL $version_num" >&6;} # Even though pgactive defines MAJORVERSION for this, we need access to it # early in Makefile, so set it directly. pgactive_PG_MAJORVERSION="$version_num" @@ -2320,6 +2456,9 @@ CPPFLAGS="-I$($PG_CONFIG --includedir-server) $CFLAGS" + + + ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' @@ -2328,13 +2467,13 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -2349,22 +2488,23 @@ do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } fi @@ -2373,13 +2513,13 @@ if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$ac_ct_CC"; then +else case e in #( + e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -2394,22 +2534,23 @@ do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi +fi ;; +esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 -printf "%s\n" "$ac_ct_CC" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +printf '%s\n' "$ac_ct_CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } fi if test "x$ac_ct_CC" = x; then @@ -2417,8 +2558,8 @@ fi else case $cross_compiling:$ac_tool_warned in yes:) -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC @@ -2431,13 +2572,13 @@ if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -2452,22 +2593,23 @@ do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } fi @@ -2476,13 +2618,13 @@ fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no @@ -2502,7 +2644,7 @@ do continue fi ac_cv_prog_CC="cc" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done @@ -2521,15 +2663,16 @@ if test $ac_prog_rejected = yes; then ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" fi fi -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } fi @@ -2540,13 +2683,13 @@ if test -z "$CC"; then do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -2561,22 +2704,23 @@ do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } fi @@ -2589,13 +2733,13 @@ if test -z "$CC"; then do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$ac_ct_CC"; then +else case e in #( + e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -2610,22 +2754,23 @@ do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi +fi ;; +esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 -printf "%s\n" "$ac_ct_CC" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +printf '%s\n' "$ac_ct_CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } fi @@ -2637,8 +2782,8 @@ done else case $cross_compiling:$ac_tool_warned in yes:) -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC @@ -2650,13 +2795,13 @@ if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. set dummy ${ac_tool_prefix}clang; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$CC"; then +else case e in #( + e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -2671,22 +2816,23 @@ do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}clang" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi +fi ;; +esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -printf "%s\n" "$CC" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } fi @@ -2695,13 +2841,13 @@ if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "clang", so it can be a program name with args. set dummy clang; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 -else $as_nop - if test -n "$ac_ct_CC"; then +else case e in #( + e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR @@ -2716,22 +2862,23 @@ do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="clang" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi +fi ;; +esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 -printf "%s\n" "$ac_ct_CC" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +printf '%s\n' "$ac_ct_CC" >&6; } else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } fi if test "x$ac_ct_CC" = x; then @@ -2739,8 +2886,8 @@ fi else case $cross_compiling:$ac_tool_warned in yes:) -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC @@ -2752,13 +2899,13 @@ fi fi -test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +test -z "$CC" && { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. -printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion -version; do @@ -2768,7 +2915,7 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then @@ -2778,7 +2925,7 @@ printf "%s\n" "$ac_try_echo"; } >&5 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done @@ -2798,9 +2945,9 @@ ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 printf %s "checking whether the C compiler works... " >&6; } -ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'` +ac_link_default=`printf '%s\n' "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" @@ -2821,14 +2968,14 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : - # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. -# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' + # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. +# So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. @@ -2848,7 +2995,7 @@ do ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not - # safe: cross compilers may not add the suffix if given an `-o' + # safe: cross compilers may not add the suffix if given an '-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. @@ -2859,33 +3006,35 @@ do done test "$ac_cv_exeext" = no && ac_cv_exeext= -else $as_nop - ac_file='' +else case e in #( + e) ac_file='' ;; +esac fi if test -z "$ac_file" then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -printf "%s\n" "$as_me: failed program was:" >&5 + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +printf '%s\n' "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +{ { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables -See \`config.log' for more details" "$LINENO" 5; } -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } +See 'config.log' for more details" "$LINENO" 5; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf '%s\n' "yes" >&6; } ;; +esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 printf %s "checking for C compiler default output file name... " >&6; } -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 -printf "%s\n" "$ac_file" >&6; } +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +printf '%s\n' "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 printf %s "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in @@ -2893,16 +3042,16 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : - # If both `conftest.exe' and `conftest' are `present' (well, observable) -# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will -# work properly (i.e., refer to `conftest.exe'), while it won't with -# `rm'. + # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) +# catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will +# work properly (i.e., refer to 'conftest.exe'), while it won't with +# 'rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in @@ -2912,15 +3061,16 @@ for ac_file in conftest.exe conftest conftest.*; do * ) break;; esac done -else $as_nop - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +else case e in #( + e) { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } ;; +esac fi rm -f conftest conftest$ac_cv_exeext -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 -printf "%s\n" "$ac_cv_exeext" >&6; } +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +printf '%s\n' "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext @@ -2932,6 +3082,8 @@ int main (void) { FILE *f = fopen ("conftest.out", "w"); + if (!f) + return 1; return ferror (f) || fclose (f) != 0; ; @@ -2941,7 +3093,7 @@ _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 printf %s "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" @@ -2950,10 +3102,10 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in @@ -2961,36 +3113,37 @@ printf "%s\n" "$ac_try_echo"; } >&5 *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot run C compiled programs. -If you meant to cross compile, use \`--host'. -See \`config.log' for more details" "$LINENO" 5; } +If you meant to cross compile, use '--host'. +See 'config.log' for more details" "$LINENO" 5; } fi fi fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 -printf "%s\n" "$cross_compiling" >&6; } +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +printf '%s\n' "$cross_compiling" >&6; } -rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +rm -f conftest.$ac_ext conftest$ac_cv_exeext \ + conftest.o conftest.obj conftest.out ac_clean_files=$ac_clean_files_save -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 printf %s "checking for suffix of object files... " >&6; } if test ${ac_cv_objext+y} then : printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int @@ -3008,10 +3161,10 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -printf "%s\n" "$ac_try_echo"; } >&5 +printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : for ac_file in conftest.o conftest.obj conftest.*; do @@ -3022,28 +3175,30 @@ then : break;; esac done -else $as_nop - printf "%s\n" "$as_me: failed program was:" >&5 +else case e in #( + e) printf '%s\n' "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} +{ { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } ;; +esac fi -rm -f conftest.$ac_cv_objext conftest.$ac_ext +rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; +esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 -printf "%s\n" "$ac_cv_objext" >&6; } +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +printf '%s\n' "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 printf %s "checking whether the compiler supports GNU C... " >&6; } if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int @@ -3060,15 +3215,17 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes -else $as_nop - ac_compiler_gnu=no +else case e in #( + e) ac_compiler_gnu=no ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu - + ;; +esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 -printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; } +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +printf '%s\n' "$ac_cv_c_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_c_compiler_gnu if test $ac_compiler_gnu = yes; then @@ -3078,13 +3235,13 @@ else fi ac_test_CFLAGS=${CFLAGS+y} ac_save_CFLAGS=$CFLAGS -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 printf %s "checking whether $CC accepts -g... " >&6; } if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_save_c_werror_flag=$ac_c_werror_flag +else case e in #( + e) ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" @@ -3102,8 +3259,8 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes -else $as_nop - CFLAGS="" +else case e in #( + e) CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3118,8 +3275,8 @@ _ACEOF if ac_fn_c_try_compile "$LINENO" then : -else $as_nop - ac_c_werror_flag=$ac_save_c_werror_flag +else case e in #( + e) ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3136,15 +3293,18 @@ if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext - ac_c_werror_flag=$ac_save_c_werror_flag + ac_c_werror_flag=$ac_save_c_werror_flag ;; +esac fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 -printf "%s\n" "$ac_cv_prog_cc_g" >&6; } +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +printf '%s\n' "$ac_cv_prog_cc_g" >&6; } if test $ac_test_CFLAGS; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then @@ -3163,19 +3323,68 @@ fi ac_prog_cc_stdc=no if test x$ac_prog_cc_stdc = xno then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C23 features" >&5 +printf %s "checking for $CC option to enable C23 features... " >&6; } +if test ${ac_cv_prog_cc_c23+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cc_c23=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_c_conftest_c23_program +_ACEOF +for ac_arg in '' -std=gnu23 +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_c23=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cc_c23" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC ;; +esac +fi + +if test "x$ac_cv_prog_cc_c23" = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf '%s\n' "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cc_c23" = x +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf '%s\n' "none needed" >&6; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c23" >&5 +printf '%s\n' "$ac_cv_prog_cc_c23" >&6; } + CC="$CC $ac_cv_prog_cc_c23" ;; +esac +fi + ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c23 + ac_prog_cc_stdc=c23 ;; +esac +fi +fi +if test x$ac_prog_cc_stdc = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 printf %s "checking for $CC option to enable C11 features... " >&6; } if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_cv_prog_cc_c11=no +else case e in #( + e) ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c11_program _ACEOF -for ac_arg in '' -std=gnu11 +for ac_arg in '' -std=gnu11 -std:c11 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" @@ -3186,36 +3395,39 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext -CC=$ac_save_CC +CC=$ac_save_CC ;; +esac fi if test "x$ac_cv_prog_cc_c11" = xno then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 -printf "%s\n" "unsupported" >&6; } -else $as_nop - if test "x$ac_cv_prog_cc_c11" = x + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf '%s\n' "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cc_c11" = x then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 -printf "%s\n" "none needed" >&6; } -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 -printf "%s\n" "$ac_cv_prog_cc_c11" >&6; } - CC="$CC $ac_cv_prog_cc_c11" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf '%s\n' "none needed" >&6; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 +printf '%s\n' "$ac_cv_prog_cc_c11" >&6; } + CC="$CC $ac_cv_prog_cc_c11" ;; +esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 - ac_prog_cc_stdc=c11 + ac_prog_cc_stdc=c11 ;; +esac fi fi if test x$ac_prog_cc_stdc = xno then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 printf %s "checking for $CC option to enable C99 features... " >&6; } if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_cv_prog_cc_c99=no +else case e in #( + e) ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3232,36 +3444,39 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext -CC=$ac_save_CC +CC=$ac_save_CC ;; +esac fi if test "x$ac_cv_prog_cc_c99" = xno then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 -printf "%s\n" "unsupported" >&6; } -else $as_nop - if test "x$ac_cv_prog_cc_c99" = x + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf '%s\n' "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cc_c99" = x then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 -printf "%s\n" "none needed" >&6; } -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 -printf "%s\n" "$ac_cv_prog_cc_c99" >&6; } - CC="$CC $ac_cv_prog_cc_c99" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf '%s\n' "none needed" >&6; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 +printf '%s\n' "$ac_cv_prog_cc_c99" >&6; } + CC="$CC $ac_cv_prog_cc_c99" ;; +esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 - ac_prog_cc_stdc=c99 + ac_prog_cc_stdc=c99 ;; +esac fi fi if test x$ac_prog_cc_stdc = xno then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 printf %s "checking for $CC option to enable C89 features... " >&6; } if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6 -else $as_nop - ac_cv_prog_cc_c89=no +else case e in #( + e) ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3278,25 +3493,28 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext -CC=$ac_save_CC +CC=$ac_save_CC ;; +esac fi if test "x$ac_cv_prog_cc_c89" = xno then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 -printf "%s\n" "unsupported" >&6; } -else $as_nop - if test "x$ac_cv_prog_cc_c89" = x + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf '%s\n' "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cc_c89" = x then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 -printf "%s\n" "none needed" >&6; } -else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 -printf "%s\n" "$ac_cv_prog_cc_c89" >&6; } - CC="$CC $ac_cv_prog_cc_c89" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf '%s\n' "none needed" >&6; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +printf '%s\n' "$ac_cv_prog_cc_c89" >&6; } + CC="$CC $ac_cv_prog_cc_c89" ;; +esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 - ac_prog_cc_stdc=c89 + ac_prog_cc_stdc=c89 ;; +esac fi fi @@ -3312,7 +3530,7 @@ ac_fn_c_check_header_compile "$LINENO" "replication/replication_identifier.h" "a " if test "x$ac_cv_header_replication_replication_identifier_h" = xyes then : - printf "%s\n" "#define HAVE_REPLICATION_REPLICATION_IDENTIFIER_H 1" >>confdefs.h + printf '%s\n' "#define HAVE_REPLICATION_REPLICATION_IDENTIFIER_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "replication/origin.h" "ac_cv_header_replication_origin_h" "#include \"postgres.h\" @@ -3320,7 +3538,7 @@ ac_fn_c_check_header_compile "$LINENO" "replication/origin.h" "ac_cv_header_repl " if test "x$ac_cv_header_replication_origin_h" = xyes then : - printf "%s\n" "#define HAVE_REPLICATION_ORIGIN_H 1" >>confdefs.h + printf '%s\n' "#define HAVE_REPLICATION_ORIGIN_H 1" >>confdefs.h fi @@ -3330,7 +3548,7 @@ ac_fn_c_check_header_compile "$LINENO" "access/committs.h" "ac_cv_header_access_ " if test "x$ac_cv_header_access_committs_h" = xyes then : - printf "%s\n" "#define HAVE_ACCESS_COMMITTS_H 1" >>confdefs.h + printf '%s\n' "#define HAVE_ACCESS_COMMITTS_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "access/commit_ts.h" "ac_cv_header_access_commit_ts_h" "#include \"postgres.h\" @@ -3338,15 +3556,15 @@ ac_fn_c_check_header_compile "$LINENO" "access/commit_ts.h" "ac_cv_header_access " if test "x$ac_cv_header_access_commit_ts_h" = xyes then : - printf "%s\n" "#define HAVE_ACCESS_COMMIT_TS_H 1" >>confdefs.h + printf '%s\n' "#define HAVE_ACCESS_COMMIT_TS_H 1" >>confdefs.h fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: building with PostgreSQL $version_num" >&5 -printf "%s\n" "$as_me: building with PostgreSQL $version_num" >&6;} +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: building with PostgreSQL $version_num" >&5 +printf '%s\n' "$as_me: building with PostgreSQL $version_num" >&6;} pgactive_PGVERCOMPAT_INCDIR="compat/$version_num" @@ -3377,50 +3595,13 @@ cat >confcache <<\_ACEOF # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # -# `ac_cv_env_foo' variables (set or unset) will be overridden when -# loading this file, other *unset* `ac_cv_foo' will be assigned the +# 'ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* 'ac_cv_foo' will be assigned the # following values. _ACEOF -# The following way of writing the cache mishandles newlines in values, -# but we know of no workaround that is simple, portable, and efficient. -# So, we kill variables containing newlines. -# Ultrix sh set writes to stderr and can't be redirected directly, -# and sets the high bit in the cache file unless we assign to the vars. -( - for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do - eval ac_val=\$$ac_var - case $ac_val in #( - *${as_nl}*) - case $ac_var in #( - *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; - esac - case $ac_var in #( - _ | IFS | as_nl) ;; #( - BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( - *) { eval $ac_var=; unset $ac_var;} ;; - esac ;; - esac - done - - (set) 2>&1 | - case $as_nl`(ac_space=' '; set) 2>&1` in #( - *${as_nl}ac_space=\ *) - # `set' does not quote correctly, so add quotes: double-quote - # substitution turns \\\\ into \\, and sed turns \\ into \. - sed -n \ - "s/'/'\\\\''/g; - s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" - ;; #( - *) - # `set' quotes correctly as required by POSIX, so do not add quotes. - sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" - ;; - esac | - sort -) | +ac_cache_dump | sed ' /^ac_cv_env_/b end t clear @@ -3432,8 +3613,8 @@ printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 -printf "%s\n" "$as_me: updating cache $cache_file" >&6;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +printf '%s\n' "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else @@ -3447,8 +3628,8 @@ printf "%s\n" "$as_me: updating cache $cache_file" >&6;} fi fi else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 -printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +printf '%s\n' "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache @@ -3465,7 +3646,7 @@ U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' - ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"` + ac_i=`printf '%s\n' "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" @@ -3478,13 +3659,21 @@ LTLIBOBJS=$ac_ltlibobjs : "${CONFIG_STATUS=./config.status}" +case $CONFIG_STATUS in #( + -*) : + CONFIG_STATUS=./$CONFIG_STATUS ;; #( + */*) : + ;; #( + *) : + CONFIG_STATUS=./$CONFIG_STATUS ;; +esac + ac_write_fail=0 -ac_clean_files_save=$ac_clean_files -ac_clean_files="$ac_clean_files $CONFIG_STATUS" -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 -printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} +ac_clean_CONFIG_STATUS='"$CONFIG_STATUS"' +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +printf '%s\n' "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 -cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +cat >"$CONFIG_STATUS" <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. @@ -3498,28 +3687,28 @@ ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF -cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +cat >>"$CONFIG_STATUS" <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which - # is contrary to our usage. Disable this feature. + # contradicts POSIX and common usage. Disable this. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else $as_nop - case `(set -o) 2>/dev/null` in #( +else case e in #( + e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; +esac ;; esac fi @@ -3591,13 +3780,13 @@ IFS=$as_save_IFS ;; esac -# We did not find ourselves, most probably we were run as `sh COMMAND' +# We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then - printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + printf '%s\n' "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi @@ -3613,14 +3802,13 @@ as_fn_error () as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi - printf "%s\n" "$as_me: error: $2" >&2 + printf '%s\n' "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error - # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. @@ -3660,11 +3848,12 @@ then : { eval $1+=\$2 }' -else $as_nop - as_fn_append () +else case e in #( + e) as_fn_append () { eval $1=\$$1\$2 - } + } ;; +esac fi # as_fn_append # as_fn_arith ARG... @@ -3678,11 +3867,12 @@ then : { as_val=$(( $* )) }' -else $as_nop - as_fn_arith () +else case e in #( + e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` - } + } ;; +esac fi # as_fn_arith @@ -3709,7 +3899,7 @@ as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X/"$0" | +printf '%s\n' X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q @@ -3731,29 +3921,6 @@ as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits - -# Determine whether it's possible to make 'echo' print without a newline. -# These variables are no longer used directly by Autoconf, but are AC_SUBSTed -# for compatibility with existing Makefiles. -ECHO_C= ECHO_N= ECHO_T= -case `echo -n x` in #((((( --n*) - case `echo 'xy\c'` in - *c*) ECHO_T=' ';; # ECHO_T is single tab character. - xy) ECHO_C='\c';; - *) echo `echo ksh88 bug on AIX 6.1` > /dev/null - ECHO_T=' ';; - esac;; -*) - ECHO_N='-n';; -esac - -# For backward compatibility with old third-party macros, we provide -# the shell variables $as_echo and $as_echo_n. New code should use -# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. -as_echo='printf %s\n' -as_echo_n='printf %s' - rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file @@ -3765,9 +3932,9 @@ if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: - # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -pR'. + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then @@ -3795,7 +3962,7 @@ as_fn_mkdir_p () as_dirs= while :; do case $as_dir in #( - *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *\'*) as_qdir=`printf '%s\n' "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" @@ -3804,7 +3971,7 @@ $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$as_dir" | +printf '%s\n' X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -3848,26 +4015,28 @@ as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. -as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. -as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated exec 6>&1 -## ----------------------------------- ## -## Main body of $CONFIG_STATUS script. ## -## ----------------------------------- ## +## ------------------------------------- ## +## Main body of "$CONFIG_STATUS" script. ## +## ------------------------------------- ## _ASEOF -test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 +test $as_write_fail = 0 && chmod +x "$CONFIG_STATUS" || ac_write_fail=1 -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by pgactive $as_me pgactive-next, which was -generated by GNU Autoconf 2.71. Invocation command line was +generated by GNU Autoconf 2.73. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS @@ -3889,16 +4058,16 @@ case $ac_config_headers in *" esac -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ -\`$as_me' instantiates files and other configuration actions +'$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. @@ -3926,25 +4095,29 @@ Report bugs to . pgactive home page: ." _ACEOF -ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` -ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config=`printf '%s\n' "$ac_configure_args" | sed "$ac_safe_unquote"` +ac_cs_config_escaped=`printf '%s\n' "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ pgactive config.status pgactive-next -configured by $0, generated by GNU Autoconf 2.71, +configured by $0, generated by GNU Autoconf 2.73, with options \\"\$ac_cs_config\\" -Copyright (C) 2021 Free Software Foundation, Inc. +Copyright (C) 2026 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' -test -n "\$AWK" || AWK=awk +test -n "\$AWK" || { + awk '' >$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 @@ -3972,15 +4145,15 @@ do -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) - printf "%s\n" "$ac_cs_version"; exit ;; + printf '%s\n' "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) - printf "%s\n" "$ac_cs_config"; exit ;; + printf '%s\n' "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in - *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + *\'*) ac_optarg=`printf '%s\n' "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" @@ -3988,23 +4161,23 @@ do --header | --heade | --head | --hea ) $ac_shift case $ac_optarg in - *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + *\'*) ac_optarg=`printf '%s\n' "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append CONFIG_HEADERS " '$ac_optarg'" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header - as_fn_error $? "ambiguous option: \`$1' -Try \`$0 --help' for more information.";; + as_fn_error $? "ambiguous option: '$1' +Try '$0 --help' for more information.";; --help | --hel | -h ) - printf "%s\n" "$ac_cs_usage"; exit ;; + printf '%s\n' "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. - -*) as_fn_error $? "unrecognized option: \`$1' -Try \`$0 --help' for more information." ;; + -*) as_fn_error $? "unrecognized option: '$1' +Try '$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; @@ -4021,32 +4194,32 @@ if $ac_cs_silent; then fi _ACEOF -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift - \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 + \printf '%s\n' "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX - printf "%s\n" "$ac_log" + printf '%s\n' "$ac_log" } >&5 _ACEOF -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets @@ -4056,7 +4229,7 @@ do "test/run_tests") CONFIG_FILES="$CONFIG_FILES test/run_tests" ;; "include/pgactive_config_generated.h") CONFIG_HEADERS="$CONFIG_HEADERS include/pgactive_config_generated.h" ;; - *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; esac done @@ -4075,7 +4248,7 @@ fi # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: -# after its creation but before its name has been assigned to `$tmp'. +# after its creation but before its name has been assigned to '$tmp'. $debug || { tmp= ac_tmp= @@ -4099,7 +4272,7 @@ ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. -# This happens for instance with `./config.status config.h'. +# This happens for instance with './config.status config.h'. if test -n "$CONFIG_FILES"; then @@ -4127,13 +4300,13 @@ _ACEOF echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 -ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim_num=`echo "$ac_subst_vars" | sed -n '$='` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 - ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | sed -n '$='` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then @@ -4144,7 +4317,7 @@ for ac_last_try in false false false false false :; do done rm -f conf$$subs.sh -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' @@ -4189,9 +4362,9 @@ t delim N s/\n// } -' >>$CONFIG_STATUS || ac_write_fail=1 +' >>"$CONFIG_STATUS" || ac_write_fail=1 rm -f conf$$subs.awk -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 @@ -4220,7 +4393,7 @@ cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && _ACAWK _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else @@ -4252,18 +4425,18 @@ s/^[^=]*=[ ]*$// }' fi -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. -# This happens for instance with `./config.status Makefile'. +# This happens for instance with './config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF -# Transform confdefs.h into an awk script `defines.awk', embedded as +# Transform confdefs.h into an awk script 'defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. @@ -4323,9 +4496,9 @@ s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p b cont ' >$CONFIG_STATUS || ac_write_fail=1 +"/g' >>"$CONFIG_STATUS" || ac_write_fail=1 -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 for (key in D) D_is_set[key] = 1 FS = "" } @@ -4343,8 +4516,12 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 macro = mac2[1] prefix = substr(line, 1, index(line, defundef) - 1) if (D_is_set[macro]) { + suffix = P[macro] D[macro] + while (suffix ~ /[\t ]$/) { + suffix = substr(suffix, 1, length(suffix) - 1) + } # Preserve the white space surrounding the "#". - print prefix "define", macro P[macro] D[macro] + print prefix "define", macro suffix next } else { # Replace #undef with comments. This is necessary, for example, @@ -4359,7 +4536,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 { print } _ACAWK _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 fi # test -n "$CONFIG_HEADERS" @@ -4373,7 +4550,7 @@ do esac case $ac_mode$ac_tag in :[FHL]*:*);; - :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac @@ -4395,33 +4572,33 @@ do -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, - # because $ac_f cannot contain `:'. + # because $ac_f cannot contain ':'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || - as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; esac - case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + case $ac_f in *\'*) ac_f=`printf '%s\n' "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done - # Let's still pretend it is `configure' which instantiates (i.e., don't + # Let's still pretend it is 'configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` - printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + printf '%s\n' "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 -printf "%s\n" "$as_me: creating $ac_file" >&6;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +printf '%s\n' "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) - ac_sed_conf_input=`printf "%s\n" "$configure_input" | + ac_sed_conf_input=`printf '%s\n' "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac @@ -4438,7 +4615,7 @@ $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$ac_file" | +printf '%s\n' X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -4462,9 +4639,9 @@ printf "%s\n" X"$ac_file" | case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) - ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` + ac_dir_suffix=/`printf '%s\n' "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + ac_top_builddir_sub=`printf '%s\n' "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; @@ -4500,7 +4677,7 @@ ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= @@ -4517,10 +4694,10 @@ ac_sed_dataroot=' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 -printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +printf '%s\n' "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g @@ -4531,14 +4708,14 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 esac _ACEOF -# Neutralize VPATH when `$srcdir' = `.'. +# Neutralize VPATH when '$srcdir' = '.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t @@ -4560,9 +4737,9 @@ test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&5 -printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +printf '%s\n' "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" @@ -4578,20 +4755,20 @@ which seems to be undefined. Please make sure it is defined" >&2;} # if test x"$ac_file" != x-; then { - printf "%s\n" "/* $configure_input */" >&1 \ + printf '%s\n' "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" } >"$ac_tmp/config.h" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 -printf "%s\n" "$as_me: $ac_file is unchanged" >&6;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 +printf '%s\n' "$as_me: $ac_file is unchanged" >&6;} else rm -f "$ac_file" mv "$ac_tmp/config.h" "$ac_file" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 fi else - printf "%s\n" "/* $configure_input */" >&1 \ + printf '%s\n' "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ || as_fn_error $? "could not create -" "$LINENO" 5 fi @@ -4610,7 +4787,7 @@ done # for ac_tag as_fn_exit 0 _ACEOF -ac_clean_files=$ac_clean_files_save +ac_clean_CONFIG_STATUS= test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 @@ -4626,19 +4803,26 @@ test $ac_write_fail = 0 || # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: + case $CONFIG_STATUS in #( + -*) : + ac_no_opts=-- ;; #( + *) : + ac_no_opts= ;; +esac ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null - $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + $SHELL $ac_no_opts "$CONFIG_STATUS" $ac_config_status_args || + ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 -printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +printf '%s\n' "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi diff --git a/test/t/010_pgactive_init_copy.pl b/test/t/010_pgactive_init_copy.pl index 531a035d..6eaa7bab 100644 --- a/test/t/010_pgactive_init_copy.pl +++ b/test/t/010_pgactive_init_copy.pl @@ -122,7 +122,7 @@ SELECT EXISTS ( SELECT 1 FROM pg_class c INNER JOIN pg_namespace n ON n.nspname = 'public' AND c.relname = 'reptest' ); -}); +}) or die "Timed out waiting for reptest table to replicate to node_a"; ok($node_b->safe_psql($pgactive_test_dbname, "SELECT 'reptest'::regclass"), "reptest table creation replicated"); @@ -132,7 +132,7 @@ SELECT EXISTS ( SELECT 1 FROM reptest ); -}); +}) or die "Timed out waiting for reptest insert to replicate to node_b"; is($node_b->safe_psql($pgactive_test_dbname, 'SELECT id, dummy FROM reptest;'), '1|42', "reptest insert successfully replicated"); diff --git a/test/t/011_pgactive_init_copy_extended.pl b/test/t/011_pgactive_init_copy_extended.pl index 884723fa..5955962b 100644 --- a/test/t/011_pgactive_init_copy_extended.pl +++ b/test/t/011_pgactive_init_copy_extended.pl @@ -128,7 +128,7 @@ SELECT EXISTS ( SELECT 1 FROM pg_class c INNER JOIN pg_namespace n ON n.nspname = 'public' AND c.relname = 'reptest' ); -}); +}) or die "Timed out waiting for reptest table to replicate to node_a"; ok($node_b->safe_psql($pgactive_test_dbname, "SELECT 'reptest'::regclass"), "reptest table creation replicated"); @@ -138,7 +138,7 @@ SELECT EXISTS ( SELECT 1 FROM reptest ); -}); +}) or die "Timed out waiting for reptest insert to replicate to node_b"; is($node_b->safe_psql($pgactive_test_dbname, 'SELECT id, dummy FROM reptest;'), '1|42', "reptest insert successfully replicated"); diff --git a/test/t/025_ddl_lock.pl b/test/t/025_ddl_lock.pl index 446d0002..f72359e2 100644 --- a/test/t/025_ddl_lock.pl +++ b/test/t/025_ddl_lock.pl @@ -81,7 +81,8 @@ # we don't acquire. (The local ddl lock is also held on the node that takes the # global ddl lock, but it's inserted in a row that's in an uncommitted xact so # we can't see it from queries; see 2ndQuadrant/pgactive-private#60) -is($node_2->safe_psql($pgactive_test_dbname, q[SELECT state FROM pgactive.pgactive_global_locks]), 'acquired', +# Use poll_query_until because lock propagation to peers is asynchronous. +ok($node_2->poll_query_until($pgactive_test_dbname, q[SELECT state = 'acquired' FROM pgactive.pgactive_global_locks]), 'local DDL lock acquired on node 2'); # No good way to show if requesting node has replies # from all peers. Best we can do is see if pgactive.pgactive_acquire_global_lock(...) @@ -98,7 +99,8 @@ # After the psql session terminates, we'll send a message to peer nodes # to release the DDL lock acquisition attempt. This will take a while, so # poll here. -$node_2->poll_query_until($pgactive_test_dbname, "SELECT NOT EXISTS (SELECT 1 FROM pgactive.pgactive_global_locks WHERE state = 'acquired')"); +$node_2->poll_query_until($pgactive_test_dbname, "SELECT NOT EXISTS (SELECT 1 FROM pgactive.pgactive_global_locks WHERE state = 'acquired')") + or die "Timed out waiting for DDL lock release on node_2"; wait_for_apply($node_0, $node_2); wait_for_apply($node_2, $node_0); diff --git a/test/t/038_apply_delay.pl b/test/t/038_apply_delay.pl index cec13182..9034199b 100644 --- a/test/t/038_apply_delay.pl +++ b/test/t/038_apply_delay.pl @@ -128,7 +128,8 @@ foreach my $node (@{$nodes}) { $node->poll_query_until($pgactive_test_dbname, - qq{SELECT COUNT(*) = 1 FROM pgactive.pgactive_conflict_history WHERE conflict_type = 'delete_delete';}); + qq{SELECT COUNT(*) = 1 FROM pgactive.pgactive_conflict_history WHERE conflict_type = 'delete_delete';}) + or die "Timed out waiting for delete/delete conflict on " . $node->name; } # now, check the expected delete/delete conflict diff --git a/test/t/045_unregister_workers_after_detach.pl b/test/t/045_unregister_workers_after_detach.pl index c4cc0faf..b96b2972 100644 --- a/test/t/045_unregister_workers_after_detach.pl +++ b/test/t/045_unregister_workers_after_detach.pl @@ -8,7 +8,6 @@ use Config; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; -use Time::HiRes qw(usleep); use IPC::Run; use Test::More; use utils::nodemanagement; @@ -25,38 +24,21 @@ my $logstart_0 = get_log_size($node_0); my $logstart_1 = get_log_size($node_1); -# Detached node must unregister apply worker -my $result = find_in_log($node_0, - qr!LOG: ( [A-Z0-9]+:)? unregistering apply worker due to .*!, - $logstart_0); - -# Let's skip if the unregister log message is not detected. Sometimes it may -# happen that the worker might get killed even before unregistering log message -# is hit. -SKIP: { - skip "unregistering apply worker on node_0 is not detected", 1 - if (!$result); - - ok($result, "unregistering apply worker on node_0 is detected"); -} +# Detached node must have no apply workers running after detach. +# Poll worker state directly rather than relying on log messages which may +# not appear if the worker is killed before it can log. +ok($node_0->poll_query_until($pgactive_test_dbname, + qq[SELECT COUNT(*) = 0 FROM pgactive.pgactive_get_workers_info() WHERE worker_type = 'apply';]), + "apply worker on node_0 is gone after detach"); # Remove pgactive from the detached node $node_0->safe_psql($pgactive_test_dbname, "select pgactive.pgactive_remove(true)"); -# per-db worker must be unregistered on a node with pgactive removed -$result = find_in_log($node_0, - qr!LOG: ( [A-Z0-9]+:)? unregistering per-db worker due to .*!, - $logstart_0); - -# Let's skip if the unregister log message is not detected. Sometimes it may -# happen that the worker might get killed even before unregistering log message -# is hit. -SKIP: { - skip "unregistering per-db worker on node_0 is not detected", 1 - if (!$result); - - ok($result, "unregistering per-db worker on node_0 is detected"); -} +# per-db worker must be gone on a node with pgactive removed. +# Poll worker state directly rather than relying on log messages. +ok($node_0->poll_query_until($pgactive_test_dbname, + qq[SELECT COUNT(*) = 0 FROM pgactive.pgactive_get_workers_info() WHERE worker_type = 'per-db';]), + "per-db worker on node_0 is gone after pgactive_remove"); # Remove pgactive from node and immediately drop the extension $node_1->safe_psql($pgactive_test_dbname, @@ -65,34 +47,11 @@ DROP EXTENSION pgactive; ]); -# Detached node must unregister apply worker -$result = find_in_log($node_1, - qr!LOG: ( [A-Z0-9]+:)? unregistering apply worker due to .*!, - $logstart_1); - -# Let's skip if the unregister log message is not detected. Sometimes it may -# happen that the worker might get killed even before unregistering log message -# is hit. -SKIP: { - skip "unregistering apply worker on node_1 is not detected", 1 - if (!$result); - - ok($result, "unregistering apply worker on node_1 is detected"); -} - -# per-db worker must be unregistered on a node with pgactive removed -$result = find_in_log($node_1, - qr!LOG: ( [A-Z0-9]+:)? unregistering per-db worker due to .*!, - $logstart_1); - -# Let's skip if the unregister log message is not detected. Sometimes it may -# happen that the worker might get killed even before unregistering log message -# is hit. -SKIP: { - skip "unregistering per-db worker on node_1 is not detected", 1 - if (!$result); - - ok($result, "unregistering per-db worker on node_1 is detected"); -} +# After pgactive_remove + DROP EXTENSION, all pgactive workers must be gone. +# Poll pg_stat_activity directly since pgactive catalog functions are no longer +# available after DROP EXTENSION. +ok($node_1->poll_query_until('postgres', + qq[SELECT COUNT(*) = 0 FROM pg_stat_activity WHERE application_name LIKE 'pgactive:%' AND datname = '$pgactive_test_dbname';]), + "all pgactive workers on node_1 are gone after pgactive_remove and DROP EXTENSION"); done_testing(); diff --git a/test/t/047_verify_pgactive_guc_settings.pl b/test/t/047_verify_pgactive_guc_settings.pl index f05d05c8..6ea00094 100644 --- a/test/t/047_verify_pgactive_guc_settings.pl +++ b/test/t/047_verify_pgactive_guc_settings.pl @@ -193,7 +193,8 @@ qq[SELECT COUNT(*) = 1 AS ok FROM pgactive.pgactive_get_workers_info() WHERE worker_type = 'apply' AND last_error = 'pgactive_apply_failure' AND - last_error_time IS NOT NULL;]); + last_error_time IS NOT NULL;]) + or die "Timed out waiting for apply failure to be reported on node_1"; $node_0->stop; $node_1->stop; diff --git a/test/t/059_misc.pl b/test/t/059_misc.pl index cce498e0..c9cd4c1c 100644 --- a/test/t/059_misc.pl +++ b/test/t/059_misc.pl @@ -8,7 +8,6 @@ use Config; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; -use Time::HiRes qw(usleep); use IPC::Run; use Test::More; use utils::nodemanagement; @@ -40,27 +39,33 @@ # Let the killed pgactive workers come up $node_0->poll_query_until($pgactive_test_dbname, - qq[SELECT COUNT(*) = 1 AS ok FROM pgactive.pgactive_get_workers_info() WHERE worker_type = 'apply';]); + qq[SELECT COUNT(*) = 1 AS ok FROM pgactive.pgactive_get_workers_info() WHERE worker_type = 'apply';]) + or die "Timed out waiting for apply worker to restart on node_0"; $node_0->poll_query_until($pgactive_test_dbname, - qq[SELECT COUNT(*) = 1 AS ok FROM pgactive.pgactive_get_workers_info() WHERE worker_type = 'walsender';]); + qq[SELECT COUNT(*) = 1 AS ok FROM pgactive.pgactive_get_workers_info() WHERE worker_type = 'walsender';]) + or die "Timed out waiting for walsender worker to restart on node_0"; $node_0->poll_query_until($pgactive_test_dbname, - qq[SELECT COUNT(*) = 1 AS ok FROM pgactive.pgactive_get_workers_info() WHERE worker_type = 'per-db';]); + qq[SELECT COUNT(*) = 1 AS ok FROM pgactive.pgactive_get_workers_info() WHERE worker_type = 'per-db';]) + or die "Timed out waiting for per-db worker to restart on node_0"; $node_0->safe_psql($pgactive_test_dbname, q[INSERT INTO fruits VALUES (2, 'Apple');]); wait_for_apply($node_0, $node_1); $node_0->poll_query_until($pgactive_test_dbname, - qq[SELECT COUNT(*) = 2 FROM fruits;]); + qq[SELECT COUNT(*) = 2 FROM fruits;]) + or die "Timed out waiting for fruits count = 2 on node_0"; # Test the capability to set all pgactive nodes read-only # Set all nodes read-only $node_0->safe_psql($pgactive_test_dbname, qq[SELECT pgactive.pgactive_set_node_read_only(node_name, true) FROM pgactive.pgactive_nodes;]); $node_0->poll_query_until($pgactive_test_dbname, - qq[SELECT node_read_only IS true FROM pgactive.pgactive_nodes WHERE node_name = 'node_0';]); + qq[SELECT node_read_only IS true FROM pgactive.pgactive_nodes WHERE node_name = 'node_0';]) + or die "Timed out waiting for node_0 to become read-only"; $node_1->poll_query_until($pgactive_test_dbname, - qq[SELECT node_read_only IS true FROM pgactive.pgactive_nodes WHERE node_name = 'node_1';]); + qq[SELECT node_read_only IS true FROM pgactive.pgactive_nodes WHERE node_name = 'node_1';]) + or die "Timed out waiting for node_1 to become read-only"; my $query = qq[CREATE TABLE readonly_test_shoulderror(a int);]; my ($result, $stdout, $stderr) = ('','', ''); @@ -122,15 +127,18 @@ $node_0->safe_psql($pgactive_test_dbname, qq[SELECT pgactive.pgactive_set_node_read_only(node_name, false) FROM pgactive.pgactive_nodes;]); $node_0->poll_query_until($pgactive_test_dbname, - qq[SELECT node_read_only IS false FROM pgactive.pgactive_nodes WHERE node_name = 'node_0';]); + qq[SELECT node_read_only IS false FROM pgactive.pgactive_nodes WHERE node_name = 'node_0';]) + or die "Timed out waiting for node_0 to become read-write"; $node_1->poll_query_until($pgactive_test_dbname, - qq[SELECT node_read_only IS false FROM pgactive.pgactive_nodes WHERE node_name = 'node_1';]); + qq[SELECT node_read_only IS false FROM pgactive.pgactive_nodes WHERE node_name = 'node_1';]) + or die "Timed out waiting for node_1 to become read-write"; $node_0->safe_psql($pgactive_test_dbname, q[DELETE FROM fruits;]); wait_for_apply($node_0, $node_1); $node_0->poll_query_until($pgactive_test_dbname, - qq[SELECT COUNT(*) = 0 FROM fruits;]); + qq[SELECT COUNT(*) = 0 FROM fruits;]) + or die "Timed out waiting for fruits to be deleted on node_0"; # The DB name pgactive_supervisordb is reserved by pgactive. None of these # commands may be permitted. @@ -403,36 +411,45 @@ note "Add new fruit to node-2"; $node_2->safe_psql($pgactive_test_dbname, q[INSERT INTO fruits VALUES (10, 'KIWI');]); +wait_for_apply($node_2, $node_3); note "Set RIF for fruits table on node-3"; $node_3->safe_psql($pgactive_test_dbname, q[ALTER TABLE fruits REPLICA IDENTITY FULL;]); note "Update id=10 to Kiwi on node-3"; $node_3->safe_psql($pgactive_test_dbname, q[UPDATE fruits set name ='Kiwi' WHERE id = 10;]); -note "Query node 3"; +note "Update id=10 to KiwiKiwi on node-3"; $node_3->safe_psql($pgactive_test_dbname, q[UPDATE fruits set name ='KiwiKiwi' WHERE id = 10;]); +wait_for_apply($node_3, $node_2); note "Query node 2"; -$node_2->safe_psql($pgactive_test_dbname, - q[SELECT count(*) = 1 FROM fruits WHERE id=10 AND name = 'Kiwi';]); +$node_2->poll_query_until($pgactive_test_dbname, + q[SELECT count(*) = 1 FROM fruits WHERE id=10;]) + or die "Timed out waiting for node_2 to receive insert from node_3"; note "Update id=10 to KiwiKiwi on node-2"; $node_2->safe_psql($pgactive_test_dbname, q[UPDATE fruits set name ='KiwiKiwi' WHERE id = 10;]); +wait_for_apply($node_2, $node_3); note "Query node-2"; -$node_2->safe_psql($pgactive_test_dbname, - q[SELECT count(*) = 1 FROM fruits WHERE id=10 AND name = 'KiwiKiwi';]); -note "Query node-2"; -$node_3->safe_psql($pgactive_test_dbname, - q[SELECT count(*) = 1 FROM fruits WHERE id=10 AND name = 'KiwiKiwi';]); +$node_2->poll_query_until($pgactive_test_dbname, + q[SELECT count(*) = 1 FROM fruits WHERE id=10 AND name = 'KiwiKiwi';]) + or die "Timed out waiting for node_2 to see KiwiKiwi"; +note "Query node-3"; +$node_3->poll_query_until($pgactive_test_dbname, + q[SELECT count(*) = 1 FROM fruits WHERE id=10 AND name = 'KiwiKiwi';]) + or die "Timed out waiting for node_3 to see KiwiKiwi"; note "Delete id=10 on node-2"; $node_2->safe_psql($pgactive_test_dbname, q[DELETE FROM fruits WHERE id = 10;]); +wait_for_apply($node_2, $node_3); note "Query node-2"; -$node_2->safe_psql($pgactive_test_dbname, - q[SELECT count(*) = 0 FROM fruits WHERE id=10;]); +$node_2->poll_query_until($pgactive_test_dbname, + q[SELECT count(*) = 0 FROM fruits WHERE id=10;]) + or die "Timed out waiting for delete on node_2"; note "Query node-3"; -$node_3->safe_psql($pgactive_test_dbname, - q[SELECT count(*) = 0 FROM fruits WHERE id=10;]); +$node_3->poll_query_until($pgactive_test_dbname, + q[SELECT count(*) = 0 FROM fruits WHERE id=10;]) + or die "Timed out waiting for delete to replicate to node_3"; $node_2->stop; $node_3->stop; diff --git a/test/t/070_pgactive_ddl_lock_detach.pl b/test/t/070_pgactive_ddl_lock_detach.pl index d3b1b792..3f8d1f33 100644 --- a/test/t/070_pgactive_ddl_lock_detach.pl +++ b/test/t/070_pgactive_ddl_lock_detach.pl @@ -70,7 +70,8 @@ # Because we have to terminate the apply worker it can take a little while for # the lock to be released. -$node_0->poll_query_until($pgactive_test_dbname, "SELECT lock_state = 'nolock' FROM pgactive.pgactive_global_locks_info"); +$node_0->poll_query_until($pgactive_test_dbname, "SELECT lock_state = 'nolock' FROM pgactive.pgactive_global_locks_info") + or die "Timed out waiting for DDL lock to be released on node_0 after detach"; is( $node_0->safe_psql( $pgactive_test_dbname, "SELECT lock_state FROM pgactive.pgactive_global_locks_info"), 'nolock', "ddl lock released after detach"); is( $node_0->safe_psql( $pgactive_test_dbname, "SELECT state FROM pgactive.pgactive_global_locks"), '', "pgactive.pgactive_global_locks row removed"); diff --git a/test/t/utils/concurrent.pm b/test/t/utils/concurrent.pm index b8f92c6c..265393bb 100644 --- a/test/t/utils/concurrent.pm +++ b/test/t/utils/concurrent.pm @@ -58,7 +58,7 @@ sub concurrent_joins { sub concurrent_safe_psql { my ($node_queries, $timeout) = @_; - $timeout = 60 if (!$timeout); + $timeout = $PostgreSQL::Test::Utils::timeout_default if (!$timeout); my @handles; foreach my $node_query (@$node_queries) { @@ -174,11 +174,10 @@ sub concurrent_joins_physical { } # wait for Pg to come up - my $timeout = 60; foreach my $join_node (@nodes) { my $node = @{$join_node}[0]; - is(wait_for_pg_isready($node, $timeout), - 1, "node " . $node->name . " came up within $timeout seconds"); + is(wait_for_pg_isready($node, $PostgreSQL::Test::Utils::timeout_default), + 1, "node " . $node->name . " came up within timeout"); } # wait for pgactive to come up @@ -372,10 +371,9 @@ sub concurrent_join_detach_physical { check_detach_status(\@{$pgactive_detach_nodes}, $upstream_node); # wait for Pg to come up - my $timeout = 60; foreach my $node (@{$join_nodes}) { - is(wait_for_pg_isready($node, $timeout), - 1, "node " . $node->name . " came up within $timeout seconds"); + is(wait_for_pg_isready($node, $PostgreSQL::Test::Utils::timeout_default), + 1, "node " . $node->name . " came up within timeout"); } # wait for pgactive to come up @@ -437,11 +435,10 @@ sub concurrent_joins_logical_physical { } # wait for Pg to come up - my $timeout = 60; foreach my $join_node (@{$join_nodes_physical}) { my $node = @{$join_node}[0]; - is(wait_for_pg_isready($node, $timeout), - 1, "node " . $node->name . " came up within $timeout seconds"); + is(wait_for_pg_isready($node, $PostgreSQL::Test::Utils::timeout_default), + 1, "node " . $node->name . " came up within timeout"); } my @join_nodes; diff --git a/test/t/utils/nodemanagement.pm b/test/t/utils/nodemanagement.pm index f14f1a77..800ad37f 100644 --- a/test/t/utils/nodemanagement.pm +++ b/test/t/utils/nodemanagement.pm @@ -10,11 +10,11 @@ use Exporter; use Cwd; use Config; use Carp qw(cluck); +use Time::HiRes; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; use Test::More; use IPC::Run; -use Time::HiRes qw(usleep); use vars qw($pgactive_test_dbname); use Carp 'verbose'; @@ -460,17 +460,17 @@ sub check_join_status { my $unid = $upstream_node->safe_psql($pgactive_test_dbname, qq[SELECT pgactive.pgactive_get_node_identifier();]); - # The join target must have an active connection to the new node - is( - $join_node->safe_psql($pgactive_test_dbname, qq[SELECT EXISTS (SELECT 1 FROM pg_stat_activity WHERE application_name = 'pgactive:$unid:send')]), - 't', + # The join target must have an active connection to the new node. + # Use poll_query_until because the walsender connection may not yet be + # visible in pg_stat_activity immediately after pgactive_wait_for_node_ready. + ok( + $join_node->poll_query_until($pgactive_test_dbname, qq[SELECT EXISTS (SELECT 1 FROM pg_stat_activity WHERE application_name = 'pgactive:$unid:send')]), qq(replication connection for $upstream_node_name on $join_node_name is present) ); # The new node must have an active connection to the join target - is( - $upstream_node->safe_psql($pgactive_test_dbname, qq[SELECT EXISTS (SELECT 1 FROM pg_stat_activity WHERE application_name = 'pgactive:$jnid:send')]), - 't', + ok( + $upstream_node->poll_query_until($pgactive_test_dbname, qq[SELECT EXISTS (SELECT 1 FROM pg_stat_activity WHERE application_name = 'pgactive:$jnid:send')]), qq(replication connection for $join_node_name on $upstream_node_name is present) ); } @@ -479,9 +479,8 @@ sub wait_detach_completion { my ($detach_node, $upstream_node) = @_; my $detach_node_name = $detach_node->name(); - if (!$upstream_node->poll_query_until($pgactive_test_dbname, qq[SELECT NOT EXISTS (SELECT 1 FROM pgactive.pgactive_get_replication_lag_info() WHERE node_name = '$detach_node_name' and active)])) { - cluck("replication slot for node " . $detach_node->name . " on " . $upstream_node->name . " was not removed, trying to continue anyway"); - } + ok($upstream_node->poll_query_until($pgactive_test_dbname, qq[SELECT NOT EXISTS (SELECT 1 FROM pgactive.pgactive_get_replication_lag_info() WHERE node_name = '$detach_node_name' and active)]), + qq(replication slot for $detach_node_name on @{[$upstream_node->name]} removed after detach)); } # Remove one or mote nodes from cluster using 'pgactive_detach_nodes'. @@ -558,10 +557,10 @@ sub check_detach_status { qq($detach_node_name status on local node after detach is 'k' or 'r') ); - # The downstream's slot on the upstream MUST be gone - is( - $upstream_node->safe_psql($pgactive_test_dbname, qq[SELECT EXISTS (SELECT 1 FROM pgactive.pgactive_get_replication_lag_info() WHERE active and node_name = '$detach_node_name')]), - 'f', + # The downstream's slot on the upstream MUST be gone. + # Use poll_query_until because slot cleanup may lag behind node status change. + ok( + $upstream_node->poll_query_until($pgactive_test_dbname, qq[SELECT NOT EXISTS (SELECT 1 FROM pgactive.pgactive_get_replication_lag_info() WHERE active and node_name = '$detach_node_name')]), qq(replication slot for $detach_node_name on $upstream_node_name has been removed) ); @@ -624,29 +623,25 @@ sub node_isready { return $?; } -# Wait until pg_isready says a node is up or timeout (if supplied) exceeded. Returns -# 0 on timeout, 1 on success. +# Wait until the node is ready to accept connections by polling pg_isready. +# Returns 0 on timeout, 1 on success. # # Threadsafe. sub wait_for_pg_isready { my ($node, $maxwait) = @_; $maxwait = $PostgreSQL::Test::Utils::timeout_default if !defined($maxwait); - my $waited = 0; - my $wait_secs = 0.5; - while (1) { - my $ret = node_isready($node); - last if $ret == 0; - sleep($wait_secs); - $waited += $wait_secs; - if ($maxwait && ($waited > $maxwait)) - { - diag "gave up waiting for node " . $node->name . " to become ready after $maxwait seconds, last result was $ret"; - return 0; + my $elapsed = 0; + while ($elapsed < $maxwait) { + if (node_isready($node) == 0) { + return 1; } - }; + Time::HiRes::usleep(100_000); + $elapsed += 0.1; + } - return 1; + diag "gave up waiting for node " . $node->name . " to become ready after $maxwait seconds"; + return 0; } # Print out pgactive.pgactive_nodes status info for a node @@ -746,7 +741,8 @@ SELECT 'acquired' FROM pgactive.pgactive_acquire_global_lock('$mode'); croak("cannot find expected query SELECT 'acquired' FROM pgactive.pgactive_acquire_global_lock... in pg_stat_activity\n"); } - $node->poll_query_until($pgactive_test_dbname, q[SELECT lock_state <> 'nolock' FROM pgactive.pgactive_global_locks_info]); + $node->poll_query_until($pgactive_test_dbname, q[SELECT lock_state <> 'nolock' FROM pgactive.pgactive_global_locks_info]) + or croak("Timed out waiting for DDL lock state to change from 'nolock'"); my $status = $node->safe_psql($pgactive_test_dbname, q[SELECT lock_state, lock_mode, owner_is_my_node, owner_is_my_backend FROM pgactive.pgactive_global_locks_info]); if (not ($status =~ qr/(?:acquire_acquired|acquire_tally_confirmations)\|$mode\|t\|f/)) @@ -817,22 +813,15 @@ sub get_log_size return (stat $node->logfile)[7]; } -# Find $pat in logfile of $node after $off-th byte +# Find $pat in logfile of $node after $off-th byte. +# Uses $node->wait_for_log which polls every 100ms up to $timeout_default. +# Returns true if pattern found, false on timeout (does not croak). sub find_in_log { my ($node, $pat, $off) = @_; - #my $max_attempts = $PostgreSQL::Test::Utils::timeout_default * 10; - my $max_attempts = 60 * 10; - my $log; - - while ($max_attempts-- >= 0) - { - $log = PostgreSQL::Test::Utils::slurp_file($node->logfile, $off); - last if ($log =~ m/$pat/); - usleep(100_000); - } - return $log =~ m/$pat/; + my $result = eval { $node->wait_for_log($pat, $off) }; + return defined($result); } sub create_pgactive_group_with_db { From 4229e751f7729732e6d13bf864accf845e484725 Mon Sep 17 00:00:00 2001 From: Yogesh Sharma Date: Fri, 29 May 2026 07:59:24 -0400 Subject: [PATCH 5/6] Fix random failure in ddl_function --- test/expected/ddl_fn/ddl_function.out | 53 +++++++++++++++++++-------- test/run_tests.in | 16 ++++---- test/sql/ddl_fn/ddl_function.sql | 14 +++++-- 3 files changed, 56 insertions(+), 27 deletions(-) diff --git a/test/expected/ddl_fn/ddl_function.out b/test/expected/ddl_fn/ddl_function.out index 1db0d7c2..9ab76260 100644 --- a/test/expected/ddl_fn/ddl_function.out +++ b/test/expected/ddl_fn/ddl_function.out @@ -1,6 +1,6 @@ -- tests for functions and triggers \c postgres super -SELECT pgactive.pgactive_replicate_ddl_command($DDL$ +SELECT pgactive.pgactive_replicate_ddl_command($DDL$ CREATE FUNCTION public.test_fn(IN inpar character varying (20), INOUT inoutpar integer, OUT timestamp with time zone) RETURNS SETOF record AS $$ BEGIN @@ -8,14 +8,20 @@ BEGIN END; $$ LANGUAGE plpgsql IMMUTABLE STRICT; $DDL$); - pgactive_replicate_ddl_command + pgactive_replicate_ddl_command -------------------------------- - + +(1 row) + +SELECT pgactive.pgactive_wait_for_slots_confirmed_flush_lsn(NULL,NULL); + pgactive_wait_for_slots_confirmed_flush_lsn +--------------------------------------------- + (1 row) \df test_fn List of functions - Schema | Name | Result data type | Argument data types | Type + Schema | Name | Result data type | Argument data types | Type --------+---------+------------------+-------------------------------------------------------------------------------+------ public | test_fn | SETOF record | inpar character varying, INOUT inoutpar integer, OUT timestamp with time zone | func (1 row) @@ -23,47 +29,61 @@ $DDL$); \c regression \df test_fn List of functions - Schema | Name | Result data type | Argument data types | Type + Schema | Name | Result data type | Argument data types | Type --------+---------+------------------+-------------------------------------------------------------------------------+------ public | test_fn | SETOF record | inpar character varying, INOUT inoutpar integer, OUT timestamp with time zone | func (1 row) +\c postgres super SELECT pgactive.pgactive_replicate_ddl_command($DDL$ ALTER FUNCTION public.test_fn(varchar, integer) SECURITY DEFINER CALLED ON NULL INPUT VOLATILE ROWS 1 COST 1; $DDL$); - pgactive_replicate_ddl_command + pgactive_replicate_ddl_command -------------------------------- - + +(1 row) + +SELECT pgactive.pgactive_wait_for_slots_confirmed_flush_lsn(NULL,NULL); + pgactive_wait_for_slots_confirmed_flush_lsn +--------------------------------------------- + (1 row) \df test_fn List of functions - Schema | Name | Result data type | Argument data types | Type + Schema | Name | Result data type | Argument data types | Type --------+---------+------------------+-------------------------------------------------------------------------------+------ public | test_fn | SETOF record | inpar character varying, INOUT inoutpar integer, OUT timestamp with time zone | func (1 row) -\c postgres +\c regression \df test_fn List of functions - Schema | Name | Result data type | Argument data types | Type + Schema | Name | Result data type | Argument data types | Type --------+---------+------------------+-------------------------------------------------------------------------------+------ public | test_fn | SETOF record | inpar character varying, INOUT inoutpar integer, OUT timestamp with time zone | func (1 row) -SELECT pgactive.pgactive_replicate_ddl_command($DDL$ -CREATE OR REPLACE FUNCTION public.test_fn(IN inpar varchar, INOUT inoutpar integer, OUT timestamp with time zone) RETURNS SETOF record AS +\c postgres super +SELECT pgactive.pgactive_replicate_ddl_command($DDL$ +CREATE OR REPLACE FUNCTION public.test_fn(IN inpar varchar, INOUT inoutpar integer, OUT timestamp with time zone) RETURNS SETOF record AS $$ BEGIN END; $$ LANGUAGE plpgsql STABLE; $DDL$); - pgactive_replicate_ddl_command + pgactive_replicate_ddl_command -------------------------------- - + +(1 row) + +SELECT pgactive.pgactive_wait_for_slots_confirmed_flush_lsn(NULL,NULL); + pgactive_wait_for_slots_confirmed_flush_lsn +--------------------------------------------- + (1 row) \df test_fn List of functions - Schema | Name | Result data type | Argument data types | Type + Schema | Name | Result data type | Argument data types | Type --------+---------+------------------+-------------------------------------------------------------------------------+------ public | test_fn | SETOF record | inpar character varying, INOUT inoutpar integer, OUT timestamp with time zone | func (1 row) @@ -71,11 +91,12 @@ $DDL$); \c regression \df test_fn List of functions - Schema | Name | Result data type | Argument data types | Type + Schema | Name | Result data type | Argument data types | Type --------+---------+------------------+-------------------------------------------------------------------------------+------ public | test_fn | SETOF record | inpar character varying, INOUT inoutpar integer, OUT timestamp with time zone | func (1 row) +\c postgres super SELECT pgactive.pgactive_replicate_ddl_command($DDL$ DROP FUNCTION public.test_fn(varchar, integer); $DDL$); pgactive_replicate_ddl_command -------------------------------- diff --git a/test/run_tests.in b/test/run_tests.in index 94fb6922..f304837c 100644 --- a/test/run_tests.in +++ b/test/run_tests.in @@ -14,9 +14,10 @@ BINDIR=$(@PG_CONFIG@ --bindir) REGRESS_BASE=$(@PG_CONFIG@ --pkglibdir)/pgxs/ DBNAME=regression -rm -rf @srcdir@/test/tmp_check -mkdir -p @srcdir@/test/tmp_check -mkdir @srcdir@/test/tmp_check/data +rm -rf @abs_srcdir@/test/tmp_check +mkdir -p @abs_srcdir@/test/tmp_check +mkdir @abs_srcdir@/test/tmp_check/data +mkdir -p @abs_srcdir@/test/results/ddl @abs_srcdir@/test/results/ddl_fn @abs_srcdir@/test/results/dml set +e for getoptbin in $(which getopt) /usr/bin/getopt /usr/local/bin/getopt ; do @@ -58,7 +59,8 @@ while true ; do esac done -SOCKET=$(pwd)/test/tmp_check +TMPDIR=@abs_srcdir@/test/tmp_check +SOCKET=$TMPDIR PORT=5440 OPTIONS="-c config_file=${CONFIGFILE}" OPTIONS="$OPTIONS -c unix_socket_directories=$SOCKET" @@ -69,13 +71,13 @@ export PGHOST=$SOCKET export PGPORT=$PORT # create new data directory -$BINDIR/initdb --nosync test/tmp_check/data > test/tmp_check/initdb.log 2>&1 +$BINDIR/initdb --nosync $TMPDIR/data > $TMPDIR/initdb.log 2>&1 # install trap to shutdown server at failure/exit -trap "$BINDIR/pg_ctl stop -w -D test/tmp_check/data" INT QUIT TERM EXIT +trap "$BINDIR/pg_ctl stop -w -D $TMPDIR/data" INT QUIT TERM EXIT #start server -$BINDIR/pg_ctl start -w -D test/tmp_check/data -o "$OPTIONS" -c -l test/tmp_check/postmaster.log +$BINDIR/pg_ctl start -w -D $TMPDIR/data -o "$OPTIONS" -c -l $TMPDIR/postmaster.log $REGRESS_BASE/$TESTBINARY \ --host $SOCKET \ diff --git a/test/sql/ddl_fn/ddl_function.sql b/test/sql/ddl_fn/ddl_function.sql index c05f1f67..1b990607 100644 --- a/test/sql/ddl_fn/ddl_function.sql +++ b/test/sql/ddl_fn/ddl_function.sql @@ -1,6 +1,6 @@ -- tests for functions and triggers \c postgres super -SELECT pgactive.pgactive_replicate_ddl_command($DDL$ +SELECT pgactive.pgactive_replicate_ddl_command($DDL$ CREATE FUNCTION public.test_fn(IN inpar character varying (20), INOUT inoutpar integer, OUT timestamp with time zone) RETURNS SETOF record AS $$ BEGIN @@ -8,26 +8,32 @@ BEGIN END; $$ LANGUAGE plpgsql IMMUTABLE STRICT; $DDL$); +SELECT pgactive.pgactive_wait_for_slots_confirmed_flush_lsn(NULL,NULL); \df test_fn \c regression \df test_fn +\c postgres super SELECT pgactive.pgactive_replicate_ddl_command($DDL$ ALTER FUNCTION public.test_fn(varchar, integer) SECURITY DEFINER CALLED ON NULL INPUT VOLATILE ROWS 1 COST 1; $DDL$); +SELECT pgactive.pgactive_wait_for_slots_confirmed_flush_lsn(NULL,NULL); \df test_fn -\c postgres +\c regression \df test_fn -SELECT pgactive.pgactive_replicate_ddl_command($DDL$ -CREATE OR REPLACE FUNCTION public.test_fn(IN inpar varchar, INOUT inoutpar integer, OUT timestamp with time zone) RETURNS SETOF record AS +\c postgres super +SELECT pgactive.pgactive_replicate_ddl_command($DDL$ +CREATE OR REPLACE FUNCTION public.test_fn(IN inpar varchar, INOUT inoutpar integer, OUT timestamp with time zone) RETURNS SETOF record AS $$ BEGIN END; $$ LANGUAGE plpgsql STABLE; $DDL$); +SELECT pgactive.pgactive_wait_for_slots_confirmed_flush_lsn(NULL,NULL); \df test_fn \c regression \df test_fn +\c postgres super SELECT pgactive.pgactive_replicate_ddl_command($DDL$ DROP FUNCTION public.test_fn(varchar, integer); $DDL$); \df test_fn \c postgres From ae8cd3689da0af4f648927af0cf1d2ade24e811c Mon Sep 17 00:00:00 2001 From: Yogesh Sharma Date: Fri, 29 May 2026 08:24:18 -0400 Subject: [PATCH 6/6] Fix out file spacing and remove more sleeps Fix more edge cases in testintg --- test/expected/ddl_fn/ddl_function.out | 36 +++++++++++++-------------- test/t/051_node_loss_desync_pause.pl | 10 +++++--- test/t/utils/nodemanagement.pm | 14 +++++------ test/t/utils/sequence.pm | 4 +++ 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/test/expected/ddl_fn/ddl_function.out b/test/expected/ddl_fn/ddl_function.out index 9ab76260..9a50d902 100644 --- a/test/expected/ddl_fn/ddl_function.out +++ b/test/expected/ddl_fn/ddl_function.out @@ -8,20 +8,20 @@ BEGIN END; $$ LANGUAGE plpgsql IMMUTABLE STRICT; $DDL$); - pgactive_replicate_ddl_command + pgactive_replicate_ddl_command -------------------------------- - + (1 row) SELECT pgactive.pgactive_wait_for_slots_confirmed_flush_lsn(NULL,NULL); - pgactive_wait_for_slots_confirmed_flush_lsn + pgactive_wait_for_slots_confirmed_flush_lsn --------------------------------------------- - + (1 row) \df test_fn List of functions - Schema | Name | Result data type | Argument data types | Type + Schema | Name | Result data type | Argument data types | Type --------+---------+------------------+-------------------------------------------------------------------------------+------ public | test_fn | SETOF record | inpar character varying, INOUT inoutpar integer, OUT timestamp with time zone | func (1 row) @@ -29,27 +29,27 @@ SELECT pgactive.pgactive_wait_for_slots_confirmed_flush_lsn(NULL,NULL); \c regression \df test_fn List of functions - Schema | Name | Result data type | Argument data types | Type + Schema | Name | Result data type | Argument data types | Type --------+---------+------------------+-------------------------------------------------------------------------------+------ public | test_fn | SETOF record | inpar character varying, INOUT inoutpar integer, OUT timestamp with time zone | func (1 row) \c postgres super SELECT pgactive.pgactive_replicate_ddl_command($DDL$ ALTER FUNCTION public.test_fn(varchar, integer) SECURITY DEFINER CALLED ON NULL INPUT VOLATILE ROWS 1 COST 1; $DDL$); - pgactive_replicate_ddl_command + pgactive_replicate_ddl_command -------------------------------- - + (1 row) SELECT pgactive.pgactive_wait_for_slots_confirmed_flush_lsn(NULL,NULL); - pgactive_wait_for_slots_confirmed_flush_lsn + pgactive_wait_for_slots_confirmed_flush_lsn --------------------------------------------- - + (1 row) \df test_fn List of functions - Schema | Name | Result data type | Argument data types | Type + Schema | Name | Result data type | Argument data types | Type --------+---------+------------------+-------------------------------------------------------------------------------+------ public | test_fn | SETOF record | inpar character varying, INOUT inoutpar integer, OUT timestamp with time zone | func (1 row) @@ -57,7 +57,7 @@ SELECT pgactive.pgactive_wait_for_slots_confirmed_flush_lsn(NULL,NULL); \c regression \df test_fn List of functions - Schema | Name | Result data type | Argument data types | Type + Schema | Name | Result data type | Argument data types | Type --------+---------+------------------+-------------------------------------------------------------------------------+------ public | test_fn | SETOF record | inpar character varying, INOUT inoutpar integer, OUT timestamp with time zone | func (1 row) @@ -70,20 +70,20 @@ BEGIN END; $$ LANGUAGE plpgsql STABLE; $DDL$); - pgactive_replicate_ddl_command + pgactive_replicate_ddl_command -------------------------------- - + (1 row) SELECT pgactive.pgactive_wait_for_slots_confirmed_flush_lsn(NULL,NULL); - pgactive_wait_for_slots_confirmed_flush_lsn + pgactive_wait_for_slots_confirmed_flush_lsn --------------------------------------------- - + (1 row) \df test_fn List of functions - Schema | Name | Result data type | Argument data types | Type + Schema | Name | Result data type | Argument data types | Type --------+---------+------------------+-------------------------------------------------------------------------------+------ public | test_fn | SETOF record | inpar character varying, INOUT inoutpar integer, OUT timestamp with time zone | func (1 row) @@ -91,7 +91,7 @@ SELECT pgactive.pgactive_wait_for_slots_confirmed_flush_lsn(NULL,NULL); \c regression \df test_fn List of functions - Schema | Name | Result data type | Argument data types | Type + Schema | Name | Result data type | Argument data types | Type --------+---------+------------------+-------------------------------------------------------------------------------+------ public | test_fn | SETOF record | inpar character varying, INOUT inoutpar integer, OUT timestamp with time zone | func (1 row) diff --git a/test/t/051_node_loss_desync_pause.pl b/test/t/051_node_loss_desync_pause.pl index c816f54d..15f59965 100644 --- a/test/t/051_node_loss_desync_pause.pl +++ b/test/t/051_node_loss_desync_pause.pl @@ -14,7 +14,6 @@ use Config; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; -use Time::HiRes qw(usleep); use threads; use Test::More; use utils::nodemanagement; @@ -47,10 +46,8 @@ # Check changes from node_2 are replayed on node_1 and not on node_0 wait_for_apply($node_2,$node_1); -sleep(1); is($node_1->safe_psql($pgactive_test_dbname,"SELECT id FROM $test_table"), $value,"Changes replayed to node_1"); -sleep(1); is($node_0->safe_psql($pgactive_test_dbname,"SELECT id FROM $test_table"), '',"Changes not replayed to node_0 due to apply pause"); @@ -78,12 +75,17 @@ $logstart_0); ok($result, "apply has paused on node_0 for the second time"); -detach_and_check_nodes([$node_2],$node_1); +# Detach node_2 while node_0 has apply paused. Slot cleanup may be delayed +# until apply resumes, so defer check_detach_status until after resume. +pgactive_detach_nodes([$node_2],$node_1); $node_0->safe_psql($pgactive_test_dbname,"select pgactive.pgactive_apply_resume()"); wait_for_apply($node_0,$node_1); wait_for_apply($node_1,$node_0); +check_detach_status([$node_2],$node_1); +stop_nodes([$node_2]); + TODO: { # See detailed explanation in t/050_node_loss_desync.pl local $TODO = 'pgactive EHA required'; diff --git a/test/t/utils/nodemanagement.pm b/test/t/utils/nodemanagement.pm index 14801e1f..f082163e 100644 --- a/test/t/utils/nodemanagement.pm +++ b/test/t/utils/nodemanagement.pm @@ -479,8 +479,9 @@ sub wait_detach_completion { my ($detach_node, $upstream_node) = @_; my $detach_node_name = $detach_node->name(); - ok($upstream_node->poll_query_until($pgactive_test_dbname, qq[SELECT NOT EXISTS (SELECT 1 FROM pgactive.pgactive_get_replication_lag_info() WHERE node_name = '$detach_node_name' and active)]), - qq(replication slot for $detach_node_name on @{[$upstream_node->name]} removed after detach)); + if (!$upstream_node->poll_query_until($pgactive_test_dbname, qq[SELECT NOT EXISTS (SELECT 1 FROM pgactive.pgactive_get_replication_lag_info() WHERE node_name = '$detach_node_name' and active)])) { + diag("replication slot for $detach_node_name on @{[$upstream_node->name]} was not removed within timeout, continuing"); + } } # Remove one or mote nodes from cluster using 'pgactive_detach_nodes'. @@ -504,7 +505,6 @@ sub pgactive_detach_nodes { $upstream_node->safe_psql( $pgactive_test_dbname, "SELECT pgactive.pgactive_detach_nodes($nodelist)" ); - sleep(5); # We can tell a detach has taken effect when the downstream's slot vanishes # on the upstream. for my $detach_node (@{$pgactive_detach_nodes}) { @@ -558,10 +558,10 @@ sub check_detach_status { qq($detach_node_name status on local node after detach is 'k' or 'r') ); - # The downstream's slot on the upstream MUST be gone. - # Use poll_query_until because slot cleanup may lag behind node status change. - ok( - $upstream_node->poll_query_until($pgactive_test_dbname, qq[SELECT NOT EXISTS (SELECT 1 FROM pgactive.pgactive_get_replication_lag_info() WHERE active and node_name = '$detach_node_name')]), + # The downstream's slot on the upstream MUST be gone + is( + $upstream_node->safe_psql($pgactive_test_dbname, qq[SELECT EXISTS (SELECT 1 FROM pgactive.pgactive_get_replication_lag_info() WHERE active and node_name = '$detach_node_name')]), + 'f', qq(replication slot for $detach_node_name on $upstream_node_name has been removed) ); diff --git a/test/t/utils/sequence.pm b/test/t/utils/sequence.pm index 1c7bbec6..ba964986 100644 --- a/test/t/utils/sequence.pm +++ b/test/t/utils/sequence.pm @@ -60,6 +60,10 @@ sub insert_into_table_sequence { sub compare_sequence_table_with_upstream { my ( $message, $upstream_node, @nodes ) = @_; + foreach my $node (@nodes) { + wait_for_apply( $node, $upstream_node ); + wait_for_apply( $upstream_node, $node ); + } my $upstream_record = $upstream_node->safe_psql( $pgactive_test_dbname, "SELECT * FROM public.test_table_sequence" ); foreach my $node (@nodes) { my $node_record = $node->safe_psql( $pgactive_test_dbname, "SELECT * FROM public.test_table_sequence" );