diff --git a/Makefile.in b/Makefile.in index e71e534a..8881e8bb 100644 --- a/Makefile.in +++ b/Makefile.in @@ -150,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 @@ -172,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/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/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/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/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..cfde11fa 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..6a63c258 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/expected/ddl_fn/ddl_function.out b/test/expected/ddl_fn/ddl_function.out index 1db0d7c2..9a50d902 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 @@ -13,6 +13,12 @@ $DDL$); (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 @@ -28,12 +34,19 @@ $DDL$); 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 -------------------------------- (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 @@ -41,7 +54,7 @@ SELECT pgactive.pgactive_replicate_ddl_command($DDL$ ALTER FUNCTION public.test_ 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 @@ -49,8 +62,9 @@ SELECT pgactive.pgactive_replicate_ddl_command($DDL$ ALTER FUNCTION public.test_ 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; @@ -61,6 +75,12 @@ $DDL$); (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 @@ -76,6 +96,7 @@ $DDL$); 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 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/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/059_misc.pl b/test/t/059_misc.pl index ae2e3c9b..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; @@ -17,6 +16,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, @@ -36,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) = ('','', ''); @@ -118,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. @@ -399,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 4b1a50a0..f082163e 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) ); } @@ -480,7 +480,7 @@ sub wait_detach_completion { 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"); + diag("replication slot for $detach_node_name on @{[$upstream_node->name]} was not removed within timeout, continuing"); } } @@ -505,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}) { @@ -559,7 +558,6 @@ sub check_detach_status { qq($detach_node_name status on local node after detach is 'k' or 'r') ); - sleep(5); # 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')]), @@ -626,29 +624,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 @@ -748,7 +742,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/)) @@ -819,22 +814,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 { 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" );