diff --git a/config/c-compiler.m4 b/config/c-compiler.m4 index d3562d6feee..f0744482e7e 100644 --- a/config/c-compiler.m4 +++ b/config/c-compiler.m4 @@ -7,10 +7,10 @@ # Select the format archetype to be used by gcc to check printf-type functions. # We prefer "gnu_printf", as that most closely matches the features supported # by src/port/snprintf.c (particularly the %m conversion spec). However, -# on some NetBSD versions, that doesn't work while "__syslog__" does. -# If all else fails, use "printf". +# on clang and on some NetBSD versions, that doesn't work while "__syslog__" +# does. If all else fails, use "printf". AC_DEFUN([PGAC_PRINTF_ARCHETYPE], -[AC_CACHE_CHECK([for printf format archetype], pgac_cv_printf_archetype, +[AC_CACHE_CHECK([for C printf format archetype], pgac_cv_printf_archetype, [pgac_cv_printf_archetype=gnu_printf PGAC_TEST_PRINTF_ARCHETYPE if [[ "$ac_archetype_ok" = no ]]; then @@ -20,8 +20,8 @@ if [[ "$ac_archetype_ok" = no ]]; then pgac_cv_printf_archetype=printf fi fi]) -AC_DEFINE_UNQUOTED([PG_PRINTF_ATTRIBUTE], [$pgac_cv_printf_archetype], -[Define to best printf format archetype, usually gnu_printf if available.]) +AC_DEFINE_UNQUOTED([PG_C_PRINTF_ATTRIBUTE], [$pgac_cv_printf_archetype], +[Define to best C printf format archetype, usually gnu_printf if available.]) ])# PGAC_PRINTF_ARCHETYPE # Subroutine: test $pgac_cv_printf_archetype, set $ac_archetype_ok to yes or no @@ -92,6 +92,42 @@ undefine([Ac_cachevar])dnl ])# PGAC_TYPE_64BIT_INT +# PGAC_CXX_PRINTF_ARCHETYPE +# ------------------------- +# Because we support using gcc as C compiler with clang as C++ compiler, +# we have to be prepared to use different printf archetypes in C++ code. +# So, do the above test all over in C++. +AC_DEFUN([PGAC_CXX_PRINTF_ARCHETYPE], +[AC_CACHE_CHECK([for C++ printf format archetype], pgac_cv_cxx_printf_archetype, +[pgac_cv_cxx_printf_archetype=gnu_printf +PGAC_TEST_CXX_PRINTF_ARCHETYPE +if [[ "$ac_archetype_ok" = no ]]; then + pgac_cv_cxx_printf_archetype=__syslog__ + PGAC_TEST_CXX_PRINTF_ARCHETYPE + if [[ "$ac_archetype_ok" = no ]]; then + pgac_cv_cxx_printf_archetype=printf + fi +fi]) +AC_DEFINE_UNQUOTED([PG_CXX_PRINTF_ATTRIBUTE], [$pgac_cv_cxx_printf_archetype], +[Define to best C++ printf format archetype, usually gnu_printf if available.]) +])# PGAC_CXX_PRINTF_ARCHETYPE + +# Subroutine: test $pgac_cv_cxx_printf_archetype, set $ac_archetype_ok to yes or no +AC_DEFUN([PGAC_TEST_CXX_PRINTF_ARCHETYPE], +[ac_save_cxx_werror_flag=$ac_cxx_werror_flag +ac_cxx_werror_flag=yes +AC_LANG_PUSH(C++) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM( +[extern void pgac_write(int ignore, const char *fmt,...) +__attribute__((format($pgac_cv_cxx_printf_archetype, 2, 3)));], +[pgac_write(0, "error %s: %m", "foo");])], + [ac_archetype_ok=yes], + [ac_archetype_ok=no]) +AC_LANG_POP([]) +ac_cxx_werror_flag=$ac_save_cxx_werror_flag +])# PGAC_TEST_CXX_PRINTF_ARCHETYPE + + # PGAC_TYPE_128BIT_INT # -------------------- # Check if __int128 is a working 128 bit integer type, and if so diff --git a/configure b/configure index fb4fae74700..04f4b84f271 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for PostgreSQL 15.17. +# Generated by GNU Autoconf 2.69 for PostgreSQL 15.18. # # Report bugs to . # @@ -582,8 +582,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='PostgreSQL' PACKAGE_TARNAME='postgresql' -PACKAGE_VERSION='15.17' -PACKAGE_STRING='PostgreSQL 15.17' +PACKAGE_VERSION='15.18' +PACKAGE_STRING='PostgreSQL 15.18' PACKAGE_BUGREPORT='pgsql-bugs@lists.postgresql.org' PACKAGE_URL='https://www.postgresql.org/' @@ -1452,7 +1452,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 PostgreSQL 15.17 to adapt to many kinds of systems. +\`configure' configures PostgreSQL 15.18 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1517,7 +1517,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of PostgreSQL 15.17:";; + short | recursive ) echo "Configuration of PostgreSQL 15.18:";; esac cat <<\_ACEOF @@ -1691,7 +1691,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -PostgreSQL configure 15.17 +PostgreSQL configure 15.18 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2444,7 +2444,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by PostgreSQL $as_me 15.17, which was +It was created by PostgreSQL $as_me 15.18, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -15145,8 +15145,8 @@ _ACEOF ;; esac -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for printf format archetype" >&5 -$as_echo_n "checking for printf format archetype... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C printf format archetype" >&5 +$as_echo_n "checking for C printf format archetype... " >&6; } if ${pgac_cv_printf_archetype+:} false; then : $as_echo_n "(cached) " >&6 else @@ -15206,7 +15206,97 @@ fi $as_echo "$pgac_cv_printf_archetype" >&6; } cat >>confdefs.h <<_ACEOF -#define PG_PRINTF_ATTRIBUTE $pgac_cv_printf_archetype +#define PG_C_PRINTF_ATTRIBUTE $pgac_cv_printf_archetype +_ACEOF + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ printf format archetype" >&5 +$as_echo_n "checking for C++ printf format archetype... " >&6; } +if ${pgac_cv_cxx_printf_archetype+:} false; then : + $as_echo_n "(cached) " >&6 +else + pgac_cv_cxx_printf_archetype=gnu_printf +ac_save_cxx_werror_flag=$ac_cxx_werror_flag +ac_cxx_werror_flag=yes +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +extern void pgac_write(int ignore, const char *fmt,...) +__attribute__((format($pgac_cv_cxx_printf_archetype, 2, 3))); +int +main () +{ +pgac_write(0, "error %s: %m", "foo"); + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_archetype_ok=yes +else + ac_archetype_ok=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ac_cxx_werror_flag=$ac_save_cxx_werror_flag + +if [ "$ac_archetype_ok" = no ]; then + pgac_cv_cxx_printf_archetype=__syslog__ + ac_save_cxx_werror_flag=$ac_cxx_werror_flag +ac_cxx_werror_flag=yes +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +extern void pgac_write(int ignore, const char *fmt,...) +__attribute__((format($pgac_cv_cxx_printf_archetype, 2, 3))); +int +main () +{ +pgac_write(0, "error %s: %m", "foo"); + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_archetype_ok=yes +else + ac_archetype_ok=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ac_cxx_werror_flag=$ac_save_cxx_werror_flag + + if [ "$ac_archetype_ok" = no ]; then + pgac_cv_cxx_printf_archetype=printf + fi +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_cxx_printf_archetype" >&5 +$as_echo "$pgac_cv_cxx_printf_archetype" >&6; } + +cat >>confdefs.h <<_ACEOF +#define PG_CXX_PRINTF_ATTRIBUTE $pgac_cv_cxx_printf_archetype _ACEOF @@ -16884,6 +16974,16 @@ fi cat >>confdefs.h <<_ACEOF #define HAVE_DECL_STRNLEN $ac_have_decl _ACEOF +ac_fn_c_check_decl "$LINENO" "timingsafe_bcmp" "ac_cv_have_decl_timingsafe_bcmp" "$ac_includes_default" +if test "x$ac_cv_have_decl_timingsafe_bcmp" = xyes; then : + ac_have_decl=1 +else + ac_have_decl=0 +fi + +cat >>confdefs.h <<_ACEOF +#define HAVE_DECL_TIMINGSAFE_BCMP $ac_have_decl +_ACEOF # We can't use AC_REPLACE_FUNCS to replace these functions, because it @@ -17236,6 +17336,19 @@ esac fi +ac_fn_c_check_func "$LINENO" "timingsafe_bcmp" "ac_cv_func_timingsafe_bcmp" +if test "x$ac_cv_func_timingsafe_bcmp" = xyes; then : + $as_echo "#define HAVE_TIMINGSAFE_BCMP 1" >>confdefs.h + +else + case " $LIBOBJS " in + *" timingsafe_bcmp.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS timingsafe_bcmp.$ac_objext" + ;; +esac + +fi + if test "$enable_thread_safety" = yes; then @@ -20932,7 +21045,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by PostgreSQL $as_me 15.17, which was +This file was extended by PostgreSQL $as_me 15.18, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -21003,7 +21116,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -PostgreSQL config.status 15.17 +PostgreSQL config.status 15.18 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 928495a8ff2..f8a6a490606 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Read the Autoconf manual for details. dnl m4_pattern_forbid(^PGAC_)dnl to catch undefined macros -AC_INIT([PostgreSQL], [15.17], [pgsql-bugs@lists.postgresql.org], [], [https://www.postgresql.org/]) +AC_INIT([PostgreSQL], [15.18], [pgsql-bugs@lists.postgresql.org], [], [https://www.postgresql.org/]) m4_if(m4_defn([m4_PACKAGE_VERSION]), [2.69], [], [m4_fatal([Autoconf version 2.69 is required. Untested combinations of 'autoconf' and PostgreSQL versions are not @@ -1681,6 +1681,7 @@ m4_defun([AC_PROG_CC_STDC], []) dnl We don't want that. AC_C_BIGENDIAN AC_C_INLINE PGAC_PRINTF_ARCHETYPE +PGAC_CXX_PRINTF_ARCHETYPE PGAC_C_FUNCNAME_SUPPORT PGAC_C_STATIC_ASSERT PGAC_C_TYPEOF @@ -1916,7 +1917,7 @@ AC_CHECK_DECLS(posix_fadvise, [], [], [#include ]) ]) # fi AC_CHECK_DECLS(fdatasync, [], [], [#include ]) -AC_CHECK_DECLS([strlcat, strlcpy, strnlen]) +AC_CHECK_DECLS([strlcat, strlcpy, strnlen, timingsafe_bcmp]) # We can't use AC_REPLACE_FUNCS to replace these functions, because it # won't handle deployment target restrictions on macOS @@ -1966,6 +1967,7 @@ AC_REPLACE_FUNCS(m4_normalize([ strlcpy strnlen strtof + timingsafe_bcmp ])) if test "$enable_thread_safety" = yes; then diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c index db5fd87429e..41405e3ffda 100644 --- a/contrib/basic_archive/basic_archive.c +++ b/contrib/basic_archive/basic_archive.c @@ -93,13 +93,11 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb) /* * check_archive_directory * - * Checks that the provided archive directory exists. + * Checks that the provided archive directory path isn't too long. */ static bool check_archive_directory(char **newval, void **extra, GucSource source) { - struct stat st; - /* * The default value is an empty string, so we have to accept that value. * Our check_configured callback also checks for this and prevents @@ -118,17 +116,6 @@ check_archive_directory(char **newval, void **extra, GucSource source) return false; } - /* - * Do a basic sanity check that the specified archive directory exists. It - * could be removed at some point in the future, so we still need to be - * prepared for it not to exist in the actual archiving logic. - */ - if (stat(*newval, &st) != 0 || !S_ISDIR(st.st_mode)) - { - GUC_check_errdetail("Specified archive directory does not exist."); - return false; - } - return true; } diff --git a/contrib/hstore_plperl/hstore_plperl.c b/contrib/hstore_plperl/hstore_plperl.c index 417b721cff9..f4c742edfa9 100644 --- a/contrib/hstore_plperl/hstore_plperl.c +++ b/contrib/hstore_plperl/hstore_plperl.c @@ -121,7 +121,7 @@ plperl_to_hstore(PG_FUNCTION_ARGS) pcount = hv_iterinit(hv); - pairs = palloc(pcount * sizeof(Pairs)); + pairs = palloc_array(Pairs, pcount); i = 0; while ((he = hv_iternext(hv))) diff --git a/contrib/hstore_plpython/hstore_plpython.c b/contrib/hstore_plpython/hstore_plpython.c index 4b3ef025f2e..de89511994c 100644 --- a/contrib/hstore_plpython/hstore_plpython.c +++ b/contrib/hstore_plpython/hstore_plpython.c @@ -149,7 +149,7 @@ plpython_to_hstore(PG_FUNCTION_ARGS) Py_ssize_t i; Pairs *pairs; - pairs = palloc(pcount * sizeof(*pairs)); + pairs = palloc_array(Pairs, pcount); for (i = 0; i < pcount; i++) { diff --git a/contrib/intarray/_int_bool.c b/contrib/intarray/_int_bool.c index 3ed88af76d7..d415a3dd6a4 100644 --- a/contrib/intarray/_int_bool.c +++ b/contrib/intarray/_int_bool.c @@ -435,35 +435,66 @@ boolop(PG_FUNCTION_ARGS) PG_RETURN_BOOL(result); } +/* + * Recursively fill the "left" fields of an ITEM array that represents + * a valid postfix tree. + * + * ptr: starting element of array + * pos: in/out argument, the array index this call is responsible to fill + * + * At exit, *pos has been decremented to point before the sub-tree whose + * top is the entry-time value of *pos. + */ static void findoprnd(ITEM *ptr, int32 *pos) { + int32 mypos; + /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); + /* get the position this call is supposed to update */ + mypos = *pos; + Assert(mypos >= 0); + + /* in all cases, we should decrement *pos to advance over this item */ + (*pos)--; + #ifdef BS_DEBUG - elog(DEBUG3, (ptr[*pos].type == OPR) ? - "%d %c" : "%d %d", *pos, ptr[*pos].val); + elog(DEBUG3, (ptr[mypos].type == OPR) ? + "%d %c" : "%d %d", mypos, ptr[mypos].val); #endif - if (ptr[*pos].type == VAL) + + if (ptr[mypos].type == VAL) { - ptr[*pos].left = 0; - (*pos)--; + /* base case: a VAL has no operand, so just set its left to zero */ + ptr[mypos].left = 0; } - else if (ptr[*pos].val == (int32) '!') + else if (ptr[mypos].val == (int32) '!') { - ptr[*pos].left = -1; - (*pos)--; + /* unary operator, likewise easy: operand is just before it */ + ptr[mypos].left = -1; + /* recurse to scan operand */ findoprnd(ptr, pos); } else { - ITEM *curitem = &ptr[*pos]; - int32 tmp = *pos; + /* binary operator */ + int32 delta; - (*pos)--; + /* recurse to scan right operand */ findoprnd(ptr, pos); - curitem->left = *pos - tmp; + /* we must fill left with offset to left operand's top */ + /* abs(delta) < QUERYTYPEMAXITEMS, so it can't overflow ... */ + delta = *pos - mypos; + /* ... but it might be too large to fit in the 16-bit left field */ + Assert(delta < 0); + if (unlikely(delta < PG_INT16_MIN)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("query_int expression is too complex"))); + ptr[mypos].left = (int16) delta; + /* recurse to scan left operand */ findoprnd(ptr, pos); } } @@ -513,6 +544,7 @@ bqarr_in(PG_FUNCTION_ARGS) query->size = state.num; ptr = GETQUERY(query); + /* fill the query array from the data makepol constructed */ for (i = state.num - 1; i >= 0; i--) { ptr[i].type = state.str->type; @@ -522,8 +554,12 @@ bqarr_in(PG_FUNCTION_ARGS) state.str = tmp; } + /* now fill the "left" fields */ pos = query->size - 1; findoprnd(ptr, &pos); + /* if successful, findoprnd should have scanned the whole array */ + Assert(pos == -1); + #ifdef BS_DEBUG initStringInfo(&pbuf); for (i = 0; i < query->size; i++) diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c index 46019a0e83a..540b678b038 100644 --- a/contrib/ltree/lquery_op.c +++ b/contrib/ltree/lquery_op.c @@ -41,8 +41,7 @@ getlexeme(char *start, char *end, int *len) } bool -compare_subnode(ltree_level *t, char *qn, int len, - ltree_prefix_eq_func prefix_eq, bool anyend) +compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci) { char *endt = t->name + t->len; char *endq = qn + len; @@ -57,10 +56,8 @@ compare_subnode(ltree_level *t, char *qn, int len, isok = false; while ((tn = getlexeme(tn, endt, &lent)) != NULL) { - if ((lent == lenq || (lent > lenq && anyend)) && - (*prefix_eq) (qn, lenq, tn, lent)) + if (ltree_label_match(qn, lenq, tn, lent, prefix, ci)) { - isok = true; break; } @@ -76,31 +73,40 @@ compare_subnode(ltree_level *t, char *qn, int len, } /* - * Check if 'a' is a prefix of 'b'. + * Check if the label matches the predicate string. If 'prefix' is true, then + * the predicate string is treated as a prefix. If 'ci' is true, then the + * predicate string is case-insensitive (and locale-aware). */ bool -ltree_prefix_eq(const char *a, size_t a_sz, const char *b, size_t b_sz) +ltree_label_match(const char *pred, size_t pred_len, const char *label, + size_t label_len, bool prefix, bool ci) { - if (a_sz > b_sz) + char *fpred; /* casefolded predicate */ + size_t fpred_len; + char *flabel; /* casefolded label */ + size_t flabel_len; + bool res; + + /* fast path for binary match or binary prefix match */ + if ((pred_len == label_len || (prefix && pred_len < label_len)) && + strncmp(pred, label, pred_len) == 0) + return true; + else if (!ci) return false; - else - return (strncmp(a, b, a_sz) == 0); -} -/* - * Case-insensitive check if 'a' is a prefix of 'b'. - */ -bool -ltree_prefix_eq_ci(const char *a, size_t a_sz, const char *b, size_t b_sz) -{ - char *al = str_tolower(a, a_sz, DEFAULT_COLLATION_OID); - char *bl = str_tolower(b, b_sz, DEFAULT_COLLATION_OID); - bool res; + fpred = str_tolower(pred, pred_len, DEFAULT_COLLATION_OID); + fpred_len = strlen(fpred); + flabel = str_tolower(label, label_len, DEFAULT_COLLATION_OID); + flabel_len = strlen(flabel); - res = (strncmp(al, bl, a_sz) == 0); + if ((fpred_len == flabel_len || (prefix && fpred_len < flabel_len)) && + strncmp(fpred, flabel, fpred_len) == 0) + res = true; + else + res = false; - pfree(al); - pfree(bl); + pfree(fpred); + pfree(flabel); return res; } @@ -125,19 +131,16 @@ checkLevel(lquery_level *curq, ltree_level *curt) for (int i = 0; i < curq->numvar; i++) { - ltree_prefix_eq_func prefix_eq; - - prefix_eq = (curvar->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq; + bool prefix = (curvar->flag & LVAR_ANYEND); + bool ci = (curvar->flag & LVAR_INCASE); if (curvar->flag & LVAR_SUBLEXEME) { - if (compare_subnode(curt, curvar->name, curvar->len, prefix_eq, - (curvar->flag & LVAR_ANYEND))) + if (compare_subnode(curt, curvar->name, curvar->len, prefix, ci)) return success; } - else if ((curvar->len == curt->len || - (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) && - (*prefix_eq) (curvar->name, curvar->len, curt->name, curt->len)) + else if (ltree_label_match(curvar->name, curvar->len, curt->name, + curt->len, prefix, ci)) return success; curvar = LVAR_NEXT(curvar); diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h index a37ee6b2c67..3673b9d74c8 100644 --- a/contrib/ltree/ltree.h +++ b/contrib/ltree/ltree.h @@ -157,8 +157,6 @@ typedef struct char data[FLEXIBLE_ARRAY_MEMBER]; } ltxtquery; -typedef bool (*ltree_prefix_eq_func) (const char *, size_t, const char *, size_t); - #define HDRSIZEQT MAXALIGN(VARHDRSZ + sizeof(int32)) #define COMPUTESIZE(size,lenofoperand) ( HDRSIZEQT + (size) * sizeof(ITEM) + (lenofoperand) ) #define LTXTQUERY_TOO_BIG(size,lenofoperand) \ @@ -209,11 +207,11 @@ bool ltree_execute(ITEM *curitem, void *checkval, int ltree_compare(const ltree *a, const ltree *b); bool inner_isparent(const ltree *c, const ltree *p); -bool compare_subnode(ltree_level *t, char *qn, int len, - ltree_prefix_eq_func prefix_eq, bool anyend); +bool compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci); ltree *lca_inner(ltree **a, int len); -bool ltree_prefix_eq(const char *a, size_t a_sz, const char *b, size_t b_sz); -bool ltree_prefix_eq_ci(const char *a, size_t a_sz, const char *b, size_t b_sz); +bool ltree_label_match(const char *pred, size_t pred_len, + const char *label, size_t label_len, + bool prefix, bool ci); /* fmgr macros for ltree objects */ #define DatumGetLtreeP(X) ((ltree *) PG_DETOAST_DATUM(X)) diff --git a/contrib/ltree/ltree_io.c b/contrib/ltree/ltree_io.c index 0a44a8c4691..385db479540 100644 --- a/contrib/ltree/ltree_io.c +++ b/contrib/ltree/ltree_io.c @@ -7,6 +7,7 @@ #include +#include "common/int.h" #include "crc32.h" #include "libpq/pqformat.h" #include "ltree.h" @@ -338,7 +339,12 @@ parse_lquery(const char *buf) lptr++; lptr->start = ptr; state = LQPRS_WAITDELIM; - curqlevel->numvar++; + if (pg_add_u16_overflow(curqlevel->numvar, 1, &curqlevel->numvar)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("lquery level has too many variants"), + errdetail("Number of variants exceeds the maximum allowed (%d).", + PG_UINT16_MAX))); } else UNCHAR; @@ -530,7 +536,16 @@ parse_lquery(const char *buf) lptr = GETVAR(curqlevel); while (lptr - GETVAR(curqlevel) < curqlevel->numvar) { - cur->totallen += MAXALIGN(LVAR_HDRSIZE + lptr->len); + int newlen = cur->totallen + MAXALIGN(LVAR_HDRSIZE + lptr->len); + + if (newlen > PG_UINT16_MAX) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("lquery level is too large"), + errdetail("Total size of level exceeds the maximum allowed (%d bytes).", + PG_UINT16_MAX))); + cur->totallen = (uint16) newlen; + lrptr->len = lptr->len; lrptr->flag = lptr->flag; lrptr->val = ltree_crc32_sz(lptr->start, lptr->len); diff --git a/contrib/ltree/ltxtquery_io.c b/contrib/ltree/ltxtquery_io.c index bda2d971021..a4e2e2b33ad 100644 --- a/contrib/ltree/ltxtquery_io.c +++ b/contrib/ltree/ltxtquery_io.c @@ -270,31 +270,60 @@ makepol(QPRS_STATE *state) return END; } +/* + * Recursively fill the "left" fields of an ITEM array that represents + * a valid postfix tree. + * + * ptr: starting element of array + * pos: in/out argument, the array index this call is responsible to fill + * + * At exit, *pos has been incremented to point after the sub-tree whose + * top is the entry-time value of *pos. + */ static void findoprnd(ITEM *ptr, int32 *pos) { + int32 mypos; + /* since this function recurses, it could be driven to stack overflow. */ check_stack_depth(); - if (ptr[*pos].type == VAL || ptr[*pos].type == VALTRUE) + /* get the position this call is supposed to update */ + mypos = *pos; + + /* in all cases, we should increment *pos to advance over this item */ + (*pos)++; + + if (ptr[mypos].type == VAL || ptr[mypos].type == VALTRUE) { - ptr[*pos].left = 0; - (*pos)++; + /* base case: a VAL has no operand, so just set its left to zero */ + ptr[mypos].left = 0; } - else if (ptr[*pos].val == (int32) '!') + else if (ptr[mypos].val == (int32) '!') { - ptr[*pos].left = 1; - (*pos)++; + /* unary operator, likewise easy: operand is just after it */ + ptr[mypos].left = 1; + /* recurse to scan operand */ findoprnd(ptr, pos); } else { - ITEM *curitem = &ptr[*pos]; - int32 tmp = *pos; + /* binary operator */ + int32 delta; - (*pos)++; + /* recurse to scan right operand */ findoprnd(ptr, pos); - curitem->left = *pos - tmp; + /* we must fill left with offset to left operand's top */ + /* delta can't overflow, see LTXTQUERY_TOO_BIG ... */ + delta = *pos - mypos; + /* ... but it might be too large to fit in the 16-bit left field */ + Assert(delta > 0); + if (unlikely(delta > PG_INT16_MAX)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("ltxtquery is too large"))); + ptr[mypos].left = (int16) delta; + /* recurse to scan left operand */ findoprnd(ptr, pos); } } @@ -371,6 +400,8 @@ queryin(char *buf) /* set left operand's position for every operator */ pos = 0; findoprnd(ptr, &pos); + /* if successful, findoprnd should have scanned the whole array */ + Assert(pos == state.num); return query; } diff --git a/contrib/ltree/ltxtquery_op.c b/contrib/ltree/ltxtquery_op.c index 3dcbab2c484..0e6612ff77a 100644 --- a/contrib/ltree/ltxtquery_op.c +++ b/contrib/ltree/ltxtquery_op.c @@ -58,19 +58,18 @@ checkcondition_str(void *checkval, ITEM *val) ltree_level *level = LTREE_FIRST(((CHKVAL *) checkval)->node); int tlen = ((CHKVAL *) checkval)->node->numlevel; char *op = ((CHKVAL *) checkval)->operand + val->distance; - ltree_prefix_eq_func prefix_eq; + bool prefix = (val->flag & LVAR_ANYEND); + bool ci = (val->flag & LVAR_INCASE); - prefix_eq = (val->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq; while (tlen > 0) { if (val->flag & LVAR_SUBLEXEME) { - if (compare_subnode(level, op, val->length, prefix_eq, (val->flag & LVAR_ANYEND))) + if (compare_subnode(level, op, val->length, prefix, ci)) return true; } - else if ((val->length == level->len || - (level->len > val->length && (val->flag & LVAR_ANYEND))) && - (*prefix_eq) (op, val->length, level->name, level->len)) + else if (ltree_label_match(op, val->length, level->name, level->len, + prefix, ci)) return true; tlen--; diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 93d238ba34a..8d71ed71321 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -785,7 +785,7 @@ pgss_shmem_shutdown(int code, Datum arg) if (fwrite(&pgss->stats, sizeof(pgssGlobalStats), 1, file) != 1) goto error; - free(qbuffer); + pfree(qbuffer); qbuffer = NULL; if (FreeFile(file)) @@ -810,7 +810,7 @@ pgss_shmem_shutdown(int code, Datum arg) errmsg("could not write file \"%s\": %m", PGSS_DUMP_FILE ".tmp"))); if (qbuffer) - free(qbuffer); + pfree(qbuffer); if (file) FreeFile(file); unlink(PGSS_DUMP_FILE ".tmp"); @@ -1670,7 +1670,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo, pgss->gc_count != gc_count) { if (qbuffer) - free(qbuffer); + pfree(qbuffer); qbuffer = qtext_load_file(&qbuffer_size); } } @@ -1855,7 +1855,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo, LWLockRelease(pgss->lock); if (qbuffer) - free(qbuffer); + pfree(qbuffer); } /* Number of output arguments (columns) for pg_stat_statements_info */ @@ -2163,7 +2163,7 @@ qtext_store(const char *query, int query_len, } /* - * Read the external query text file into a malloc'd buffer. + * Read the external query text file into a palloc'd buffer. * * Returns NULL (without throwing an error) if unable to read, eg * file not there or insufficient memory. @@ -2205,7 +2205,7 @@ qtext_load_file(Size *buffer_size) /* Allocate buffer; beware that off_t might be wider than size_t */ if (stat.st_size <= MaxAllocHugeSize) - buf = (char *) malloc(stat.st_size); + buf = (char *) palloc_extended(stat.st_size, MCXT_ALLOC_HUGE | MCXT_ALLOC_NO_OOM); else buf = NULL; if (buf == NULL) @@ -2244,7 +2244,7 @@ qtext_load_file(Size *buffer_size) (errcode_for_file_access(), errmsg("could not read file \"%s\": %m", PGSS_TEXT_FILE))); - free(buf); + pfree(buf); CloseTransientFile(fd); return NULL; } @@ -2459,7 +2459,7 @@ gc_qtexts(void) else pgss->mean_query_len = ASSUMED_LENGTH_INIT; - free(qbuffer); + pfree(qbuffer); /* * OK, count a garbage collection cycle. (Note: even though we have @@ -2477,7 +2477,7 @@ gc_qtexts(void) if (qfile) FreeFile(qfile); if (qbuffer) - free(qbuffer); + pfree(qbuffer); /* * Since the contents of the external file are now uncertain, mark all diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c index db2a2c42229..7c9fbca4227 100644 --- a/contrib/postgres_fdw/connection.c +++ b/contrib/postgres_fdw/connection.c @@ -535,8 +535,9 @@ disconnect_pg_server(ConnCacheEntry *entry) } /* - * Return true if the password_required is defined and false for this user - * mapping, otherwise false. The mapping has been pre-validated. + * Check and return the value of password_required, if defined; otherwise, + * return true, which is the default value of it. The mapping has been + * pre-validated. */ static bool UserMappingPasswordRequired(UserMapping *user) @@ -1193,6 +1194,11 @@ pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue) * Such connections can't safely be further used. Re-establishing the * connection would change the snapshot and roll back any writes already * performed, so that's not an option, either. Thus, we must abort. + * + * Note: there might be open cursors that use the connection, so even if the + * connection cache entry is marked as such, we will retain it until abort + * cleanup of the main transaction, to ensure such open cursors can safely + * refer to the PGconn for the connection. */ static void pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry) @@ -1203,15 +1209,12 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry) if (entry->conn == NULL || !entry->changing_xact_state) return; - /* make sure this entry is inactive */ - disconnect_pg_server(entry); - /* find server name to be shown in the message below */ server = GetForeignServer(entry->serverid); ereport(ERROR, (errcode(ERRCODE_CONNECTION_EXCEPTION), - errmsg("connection to server \"%s\" was lost", + errmsg("connection to server \"%s\" cannot be used due to abort cleanup failure", server->servername))); } diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 6ef1af29aaa..41beb2f3b07 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -11838,3 +11838,79 @@ SELECT * FROM prem2; ALTER SERVER loopback OPTIONS (DROP parallel_commit); ALTER SERVER loopback2 OPTIONS (DROP parallel_commit); +-- =================================================================== +-- test cleanup of failed connections on abort +-- =================================================================== +CREATE VIEW my_backend_pid (pid) AS SELECT pg_backend_pid(); +CREATE FOREIGN TABLE remote_backend_pid (pid int) + SERVER loopback OPTIONS (table_name 'my_backend_pid'); +CREATE FUNCTION wait_for_backend_termination(int) RETURNS void AS $$ + BEGIN + WHILE (SELECT count(*) FROM pg_stat_activity WHERE pid = $1) > 0 + LOOP + PERFORM pg_stat_clear_snapshot(); + END LOOP; + END +$$ LANGUAGE plpgsql; +SET client_min_messages = 'ERROR'; +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +SAVEPOINT s; +SELECT pid AS remote_pid FROM remote_backend_pid \gset +SELECT pg_terminate_backend(:remote_pid); + pg_terminate_backend +---------------------- + t +(1 row) + +SELECT wait_for_backend_termination(:remote_pid); + wait_for_backend_termination +------------------------------ + +(1 row) + +ROLLBACK TO SAVEPOINT s; +SELECT pid FROM remote_backend_pid; +ERROR: connection to server "loopback" cannot be used due to abort cleanup failure +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +FETCH c; +ERROR: no connection to the server +CONTEXT: remote SQL command: DECLARE c1 CURSOR FOR +SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST +ABORT; +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +FETCH c; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------------------+------------------------------+--------------------------+----+------------+----- + 1 | 2 | 00001_trig_update | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +SAVEPOINT s; +SELECT pid AS remote_pid FROM remote_backend_pid \gset +SELECT pg_terminate_backend(:remote_pid); + pg_terminate_backend +---------------------- + t +(1 row) + +SELECT wait_for_backend_termination(:remote_pid); + wait_for_backend_termination +------------------------------ + +(1 row) + +ROLLBACK TO SAVEPOINT s; +SELECT pid FROM remote_backend_pid; +ERROR: connection to server "loopback" cannot be used due to abort cleanup failure +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +CLOSE c; +ERROR: no connection to the server +CONTEXT: remote SQL command: CLOSE c1 +ABORT; +RESET client_min_messages; +DROP FUNCTION wait_for_backend_termination(int); +DROP FOREIGN TABLE remote_backend_pid; +DROP VIEW my_backend_pid; diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 4f188c7da6b..050a2c98bdc 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -3931,3 +3931,54 @@ SELECT * FROM prem2; ALTER SERVER loopback OPTIONS (DROP parallel_commit); ALTER SERVER loopback2 OPTIONS (DROP parallel_commit); + +-- =================================================================== +-- test cleanup of failed connections on abort +-- =================================================================== + +CREATE VIEW my_backend_pid (pid) AS SELECT pg_backend_pid(); +CREATE FOREIGN TABLE remote_backend_pid (pid int) + SERVER loopback OPTIONS (table_name 'my_backend_pid'); +CREATE FUNCTION wait_for_backend_termination(int) RETURNS void AS $$ + BEGIN + WHILE (SELECT count(*) FROM pg_stat_activity WHERE pid = $1) > 0 + LOOP + PERFORM pg_stat_clear_snapshot(); + END LOOP; + END +$$ LANGUAGE plpgsql; + +SET client_min_messages = 'ERROR'; + +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +SAVEPOINT s; +SELECT pid AS remote_pid FROM remote_backend_pid \gset +SELECT pg_terminate_backend(:remote_pid); +SELECT wait_for_backend_termination(:remote_pid); +ROLLBACK TO SAVEPOINT s; +SELECT pid FROM remote_backend_pid; +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +FETCH c; +ABORT; + +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +FETCH c; +SAVEPOINT s; +SELECT pid AS remote_pid FROM remote_backend_pid \gset +SELECT pg_terminate_backend(:remote_pid); +SELECT wait_for_backend_termination(:remote_pid); +ROLLBACK TO SAVEPOINT s; +SELECT pid FROM remote_backend_pid; +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +CLOSE c; +ABORT; + +RESET client_min_messages; + +DROP FUNCTION wait_for_backend_termination(int); +DROP FOREIGN TABLE remote_backend_pid; +DROP VIEW my_backend_pid; diff --git a/contrib/spi/refint.c b/contrib/spi/refint.c index 18062eb1cff..2733e807046 100644 --- a/contrib/spi/refint.c +++ b/contrib/spi/refint.c @@ -166,21 +166,24 @@ check_primary_key(PG_FUNCTION_ARGS) if (plan->nplans <= 0) { SPIPlanPtr pplan; - char sql[8192]; + StringInfoData sql; + + initStringInfo(&sql); /* * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 = * $1 [AND Pkey2 = $2 [...]] */ - snprintf(sql, sizeof(sql), "select 1 from %s where ", relname); - for (i = 0; i < nkeys; i++) + appendStringInfo(&sql, "select 1 from %s where ", relname); + for (i = 1; i <= nkeys; i++) { - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s", - args[i + nkeys + 1], i + 1, (i < nkeys - 1) ? "and " : ""); + appendStringInfo(&sql, "%s = $%d ", args[i + nkeys], i); + if (i < nkeys) + appendStringInfoString(&sql, "and "); } /* Prepare plan for query */ - pplan = SPI_prepare(sql, nkeys, argtypes); + pplan = SPI_prepare(sql.data, nkeys, argtypes); if (pplan == NULL) /* internal error */ elog(ERROR, "check_primary_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result)); @@ -196,6 +199,8 @@ check_primary_key(PG_FUNCTION_ARGS) sizeof(SPIPlanPtr)); *(plan->splan) = pplan; plan->nplans = 1; + + pfree(sql.data); } /* @@ -416,7 +421,6 @@ check_foreign_key(PG_FUNCTION_ARGS) if (plan->nplans <= 0) { SPIPlanPtr pplan; - char sql[8192]; char **args2 = args; plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext, @@ -424,6 +428,10 @@ check_foreign_key(PG_FUNCTION_ARGS) for (r = 0; r < nrefs; r++) { + StringInfoData sql; + + initStringInfo(&sql); + relname = args2[0]; /*--------- @@ -437,8 +445,7 @@ check_foreign_key(PG_FUNCTION_ARGS) *--------- */ if (action == 'r') - - snprintf(sql, sizeof(sql), "select 1 from %s where ", relname); + appendStringInfo(&sql, "select 1 from %s where ", relname); /*--------- * For 'C'ascade action we construct DELETE query @@ -465,42 +472,23 @@ check_foreign_key(PG_FUNCTION_ARGS) char *nv; int k; - snprintf(sql, sizeof(sql), "update %s set ", relname); + appendStringInfo(&sql, "update %s set ", relname); for (k = 1; k <= nkeys; k++) { - int is_char_type = 0; - char *type; - fn = SPI_fnumber(tupdesc, args_temp[k - 1]); Assert(fn > 0); /* already checked above */ nv = SPI_getvalue(newtuple, tupdesc, fn); - type = SPI_gettype(tupdesc, fn); - - if (strcmp(type, "text") == 0 || - strcmp(type, "varchar") == 0 || - strcmp(type, "char") == 0 || - strcmp(type, "bpchar") == 0 || - strcmp(type, "date") == 0 || - strcmp(type, "timestamp") == 0) - is_char_type = 1; -#ifdef DEBUG_QUERY - elog(DEBUG4, "check_foreign_key Debug value %s type %s %d", - nv, type, is_char_type); -#endif - /* - * is_char_type =1 i set ' ' for define a new value - */ - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), - " %s = %s%s%s %s ", - args2[k], (is_char_type > 0) ? "'" : "", - nv, (is_char_type > 0) ? "'" : "", (k < nkeys) ? ", " : ""); + appendStringInfo(&sql, " %s = %s ", + args2[k], quote_literal_cstr(nv)); + if (k < nkeys) + appendStringInfoString(&sql, ", "); } - strcat(sql, " where "); + appendStringInfoString(&sql, " where "); } else /* DELETE */ - snprintf(sql, sizeof(sql), "delete from %s where ", relname); + appendStringInfo(&sql, "delete from %s where ", relname); } /* @@ -511,25 +499,26 @@ check_foreign_key(PG_FUNCTION_ARGS) */ else if (action == 's') { - snprintf(sql, sizeof(sql), "update %s set ", relname); + appendStringInfo(&sql, "update %s set ", relname); for (i = 1; i <= nkeys; i++) { - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), - "%s = null%s", - args2[i], (i < nkeys) ? ", " : ""); + appendStringInfo(&sql, "%s = null", args2[i]); + if (i < nkeys) + appendStringInfoString(&sql, ", "); } - strcat(sql, " where "); + appendStringInfoString(&sql, " where "); } /* Construct WHERE qual */ for (i = 1; i <= nkeys; i++) { - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s", - args2[i], i, (i < nkeys) ? "and " : ""); + appendStringInfo(&sql, "%s = $%d ", args2[i], i); + if (i < nkeys) + appendStringInfoString(&sql, "and "); } /* Prepare plan for query */ - pplan = SPI_prepare(sql, nkeys, argtypes); + pplan = SPI_prepare(sql.data, nkeys, argtypes); if (pplan == NULL) /* internal error */ elog(ERROR, "check_foreign_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result)); @@ -545,11 +534,14 @@ check_foreign_key(PG_FUNCTION_ARGS) plan->splan[r] = pplan; args2 += nkeys + 1; /* to the next relation */ + +#ifdef DEBUG_QUERY + elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql.data); +#endif + + pfree(sql.data); } plan->nplans = nrefs; -#ifdef DEBUG_QUERY - elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql); -#endif } /* diff --git a/contrib/xml2/expected/xml2.out b/contrib/xml2/expected/xml2.out index eba6ae60364..3027e4df868 100644 --- a/contrib/xml2/expected/xml2.out +++ b/contrib/xml2/expected/xml2.out @@ -222,3 +222,13 @@ $$ $$); ERROR: failed to apply stylesheet +-- empty output +select xslt_process('', +$$ +$$); + xslt_process +-------------- + +(1 row) + diff --git a/contrib/xml2/expected/xml2_1.out b/contrib/xml2/expected/xml2_1.out index bac90e5a2a9..ed3e399aa31 100644 --- a/contrib/xml2/expected/xml2_1.out +++ b/contrib/xml2/expected/xml2_1.out @@ -166,3 +166,9 @@ $$ $$); ERROR: xslt_process() is not available without libxslt +-- empty output +select xslt_process('', +$$ +$$); +ERROR: xslt_process() is not available without libxslt diff --git a/contrib/xml2/sql/xml2.sql b/contrib/xml2/sql/xml2.sql index ac49cfa7c52..c7fe0c102da 100644 --- a/contrib/xml2/sql/xml2.sql +++ b/contrib/xml2/sql/xml2.sql @@ -137,3 +137,9 @@ $$ $$); + +-- empty output +select xslt_process('', +$$ +$$); diff --git a/contrib/xml2/xslt_proc.c b/contrib/xml2/xslt_proc.c index f30a3a42c03..9f3bf527d80 100644 --- a/contrib/xml2/xslt_proc.c +++ b/contrib/xml2/xslt_proc.c @@ -179,7 +179,14 @@ xslt_process(PG_FUNCTION_ARGS) if (resstat < 0) PG_RETURN_NULL(); - result = cstring_to_text_with_len((char *) resstr, reslen); + /* + * If an empty string has been returned, resstr would be NULL. In + * this case, assume that the result is an empty string. + */ + if (reslen == 0) + result = cstring_to_text(""); + else + result = cstring_to_text_with_len((char *) resstr, reslen); if (resstr) xmlFree(resstr); diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index f40becbcf22..37d586cc167 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -11444,7 +11444,9 @@ LOG: CleanUpLock: deleting: lock(0xb7acd844) id(24688,24696,0,0,0,1) If LLVM has the required functionality, register generated functions with GDB. This makes debugging easier. The default setting is off. - This parameter can only be set at server start. + Only superusers and users with the appropriate SET + privilege can change this parameter at session start, + and it cannot be changed at all within a session. @@ -11495,7 +11497,9 @@ LOG: CleanUpLock: deleting: lock(0xb7acd844) id(24688,24696,0,0,0,1) This writes out files to ~/.debug/jit/; the user is responsible for performing cleanup when desired. The default setting is off. - This parameter can only be set at server start. + Only superusers and users with the appropriate SET + privilege can change this parameter at session start, + and it cannot be changed at all within a session. diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index a3877674863..48980614415 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -5743,15 +5743,20 @@ int PQrequestCancel(PGconn *conn); to send simple function calls to the server. - + - This interface is somewhat obsolete, as one can achieve similar + This interface is unsafe and should not be used. When + result_is_int is set to 0, + PQfn may write data beyond the end of + result_buf, regardless of whether the buffer has + enough space for the requested number of bytes. Furthermore, it is + obsolete, as one can achieve similar performance and greater functionality by setting up a prepared statement to define the function call. Then, executing the statement with binary transmission of parameters and results substitutes for a fast-path function call. - + The function PQfnPQfn diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml index e6096c67e06..d44eeca779a 100644 --- a/doc/src/sgml/logical-replication.sgml +++ b/doc/src/sgml/logical-replication.sgml @@ -67,7 +67,7 @@ Replicating between PostgreSQL instances on different platforms (for - example Linux to Windows) + example Linux to Windows). @@ -88,7 +88,7 @@ The subscriber database behaves in the same way as any other PostgreSQL instance and can be used as a publisher for other databases by defining its - own publications. When the subscriber is treated as read-only by + own publications. When the subscriber is treated as read-only by an application, there will be no conflicts from a single subscription. On the other hand, if there are other writes done either by an application or by other subscribers to the same set of tables, conflicts can arise. @@ -170,8 +170,8 @@ A subscription is the downstream side of logical replication. The node where a subscription is defined is referred to as the subscriber. A subscription defines the connection - to another database and set of publications (one or more) to which it wants - to subscribe. + to another database and the set of publications (one or more) to which it + wants to subscribe. @@ -668,7 +668,7 @@ test_sub=# SELECT * FROM t3; - If the subscriber is in a release prior to 15, copy pre-existing data + If the subscriber is in a release prior to 15, copying pre-existing data doesn't use row filters even if they are defined in the publication. This is because old releases can only copy the entire table data. @@ -1482,7 +1482,7 @@ CONTEXT: processing remote data for replication origin "pg_16395" during "INSER Initial Snapshot - The initial data in existing subscribed tables are snapshotted and + The initial data in existing subscribed tables is snapshotted and copied in a parallel instance of a special kind of apply process. This process will create its own replication slot and copy the existing data. As soon as the copy is finished the table contents will become diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml index 5d373ae9a34..9f1a63fc96e 100644 --- a/doc/src/sgml/ref/alter_foreign_table.sgml +++ b/doc/src/sgml/ref/alter_foreign_table.sgml @@ -32,7 +32,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] namewhere action is one of: - ADD [ COLUMN ] column_name data_type [ COLLATE collation ] [ column_constraint [ ... ] ] + ADD [ COLUMN ] [ IF NOT EXISTS ] column_name data_type [ COLLATE collation ] [ column_constraint [ ... ] ] DROP [ COLUMN ] [ IF EXISTS ] column_name [ RESTRICT | CASCADE ] ALTER [ COLUMN ] column_name [ SET DATA ] TYPE data_type [ COLLATE collation ] ALTER [ COLUMN ] column_name SET DEFAULT expression @@ -67,11 +67,13 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name - ADD COLUMN + ADD [ COLUMN ] [ IF NOT EXISTS ] This form adds a new column to the foreign table, using the same syntax as CREATE FOREIGN TABLE. + If IF NOT EXISTS is specified and a column already + exists with this name, no error is thrown. Unlike the case when adding a column to a regular table, nothing happens to the underlying storage: this action simply declares that some new column is now accessible through the foreign table. @@ -80,7 +82,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name - DROP COLUMN [ IF EXISTS ] + DROP [ COLUMN ] [ IF EXISTS ] This form drops a column from a foreign table. diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgml index 1d42d05d858..fb7096c16ea 100644 --- a/doc/src/sgml/ref/alter_index.sgml +++ b/doc/src/sgml/ref/alter_index.sgml @@ -97,6 +97,11 @@ ALTER INDEX ALL IN TABLESPACE name index cannot be dropped by itself, and will automatically be dropped if its parent index is dropped. + + If the named index is already attached to the altered index, the + command will attempt to validate the parent index if the parent is + currently invalid. + diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index acdd7f684bf..caf9c071142 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -154,7 +154,7 @@ WITH ( MODULUS numeric_literal, REM - ADD COLUMN [ IF NOT EXISTS ] + ADD [ COLUMN ] [ IF NOT EXISTS ] This form adds a new column to the table, using the same syntax as @@ -166,7 +166,7 @@ WITH ( MODULUS numeric_literal, REM - DROP COLUMN [ IF EXISTS ] + DROP [ COLUMN ] [ IF EXISTS ] This form drops a column from a table. Indexes and diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index fd3f4652491..bc2f02c3af3 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -80,14 +80,16 @@ COMMENT ON Description - COMMENT stores a comment about a database object. + COMMENT stores, replaces, or removes the comment on a + database object. - Only one comment string is stored for each object, so to modify a comment, - issue a new COMMENT command for the same object. To remove a - comment, write NULL in place of the text string. - Comments are automatically dropped when their object is dropped. + Only one comment string is stored for each object. Issuing a new + COMMENT command for the same object replaces the + existing comment. Specifying NULL or an empty + string ('') removes the comment. Comments are + automatically dropped when their object is dropped. @@ -265,7 +267,8 @@ COMMENT ON string_literal - The new comment contents, written as a string literal. + The new comment contents, written as a string literal. An empty string + ('') removes the comment. @@ -274,7 +277,7 @@ COMMENT ON NULL - Write NULL to drop the comment. + Write NULL to remove the comment. @@ -361,6 +364,7 @@ COMMENT ON TRANSFORM FOR hstore LANGUAGE plpython3u IS 'Transform between hstore COMMENT ON TRIGGER my_trigger ON my_table IS 'Used for RI'; COMMENT ON TYPE complex IS 'Complex number data type'; COMMENT ON VIEW my_view IS 'View of departmental costs'; +COMMENT ON VIEW my_view IS NULL; diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml index 90ce0c61bd2..93432397aff 100644 --- a/doc/src/sgml/ref/create_publication.sgml +++ b/doc/src/sgml/ref/create_publication.sgml @@ -152,7 +152,7 @@ CREATE PUBLICATION name - When a partitioned table is published via schema level publication, all + When a partitioned table is published via a schema-level publication, all of its existing and future partitions are implicitly considered to be part of the publication, regardless of whether they are from the publication schema or not. So, even operations that are performed directly on a @@ -175,7 +175,7 @@ CREATE PUBLICATION name This parameter determines which DML operations will be published by - the new publication to the subscribers. The value is + the new publication to the subscribers. The value is a comma-separated list of operations. The allowed operations are insert, update, delete, and truncate. diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 407120f4d56..8b26e086431 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -643,9 +643,10 @@ WITH ( MODULUS numeric_literal, REM INCLUDING COMMENTS - Comments for the copied columns, constraints, and indexes will be + Comments for the copied columns, check constraints, + indexes, and extended statistics will be copied. The default behavior is to exclude comments, resulting in - the copied columns and constraints in the new table having no + the corresponding objects in the new table having no comments. diff --git a/doc/src/sgml/ref/drop_subscription.sgml b/doc/src/sgml/ref/drop_subscription.sgml index d79f137c1e8..97ecca7f982 100644 --- a/doc/src/sgml/ref/drop_subscription.sgml +++ b/doc/src/sgml/ref/drop_subscription.sgml @@ -49,6 +49,16 @@ DROP SUBSCRIPTION [ IF EXISTS ] nameParameters + + IF EXISTS + + + Do not throw an error if the subscription does not exist. A notice is + issued in this case. + + + + name diff --git a/doc/src/sgml/ref/pg_ctl-ref.sgml b/doc/src/sgml/ref/pg_ctl-ref.sgml index a0287bb81d6..b762b002c02 100644 --- a/doc/src/sgml/ref/pg_ctl-ref.sgml +++ b/doc/src/sgml/ref/pg_ctl-ref.sgml @@ -299,8 +299,9 @@ PostgreSQL documentation Append the server log output to filename. If the file does not - exist, it is created. The umask is set to 077, - so access to the log file is disallowed to other users by default. + exist, it is created. By default, only the cluster owner can + access the log file. If group access is enabled in the cluster, + users in the same group as the cluster owner can also read it. diff --git a/doc/src/sgml/release-15.sgml b/doc/src/sgml/release-15.sgml index 15feb313ecb..3e8b4dc39c4 100644 --- a/doc/src/sgml/release-15.sgml +++ b/doc/src/sgml/release-15.sgml @@ -1,6 +1,1264 @@ + + Release 15.18 + + + Release date: + 2026-05-14 + + + + This release contains a variety of fixes from 15.17. + For information about new features in major release 15, see + . + + + + Migration to Version 15.18 + + + A dump/restore is not required for those running 15.X. + + + + However, if you are upgrading from a version earlier than 15.14, + see . + + + + + Changes + + + + + + + Prevent unbounded recursion while processing startup packets + (Michael Paquier) + § + § + + + + A malicious client could crash the connected backend by alternating + rejected SSL and GSS encryption requests indefinitely. + + + + The PostgreSQL Project thanks Calif.io + (in collaboration with Claude and Anthropic Research) for reporting + this problem. + (CVE-2026-6479) + + + + + + + Fix assorted integer overflows in memory-allocation calculations + (Tom Lane, Nathan Bossart, Heikki Linnakangas) + § + § + § + § + § + § + § + § + § + § + + + + Various places were incautious about the possibility of integer + overflow in calculations of how much memory to allocate. Overflow + would lead to allocating a too-small buffer which the caller would + then write past the end of. This would at least trigger server + crashes, and probably could be exploited for arbitrary code + execution. In many but by no means all cases, the hazard exists + only in 32-bit builds. + + + + The PostgreSQL Project thanks Xint Code, + Bruce Dang, Sven Klemm, and Pavel Kohout for reporting these problems. + (CVE-2026-6473) + + + + + + + Reject over-length options in ts_headline() + (Michael Paquier) + § + + + + The StartSel, StopSel + and FragmentDelimiter strings must not exceed + 32Kb in length, but this was not checked for. An over-length value + would typically crash the server. + + + + The PostgreSQL Project thanks + Xint Code for reporting this problem. + (CVE-2026-6473) + + + + + + + Guard against malicious time zone names + in timeofday() + and pg_strftime() (Tom Lane) + § + § + + + + A crafted time zone setting could pass % + sequences to snprintf(), potentially causing + crashes or disclosure of server memory. Another path to similar + results was to overflow the limited-size output buffer used + by pg_strftime(). + + + + The PostgreSQL Project thanks + Xint Code for reporting this problem. + (CVE-2026-6474) + + + + + + + When creating a multirange type, ensure the user + has CREATE privilege on the schema specified for + the multirange type (Jelte Fennema-Nio) + § + + + + The multirange type can be put into a different schema than its + parent range type, but we neglected to apply the required privilege + check when doing so. + + + + The PostgreSQL Project thanks + Jelte Fennema-Nio for reporting this problem. + (CVE-2026-6472) + + + + + + + Use timing-safe string comparisons in authentication code + (Michael Paquier) + § + § + + + + Use timingsafe_bcmp() instead + of memcpy() or strcmp() + when checking passwords, hashes, etc. It is not known whether the + data dependency of those functions is usefully exploitable in any of + these places, but in the interests of safety, replace them. + + + + The PostgreSQL Project thanks + Joe Conway for reporting this problem. + (CVE-2026-6478) + + + + + + + Mark PQfn() as unsafe, and avoid using it + within libpq (Nathan Bossart) + § + + + + For a non-integral result type, PQfn() is not + passed the size of the output buffer, so it cannot check that the + data returned by the server will fit. A malicious server could + therefore overwrite client memory. This is unfixable without an + API change, so mark the function as deprecated. Internally + to libpq, use a variant version that can + apply the missing check. + + + + The PostgreSQL Project thanks + Yu Kunpeng and Martin Heistermann for reporting this problem. + (CVE-2026-6477) + + + + + + + Prevent path traversal in pg_basebackup + and pg_rewind (Michael Paquier) + § + + + + These applications failed to validate output file paths read from + their input, so that a malicious source could overwrite any file + writable by these applications. Constrain where data can be written + by rejecting paths that are absolute or contain parent-directory + references. + + + + The PostgreSQL Project thanks XlabAI Team + of Tencent Xuanwu Lab and Valery Gubanov for reporting this problem. + (CVE-2026-6475) + + + + + + + Guard against field overflow + within contrib/intarray's query_int + type and contrib/ltree's ltxtquery + type (Tom Lane) + § + § + + + + Parsing of these query structures did not check for overflow of + 16-bit fields, so that construction of an invalid query tree was + possible. This can crash the server when executing the query. + + + + The PostgreSQL Project thanks + Xint Code for reporting this problem. + (CVE-2026-6473) + + + + + + + Guard against overly long values + of contrib/ltree's lquery type + (Michael Paquier) + § + + + + Values with more than 64K items caused internal overflows, + potentially resulting in stack smashes or wrong answers. + + + + The PostgreSQL Project thanks + Vergissmeinnicht, A1ex, and Jihe Wang + for reporting this problem. + (CVE-2026-6473) + + + + + + + Prevent SQL injection and buffer overruns + in contrib/spi (Nathan Bossart) + § + + + + check_foreign_key() was insufficiently careful + about quoting key values, and also used fixed-length buffers for + constructing queries. While this module is only meant as example + code, it still shouldn't contain such dangerous errors. + + + + The PostgreSQL Project thanks + Nikolay Samokhvalov for reporting this problem. + (CVE-2026-6637) + + + + + + + Check for nondeterministic collations before assuming that an + equality condition on a collatable type implies uniqueness + (Richard Guo) + § + § + + + + Numerous planner optimizations assume that, for example, at most one + table row can satisfy WHERE x = 'abc' if there is + a unique index on x. However this conclusion is + unsafe in general if the index and the WHERE + clause have different collations attached. It is safe when both + collations are deterministic, because that property essentially + requires that equality of two strings means bitwise equality. But + nondeterministic collations don't act that way, so that optimizing + on the assumption of unique matches can give wrong query answers if + either the WHERE clause or the index has a + nondeterministic collation. + + + + + + + Fix incorrect handling of NEW generated columns + in rule actions and rule qualifications (Richard Guo, Dean Rasheed) + § + + + + Previously, such column references would produce NULL + in INSERT cases, or be equivalent to + the OLD value in UPDATE cases. + + + + + + + Fix spurious generated columns are not supported in COPY FROM + WHERE conditions errors (Tom Lane) + § + + + + Use of a system column in a COPY FROM WHERE + condition could sometimes incorrectly report this error. + + + + + + + Correctly report a serialization failure + when MERGE encounters a concurrently-updated + tuple in repeatable-read or serializable mode (Tender Wang) + § + + + + Previously, such cases behaved the same as in lower isolation + levels. + + + + + + + Fix CREATE TABLE ... LIKE ... INCLUDING + STATISTICS for cases where the source table has dropped + column(s) (Julien Tachoires) + § + + + + In such cases, extended statistics objects could be copied + incorrectly, or the command could give an incorrect error. + + + + + + + Allow ALTER INDEX ... ATTACH PARTITION to mark + the parent index valid if appropriate (Sami Imseih) + § + + + + There are edge cases in which a partitioned index might remain + marked as invalid even when all its leaf indexes are valid. This + change provides a mechanism whereby a user can correct such a + situation without resorting to manual catalog updates. + + + + + + + Fix ALTER FOREIGN DATA WRAPPER to not drop the + wrapper object's dependency on its handler function (Jeff Davis) + § + + + + + + + Disallow making a composite type be a member of itself via a + multirange (Heikki Linnakangas) + § + + + + We already forbade such cases when the intermediate type is a + domain, array, composite type, or range; but multiranges were + overlooked. + + + + + + + Fix datum-image comparisons to be insensitive to sign-extension + variations (David Rowley) + § + + + + This fixes some situations that previously led to could not + find memoization table entry errors or wrong query results. + + + + + + + Fix incorrect logic for hashed IN/NOT + IN with non-strict equality operator (Chengpeng Yan) + § + + + + The previous coding could crash or give wrong answers. All built-in + data types have strict equality operators, so that this issue could + only arise with an extension data type. + + + + + + + Truncate overly-long locale-specific numeric symbols + in to_char() (Tom Lane) + § + + + + If a locale specified a currency symbol, thousands separator, or + decimal or sign symbol more than 8 bytes long, a buffer overrun was + possible. No such locales exist in the real world, and it's + impractical for an unprivileged attacker to install a malicious + locale definition underneath a Postgres server; but for safety's + sake check for overlength symbols and truncate if needed. + + + + + + + Prevent buffer overruns when parsing an affix file for + an Ispell dictionary (Tom Lane) + § + § + + + + A corrupt or malicious affix file could crash the server. + This is not considered a security issue because text search + configuration files are presumed trustworthy, but it still seems + worth fixing. + + + + + + + Guard against integer overflow in calculations of frame start and + end positions for window aggregates (Richard Guo) + § + + + + Very large user-specified offsets (close to INT64_MAX) could result + in errors or incorrect query results. + + + + + + + Fix incorrect behavior + of pg_stat_reset_single_table_counters() on a + shared catalog (Chao Li) + § + + + + Such cases had a side-effect of resetting the + current database's stat_reset_timestamp, which + was unintended. + + + + + + + Fix buffer overread when pglz_decompress() + receives corrupt input (Andrew Dunstan) + § + + + + It was possible to read a few bytes past the end of the input, which + in very unlucky cases might cause a crash. + + + + + + + Ensure that tuplestore data structures are internally consistent + even after an error (Tom Lane) + § + + + + The code was previously careless about this, which is fine most of + the time but is problematic for the tuplestore backing + a WITH HOLD cursor. In v15 and before this + leads to easily-reproducible crashes; later branches are not known + to be vulnerable, but it seems best to preserve consistency in all. + + + + + + + Fix premature NULL lag reporting + in pg_stat_replication (Shinya Kato) + § + + + + The lag columns frequently read as NULL even while replication + activity was happening. + + + + + + + Avoid rare flush failure when working with non-WAL-logged GiST + indexes (Tomas Vondra) + § + + + + A non-logged GiST index could nonetheless sometimes + produce xlog flush request n/nnnn + is not satisfied errors, due to incorrect selection of + a fake LSN to represent an insertion point. + + + + + + + Fix underestimate of required size of DSA page maps for odd-size + segments (Paul Bunn) + § + + + + This miscalculation led to out-of-bounds accesses and hence server + crashes. + + + + + + + Fix possible server crash when processing extended statistics on + expressions of extension data types (Michael Paquier) + § + + + + NULL pointer dereferences were possible if the data type's + typanalyze function does not compute any useful statistics. + No in-core typanalyze function behaves that way, but extensions + could. + + + + + + + If the startup process fails, properly shut down other child + processes before exiting the postmaster (Ayush Tiwari) + § + + + + The handling of this situation relied on a long-obsolete assumption + that no other postmaster children exist while the startup process is + running, so that immediate postmaster exit is acceptable. + Orphaned children would eventually notice the postmaster's death and + exit on their own, but a cleaner shutdown procedure is desirable. + + + + + + + Fix race condition between WAL replay of checkpoints and multixact + ID creations (Heikki Linnakangas) + § + + + + A standby server following WAL from a primary of an older minor + version could get into a crash-and-restart loop complaining + about could not access status of transaction. + + + + + + + Prevent indefinite wait in shutdown of a walsender process + (Anthonin Bonnefoy) + § + § + + + + At shutdown of a cluster that is publishing logical replication + data, the walsender waits for all pending WAL to be written out. + But it did not correctly request that to happen, so that in some + cases this could become an indefinite wait. + + + + + + + Ensure that changes to tables' free space maps are persisted during + recovery (Alexey Makhmutov) + § + + + + Previously, while WAL replay did update the free space map while + replaying operations that should change it, the map page buffer did + not get marked dirty if checksums are enabled, so that the changes + might never get written out. On a standby server, over time this + would result in a map wildly at variance with the table's actual + contents. While the map is only used as a hint, this condition + could cause significant performance degradation for some period of + time after the standby server is promoted to be active, until most + of the map has been repaired by updates. + + + + + + + Fix crashes in some ecpg functions when + called without any established connection (Shruthi Gowda) + § + + + + + + + Fix assorted bugs in backup decompression and tar-parsing code + (Andrew Dunstan, Tom Lane, Chao Li) + § + § + § + + + + The decompression and tar-file reading code used + in pg_basebackup + and pg_verifybackup mishandled tar-file + padding data, could corrupt LZ4-compressed data in edge cases, + failed to check for some unusual error conditions, failed to exit + after compression/decompression errors (leading to cascading error + reports), and leaked memory. + + + + + + + In pg_upgrade, take care to use the + correct protocol version when connecting to older source servers + (Jacob Champion) + § + + + + This could be problematic when attempting to upgrade from a pre-2018 + server. + + + + + + + In contrib/basic_archive, allow the archive + directory to be missing at startup (Nathan Bossart) + § + + + + Previously, the setting + of basic_archive.archive_directory was rejected + if it didn't point to an existing directory. This is undesirable + because archiving will be stuck indefinitely, even if the directory + appears later. + + + + + + + Fix contrib/ltree to cope when case-folding + changes a string's byte length (Jeff Davis) + § + + + + Previously, lquery patterns specifying case-insensitive + matching might fail to match labels they should match. + + + + + + + In contrib/pg_stat_statements, don't leak + memory if an error occurs while parsing the + pgss_query_texts.stat file (Heikki Linnakangas) + § + + + + + + + In contrib/postgres_fdw, avoid crash due to + premature cleanup of a failed connection (Etsuro Fujita) + § + + + + If a remote connection fails abort cleanup, we can't use it any + longer. But delay closing the connection object until end of + transaction, because there might still be references to it within + data structures such as open cursors. + + + + + + + Update time zone data files to tzdata + release 2026b (Tom Lane) + § + + + + British Columbia (America/Vancouver) will be on year-round UTC-07 + (effectively, permanent DST) beginning in November 2026. This + release assumes that their TZ abbreviation will + be MST from that time forward. That seems likely + to change, but it's unclear what new abbreviation will be used. + Also a historical correction for Moldova: they have followed EU DST + transition times since 2022. + + + + + + + + Release 15.17 diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index d4bf0c7563d..a6397682563 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -1043,7 +1043,7 @@ gistGetFakeLSN(Relation rel) * last call. */ static XLogRecPtr lastlsn = InvalidXLogRecPtr; - XLogRecPtr currlsn = GetXLogInsertRecPtr(); + XLogRecPtr currlsn = GetXLogInsertEndRecPtr(); /* Shouldn't be called for WAL-logging relations */ Assert(!RelationNeedsWAL(rel)); diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index 53b3274a032..0af1c61a4cc 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -337,8 +337,18 @@ static MemoryContext MXactContext = NULL; #define debug_elog6(a,b,c,d,e,f) #endif -/* hack to deal with WAL generated with older minor versions */ -static int pre_initialized_offsets_page = -1; +/* + * Hack to deal with WAL generated with older minor versions. + * + * last_initialized_offsets_page is the XLOG_MULTIXACT_ZERO_OFF_PAGE record + * that we saw during WAL replay, or -1 if we haven't seen any yet. + * + * pre_initialized_offsets_page is the last page that was implicitly + * initialized by replaying a XLOG_MULTIXACT_CREATE_ID record, when we had not + * seen a XLOG_MULTIXACT_ZERO_OFF_PAGE record for the page yet. + */ +static int last_initialized_offsets_page = -1; +static int pre_initialized_offsets_page = -1; /* internal MultiXactId management */ static void MultiXactIdSetOldestVisible(void); @@ -897,24 +907,63 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset, * such a version, the next page might not be initialized yet. Initialize * it now. */ - if (InRecovery && - next_pageno != pageno && - MultiXactOffsetCtl->shared->latest_page_number == pageno) + if (InRecovery && next_pageno != pageno) { - elog(DEBUG1, "next offsets page is not initialized, initializing it now"); + bool init_needed; - /* Create and zero the page */ - slotno = SimpleLruZeroPage(MultiXactOffsetCtl, next_pageno); + /*---------- + * Check if the page exists, and if not, initialize it now. + * + * The straightforward way to check if the page exists is to call + * SimpleLruDoesPhysicalPageExist(). However, there two problems with + * that: + * + * 1. It's somewhat expensive to call on every page switch. + * + * 2. It does not take into account pages that have been initialized + * in the SLRU buffer cache but not yet flushed to disk. For such + * pages, it will incorrectly return false. + * + * To fix both of those problems, if we have replayed any + * XLOG_MULTIXACT_ZERO_OFF_PAGE records, we assume that the last page + * that was zeroed by XLOG_MULTIXACT_ZERO_OFF_PAGE is the last page + * that exists. This works because the XLOG_MULTIXACT_ZERO_OFF_PAGE + * records must appear in the WAL in order, unlike CREATE_ID records. + * We only resort to SimpleLruDoesPhysicalPageExist() if we haven't + * seen any XLOG_MULTIXACT_ZERO_OFF_PAGE records yet, which should + * happen at most once after starting WAL recovery. + * + * As an extra safety measure, if we do resort to + * SimpleLruDoesPhysicalPageExist(), flush the SLRU buffers first so + * that it will return an accurate result. + *---------- + */ + if (last_initialized_offsets_page == -1) + { + SimpleLruWriteAll(MultiXactOffsetCtl, false); + init_needed = !SimpleLruDoesPhysicalPageExist(MultiXactOffsetCtl, next_pageno); + } + else + init_needed = (last_initialized_offsets_page == pageno); - /* Make sure it's written out */ - SimpleLruWritePage(MultiXactOffsetCtl, slotno); - Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]); + if (init_needed) + { + elog(DEBUG1, "next offsets page is not initialized, initializing it now"); - /* - * Remember that we initialized the page, so that we don't zero it - * again at the XLOG_MULTIXACT_ZERO_OFF_PAGE record. - */ - pre_initialized_offsets_page = next_pageno; + /* Create and zero the page */ + slotno = SimpleLruZeroPage(MultiXactOffsetCtl, next_pageno); + + /* Make sure it's written out */ + SimpleLruWritePage(MultiXactOffsetCtl, slotno); + Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]); + + /* + * Remember that we initialized the page, so that we don't zero it + * again at the XLOG_MULTIXACT_ZERO_OFF_PAGE record. + */ + pre_initialized_offsets_page = next_pageno; + last_initialized_offsets_page = next_pageno; + } } /* @@ -3370,6 +3419,8 @@ multixact_redo(XLogReaderState *record) SimpleLruWritePage(MultiXactOffsetCtl, slotno); Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]); LWLockRelease(MultiXactOffsetSLRULock); + + last_initialized_offsets_page = pageno; } else elog(DEBUG1, "skipping initialization of offsets page %d because it was already initialized on multixid creation", pageno); diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 7aa2a354f2d..f79961633a6 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -8932,6 +8932,22 @@ GetXLogInsertRecPtr(void) return XLogBytePosToRecPtr(current_bytepos); } +/* + * Get latest WAL record end pointer + */ +XLogRecPtr +GetXLogInsertEndRecPtr(void) +{ + XLogCtlInsert *Insert = &XLogCtl->Insert; + uint64 current_bytepos; + + SpinLockAcquire(&Insert->insertpos_lck); + current_bytepos = Insert->CurrBytePos; + SpinLockRelease(&Insert->insertpos_lck); + + return XLogBytePosToEndRecPtr(current_bytepos); +} + /* * Get latest WAL write pointer */ diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 798404111f4..8b2cce1552c 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -514,9 +514,13 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind, */ for (i = 0; i < natts; i++) { - CheckAttributeType(NameStr(TupleDescAttr(tupdesc, i)->attname), - TupleDescAttr(tupdesc, i)->atttypid, - TupleDescAttr(tupdesc, i)->attcollation, + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + + if (attr->attisdropped) + continue; + CheckAttributeType(NameStr(attr->attname), + attr->atttypid, + attr->attcollation, NIL, /* assume we're creating a new rowtype */ flags); } @@ -653,13 +657,13 @@ CheckAttributeType(const char *attname, containing_rowtypes, flags); } - else if (att_typtype == TYPTYPE_RANGE) + else if (att_typtype == TYPTYPE_MULTIRANGE) { /* - * If it's a range, recurse to check its subtype. + * If it's a multirange, recurse to check its plain range type. */ - CheckAttributeType(attname, get_range_subtype(atttypid), - get_range_collation(atttypid), + CheckAttributeType(attname, get_multirange_range(atttypid), + InvalidOid, /* range types are not collatable */ containing_rowtypes, flags); } diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index 218eee0f749..76fc387997d 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -908,7 +908,7 @@ char * makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace) { char *buf; - char *rangestr; + const char *rangestr; /* * If the range type name contains "range" then change that to diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 23efb4c8711..82b87ba8eda 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -171,7 +171,8 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, * generated columns are not yet computed when the filtering * happens. */ - if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated) + if (attno > 0 && + TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated) ereport(ERROR, errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("generated columns are not supported in COPY FROM WHERE conditions"), diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 608947b9a34..a128e421960 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -1916,8 +1916,11 @@ EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId, command->in_extension = creating_extension; ObjectAddressSet(command->d.atscfg.address, TSConfigRelationId, cfgId); - command->d.atscfg.dictIds = palloc(sizeof(Oid) * ndicts); - memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts); + if (ndicts > 0) + { + command->d.atscfg.dictIds = palloc_array(Oid, ndicts); + memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts); + } command->d.atscfg.ndicts = ndicts; command->parsetree = (Node *) copyObject(stmt); diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c index 446b5568eab..e98b7d42675 100644 --- a/src/backend/commands/foreigncmds.c +++ b/src/backend/commands/foreigncmds.c @@ -740,6 +740,11 @@ AlterForeignDataWrapper(ParseState *pstate, AlterFdwStmt *stmt) ereport(WARNING, (errmsg("changing the foreign-data wrapper handler can change behavior of existing foreign tables"))); } + else + { + /* handler unchanged */ + fdwhandler = fdwForm->fdwhandler; + } if (validator_given) { diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index df10e09def9..fccc0f48362 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -19544,7 +19544,10 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name) ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx)); - /* Silently do nothing if already in the right state */ + /* + * Check if the index is already attached to the correct parent, + * ultimately attempting one round of validation if already the case. + */ currParent = partIdx->rd_rel->relispartition ? get_partition_parent(partIdxId, false) : InvalidOid; if (currParent != RelationGetRelid(parentIdx)) @@ -19645,6 +19648,14 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name) validatePartitionedIndex(parentIdx, parentTbl); } + else if (!parentIdx->rd_index->indisvalid) + { + /* + * The index is attached, but the parent is still invalid; see if it + * can be validated now. + */ + validatePartitionedIndex(parentIdx, parentTbl); + } relation_close(parentTbl, AccessShareLock); /* keep these locks till commit */ diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 63c6030fcf7..1ed9b5a81b2 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -1468,6 +1468,13 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt) /* we can look up the subtype name immediately */ multirangeNamespace = QualifiedNameGetCreationNamespace(defGetQualifiedName(defel), &multirangeTypeName); + + /* Check we have creation rights in target namespace */ + aclresult = pg_namespace_aclcheck(multirangeNamespace, + GetUserId(), ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_SCHEMA, + get_namespace_name(multirangeNamespace)); } else ereport(ERROR, diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index faf203663e3..40eff0f2a4c 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -170,6 +170,14 @@ static Datum ExecJustAssignOuterVarVirt(ExprState *state, ExprContext *econtext, static Datum ExecJustAssignScanVarVirt(ExprState *state, ExprContext *econtext, bool *isnull); /* execution helper functions */ +static pg_attribute_always_inline void ExecEvalArrayCompareInternal(FunctionCallInfo fcinfo, + ArrayType *arr, + int16 typlen, + bool typbyval, + char typalign, + bool useOr, + Datum *result, + bool *resultnull); static pg_attribute_always_inline void ExecAggPlainTransByVal(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup, @@ -3339,12 +3347,6 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op) int nitems; Datum result; bool resultnull; - int16 typlen; - bool typbyval; - char typalign; - char *s; - bits8 *bitmap; - int bitmask; /* * If the array is NULL then we return NULL --- it's not very meaningful @@ -3393,13 +3395,42 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op) op->d.scalararrayop.element_type = ARR_ELEMTYPE(arr); } - typlen = op->d.scalararrayop.typlen; - typbyval = op->d.scalararrayop.typbyval; - typalign = op->d.scalararrayop.typalign; + ExecEvalArrayCompareInternal(fcinfo, + arr, + op->d.scalararrayop.typlen, + op->d.scalararrayop.typbyval, + op->d.scalararrayop.typalign, + useOr, + &result, + &resultnull); + + *op->resvalue = result; + *op->resnull = resultnull; +} + +/* + * Shared helper for ExecEvalScalarArrayOp() and the NULL-LHS fallback for + * non-strict ExecEvalHashedScalarArrayOp(). + * + * Callers must handle the strict LHS-is-NULL; return NULL fast path prior to + * calling this. + */ +static pg_attribute_always_inline void +ExecEvalArrayCompareInternal(FunctionCallInfo fcinfo, ArrayType *arr, + int16 typlen, bool typbyval, char typalign, + bool useOr, Datum *result, bool *resultnull) +{ + int nitems; + char *s; + bits8 *bitmap; + int bitmask; + bool strictfunc = fcinfo->flinfo->fn_strict; + + nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr)); /* Initialize result appropriately depending on useOr */ - result = BoolGetDatum(!useOr); - resultnull = false; + *result = BoolGetDatum(!useOr); + *resultnull = false; /* Loop over the array elements */ s = (char *) ARR_DATA_PTR(arr); @@ -3435,18 +3466,18 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op) else { fcinfo->isnull = false; - thisresult = op->d.scalararrayop.fn_addr(fcinfo); + thisresult = fcinfo->flinfo->fn_addr(fcinfo); } /* Combine results per OR or AND semantics */ if (fcinfo->isnull) - resultnull = true; + *resultnull = true; else if (useOr) { if (DatumGetBool(thisresult)) { - result = BoolGetDatum(true); - resultnull = false; + *result = BoolGetDatum(true); + *resultnull = false; break; /* needn't look at any more elements */ } } @@ -3454,8 +3485,8 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op) { if (!DatumGetBool(thisresult)) { - result = BoolGetDatum(false); - resultnull = false; + *result = BoolGetDatum(false); + *resultnull = false; break; /* needn't look at any more elements */ } } @@ -3471,9 +3502,6 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op) } } } - - *op->resvalue = result; - *op->resnull = resultnull; } /* @@ -3552,7 +3580,7 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco * If the scalar is NULL, and the function is strict, return NULL; no * point in executing the search. */ - if (fcinfo->args[0].isnull && strictfunc) + if (scalar_isnull && strictfunc) { *op->resnull = true; return; @@ -3650,8 +3678,51 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco * non-strict functions with a null lhs value if no match is found. */ op->d.hashedscalararrayop.has_nulls = has_nulls; + + /* + * When we have a non-strict equality function, check and cache the + * result from looking up a NULL. Non-strict functions are free to + * treat a NULL as equal to any other value, e.g. a 0 or an empty + * string. Here we perform a linear search over the array and cache + * the outcome so that we can use that result any time we receive a + * NULL. + */ + if (!strictfunc) + { + bool null_lhs_result; + + fcinfo->args[0].value = (Datum) 0; + fcinfo->args[0].isnull = true; + + ExecEvalArrayCompareInternal(fcinfo, arr, typlen, typbyval, + typalign, true, &result, + &resultnull); + + null_lhs_result = DatumGetBool(result); + + /* invert non-NULL results for NOT IN */ + if (!resultnull && !inclause) + null_lhs_result = !null_lhs_result; + + op->d.hashedscalararrayop.null_lhs_isnull = resultnull; + op->d.hashedscalararrayop.null_lhs_result = null_lhs_result; + } + } + + /* + * When looking up an SQL NULL value with non-strict functions, we defer + * to the value we cached when building the hash table. + */ + if (scalar_isnull) + { + Assert(!strictfunc); + + *op->resnull = op->d.hashedscalararrayop.null_lhs_isnull; + *op->resvalue = BoolGetDatum(op->d.hashedscalararrayop.null_lhs_result); + return; } + /* Check the hash to see if we have a match. */ hashfound = NULL != saophash_lookup(elements_tab->hashtab, scalar); diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 2fb975373d9..1270e6b8727 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -4298,7 +4298,7 @@ ExecEndAgg(AggState *node) { AggregateInstrumentation *si; - Assert(ParallelWorkerNumber <= node->shared_info->num_workers); + Assert(ParallelWorkerNumber < node->shared_info->num_workers); si = &node->shared_info->sinstrument[ParallelWorkerNumber]; si->hash_batches_used = node->hash_batches_used; si->hash_disk_used = node->hash_disk_used; diff --git a/src/backend/executor/nodeIncrementalSort.c b/src/backend/executor/nodeIncrementalSort.c index c3c5b4f733f..7c0a80858ec 100644 --- a/src/backend/executor/nodeIncrementalSort.c +++ b/src/backend/executor/nodeIncrementalSort.c @@ -103,7 +103,7 @@ if ((node)->shared_info && (node)->am_worker) \ { \ Assert(IsParallelWorker()); \ - Assert(ParallelWorkerNumber <= (node)->shared_info->num_workers); \ + Assert(ParallelWorkerNumber < (node)->shared_info->num_workers); \ instrumentSortedGroup(&(node)->shared_info->sinfo[ParallelWorkerNumber].groupName##GroupInfo, \ (node)->groupName##_state); \ } \ diff --git a/src/backend/executor/nodeMemoize.c b/src/backend/executor/nodeMemoize.c index 64c7a0ae900..1da0600999d 100644 --- a/src/backend/executor/nodeMemoize.c +++ b/src/backend/executor/nodeMemoize.c @@ -1109,7 +1109,7 @@ ExecEndMemoize(MemoizeState *node) if (node->stats.mem_peak == 0) node->stats.mem_peak = node->mem_used; - Assert(ParallelWorkerNumber <= node->shared_info->num_workers); + Assert(ParallelWorkerNumber < node->shared_info->num_workers); si = &node->shared_info->sinstrument[ParallelWorkerNumber]; memcpy(si, &node->stats, sizeof(MemoizeInstrumentation)); } diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 90af0e37b6c..53cb8e1dc2f 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -3095,6 +3095,11 @@ lmerge_matched:; *inputslot; LockTupleMode lockmode; + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + /* * The target tuple was concurrently updated by some other * transaction. Run EvalPlanQual() with the new version of diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c index 37ad35704bc..c668ba2c46a 100644 --- a/src/backend/executor/nodeSort.c +++ b/src/backend/executor/nodeSort.c @@ -175,7 +175,7 @@ ExecSort(PlanState *pstate) TuplesortInstrumentation *si; Assert(IsParallelWorker()); - Assert(ParallelWorkerNumber <= node->shared_info->num_workers); + Assert(ParallelWorkerNumber < node->shared_info->num_workers); si = &node->shared_info->sinstrument[ParallelWorkerNumber]; tuplesort_get_stats(tuplesortstate, si); } diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index 3e7436f4918..6bbe7314f51 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -37,6 +37,7 @@ #include "catalog/objectaccess.h" #include "catalog/pg_aggregate.h" #include "catalog/pg_proc.h" +#include "common/int.h" #include "executor/executor.h" #include "executor/nodeWindowAgg.h" #include "miscadmin.h" @@ -1423,12 +1424,21 @@ row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot) if (frameOptions & FRAMEOPTION_ROWS) { int64 offset = DatumGetInt64(winstate->endOffsetValue); + int64 frameendpos = 0; /* rows after current row + offset are out of frame */ if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING) offset = -offset; - if (pos > winstate->currentpos + offset) + /* + * If we have an overflow, it means the frame end is beyond the + * range of int64. Since currentpos >= 0, this can only be a + * positive overflow. We treat this as meaning that the frame + * extends to end of partition. + */ + if (!pg_add_s64_overflow(winstate->currentpos, offset, + &frameendpos) && + pos > frameendpos) return -1; } else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS)) @@ -1563,7 +1573,16 @@ update_frameheadpos(WindowAggState *winstate) if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING) offset = -offset; - winstate->frameheadpos = winstate->currentpos + offset; + /* + * If we have an overflow, it means the frame head is beyond the + * range of int64. Since currentpos >= 0, this can only be a + * positive overflow. We treat this as being beyond end of + * partition. + */ + if (pg_add_s64_overflow(winstate->currentpos, offset, + &winstate->frameheadpos)) + winstate->frameheadpos = PG_INT64_MAX; + /* frame head can't go before first row */ if (winstate->frameheadpos < 0) winstate->frameheadpos = 0; @@ -1675,12 +1694,21 @@ update_frameheadpos(WindowAggState *winstate) * framehead_slot empty. */ int64 offset = DatumGetInt64(winstate->startOffsetValue); - int64 minheadgroup; + int64 minheadgroup = 0; if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING) minheadgroup = winstate->currentgroup - offset; else - minheadgroup = winstate->currentgroup + offset; + { + /* + * If we have an overflow, it means the target group is beyond + * the range of int64. We treat this as "infinity", which + * ensures the loop below advances to end of partition. + */ + if (pg_add_s64_overflow(winstate->currentgroup, offset, + &minheadgroup)) + minheadgroup = PG_INT64_MAX; + } tuplestore_select_read_pointer(winstate->buffer, winstate->framehead_ptr); @@ -1817,7 +1845,18 @@ update_frametailpos(WindowAggState *winstate) if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING) offset = -offset; - winstate->frametailpos = winstate->currentpos + offset + 1; + /* + * If we have an overflow, it means the frame tail is beyond the + * range of int64. Since currentpos >= 0, this can only be a + * positive overflow. We treat this as being beyond end of + * partition. + */ + if (pg_add_s64_overflow(winstate->currentpos, offset, + &winstate->frametailpos) || + pg_add_s64_overflow(winstate->frametailpos, 1, + &winstate->frametailpos)) + winstate->frametailpos = PG_INT64_MAX; + /* smallest allowable value of frametailpos is 0 */ if (winstate->frametailpos < 0) winstate->frametailpos = 0; @@ -1929,12 +1968,21 @@ update_frametailpos(WindowAggState *winstate) * leave frametailpos = end+1 and frametail_slot empty. */ int64 offset = DatumGetInt64(winstate->endOffsetValue); - int64 maxtailgroup; + int64 maxtailgroup = 0; if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING) maxtailgroup = winstate->currentgroup - offset; else - maxtailgroup = winstate->currentgroup + offset; + { + /* + * If we have an overflow, it means the target group is beyond + * the range of int64. We treat this as "infinity", which + * ensures the loop below advances to end of partition. + */ + if (pg_add_s64_overflow(winstate->currentgroup, offset, + &maxtailgroup)) + maxtailgroup = PG_INT64_MAX; + } tuplestore_select_read_pointer(winstate->buffer, winstate->frametail_ptr); diff --git a/src/backend/jit/llvm/Makefile b/src/backend/jit/llvm/Makefile index 1c3022b6274..13d9d7c6256 100644 --- a/src/backend/jit/llvm/Makefile +++ b/src/backend/jit/llvm/Makefile @@ -39,7 +39,7 @@ SHLIB_LINK += $(LLVM_LIBS) # Because this module includes C++ files, we need to use a C++ # compiler for linking. Makefile.shlib uses $(COMPILER) to build # loadable modules. -override COMPILER = $(CXX) $(CFLAGS) +override COMPILER = $(CXX) $(CXXFLAGS) OBJS = \ $(WIN32RES) diff --git a/src/backend/jit/llvm/SectionMemoryManager.cpp b/src/backend/jit/llvm/SectionMemoryManager.cpp index c4fbf15a961..f04e4e4231e 100644 --- a/src/backend/jit/llvm/SectionMemoryManager.cpp +++ b/src/backend/jit/llvm/SectionMemoryManager.cpp @@ -1,18 +1,11 @@ /* - * This file is from https://github.com/llvm/llvm-project/pull/71968 - * with minor modifications to avoid name clash and work with older - * LLVM versions. The llvm::backport::SectionMemoryManager class is a - * drop-in replacement for llvm::SectionMemoryManager, for use with - * llvm::RuntimeDyld. It fixes a memory layout bug on large memory - * ARM systems (see pull request for details). If the LLVM project - * eventually commits the change, we may need to resynchronize our - * copy with any further modifications, but they would be unlikely to - * backport it into the LLVM versions that we target so we would still - * need this copy. + * This file is from LLVM 22 (originally pull request #71968), with minor + * modifications to avoid name clash and work with older LLVM versions. It + * replaces llvm::SectionMemoryManager, and is injected into llvm::RuntimeDyld + * to fix a memory layout bug on large memory ARM systems on LLVM < 22. * - * In the future we will switch to using JITLink instead of - * RuntimeDyld where possible, and later remove this code (.cpp, .h, - * .LICENSE) after all LLVM versions that we target allow it. + * We can remove this code (.cpp, .h, .LICENSE) once LLVM 22 is our minimum + * supported version or we've switched to JITLink for at least Aarch64. * * This file is a modified copy of a part of the LLVM source code that * we would normally access from the LLVM library. It is therefore diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c index 377b0f98e74..4171ed2627d 100644 --- a/src/backend/jit/llvm/llvmjit.c +++ b/src/backend/jit/llvm/llvmjit.c @@ -1321,7 +1321,10 @@ llvm_log_jit_error(void *ctx, LLVMErrorRef error) static LLVMOrcObjectLayerRef llvm_create_object_layer(void *Ctx, LLVMOrcExecutionSessionRef ES, const char *Triple) { -#ifdef USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER +#if LLVM_VERSION_MAJOR >= 22 + LLVMOrcObjectLayerRef objlayer = + LLVMOrcCreateRTDyldObjectLinkingLayerWithSectionMemoryManagerReserveAlloc(ES, true); +#elif defined(USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER) LLVMOrcObjectLayerRef objlayer = LLVMOrcCreateRTDyldObjectLinkingLayerWithSafeSectionMemoryManager(ES); #else diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 56aac24065a..bb5173abbe7 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -62,7 +62,9 @@ static LLVMValueRef build_EvalXFuncInt(LLVMBuilderRef b, LLVMModuleRef mod, LLVMValueRef v_state, ExprEvalStep *op, int natts, LLVMValueRef *v_args); +#if LLVM_VERSION_MAJOR < 22 static LLVMValueRef create_LifetimeEnd(LLVMModuleRef mod); +#endif /* macro making it easier to call ExecEval* functions */ #define build_EvalXFunc(b, mod, funcname, v_state, op, ...) \ @@ -2520,14 +2522,11 @@ BuildV1Call(LLVMJitContext *context, LLVMBuilderRef b, LLVMModuleRef mod, FunctionCallInfo fcinfo, LLVMValueRef *v_fcinfo_isnull) { - LLVMContextRef lc; LLVMValueRef v_fn; LLVMValueRef v_fcinfo_isnullp; LLVMValueRef v_retval; LLVMValueRef v_fcinfo; - lc = LLVMGetModuleContext(mod); - v_fn = llvm_function_reference(context, b, mod, fcinfo); v_fcinfo = l_ptr_const(fcinfo, l_ptr(StructFunctionCallInfoData)); @@ -2543,11 +2542,14 @@ BuildV1Call(LLVMJitContext *context, LLVMBuilderRef b, if (v_fcinfo_isnull) *v_fcinfo_isnull = l_load(b, TypeStorageBool, v_fcinfo_isnullp, ""); +#if LLVM_VERSION_MAJOR < 22 + /* * Add lifetime-end annotation, signaling that writes to memory don't have * to be retained (important for inlining potential). */ { + LLVMContextRef lc = LLVMGetModuleContext(mod); LLVMValueRef v_lifetime = create_LifetimeEnd(mod); LLVMValueRef params[2]; @@ -2559,6 +2561,7 @@ BuildV1Call(LLVMJitContext *context, LLVMBuilderRef b, params[1] = l_ptr_const(&fcinfo->isnull, l_ptr(LLVMInt8TypeInContext(lc))); l_call(b, LLVMGetFunctionType(v_lifetime), v_lifetime, params, lengthof(params), ""); } +#endif return v_retval; } @@ -2596,6 +2599,7 @@ build_EvalXFuncInt(LLVMBuilderRef b, LLVMModuleRef mod, const char *funcname, return v_ret; } +#if LLVM_VERSION_MAJOR < 22 static LLVMValueRef create_LifetimeEnd(LLVMModuleRef mod) { @@ -2629,3 +2633,4 @@ create_LifetimeEnd(LLVMModuleRef mod) return fn; } +#endif diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c index ee7f52218ab..2e66dd9c383 100644 --- a/src/backend/libpq/auth-scram.c +++ b/src/backend/libpq/auth-scram.c @@ -555,7 +555,7 @@ scram_verify_plain_password(const char *username, const char *password, * Compare the secret's Server Key with the one computed from the * user-supplied password. */ - return memcmp(computed_key, server_key, SCRAM_KEY_LEN) == 0; + return timingsafe_bcmp(computed_key, server_key, SCRAM_KEY_LEN) == 0; } @@ -1095,9 +1095,9 @@ verify_final_nonce(scram_state *state) if (final_nonce_len != client_nonce_len + server_nonce_len) return false; - if (memcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0) + if (timingsafe_bcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0) return false; - if (memcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0) + if (timingsafe_bcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0) return false; return true; @@ -1151,7 +1151,7 @@ verify_client_proof(scram_state *state) if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey, &errstr) < 0) elog(ERROR, "could not hash stored key: %s", errstr); - if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0) + if (timingsafe_bcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0) return false; return true; diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 280cfb9fff3..281ebb79e03 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -3322,7 +3322,7 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por } pfree(cryptvector); - if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0) + if (timingsafe_bcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0) { ereport(LOG, (errmsg("RADIUS response from %s has incorrect MD5 signature", diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index 1ff8b0507d4..21ab5f1bb0c 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -195,7 +195,8 @@ md5_crypt_verify(const char *role, const char *shadow_pass, return STATUS_ERROR; } - if (strcmp(client_pass, crypt_pwd) == 0) + if (strlen(client_pass) == strlen(crypt_pwd) && + timingsafe_bcmp(client_pass, crypt_pwd, strlen(crypt_pwd)) == 0) retval = STATUS_OK; else { @@ -257,7 +258,8 @@ plain_crypt_verify(const char *role, const char *shadow_pass, *logdetail = errstr; return STATUS_ERROR; } - if (strcmp(crypt_client_pass, shadow_pass) == 0) + if (strlen(crypt_client_pass) == strlen(shadow_pass) && + timingsafe_bcmp(crypt_client_pass, shadow_pass, strlen(shadow_pass)) == 0) return STATUS_OK; else { diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index a50a0a386db..e17185803c0 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -3595,16 +3595,19 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, * The condition's equality operator must be a member of the * index opfamily, else it is not asserting the right kind of * equality behavior for this index. We check this first - * since it's probably cheaper than match_index_to_operand(). + * since it's probably the cheapest test. */ if (!list_member_oid(rinfo->mergeopfamilies, ind->opfamily[c])) continue; /* - * XXX at some point we may need to check collations here too. - * For the moment we assume all collations reduce to the same - * notion of equality. + * The index's collation must agree with the clause's input + * collation on equality, else the index's uniqueness does not + * imply uniqueness under the clause's equality semantics. */ + if (!collations_agree_on_equality(ind->indexcollations[c], + exprInputCollation((Node *) rinfo->clause))) + continue; /* OK, see if the condition operand matches the index key */ if (rinfo->outer_is_left) @@ -3642,10 +3645,13 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, continue; /* - * XXX at some point we may need to check collations here too. - * For the moment we assume all collations reduce to the same - * notion of equality. + * The index's collation must agree with the operand's + * collation on equality, else the index's uniqueness does not + * imply uniqueness under the operator's equality semantics. */ + if (!collations_agree_on_equality(ind->indexcollations[c], + exprCollation(expr))) + continue; matched = true; /* column is unique */ break; diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 34efeee93f9..11e53c5a56c 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -32,6 +32,20 @@ #include "optimizer/tlist.h" #include "utils/lsyscache.h" +/* + * One element of the list passed to query_is_distinct_for_with_collations(). + * Each entry names a subquery output column that the caller needs to be + * distinct over, plus the upper-level equality operator and its input + * collation, so that the subquery's own DISTINCT/GROUP BY/set-op clauses can + * be compared for compatibility. + */ +typedef struct DistinctColInfo +{ + int colno; /* subquery output column resno */ + Oid opid; /* upper-level equality operator */ + Oid collid; /* input collation of opid */ +} DistinctColInfo; + /* local functions */ static bool join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo); static void remove_rel_from_query(PlannerInfo *root, int relid, @@ -40,7 +54,9 @@ static List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved); static bool rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel); static bool rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list); -static Oid distinct_col_search(int colno, List *colnos, List *opids); +static bool query_is_distinct_for_with_collations(Query *query, + List *distinct_cols); +static DistinctColInfo *distinct_col_search(int colno, List *distinct_cols); static bool is_innerrel_unique_for(PlannerInfo *root, Relids joinrelids, Relids outerrelids, @@ -668,15 +684,17 @@ rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list) { Index relid = rel->relid; Query *subquery = root->simple_rte_array[relid]->subquery; - List *colnos = NIL; - List *opids = NIL; + List *distinct_cols = NIL; ListCell *l; /* - * Build the argument lists for query_is_distinct_for: a list of - * output column numbers that the query needs to be distinct over, and - * a list of equality operators that the output columns need to be - * distinct according to. + * Build the argument list for query_is_distinct_for_with_collations: + * a list of DistinctColInfo entries, each holding an output column + * number that the query needs to be distinct over, the equality + * operator that the column needs to be distinct according to, and + * that operator's input collation. The collation matters because the + * subquery's own DISTINCT / GROUP BY / set-op proves uniqueness under + * its own collation, which need not agree with the operator's. * * (XXX we are not considering restriction clauses attached to the * subquery; is that worth doing?) @@ -684,18 +702,18 @@ rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list) foreach(l, clause_list) { RestrictInfo *rinfo = lfirst_node(RestrictInfo, l); - Oid op; + OpExpr *opexpr; Var *var; + DistinctColInfo *dcinfo; /* - * Get the equality operator we need uniqueness according to. - * (This might be a cross-type operator and thus not exactly the - * same operator the subquery would consider; that's all right - * since query_is_distinct_for can resolve such cases.) The - * caller's mergejoinability test should have selected only - * OpExprs. + * The caller's mergejoinability test should have selected only + * OpExprs. The operator might be a cross-type operator and thus + * not exactly the same operator the subquery would consider; + * that's all right since query_is_distinct_for_with_collations + * can resolve such cases. */ - op = castNode(OpExpr, rinfo->clause)->opno; + opexpr = castNode(OpExpr, rinfo->clause); /* caller identified the inner side for us */ if (rinfo->outer_is_left) @@ -719,11 +737,14 @@ rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list) var->varno != relid || var->varlevelsup != 0) continue; - colnos = lappend_int(colnos, var->varattno); - opids = lappend_oid(opids, op); + dcinfo = palloc(sizeof(DistinctColInfo)); + dcinfo->colno = var->varattno; + dcinfo->opid = opexpr->opno; + dcinfo->collid = opexpr->inputcollid; + distinct_cols = lappend(distinct_cols, dcinfo); } - if (query_is_distinct_for(subquery, colnos, opids)) + if (query_is_distinct_for_with_collations(subquery, distinct_cols)) return true; } return false; @@ -761,31 +782,71 @@ query_supports_distinctness(Query *query) } /* - * query_is_distinct_for - does query never return duplicates of the - * specified columns? + * query_is_distinct_for - ABI-preserving wrapper around + * query_is_distinct_for_with_collations(). * - * query is a not-yet-planned subquery (in current usage, it's always from - * a subquery RTE, which the planner avoids scribbling on). - * - * colnos is an integer list of output column numbers (resno's). We are - * interested in whether rows consisting of just these columns are certain - * to be distinct. "Distinctness" is defined according to whether the - * corresponding upper-level equality operators listed in opids would think - * the values are distinct. (Note: the opids entries could be cross-type - * operators, and thus not exactly the equality operators that the subquery - * would use itself. We use equality_ops_are_compatible() to check - * compatibility. That looks at btree or hash opfamily membership, and so - * should give trustworthy answers for all operators that we might need - * to deal with here.) + * The original signature took parallel colnos/opids lists and did not + * consider collations. External callers built against earlier minor + * releases continue to call it with the historical (collation-blind) + * semantics; we forward with InvalidOid collations, which makes the + * collation check a no-op (see collations_agree_on_equality()). */ bool query_is_distinct_for(Query *query, List *colnos, List *opids) { - ListCell *l; - Oid opid; + List *distinct_cols = NIL; + ListCell *lc1; + ListCell *lc2; Assert(list_length(colnos) == list_length(opids)); + forboth(lc1, colnos, lc2, opids) + { + DistinctColInfo *dcinfo = palloc(sizeof(DistinctColInfo)); + + dcinfo->colno = lfirst_int(lc1); + dcinfo->opid = lfirst_oid(lc2); + dcinfo->collid = InvalidOid; + distinct_cols = lappend(distinct_cols, dcinfo); + } + + return query_is_distinct_for_with_collations(query, distinct_cols); +} + +/* + * query_is_distinct_for_with_collations - does query never return duplicates + * of the specified columns? + * + * query is a not-yet-planned subquery (in current usage, it's always from + * a subquery RTE, which the planner avoids scribbling on). + * + * distinct_cols is a list of DistinctColInfo, one per requested output column. + * Each entry names the subquery output column number we want distinct, the + * upper-level equality operator we'll compare values with, and that operator's + * input collation. We are interested in whether rows consisting of just these + * columns are certain to be distinct. + * + * "Distinctness" is defined according to whether the corresponding upper-level + * equality operators would think the values are distinct. (Note: each opid + * could be a cross-type operator, and thus not exactly the equality operator + * that the subquery would use itself. We use equality_ops_are_compatible() to + * check compatibility. That looks at opfamily membership for index AMs that + * have declared that they support consistent equality semantics within an + * opfamily, and so should give trustworthy answers for all operators that we + * might need to deal with here.) + * + * The collid must also agree on equality with the collation the subquery's own + * DISTINCT/GROUP BY/set-op uses to deduplicate the column, else the subquery's + * distinctness does not carry over to the caller's equality semantics. Two + * collations agree on equality if they match or if both are deterministic (in + * which case both reduce equality to byte-equality; see CREATE COLLATION). + */ +static bool +query_is_distinct_for_with_collations(Query *query, List *distinct_cols) +{ + ListCell *l; + DistinctColInfo *dcinfo; + /* * DISTINCT (including DISTINCT ON) guarantees uniqueness if all the * columns in the DISTINCT clause appear in colnos and operator semantics @@ -800,9 +861,11 @@ query_is_distinct_for(Query *query, List *colnos, List *opids) TargetEntry *tle = get_sortgroupclause_tle(sgc, query->targetList); - opid = distinct_col_search(tle->resno, colnos, opids); - if (!OidIsValid(opid) || - !equality_ops_are_compatible(opid, sgc->eqop)) + dcinfo = distinct_col_search(tle->resno, distinct_cols); + if (dcinfo == NULL || + !equality_ops_are_compatible(dcinfo->opid, sgc->eqop) || + !collations_agree_on_equality(dcinfo->collid, + exprCollation((Node *) tle->expr))) break; /* exit early if no match */ } if (l == NULL) /* had matches for all? */ @@ -831,9 +894,11 @@ query_is_distinct_for(Query *query, List *colnos, List *opids) TargetEntry *tle = get_sortgroupclause_tle(sgc, query->targetList); - opid = distinct_col_search(tle->resno, colnos, opids); - if (!OidIsValid(opid) || - !equality_ops_are_compatible(opid, sgc->eqop)) + dcinfo = distinct_col_search(tle->resno, distinct_cols); + if (dcinfo == NULL || + !equality_ops_are_compatible(dcinfo->opid, sgc->eqop) || + !collations_agree_on_equality(dcinfo->collid, + exprCollation((Node *) tle->expr))) break; /* exit early if no match */ } if (l == NULL) /* had matches for all? */ @@ -899,9 +964,11 @@ query_is_distinct_for(Query *query, List *colnos, List *opids) sgc = (SortGroupClause *) lfirst(lg); lg = lnext(topop->groupClauses, lg); - opid = distinct_col_search(tle->resno, colnos, opids); - if (!OidIsValid(opid) || - !equality_ops_are_compatible(opid, sgc->eqop)) + dcinfo = distinct_col_search(tle->resno, distinct_cols); + if (dcinfo == NULL || + !equality_ops_are_compatible(dcinfo->opid, sgc->eqop) || + !collations_agree_on_equality(dcinfo->collid, + exprCollation((Node *) tle->expr))) break; /* exit early if no match */ } if (l == NULL) /* had matches for all? */ @@ -921,24 +988,27 @@ query_is_distinct_for(Query *query, List *colnos, List *opids) } /* - * distinct_col_search - subroutine for query_is_distinct_for + * distinct_col_search - subroutine for query_is_distinct_for_with_collations * - * If colno is in colnos, return the corresponding element of opids, - * else return InvalidOid. (Ordinarily colnos would not contain duplicates, - * but if it does, we arbitrarily select the first match.) + * If colno matches the colno field of an entry in distinct_cols, return a + * pointer to that entry; else return NULL. (Ordinarily distinct_cols would + * not contain duplicate colnos, but if it does, we arbitrarily select the + * first match.) */ -static Oid -distinct_col_search(int colno, List *colnos, List *opids) +static DistinctColInfo * +distinct_col_search(int colno, List *distinct_cols) { - ListCell *lc1, - *lc2; + ListCell *lc; - forboth(lc1, colnos, lc2, opids) + foreach(lc, distinct_cols) { - if (colno == lfirst_int(lc1)) - return lfirst_oid(lc2); + DistinctColInfo *dcinfo = (DistinctColInfo *) lfirst(lc); + + if (dcinfo->colno == colno) + return dcinfo; } - return InvalidOid; + + return NULL; } diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index f3070c57a34..aab6f350fbc 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1928,7 +1928,10 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, * extended statistic "source_statsid", for the rel identified by heapRel and * heapRelid. * - * Attribute numbers in expression Vars are adjusted according to attmap. + * stxkeys in the source statistic holds attribute numbers from the parent + * relation. Those attnums, along with the attribute numbers referenced by + * Vars inside the expression tree, are remapped to the new relation's + * numbering according to attmap. */ static CreateStatsStmt * generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid, @@ -1987,7 +1990,8 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid, StatsElem *selem = makeNode(StatsElem); AttrNumber attnum = statsrec->stxkeys.values[i]; - selem->name = get_attname(heapRelid, attnum, false); + selem->name = + get_attname(heapRelid, attmap->attnums[attnum - 1], false); selem->expr = NULL; def_names = lappend(def_names, selem); diff --git a/src/backend/po/es.po b/src/backend/po/es.po index 77e8b182440..98e14fe649f 100644 --- a/src/backend/po/es.po +++ b/src/backend/po/es.po @@ -67,7 +67,7 @@ msgstr "" "Project-Id-Version: PostgreSQL server 15\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" "POT-Creation-Date: 2026-02-06 21:12+0000\n" -"PO-Revision-Date: 2025-02-15 12:02+0100\n" +"PO-Revision-Date: 2026-02-25 16:52+0100\n" "Last-Translator: Carlos Chapi \n" "Language-Team: PgSQL-es-Ayuda \n" "Language: es\n" @@ -20191,7 +20191,7 @@ msgstr "Cambie wal_level a logical o superior." #: replication/slot.c:1977 #, c-format msgid "physical replication slot \"%s\" exists, but wal_level < replica" -msgstr "existe el slot de replicación lógica «%s», pero wal_level < logical" +msgstr "existe el slot de replicación física «%s», pero wal_level < replica" # <> hello vim #: replication/slot.c:1979 diff --git a/src/backend/po/ru.po b/src/backend/po/ru.po index bf64e808b3d..98c062fa5f5 100644 --- a/src/backend/po/ru.po +++ b/src/backend/po/ru.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: postgres (PostgreSQL current)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2026-02-21 05:20+0200\n" +"POT-Creation-Date: 2026-05-10 08:02+0300\n" "PO-Revision-Date: 2026-02-21 05:29+0200\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -160,7 +160,7 @@ msgstr "" #: replication/logical/reorderbuffer.c:5074 #: replication/logical/snapbuild.c:1790 replication/logical/snapbuild.c:1897 #: replication/slot.c:1846 replication/walsender.c:645 -#: replication/walsender.c:2740 storage/file/copydir.c:161 +#: replication/walsender.c:2749 storage/file/copydir.c:161 #: storage/file/fd.c:717 storage/file/fd.c:3392 storage/file/fd.c:3622 #: storage/file/fd.c:3712 storage/smgr/md.c:541 utils/cache/relmapper.c:795 #: utils/cache/relmapper.c:912 utils/error/elog.c:1953 @@ -174,7 +174,7 @@ msgstr "не удалось открыть файл \"%s\": %m" #: access/transam/twophase.c:1753 access/transam/twophase.c:1762 #: access/transam/xlog.c:8770 access/transam/xlogfuncs.c:600 #: backup/basebackup_server.c:173 backup/basebackup_server.c:266 -#: postmaster/postmaster.c:5637 postmaster/syslogger.c:1571 +#: postmaster/postmaster.c:5623 postmaster/syslogger.c:1571 #: postmaster/syslogger.c:1584 postmaster/syslogger.c:1597 #: utils/cache/relmapper.c:946 #, c-format @@ -207,9 +207,9 @@ msgstr "не удалось синхронизировать с ФС файл \" #: access/transam/xlogrecovery.c:587 lib/dshash.c:253 libpq/auth.c:1344 #: libpq/auth.c:1412 libpq/auth.c:1970 libpq/be-secure-gssapi.c:530 #: libpq/be-secure-gssapi.c:702 postmaster/bgworker.c:349 -#: postmaster/bgworker.c:931 postmaster/postmaster.c:2598 -#: postmaster/postmaster.c:4183 postmaster/postmaster.c:5562 -#: postmaster/postmaster.c:5933 +#: postmaster/bgworker.c:931 postmaster/postmaster.c:2599 +#: postmaster/postmaster.c:4169 postmaster/postmaster.c:5548 +#: postmaster/postmaster.c:5919 #: replication/libpqwalreceiver/libpqwalreceiver.c:313 #: replication/logical/logical.c:206 replication/walsender.c:715 #: storage/buffer/localbuf.c:442 storage/file/fd.c:889 storage/file/fd.c:1431 @@ -217,8 +217,8 @@ msgstr "не удалось синхронизировать с ФС файл \" #: storage/ipc/procarray.c:2292 storage/ipc/procarray.c:2299 #: storage/ipc/procarray.c:2804 storage/ipc/procarray.c:3435 #: tcop/postgres.c:3645 utils/activity/pgstat_shmem.c:503 -#: utils/adt/formatting.c:1732 utils/adt/formatting.c:1854 -#: utils/adt/formatting.c:1977 utils/adt/pg_locale.c:454 +#: utils/adt/formatting.c:1734 utils/adt/formatting.c:1856 +#: utils/adt/formatting.c:1979 utils/adt/pg_locale.c:454 #: utils/adt/pg_locale.c:618 utils/adt/regexp.c:224 utils/fmgr/dfmgr.c:229 #: utils/hash/dynahash.c:513 utils/hash/dynahash.c:613 #: utils/hash/dynahash.c:1116 utils/mb/mbutils.c:410 utils/mb/mbutils.c:438 @@ -318,7 +318,7 @@ msgid "could not stat file \"%s\": %m" msgstr "не удалось получить информацию о файле \"%s\": %m" #: ../common/file_utils.c:161 ../common/pgfnames.c:48 commands/tablespace.c:749 -#: commands/tablespace.c:759 postmaster/postmaster.c:1583 +#: commands/tablespace.c:759 postmaster/postmaster.c:1584 #: storage/file/fd.c:2809 storage/file/reinit.c:126 utils/adt/misc.c:235 #: utils/misc/tzparser.c:338 #, c-format @@ -723,7 +723,7 @@ msgid "could not open parent table of index \"%s\"" msgstr "не удалось открыть родительскую таблицу индекса \"%s\"" #: access/brin/brin.c:1111 access/brin/brin.c:1207 access/gin/ginfast.c:1087 -#: parser/parse_utilcmd.c:2331 +#: parser/parse_utilcmd.c:2335 #, c-format msgid "index \"%s\" is not valid" msgstr "индекс \"%s\" - нерабочий" @@ -1142,11 +1142,11 @@ msgstr "" "не удалось определить, какое правило сортировки использовать для хеширования " "строк" -#: access/hash/hashfunc.c:281 access/hash/hashfunc.c:338 catalog/heap.c:672 -#: catalog/heap.c:678 commands/createas.c:206 commands/createas.c:515 +#: access/hash/hashfunc.c:281 access/hash/hashfunc.c:338 catalog/heap.c:686 +#: catalog/heap.c:692 commands/createas.c:206 commands/createas.c:515 #: commands/indexcmds.c:1962 commands/tablecmds.c:17808 commands/view.c:86 -#: regex/regc_pg_locale.c:243 utils/adt/formatting.c:1690 -#: utils/adt/formatting.c:1812 utils/adt/formatting.c:1935 utils/adt/like.c:190 +#: regex/regc_pg_locale.c:243 utils/adt/formatting.c:1692 +#: utils/adt/formatting.c:1814 utils/adt/formatting.c:1937 utils/adt/like.c:190 #: utils/adt/like_support.c:1025 utils/adt/varchar.c:733 #: utils/adt/varchar.c:1004 utils/adt/varchar.c:1065 utils/adt/varlena.c:1544 #, c-format @@ -1271,7 +1271,7 @@ msgstr "не удалось записать в файл \"%s\" (записан #: access/transam/xlog.c:3965 access/transam/xlog.c:8753 #: access/transam/xlogfuncs.c:594 backup/basebackup_server.c:149 #: backup/basebackup_server.c:242 commands/dbcommands.c:494 -#: postmaster/postmaster.c:4610 postmaster/postmaster.c:5624 +#: postmaster/postmaster.c:4596 postmaster/postmaster.c:5610 #: replication/logical/origin.c:587 replication/slot.c:1691 #: storage/file/copydir.c:167 storage/smgr/md.c:222 utils/time/snapmgr.c:1261 #, c-format @@ -1287,7 +1287,7 @@ msgstr "не удалось обрезать файл \"%s\" до нужного #: access/transam/timeline.c:424 access/transam/timeline.c:498 #: access/transam/xlog.c:3039 access/transam/xlog.c:3236 #: access/transam/xlog.c:3977 commands/dbcommands.c:506 -#: postmaster/postmaster.c:4620 postmaster/postmaster.c:4630 +#: postmaster/postmaster.c:4606 postmaster/postmaster.c:4616 #: replication/logical/origin.c:599 replication/logical/origin.c:641 #: replication/logical/origin.c:660 replication/logical/snapbuild.c:1804 #: replication/slot.c:1727 storage/file/buffile.c:537 @@ -1301,7 +1301,7 @@ msgstr "не удалось записать в файл \"%s\": %m" #: access/heap/rewriteheap.c:1249 access/transam/twophase.c:1713 #: access/transam/xlogarchive.c:119 access/transam/xlogarchive.c:436 -#: postmaster/postmaster.c:1159 postmaster/syslogger.c:1537 +#: postmaster/postmaster.c:1160 postmaster/syslogger.c:1537 #: replication/logical/origin.c:575 replication/logical/reorderbuffer.c:4567 #: replication/logical/snapbuild.c:1749 replication/logical/snapbuild.c:2169 #: replication/slot.c:1830 storage/file/fd.c:792 storage/file/fd.c:3260 @@ -1625,7 +1625,7 @@ msgid "This may be because of a non-immutable index expression." msgstr "Возможно, это вызвано переменной природой индексного выражения." #: access/nbtree/nbtpage.c:159 access/nbtree/nbtpage.c:608 -#: parser/parse_utilcmd.c:2377 +#: parser/parse_utilcmd.c:2381 #, c-format msgid "index \"%s\" is not a btree" msgstr "индекс \"%s\" не является b-деревом" @@ -1782,7 +1782,7 @@ msgstr "" msgid "Make sure the configuration parameter \"%s\" is set." msgstr "Убедитесь, что в конфигурации установлен параметр \"%s\"." -#: access/transam/multixact.c:1106 +#: access/transam/multixact.c:1155 #, c-format msgid "" "database is not accepting commands that generate new MultiXactIds to avoid " @@ -1791,8 +1791,8 @@ msgstr "" "база данных не принимает команды, создающие новые MultiXactId, во избежание " "потери данных из-за зацикливания в базе данных \"%s\"" -#: access/transam/multixact.c:1108 access/transam/multixact.c:1115 -#: access/transam/multixact.c:1139 access/transam/multixact.c:1148 +#: access/transam/multixact.c:1157 access/transam/multixact.c:1164 +#: access/transam/multixact.c:1188 access/transam/multixact.c:1197 #, c-format msgid "" "Execute a database-wide VACUUM in that database.\n" @@ -1803,7 +1803,7 @@ msgstr "" "Возможно, вам также придётся зафиксировать или откатить старые " "подготовленные транзакции и удалить неиспользуемые слоты репликации." -#: access/transam/multixact.c:1113 +#: access/transam/multixact.c:1162 #, c-format msgid "" "database is not accepting commands that generate new MultiXactIds to avoid " @@ -1812,7 +1812,7 @@ msgstr "" "база данных не принимает команды, создающие новые MultiXactId, во избежание " "потери данных из-за зацикливания в базе данных с OID %u" -#: access/transam/multixact.c:1134 access/transam/multixact.c:2421 +#: access/transam/multixact.c:1183 access/transam/multixact.c:2470 #, c-format msgid "database \"%s\" must be vacuumed before %u more MultiXactId is used" msgid_plural "" @@ -1827,7 +1827,7 @@ msgstr[2] "" "база данных \"%s\" должна быть очищена, прежде чем будут использованы " "оставшиеся MultiXactId (%u)" -#: access/transam/multixact.c:1143 access/transam/multixact.c:2430 +#: access/transam/multixact.c:1192 access/transam/multixact.c:2479 #, c-format msgid "" "database with OID %u must be vacuumed before %u more MultiXactId is used" @@ -1843,12 +1843,12 @@ msgstr[2] "" "база данных с OID %u должна быть очищена, прежде чем будут использованы " "оставшиеся MultiXactId (%u)" -#: access/transam/multixact.c:1207 +#: access/transam/multixact.c:1256 #, c-format msgid "multixact \"members\" limit exceeded" msgstr "слишком много членов мультитранзакции" -#: access/transam/multixact.c:1208 +#: access/transam/multixact.c:1257 #, c-format msgid "" "This command would create a multixact with %u members, but the remaining " @@ -1866,7 +1866,7 @@ msgstr[2] "" "Мультитранзакция, создаваемая этой командой, должна включать членов: %u, но " "оставшегося места хватает только для %u." -#: access/transam/multixact.c:1213 +#: access/transam/multixact.c:1262 #, c-format msgid "" "Execute a database-wide VACUUM in database with OID %u with reduced " @@ -1876,7 +1876,7 @@ msgstr "" "Выполните очистку (VACUUM) всей базы данных с OID %u, уменьшив значения " "vacuum_multixact_freeze_min_age и vacuum_multixact_freeze_table_age." -#: access/transam/multixact.c:1244 +#: access/transam/multixact.c:1293 #, c-format msgid "" "database with OID %u must be vacuumed before %d more multixact member is used" @@ -1893,7 +1893,7 @@ msgstr[2] "" "база данных с OID %u должна быть очищена, пока не использованы оставшиеся " "члены мультитранзакций (%d)" -#: access/transam/multixact.c:1249 +#: access/transam/multixact.c:1298 #, c-format msgid "" "Execute a database-wide VACUUM in that database with reduced " @@ -1903,22 +1903,22 @@ msgstr "" "Выполните очистку (VACUUM) всей этой базы данных, уменьшив значения " "vacuum_multixact_freeze_min_age и vacuum_multixact_freeze_table_age." -#: access/transam/multixact.c:1388 +#: access/transam/multixact.c:1437 #, c-format msgid "MultiXactId %u does no longer exist -- apparent wraparound" msgstr "MultiXactId %u прекратил существование: видимо, произошло зацикливание" -#: access/transam/multixact.c:1394 +#: access/transam/multixact.c:1443 #, c-format msgid "MultiXactId %u has not been created yet -- apparent wraparound" msgstr "MultiXactId %u ещё не был создан: видимо, произошло зацикливание" -#: access/transam/multixact.c:1469 +#: access/transam/multixact.c:1518 #, c-format msgid "MultiXact %u has invalid next offset" msgstr "для мультитранзакции %u получено некорректное следующее смещение" -#: access/transam/multixact.c:2426 access/transam/multixact.c:2435 +#: access/transam/multixact.c:2475 access/transam/multixact.c:2484 #: access/transam/varsup.c:151 access/transam/varsup.c:158 #: access/transam/varsup.c:466 access/transam/varsup.c:473 #, c-format @@ -1932,7 +1932,7 @@ msgstr "" "Возможно, вам также придётся зафиксировать или откатить старые " "подготовленные транзакции и удалить неиспользуемые слоты репликации." -#: access/transam/multixact.c:2709 +#: access/transam/multixact.c:2758 #, c-format msgid "" "MultiXact member wraparound protections are disabled because oldest " @@ -1941,12 +1941,12 @@ msgstr "" "Защита от зацикливания членов мультитранзакций отключена, так как старейшая " "отмеченная мультитранзакция %u не найдена на диске" -#: access/transam/multixact.c:2731 +#: access/transam/multixact.c:2780 #, c-format msgid "MultiXact member wraparound protections are now enabled" msgstr "Защита от зацикливания мультитранзакций сейчас включена" -#: access/transam/multixact.c:3125 +#: access/transam/multixact.c:3174 #, c-format msgid "" "oldest MultiXact %u not found, earliest MultiXact %u, skipping truncation" @@ -1954,7 +1954,7 @@ msgstr "" "старейшая мультитранзакция %u не найдена, новейшая мультитранзакция: %u, " "усечение пропускается" -#: access/transam/multixact.c:3143 +#: access/transam/multixact.c:3192 #, c-format msgid "" "cannot truncate up to MultiXact %u because it does not exist on disk, " @@ -1963,7 +1963,7 @@ msgstr "" "выполнить усечение до мультитранзакции %u нельзя ввиду её отсутствия на " "диске, усечение пропускается" -#: access/transam/multixact.c:3160 +#: access/transam/multixact.c:3209 #, c-format msgid "" "cannot truncate up to MultiXact %u because it has invalid offset, skipping " @@ -1972,7 +1972,7 @@ msgstr "" "выполнить усечение до мультитранзакции %u нельзя из-за некорректного " "смещения, усечение пропускается" -#: access/transam/multixact.c:3489 +#: access/transam/multixact.c:3540 #, c-format msgid "invalid MultiXactId: %u" msgstr "неверный MultiXactId: %u" @@ -2554,7 +2554,7 @@ msgid "could not write to log file %s at offset %u, length %zu: %m" msgstr "не удалось записать в файл журнала %s (смещение: %u, длина: %zu): %m" #: access/transam/xlog.c:3472 access/transam/xlogutils.c:847 -#: replication/walsender.c:2734 +#: replication/walsender.c:2743 #, c-format msgid "requested WAL segment %s has already been removed" msgstr "запрошенный сегмент WAL %s уже удалён" @@ -4284,18 +4284,18 @@ msgid "-X requires a power of two value between 1 MB and 1 GB" msgstr "" "для -X требуется число, равное степени двух, в интервале от 1 МБ до 1 ГБ" -#: bootstrap/bootstrap.c:280 postmaster/postmaster.c:846 tcop/postgres.c:3964 +#: bootstrap/bootstrap.c:280 postmaster/postmaster.c:847 tcop/postgres.c:3964 #, c-format msgid "--%s requires a value" msgstr "для --%s требуется значение" -#: bootstrap/bootstrap.c:285 postmaster/postmaster.c:851 tcop/postgres.c:3969 +#: bootstrap/bootstrap.c:285 postmaster/postmaster.c:852 tcop/postgres.c:3969 #, c-format msgid "-c %s requires a value" msgstr "для -c %s требуется значение" -#: bootstrap/bootstrap.c:296 postmaster/postmaster.c:863 -#: postmaster/postmaster.c:876 +#: bootstrap/bootstrap.c:296 postmaster/postmaster.c:864 +#: postmaster/postmaster.c:877 #, c-format msgid "Try \"%s --help\" for more information.\n" msgstr "Для дополнительной информации попробуйте \"%s --help\".\n" @@ -4455,7 +4455,7 @@ msgid "cannot use IN SCHEMA clause when using GRANT/REVOKE ON SCHEMAS" msgstr "предложение IN SCHEMA нельзя использовать в GRANT/REVOKE ON SCHEMAS" #: catalog/aclchk.c:1588 catalog/catalog.c:657 catalog/objectaddress.c:1543 -#: catalog/pg_publication.c:510 commands/analyze.c:391 commands/copy.c:816 +#: catalog/pg_publication.c:510 commands/analyze.c:391 commands/copy.c:817 #: commands/sequence.c:1673 commands/tablecmds.c:7376 commands/tablecmds.c:7532 #: commands/tablecmds.c:7582 commands/tablecmds.c:7656 #: commands/tablecmds.c:7726 commands/tablecmds.c:7838 @@ -4467,8 +4467,8 @@ msgstr "предложение IN SCHEMA нельзя использовать #: commands/tablecmds.c:12793 commands/tablecmds.c:14013 #: commands/tablecmds.c:16583 commands/trigger.c:954 parser/analyze.c:2517 #: parser/parse_relation.c:725 parser/parse_target.c:1077 -#: parser/parse_type.c:144 parser/parse_utilcmd.c:3465 -#: parser/parse_utilcmd.c:3501 parser/parse_utilcmd.c:3543 utils/adt/acl.c:2886 +#: parser/parse_type.c:144 parser/parse_utilcmd.c:3469 +#: parser/parse_utilcmd.c:3505 parser/parse_utilcmd.c:3547 utils/adt/acl.c:2886 #: utils/adt/ruleutils.c:2828 #, c-format msgid "column \"%s\" of relation \"%s\" does not exist" @@ -5034,7 +5034,7 @@ msgstr "вызывать %s() может только суперпользова msgid "pg_nextoid() can only be used on system catalogs" msgstr "pg_nextoid() можно использовать только для системных каталогов" -#: catalog/catalog.c:649 parser/parse_utilcmd.c:2324 +#: catalog/catalog.c:649 parser/parse_utilcmd.c:2328 #, c-format msgid "index \"%s\" does not belong to table \"%s\"" msgstr "индекс \"%s\" не принадлежит таблице \"%s\"" @@ -5167,23 +5167,23 @@ msgid "column name \"%s\" specified more than once" msgstr "имя столбца \"%s\" указано неоднократно" #. translator: first %s is an integer not a name -#: catalog/heap.c:579 +#: catalog/heap.c:583 #, c-format msgid "partition key column %s has pseudo-type %s" msgstr "столбец \"%s\" ключа разбиения имеет псевдотип %s" -#: catalog/heap.c:584 +#: catalog/heap.c:588 #, c-format msgid "column \"%s\" has pseudo-type %s" msgstr "столбец \"%s\" имеет псевдотип %s" -#: catalog/heap.c:615 +#: catalog/heap.c:619 #, c-format msgid "composite type %s cannot be made a member of itself" msgstr "составной тип %s не может содержать себя же" #. translator: first %s is an integer not a name -#: catalog/heap.c:670 +#: catalog/heap.c:684 #, c-format msgid "" "no collation was derived for partition key column %s with collatable type %s" @@ -5191,20 +5191,20 @@ msgstr "" "для входящего в ключ разбиения столбца \"%s\" с сортируемым типом %s не " "удалось получить правило сортировки" -#: catalog/heap.c:676 commands/createas.c:203 commands/createas.c:512 +#: catalog/heap.c:690 commands/createas.c:203 commands/createas.c:512 #, c-format msgid "no collation was derived for column \"%s\" with collatable type %s" msgstr "" "для столбца \"%s\" с сортируемым типом %s не удалось получить правило " "сортировки" -#: catalog/heap.c:1152 catalog/index.c:875 commands/createas.c:408 +#: catalog/heap.c:1166 catalog/index.c:875 commands/createas.c:408 #: commands/tablecmds.c:3921 #, c-format msgid "relation \"%s\" already exists" msgstr "отношение \"%s\" уже существует" -#: catalog/heap.c:1168 catalog/pg_type.c:436 catalog/pg_type.c:784 +#: catalog/heap.c:1182 catalog/pg_type.c:436 catalog/pg_type.c:784 #: catalog/pg_type.c:931 commands/typecmds.c:249 commands/typecmds.c:261 #: commands/typecmds.c:754 commands/typecmds.c:1169 commands/typecmds.c:1395 #: commands/typecmds.c:1575 commands/typecmds.c:2547 @@ -5212,7 +5212,7 @@ msgstr "отношение \"%s\" уже существует" msgid "type \"%s\" already exists" msgstr "тип \"%s\" уже существует" -#: catalog/heap.c:1169 +#: catalog/heap.c:1183 #, c-format msgid "" "A relation has an associated type of the same name, so you must use a name " @@ -5221,53 +5221,53 @@ msgstr "" "С отношением уже связан тип с таким же именем; выберите имя, не " "конфликтующее с существующими типами." -#: catalog/heap.c:1209 +#: catalog/heap.c:1223 #, c-format msgid "toast relfilenode value not set when in binary upgrade mode" msgstr "значение relfilenode для TOAST не задано в режиме двоичного обновления" -#: catalog/heap.c:1220 +#: catalog/heap.c:1234 #, c-format msgid "pg_class heap OID value not set when in binary upgrade mode" msgstr "значение OID кучи в pg_class не задано в режиме двоичного обновления" -#: catalog/heap.c:1230 +#: catalog/heap.c:1244 #, c-format msgid "relfilenode value not set when in binary upgrade mode" msgstr "значение relfilenode не задано в режиме двоичного обновления" -#: catalog/heap.c:2192 +#: catalog/heap.c:2206 #, c-format msgid "cannot add NO INHERIT constraint to partitioned table \"%s\"" msgstr "" "добавить ограничение NO INHERIT к секционированной таблице \"%s\" нельзя" -#: catalog/heap.c:2462 +#: catalog/heap.c:2476 #, c-format msgid "check constraint \"%s\" already exists" msgstr "ограничение-проверка \"%s\" уже существует" -#: catalog/heap.c:2632 catalog/index.c:889 catalog/pg_constraint.c:690 +#: catalog/heap.c:2646 catalog/index.c:889 catalog/pg_constraint.c:690 #: commands/tablecmds.c:8972 #, c-format msgid "constraint \"%s\" for relation \"%s\" already exists" msgstr "ограничение \"%s\" для отношения \"%s\" уже существует" -#: catalog/heap.c:2639 +#: catalog/heap.c:2653 #, c-format msgid "" "constraint \"%s\" conflicts with non-inherited constraint on relation \"%s\"" msgstr "" "ограничение \"%s\" конфликтует с ненаследуемым ограничением таблицы \"%s\"" -#: catalog/heap.c:2650 +#: catalog/heap.c:2664 #, c-format msgid "" "constraint \"%s\" conflicts with inherited constraint on relation \"%s\"" msgstr "" "ограничение \"%s\" конфликтует с наследуемым ограничением таблицы \"%s\"" -#: catalog/heap.c:2660 +#: catalog/heap.c:2674 #, c-format msgid "" "constraint \"%s\" conflicts with NOT VALID constraint on relation \"%s\"" @@ -5275,64 +5275,64 @@ msgstr "" "ограничение \"%s\" конфликтует с непроверенным (NOT VALID) ограничением " "таблицы \"%s\"" -#: catalog/heap.c:2665 +#: catalog/heap.c:2679 #, c-format msgid "merging constraint \"%s\" with inherited definition" msgstr "слияние ограничения \"%s\" с унаследованным определением" -#: catalog/heap.c:2770 +#: catalog/heap.c:2784 #, c-format msgid "cannot use generated column \"%s\" in column generation expression" msgstr "" "использовать генерируемый столбец \"%s\" в выражении генерируемого столбца " "нельзя" -#: catalog/heap.c:2772 +#: catalog/heap.c:2786 #, c-format msgid "A generated column cannot reference another generated column." msgstr "" "Генерируемый столбец не может ссылаться на другой генерируемый столбец." -#: catalog/heap.c:2778 +#: catalog/heap.c:2792 #, c-format msgid "cannot use whole-row variable in column generation expression" msgstr "" "в выражении генерируемого столбца нельзя использовать переменные «вся строка»" -#: catalog/heap.c:2779 +#: catalog/heap.c:2793 #, c-format msgid "This would cause the generated column to depend on its own value." msgstr "" "Это сделало бы генерируемый столбец зависимым от собственного значения." -#: catalog/heap.c:2834 +#: catalog/heap.c:2848 #, c-format msgid "generation expression is not immutable" msgstr "генерирующее выражение не является постоянным" -#: catalog/heap.c:2862 rewrite/rewriteHandler.c:1288 +#: catalog/heap.c:2876 rewrite/rewriteHandler.c:1322 #, c-format msgid "column \"%s\" is of type %s but default expression is of type %s" msgstr "столбец \"%s\" имеет тип %s, но тип выражения по умолчанию %s" -#: catalog/heap.c:2867 commands/prepare.c:334 parser/analyze.c:2741 +#: catalog/heap.c:2881 commands/prepare.c:334 parser/analyze.c:2741 #: parser/parse_target.c:594 parser/parse_target.c:891 -#: parser/parse_target.c:901 rewrite/rewriteHandler.c:1293 +#: parser/parse_target.c:901 rewrite/rewriteHandler.c:1327 #, c-format msgid "You will need to rewrite or cast the expression." msgstr "Перепишите выражение или преобразуйте его тип." -#: catalog/heap.c:2914 +#: catalog/heap.c:2928 #, c-format msgid "only table \"%s\" can be referenced in check constraint" msgstr "в ограничении-проверке можно ссылаться только на таблицу \"%s\"" -#: catalog/heap.c:3212 +#: catalog/heap.c:3226 #, c-format msgid "unsupported ON COMMIT and foreign key combination" msgstr "неподдерживаемое сочетание внешнего ключа с ON COMMIT" -#: catalog/heap.c:3213 +#: catalog/heap.c:3227 #, c-format msgid "" "Table \"%s\" references \"%s\", but they do not have the same ON COMMIT " @@ -5340,23 +5340,23 @@ msgid "" msgstr "" "Таблица \"%s\" ссылается на \"%s\", и для них задан разный режим ON COMMIT." -#: catalog/heap.c:3218 +#: catalog/heap.c:3232 #, c-format msgid "cannot truncate a table referenced in a foreign key constraint" msgstr "опустошить таблицу, на которую ссылается внешний ключ, нельзя" -#: catalog/heap.c:3219 +#: catalog/heap.c:3233 #, c-format msgid "Table \"%s\" references \"%s\"." msgstr "Таблица \"%s\" ссылается на \"%s\"." -#: catalog/heap.c:3221 +#: catalog/heap.c:3235 #, c-format msgid "Truncate table \"%s\" at the same time, or use TRUNCATE ... CASCADE." msgstr "" "Опустошите таблицу \"%s\" параллельно или используйте TRUNCATE ... CASCADE." -#: catalog/index.c:224 parser/parse_utilcmd.c:2229 +#: catalog/index.c:224 parser/parse_utilcmd.c:2233 #, c-format msgid "multiple primary keys for table \"%s\" are not allowed" msgstr "таблица \"%s\" не может иметь несколько первичных ключей" @@ -5660,7 +5660,7 @@ msgid "user mapping for user \"%s\" on server \"%s\" does not exist" msgstr "сопоставление для пользователя \"%s\" на сервере \"%s\" не существует" #: catalog/objectaddress.c:1854 commands/foreigncmds.c:441 -#: commands/foreigncmds.c:1004 commands/foreigncmds.c:1367 +#: commands/foreigncmds.c:1009 commands/foreigncmds.c:1372 #: foreign/foreign.c:701 #, c-format msgid "server \"%s\" does not exist" @@ -6125,7 +6125,7 @@ msgstr "" msgid "return type of inverse transition function %s is not %s" msgstr "обратная функция перехода %s должна возвращать тип %s" -#: catalog/pg_aggregate.c:352 executor/nodeWindowAgg.c:3006 +#: catalog/pg_aggregate.c:352 executor/nodeWindowAgg.c:3054 #, c-format msgid "" "strictness of aggregate's forward and inverse transition functions must match" @@ -6963,7 +6963,7 @@ msgstr "событийный триггер \"%s\" уже существует" msgid "foreign-data wrapper \"%s\" already exists" msgstr "обёртка сторонних данных \"%s\" уже существует" -#: commands/alter.c:91 commands/foreigncmds.c:895 +#: commands/alter.c:91 commands/foreigncmds.c:900 #, c-format msgid "server \"%s\" already exists" msgstr "сервер \"%s\" уже существует" @@ -7456,179 +7456,179 @@ msgstr "" "для выполнения COPY с записью в файл нужно быть суперпользователем или иметь " "права роли pg_write_server_files" -#: commands/copy.c:175 +#: commands/copy.c:176 #, c-format msgid "generated columns are not supported in COPY FROM WHERE conditions" msgstr "генерируемые столбцы не поддерживаются в условиях COPY FROM WHERE" -#: commands/copy.c:176 commands/tablecmds.c:12461 commands/tablecmds.c:17648 +#: commands/copy.c:177 commands/tablecmds.c:12461 commands/tablecmds.c:17648 #: commands/tablecmds.c:17727 commands/trigger.c:668 -#: rewrite/rewriteHandler.c:939 rewrite/rewriteHandler.c:974 +#: rewrite/rewriteHandler.c:973 rewrite/rewriteHandler.c:1008 #, c-format msgid "Column \"%s\" is a generated column." msgstr "Столбец \"%s\" является генерируемым." -#: commands/copy.c:225 +#: commands/copy.c:226 #, c-format msgid "COPY FROM not supported with row-level security" msgstr "COPY FROM не поддерживается с защитой на уровне строк." -#: commands/copy.c:226 +#: commands/copy.c:227 #, c-format msgid "Use INSERT statements instead." msgstr "Используйте операторы INSERT." -#: commands/copy.c:320 +#: commands/copy.c:321 #, c-format msgid "MERGE not supported in COPY" msgstr "MERGE не поддерживается в COPY" -#: commands/copy.c:413 +#: commands/copy.c:414 #, c-format msgid "cannot use \"%s\" with HEADER in COPY TO" msgstr "использовать \"%s\" с параметром HEADER в COPY TO нельзя" -#: commands/copy.c:422 +#: commands/copy.c:423 #, c-format msgid "%s requires a Boolean value or \"match\"" msgstr "%s требует логическое значение или \"match\"" -#: commands/copy.c:481 +#: commands/copy.c:482 #, c-format msgid "COPY format \"%s\" not recognized" msgstr "формат \"%s\" для COPY не распознан" -#: commands/copy.c:533 commands/copy.c:546 commands/copy.c:559 -#: commands/copy.c:578 +#: commands/copy.c:534 commands/copy.c:547 commands/copy.c:560 +#: commands/copy.c:579 #, c-format msgid "argument to option \"%s\" must be a list of column names" msgstr "аргументом параметра \"%s\" должен быть список имён столбцов" -#: commands/copy.c:590 +#: commands/copy.c:591 #, c-format msgid "argument to option \"%s\" must be a valid encoding name" msgstr "аргументом параметра \"%s\" должно быть название допустимой кодировки" -#: commands/copy.c:597 commands/dbcommands.c:849 commands/dbcommands.c:2270 +#: commands/copy.c:598 commands/dbcommands.c:849 commands/dbcommands.c:2270 #, c-format msgid "option \"%s\" not recognized" msgstr "параметр \"%s\" не распознан" -#: commands/copy.c:609 +#: commands/copy.c:610 #, c-format msgid "cannot specify DELIMITER in BINARY mode" msgstr "в режиме BINARY нельзя указывать DELIMITER" -#: commands/copy.c:614 +#: commands/copy.c:615 #, c-format msgid "cannot specify NULL in BINARY mode" msgstr "в режиме BINARY нельзя указывать NULL" -#: commands/copy.c:636 +#: commands/copy.c:637 #, c-format msgid "COPY delimiter must be a single one-byte character" msgstr "разделитель для COPY должен быть однобайтным символом" -#: commands/copy.c:643 +#: commands/copy.c:644 #, c-format msgid "COPY delimiter cannot be newline or carriage return" msgstr "" "разделителем для COPY не может быть символ новой строки или возврата каретки" -#: commands/copy.c:649 +#: commands/copy.c:650 #, c-format msgid "COPY null representation cannot use newline or carriage return" msgstr "" "представление NULL для COPY не может включать символ новой строки или " "возврата каретки" -#: commands/copy.c:666 +#: commands/copy.c:667 #, c-format msgid "COPY delimiter cannot be \"%s\"" msgstr "\"%s\" не может быть разделителем для COPY" -#: commands/copy.c:672 +#: commands/copy.c:673 #, c-format msgid "cannot specify HEADER in BINARY mode" msgstr "в режиме BINARY нельзя использовать HEADER" -#: commands/copy.c:678 +#: commands/copy.c:679 #, c-format msgid "COPY quote available only in CSV mode" msgstr "определить кавычки для COPY можно только в режиме CSV" -#: commands/copy.c:683 +#: commands/copy.c:684 #, c-format msgid "COPY quote must be a single one-byte character" msgstr "символ кавычек для COPY должен быть однобайтным" -#: commands/copy.c:688 +#: commands/copy.c:689 #, c-format msgid "COPY delimiter and quote must be different" msgstr "символ кавычек для COPY должен отличаться от разделителя" -#: commands/copy.c:694 +#: commands/copy.c:695 #, c-format msgid "COPY escape available only in CSV mode" msgstr "определить спецсимвол для COPY можно только в режиме CSV" -#: commands/copy.c:699 +#: commands/copy.c:700 #, c-format msgid "COPY escape must be a single one-byte character" msgstr "спецсимвол для COPY должен быть однобайтным" -#: commands/copy.c:705 +#: commands/copy.c:706 #, c-format msgid "COPY force quote available only in CSV mode" msgstr "параметр force quote для COPY можно использовать только в режиме CSV" -#: commands/copy.c:709 +#: commands/copy.c:710 #, c-format msgid "COPY force quote only available using COPY TO" msgstr "параметр force quote для COPY можно использовать только с COPY TO" -#: commands/copy.c:715 +#: commands/copy.c:716 #, c-format msgid "COPY force not null available only in CSV mode" msgstr "" "параметр force not null для COPY можно использовать только в режиме CSV" -#: commands/copy.c:719 +#: commands/copy.c:720 #, c-format msgid "COPY force not null only available using COPY FROM" msgstr "параметр force not null для COPY можно использовать только с COPY FROM" -#: commands/copy.c:725 +#: commands/copy.c:726 #, c-format msgid "COPY force null available only in CSV mode" msgstr "параметр force null для COPY можно использовать только в режиме CSV" -#: commands/copy.c:730 +#: commands/copy.c:731 #, c-format msgid "COPY force null only available using COPY FROM" msgstr "параметр force null для COPY можно использовать только с COPY FROM" -#: commands/copy.c:736 +#: commands/copy.c:737 #, c-format msgid "COPY delimiter must not appear in the NULL specification" msgstr "разделитель для COPY не должен присутствовать в представлении NULL" -#: commands/copy.c:743 +#: commands/copy.c:744 #, c-format msgid "CSV quote character must not appear in the NULL specification" msgstr "символ кавычек в CSV не должен присутствовать в представлении NULL" -#: commands/copy.c:804 +#: commands/copy.c:805 #, c-format msgid "column \"%s\" is a generated column" msgstr "столбец \"%s\" — генерируемый" -#: commands/copy.c:806 +#: commands/copy.c:807 #, c-format msgid "Generated columns cannot be used in COPY." msgstr "Генерируемые столбцы нельзя использовать в COPY." -#: commands/copy.c:821 commands/indexcmds.c:1833 commands/statscmds.c:263 +#: commands/copy.c:822 commands/indexcmds.c:1833 commands/statscmds.c:263 #: commands/tablecmds.c:2393 commands/tablecmds.c:3049 #: commands/tablecmds.c:3558 parser/parse_relation.c:3669 #: parser/parse_relation.c:3689 utils/adt/tsvector_op.c:2692 @@ -7636,7 +7636,7 @@ msgstr "Генерируемые столбцы нельзя использов msgid "column \"%s\" does not exist" msgstr "столбец \"%s\" не существует" -#: commands/copy.c:828 commands/tablecmds.c:2419 commands/trigger.c:963 +#: commands/copy.c:829 commands/tablecmds.c:2419 commands/trigger.c:963 #: parser/parse_target.c:1093 parser/parse_target.c:1104 #, c-format msgid "column \"%s\" specified more than once" @@ -8635,7 +8635,7 @@ msgstr "правило \"%s\" для отношения \"%s\" не сущест msgid "foreign-data wrapper \"%s\" does not exist, skipping" msgstr "обёртка сторонних данных \"%s\" не существует, пропускается" -#: commands/dropcmds.c:453 commands/foreigncmds.c:1371 +#: commands/dropcmds.c:453 commands/foreigncmds.c:1376 #, c-format msgid "server \"%s\" does not exist, skipping" msgstr "сервер \"%s\" не существует, пропускается" @@ -8721,7 +8721,7 @@ msgstr "%s можно вызывать только в событийной тр msgid "%s can only be called in a table_rewrite event trigger function" msgstr "%s можно вызывать только в событийной триггерной функции table_rewrite" -#: commands/event_trigger.c:1967 +#: commands/event_trigger.c:1970 #, c-format msgid "%s can only be called in an event trigger function" msgstr "%s можно вызывать только в событийной триггерной функции" @@ -9108,7 +9108,7 @@ msgstr "" "при изменении обработчика в обёртке сторонних данных может измениться " "поведение существующих сторонних таблиц" -#: commands/foreigncmds.c:756 +#: commands/foreigncmds.c:761 #, c-format msgid "" "changing the foreign-data wrapper validator can cause the options for " @@ -9117,46 +9117,46 @@ msgstr "" "при изменении функции проверки в обёртке сторонних данных параметры " "зависимых объектов могут стать неверными" -#: commands/foreigncmds.c:887 +#: commands/foreigncmds.c:892 #, c-format msgid "server \"%s\" already exists, skipping" msgstr "сервер \"%s\" уже существует, пропускается" -#: commands/foreigncmds.c:1155 +#: commands/foreigncmds.c:1160 #, c-format msgid "user mapping for \"%s\" already exists for server \"%s\", skipping" msgstr "" "сопоставление пользователя \"%s\" для сервера \"%s\" уже существует, " "пропускается" -#: commands/foreigncmds.c:1165 +#: commands/foreigncmds.c:1170 #, c-format msgid "user mapping for \"%s\" already exists for server \"%s\"" msgstr "сопоставление пользователя \"%s\" для сервера \"%s\" уже существует" -#: commands/foreigncmds.c:1265 commands/foreigncmds.c:1385 +#: commands/foreigncmds.c:1270 commands/foreigncmds.c:1390 #, c-format msgid "user mapping for \"%s\" does not exist for server \"%s\"" msgstr "сопоставление пользователя \"%s\" для сервера \"%s\" не существует" -#: commands/foreigncmds.c:1390 +#: commands/foreigncmds.c:1395 #, c-format msgid "user mapping for \"%s\" does not exist for server \"%s\", skipping" msgstr "" "сопоставление пользователя \"%s\" для сервера \"%s\" не существует, " "пропускается" -#: commands/foreigncmds.c:1518 foreign/foreign.c:400 +#: commands/foreigncmds.c:1523 foreign/foreign.c:400 #, c-format msgid "foreign-data wrapper \"%s\" has no handler" msgstr "обёртка сторонних данных \"%s\" не имеет обработчика" -#: commands/foreigncmds.c:1524 +#: commands/foreigncmds.c:1529 #, c-format msgid "foreign-data wrapper \"%s\" does not support IMPORT FOREIGN SCHEMA" msgstr "обёртка сторонних данных \"%s\" не поддерживает IMPORT FOREIGN SCHEMA" -#: commands/foreigncmds.c:1626 +#: commands/foreigncmds.c:1631 #, c-format msgid "importing foreign table \"%s\"" msgstr "импорт сторонней таблицы \"%s\"" @@ -9673,8 +9673,8 @@ msgstr "Таблица \"%s\" содержит секции, являющиес msgid "functions in index predicate must be marked IMMUTABLE" msgstr "функции в предикате индекса должны быть помечены как IMMUTABLE" -#: commands/indexcmds.c:1828 parser/parse_utilcmd.c:2573 -#: parser/parse_utilcmd.c:2708 +#: commands/indexcmds.c:1828 parser/parse_utilcmd.c:2577 +#: parser/parse_utilcmd.c:2712 #, c-format msgid "column \"%s\" named in key does not exist" msgstr "указанный в ключе столбец \"%s\" не существует" @@ -9715,7 +9715,7 @@ msgid "could not determine which collation to use for index expression" msgstr "не удалось определить правило сортировки для индексного выражения" #: commands/indexcmds.c:1969 commands/tablecmds.c:17815 commands/typecmds.c:807 -#: parser/parse_expr.c:2698 parser/parse_type.c:570 parser/parse_utilcmd.c:3823 +#: parser/parse_expr.c:2698 parser/parse_type.c:570 parser/parse_utilcmd.c:3827 #: utils/adt/misc.c:594 #, c-format msgid "collations are not supported by type %s" @@ -10344,8 +10344,8 @@ msgid "must be superuser to create custom procedural language" msgstr "" "для создания дополнительного процедурного языка нужно быть суперпользователем" -#: commands/publicationcmds.c:135 postmaster/postmaster.c:1224 -#: postmaster/postmaster.c:1323 utils/init/miscinit.c:1703 +#: commands/publicationcmds.c:135 postmaster/postmaster.c:1225 +#: postmaster/postmaster.c:1324 utils/init/miscinit.c:1703 #, c-format msgid "invalid list syntax in parameter \"%s\"" msgstr "неверный формат списка в параметре \"%s\"" @@ -11124,7 +11124,7 @@ msgstr "" "представления." #: commands/tablecmds.c:269 commands/tablecmds.c:293 commands/tablecmds.c:19425 -#: parser/parse_utilcmd.c:2305 +#: parser/parse_utilcmd.c:2309 #, c-format msgid "index \"%s\" does not exist" msgstr "индекс \"%s\" не существует" @@ -11261,8 +11261,8 @@ msgstr "наследование от секционированной табл msgid "cannot inherit from partition \"%s\"" msgstr "наследование от секции \"%s\" не допускается" -#: commands/tablecmds.c:2489 parser/parse_utilcmd.c:2535 -#: parser/parse_utilcmd.c:2677 +#: commands/tablecmds.c:2489 parser/parse_utilcmd.c:2539 +#: parser/parse_utilcmd.c:2681 #, c-format msgid "inherited relation \"%s\" is not a table or foreign table" msgstr "" @@ -12703,29 +12703,29 @@ msgstr "секционированная таблица \"%s\" была пара msgid "partition \"%s\" was removed concurrently" msgstr "секция \"%s\" была параллельно удалена" -#: commands/tablecmds.c:19459 commands/tablecmds.c:19479 -#: commands/tablecmds.c:19499 commands/tablecmds.c:19518 -#: commands/tablecmds.c:19560 +#: commands/tablecmds.c:19462 commands/tablecmds.c:19482 +#: commands/tablecmds.c:19502 commands/tablecmds.c:19521 +#: commands/tablecmds.c:19571 #, c-format msgid "cannot attach index \"%s\" as a partition of index \"%s\"" msgstr "нельзя присоединить индекс \"%s\" в качестве секции индекса \"%s\"" -#: commands/tablecmds.c:19462 +#: commands/tablecmds.c:19465 #, c-format msgid "Index \"%s\" is already attached to another index." msgstr "Индекс \"%s\" уже присоединён к другому индексу." -#: commands/tablecmds.c:19482 +#: commands/tablecmds.c:19485 #, c-format msgid "Index \"%s\" is not an index on any partition of table \"%s\"." msgstr "Индекс \"%s\" не является индексом какой-либо секции таблицы \"%s\"." -#: commands/tablecmds.c:19502 +#: commands/tablecmds.c:19505 #, c-format msgid "The index definitions do not match." msgstr "Определения индексов не совпадают." -#: commands/tablecmds.c:19521 +#: commands/tablecmds.c:19524 #, c-format msgid "" "The index \"%s\" belongs to a constraint in table \"%s\" but no constraint " @@ -12734,17 +12734,17 @@ msgstr "" "Индекс \"%s\" принадлежит ограничению в таблице \"%s\", но для индекса " "\"%s\" ограничения нет." -#: commands/tablecmds.c:19563 +#: commands/tablecmds.c:19574 #, c-format msgid "Another index is already attached for partition \"%s\"." msgstr "К секции \"%s\" уже присоединён другой индекс." -#: commands/tablecmds.c:19800 +#: commands/tablecmds.c:19811 #, c-format msgid "column data type %s does not support compression" msgstr "тим данных столбца %s не поддерживает сжатие" -#: commands/tablecmds.c:19807 +#: commands/tablecmds.c:19818 #, c-format msgid "invalid compression method \"%s\"" msgstr "неверный метод сжатия \"%s\"" @@ -13161,7 +13161,7 @@ msgstr "собрать переходные кортежи из дочерних #: commands/trigger.c:3472 executor/nodeModifyTable.c:1543 #: executor/nodeModifyTable.c:1617 executor/nodeModifyTable.c:2384 #: executor/nodeModifyTable.c:2475 executor/nodeModifyTable.c:3027 -#: executor/nodeModifyTable.c:3166 +#: executor/nodeModifyTable.c:3171 #, c-format msgid "" "Consider using an AFTER trigger instead of a BEFORE trigger to propagate " @@ -13173,7 +13173,7 @@ msgstr "" #: commands/trigger.c:3513 executor/nodeLockRows.c:229 #: executor/nodeLockRows.c:238 executor/nodeModifyTable.c:337 #: executor/nodeModifyTable.c:1559 executor/nodeModifyTable.c:2401 -#: executor/nodeModifyTable.c:2625 +#: executor/nodeModifyTable.c:2625 executor/nodeModifyTable.c:3064 #, c-format msgid "could not serialize access due to concurrent update" msgstr "не удалось сериализовать доступ из-за параллельного изменения" @@ -14320,7 +14320,7 @@ msgid "cursor \"%s\" is not a simply updatable scan of table \"%s\"" msgstr "" "для курсора \"%s\" не выполняется обновляемое сканирование таблицы \"%s\"" -#: executor/execCurrent.c:280 executor/execExprInterp.c:2466 +#: executor/execCurrent.c:280 executor/execExprInterp.c:2474 #, c-format msgid "" "type of parameter %d (%s) does not match that when preparing the plan (%s)" @@ -14328,14 +14328,14 @@ msgstr "" "тип параметра %d (%s) не соответствует тому, с которым подготавливался план " "(%s)" -#: executor/execCurrent.c:292 executor/execExprInterp.c:2478 +#: executor/execCurrent.c:292 executor/execExprInterp.c:2486 #, c-format msgid "no value found for parameter %d" msgstr "не найдено значение параметра %d" #: executor/execExpr.c:636 executor/execExpr.c:643 executor/execExpr.c:649 -#: executor/execExprInterp.c:4074 executor/execExprInterp.c:4091 -#: executor/execExprInterp.c:4190 executor/nodeModifyTable.c:206 +#: executor/execExprInterp.c:4145 executor/execExprInterp.c:4162 +#: executor/execExprInterp.c:4261 executor/nodeModifyTable.c:206 #: executor/nodeModifyTable.c:225 executor/nodeModifyTable.c:242 #: executor/nodeModifyTable.c:252 executor/nodeModifyTable.c:262 #, c-format @@ -14353,7 +14353,7 @@ msgid "Query provides a value for a dropped column at ordinal position %d." msgstr "" "Запрос выдаёт значение для удалённого столбца (с порядковым номером %d)." -#: executor/execExpr.c:650 executor/execExprInterp.c:4092 +#: executor/execExpr.c:650 executor/execExprInterp.c:4163 #: executor/nodeModifyTable.c:253 #, c-format msgid "Table has type %s at ordinal position %d, but query expects %s." @@ -14404,23 +14404,23 @@ msgstr "" msgid "type %s does not support subscripted assignment" msgstr "тип %s не поддерживает изменение элемента по индексу" -#: executor/execExprInterp.c:1930 +#: executor/execExprInterp.c:1938 #, c-format msgid "attribute %d of type %s has been dropped" msgstr "атрибут %d типа %s был удалён" -#: executor/execExprInterp.c:1936 +#: executor/execExprInterp.c:1944 #, c-format msgid "attribute %d of type %s has wrong type" msgstr "атрибут %d типа %s имеет неправильный тип" -#: executor/execExprInterp.c:1938 executor/execExprInterp.c:3072 -#: executor/execExprInterp.c:3118 +#: executor/execExprInterp.c:1946 executor/execExprInterp.c:3080 +#: executor/execExprInterp.c:3126 #, c-format msgid "Table has type %s, but query expects %s." msgstr "В таблице задан тип %s, а в запросе ожидается %s." -#: executor/execExprInterp.c:2018 utils/adt/expandedrecord.c:99 +#: executor/execExprInterp.c:2026 utils/adt/expandedrecord.c:99 #: utils/adt/expandedrecord.c:231 utils/cache/typcache.c:1749 #: utils/cache/typcache.c:1908 utils/cache/typcache.c:2055 #: utils/fmgr/funcapi.c:578 @@ -14428,17 +14428,17 @@ msgstr "В таблице задан тип %s, а в запросе ожида msgid "type %s is not composite" msgstr "тип %s не является составным" -#: executor/execExprInterp.c:2556 +#: executor/execExprInterp.c:2564 #, c-format msgid "WHERE CURRENT OF is not supported for this table type" msgstr "WHERE CURRENT OF для таблиц такого типа не поддерживается" -#: executor/execExprInterp.c:2769 +#: executor/execExprInterp.c:2777 #, c-format msgid "cannot merge incompatible arrays" msgstr "не удалось объединить несовместимые массивы" -#: executor/execExprInterp.c:2770 +#: executor/execExprInterp.c:2778 #, c-format msgid "" "Array with element type %s cannot be included in ARRAY construct with " @@ -14447,7 +14447,7 @@ msgstr "" "Массив с типом элементов %s нельзя включить в конструкцию ARRAY с типом " "элементов %s." -#: executor/execExprInterp.c:2791 utils/adt/arrayfuncs.c:264 +#: executor/execExprInterp.c:2799 utils/adt/arrayfuncs.c:264 #: utils/adt/arrayfuncs.c:564 utils/adt/arrayfuncs.c:1306 #: utils/adt/arrayfuncs.c:3515 utils/adt/arrayfuncs.c:5587 #: utils/adt/arrayfuncs.c:6106 utils/adt/arraysubs.c:150 @@ -14456,7 +14456,7 @@ msgstr "" msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)" msgstr "число размерностей массива (%d) превышает предел (%d)" -#: executor/execExprInterp.c:2811 executor/execExprInterp.c:2846 +#: executor/execExprInterp.c:2819 executor/execExprInterp.c:2854 #, c-format msgid "" "multidimensional arrays must have array expressions with matching dimensions" @@ -14464,7 +14464,7 @@ msgstr "" "для многомерных массивов должны задаваться выражения с соответствующими " "размерностями" -#: executor/execExprInterp.c:2823 utils/adt/array_expanded.c:274 +#: executor/execExprInterp.c:2831 utils/adt/array_expanded.c:274 #: utils/adt/arrayfuncs.c:937 utils/adt/arrayfuncs.c:1545 #: utils/adt/arrayfuncs.c:2353 utils/adt/arrayfuncs.c:2368 #: utils/adt/arrayfuncs.c:2630 utils/adt/arrayfuncs.c:2646 @@ -14477,22 +14477,22 @@ msgstr "" msgid "array size exceeds the maximum allowed (%d)" msgstr "размер массива превышает предел (%d)" -#: executor/execExprInterp.c:3071 executor/execExprInterp.c:3117 +#: executor/execExprInterp.c:3079 executor/execExprInterp.c:3125 #, c-format msgid "attribute %d has wrong type" msgstr "атрибут %d имеет неверный тип" -#: executor/execExprInterp.c:3703 utils/adt/domains.c:149 +#: executor/execExprInterp.c:3774 utils/adt/domains.c:149 #, c-format msgid "domain %s does not allow null values" msgstr "домен %s не допускает значения null" -#: executor/execExprInterp.c:3718 utils/adt/domains.c:184 +#: executor/execExprInterp.c:3789 utils/adt/domains.c:184 #, c-format msgid "value for domain %s violates check constraint \"%s\"" msgstr "значение домена %s нарушает ограничение-проверку \"%s\"" -#: executor/execExprInterp.c:4075 +#: executor/execExprInterp.c:4146 #, c-format msgid "Table row contains %d attribute, but query expects %d." msgid_plural "Table row contains %d attributes, but query expects %d." @@ -14500,7 +14500,7 @@ msgstr[0] "Строка таблицы содержит %d атрибут, а в msgstr[1] "Строка таблицы содержит %d атрибута, а в запросе ожидается %d." msgstr[2] "Строка таблицы содержит %d атрибутов, а в запросе ожидается %d." -#: executor/execExprInterp.c:4191 executor/execSRF.c:977 +#: executor/execExprInterp.c:4262 executor/execSRF.c:977 #, c-format msgid "Physical storage mismatch on dropped attribute at ordinal position %d." msgstr "" @@ -14556,14 +14556,14 @@ msgstr "последовательность \"%s\" изменить нельз msgid "cannot change TOAST relation \"%s\"" msgstr "TOAST-отношение \"%s\" изменить нельзя" -#: executor/execMain.c:1063 rewrite/rewriteHandler.c:3152 -#: rewrite/rewriteHandler.c:4057 +#: executor/execMain.c:1063 rewrite/rewriteHandler.c:3217 +#: rewrite/rewriteHandler.c:4122 #, c-format msgid "cannot insert into view \"%s\"" msgstr "вставить данные в представление \"%s\" нельзя" -#: executor/execMain.c:1065 rewrite/rewriteHandler.c:3155 -#: rewrite/rewriteHandler.c:4060 +#: executor/execMain.c:1065 rewrite/rewriteHandler.c:3220 +#: rewrite/rewriteHandler.c:4125 #, c-format msgid "" "To enable inserting into the view, provide an INSTEAD OF INSERT trigger or " @@ -14572,14 +14572,14 @@ msgstr "" "Чтобы представление допускало добавление данных, установите триггер INSTEAD " "OF INSERT или безусловное правило ON INSERT DO INSTEAD." -#: executor/execMain.c:1071 rewrite/rewriteHandler.c:3160 -#: rewrite/rewriteHandler.c:4065 +#: executor/execMain.c:1071 rewrite/rewriteHandler.c:3225 +#: rewrite/rewriteHandler.c:4130 #, c-format msgid "cannot update view \"%s\"" msgstr "изменить данные в представлении \"%s\" нельзя" -#: executor/execMain.c:1073 rewrite/rewriteHandler.c:3163 -#: rewrite/rewriteHandler.c:4068 +#: executor/execMain.c:1073 rewrite/rewriteHandler.c:3228 +#: rewrite/rewriteHandler.c:4133 #, c-format msgid "" "To enable updating the view, provide an INSTEAD OF UPDATE trigger or an " @@ -14588,14 +14588,14 @@ msgstr "" "Чтобы представление допускало изменение данных, установите триггер INSTEAD " "OF UPDATE или безусловное правило ON UPDATE DO INSTEAD." -#: executor/execMain.c:1079 rewrite/rewriteHandler.c:3168 -#: rewrite/rewriteHandler.c:4073 +#: executor/execMain.c:1079 rewrite/rewriteHandler.c:3233 +#: rewrite/rewriteHandler.c:4138 #, c-format msgid "cannot delete from view \"%s\"" msgstr "удалить данные из представления \"%s\" нельзя" -#: executor/execMain.c:1081 rewrite/rewriteHandler.c:3171 -#: rewrite/rewriteHandler.c:4076 +#: executor/execMain.c:1081 rewrite/rewriteHandler.c:3236 +#: rewrite/rewriteHandler.c:4141 #, c-format msgid "" "To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an " @@ -15006,7 +15006,7 @@ msgstr "Последний оператор возвращает слишком msgid "return type %s is not supported for SQL functions" msgstr "для SQL-функций тип возврата %s не поддерживается" -#: executor/nodeAgg.c:3922 executor/nodeWindowAgg.c:2990 +#: executor/nodeAgg.c:3922 executor/nodeWindowAgg.c:3038 #, c-format msgid "aggregate %u needs to have compatible input type and transition type" msgstr "" @@ -15122,7 +15122,7 @@ msgstr "Возможно, имеет смысл перенацелить вне #. translator: %s is a SQL command name #: executor/nodeModifyTable.c:2603 executor/nodeModifyTable.c:3033 -#: executor/nodeModifyTable.c:3172 +#: executor/nodeModifyTable.c:3177 #, c-format msgid "%s command cannot affect row a second time" msgstr "команда %s не может подействовать на строку дважды" @@ -15136,7 +15136,7 @@ msgstr "" "Проверьте, не содержат ли строки, которые должна добавить команда, " "дублирующиеся значения, подпадающие под ограничения." -#: executor/nodeModifyTable.c:3026 executor/nodeModifyTable.c:3165 +#: executor/nodeModifyTable.c:3026 executor/nodeModifyTable.c:3170 #, c-format msgid "" "tuple to be updated or deleted was already modified by an operation " @@ -15145,14 +15145,14 @@ msgstr "" "кортеж, который должен быть изменён или удалён, уже модифицирован в " "операции, вызванной текущей командой" -#: executor/nodeModifyTable.c:3035 executor/nodeModifyTable.c:3174 +#: executor/nodeModifyTable.c:3035 executor/nodeModifyTable.c:3179 #, c-format msgid "Ensure that not more than one source row matches any one target row." msgstr "" "Проверьте, не может ли какой-либо целевой строке соответствовать более одной " "исходной строки." -#: executor/nodeModifyTable.c:3124 +#: executor/nodeModifyTable.c:3129 #, c-format msgid "" "tuple to be deleted was already moved to another partition due to concurrent " @@ -15202,32 +15202,32 @@ msgstr "Для столбца \"%s\" задано выражение NULL." msgid "null is not allowed in column \"%s\"" msgstr "в столбце \"%s\" не допускается NULL" -#: executor/nodeWindowAgg.c:356 +#: executor/nodeWindowAgg.c:357 #, c-format msgid "moving-aggregate transition function must not return null" msgstr "функция перехода движимого агрегата не должна возвращать NULL" -#: executor/nodeWindowAgg.c:2081 +#: executor/nodeWindowAgg.c:2129 #, c-format msgid "frame starting offset must not be null" msgstr "смещение начала рамки не может быть NULL" -#: executor/nodeWindowAgg.c:2094 +#: executor/nodeWindowAgg.c:2142 #, c-format msgid "frame starting offset must not be negative" msgstr "смещение начала рамки не может быть отрицательным" -#: executor/nodeWindowAgg.c:2106 +#: executor/nodeWindowAgg.c:2154 #, c-format msgid "frame ending offset must not be null" msgstr "смещение конца рамки не может быть NULL" -#: executor/nodeWindowAgg.c:2119 +#: executor/nodeWindowAgg.c:2167 #, c-format msgid "frame ending offset must not be negative" msgstr "смещение конца рамки не может быть отрицательным" -#: executor/nodeWindowAgg.c:2906 +#: executor/nodeWindowAgg.c:2954 #, c-format msgid "aggregate function %s does not support use as a window function" msgstr "" @@ -17470,7 +17470,7 @@ msgstr "расширенный тип узла \"%s\" уже существуе msgid "ExtensibleNodeMethods \"%s\" was not registered" msgstr "методы расширенного узла \"%s\" не зарегистрированы" -#: nodes/makefuncs.c:150 nodes/makefuncs.c:176 statistics/extended_stats.c:2316 +#: nodes/makefuncs.c:150 nodes/makefuncs.c:176 statistics/extended_stats.c:2326 #, c-format msgid "relation \"%s\" does not have a composite type" msgstr "отношение \"%s\" не имеет составного типа" @@ -20112,68 +20112,68 @@ msgstr "отношение \"%s\" не подходит для предложе msgid "Index \"%s\" contains a whole-row table reference." msgstr "Индекс \"%s\" ссылается на тип всей строки таблицы." -#: parser/parse_utilcmd.c:2296 +#: parser/parse_utilcmd.c:2300 #, c-format msgid "cannot use an existing index in CREATE TABLE" msgstr "в CREATE TABLE нельзя использовать существующий индекс" -#: parser/parse_utilcmd.c:2316 +#: parser/parse_utilcmd.c:2320 #, c-format msgid "index \"%s\" is already associated with a constraint" msgstr "индекс \"%s\" уже связан с ограничением" -#: parser/parse_utilcmd.c:2337 +#: parser/parse_utilcmd.c:2341 #, c-format msgid "\"%s\" is not a unique index" msgstr "\"%s\" не является уникальным индексом" -#: parser/parse_utilcmd.c:2338 parser/parse_utilcmd.c:2345 -#: parser/parse_utilcmd.c:2352 parser/parse_utilcmd.c:2429 +#: parser/parse_utilcmd.c:2342 parser/parse_utilcmd.c:2349 +#: parser/parse_utilcmd.c:2356 parser/parse_utilcmd.c:2433 #, c-format msgid "Cannot create a primary key or unique constraint using such an index." msgstr "" "Создать первичный ключ или ограничение уникальности для такого индекса " "нельзя." -#: parser/parse_utilcmd.c:2344 +#: parser/parse_utilcmd.c:2348 #, c-format msgid "index \"%s\" contains expressions" msgstr "индекс \"%s\" содержит выражения" -#: parser/parse_utilcmd.c:2351 +#: parser/parse_utilcmd.c:2355 #, c-format msgid "\"%s\" is a partial index" msgstr "\"%s\" - частичный индекс" -#: parser/parse_utilcmd.c:2363 +#: parser/parse_utilcmd.c:2367 #, c-format msgid "\"%s\" is a deferrable index" msgstr "\"%s\" - откладываемый индекс" -#: parser/parse_utilcmd.c:2364 +#: parser/parse_utilcmd.c:2368 #, c-format msgid "Cannot create a non-deferrable constraint using a deferrable index." msgstr "" "Создать не откладываемое ограничение на базе откладываемого индекса нельзя." -#: parser/parse_utilcmd.c:2428 +#: parser/parse_utilcmd.c:2432 #, c-format msgid "index \"%s\" column number %d does not have default sorting behavior" msgstr "" "в индексе \"%s\" для столбца номер %d не определено поведение сортировки по " "умолчанию" -#: parser/parse_utilcmd.c:2585 +#: parser/parse_utilcmd.c:2589 #, c-format msgid "column \"%s\" appears twice in primary key constraint" msgstr "столбец \"%s\" фигурирует в первичном ключе дважды" -#: parser/parse_utilcmd.c:2591 +#: parser/parse_utilcmd.c:2595 #, c-format msgid "column \"%s\" appears twice in unique constraint" msgstr "столбец \"%s\" фигурирует в ограничении уникальности дважды" -#: parser/parse_utilcmd.c:2925 +#: parser/parse_utilcmd.c:2929 #, c-format msgid "" "index expressions and predicates can refer only to the table being indexed" @@ -20181,22 +20181,22 @@ msgstr "" "индексные выражения и предикаты могут ссылаться только на индексируемую " "таблицу" -#: parser/parse_utilcmd.c:2997 +#: parser/parse_utilcmd.c:3001 #, c-format msgid "statistics expressions can refer only to the table being referenced" msgstr "выражения статистики могут ссылаться только на целевую таблицу" -#: parser/parse_utilcmd.c:3040 +#: parser/parse_utilcmd.c:3044 #, c-format msgid "rules on materialized views are not supported" msgstr "правила для материализованных представлений не поддерживаются" -#: parser/parse_utilcmd.c:3103 +#: parser/parse_utilcmd.c:3107 #, c-format msgid "rule WHERE condition cannot contain references to other relations" msgstr "в условиях WHERE для правил нельзя ссылаться на другие отношения" -#: parser/parse_utilcmd.c:3176 +#: parser/parse_utilcmd.c:3180 #, c-format msgid "" "rules with WHERE conditions can only have SELECT, INSERT, UPDATE, or DELETE " @@ -20205,158 +20205,158 @@ msgstr "" "правила с условиями WHERE могут содержать только действия SELECT, INSERT, " "UPDATE или DELETE" -#: parser/parse_utilcmd.c:3194 parser/parse_utilcmd.c:3295 -#: rewrite/rewriteHandler.c:539 rewrite/rewriteManip.c:1022 +#: parser/parse_utilcmd.c:3198 parser/parse_utilcmd.c:3299 +#: rewrite/rewriteHandler.c:540 rewrite/rewriteManip.c:1022 #, c-format msgid "conditional UNION/INTERSECT/EXCEPT statements are not implemented" msgstr "условные операторы UNION/INTERSECT/EXCEPT не реализованы" -#: parser/parse_utilcmd.c:3212 +#: parser/parse_utilcmd.c:3216 #, c-format msgid "ON SELECT rule cannot use OLD" msgstr "в правиле ON SELECT нельзя использовать OLD" -#: parser/parse_utilcmd.c:3216 +#: parser/parse_utilcmd.c:3220 #, c-format msgid "ON SELECT rule cannot use NEW" msgstr "в правиле ON SELECT нельзя использовать NEW" -#: parser/parse_utilcmd.c:3225 +#: parser/parse_utilcmd.c:3229 #, c-format msgid "ON INSERT rule cannot use OLD" msgstr "в правиле ON INSERT нельзя использовать OLD" -#: parser/parse_utilcmd.c:3231 +#: parser/parse_utilcmd.c:3235 #, c-format msgid "ON DELETE rule cannot use NEW" msgstr "в правиле ON DELETE нельзя использовать NEW" -#: parser/parse_utilcmd.c:3259 +#: parser/parse_utilcmd.c:3263 #, c-format msgid "cannot refer to OLD within WITH query" msgstr "в запросе WITH нельзя ссылаться на OLD" -#: parser/parse_utilcmd.c:3266 +#: parser/parse_utilcmd.c:3270 #, c-format msgid "cannot refer to NEW within WITH query" msgstr "в запросе WITH нельзя ссылаться на NEW" -#: parser/parse_utilcmd.c:3716 +#: parser/parse_utilcmd.c:3720 #, c-format msgid "misplaced DEFERRABLE clause" msgstr "предложение DEFERRABLE расположено неправильно" -#: parser/parse_utilcmd.c:3721 parser/parse_utilcmd.c:3736 +#: parser/parse_utilcmd.c:3725 parser/parse_utilcmd.c:3740 #, c-format msgid "multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed" msgstr "DEFERRABLE/NOT DEFERRABLE можно указать только один раз" -#: parser/parse_utilcmd.c:3731 +#: parser/parse_utilcmd.c:3735 #, c-format msgid "misplaced NOT DEFERRABLE clause" msgstr "предложение NOT DEFERRABLE расположено неправильно" -#: parser/parse_utilcmd.c:3744 parser/parse_utilcmd.c:3770 gram.y:5944 +#: parser/parse_utilcmd.c:3748 parser/parse_utilcmd.c:3774 gram.y:5944 #, c-format msgid "constraint declared INITIALLY DEFERRED must be DEFERRABLE" msgstr "" "ограничение со свойством INITIALLY DEFERRED должно быть объявлено как " "DEFERRABLE" -#: parser/parse_utilcmd.c:3752 +#: parser/parse_utilcmd.c:3756 #, c-format msgid "misplaced INITIALLY DEFERRED clause" msgstr "предложение INITIALLY DEFERRED расположено неправильно" -#: parser/parse_utilcmd.c:3757 parser/parse_utilcmd.c:3783 +#: parser/parse_utilcmd.c:3761 parser/parse_utilcmd.c:3787 #, c-format msgid "multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed" msgstr "INITIALLY IMMEDIATE/DEFERRED можно указать только один раз" -#: parser/parse_utilcmd.c:3778 +#: parser/parse_utilcmd.c:3782 #, c-format msgid "misplaced INITIALLY IMMEDIATE clause" msgstr "предложение INITIALLY IMMEDIATE расположено неправильно" -#: parser/parse_utilcmd.c:3971 +#: parser/parse_utilcmd.c:3975 #, c-format msgid "" "CREATE specifies a schema (%s) different from the one being created (%s)" msgstr "в CREATE указана схема (%s), отличная от создаваемой (%s)" -#: parser/parse_utilcmd.c:4006 +#: parser/parse_utilcmd.c:4010 #, c-format msgid "\"%s\" is not a partitioned table" msgstr "\"%s\" — не секционированная таблица" -#: parser/parse_utilcmd.c:4013 +#: parser/parse_utilcmd.c:4017 #, c-format msgid "table \"%s\" is not partitioned" msgstr "таблица \"%s\" не является секционированной" -#: parser/parse_utilcmd.c:4020 +#: parser/parse_utilcmd.c:4024 #, c-format msgid "index \"%s\" is not partitioned" msgstr "индекс \"%s\" не секционирован" -#: parser/parse_utilcmd.c:4060 +#: parser/parse_utilcmd.c:4064 #, c-format msgid "a hash-partitioned table may not have a default partition" msgstr "у секционированной по хешу таблицы не может быть секции по умолчанию" -#: parser/parse_utilcmd.c:4077 +#: parser/parse_utilcmd.c:4081 #, c-format msgid "invalid bound specification for a hash partition" msgstr "неправильное указание ограничения для хеш-секции" -#: parser/parse_utilcmd.c:4083 partitioning/partbounds.c:4824 +#: parser/parse_utilcmd.c:4087 partitioning/partbounds.c:4824 #, c-format msgid "modulus for hash partition must be an integer value greater than zero" msgstr "модуль для хеш-секции должен быть положительным целым" -#: parser/parse_utilcmd.c:4090 partitioning/partbounds.c:4832 +#: parser/parse_utilcmd.c:4094 partitioning/partbounds.c:4832 #, c-format msgid "remainder for hash partition must be less than modulus" msgstr "остаток для хеш-секции должен быть меньше модуля" -#: parser/parse_utilcmd.c:4103 +#: parser/parse_utilcmd.c:4107 #, c-format msgid "invalid bound specification for a list partition" msgstr "неправильное указание ограничения для секции по списку" -#: parser/parse_utilcmd.c:4156 +#: parser/parse_utilcmd.c:4160 #, c-format msgid "invalid bound specification for a range partition" msgstr "неправильное указание ограничения для секции по диапазону" -#: parser/parse_utilcmd.c:4162 +#: parser/parse_utilcmd.c:4166 #, c-format msgid "FROM must specify exactly one value per partitioning column" msgstr "" "во FROM должно указываться ровно одно значение для секционирующего столбца" -#: parser/parse_utilcmd.c:4166 +#: parser/parse_utilcmd.c:4170 #, c-format msgid "TO must specify exactly one value per partitioning column" msgstr "" "в TO должно указываться ровно одно значение для секционирующего столбца" -#: parser/parse_utilcmd.c:4282 +#: parser/parse_utilcmd.c:4286 #, c-format msgid "cannot specify NULL in range bound" msgstr "указать NULL в диапазонном ограничении нельзя" -#: parser/parse_utilcmd.c:4330 +#: parser/parse_utilcmd.c:4334 #, c-format msgid "every bound following MAXVALUE must also be MAXVALUE" msgstr "за границей MAXVALUE могут следовать только границы MAXVALUE" -#: parser/parse_utilcmd.c:4337 +#: parser/parse_utilcmd.c:4341 #, c-format msgid "every bound following MINVALUE must also be MINVALUE" msgstr "за границей MINVALUE могут следовать только границы MINVALUE" -#: parser/parse_utilcmd.c:4380 +#: parser/parse_utilcmd.c:4384 #, c-format msgid "specified value cannot be cast to type %s for column \"%s\"" msgstr "указанное значение нельзя привести к типу %s столбца \"%s\"" @@ -20962,22 +20962,22 @@ msgstr "в модулях архивирования должен объявля msgid "archive modules must register an archive callback" msgstr "модули архивирования должны регистрировать обработчик вызова архивации" -#: postmaster/postmaster.c:744 +#: postmaster/postmaster.c:745 #, c-format msgid "%s: invalid argument for option -f: \"%s\"\n" msgstr "%s: неверный аргумент для параметра -f: \"%s\"\n" -#: postmaster/postmaster.c:823 +#: postmaster/postmaster.c:824 #, c-format msgid "%s: invalid argument for option -t: \"%s\"\n" msgstr "%s: неверный аргумент для параметра -t: \"%s\"\n" -#: postmaster/postmaster.c:874 +#: postmaster/postmaster.c:875 #, c-format msgid "%s: invalid argument: \"%s\"\n" msgstr "%s: неверный аргумент: \"%s\"\n" -#: postmaster/postmaster.c:942 +#: postmaster/postmaster.c:943 #, c-format msgid "" "%s: superuser_reserved_connections (%d) must be less than max_connections " @@ -20986,12 +20986,12 @@ msgstr "" "%s: значение superuser_reserved_connections (%d) должно быть меньше " "max_connections (%d)\n" -#: postmaster/postmaster.c:949 +#: postmaster/postmaster.c:950 #, c-format msgid "WAL archival cannot be enabled when wal_level is \"minimal\"" msgstr "Архивацию WAL нельзя включить, если установлен wal_level \"minimal\"" -#: postmaster/postmaster.c:952 +#: postmaster/postmaster.c:953 #, c-format msgid "" "WAL streaming (max_wal_senders > 0) requires wal_level \"replica\" or " @@ -21000,97 +21000,97 @@ msgstr "" "Для потоковой трансляции WAL (max_wal_senders > 0) wal_level должен быть " "\"replica\" или \"logical\"" -#: postmaster/postmaster.c:960 +#: postmaster/postmaster.c:961 #, c-format msgid "%s: invalid datetoken tables, please fix\n" msgstr "%s: ошибка в таблицах маркеров времени, требуется исправление\n" -#: postmaster/postmaster.c:1115 +#: postmaster/postmaster.c:1116 #, c-format msgid "could not create I/O completion port for child queue" msgstr "не удалось создать порт завершения ввода/вывода для очереди потомков" -#: postmaster/postmaster.c:1191 +#: postmaster/postmaster.c:1192 #, c-format msgid "ending log output to stderr" msgstr "завершение вывода в stderr" -#: postmaster/postmaster.c:1192 +#: postmaster/postmaster.c:1193 #, c-format msgid "Future log output will go to log destination \"%s\"." msgstr "В дальнейшем протокол будет выводиться в \"%s\"." -#: postmaster/postmaster.c:1203 +#: postmaster/postmaster.c:1204 #, c-format msgid "starting %s" msgstr "запускается %s" -#: postmaster/postmaster.c:1255 +#: postmaster/postmaster.c:1256 #, c-format msgid "could not create listen socket for \"%s\"" msgstr "не удалось создать принимающий сокет для \"%s\"" -#: postmaster/postmaster.c:1261 +#: postmaster/postmaster.c:1262 #, c-format msgid "could not create any TCP/IP sockets" msgstr "не удалось создать сокеты TCP/IP" -#: postmaster/postmaster.c:1293 +#: postmaster/postmaster.c:1294 #, c-format msgid "DNSServiceRegister() failed: error code %ld" msgstr "функция DNSServiceRegister() выдала ошибку с кодом %ld" -#: postmaster/postmaster.c:1345 +#: postmaster/postmaster.c:1346 #, c-format msgid "could not create Unix-domain socket in directory \"%s\"" msgstr "не удалось создать Unix-сокет в каталоге \"%s\"" -#: postmaster/postmaster.c:1351 +#: postmaster/postmaster.c:1352 #, c-format msgid "could not create any Unix-domain sockets" msgstr "ни один Unix-сокет создать не удалось" -#: postmaster/postmaster.c:1363 +#: postmaster/postmaster.c:1364 #, c-format msgid "no socket created for listening" msgstr "отсутствуют принимающие сокеты" -#: postmaster/postmaster.c:1394 +#: postmaster/postmaster.c:1395 #, c-format msgid "%s: could not change permissions of external PID file \"%s\": %s\n" msgstr "%s: не удалось поменять права для внешнего файла PID \"%s\": %s\n" -#: postmaster/postmaster.c:1398 +#: postmaster/postmaster.c:1399 #, c-format msgid "%s: could not write external PID file \"%s\": %s\n" msgstr "%s: не удалось записать внешний файл PID \"%s\": %s\n" -#: postmaster/postmaster.c:1425 utils/init/postinit.c:220 +#: postmaster/postmaster.c:1426 utils/init/postinit.c:220 #, c-format msgid "could not load pg_hba.conf" msgstr "не удалось загрузить pg_hba.conf" -#: postmaster/postmaster.c:1453 +#: postmaster/postmaster.c:1454 #, c-format msgid "postmaster became multithreaded during startup" msgstr "процесс postmaster стал многопоточным при запуске" -#: postmaster/postmaster.c:1454 postmaster/postmaster.c:5114 +#: postmaster/postmaster.c:1455 postmaster/postmaster.c:5100 #, c-format msgid "Set the LC_ALL environment variable to a valid locale." msgstr "Установите в переменной окружения LC_ALL правильную локаль." -#: postmaster/postmaster.c:1555 +#: postmaster/postmaster.c:1556 #, c-format msgid "%s: could not locate my own executable path" msgstr "%s: не удалось найти путь к собственному исполняемому файлу" -#: postmaster/postmaster.c:1562 +#: postmaster/postmaster.c:1563 #, c-format msgid "%s: could not locate matching postgres executable" msgstr "%s: подходящий исполняемый файл postgres не найден" -#: postmaster/postmaster.c:1585 utils/misc/tzparser.c:340 +#: postmaster/postmaster.c:1586 utils/misc/tzparser.c:340 #, c-format msgid "" "This may indicate an incomplete PostgreSQL installation, or that the file " @@ -21099,7 +21099,7 @@ msgstr "" "Возможно, PostgreSQL установлен не полностью или файла \"%s\" нет в " "положенном месте." -#: postmaster/postmaster.c:1612 +#: postmaster/postmaster.c:1613 #, c-format msgid "" "%s: could not find the database system\n" @@ -21110,45 +21110,45 @@ msgstr "" "Ожидалось найти её в каталоге \"%s\",\n" "но открыть файл \"%s\" не удалось: %s\n" -#: postmaster/postmaster.c:1789 +#: postmaster/postmaster.c:1790 #, c-format msgid "select() failed in postmaster: %m" msgstr "сбой select() в postmaster'е: %m" # well-spelled: неподчиняющимся -#: postmaster/postmaster.c:1920 +#: postmaster/postmaster.c:1921 #, c-format msgid "issuing SIGKILL to recalcitrant children" msgstr "неподчиняющимся потомкам посылается SIGKILL" -#: postmaster/postmaster.c:1941 +#: postmaster/postmaster.c:1942 #, c-format msgid "" "performing immediate shutdown because data directory lock file is invalid" msgstr "" "немедленное отключение из-за ошибочного файла блокировки каталога данных" -#: postmaster/postmaster.c:2044 postmaster/postmaster.c:2072 +#: postmaster/postmaster.c:2045 postmaster/postmaster.c:2073 #, c-format msgid "incomplete startup packet" msgstr "неполный стартовый пакет" -#: postmaster/postmaster.c:2056 postmaster/postmaster.c:2089 +#: postmaster/postmaster.c:2057 postmaster/postmaster.c:2090 #, c-format msgid "invalid length of startup packet" msgstr "неверная длина стартового пакета" -#: postmaster/postmaster.c:2118 +#: postmaster/postmaster.c:2119 #, c-format msgid "failed to send SSL negotiation response: %m" msgstr "не удалось отправить ответ в процессе SSL-согласования: %m" -#: postmaster/postmaster.c:2136 +#: postmaster/postmaster.c:2137 #, c-format msgid "received unencrypted data after SSL request" msgstr "после запроса SSL получены незашифрованные данные" -#: postmaster/postmaster.c:2137 postmaster/postmaster.c:2181 +#: postmaster/postmaster.c:2138 postmaster/postmaster.c:2182 #, c-format msgid "" "This could be either a client-software bug or evidence of an attempted man-" @@ -21157,216 +21157,211 @@ msgstr "" "Это может свидетельствовать об ошибке в клиентском ПО или о попытке атаки " "MITM." -#: postmaster/postmaster.c:2162 +#: postmaster/postmaster.c:2163 #, c-format msgid "failed to send GSSAPI negotiation response: %m" msgstr "не удалось отправить ответ в процессе согласования GSSAPI: %m" -#: postmaster/postmaster.c:2180 +#: postmaster/postmaster.c:2181 #, c-format msgid "received unencrypted data after GSSAPI encryption request" msgstr "после запроса шифрования GSSAPI получены незашифрованные данные" -#: postmaster/postmaster.c:2204 +#: postmaster/postmaster.c:2205 #, c-format msgid "unsupported frontend protocol %u.%u: server supports %u.0 to %u.%u" msgstr "" "неподдерживаемый протокол клиентского приложения %u.%u; сервер поддерживает " "%u.0 - %u.%u" -#: postmaster/postmaster.c:2268 utils/misc/guc.c:7412 utils/misc/guc.c:7448 +#: postmaster/postmaster.c:2269 utils/misc/guc.c:7412 utils/misc/guc.c:7448 #: utils/misc/guc.c:7518 utils/misc/guc.c:9003 utils/misc/guc.c:12045 #: utils/misc/guc.c:12086 #, c-format msgid "invalid value for parameter \"%s\": \"%s\"" msgstr "неверное значение для параметра \"%s\": \"%s\"" -#: postmaster/postmaster.c:2271 +#: postmaster/postmaster.c:2272 #, c-format msgid "Valid values are: \"false\", 0, \"true\", 1, \"database\"." msgstr "Допустимые значения: \"false\", 0, \"true\", 1, \"database\"." -#: postmaster/postmaster.c:2316 +#: postmaster/postmaster.c:2317 #, c-format msgid "invalid startup packet layout: expected terminator as last byte" msgstr "" "неверная структура стартового пакета: последним байтом должен быть терминатор" -#: postmaster/postmaster.c:2333 +#: postmaster/postmaster.c:2334 #, c-format msgid "no PostgreSQL user name specified in startup packet" msgstr "в стартовом пакете не указано имя пользователя PostgreSQL" -#: postmaster/postmaster.c:2397 +#: postmaster/postmaster.c:2398 #, c-format msgid "the database system is starting up" msgstr "система баз данных запускается" -#: postmaster/postmaster.c:2403 +#: postmaster/postmaster.c:2404 #, c-format msgid "the database system is not yet accepting connections" msgstr "система БД ещё не принимает подключения" -#: postmaster/postmaster.c:2404 +#: postmaster/postmaster.c:2405 #, c-format msgid "Consistent recovery state has not been yet reached." msgstr "Согласованное состояние восстановления ещё не достигнуто." -#: postmaster/postmaster.c:2408 +#: postmaster/postmaster.c:2409 #, c-format msgid "the database system is not accepting connections" msgstr "система БД не принимает подключения" -#: postmaster/postmaster.c:2409 +#: postmaster/postmaster.c:2410 #, c-format msgid "Hot standby mode is disabled." msgstr "Режим горячего резерва отключён." -#: postmaster/postmaster.c:2414 +#: postmaster/postmaster.c:2415 #, c-format msgid "the database system is shutting down" msgstr "система баз данных останавливается" -#: postmaster/postmaster.c:2419 +#: postmaster/postmaster.c:2420 #, c-format msgid "the database system is in recovery mode" msgstr "система баз данных в режиме восстановления" -#: postmaster/postmaster.c:2424 storage/ipc/procarray.c:493 +#: postmaster/postmaster.c:2425 storage/ipc/procarray.c:493 #: storage/ipc/sinvaladt.c:306 storage/lmgr/proc.c:359 #, c-format msgid "sorry, too many clients already" msgstr "извините, уже слишком много клиентов" -#: postmaster/postmaster.c:2511 +#: postmaster/postmaster.c:2512 #, c-format msgid "wrong key in cancel request for process %d" msgstr "неправильный ключ в запросе на отмену процесса %d" -#: postmaster/postmaster.c:2523 +#: postmaster/postmaster.c:2524 #, c-format msgid "PID %d in cancel request did not match any process" msgstr "процесс с кодом %d, полученным в запросе на отмену, не найден" -#: postmaster/postmaster.c:2776 +#: postmaster/postmaster.c:2777 #, c-format msgid "received SIGHUP, reloading configuration files" msgstr "получен SIGHUP, файлы конфигурации перезагружаются" #. translator: %s is a configuration file -#: postmaster/postmaster.c:2800 postmaster/postmaster.c:2804 +#: postmaster/postmaster.c:2801 postmaster/postmaster.c:2805 #, c-format msgid "%s was not reloaded" msgstr "%s не был перезагружен" -#: postmaster/postmaster.c:2814 +#: postmaster/postmaster.c:2815 #, c-format msgid "SSL configuration was not reloaded" msgstr "конфигурация SSL не была перезагружена" -#: postmaster/postmaster.c:2870 +#: postmaster/postmaster.c:2871 #, c-format msgid "received smart shutdown request" msgstr "получен запрос на \"вежливое\" выключение" -#: postmaster/postmaster.c:2911 +#: postmaster/postmaster.c:2912 #, c-format msgid "received fast shutdown request" msgstr "получен запрос на быстрое выключение" -#: postmaster/postmaster.c:2929 +#: postmaster/postmaster.c:2930 #, c-format msgid "aborting any active transactions" msgstr "прерывание всех активных транзакций" -#: postmaster/postmaster.c:2953 +#: postmaster/postmaster.c:2954 #, c-format msgid "received immediate shutdown request" msgstr "получен запрос на немедленное выключение" -#: postmaster/postmaster.c:3030 +#: postmaster/postmaster.c:3031 #, c-format msgid "shutdown at recovery target" msgstr "выключение при достижении цели восстановления" -#: postmaster/postmaster.c:3048 postmaster/postmaster.c:3084 +#: postmaster/postmaster.c:3069 msgid "startup process" msgstr "стартовый процесс" -#: postmaster/postmaster.c:3051 -#, c-format -msgid "aborting startup due to startup process failure" -msgstr "прерывание запуска из-за ошибки в стартовом процессе" - -#: postmaster/postmaster.c:3124 +#: postmaster/postmaster.c:3109 #, c-format msgid "database system is ready to accept connections" msgstr "система БД готова принимать подключения" -#: postmaster/postmaster.c:3145 +#: postmaster/postmaster.c:3130 msgid "background writer process" msgstr "процесс фоновой записи" -#: postmaster/postmaster.c:3192 +#: postmaster/postmaster.c:3177 msgid "checkpointer process" msgstr "процесс контрольных точек" -#: postmaster/postmaster.c:3208 +#: postmaster/postmaster.c:3193 msgid "WAL writer process" msgstr "процесс записи WAL" -#: postmaster/postmaster.c:3223 +#: postmaster/postmaster.c:3208 msgid "WAL receiver process" msgstr "процесс считывания WAL" -#: postmaster/postmaster.c:3238 +#: postmaster/postmaster.c:3223 msgid "autovacuum launcher process" msgstr "процесс запуска автоочистки" -#: postmaster/postmaster.c:3256 +#: postmaster/postmaster.c:3241 msgid "archiver process" msgstr "процесс архивации" -#: postmaster/postmaster.c:3269 +#: postmaster/postmaster.c:3254 msgid "system logger process" msgstr "процесс системного протоколирования" -#: postmaster/postmaster.c:3333 +#: postmaster/postmaster.c:3318 #, c-format msgid "background worker \"%s\"" msgstr "фоновый процесс \"%s\"" -#: postmaster/postmaster.c:3412 postmaster/postmaster.c:3432 -#: postmaster/postmaster.c:3439 postmaster/postmaster.c:3457 +#: postmaster/postmaster.c:3397 postmaster/postmaster.c:3417 +#: postmaster/postmaster.c:3424 postmaster/postmaster.c:3442 msgid "server process" msgstr "процесс сервера" -#: postmaster/postmaster.c:3511 +#: postmaster/postmaster.c:3496 #, c-format msgid "terminating any other active server processes" msgstr "завершение всех остальных активных серверных процессов" #. translator: %s is a noun phrase describing a child process, such as #. "server process" -#: postmaster/postmaster.c:3748 +#: postmaster/postmaster.c:3734 #, c-format msgid "%s (PID %d) exited with exit code %d" msgstr "%s (PID %d) завершился с кодом выхода %d" -#: postmaster/postmaster.c:3750 postmaster/postmaster.c:3762 -#: postmaster/postmaster.c:3772 postmaster/postmaster.c:3783 +#: postmaster/postmaster.c:3736 postmaster/postmaster.c:3748 +#: postmaster/postmaster.c:3758 postmaster/postmaster.c:3769 #, c-format msgid "Failed process was running: %s" msgstr "Завершившийся процесс выполнял действие: %s" #. translator: %s is a noun phrase describing a child process, such as #. "server process" -#: postmaster/postmaster.c:3759 +#: postmaster/postmaster.c:3745 #, c-format msgid "%s (PID %d) was terminated by exception 0x%X" msgstr "%s (PID %d) был прерван исключением 0x%X" -#: postmaster/postmaster.c:3761 postmaster/shell_archive.c:134 +#: postmaster/postmaster.c:3747 postmaster/shell_archive.c:134 #, c-format msgid "" "See C include file \"ntstatus.h\" for a description of the hexadecimal value." @@ -21376,235 +21371,235 @@ msgstr "" #. translator: %s is a noun phrase describing a child process, such as #. "server process" -#: postmaster/postmaster.c:3769 +#: postmaster/postmaster.c:3755 #, c-format msgid "%s (PID %d) was terminated by signal %d: %s" msgstr "%s (PID %d) был завершён по сигналу %d: %s" #. translator: %s is a noun phrase describing a child process, such as #. "server process" -#: postmaster/postmaster.c:3781 +#: postmaster/postmaster.c:3767 #, c-format msgid "%s (PID %d) exited with unrecognized status %d" msgstr "%s (PID %d) завершился с нераспознанным кодом состояния %d" -#: postmaster/postmaster.c:3981 +#: postmaster/postmaster.c:3967 #, c-format msgid "abnormal database system shutdown" msgstr "аварийное выключение системы БД" -#: postmaster/postmaster.c:4007 +#: postmaster/postmaster.c:3993 #, c-format msgid "shutting down due to startup process failure" msgstr "сервер останавливается из-за ошибки в стартовом процессе" -#: postmaster/postmaster.c:4013 +#: postmaster/postmaster.c:3999 #, c-format msgid "shutting down because restart_after_crash is off" msgstr "сервер останавливается, так как параметр restart_after_crash равен off" -#: postmaster/postmaster.c:4025 +#: postmaster/postmaster.c:4011 #, c-format msgid "all server processes terminated; reinitializing" msgstr "все серверные процессы завершены... переинициализация" -#: postmaster/postmaster.c:4197 postmaster/postmaster.c:5526 -#: postmaster/postmaster.c:5924 +#: postmaster/postmaster.c:4183 postmaster/postmaster.c:5512 +#: postmaster/postmaster.c:5910 #, c-format msgid "could not generate random cancel key" msgstr "не удалось сгенерировать случайный ключ отмены" -#: postmaster/postmaster.c:4259 +#: postmaster/postmaster.c:4245 #, c-format msgid "could not fork new process for connection: %m" msgstr "породить новый процесс для соединения не удалось: %m" -#: postmaster/postmaster.c:4301 +#: postmaster/postmaster.c:4287 msgid "could not fork new process for connection: " msgstr "породить новый процесс для соединения не удалось: " -#: postmaster/postmaster.c:4407 +#: postmaster/postmaster.c:4393 #, c-format msgid "connection received: host=%s port=%s" msgstr "принято подключение: узел=%s порт=%s" -#: postmaster/postmaster.c:4412 +#: postmaster/postmaster.c:4398 #, c-format msgid "connection received: host=%s" msgstr "принято подключение: узел=%s" -#: postmaster/postmaster.c:4649 +#: postmaster/postmaster.c:4635 #, c-format msgid "could not execute server process \"%s\": %m" msgstr "запустить серверный процесс \"%s\" не удалось: %m" -#: postmaster/postmaster.c:4707 +#: postmaster/postmaster.c:4693 #, c-format msgid "could not create backend parameter file mapping: error code %lu" msgstr "" "создать отображение файла серверных параметров не удалось (код ошибки: %lu)" -#: postmaster/postmaster.c:4716 +#: postmaster/postmaster.c:4702 #, c-format msgid "could not map backend parameter memory: error code %lu" msgstr "" "отобразить файл серверных параметров в память не удалось (код ошибки: %lu)" -#: postmaster/postmaster.c:4743 +#: postmaster/postmaster.c:4729 #, c-format msgid "subprocess command line too long" msgstr "слишком длинная командная строка подпроцесса" -#: postmaster/postmaster.c:4761 +#: postmaster/postmaster.c:4747 #, c-format msgid "CreateProcess() call failed: %m (error code %lu)" msgstr "ошибка в CreateProcess(): %m (код ошибки: %lu)" -#: postmaster/postmaster.c:4788 +#: postmaster/postmaster.c:4774 #, c-format msgid "could not unmap view of backend parameter file: error code %lu" msgstr "" "отключить отображение файла серверных параметров не удалось (код ошибки: %lu)" -#: postmaster/postmaster.c:4792 +#: postmaster/postmaster.c:4778 #, c-format msgid "could not close handle to backend parameter file: error code %lu" msgstr "" "закрыть указатель файла серверных параметров не удалось (код ошибки: %lu)" -#: postmaster/postmaster.c:4814 +#: postmaster/postmaster.c:4800 #, c-format msgid "giving up after too many tries to reserve shared memory" msgstr "" "число повторных попыток резервирования разделяемой памяти достигло предела" -#: postmaster/postmaster.c:4815 +#: postmaster/postmaster.c:4801 #, c-format msgid "This might be caused by ASLR or antivirus software." msgstr "Это может быть вызвано антивирусным ПО или механизмом ASLR." -#: postmaster/postmaster.c:4988 +#: postmaster/postmaster.c:4974 #, c-format msgid "SSL configuration could not be loaded in child process" msgstr "не удалось загрузить конфигурацию SSL в дочерний процесс" -#: postmaster/postmaster.c:5113 +#: postmaster/postmaster.c:5099 #, c-format msgid "postmaster became multithreaded" msgstr "процесс postmaster стал многопоточным" -#: postmaster/postmaster.c:5186 +#: postmaster/postmaster.c:5172 #, c-format msgid "database system is ready to accept read-only connections" msgstr "система БД готова принимать подключения в режиме \"только чтение\"" -#: postmaster/postmaster.c:5450 +#: postmaster/postmaster.c:5436 #, c-format msgid "could not fork startup process: %m" msgstr "породить стартовый процесс не удалось: %m" -#: postmaster/postmaster.c:5454 +#: postmaster/postmaster.c:5440 #, c-format msgid "could not fork archiver process: %m" msgstr "породить процесс архиватора не удалось: %m" -#: postmaster/postmaster.c:5458 +#: postmaster/postmaster.c:5444 #, c-format msgid "could not fork background writer process: %m" msgstr "породить процесс фоновой записи не удалось: %m" -#: postmaster/postmaster.c:5462 +#: postmaster/postmaster.c:5448 #, c-format msgid "could not fork checkpointer process: %m" msgstr "породить процесс контрольных точек не удалось: %m" -#: postmaster/postmaster.c:5466 +#: postmaster/postmaster.c:5452 #, c-format msgid "could not fork WAL writer process: %m" msgstr "породить процесс записи WAL не удалось: %m" -#: postmaster/postmaster.c:5470 +#: postmaster/postmaster.c:5456 #, c-format msgid "could not fork WAL receiver process: %m" msgstr "породить процесс считывания WAL не удалось: %m" -#: postmaster/postmaster.c:5474 +#: postmaster/postmaster.c:5460 #, c-format msgid "could not fork process: %m" msgstr "породить процесс не удалось: %m" -#: postmaster/postmaster.c:5675 postmaster/postmaster.c:5702 +#: postmaster/postmaster.c:5661 postmaster/postmaster.c:5688 #, c-format msgid "database connection requirement not indicated during registration" msgstr "" "при регистрации фонового процесса не указывалось, что ему требуется " "подключение к БД" -#: postmaster/postmaster.c:5686 postmaster/postmaster.c:5713 +#: postmaster/postmaster.c:5672 postmaster/postmaster.c:5699 #, c-format msgid "invalid processing mode in background worker" msgstr "неправильный режим обработки в фоновом процессе" -#: postmaster/postmaster.c:5798 +#: postmaster/postmaster.c:5784 #, c-format msgid "could not fork worker process: %m" msgstr "породить рабочий процесс не удалось: %m" -#: postmaster/postmaster.c:5910 +#: postmaster/postmaster.c:5896 #, c-format msgid "no slot available for new worker process" msgstr "для нового рабочего процесса не нашлось свободного слота" -#: postmaster/postmaster.c:6241 +#: postmaster/postmaster.c:6227 #, c-format msgid "could not duplicate socket %d for use in backend: error code %d" msgstr "" "продублировать сокет %d для серверного процесса не удалось (код ошибки: %d)" -#: postmaster/postmaster.c:6273 +#: postmaster/postmaster.c:6259 #, c-format msgid "could not create inherited socket: error code %d\n" msgstr "создать наследуемый сокет не удалось (код ошибки: %d)\n" -#: postmaster/postmaster.c:6302 +#: postmaster/postmaster.c:6288 #, c-format msgid "could not open backend variables file \"%s\": %s\n" msgstr "открыть файл серверных переменных \"%s\" не удалось: %s\n" -#: postmaster/postmaster.c:6309 +#: postmaster/postmaster.c:6295 #, c-format msgid "could not read from backend variables file \"%s\": %s\n" msgstr "прочитать файл серверных переменных \"%s\" не удалось: %s\n" -#: postmaster/postmaster.c:6318 +#: postmaster/postmaster.c:6304 #, c-format msgid "could not remove file \"%s\": %s\n" msgstr "не удалось стереть файл \"%s\": %s\n" -#: postmaster/postmaster.c:6335 +#: postmaster/postmaster.c:6321 #, c-format msgid "could not map view of backend variables: error code %lu\n" msgstr "отобразить файл серверных переменных не удалось (код ошибки: %lu)\n" -#: postmaster/postmaster.c:6344 +#: postmaster/postmaster.c:6330 #, c-format msgid "could not unmap view of backend variables: error code %lu\n" msgstr "" "отключить отображение файла серверных переменных не удалось (код ошибки: " "%lu)\n" -#: postmaster/postmaster.c:6351 +#: postmaster/postmaster.c:6337 #, c-format msgid "could not close handle to backend parameter variables: error code %lu\n" msgstr "" "закрыть указатель файла серверных переменных не удалось (код ошибки: %lu)\n" -#: postmaster/postmaster.c:6510 +#: postmaster/postmaster.c:6496 #, c-format msgid "could not read exit code for process\n" msgstr "прочитать код завершения процесса не удалось\n" -#: postmaster/postmaster.c:6552 +#: postmaster/postmaster.c:6538 #, c-format msgid "could not post child completion status\n" msgstr "отправить состояние завершения потомка не удалось\n" @@ -22994,26 +22989,26 @@ msgstr "" msgid "terminating walsender process after promotion" msgstr "завершение процесса передачи журнала после повышения" -#: replication/walsender.c:1718 +#: replication/walsender.c:1724 #, c-format msgid "cannot execute new commands while WAL sender is in stopping mode" msgstr "" "нельзя выполнять новые команды, пока процесс передачи WAL находится в режиме " "остановки" -#: replication/walsender.c:1753 +#: replication/walsender.c:1759 #, c-format msgid "cannot execute SQL commands in WAL sender for physical replication" msgstr "" "нельзя выполнять команды SQL в процессе, передающем WAL для физической " "репликации" -#: replication/walsender.c:1786 +#: replication/walsender.c:1792 #, c-format msgid "received replication command: %s" msgstr "получена команда репликации: %s" -#: replication/walsender.c:1794 tcop/fastpath.c:208 tcop/postgres.c:1083 +#: replication/walsender.c:1800 tcop/fastpath.c:208 tcop/postgres.c:1083 #: tcop/postgres.c:1441 tcop/postgres.c:1693 tcop/postgres.c:2174 #: tcop/postgres.c:2607 tcop/postgres.c:2685 #, c-format @@ -23023,22 +23018,22 @@ msgid "" msgstr "" "текущая транзакция прервана, команды до конца блока транзакции игнорируются" -#: replication/walsender.c:1936 replication/walsender.c:1971 +#: replication/walsender.c:1942 replication/walsender.c:1977 #, c-format msgid "unexpected EOF on standby connection" msgstr "неожиданный обрыв соединения с резервным сервером" -#: replication/walsender.c:1959 +#: replication/walsender.c:1965 #, c-format msgid "invalid standby message type \"%c\"" msgstr "неверный тип сообщения резервного сервера: \"%c\"" -#: replication/walsender.c:2048 +#: replication/walsender.c:2054 #, c-format msgid "unexpected message type \"%c\"" msgstr "неожиданный тип сообщения \"%c\"" -#: replication/walsender.c:2465 +#: replication/walsender.c:2474 #, c-format msgid "terminating walsender process due to replication timeout" msgstr "завершение процесса передачи журнала из-за тайм-аута репликации" @@ -23294,7 +23289,7 @@ msgstr "правило \"%s\" для отношения\"%s\" не сущест msgid "renaming an ON SELECT rule is not allowed" msgstr "переименовывать правило ON SELECT нельзя" -#: rewrite/rewriteHandler.c:583 +#: rewrite/rewriteHandler.c:584 #, c-format msgid "" "WITH query name \"%s\" appears in both a rule action and the query being " @@ -23303,7 +23298,7 @@ msgstr "" "имя запроса WITH \"%s\" оказалось и в действии правила, и в переписываемом " "запросе" -#: rewrite/rewriteHandler.c:613 +#: rewrite/rewriteHandler.c:614 #, c-format msgid "" "INSERT...SELECT rule actions are not supported for queries having data-" @@ -23312,118 +23307,118 @@ msgstr "" "правила INSERT...SELECT не поддерживаются для запросов с операторами, " "изменяющими данные, в WITH" -#: rewrite/rewriteHandler.c:666 +#: rewrite/rewriteHandler.c:700 #, c-format msgid "cannot have RETURNING lists in multiple rules" msgstr "RETURNING можно определить только для одного правила" -#: rewrite/rewriteHandler.c:898 rewrite/rewriteHandler.c:937 +#: rewrite/rewriteHandler.c:932 rewrite/rewriteHandler.c:971 #, c-format msgid "cannot insert a non-DEFAULT value into column \"%s\"" msgstr "в столбец \"%s\" можно вставить только значение по умолчанию" -#: rewrite/rewriteHandler.c:900 rewrite/rewriteHandler.c:966 +#: rewrite/rewriteHandler.c:934 rewrite/rewriteHandler.c:1000 #, c-format msgid "Column \"%s\" is an identity column defined as GENERATED ALWAYS." msgstr "" "Столбец \"%s\" является столбцом идентификации со свойством GENERATED ALWAYS." -#: rewrite/rewriteHandler.c:902 +#: rewrite/rewriteHandler.c:936 #, c-format msgid "Use OVERRIDING SYSTEM VALUE to override." msgstr "Для переопределения укажите OVERRIDING SYSTEM VALUE." -#: rewrite/rewriteHandler.c:964 rewrite/rewriteHandler.c:972 +#: rewrite/rewriteHandler.c:998 rewrite/rewriteHandler.c:1006 #, c-format msgid "column \"%s\" can only be updated to DEFAULT" msgstr "столбцу \"%s\" можно присвоить только значение DEFAULT" -#: rewrite/rewriteHandler.c:1107 rewrite/rewriteHandler.c:1125 +#: rewrite/rewriteHandler.c:1141 rewrite/rewriteHandler.c:1159 #, c-format msgid "multiple assignments to same column \"%s\"" msgstr "многочисленные присвоения одному столбцу \"%s\"" -#: rewrite/rewriteHandler.c:1730 rewrite/rewriteHandler.c:3185 +#: rewrite/rewriteHandler.c:1764 rewrite/rewriteHandler.c:3250 #, c-format msgid "access to non-system view \"%s\" is restricted" msgstr "доступ к несистемному представлению \"%s\" ограничен" -#: rewrite/rewriteHandler.c:2162 rewrite/rewriteHandler.c:4131 +#: rewrite/rewriteHandler.c:2196 rewrite/rewriteHandler.c:4196 #, c-format msgid "infinite recursion detected in rules for relation \"%s\"" msgstr "обнаружена бесконечная рекурсия в правилах для отношения \"%s\"" -#: rewrite/rewriteHandler.c:2267 +#: rewrite/rewriteHandler.c:2301 #, c-format msgid "infinite recursion detected in policy for relation \"%s\"" msgstr "обнаружена бесконечная рекурсия в политике для отношения \"%s\"" -#: rewrite/rewriteHandler.c:2597 +#: rewrite/rewriteHandler.c:2662 msgid "Junk view columns are not updatable." msgstr "Утилизируемые столбцы представлений не обновляются." -#: rewrite/rewriteHandler.c:2602 +#: rewrite/rewriteHandler.c:2667 msgid "" "View columns that are not columns of their base relation are not updatable." msgstr "" "Столбцы представлений, не являющиеся столбцами базовых отношений, не " "обновляются." -#: rewrite/rewriteHandler.c:2605 +#: rewrite/rewriteHandler.c:2670 msgid "View columns that refer to system columns are not updatable." msgstr "" "Столбцы представлений, ссылающиеся на системные столбцы, не обновляются." -#: rewrite/rewriteHandler.c:2608 +#: rewrite/rewriteHandler.c:2673 msgid "View columns that return whole-row references are not updatable." msgstr "" "Столбцы представлений, возвращающие ссылки на всю строку, не обновляются." -#: rewrite/rewriteHandler.c:2669 +#: rewrite/rewriteHandler.c:2734 msgid "Views containing DISTINCT are not automatically updatable." msgstr "Представления с DISTINCT не обновляются автоматически." -#: rewrite/rewriteHandler.c:2672 +#: rewrite/rewriteHandler.c:2737 msgid "Views containing GROUP BY are not automatically updatable." msgstr "Представления с GROUP BY не обновляются автоматически." -#: rewrite/rewriteHandler.c:2675 +#: rewrite/rewriteHandler.c:2740 msgid "Views containing HAVING are not automatically updatable." msgstr "Представления с HAVING не обновляются автоматически." -#: rewrite/rewriteHandler.c:2678 +#: rewrite/rewriteHandler.c:2743 msgid "" "Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable." msgstr "" "Представления с UNION, INTERSECT или EXCEPT не обновляются автоматически." -#: rewrite/rewriteHandler.c:2681 +#: rewrite/rewriteHandler.c:2746 msgid "Views containing WITH are not automatically updatable." msgstr "Представления с WITH не обновляются автоматически." -#: rewrite/rewriteHandler.c:2684 +#: rewrite/rewriteHandler.c:2749 msgid "Views containing LIMIT or OFFSET are not automatically updatable." msgstr "Представления с LIMIT или OFFSET не обновляются автоматически." -#: rewrite/rewriteHandler.c:2696 +#: rewrite/rewriteHandler.c:2761 msgid "Views that return aggregate functions are not automatically updatable." msgstr "" "Представления, возвращающие агрегатные функции, не обновляются автоматически." -#: rewrite/rewriteHandler.c:2699 +#: rewrite/rewriteHandler.c:2764 msgid "Views that return window functions are not automatically updatable." msgstr "" "Представления, возвращающие оконные функции, не обновляются автоматически." -#: rewrite/rewriteHandler.c:2702 +#: rewrite/rewriteHandler.c:2767 msgid "" "Views that return set-returning functions are not automatically updatable." msgstr "" "Представления, возвращающие функции с результатом-множеством, не обновляются " "автоматически." -#: rewrite/rewriteHandler.c:2709 rewrite/rewriteHandler.c:2713 -#: rewrite/rewriteHandler.c:2721 +#: rewrite/rewriteHandler.c:2774 rewrite/rewriteHandler.c:2778 +#: rewrite/rewriteHandler.c:2786 msgid "" "Views that do not select from a single table or view are not automatically " "updatable." @@ -23431,27 +23426,27 @@ msgstr "" "Представления, выбирающие данные не из одной таблицы или представления, не " "обновляются автоматически." -#: rewrite/rewriteHandler.c:2724 +#: rewrite/rewriteHandler.c:2789 msgid "Views containing TABLESAMPLE are not automatically updatable." msgstr "Представления, содержащие TABLESAMPLE, не обновляются автоматически." -#: rewrite/rewriteHandler.c:2748 +#: rewrite/rewriteHandler.c:2813 msgid "Views that have no updatable columns are not automatically updatable." msgstr "" "Представления, не содержащие обновляемых столбцов, не обновляются " "автоматически." -#: rewrite/rewriteHandler.c:3245 +#: rewrite/rewriteHandler.c:3310 #, c-format msgid "cannot insert into column \"%s\" of view \"%s\"" msgstr "вставить данные в столбец \"%s\" представления \"%s\" нельзя" -#: rewrite/rewriteHandler.c:3253 +#: rewrite/rewriteHandler.c:3318 #, c-format msgid "cannot update column \"%s\" of view \"%s\"" msgstr "изменить данные в столбце \"%s\" представления \"%s\" нельзя" -#: rewrite/rewriteHandler.c:3757 +#: rewrite/rewriteHandler.c:3822 #, c-format msgid "" "DO INSTEAD NOTIFY rules are not supported for data-modifying statements in " @@ -23460,7 +23455,7 @@ msgstr "" "правила DO INSTEAD NOTIFY не поддерживаются в операторах, изменяющих данные, " "в WITH" -#: rewrite/rewriteHandler.c:3768 +#: rewrite/rewriteHandler.c:3833 #, c-format msgid "" "DO INSTEAD NOTHING rules are not supported for data-modifying statements in " @@ -23469,7 +23464,7 @@ msgstr "" "правила DO INSTEAD NOTHING не поддерживаются в операторах, изменяющих " "данные, в WITH" -#: rewrite/rewriteHandler.c:3782 +#: rewrite/rewriteHandler.c:3847 #, c-format msgid "" "conditional DO INSTEAD rules are not supported for data-modifying statements " @@ -23478,13 +23473,13 @@ msgstr "" "условные правила DO INSTEAD не поддерживаются для операторов, изменяющих " "данные, в WITH" -#: rewrite/rewriteHandler.c:3786 +#: rewrite/rewriteHandler.c:3851 #, c-format msgid "DO ALSO rules are not supported for data-modifying statements in WITH" msgstr "" "правила DO ALSO не поддерживаются для операторов, изменяющих данные, в WITH" -#: rewrite/rewriteHandler.c:3791 +#: rewrite/rewriteHandler.c:3856 #, c-format msgid "" "multi-statement DO INSTEAD rules are not supported for data-modifying " @@ -23493,8 +23488,8 @@ msgstr "" "составные правила DO INSTEAD не поддерживаются для операторов, изменяющих " "данные, в WITH" -#: rewrite/rewriteHandler.c:4059 rewrite/rewriteHandler.c:4067 -#: rewrite/rewriteHandler.c:4075 +#: rewrite/rewriteHandler.c:4124 rewrite/rewriteHandler.c:4132 +#: rewrite/rewriteHandler.c:4140 #, c-format msgid "" "Views with conditional DO INSTEAD rules are not automatically updatable." @@ -23502,43 +23497,43 @@ msgstr "" "Представления в сочетании с правилами DO INSTEAD с условиями не обновляются " "автоматически." -#: rewrite/rewriteHandler.c:4181 +#: rewrite/rewriteHandler.c:4246 #, c-format msgid "cannot perform INSERT RETURNING on relation \"%s\"" msgstr "выполнить INSERT RETURNING для отношения \"%s\" нельзя" -#: rewrite/rewriteHandler.c:4183 +#: rewrite/rewriteHandler.c:4248 #, c-format msgid "" "You need an unconditional ON INSERT DO INSTEAD rule with a RETURNING clause." msgstr "" "Необходимо безусловное правило ON INSERT DO INSTEAD с предложением RETURNING." -#: rewrite/rewriteHandler.c:4188 +#: rewrite/rewriteHandler.c:4253 #, c-format msgid "cannot perform UPDATE RETURNING on relation \"%s\"" msgstr "выполнить UPDATE RETURNING для отношения \"%s\" нельзя" -#: rewrite/rewriteHandler.c:4190 +#: rewrite/rewriteHandler.c:4255 #, c-format msgid "" "You need an unconditional ON UPDATE DO INSTEAD rule with a RETURNING clause." msgstr "" "Необходимо безусловное правило ON UPDATE DO INSTEAD с предложением RETURNING." -#: rewrite/rewriteHandler.c:4195 +#: rewrite/rewriteHandler.c:4260 #, c-format msgid "cannot perform DELETE RETURNING on relation \"%s\"" msgstr "выполнить DELETE RETURNING для отношения \"%s\" нельзя" -#: rewrite/rewriteHandler.c:4197 +#: rewrite/rewriteHandler.c:4262 #, c-format msgid "" "You need an unconditional ON DELETE DO INSTEAD rule with a RETURNING clause." msgstr "" "Необходимо безусловное правило ON DELETE DO INSTEAD с предложением RETURNING." -#: rewrite/rewriteHandler.c:4215 +#: rewrite/rewriteHandler.c:4280 #, c-format msgid "" "INSERT with ON CONFLICT clause cannot be used with table that has INSERT or " @@ -23547,7 +23542,7 @@ msgstr "" "INSERT c предложением ON CONFLICT нельзя использовать с таблицей, для " "которой заданы правила INSERT или UPDATE" -#: rewrite/rewriteHandler.c:4272 +#: rewrite/rewriteHandler.c:4337 #, c-format msgid "" "WITH cannot be used in a query that is rewritten by rules into multiple " @@ -25081,12 +25076,12 @@ msgid "missing Dictionary parameter" msgstr "отсутствует параметр Dictionary" #: tsearch/spell.c:383 tsearch/spell.c:400 tsearch/spell.c:409 -#: tsearch/spell.c:1060 +#: tsearch/spell.c:1072 #, c-format msgid "invalid affix flag \"%s\"" msgstr "неверный флаг аффиксов \"%s\"" -#: tsearch/spell.c:387 tsearch/spell.c:1064 +#: tsearch/spell.c:387 tsearch/spell.c:1076 #, c-format msgid "affix flag \"%s\" is out of range" msgstr "флаг аффикса \"%s\" вне диапазона" @@ -25111,24 +25106,24 @@ msgstr "не удалось открыть файл словаря \"%s\": %m" msgid "invalid regular expression: %s" msgstr "неверное регулярное выражение: %s" -#: tsearch/spell.c:982 tsearch/spell.c:998 tsearch/spell.c:1014 -#: tsearch/spell.c:1030 tsearch/spell.c:1095 gram.y:17826 gram.y:17843 +#: tsearch/spell.c:991 tsearch/spell.c:1008 tsearch/spell.c:1025 +#: tsearch/spell.c:1042 tsearch/spell.c:1107 gram.y:17826 gram.y:17843 #, c-format msgid "syntax error" msgstr "ошибка синтаксиса" -#: tsearch/spell.c:1187 tsearch/spell.c:1199 tsearch/spell.c:1759 -#: tsearch/spell.c:1764 tsearch/spell.c:1769 +#: tsearch/spell.c:1201 tsearch/spell.c:1213 tsearch/spell.c:1773 +#: tsearch/spell.c:1778 tsearch/spell.c:1783 #, c-format msgid "invalid affix alias \"%s\"" msgstr "неверное указание аффикса \"%s\"" -#: tsearch/spell.c:1240 tsearch/spell.c:1311 tsearch/spell.c:1460 +#: tsearch/spell.c:1254 tsearch/spell.c:1325 tsearch/spell.c:1474 #, c-format msgid "could not open affix file \"%s\": %m" msgstr "не удалось открыть файл аффиксов \"%s\": %m" -#: tsearch/spell.c:1294 +#: tsearch/spell.c:1308 #, c-format msgid "" "Ispell dictionary supports only \"default\", \"long\", and \"num\" flag " @@ -25137,17 +25132,17 @@ msgstr "" "словарь Ispell поддерживает для флага только значения \"default\", \"long\" " "и \"num\"" -#: tsearch/spell.c:1338 +#: tsearch/spell.c:1352 #, c-format msgid "invalid number of flag vector aliases" msgstr "неверное количество векторов флагов" -#: tsearch/spell.c:1361 +#: tsearch/spell.c:1375 #, c-format msgid "number of aliases exceeds specified number %d" msgstr "количество псевдонимов превышает заданное число %d" -#: tsearch/spell.c:1575 +#: tsearch/spell.c:1589 #, c-format msgid "affix file contains both old-style and new-style commands" msgstr "файл аффиксов содержит команды и в старом, и в новом стиле" @@ -25565,7 +25560,7 @@ msgstr "" "(%s)" #: utils/adt/arrayfuncs.c:1379 utils/adt/multirangetypes.c:445 -#: utils/adt/rangetypes.c:333 utils/cache/lsyscache.c:2915 +#: utils/adt/rangetypes.c:333 utils/cache/lsyscache.c:2953 #, c-format msgid "no binary input function available for type %s" msgstr "для типа %s нет функции ввода двоичных данных" @@ -25576,7 +25571,7 @@ msgid "improper binary format in array element %d" msgstr "неподходящий двоичный формат в элементе массива %d" #: utils/adt/arrayfuncs.c:1600 utils/adt/multirangetypes.c:450 -#: utils/adt/rangetypes.c:338 utils/cache/lsyscache.c:2948 +#: utils/adt/rangetypes.c:338 utils/cache/lsyscache.c:2986 #, c-format msgid "no binary output function available for type %s" msgstr "для типа %s нет функции вывода двоичных данных" @@ -25826,9 +25821,9 @@ msgstr "TIME(%d)%s: точность должна быть неотрицате msgid "TIME(%d)%s precision reduced to maximum allowed, %d" msgstr "TIME(%d)%s: точность уменьшена до дозволенного максимума: %d" -#: utils/adt/date.c:160 utils/adt/date.c:168 utils/adt/formatting.c:4299 -#: utils/adt/formatting.c:4308 utils/adt/formatting.c:4414 -#: utils/adt/formatting.c:4424 +#: utils/adt/date.c:160 utils/adt/date.c:168 utils/adt/formatting.c:4301 +#: utils/adt/formatting.c:4310 utils/adt/formatting.c:4416 +#: utils/adt/formatting.c:4426 #, c-format msgid "date out of range: \"%s\"" msgstr "дата вне диапазона: \"%s\"" @@ -25882,8 +25877,8 @@ msgstr "единица \"%s\" для типа %s не распознана" #: utils/adt/date.c:1307 utils/adt/date.c:1353 utils/adt/date.c:1907 #: utils/adt/date.c:1938 utils/adt/date.c:1967 utils/adt/date.c:2831 #: utils/adt/date.c:3078 utils/adt/datetime.c:420 utils/adt/datetime.c:1869 -#: utils/adt/formatting.c:4141 utils/adt/formatting.c:4177 -#: utils/adt/formatting.c:4268 utils/adt/formatting.c:4390 utils/adt/json.c:418 +#: utils/adt/formatting.c:4143 utils/adt/formatting.c:4179 +#: utils/adt/formatting.c:4270 utils/adt/formatting.c:4392 utils/adt/json.c:418 #: utils/adt/json.c:457 utils/adt/timestamp.c:225 utils/adt/timestamp.c:257 #: utils/adt/timestamp.c:699 utils/adt/timestamp.c:708 #: utils/adt/timestamp.c:786 utils/adt/timestamp.c:819 @@ -25911,7 +25906,7 @@ msgstr "единица \"%s\" для типа %s не распознана" msgid "timestamp out of range" msgstr "timestamp вне диапазона" -#: utils/adt/date.c:1524 utils/adt/date.c:2326 utils/adt/formatting.c:4476 +#: utils/adt/date.c:1524 utils/adt/date.c:2326 utils/adt/formatting.c:4478 #, c-format msgid "time out of range" msgstr "время вне диапазона" @@ -26191,106 +26186,106 @@ msgstr "неправильная спецификация формата для msgid "Intervals are not tied to specific calendar dates." msgstr "Интервалы не привязываются к определённым календарным датам." -#: utils/adt/formatting.c:1192 +#: utils/adt/formatting.c:1194 #, c-format msgid "\"EEEE\" must be the last pattern used" msgstr "\"EEEE\" может быть только последним шаблоном" -#: utils/adt/formatting.c:1200 +#: utils/adt/formatting.c:1202 #, c-format msgid "\"9\" must be ahead of \"PR\"" msgstr "\"9\" должна стоять до \"PR\"" -#: utils/adt/formatting.c:1216 +#: utils/adt/formatting.c:1218 #, c-format msgid "\"0\" must be ahead of \"PR\"" msgstr "\"0\" должен стоять до \"PR\"" -#: utils/adt/formatting.c:1243 +#: utils/adt/formatting.c:1245 #, c-format msgid "multiple decimal points" msgstr "многочисленные десятичные точки" -#: utils/adt/formatting.c:1247 utils/adt/formatting.c:1330 +#: utils/adt/formatting.c:1249 utils/adt/formatting.c:1332 #, c-format msgid "cannot use \"V\" and decimal point together" msgstr "нельзя использовать \"V\" вместе с десятичной точкой" -#: utils/adt/formatting.c:1259 +#: utils/adt/formatting.c:1261 #, c-format msgid "cannot use \"S\" twice" msgstr "нельзя использовать \"S\" дважды" -#: utils/adt/formatting.c:1263 +#: utils/adt/formatting.c:1265 #, c-format msgid "cannot use \"S\" and \"PL\"/\"MI\"/\"SG\"/\"PR\" together" msgstr "нельзя использовать \"S\" вместе с \"PL\"/\"MI\"/\"SG\"/\"PR\"" -#: utils/adt/formatting.c:1283 +#: utils/adt/formatting.c:1285 #, c-format msgid "cannot use \"S\" and \"MI\" together" msgstr "нельзя использовать \"S\" вместе с \"MI\"" -#: utils/adt/formatting.c:1293 +#: utils/adt/formatting.c:1295 #, c-format msgid "cannot use \"S\" and \"PL\" together" msgstr "нельзя использовать \"S\" вместе с \"PL\"" -#: utils/adt/formatting.c:1303 +#: utils/adt/formatting.c:1305 #, c-format msgid "cannot use \"S\" and \"SG\" together" msgstr "нельзя использовать \"S\" вместе с \"SG\"" -#: utils/adt/formatting.c:1312 +#: utils/adt/formatting.c:1314 #, c-format msgid "cannot use \"PR\" and \"S\"/\"PL\"/\"MI\"/\"SG\" together" msgstr "нельзя использовать \"PR\" вместе с \"S\"/\"PL\"/\"MI\"/\"SG\"" -#: utils/adt/formatting.c:1338 +#: utils/adt/formatting.c:1340 #, c-format msgid "cannot use \"EEEE\" twice" msgstr "нельзя использовать \"EEEE\" дважды" -#: utils/adt/formatting.c:1344 +#: utils/adt/formatting.c:1346 #, c-format msgid "\"EEEE\" is incompatible with other formats" msgstr "\"EEEE\" несовместим с другими форматами" -#: utils/adt/formatting.c:1345 +#: utils/adt/formatting.c:1347 #, c-format msgid "" "\"EEEE\" may only be used together with digit and decimal point patterns." msgstr "" "\"EEEE\" может использоваться только с шаблонами цифр и десятичной точки." -#: utils/adt/formatting.c:1429 +#: utils/adt/formatting.c:1431 #, c-format msgid "invalid datetime format separator: \"%s\"" msgstr "неверный разделитель в формате datetime: \"%s\"" -#: utils/adt/formatting.c:1556 +#: utils/adt/formatting.c:1558 #, c-format msgid "\"%s\" is not a number" msgstr "\"%s\" не является числом" -#: utils/adt/formatting.c:1634 +#: utils/adt/formatting.c:1636 #, c-format msgid "case conversion failed: %s" msgstr "преобразовать регистр не удалось: %s" -#: utils/adt/formatting.c:1688 utils/adt/formatting.c:1810 -#: utils/adt/formatting.c:1933 +#: utils/adt/formatting.c:1690 utils/adt/formatting.c:1812 +#: utils/adt/formatting.c:1935 #, c-format msgid "could not determine which collation to use for %s function" msgstr "" "не удалось определить, какое правило сортировки использовать для функции %s" -#: utils/adt/formatting.c:2314 +#: utils/adt/formatting.c:2316 #, c-format msgid "invalid combination of date conventions" msgstr "неверное сочетание стилей дат" -#: utils/adt/formatting.c:2315 +#: utils/adt/formatting.c:2317 #, c-format msgid "" "Do not mix Gregorian and ISO week date conventions in a formatting template." @@ -26298,27 +26293,27 @@ msgstr "" "Не смешивайте Григорианский стиль дат (недель) с ISO в одном шаблоне " "форматирования." -#: utils/adt/formatting.c:2338 +#: utils/adt/formatting.c:2340 #, c-format msgid "conflicting values for \"%s\" field in formatting string" msgstr "конфликтующие значения поля \"%s\" в строке форматирования" -#: utils/adt/formatting.c:2341 +#: utils/adt/formatting.c:2343 #, c-format msgid "This value contradicts a previous setting for the same field type." msgstr "Это значение противоречит предыдущему значению поля того же типа." -#: utils/adt/formatting.c:2412 +#: utils/adt/formatting.c:2414 #, c-format msgid "source string too short for \"%s\" formatting field" msgstr "входная строка короче, чем требует поле форматирования \"%s\"" -#: utils/adt/formatting.c:2415 +#: utils/adt/formatting.c:2417 #, c-format msgid "Field requires %d characters, but only %d remain." msgstr "Требуется символов: %d, а осталось только %d." -#: utils/adt/formatting.c:2418 utils/adt/formatting.c:2433 +#: utils/adt/formatting.c:2420 utils/adt/formatting.c:2435 #, c-format msgid "" "If your source string is not fixed-width, try using the \"FM\" modifier." @@ -26326,132 +26321,132 @@ msgstr "" "Если входная строка имеет переменную длину, попробуйте использовать " "модификатор \"FM\"." -#: utils/adt/formatting.c:2428 utils/adt/formatting.c:2442 -#: utils/adt/formatting.c:2665 +#: utils/adt/formatting.c:2430 utils/adt/formatting.c:2444 +#: utils/adt/formatting.c:2667 #, c-format msgid "invalid value \"%s\" for \"%s\"" msgstr "неверное значение \"%s\" для \"%s\"" -#: utils/adt/formatting.c:2430 +#: utils/adt/formatting.c:2432 #, c-format msgid "Field requires %d characters, but only %d could be parsed." msgstr "Поле должно поглотить символов: %d, но удалось разобрать только %d." -#: utils/adt/formatting.c:2444 +#: utils/adt/formatting.c:2446 #, c-format msgid "Value must be an integer." msgstr "Значение должно быть целым числом." -#: utils/adt/formatting.c:2449 +#: utils/adt/formatting.c:2451 #, c-format msgid "value for \"%s\" in source string is out of range" msgstr "значение \"%s\" во входной строке вне диапазона" -#: utils/adt/formatting.c:2451 +#: utils/adt/formatting.c:2453 #, c-format msgid "Value must be in the range %d to %d." msgstr "Значение должно быть в интервале %d..%d." -#: utils/adt/formatting.c:2667 +#: utils/adt/formatting.c:2669 #, c-format msgid "The given value did not match any of the allowed values for this field." msgstr "" "Данное значение не соответствует ни одному из допустимых значений для этого " "поля." -#: utils/adt/formatting.c:2886 utils/adt/formatting.c:2906 -#: utils/adt/formatting.c:2926 utils/adt/formatting.c:2946 -#: utils/adt/formatting.c:2965 utils/adt/formatting.c:2984 -#: utils/adt/formatting.c:3008 utils/adt/formatting.c:3026 -#: utils/adt/formatting.c:3044 utils/adt/formatting.c:3062 -#: utils/adt/formatting.c:3079 utils/adt/formatting.c:3096 +#: utils/adt/formatting.c:2888 utils/adt/formatting.c:2908 +#: utils/adt/formatting.c:2928 utils/adt/formatting.c:2948 +#: utils/adt/formatting.c:2967 utils/adt/formatting.c:2986 +#: utils/adt/formatting.c:3010 utils/adt/formatting.c:3028 +#: utils/adt/formatting.c:3046 utils/adt/formatting.c:3064 +#: utils/adt/formatting.c:3081 utils/adt/formatting.c:3098 #, c-format msgid "localized string format value too long" msgstr "слишком длинное значение формата локализованной строки" -#: utils/adt/formatting.c:3373 +#: utils/adt/formatting.c:3375 #, c-format msgid "unmatched format separator \"%c\"" msgstr "нет соответствия для заданного в формате разделителя \"%c\"" -#: utils/adt/formatting.c:3434 +#: utils/adt/formatting.c:3436 #, c-format msgid "unmatched format character \"%s\"" msgstr "нет соответствия для заданного в формате символа \"%s\"" -#: utils/adt/formatting.c:3540 utils/adt/formatting.c:3884 +#: utils/adt/formatting.c:3542 utils/adt/formatting.c:3886 #, c-format msgid "formatting field \"%s\" is only supported in to_char" msgstr "поле форматирования \"%s\" поддерживается только в функции to_char" -#: utils/adt/formatting.c:3715 +#: utils/adt/formatting.c:3717 #, c-format msgid "invalid input string for \"Y,YYY\"" msgstr "ошибка синтаксиса в значении для шаблона \"Y,YYY\"" -#: utils/adt/formatting.c:3801 +#: utils/adt/formatting.c:3803 #, c-format msgid "input string is too short for datetime format" msgstr "входная строка короче, чем требует формат datetime" -#: utils/adt/formatting.c:3809 +#: utils/adt/formatting.c:3811 #, c-format msgid "trailing characters remain in input string after datetime format" msgstr "" "после разбора формата datetime во входной строке остались дополнительные " "символы" -#: utils/adt/formatting.c:4370 +#: utils/adt/formatting.c:4372 #, c-format msgid "missing time zone in input string for type timestamptz" msgstr "во входной строке для типа timestamptz нет указания часового пояса" -#: utils/adt/formatting.c:4376 +#: utils/adt/formatting.c:4378 #, c-format msgid "timestamptz out of range" msgstr "значение timestamptz вне диапазона" -#: utils/adt/formatting.c:4404 +#: utils/adt/formatting.c:4406 #, c-format msgid "datetime format is zoned but not timed" msgstr "в формате datetime указан часовой пояс, но отсутствует время" -#: utils/adt/formatting.c:4456 +#: utils/adt/formatting.c:4458 #, c-format msgid "missing time zone in input string for type timetz" msgstr "во входной строке для типа timetz нет указания часового пояса" -#: utils/adt/formatting.c:4462 +#: utils/adt/formatting.c:4464 #, c-format msgid "timetz out of range" msgstr "значение timetz вне диапазона" -#: utils/adt/formatting.c:4488 +#: utils/adt/formatting.c:4490 #, c-format msgid "datetime format is not dated and not timed" msgstr "в формате datetime нет ни даты, ни времени" -#: utils/adt/formatting.c:4621 +#: utils/adt/formatting.c:4623 #, c-format msgid "hour \"%d\" is invalid for the 12-hour clock" msgstr "час \"%d\" не соответствует 12-часовому формату времени" -#: utils/adt/formatting.c:4623 +#: utils/adt/formatting.c:4625 #, c-format msgid "Use the 24-hour clock, or give an hour between 1 and 12." msgstr "Используйте 24-часовой формат или передавайте часы от 1 до 12." -#: utils/adt/formatting.c:4734 +#: utils/adt/formatting.c:4736 #, c-format msgid "cannot calculate day of year without year information" msgstr "нельзя рассчитать день года без информации о годе" -#: utils/adt/formatting.c:5655 +#: utils/adt/formatting.c:5668 #, c-format msgid "\"EEEE\" not supported for input" msgstr "\"EEEE\" не поддерживается при вводе" -#: utils/adt/formatting.c:5667 +#: utils/adt/formatting.c:5680 #, c-format msgid "\"RN\" not supported for input" msgstr "\"RN\" не поддерживается при вводе" @@ -28744,23 +28739,23 @@ msgstr "путь отбираемого столбца не должен быт msgid "more than one value returned by column XPath expression" msgstr "выражение XPath, отбирающее столбец, возвратило более одного значения" -#: utils/cache/lsyscache.c:1042 +#: utils/cache/lsyscache.c:1080 #, c-format msgid "cast from type %s to type %s does not exist" msgstr "приведение типа %s к типу %s не существует" -#: utils/cache/lsyscache.c:2844 utils/cache/lsyscache.c:2877 -#: utils/cache/lsyscache.c:2910 utils/cache/lsyscache.c:2943 +#: utils/cache/lsyscache.c:2882 utils/cache/lsyscache.c:2915 +#: utils/cache/lsyscache.c:2948 utils/cache/lsyscache.c:2981 #, c-format msgid "type %s is only a shell" msgstr "тип %s является пустышкой" -#: utils/cache/lsyscache.c:2849 +#: utils/cache/lsyscache.c:2887 #, c-format msgid "no input function available for type %s" msgstr "для типа %s нет функции ввода" -#: utils/cache/lsyscache.c:2882 +#: utils/cache/lsyscache.c:2920 #, c-format msgid "no output function available for type %s" msgstr "для типа %s нет функции вывода" @@ -32619,14 +32614,14 @@ msgstr "Данные содержат дублирующиеся ключи." #: utils/sort/tuplestore.c:518 utils/sort/tuplestore.c:528 #: utils/sort/tuplestore.c:869 utils/sort/tuplestore.c:973 #: utils/sort/tuplestore.c:1037 utils/sort/tuplestore.c:1054 -#: utils/sort/tuplestore.c:1256 utils/sort/tuplestore.c:1321 -#: utils/sort/tuplestore.c:1330 +#: utils/sort/tuplestore.c:1268 utils/sort/tuplestore.c:1333 +#: utils/sort/tuplestore.c:1342 #, c-format msgid "could not seek in tuplestore temporary file" msgstr "не удалось переместиться во временном файле хранилища кортежей" -#: utils/sort/tuplestore.c:1477 utils/sort/tuplestore.c:1540 -#: utils/sort/tuplestore.c:1548 +#: utils/sort/tuplestore.c:1491 utils/sort/tuplestore.c:1554 +#: utils/sort/tuplestore.c:1562 #, c-format msgid "" "could not read from tuplestore temporary file: read only %zu of %zu bytes" @@ -33327,6 +33322,10 @@ msgstr "нестандартное использование спецсимво msgid "Use the escape string syntax for escapes, e.g., E'\\r\\n'." msgstr "Используйте для записи спецсимволов синтаксис спецстрок E'\\r\\n'." +#, c-format +#~ msgid "aborting startup due to startup process failure" +#~ msgstr "прерывание запуска из-за ошибки в стартовом процессе" + #, c-format #~ msgid "replication origin \"%s\" already exists" #~ msgstr "источник репликации \"%s\" уже существует" diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index c255c211af8..e49ff801a69 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -318,12 +318,13 @@ static bool FatalError = false; /* T if recovering from backend crash */ * * When the startup process is ready to start archive recovery, it signals the * postmaster, and we switch to PM_RECOVERY state. The background writer and - * checkpointer are launched, while the startup process continues applying WAL. - * If Hot Standby is enabled, then, after reaching a consistent point in WAL - * redo, startup process signals us again, and we switch to PM_HOT_STANDBY - * state and begin accepting connections to perform read-only queries. When - * archive recovery is finished, the startup process exits with exit code 0 - * and we switch to PM_RUN state. + * checkpointer are already running (as these are launched during PM_STARTUP), + * and the startup process continues applying WAL. If Hot Standby is enabled, + * then, after reaching a consistent point in WAL redo, startup process + * signals us again, and we switch to PM_HOT_STANDBY state and begin accepting + * connections to perform read-only queries. When archive recovery is + * finished, the startup process exits with exit code 0 and we switch to + * PM_RUN state. * * Normal child backends can only be launched when we are in PM_RUN or * PM_HOT_STANDBY state. (connsAllowed can also restrict launching.) @@ -2183,6 +2184,7 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done) ProtocolVersion proto; MemoryContext oldcontext; +retry: pq_startmsgread(); /* @@ -2312,7 +2314,16 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done) * another SSL negotiation request, and a GSS request should only * follow if SSL was rejected (client may negotiate in either order) */ - return ProcessStartupPacket(port, true, SSLok == 'S'); + ssl_done = true; + if (SSLok == 'S') + { + /* + * We are done with SSL and negotiated correctly, so consider the + * same for GSS. + */ + gss_done = true; + } + goto retry; } else if (proto == NEGOTIATE_GSS_CODE && !gss_done) { @@ -2356,7 +2367,16 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done) * another GSS negotiation request, and an SSL request should only * follow if GSS was rejected (client may negotiate in either order) */ - return ProcessStartupPacket(port, GSSok == 'G', true); + gss_done = true; + if (GSSok == 'G') + { + /* + * We are done with GSS and negotiated correctly, so consider the + * same for SSL. + */ + ssl_done = true; + } + goto retry; } /* Could add additional special packet types here */ @@ -3210,29 +3230,13 @@ reaper(SIGNAL_ARGS) } /* - * Unexpected exit of startup process (including FATAL exit) - * during PM_STARTUP is treated as catastrophic. There are no - * other processes running yet, so we can just exit. - */ - if (pmState == PM_STARTUP && - StartupStatus != STARTUP_SIGNALED && - !EXIT_STATUS_0(exitstatus)) - { - LogChildExit(LOG, _("startup process"), - pid, exitstatus); - ereport(LOG, - (errmsg("aborting startup due to startup process failure"))); - ExitPostmaster(1); - } - - /* - * After PM_STARTUP, any unexpected exit (including FATAL exit) of - * the startup process is catastrophic, so kill other children, - * and set StartupStatus so we don't try to reinitialize after - * they're gone. Exception: if StartupStatus is STARTUP_SIGNALED, - * then we previously sent the startup process a SIGQUIT; so - * that's probably the reason it died, and we do want to try to - * restart in that case. + * Any unexpected exit (including FATAL exit) of the startup + * process is catastrophic, so kill other children, and set + * StartupStatus so we don't try to reinitialize after they're + * gone. Exception: if StartupStatus is STARTUP_SIGNALED, then we + * previously sent the startup process a SIGQUIT; so that's + * probably the reason it died, and we do want to try to restart + * in that case. * * This stanza also handles the case where we sent a SIGQUIT * during PM_STARTUP due to some dead_end child crashing: in that @@ -3879,7 +3883,8 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) FatalError = true; /* We now transit into a state of waiting for children to die */ - if (pmState == PM_RECOVERY || + if (pmState == PM_STARTUP || + pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_RUN || pmState == PM_STOP_BACKENDS || diff --git a/src/backend/regex/regc_color.c b/src/backend/regex/regc_color.c index 8ae788f5195..1587f452ea3 100644 --- a/src/backend/regex/regc_color.c +++ b/src/backend/regex/regc_color.c @@ -218,6 +218,7 @@ newcolor(struct colormap *cm) n = cm->ncds * 2; if (n > MAX_COLOR + 1) n = MAX_COLOR + 1; + /* the MAX_COLOR+1 limit ensures these alloc sizes can't overflow: */ if (cm->cd == cm->cdspace) { newCd = (struct colordesc *) MALLOC(n * sizeof(struct colordesc)); @@ -434,9 +435,8 @@ newhicolorrow(struct colormap *cm, CERR(REG_ESPACE); return 0; } - newarray = (color *) REALLOC(cm->hicolormap, - cm->maxarrayrows * 2 * - cm->hiarraycols * sizeof(color)); + newarray = REALLOC_ARRAY(cm->hicolormap, color, + cm->maxarrayrows * 2 * cm->hiarraycols); if (newarray == NULL) { CERR(REG_ESPACE); @@ -477,9 +477,8 @@ newhicolorcols(struct colormap *cm) CERR(REG_ESPACE); return; } - newarray = (color *) REALLOC(cm->hicolormap, - cm->maxarrayrows * - cm->hiarraycols * 2 * sizeof(color)); + newarray = REALLOC_ARRAY(cm->hicolormap, color, + cm->maxarrayrows * cm->hiarraycols * 2); if (newarray == NULL) { CERR(REG_ESPACE); @@ -652,8 +651,7 @@ subcoloronechr(struct vars *v, * Potentially, we could need two more colormapranges than we have now, if * the given chr is in the middle of some existing range. */ - newranges = (colormaprange *) - MALLOC((cm->numcmranges + 2) * sizeof(colormaprange)); + newranges = MALLOC_ARRAY(colormaprange, cm->numcmranges + 2); if (newranges == NULL) { CERR(REG_ESPACE); @@ -766,8 +764,7 @@ subcoloronerange(struct vars *v, * Potentially, if we have N non-adjacent ranges, we could need as many as * 2N+1 result ranges (consider case where new range spans 'em all). */ - newranges = (colormaprange *) - MALLOC((cm->numcmranges * 2 + 1) * sizeof(colormaprange)); + newranges = MALLOC_ARRAY(colormaprange, cm->numcmranges * 2 + 1); if (newranges == NULL) { CERR(REG_ESPACE); diff --git a/src/backend/regex/regc_cvec.c b/src/backend/regex/regc_cvec.c index 10306215596..8dbcf3c55e3 100644 --- a/src/backend/regex/regc_cvec.c +++ b/src/backend/regex/regc_cvec.c @@ -40,6 +40,9 @@ /* * newcvec - allocate a new cvec + * + * Note: in current usage, nchrs and nranges are never so large that we risk + * integer overflow in these size calculations, even with 32-bit size_t. */ static struct cvec * newcvec(int nchrs, /* to hold this many chrs... */ diff --git a/src/backend/regex/regc_nfa.c b/src/backend/regex/regc_nfa.c index 4b19740788e..2d37c3fa827 100644 --- a/src/backend/regex/regc_nfa.c +++ b/src/backend/regex/regc_nfa.c @@ -3558,6 +3558,10 @@ compact(struct nfa *nfa, assert(!NISERR()); + /* + * The REG_MAX_COMPILE_SPACE restriction ensures that integer overflow + * can't occur in this loop nor in the allocation requests below. + */ nstates = 0; narcs = 0; for (s = nfa->states; s != NULL; s = s->next) @@ -3610,6 +3614,12 @@ compact(struct nfa *nfa, case LACON: assert(s->no != cnfa->pre); assert(a->co >= 0); + /* make sure the modified color number will fit */ + if (a->co > MAX_COLOR - cnfa->ncolors) + { + NERR(REG_ECOLORS); + return; + } ca->co = (color) (cnfa->ncolors + a->co); ca->to = a->to->no; ca++; diff --git a/src/backend/regex/regcomp.c b/src/backend/regex/regcomp.c index 6fbe15f3781..980a84f4ea7 100644 --- a/src/backend/regex/regcomp.c +++ b/src/backend/regex/regcomp.c @@ -519,6 +519,7 @@ moresubs(struct vars *v, assert(wanted > 0 && (size_t) wanted >= v->nsubs); n = (size_t) wanted * 3 / 2 + 1; + /* n is bounded by the number of states, so no chance of overflow here */ if (v->subs == v->sub10) { p = (struct subre **) MALLOC(n * sizeof(struct subre *)); @@ -2363,8 +2364,8 @@ newlacon(struct vars *v, else { n = v->nlacons; - newlacons = (struct subre *) REALLOC(v->lacons, - (n + 1) * sizeof(struct subre)); + /* better use REALLOC_ARRAY here, as struct subre is big */ + newlacons = REALLOC_ARRAY(v->lacons, struct subre, n + 1); } if (newlacons == NULL) { diff --git a/src/backend/regex/rege_dfa.c b/src/backend/regex/rege_dfa.c index ba1289c64a9..e2b6e520f15 100644 --- a/src/backend/regex/rege_dfa.c +++ b/src/backend/regex/rege_dfa.c @@ -640,20 +640,29 @@ newdfa(struct vars *v, } else { + /* + * Restrict the ranges of nstates and ncolors enough that the arrays + * we allocate here have no more than INT_MAX members. This protects + * not only the allocation calculations just below, but later indexing + * into these arrays. + */ + if (wordsper >= INT_MAX / (nss + WORK) || + cnfa->ncolors >= INT_MAX / nss) + { + ERR(REG_ETOOBIG); + return NULL; + } d = (struct dfa *) MALLOC(sizeof(struct dfa)); if (d == NULL) { ERR(REG_ESPACE); return NULL; } - d->ssets = (struct sset *) MALLOC(nss * sizeof(struct sset)); - d->statesarea = (unsigned *) MALLOC((nss + WORK) * wordsper * - sizeof(unsigned)); + d->ssets = MALLOC_ARRAY(struct sset, nss); + d->statesarea = MALLOC_ARRAY(unsigned, (nss + WORK) * wordsper); d->work = &d->statesarea[nss * wordsper]; - d->outsarea = (struct sset **) MALLOC(nss * cnfa->ncolors * - sizeof(struct sset *)); - d->incarea = (struct arcp *) MALLOC(nss * cnfa->ncolors * - sizeof(struct arcp)); + d->outsarea = MALLOC_ARRAY(struct sset *, nss * cnfa->ncolors); + d->incarea = MALLOC_ARRAY(struct arcp, nss * cnfa->ncolors); d->ismalloced = true; d->arraysmalloced = true; /* now freedfa() will behave sanely */ diff --git a/src/backend/regex/regexec.c b/src/backend/regex/regexec.c index 92715443606..54f6ff05d42 100644 --- a/src/backend/regex/regexec.c +++ b/src/backend/regex/regexec.c @@ -222,7 +222,7 @@ pg_regexec(regex_t *re, if (v->nmatch <= LOCALMAT) v->pmatch = mat; else - v->pmatch = (regmatch_t *) MALLOC(v->nmatch * sizeof(regmatch_t)); + v->pmatch = MALLOC_ARRAY(regmatch_t, v->nmatch); if (v->pmatch == NULL) return REG_ESPACE; zapallsubs(v->pmatch, v->nmatch); @@ -256,6 +256,7 @@ pg_regexec(regex_t *re, v->subdfas = subdfas; else { + /* ntree is surely less than the number of states, so this is safe: */ v->subdfas = (struct dfa **) MALLOC(n * sizeof(struct dfa *)); if (v->subdfas == NULL) { @@ -270,6 +271,7 @@ pg_regexec(regex_t *re, n = (size_t) v->g->nlacons; if (n > 0) { + /* nlacons is surely less than the number of arcs, so this is safe: */ v->ladfas = (struct dfa **) MALLOC(n * sizeof(struct dfa *)); if (v->ladfas == NULL) { @@ -1155,7 +1157,7 @@ citerdissect(struct vars *v, max_matches = t->max; if (max_matches < min_matches) max_matches = min_matches; - endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *)); + endpts = MALLOC_ARRAY(chr *, max_matches + 1); if (endpts == NULL) return REG_ESPACE; endpts[0] = begin; @@ -1362,7 +1364,7 @@ creviterdissect(struct vars *v, max_matches = t->max; if (max_matches < min_matches) max_matches = min_matches; - endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *)); + endpts = MALLOC_ARRAY(chr *, max_matches + 1); if (endpts == NULL) return REG_ESPACE; endpts[0] = begin; diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 6ceca27a29a..ac1c12137f0 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1602,9 +1602,15 @@ WalSndWaitForWal(XLogRecPtr loc) * If we're shutting down, trigger pending WAL to be written out, * otherwise we'd possibly end up waiting for WAL that never gets * written, because walwriter has shut down already. + * + * Note that GetXLogInsertEndRecPtr() is used to obtain the WAL flush + * request location instead of GetXLogInsertRecPtr(). Because if the + * last WAL record ends at a page boundary, GetXLogInsertRecPtr() can + * return an LSN pointing past the page header, which may cause + * XLogFlush() to report an error. */ - if (got_STOPPING) - XLogBackgroundFlush(); + if (got_STOPPING && !RecoveryInProgress()) + XLogFlush(GetXLogInsertEndRecPtr()); /* Update our idea of the currently flushed position. */ if (!RecoveryInProgress()) @@ -2103,7 +2109,9 @@ ProcessStandbyReplyMessage(void) TimestampTz now; TimestampTz replyTime; - static bool fullyAppliedLastTime = false; + static XLogRecPtr prevWritePtr = InvalidXLogRecPtr; + static XLogRecPtr prevFlushPtr = InvalidXLogRecPtr; + static XLogRecPtr prevApplyPtr = InvalidXLogRecPtr; /* the caller already consumed the msgtype byte */ writePtr = pq_getmsgint64(&reply_message); @@ -2136,22 +2144,23 @@ ProcessStandbyReplyMessage(void) applyLag = LagTrackerRead(SYNC_REP_WAIT_APPLY, applyPtr, now); /* - * If the standby reports that it has fully replayed the WAL in two - * consecutive reply messages, then the second such message must result - * from wal_receiver_status_interval expiring on the standby. This is a - * convenient time to forget the lag times measured when it last - * wrote/flushed/applied a WAL record, to avoid displaying stale lag data - * until more WAL traffic arrives. - */ - clearLagTimes = false; - if (applyPtr == sentPtr) - { - if (fullyAppliedLastTime) - clearLagTimes = true; - fullyAppliedLastTime = true; - } - else - fullyAppliedLastTime = false; + * If the standby reports that it has fully replayed the WAL, and the + * write/flush/apply positions remain unchanged across two consecutive + * reply messages, forget the lag times measured when it last + * wrote/flushed/applied a WAL record. + * + * The second message with unchanged positions typically results from + * wal_receiver_status_interval expiring on the standby, so lag values are + * usually cleared after that interval when there is no activity. This + * avoids displaying stale lag data until more WAL traffic arrives. + */ + clearLagTimes = (applyPtr == sentPtr && flushPtr == sentPtr && + writePtr == prevWritePtr && flushPtr == prevFlushPtr && + applyPtr == prevApplyPtr); + + prevWritePtr = writePtr; + prevFlushPtr = flushPtr; + prevApplyPtr = applyPtr; /* Send a reply if the standby requested one. */ if (replyRequested) diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 7b37857d1ba..6a5ff66e16f 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -100,6 +100,7 @@ static List *matchLocks(CmdType event, RuleLock *rulelocks, static Query *fireRIRrules(Query *parsetree, List *activeRIRs); static bool view_has_instead_trigger(Relation view, CmdType event); static Bitmapset *adjust_view_column_set(Bitmapset *cols, List *targetlist); +static List *get_generated_columns(Relation rel, int rt_index); /* @@ -635,12 +636,45 @@ rewriteRuleAction(Query *parsetree, if ((event == CMD_INSERT || event == CMD_UPDATE) && sub_action->commandType != CMD_UTILITY) { + RangeTblEntry *new_rte = rt_fetch(new_varno, sub_action->rtable); + Relation new_rel; + List *gen_cols; + + /* + * The target list does not contain entries for generated columns + * (they are removed by rewriteTargetListIU), so we must build entries + * for them here, so that new.gen_col can be rewritten correctly. + */ + new_rel = relation_open(new_rte->relid, NoLock); + gen_cols = get_generated_columns(new_rel, new_varno); + relation_close(new_rel, NoLock); + + /* + * The generated column expressions refer to new.attribute, so they + * must be rewritten before they can be used as replacements. + */ + gen_cols = (List *) + ReplaceVarsFromTargetList((Node *) gen_cols, + new_varno, + 0, + new_rte, + parsetree->targetList, + (event == CMD_UPDATE) ? + REPLACEVARS_CHANGE_VARNO : + REPLACEVARS_SUBSTITUTE_NULL, + current_varno, + &sub_action->hasSubLinks); + + /* + * Now rewrite new.attribute in sub_action, using both the target list + * and the rewritten generated column expressions. + */ sub_action = (Query *) ReplaceVarsFromTargetList((Node *) sub_action, new_varno, 0, - rt_fetch(new_varno, sub_action->rtable), - parsetree->targetList, + new_rte, + list_concat(gen_cols, parsetree->targetList), (event == CMD_UPDATE) ? REPLACEVARS_CHANGE_VARNO : REPLACEVARS_SUBSTITUTE_NULL, @@ -2374,17 +2408,48 @@ CopyAndAddInvertedQual(Query *parsetree, ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0); /* Fix references to NEW */ if (event == CMD_INSERT || event == CMD_UPDATE) + { + RangeTblEntry *rte = rt_fetch(rt_index, parsetree->rtable); + Relation rel; + List *gen_cols; + + /* + * As in rewriteRuleAction, build entries for generated columns so + * that new.gen_col in the rule qualification can be rewritten + * correctly. + */ + rel = relation_open(rte->relid, NoLock); + gen_cols = get_generated_columns(rel, PRS2_NEW_VARNO); + relation_close(rel, NoLock); + + /* + * The generated column expressions refer to new.attribute, so they + * must be rewritten before they can be used as replacements. + */ + gen_cols = (List *) + ReplaceVarsFromTargetList((Node *) gen_cols, + PRS2_NEW_VARNO, + 0, + rte, + parsetree->targetList, + (event == CMD_UPDATE) ? + REPLACEVARS_CHANGE_VARNO : + REPLACEVARS_SUBSTITUTE_NULL, + rt_index, + &parsetree->hasSubLinks); + new_qual = ReplaceVarsFromTargetList(new_qual, PRS2_NEW_VARNO, 0, - rt_fetch(rt_index, - parsetree->rtable), - parsetree->targetList, + rte, + list_concat(gen_cols, + parsetree->targetList), (event == CMD_UPDATE) ? REPLACEVARS_CHANGE_VARNO : REPLACEVARS_SUBSTITUTE_NULL, rt_index, &parsetree->hasSubLinks); + } /* And attach the fixed qual */ AddInvertedQual(parsetree, new_qual); @@ -4280,6 +4345,65 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length, } +/* + * Get a table's generated columns + * + * Returns a list of TargetEntry, one for each generated column, containing + * the attribute numbers and generation expressions. + */ +static List * +get_generated_columns(Relation rel, int rt_index) +{ + List *gen_cols = NIL; + TupleDesc tupdesc; + + tupdesc = RelationGetDescr(rel); + if (tupdesc->constr && tupdesc->constr->has_generated_stored) + { + for (int i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + + if (attr->attgenerated == ATTRIBUTE_GENERATED_STORED) + { + Node *defexpr; + TargetEntry *te; + Oid attcollid; + + defexpr = build_column_default(rel, i + 1); + if (defexpr == NULL) + elog(ERROR, "no generation expression found for column number %d of table \"%s\"", + i + 1, RelationGetRelationName(rel)); + + /* + * If the column definition has a collation and it is + * different from the collation of the generation expression, + * put a COLLATE clause around the expression. + */ + attcollid = attr->attcollation; + if (attcollid && attcollid != exprCollation(defexpr)) + { + CollateExpr *ce = makeNode(CollateExpr); + + ce->arg = (Expr *) defexpr; + ce->collOid = attcollid; + ce->location = -1; + + defexpr = (Node *) ce; + } + + ChangeVarNodes(defexpr, 1, rt_index, 0); + + te = makeTargetEntry((Expr *) defexpr, i + 1, 0, false); + gen_cols = lappend(gen_cols, te); + } + } + } + + return gen_cols; +} + + /* * QueryRewrite - * Primary entry point to the query rewriter. diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c index 31aab79a8fc..3a3e8ef7fb8 100644 --- a/src/backend/statistics/extended_stats.c +++ b/src/backend/statistics/extended_stats.c @@ -764,6 +764,16 @@ lookup_var_attr_stats(Relation rel, Bitmapset *attrs, List *exprs, stats[i] = examine_attribute(expr); + /* + * If the expression has been found as non-analyzable, give up. We + * will not be able to build extended stats with it. + */ + if (stats[i] == NULL) + { + pfree(stats); + return NULL; + } + /* * XXX We need tuple descriptor later, and we just grab it from * stats[0]->tupDesc (see e.g. statext_mcv_build). But as coded @@ -2429,6 +2439,9 @@ serialize_expr_stats(AnlExprData *exprdata, int nexprs) /* * Loads pg_statistic record from expression statistics for expression * identified by the supplied index. + * + * Returns the pg_statistic record found, or NULL if there is no statistics + * data to use. */ HeapTuple statext_expressions_load(Oid stxoid, bool inh, int idx) @@ -2457,6 +2470,13 @@ statext_expressions_load(Oid stxoid, bool inh, int idx) deconstruct_expanded_array(eah); + if (eah->dnulls && eah->dnulls[idx]) + { + /* No data found for this expression, give up. */ + ReleaseSysCache(htup); + return NULL; + } + td = DatumGetHeapTupleHeader(eah->dvalues[idx]); /* Build a temporary HeapTuple control structure */ diff --git a/src/backend/storage/freespace/freespace.c b/src/backend/storage/freespace/freespace.c index 01adf287958..569a9d791a2 100644 --- a/src/backend/storage/freespace/freespace.c +++ b/src/backend/storage/freespace/freespace.c @@ -231,8 +231,18 @@ XLogRecordPageWithFreeSpace(RelFileNode rnode, BlockNumber heapBlk, if (PageIsNew(page)) PageInit(page, BLCKSZ, 0); + /* + * Changes to FSM are usually marked as changed using MarkBufferDirtyHint; + * however, during recovery, it does nothing if checksums are enabled. It + * is assumed that the page should not be dirtied during recovery while + * modifying hints to prevent torn pages, since no new WAL data can be + * generated at this point to store FPI. This is not relevant to the FSM + * case, as its blocks are zeroed when a checksum mismatch occurs. So, we + * need to use regular MarkBufferDirty here to mark the FSM block as + * modified during recovery, otherwise changes to the FSM may be lost. + */ if (fsm_set_avail(page, slot, new_cat)) - MarkBufferDirtyHint(buf, false); + MarkBufferDirty(buf); UnlockReleaseBuffer(buf); } diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index 10be765fb7b..f35cbaf02b2 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -495,42 +495,6 @@ ShmemInitStruct(const char *name, Size size, bool *foundPtr) } -/* - * Add two Size values, checking for overflow - */ -Size -add_size(Size s1, Size s2) -{ - Size result; - - result = s1 + s2; - /* We are assuming Size is an unsigned type here... */ - if (result < s1 || result < s2) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("requested shared memory size overflows size_t"))); - return result; -} - -/* - * Multiply two Size values, checking for overflow - */ -Size -mul_size(Size s1, Size s2) -{ - Size result; - - if (s1 == 0 || s2 == 0) - return 0; - result = s1 * s2; - /* We are assuming Size is an unsigned type here... */ - if (result / s2 != s1) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("requested shared memory size overflows size_t"))); - return result; -} - /* SQL SRF showing allocated shared memory */ Datum pg_get_shmem_allocations(PG_FUNCTION_ARGS) diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 6fbbf5c1657..93c4324534b 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1695,7 +1695,7 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus) proc->waitLock = NULL; proc->waitProcLock = NULL; proc->waitStatus = waitStatus; - pg_atomic_write_u64(&MyProc->waitStart, 0); + pg_atomic_write_u64(&proc->waitStart, 0); /* And awaken it */ SetLatch(&proc->procLatch); diff --git a/src/backend/tsearch/spell.c b/src/backend/tsearch/spell.c index 047bae6fe6c..31e52df365b 100644 --- a/src/backend/tsearch/spell.c +++ b/src/backend/tsearch/spell.c @@ -928,14 +928,20 @@ parse_ooaffentry(char *str, char *type, char *flag, char *find, * * An .affix file entry has the following format: * > [-,] + * + * Output buffers mask, find, repl must be of length BUFSIZ; + * we truncate the input to fit. */ static bool -parse_affentry(char *str, char *mask, char *find, char *repl) +parse_affentry(const char *str, char *mask, char *find, char *repl) { int state = PAE_WAIT_MASK; char *pmask = mask, *pfind = find, *prepl = repl; + char *emask = mask + BUFSIZ; + char *efind = find + BUFSIZ; + char *erepl = repl + BUFSIZ; *mask = *find = *repl = '\0'; @@ -949,7 +955,8 @@ parse_affentry(char *str, char *mask, char *find, char *repl) return false; else if (!t_isspace_cstr(str)) { - pmask += ts_copychar_with_len(pmask, str, clen); + if (pmask < emask - clen) + pmask += ts_copychar_with_len(pmask, str, clen); state = PAE_INMASK; } } @@ -962,7 +969,8 @@ parse_affentry(char *str, char *mask, char *find, char *repl) } else if (!t_isspace_cstr(str)) { - pmask += ts_copychar_with_len(pmask, str, clen); + if (pmask < emask - clen) + pmask += ts_copychar_with_len(pmask, str, clen); } } else if (state == PAE_WAIT_FIND) @@ -973,7 +981,8 @@ parse_affentry(char *str, char *mask, char *find, char *repl) } else if (t_isalpha_cstr(str) || t_iseq(str, '\'') /* english 's */ ) { - prepl += ts_copychar_with_len(prepl, str, clen); + if (prepl < erepl - clen) + prepl += ts_copychar_with_len(prepl, str, clen); state = PAE_INREPL; } else if (!t_isspace_cstr(str)) @@ -990,7 +999,8 @@ parse_affentry(char *str, char *mask, char *find, char *repl) } else if (t_isalpha_cstr(str)) { - pfind += ts_copychar_with_len(pfind, str, clen); + if (pfind < efind - clen) + pfind += ts_copychar_with_len(pfind, str, clen); } else if (!t_isspace_cstr(str)) ereport(ERROR, @@ -1005,7 +1015,8 @@ parse_affentry(char *str, char *mask, char *find, char *repl) } else if (t_isalpha_cstr(str)) { - prepl += ts_copychar_with_len(prepl, str, clen); + if (prepl < erepl - clen) + prepl += ts_copychar_with_len(prepl, str, clen); state = PAE_INREPL; } else if (!t_isspace_cstr(str)) @@ -1022,7 +1033,8 @@ parse_affentry(char *str, char *mask, char *find, char *repl) } else if (t_isalpha_cstr(str)) { - prepl += ts_copychar_with_len(prepl, str, clen); + if (prepl < erepl - clen) + prepl += ts_copychar_with_len(prepl, str, clen); } else if (!t_isspace_cstr(str)) ereport(ERROR, @@ -1080,7 +1092,7 @@ setCompoundAffixFlagValue(IspellDict *Conf, CompoundAffixFlag *entry, * val: affix parameter. */ static void -addCompoundAffixFlagValue(IspellDict *Conf, char *s, uint32 val) +addCompoundAffixFlagValue(IspellDict *Conf, const char *s, uint32 val) { CompoundAffixFlag *newValue; char sbuf[BUFSIZ]; @@ -1098,9 +1110,11 @@ addCompoundAffixFlagValue(IspellDict *Conf, char *s, uint32 val) sflag = sbuf; while (*s && !t_isspace_cstr(s) && *s != '\n') { - int clen = ts_copychar_cstr(sflag, s); + int clen = pg_mblen_cstr(s); - sflag += clen; + /* Truncate the input to fit in BUFSIZ */ + if (sflag < sbuf + BUFSIZ - clen) + sflag += ts_copychar_with_len(sflag, s, clen); s += clen; } *sflag = '\0'; @@ -2080,9 +2094,32 @@ FindAffixes(AffixNode *node, const char *word, int wrdlen, int *level, int type) return NULL; } +/* + * Checks to see if affix applies to word, transforms word if so. + * The transformation consists of replacing Affix->replen leading or + * trailing bytes with the Affix->find string. + * + * word: input word + * len: length of input word + * Affix: affix to consider + * flagflags: context flags showing whether we are handling a compound word + * newword: output buffer (MUST be of length 2 * MAXNORMLEN) + * baselen: input/output argument + * + * If baselen isn't NULL, then *baselen is used to return the length of + * the non-changed part of the word when applying a suffix, and is used + * to detect whether the input contained only a prefix and suffix when + * later applying a prefix. + * + * Returns newword on success, or NULL if the affix can't be applied. + * On success, the modified word is stored into newword. + */ static char * CheckAffix(const char *word, size_t len, AFFIX *Affix, int flagflags, char *newword, int *baselen) { + size_t keeplen, + findlen; + /* * Check compound allow flags */ @@ -2115,15 +2152,27 @@ CheckAffix(const char *word, size_t len, AFFIX *Affix, int flagflags, char *neww return NULL; } + /* + * Protect against output buffer overrun (len < Affix->replen would be + * caller error, but check anyway) + */ + Assert(len == strlen(word)); + if (len < Affix->replen) + return NULL; + keeplen = len - Affix->replen; /* how much of word we will keep */ + findlen = strlen(Affix->find); + if (keeplen + findlen >= 2 * MAXNORMLEN) + return NULL; + /* * make replace pattern of affix */ if (Affix->type == FF_SUFFIX) { - strcpy(newword, word); - strcpy(newword + len - Affix->replen, Affix->find); + memcpy(newword, word, keeplen); + strcpy(newword + keeplen, Affix->find); if (baselen) /* store length of non-changed part of word */ - *baselen = len - Affix->replen; + *baselen = keeplen; } else { @@ -2131,10 +2180,10 @@ CheckAffix(const char *word, size_t len, AFFIX *Affix, int flagflags, char *neww * if prefix is an all non-changed part's length then all word * contains only prefix and suffix, so out */ - if (baselen && *baselen + strlen(Affix->find) <= Affix->replen) + if (baselen && *baselen + findlen <= Affix->replen) return NULL; - strcpy(newword, Affix->find); - strcat(newword, word + Affix->replen); + memcpy(newword, Affix->find, findlen); + strcpy(newword + findlen, word + Affix->replen); } /* @@ -2328,7 +2377,7 @@ CheckCompoundAffixes(CMPDAffix **ptr, char *word, int len, bool CheckInPlace) } else { - char *affbegin; + const char *affbegin; while ((*ptr)->affix) { diff --git a/src/backend/tsearch/wparser_def.c b/src/backend/tsearch/wparser_def.c index 10bd6506719..9ffa5cf2406 100644 --- a/src/backend/tsearch/wparser_def.c +++ b/src/backend/tsearch/wparser_def.c @@ -2556,6 +2556,9 @@ prsd_headline(PG_FUNCTION_ARGS) bool highlightall = false; int max_cover; ListCell *l; + size_t startsellen; + size_t stopsellen; + size_t fragdelimlen; /* Extract configuration option values */ prs->startsel = NULL; @@ -2641,9 +2644,24 @@ prsd_headline(PG_FUNCTION_ARGS) prs->fragdelim = pstrdup(" ... "); /* Caller will need these lengths, too */ - prs->startsellen = strlen(prs->startsel); - prs->stopsellen = strlen(prs->stopsel); - prs->fragdelimlen = strlen(prs->fragdelim); + startsellen = strlen(prs->startsel); + stopsellen = strlen(prs->stopsel); + fragdelimlen = strlen(prs->fragdelim); + if (startsellen > PG_INT16_MAX) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("value for \"%s\" is too long", "StartSel"))); + if (stopsellen > PG_INT16_MAX) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("value for \"%s\" is too long", "StopSel"))); + if (fragdelimlen > PG_INT16_MAX) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("value for \"%s\" is too long", "FragmentDelimiter"))); + prs->startsellen = startsellen; + prs->stopsellen = stopsellen; + prs->fragdelimlen = fragdelimlen; PG_RETURN_POINTER(prs); } diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c index e7b5633a40b..f4f5f1e85ae 100644 --- a/src/backend/utils/activity/pgstat_database.c +++ b/src/backend/utils/activity/pgstat_database.c @@ -194,7 +194,7 @@ pgstat_report_connect(Oid dboid) pgLastSessionReportTime = MyStartTimestamp; - dbentry = pgstat_prep_database_pending(MyDatabaseId); + dbentry = pgstat_prep_database_pending(dboid); dbentry->n_sessions++; } @@ -209,7 +209,7 @@ pgstat_report_disconnect(Oid dboid) if (!pgstat_should_report_connstat()) return; - dbentry = pgstat_prep_database_pending(MyDatabaseId); + dbentry = pgstat_prep_database_pending(dboid); switch (pgStatSessionEndCause) { @@ -353,7 +353,7 @@ pgstat_reset_database_timestamp(Oid dboid, TimestampTz ts) PgStat_EntryRef *dbref; PgStatShared_Database *dbentry; - dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, MyDatabaseId, InvalidOid, + dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, dboid, InvalidOid, false); dbentry = (PgStatShared_Database *) dbref->shared_stats; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 3493a5374a1..1047b48eb70 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -5543,6 +5543,7 @@ accumArrayResultArr(ArrayBuildStateArr *astate, ndatabytes; char *data; int i; + int newnitems; /* * We disallow accumulating null subarrays. Another plausible definition @@ -5572,6 +5573,14 @@ accumArrayResultArr(ArrayBuildStateArr *astate, nitems = ArrayGetNItems(ndims, dims); ndatabytes = ARR_SIZE(arg) - ARR_DATA_OFFSET(arg); + /* Check that the array doesn't grow too large */ + newnitems = astate->nitems + nitems; + if (newnitems > MaxArraySize) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%zu)", + MaxArraySize))); + if (astate->ndims == 0) { /* First input; check/save the dimensionality info */ @@ -5637,8 +5646,6 @@ accumArrayResultArr(ArrayBuildStateArr *astate, /* Deal with null bitmap if needed */ if (astate->nullbitmap || ARR_HASNULL(arg)) { - int newnitems = astate->nitems + nitems; - if (astate->nullbitmap == NULL) { /* @@ -5662,7 +5669,7 @@ accumArrayResultArr(ArrayBuildStateArr *astate, nitems); } - astate->nitems += nitems; + astate->nitems = newnitems; astate->dims[0] += 1; MemoryContextSwitchTo(oldcontext); diff --git a/src/backend/utils/adt/datum.c b/src/backend/utils/adt/datum.c index f421024c89a..7024dc99875 100644 --- a/src/backend/utils/adt/datum.c +++ b/src/backend/utils/adt/datum.c @@ -259,8 +259,13 @@ datumIsEqual(Datum value1, Datum value2, bool typByVal, int typLen) /*------------------------------------------------------------------------- * datum_image_eq * - * Compares two datums for identical contents, based on byte images. Return - * true if the two datums are equal, false otherwise. + * Compares two datums for identical contents when coerced to a signed integer + * of typLen bytes. Return true if the two datums are equal, false otherwise. + * + * The coercion is required as we're not always careful to use the correct + * PG_RETURN_* macro. If we didn't do this, a Datum that's been formed and + * deformed into a tuple may not have the same signed representation as the + * other datum value. *------------------------------------------------------------------------- */ bool @@ -272,7 +277,21 @@ datum_image_eq(Datum value1, Datum value2, bool typByVal, int typLen) if (typByVal) { - result = (value1 == value2); + switch (typLen) + { + case sizeof(char): + result = (DatumGetChar(value1) == DatumGetChar(value2)); + break; + case sizeof(int16): + result = (DatumGetInt16(value1) == DatumGetInt16(value2)); + break; + case sizeof(int32): + result = (DatumGetInt32(value1) == DatumGetInt32(value2)); + break; + default: + result = (value1 == value2); + break; + } } else if (typLen > 0) { @@ -329,10 +348,11 @@ datum_image_eq(Datum value1, Datum value2, bool typByVal, int typLen) /*------------------------------------------------------------------------- * datum_image_hash * - * Generate a hash value based on the binary representation of 'value'. Most - * use cases will want to use the hash function specific to the Datum's type, - * however, some corner cases require generating a hash value based on the - * actual bits rather than the logical value. + * Generate a hash value based on the binary representation of 'value' when + * represented as a signed integer of typLen bytes. Most use cases will want + * to use the hash function specific to the Datum's type, however, some corner + * cases require generating a hash value based on the actual bits rather than + * the logical value. *------------------------------------------------------------------------- */ uint32 @@ -342,7 +362,23 @@ datum_image_hash(Datum value, bool typByVal, int typLen) uint32 result; if (typByVal) + { + switch (typLen) + { + case sizeof(char): + value = CharGetDatum(DatumGetChar(value)); + break; + case sizeof(int16): + value = Int16GetDatum(DatumGetInt16(value)); + break; + case sizeof(int32): + value = Int32GetDatum(DatumGetInt32(value)); + break; + /* Nothing needs done for 64-bit types */ + } + result = hash_bytes((unsigned char *) &value, sizeof(Datum)); + } else if (typLen > 0) result = hash_bytes((unsigned char *) DatumGetPointer(value), typLen); else if (typLen == -1) diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 06bd8ca67c7..dc713c83f8c 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -1043,8 +1043,9 @@ typedef struct NUMProc char *number, /* string with number */ *number_p, /* pointer to current number position */ *inout, /* in / out buffer */ - *inout_p, /* pointer to current inout position */ - *last_relevant, /* last relevant number after decimal point */ + *inout_p; /* pointer to current inout position */ + + const char *last_relevant, /* last relevant number after decimal point */ *L_negative_sign, /* Locale */ *L_positive_sign, @@ -1109,6 +1110,7 @@ static void NUM_prepare_locale(NUMProc *Np); static char *get_last_relevant_decnum(char *num); static void NUM_numpart_from_char(NUMProc *Np, int id, int input_len); static void NUM_numpart_to_char(NUMProc *Np, int id); +static void NUM_add_locale_symbol(NUMProc *Np, const char *pattern); static char *NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number, int input_len, int to_char_out_pre_spaces, int sign, bool is_to_char, Oid collid); @@ -4068,7 +4070,7 @@ datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval, Oid collid) /* * Allocate workspace for result as C string */ - result = palloc((fmt_len * DCH_MAX_ITEM_SIZ) + 1); + result = palloc(mul_size(fmt_len, DCH_MAX_ITEM_SIZ) + 1); *result = '\0'; if (fmt_len > DCH_CACHE_SIZE) @@ -4079,7 +4081,7 @@ datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval, Oid collid) */ incache = false; - format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode)); + format = palloc_array(FormatNode, fmt_len + 1); parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index, DCH_FLAG, NULL); @@ -4551,7 +4553,7 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, * Allocate new memory if format picture is bigger than static * cache and do not use cache (call parser always) */ - format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode)); + format = palloc_array(FormatNode, fmt_len + 1); parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index, DCH_FLAG | (std ? STD_FLAG : 0), NULL); @@ -4991,7 +4993,7 @@ NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree) * Allocate new memory if format picture is bigger than static cache * and do not use cache (call parser always) */ - format = (FormatNode *) palloc((len + 1) * sizeof(FormatNode)); + format = palloc_array(FormatNode, len + 1); *shouldFree = true; @@ -5454,11 +5456,9 @@ NUM_numpart_to_char(NUMProc *Np, int id) { if (Np->Num->lsign == NUM_LSIGN_PRE) { - if (Np->sign == '-') - strcpy(Np->inout_p, Np->L_negative_sign); - else - strcpy(Np->inout_p, Np->L_positive_sign); - Np->inout_p += strlen(Np->inout_p); + NUM_add_locale_symbol(Np, (Np->sign == '-') ? + Np->L_negative_sign : + Np->L_positive_sign); Np->sign_wrote = true; } } @@ -5523,8 +5523,7 @@ NUM_numpart_to_char(NUMProc *Np, int id) { if (!Np->last_relevant || *Np->last_relevant != '.') { - strcpy(Np->inout_p, Np->decimal); /* Write DEC/D */ - Np->inout_p += strlen(Np->inout_p); + NUM_add_locale_symbol(Np, Np->decimal); /* Write DEC/D */ } /* @@ -5533,8 +5532,7 @@ NUM_numpart_to_char(NUMProc *Np, int id) else if (IS_FILLMODE(Np->Num) && Np->last_relevant && *Np->last_relevant == '.') { - strcpy(Np->inout_p, Np->decimal); /* Write DEC/D */ - Np->inout_p += strlen(Np->inout_p); + NUM_add_locale_symbol(Np, Np->decimal); /* Write DEC/D */ } } else @@ -5592,11 +5590,9 @@ NUM_numpart_to_char(NUMProc *Np, int id) } else if (IS_LSIGN(Np->Num) && Np->Num->lsign == NUM_LSIGN_POST) { - if (Np->sign == '-') - strcpy(Np->inout_p, Np->L_negative_sign); - else - strcpy(Np->inout_p, Np->L_positive_sign); - Np->inout_p += strlen(Np->inout_p); + NUM_add_locale_symbol(Np, (Np->sign == '-') ? + Np->L_negative_sign : + Np->L_positive_sign); } } } @@ -5604,6 +5600,23 @@ NUM_numpart_to_char(NUMProc *Np, int id) ++Np->num_curr; } +/* + * Append locale-specific symbol to Np->inout. + * Note we don't null-terminate the output + */ +static void +NUM_add_locale_symbol(NUMProc *Np, const char *pattern) +{ + size_t pattern_len = strlen(pattern); + + /* Truncate symbol if it's potentially too long */ + if (unlikely(pattern_len > NUM_MAX_ITEM_SIZ)) + pattern_len = pg_mbcliplen(pattern, pattern_len, + NUM_MAX_ITEM_SIZ); + memcpy(Np->inout_p, pattern, pattern_len); + Np->inout_p += pattern_len; +} + /* * Skip over "n" input characters, but only if they aren't numeric data */ @@ -5878,6 +5891,10 @@ NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, pattern_len = strlen(pattern); if (Np->is_to_char) { + /* Truncate symbol if it's potentially too long */ + if (unlikely(pattern_len > NUM_MAX_ITEM_SIZ)) + pattern_len = pg_mbcliplen(pattern, pattern_len, + NUM_MAX_ITEM_SIZ); if (!Np->num_in) { if (IS_FILLMODE(Np->Num)) @@ -5885,19 +5902,21 @@ NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, else { /* just in case there are MB chars */ - pattern_len = pg_mbstrlen(pattern); + pattern_len = pg_mbstrlen_with_len(pattern, + pattern_len); memset(Np->inout_p, ' ', pattern_len); Np->inout_p += pattern_len - 1; } } else { - strcpy(Np->inout_p, pattern); + memcpy(Np->inout_p, pattern, pattern_len); Np->inout_p += pattern_len - 1; } } else { + /* Here we do not truncate the symbol ... */ if (!Np->num_in) { if (IS_FILLMODE(Np->Num)) @@ -5922,11 +5941,18 @@ NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, pattern = Np->L_currency_symbol; if (Np->is_to_char) { - strcpy(Np->inout_p, pattern); - Np->inout_p += strlen(pattern) - 1; + /* Truncate symbol if it's potentially too long */ + pattern_len = strlen(pattern); + if (unlikely(pattern_len > NUM_MAX_ITEM_SIZ)) + pattern_len = pg_mbcliplen(pattern, pattern_len, + NUM_MAX_ITEM_SIZ); + + memcpy(Np->inout_p, pattern, pattern_len); + Np->inout_p += pattern_len - 1; } else { + /* Here we do not truncate the symbol ... */ NUM_eat_non_data_chars(Np, pg_mbstrlen(pattern), input_len); continue; } diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c index b482e3e6117..0b228a1d16f 100644 --- a/src/backend/utils/adt/multirangetypes.c +++ b/src/backend/utils/adt/multirangetypes.c @@ -335,7 +335,7 @@ multirange_recv(PG_FUNCTION_ARGS) Oid mltrngtypoid = PG_GETARG_OID(1); int32 typmod = PG_GETARG_INT32(2); MultirangeIOData *cache; - uint32 range_count; + int32 range_count; RangeType **ranges; MultirangeType *ret; StringInfoData tmpbuf; @@ -343,7 +343,8 @@ multirange_recv(PG_FUNCTION_ARGS) cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive); range_count = pq_getmsgint(buf, 4); - ranges = palloc(range_count * sizeof(RangeType *)); + /* palloc_array will enforce a more-or-less-sane range_count value */ + ranges = palloc_array(RangeType *, range_count); initStringInfo(&tmpbuf); for (int i = 0; i < range_count; i++) @@ -830,7 +831,7 @@ multirange_deserialize(TypeCacheEntry *rangetyp, { int i; - *ranges = palloc(*range_count * sizeof(RangeType *)); + *ranges = palloc_array(RangeType *, *range_count); for (i = 0; i < *range_count; i++) (*ranges)[i] = multirange_get_range(rangetyp, multirange, i); } @@ -994,7 +995,7 @@ multirange_constructor2(PG_FUNCTION_ARGS) deconstruct_array(rangeArray, rngtypid, rangetyp->typlen, rangetyp->typbyval, rangetyp->typalign, &elements, &nulls, &range_count); - ranges = palloc0(range_count * sizeof(RangeType *)); + ranges = palloc_array(RangeType *, range_count); for (i = 0; i < range_count; i++) { if (nulls[i]) diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index 76eea4a1e2d..5b30a386d50 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -1051,7 +1051,7 @@ get_iso_localename(const char *winlocname) wchar_t wc_locale_name[LOCALE_NAME_MAX_LENGTH]; wchar_t buffer[LOCALE_NAME_MAX_LENGTH]; static char iso_lc_messages[LOCALE_NAME_MAX_LENGTH]; - char *period; + const char *period; int len; int ret_val; @@ -1856,7 +1856,7 @@ icu_to_uchar(UChar **buff_uchar, const char *buff, size_t nbytes) ereport(ERROR, (errmsg("%s failed: %s", "ucnv_toUChars", u_errorName(status)))); - *buff_uchar = palloc((len_uchar + 1) * sizeof(**buff_uchar)); + *buff_uchar = palloc_array(UChar, len_uchar + 1); status = U_ZERO_ERROR; len_uchar = ucnv_toUChars(icu_converter, *buff_uchar, len_uchar + 1, diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 829aa0128d9..e7f3bc9b6ee 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -5222,7 +5222,11 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, vardata->statsTuple = statext_expressions_load(info->statOid, rte->inh, pos); - vardata->freefunc = ReleaseDummy; + /* Nothing to release if no data found */ + if (vardata->statsTuple != NULL) + { + vardata->freefunc = ReleaseDummy; + } /* * Test if user has permission to access all rows from the diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 1544d54460e..e365c0e91b9 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -1620,15 +1620,19 @@ Datum timeofday(PG_FUNCTION_ARGS) { struct timeval tp; - char templ[128]; - char buf[128]; pg_time_t tt; + struct pg_tm *tm; + char part1[128]; + char part2[128]; + char buf[128 + 128 + 10]; gettimeofday(&tp, NULL); tt = (pg_time_t) tp.tv_sec; - pg_strftime(templ, sizeof(templ), "%a %b %d %H:%M:%S.%%06d %Y %Z", - pg_localtime(&tt, session_timezone)); - snprintf(buf, sizeof(buf), templ, tp.tv_usec); + tm = pg_localtime(&tt, session_timezone); + + pg_strftime(part1, sizeof(part1), "%a %b %d %H:%M:%S", tm); + pg_strftime(part2, sizeof(part2), "%Y %Z", tm); + snprintf(buf, sizeof(buf), "%s.%06d %s", part1, (int) tp.tv_usec, part2); PG_RETURN_TEXT_P(cstring_to_text(buf)); } diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c index 48b66870296..f995e1f6e40 100644 --- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -6283,18 +6283,18 @@ unicode_normalize_func(PG_FUNCTION_ARGS) text *input = PG_GETARG_TEXT_PP(0); char *formstr = text_to_cstring(PG_GETARG_TEXT_PP(1)); UnicodeNormalizationForm form; - int size; + size_t size; pg_wchar *input_chars; pg_wchar *output_chars; unsigned char *p; text *result; - int i; + size_t i; form = unicode_norm_form_from_string(formstr); /* convert to pg_wchar */ size = pg_mbstrlen_with_len(VARDATA_ANY(input), VARSIZE_ANY_EXHDR(input)); - input_chars = palloc((size + 1) * sizeof(pg_wchar)); + input_chars = palloc_array(pg_wchar, size + 1); p = (unsigned char *) VARDATA_ANY(input); for (i = 0; i < size; i++) { @@ -6349,20 +6349,20 @@ unicode_is_normalized(PG_FUNCTION_ARGS) text *input = PG_GETARG_TEXT_PP(0); char *formstr = text_to_cstring(PG_GETARG_TEXT_PP(1)); UnicodeNormalizationForm form; - int size; + size_t size; pg_wchar *input_chars; pg_wchar *output_chars; unsigned char *p; - int i; + size_t i; UnicodeNormalizationQC quickcheck; - int output_size; + size_t output_size; bool result; form = unicode_norm_form_from_string(formstr); /* convert to pg_wchar */ size = pg_mbstrlen_with_len(VARDATA_ANY(input), VARSIZE_ANY_EXHDR(input)); - input_chars = palloc((size + 1) * sizeof(pg_wchar)); + input_chars = palloc_array(pg_wchar, size + 1); p = (unsigned char *) VARDATA_ANY(input); for (i = 0; i < size; i++) { diff --git a/src/backend/utils/adt/xid8funcs.c b/src/backend/utils/adt/xid8funcs.c index 3af9ebadce6..fa1828d2dcf 100644 --- a/src/backend/utils/adt/xid8funcs.c +++ b/src/backend/utils/adt/xid8funcs.c @@ -224,7 +224,7 @@ is_visible_fxid(FullTransactionId value, const pg_snapshot *snap) #ifdef USE_BSEARCH_IF_NXIP_GREATER else if (snap->nxip > USE_BSEARCH_IF_NXIP_GREATER) { - void *res; + const void *res; res = bsearch(&value, snap->xip, snap->nxip, sizeof(FullTransactionId), cmp_fxid); diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index fcc411b3715..bd2984437bb 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -781,6 +781,44 @@ comparison_ops_are_compatible(Oid opno1, Oid opno2) return result; } +/* + * collations_agree_on_equality + * Return true if the two collations have equivalent notions of equality, + * so that a uniqueness or equality proof established under one side + * carries over to a comparison performed under the other side. + * + * Note: this is equality compatibility only. Do NOT use this to reason + * about ordering. + * + * An InvalidOid on either side denotes the absence of a collation -- that + * side's operation is not collation-sensitive (e.g. a non-collatable column + * type). Absence of a collation cannot conflict with the other side's + * collation, so we treat such pairs as agreeing on equality. This generalizes + * the asymmetric treatment in IndexCollMatchesExprColl(). + * + * Otherwise the collations have equivalent equality if they match, or if both + * are deterministic: by definition a deterministic collation treats two + * strings as equal iff they are byte-wise equal (see CREATE COLLATION), so any + * two deterministic collations share the same equality relation. A mismatch + * involving a nondeterministic collation, however, may mean the two equality + * relations disagree, and the proof is unsound. + */ +bool +collations_agree_on_equality(Oid coll1, Oid coll2) +{ + if (!OidIsValid(coll1) || !OidIsValid(coll2)) + return true; + + if (coll1 == coll2) + return true; + + if (!get_collation_isdeterministic(coll1) || + !get_collation_isdeterministic(coll2)) + return false; + + return true; +} + /* ---------- AMPROC CACHES ---------- */ diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c index c3c69bd3894..a1ca2c8b46c 100644 --- a/src/backend/utils/mmgr/dsa.c +++ b/src/backend/utils/mmgr/dsa.c @@ -2117,6 +2117,8 @@ make_new_segment(dsa_area *area, size_t requested_pages) /* See if that is enough... */ if (requested_pages > usable_pages) { + size_t total_requested_pages PG_USED_FOR_ASSERTS_ONLY; + /* * We'll make an odd-sized segment, working forward from the requested * number of pages. @@ -2127,10 +2129,37 @@ make_new_segment(dsa_area *area, size_t requested_pages) MAXALIGN(sizeof(FreePageManager)) + usable_pages * sizeof(dsa_pointer); + /* + * We must also account for pagemap entries needed to cover the + * metadata pages themselves. The pagemap must track all pages in the + * segment, including the pages occupied by metadata. + * + * This formula uses integer ceiling division to compute the exact + * number of additional entries needed. The divisor (FPM_PAGE_SIZE - + * sizeof(dsa_pointer)) accounts for the fact that each metadata page + * consumes one pagemap entry of sizeof(dsa_pointer) bytes, leaving + * only (FPM_PAGE_SIZE - sizeof(dsa_pointer)) net bytes per metadata + * page. + */ + metadata_bytes += + ((metadata_bytes + (FPM_PAGE_SIZE - sizeof(dsa_pointer)) - 1) / + (FPM_PAGE_SIZE - sizeof(dsa_pointer))) * + sizeof(dsa_pointer); + /* Add padding up to next page boundary. */ if (metadata_bytes % FPM_PAGE_SIZE != 0) metadata_bytes += FPM_PAGE_SIZE - (metadata_bytes % FPM_PAGE_SIZE); total_size = metadata_bytes + usable_pages * FPM_PAGE_SIZE; + total_requested_pages = total_size / FPM_PAGE_SIZE; + + /* + * Verify that we allocated enough pagemap entries for metadata and + * usable pages. This reverse-engineers the new calculation of + * "metadata_bytes" done based on the new "requested_pages" for an + * odd-sized segment. + */ + Assert((metadata_bytes - MAXALIGN(sizeof(dsa_segment_header)) - + MAXALIGN(sizeof(FreePageManager))) / sizeof(dsa_pointer) >= total_requested_pages); /* Is that too large for dsa_pointer's addressing scheme? */ if (total_size > DSA_MAX_SEGMENT_SIZE) diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index 339a52891ce..f2d9cb2cb8b 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -21,6 +21,7 @@ #include "postgres.h" +#include "common/int.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "storage/proc.h" @@ -67,6 +68,8 @@ static void MemoryContextStatsInternal(MemoryContext context, int level, static void MemoryContextStatsPrint(MemoryContext context, void *passthru, const char *stats_string, bool print_to_stderr); +static pg_noinline void add_size_error(Size s1, Size s2) pg_attribute_noreturn(); +static pg_noinline void mul_size_error(Size s1, Size s2) pg_attribute_noreturn(); /* * You should not do memory allocations within a critical section, because @@ -1237,6 +1240,172 @@ repalloc(void *pointer, Size size) return ret; } +/* + * repalloc_extended + * Adjust the size of a previously allocated chunk, + * with HUGE and NO_OOM options. + */ +void * +repalloc_extended(void *pointer, Size size, int flags) +{ + MemoryContext context = GetMemoryChunkContext(pointer); + void *ret; + + if (!((flags & MCXT_ALLOC_HUGE) != 0 ? AllocHugeSizeIsValid(size) : + AllocSizeIsValid(size))) + elog(ERROR, "invalid memory alloc request size %zu", size); + + AssertNotInCriticalSection(context); + + /* isReset must be false already */ + Assert(!context->isReset); + + ret = context->methods->realloc(context, pointer, size); + if (unlikely(ret == NULL)) + { + if ((flags & MCXT_ALLOC_NO_OOM) == 0) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu in memory context \"%s\".", + size, context->name))); + } + return NULL; + } + + VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size); + + return ret; +} + +/* + * Support for safe calculation of memory request sizes + * + * These functions perform the requested calculation, but throw error if the + * result overflows. + * + * An important property of these functions is that if an argument was a + * negative signed int before promotion (implying overflow in calculating it) + * we will detect that as an error. That happens because we reject results + * larger than SIZE_MAX / 2 later on, in the actual allocation step. + */ +Size +add_size(Size s1, Size s2) +{ + Size result; + + if (unlikely(pg_add_size_overflow(s1, s2, &result))) + add_size_error(s1, s2); + return result; +} + +static pg_noinline void +add_size_error(Size s1, Size s2) +{ + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("invalid memory allocation request size %zu + %zu", + s1, s2))); +} + +Size +mul_size(Size s1, Size s2) +{ + Size result; + + if (unlikely(pg_mul_size_overflow(s1, s2, &result))) + mul_size_error(s1, s2); + return result; +} + +static pg_noinline void +mul_size_error(Size s1, Size s2) +{ + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("invalid memory allocation request size %zu * %zu", + s1, s2))); +} + +/* + * palloc_mul + * Equivalent to palloc(mul_size(s1, s2)). + */ +void * +palloc_mul(Size s1, Size s2) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req))) + mul_size_error(s1, s2); + return palloc(req); +} + +/* + * palloc0_mul + * Equivalent to palloc0(mul_size(s1, s2)). + * + * This is comparable to standard calloc's behavior. + */ +void * +palloc0_mul(Size s1, Size s2) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req))) + mul_size_error(s1, s2); + return palloc0(req); +} + +/* + * palloc_mul_extended + * Equivalent to palloc_extended(mul_size(s1, s2), flags). + */ +void * +palloc_mul_extended(Size s1, Size s2, int flags) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req))) + mul_size_error(s1, s2); + return palloc_extended(req, flags); +} + +/* + * repalloc_mul + * Equivalent to repalloc(p, mul_size(s1, s2)). + */ +void * +repalloc_mul(void *p, Size s1, Size s2) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req))) + mul_size_error(s1, s2); + return repalloc(p, req); +} + +/* + * repalloc_mul_extended + * Equivalent to repalloc_extended(p, mul_size(s1, s2), flags). + */ +void * +repalloc_mul_extended(void *p, Size s1, Size s2, int flags) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req))) + mul_size_error(s1, s2); + return repalloc_extended(p, req, flags); +} + /* * MemoryContextAllocHuge * Allocate (possibly-expansive) space within the specified context. @@ -1280,31 +1449,8 @@ MemoryContextAllocHuge(MemoryContext context, Size size) void * repalloc_huge(void *pointer, Size size) { - MemoryContext context = GetMemoryChunkContext(pointer); - void *ret; - - if (!AllocHugeSizeIsValid(size)) - elog(ERROR, "invalid memory alloc request size %zu", size); - - AssertNotInCriticalSection(context); - - /* isReset must be false already */ - Assert(!context->isReset); - - ret = context->methods->realloc(context, pointer, size); - if (unlikely(ret == NULL)) - { - MemoryContextStats(TopMemoryContext); - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("out of memory"), - errdetail("Failed on request of size %zu in memory context \"%s\".", - size, context->name))); - } - - VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size); - - return ret; + /* this one seems not worth its own implementation */ + return repalloc_extended(pointer, size, MCXT_ALLOC_HUGE); } /* diff --git a/src/backend/utils/sort/tuplestore.c b/src/backend/utils/sort/tuplestore.c index f605ece721e..4399f4d5add 100644 --- a/src/backend/utils/sort/tuplestore.c +++ b/src/backend/utils/sort/tuplestore.c @@ -673,10 +673,10 @@ grow_memtuples(Tuplestorestate *state) /* OK, do it */ FREEMEM(state, GetMemoryChunkSpace(state->memtuples)); - state->memtupsize = newmemtupsize; state->memtuples = (void **) repalloc_huge(state->memtuples, - state->memtupsize * sizeof(void *)); + newmemtupsize * sizeof(void *)); + state->memtupsize = newmemtupsize; USEMEM(state, GetMemoryChunkSpace(state->memtuples)); if (LACKMEM(state)) elog(ERROR, "unexpected out-of-memory situation in tuplestore"); @@ -1221,7 +1221,19 @@ dumptuples(Tuplestorestate *state) if (i >= state->memtupcount) break; WRITETUP(state, state->memtuples[i]); + + /* + * Increase memtupdeleted to track the fact that we just deleted that + * tuple. Think not to remove this on the grounds that we'll reset + * memtupdeleted to zero below. We might not reach that if some later + * WRITETUP fails (e.g. due to overrunning temp_file_limit). If so, + * we'd error out leaving an effectively-corrupt tuplestore, which + * would be quite bad if it's a persistent data structure such as a + * Portal's holdStore. + */ + state->memtupdeleted++; } + /* Now we can reset memtupdeleted along with memtupcount */ state->memtupdeleted = 0; state->memtupcount = 0; } @@ -1408,8 +1420,10 @@ tuplestore_trim(Tuplestorestate *state) FREEMEM(state, GetMemoryChunkSpace(state->memtuples[i])); pfree(state->memtuples[i]); state->memtuples[i] = NULL; + /* As in dumptuples(), increment memtupdeleted synchronously */ + state->memtupdeleted++; } - state->memtupdeleted = nremove; + Assert(state->memtupdeleted == nremove); /* mark tuplestore as truncated (used for Assert crosschecks only) */ state->truncated = true; diff --git a/src/bin/pg_basebackup/bbstreamer_file.c b/src/bin/pg_basebackup/bbstreamer_file.c index 1a94fb2796c..54417837ff2 100644 --- a/src/bin/pg_basebackup/bbstreamer_file.c +++ b/src/bin/pg_basebackup/bbstreamer_file.c @@ -215,6 +215,10 @@ bbstreamer_extractor_content(bbstreamer *streamer, bbstreamer_member *member, case BBSTREAMER_MEMBER_HEADER: Assert(mystreamer->file == NULL); + if (!path_is_safe_for_extraction(member->pathname)) + pg_fatal("tar member has unsafe path name: \"%s\"", + member->pathname); + /* Prepend basepath. */ snprintf(mystreamer->filename, sizeof(mystreamer->filename), "%s/%s", mystreamer->basepath, member->pathname); @@ -233,6 +237,14 @@ bbstreamer_extractor_content(bbstreamer *streamer, bbstreamer_member *member, if (mystreamer->link_map) linktarget = mystreamer->link_map(linktarget); + + if (!is_absolute_path(linktarget) && + !path_is_safe_for_extraction(member->linktarget)) + { + pg_fatal("link target has unsafe path name: \"%s\"", + member->linktarget); + } + extract_link(mystreamer->filename, linktarget); } else @@ -263,7 +275,9 @@ bbstreamer_extractor_content(bbstreamer *streamer, bbstreamer_member *member, case BBSTREAMER_MEMBER_TRAILER: if (mystreamer->file == NULL) break; - fclose(mystreamer->file); + if (fclose(mystreamer->file) != 0) + pg_fatal("could not close file \"%s\": %m", + mystreamer->filename); mystreamer->file = NULL; break; diff --git a/src/bin/pg_basebackup/bbstreamer_gzip.c b/src/bin/pg_basebackup/bbstreamer_gzip.c index c3455ffbddf..dfe901a3860 100644 --- a/src/bin/pg_basebackup/bbstreamer_gzip.c +++ b/src/bin/pg_basebackup/bbstreamer_gzip.c @@ -292,8 +292,9 @@ bbstreamer_gzip_decompressor_content(bbstreamer *streamer, */ res = inflate(zs, Z_NO_FLUSH); - if (res == Z_STREAM_ERROR) - pg_log_error("could not decompress data: %s", zs->msg); + if (res != Z_OK && res != Z_STREAM_END && res != Z_BUF_ERROR) + pg_fatal("could not decompress data: %s", + zs->msg ? zs->msg : "unknown error"); mystreamer->bytes_written = mystreamer->base.bbs_buffer.maxlen - zs->avail_out; @@ -323,10 +324,11 @@ bbstreamer_gzip_decompressor_finalize(bbstreamer *streamer) * End of the stream, if there is some pending data in output buffers then * we must forward it to next streamer. */ - bbstreamer_content(mystreamer->base.bbs_next, NULL, - mystreamer->base.bbs_buffer.data, - mystreamer->base.bbs_buffer.maxlen, - BBSTREAMER_UNKNOWN); + if (mystreamer->bytes_written > 0) + bbstreamer_content(mystreamer->base.bbs_next, NULL, + mystreamer->base.bbs_buffer.data, + mystreamer->bytes_written, + BBSTREAMER_UNKNOWN); bbstreamer_finalize(mystreamer->base.bbs_next); } @@ -337,7 +339,12 @@ bbstreamer_gzip_decompressor_finalize(bbstreamer *streamer) static void bbstreamer_gzip_decompressor_free(bbstreamer *streamer) { + bbstreamer_gzip_decompressor *mystreamer; + + mystreamer = (bbstreamer_gzip_decompressor *) streamer; + bbstreamer_free(streamer->bbs_next); + inflateEnd(&mystreamer->zstream); pfree(streamer->bbs_buffer.data); pfree(streamer); } diff --git a/src/bin/pg_basebackup/bbstreamer_lz4.c b/src/bin/pg_basebackup/bbstreamer_lz4.c index fb287f5b771..a914bfa5c1e 100644 --- a/src/bin/pg_basebackup/bbstreamer_lz4.c +++ b/src/bin/pg_basebackup/bbstreamer_lz4.c @@ -92,8 +92,8 @@ bbstreamer_lz4_compressor_new(bbstreamer *next, pg_compress_specification *compr ctxError = LZ4F_createCompressionContext(&streamer->cctx, LZ4F_VERSION); if (LZ4F_isError(ctxError)) - pg_log_error("could not create lz4 compression context: %s", - LZ4F_getErrorName(ctxError)); + pg_fatal("could not create lz4 compression context: %s", + LZ4F_getErrorName(ctxError)); return &streamer->base; #else @@ -137,8 +137,8 @@ bbstreamer_lz4_compressor_content(bbstreamer *streamer, &mystreamer->prefs); if (LZ4F_isError(compressed_size)) - pg_log_error("could not write lz4 header: %s", - LZ4F_getErrorName(compressed_size)); + pg_fatal("could not write lz4 header: %s", + LZ4F_getErrorName(compressed_size)); mystreamer->bytes_written += compressed_size; mystreamer->header_written = true; @@ -186,8 +186,8 @@ bbstreamer_lz4_compressor_content(bbstreamer *streamer, next_in, len, NULL); if (LZ4F_isError(compressed_size)) - pg_log_error("could not compress data: %s", - LZ4F_getErrorName(compressed_size)); + pg_fatal("could not compress data: %s", + LZ4F_getErrorName(compressed_size)); mystreamer->bytes_written += compressed_size; } @@ -238,8 +238,8 @@ bbstreamer_lz4_compressor_finalize(bbstreamer *streamer) next_out, avail_out, NULL); if (LZ4F_isError(compressed_size)) - pg_log_error("could not end lz4 compression: %s", - LZ4F_getErrorName(compressed_size)); + pg_fatal("could not end lz4 compression: %s", + LZ4F_getErrorName(compressed_size)); mystreamer->bytes_written += compressed_size; @@ -351,18 +351,21 @@ bbstreamer_lz4_decompressor_content(bbstreamer *streamer, next_in, &read_size, NULL); if (LZ4F_isError(ret)) - pg_log_error("could not decompress data: %s", - LZ4F_getErrorName(ret)); + pg_fatal("could not decompress data: %s", + LZ4F_getErrorName(ret)); /* Update input buffer based on number of bytes consumed */ avail_in -= read_size; next_in += read_size; + /* Update output buffer based on number of bytes produced */ + avail_out -= out_size; + next_out += out_size; mystreamer->bytes_written += out_size; /* * If output buffer is full then forward the content to next streamer - * and update the output buffer. + * and reset the output buffer. */ if (mystreamer->bytes_written >= mystreamer->base.bbs_buffer.maxlen) { @@ -372,13 +375,8 @@ bbstreamer_lz4_decompressor_content(bbstreamer *streamer, context); avail_out = mystreamer->base.bbs_buffer.maxlen; - mystreamer->bytes_written = 0; next_out = (uint8 *) mystreamer->base.bbs_buffer.data; - } - else - { - avail_out = mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written; - next_out += mystreamer->bytes_written; + mystreamer->bytes_written = 0; } } } @@ -397,10 +395,11 @@ bbstreamer_lz4_decompressor_finalize(bbstreamer *streamer) * End of the stream, if there is some pending data in output buffers then * we must forward it to next streamer. */ - bbstreamer_content(mystreamer->base.bbs_next, NULL, - mystreamer->base.bbs_buffer.data, - mystreamer->base.bbs_buffer.maxlen, - BBSTREAMER_UNKNOWN); + if (mystreamer->bytes_written > 0) + bbstreamer_content(mystreamer->base.bbs_next, NULL, + mystreamer->base.bbs_buffer.data, + mystreamer->bytes_written, + BBSTREAMER_UNKNOWN); bbstreamer_finalize(mystreamer->base.bbs_next); } diff --git a/src/bin/pg_basebackup/bbstreamer_tar.c b/src/bin/pg_basebackup/bbstreamer_tar.c index ef5586c488f..838d285681f 100644 --- a/src/bin/pg_basebackup/bbstreamer_tar.c +++ b/src/bin/pg_basebackup/bbstreamer_tar.c @@ -224,7 +224,8 @@ bbstreamer_tar_parser_content(bbstreamer *streamer, bbstreamer_member *member, /* OK, now we can send it. */ bbstreamer_content(mystreamer->base.bbs_next, &mystreamer->member, - data, mystreamer->pad_bytes_expected, + mystreamer->base.bbs_buffer.data, + mystreamer->pad_bytes_expected, BBSTREAMER_MEMBER_TRAILER); /* Expect next file header. */ @@ -294,6 +295,9 @@ bbstreamer_tar_header(bbstreamer_tar_parser *mystreamer) strlcpy(member->pathname, &buffer[0], MAXPGPATH); if (member->pathname[0] == '\0') pg_fatal("tar member has empty name"); + if (!path_is_safe_for_extraction(member->pathname)) + pg_fatal("tar member has unsafe path name: \"%s\"", + member->pathname); member->size = read_tar_number(&buffer[124], 12); member->mode = read_tar_number(&buffer[100], 8); member->uid = read_tar_number(&buffer[108], 8); @@ -344,6 +348,7 @@ bbstreamer_tar_parser_free(bbstreamer *streamer) { pfree(streamer->bbs_buffer.data); bbstreamer_free(streamer->bbs_next); + pfree(streamer); } /* diff --git a/src/bin/pg_basebackup/bbstreamer_zstd.c b/src/bin/pg_basebackup/bbstreamer_zstd.c index 1207dd771ae..1671e03699a 100644 --- a/src/bin/pg_basebackup/bbstreamer_zstd.c +++ b/src/bin/pg_basebackup/bbstreamer_zstd.c @@ -165,8 +165,8 @@ bbstreamer_zstd_compressor_content(bbstreamer *streamer, &inBuf, ZSTD_e_continue); if (ZSTD_isError(yet_to_flush)) - pg_log_error("could not compress data: %s", - ZSTD_getErrorName(yet_to_flush)); + pg_fatal("could not compress data: %s", + ZSTD_getErrorName(yet_to_flush)); } } @@ -207,8 +207,8 @@ bbstreamer_zstd_compressor_finalize(bbstreamer *streamer) &in, ZSTD_e_end); if (ZSTD_isError(yet_to_flush)) - pg_log_error("could not compress data: %s", - ZSTD_getErrorName(yet_to_flush)); + pg_fatal("could not compress data: %s", + ZSTD_getErrorName(yet_to_flush)); } while (yet_to_flush > 0); @@ -313,8 +313,8 @@ bbstreamer_zstd_decompressor_content(bbstreamer *streamer, &mystreamer->zstd_outBuf, &inBuf); if (ZSTD_isError(ret)) - pg_log_error("could not decompress data: %s", - ZSTD_getErrorName(ret)); + pg_fatal("could not decompress data: %s", + ZSTD_getErrorName(ret)); } } @@ -333,7 +333,7 @@ bbstreamer_zstd_decompressor_finalize(bbstreamer *streamer) if (mystreamer->zstd_outBuf.pos > 0) bbstreamer_content(mystreamer->base.bbs_next, NULL, mystreamer->base.bbs_buffer.data, - mystreamer->base.bbs_buffer.maxlen, + mystreamer->zstd_outBuf.pos, BBSTREAMER_UNKNOWN); bbstreamer_finalize(mystreamer->base.bbs_next); diff --git a/src/bin/pg_basebackup/po/ru.po b/src/bin/pg_basebackup/po/ru.po index 97e76243bd1..192992c64e7 100644 --- a/src/bin/pg_basebackup/po/ru.po +++ b/src/bin/pg_basebackup/po/ru.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: pg_basebackup (PostgreSQL current)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2024-11-09 07:47+0300\n" +"POT-Creation-Date: 2026-05-10 08:02+0300\n" "PO-Revision-Date: 2025-11-09 08:45+0200\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -149,38 +149,38 @@ msgstr "нехватка памяти" msgid "could not write to file \"%s\": %m" msgstr "не удалось записать в файл \"%s\": %m" -#: ../../fe_utils/recovery_gen.c:133 bbstreamer_file.c:93 bbstreamer_file.c:339 +#: ../../fe_utils/recovery_gen.c:133 bbstreamer_file.c:93 bbstreamer_file.c:341 #: pg_basebackup.c:1508 pg_basebackup.c:1717 #, c-format msgid "could not create file \"%s\": %m" msgstr "не удалось создать файл \"%s\": %m" -#: bbstreamer_file.c:138 pg_recvlogical.c:635 +#: bbstreamer_file.c:138 bbstreamer_file.c:267 pg_recvlogical.c:635 #, c-format msgid "could not close file \"%s\": %m" msgstr "не удалось закрыть файл \"%s\": %m" -#: bbstreamer_file.c:275 +#: bbstreamer_file.c:277 #, c-format msgid "unexpected state while extracting archive" msgstr "неожиданное состояние при извлечении архива" -#: bbstreamer_file.c:298 pg_basebackup.c:697 pg_basebackup.c:741 +#: bbstreamer_file.c:300 pg_basebackup.c:697 pg_basebackup.c:741 #, c-format msgid "could not create directory \"%s\": %m" msgstr "не удалось создать каталог \"%s\": %m" -#: bbstreamer_file.c:304 +#: bbstreamer_file.c:306 #, c-format msgid "could not set permissions on directory \"%s\": %m" msgstr "не удалось установить права для каталога \"%s\": %m" -#: bbstreamer_file.c:323 +#: bbstreamer_file.c:325 #, c-format msgid "could not create symbolic link from \"%s\" to \"%s\": %m" msgstr "не удалось создать символическую ссылку \"%s\" в \"%s\": %m" -#: bbstreamer_file.c:343 +#: bbstreamer_file.c:345 #, c-format msgid "could not set permissions on file \"%s\": %m" msgstr "не удалось установить права доступа для файла \"%s\": %m" @@ -265,22 +265,22 @@ msgstr "не удалось завершить сжатие lz4: %s" msgid "could not initialize compression library: %s" msgstr "не удалось инициализировать библиотеку сжатия: %s" -#: bbstreamer_tar.c:244 +#: bbstreamer_tar.c:245 #, c-format msgid "tar file trailer exceeds 2 blocks" msgstr "окончание файла tar занимает больше 2 блоков" -#: bbstreamer_tar.c:249 +#: bbstreamer_tar.c:250 #, c-format msgid "unexpected state while parsing tar archive" msgstr "неожиданное состояние при разборе архива tar" -#: bbstreamer_tar.c:296 +#: bbstreamer_tar.c:297 #, c-format msgid "tar member has empty name" msgstr "пустое имя у компонента tar" -#: bbstreamer_tar.c:328 +#: bbstreamer_tar.c:329 #, c-format msgid "COPY stream ended before last file was finished" msgstr "поток COPY закончился до завершения последнего файла" diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c index f88f6872f4b..f741d2a8227 100644 --- a/src/bin/pg_rewind/file_ops.c +++ b/src/bin/pg_rewind/file_ops.c @@ -48,6 +48,9 @@ open_target_file(const char *path, bool trunc) { int mode; + if (!path_is_safe_for_extraction(path)) + pg_fatal("target file path is unsafe for open: \"%s\"", path); + if (dry_run) return; @@ -188,6 +191,9 @@ remove_target_file(const char *path, bool missing_ok) { char dstpath[MAXPGPATH]; + if (!path_is_safe_for_extraction(path)) + pg_fatal("target file path is unsafe for removal: \"%s\"", path); + if (dry_run) return; @@ -208,6 +214,9 @@ truncate_target_file(const char *path, off_t newsize) char dstpath[MAXPGPATH]; int fd; + if (!path_is_safe_for_extraction(path)) + pg_fatal("target file path is unsafe for truncation: \"%s\"", path); + if (dry_run) return; @@ -230,6 +239,10 @@ create_target_dir(const char *path) { char dstpath[MAXPGPATH]; + if (!path_is_safe_for_extraction(path)) + pg_fatal("target directory path is unsafe for directory creation: \"%s\"", + path); + if (dry_run) return; @@ -244,6 +257,10 @@ remove_target_dir(const char *path) { char dstpath[MAXPGPATH]; + if (!path_is_safe_for_extraction(path)) + pg_fatal("target directory path is unsafe for directory removal: \"%s\"", + path); + if (dry_run) return; @@ -258,6 +275,9 @@ create_target_symlink(const char *path, const char *link) { char dstpath[MAXPGPATH]; + if (!path_is_safe_for_extraction(path)) + pg_fatal("target symlink path is unsafe for creation: \"%s\"", path); + if (dry_run) return; @@ -272,6 +292,9 @@ remove_target_symlink(const char *path) { char dstpath[MAXPGPATH]; + if (!path_is_safe_for_extraction(path)) + pg_fatal("target symlink path is unsafe for removal: \"%s\"", path); + if (dry_run) return; diff --git a/src/bin/pg_test_fsync/po/ru.po b/src/bin/pg_test_fsync/po/ru.po index 1bddfb0b279..daa5c18b04d 100644 --- a/src/bin/pg_test_fsync/po/ru.po +++ b/src/bin/pg_test_fsync/po/ru.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: pg_test_fsync (PostgreSQL) 10\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" "POT-Creation-Date: 2022-08-27 14:52+0300\n" -"PO-Revision-Date: 2022-09-05 13:36+0300\n" +"PO-Revision-Date: 2024-09-04 18:11+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" "Language: ru\n" @@ -90,7 +90,7 @@ msgstr[2] "на тест отводится %u сек.\n" #, c-format msgid "O_DIRECT supported on this platform for open_datasync and open_sync.\n" msgstr "" -"O_DIRECT на этой платформе не поддерживается для open_datasync и open_sync.\n" +"O_DIRECT на этой платформе поддерживается для open_datasync и open_sync.\n" #: pg_test_fsync.c:218 #, c-format diff --git a/src/bin/pg_test_timing/pg_test_timing.c b/src/bin/pg_test_timing/pg_test_timing.c index c29d6f87629..0ccf55761ce 100644 --- a/src/bin/pg_test_timing/pg_test_timing.c +++ b/src/bin/pg_test_timing/pg_test_timing.c @@ -149,7 +149,7 @@ test_timing(unsigned int duration) if (diff < 0) { fprintf(stderr, _("Detected clock going backwards in time.\n")); - fprintf(stderr, _("Time warp: %d ms\n"), diff); + fprintf(stderr, _("Time warp: %d us\n"), diff); exit(1); } diff --git a/src/bin/pg_test_timing/po/de.po b/src/bin/pg_test_timing/po/de.po index 6bcbc73064c..0ed3d9e23a0 100644 --- a/src/bin/pg_test_timing/po/de.po +++ b/src/bin/pg_test_timing/po/de.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: pg_test_timing (PostgreSQL) 13\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2021-04-12 14:17+0000\n" +"POT-Creation-Date: 2026-05-05 10:59+0000\n" "PO-Revision-Date: 2021-04-12 16:37+0200\n" "Last-Translator: Peter Eisentraut \n" "Language-Team: German \n" @@ -57,8 +57,8 @@ msgstr "Rückwärts gehende Uhr festgestellt.\n" #: pg_test_timing.c:152 #, c-format -msgid "Time warp: %d ms\n" -msgstr "Zeitdifferenz: %d ms\n" +msgid "Time warp: %d us\n" +msgstr "Zeitdifferenz: %d µs\n" #: pg_test_timing.c:175 #, c-format diff --git a/src/bin/pg_test_timing/po/fr.po b/src/bin/pg_test_timing/po/fr.po index 61f973bf6e1..228d4d15def 100644 --- a/src/bin/pg_test_timing/po/fr.po +++ b/src/bin/pg_test_timing/po/fr.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: PostgreSQL 15\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2022-04-12 05:16+0000\n" -"PO-Revision-Date: 2024-09-16 16:35+0200\n" +"POT-Creation-Date: 2026-04-24 03:26+0000\n" +"PO-Revision-Date: 2026-04-24 17:51+0200\n" "Last-Translator: Guillaume Lelarge \n" "Language-Team: French \n" "Language: fr\n" @@ -19,7 +19,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 3.5\n" +"X-Generator: Poedit 3.9\n" #: pg_test_timing.c:59 #, c-format @@ -60,8 +60,8 @@ msgstr "Détection d'une horloge partant à rebours.\n" #: pg_test_timing.c:152 #, c-format -msgid "Time warp: %d ms\n" -msgstr "Décalage de temps : %d ms\n" +msgid "Time warp: %d us\n" +msgstr "Décalage de temps : %d us\n" #: pg_test_timing.c:175 #, c-format diff --git a/src/bin/pg_test_timing/po/ru.po b/src/bin/pg_test_timing/po/ru.po index c17a9921393..11a3448a814 100644 --- a/src/bin/pg_test_timing/po/ru.po +++ b/src/bin/pg_test_timing/po/ru.po @@ -1,13 +1,13 @@ # Russian message translation file for pg_test_timing # Copyright (C) 2017 PostgreSQL Global Development Group # This file is distributed under the same license as the PostgreSQL package. -# Alexander Lakhin , 2017, 2021, 2024. +# SPDX-FileCopyrightText: 2017, 2021, 2024, 2026 Alexander Lakhin msgid "" msgstr "" "Project-Id-Version: pg_test_timing (PostgreSQL) 10\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2021-08-14 06:29+0300\n" -"PO-Revision-Date: 2021-09-04 12:18+0300\n" +"POT-Creation-Date: 2026-05-10 08:02+0300\n" +"PO-Revision-Date: 2026-05-10 08:31+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" "Language: ru\n" @@ -57,8 +57,8 @@ msgstr "Обнаружен обратный ход часов.\n" #: pg_test_timing.c:152 #, c-format -msgid "Time warp: %d ms\n" -msgstr "Сдвиг времени: %d мс\n" +msgid "Time warp: %d us\n" +msgstr "Сдвиг времени: %d мкс\n" #: pg_test_timing.c:175 #, c-format diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c index 29b9e44f782..f7d6a537b4f 100644 --- a/src/bin/pg_upgrade/dump.c +++ b/src/bin/pg_upgrade/dump.c @@ -21,9 +21,10 @@ generate_old_dump(void) /* run new pg_dumpall binary for globals */ exec_prog(UTILITY_LOG_FILE, NULL, true, true, - "\"%s/pg_dumpall\" %s --globals-only --quote-all-identifiers " + "\"%s/pg_dumpall\" %s%s --globals-only --quote-all-identifiers " "--binary-upgrade %s -f \"%s/%s\"", new_cluster.bindir, cluster_conn_opts(&old_cluster), + protocol_negotiation_supported(&old_cluster) ? "" : " -d \"max_protocol_version=3.0\"", log_opts.verbose ? "--verbose" : "", log_opts.dumpdir, GLOBALS_DUMP_FILE); @@ -43,6 +44,9 @@ generate_old_dump(void) initPQExpBuffer(&connstr); appendPQExpBufferStr(&connstr, "dbname="); appendConnStrVal(&connstr, old_db->db_name); + if (!protocol_negotiation_supported(&old_cluster)) + appendPQExpBufferStr(&connstr, " max_protocol_version=3.0"); + initPQExpBuffer(&escaped_connstr); appendShellString(&escaped_connstr, connstr.data); termPQExpBuffer(&connstr); diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h index 19ca68ed619..803acc696ef 100644 --- a/src/bin/pg_upgrade/pg_upgrade.h +++ b/src/bin/pg_upgrade/pg_upgrade.h @@ -449,6 +449,7 @@ bool check_for_data_types_usage(ClusterInfo *cluster, bool check_for_data_type_usage(ClusterInfo *cluster, const char *type_name, const char *output_path); +bool protocol_negotiation_supported(const ClusterInfo *cluster); void old_9_3_check_for_line_data_type_usage(ClusterInfo *cluster); void old_9_6_check_for_unknown_data_type_usage(ClusterInfo *cluster); void old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, diff --git a/src/bin/pg_upgrade/po/ru.po b/src/bin/pg_upgrade/po/ru.po index 09f65c87cbe..3a502cbc73b 100644 --- a/src/bin/pg_upgrade/po/ru.po +++ b/src/bin/pg_upgrade/po/ru.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: pg_upgrade (PostgreSQL) 10\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-08-09 07:12+0300\n" +"POT-Creation-Date: 2026-05-10 08:02+0300\n" "PO-Revision-Date: 2025-08-09 07:24+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -228,7 +228,7 @@ msgstr "Создание скрипта для удаления старого #: check.c:601 check.c:776 check.c:896 check.c:995 check.c:1126 check.c:1205 #: check.c:1285 check.c:1587 file.c:338 function.c:165 option.c:465 -#: version.c:116 version.c:292 version.c:429 +#: version.c:116 version.c:323 version.c:460 #, c-format msgid "could not open file \"%s\": %s\n" msgstr "не удалось открыть файл \"%s\": %s\n" @@ -274,7 +274,7 @@ msgstr "" #: check.c:793 check.c:918 check.c:1020 check.c:1146 check.c:1227 check.c:1305 #: check.c:1365 check.c:1426 check.c:1460 check.c:1491 check.c:1610 -#: function.c:187 version.c:192 version.c:232 version.c:378 +#: function.c:187 version.c:192 version.c:232 version.c:409 #, c-format msgid "fatal\n" msgstr "сбой\n" @@ -646,7 +646,7 @@ msgstr "В исходном кластере не хватает информа msgid "The target cluster lacks cluster state information:\n" msgstr "В целевом кластере не хватает информации о состоянии кластера:\n" -#: controldata.c:209 dump.c:50 pg_upgrade.c:440 pg_upgrade.c:477 +#: controldata.c:209 dump.c:54 pg_upgrade.c:440 pg_upgrade.c:477 #: relfilenode.c:231 server.c:34 #, c-format msgid "%s" @@ -920,7 +920,7 @@ msgstr "" msgid "Creating dump of global objects" msgstr "Формирование выгрузки глобальных объектов" -#: dump.c:32 +#: dump.c:33 #, c-format msgid "Creating dump of database schemas" msgstr "Формирование выгрузки схем базы данных" @@ -1835,17 +1835,17 @@ msgstr "копирование \"%s\" в \"%s\"\n" msgid "linking \"%s\" to \"%s\"\n" msgstr "создание ссылки на \"%s\" в \"%s\"\n" -#: server.c:39 server.c:143 util.c:220 util.c:250 +#: server.c:39 server.c:145 util.c:220 util.c:250 #, c-format msgid "Failure, exiting\n" msgstr "Ошибка, выполняется выход\n" -#: server.c:133 +#: server.c:135 #, c-format msgid "executing: %s\n" msgstr "выполняется: %s\n" -#: server.c:139 +#: server.c:141 #, c-format msgid "" "SQL command failed\n" @@ -1856,17 +1856,17 @@ msgstr "" "%s\n" "%s" -#: server.c:169 +#: server.c:171 #, c-format msgid "could not open version file \"%s\": %m\n" msgstr "не удалось открыть файл с версией \"%s\": %m\n" -#: server.c:173 +#: server.c:175 #, c-format msgid "could not parse version file \"%s\"\n" msgstr "не удалось разобрать файл с версией \"%s\"\n" -#: server.c:291 +#: server.c:293 #, c-format msgid "" "\n" @@ -1875,7 +1875,7 @@ msgstr "" "\n" "%s" -#: server.c:295 +#: server.c:297 #, c-format msgid "" "could not connect to source postmaster started with the command:\n" @@ -1885,7 +1885,7 @@ msgstr "" "командой:\n" "%s\n" -#: server.c:299 +#: server.c:301 #, c-format msgid "" "could not connect to target postmaster started with the command:\n" @@ -1895,26 +1895,26 @@ msgstr "" "командой:\n" "%s\n" -#: server.c:313 +#: server.c:315 #, c-format msgid "pg_ctl failed to start the source server, or connection failed\n" msgstr "" "программа pg_ctl не смогла запустить исходный сервер, либо к нему не удалось " "подключиться\n" -#: server.c:315 +#: server.c:317 #, c-format msgid "pg_ctl failed to start the target server, or connection failed\n" msgstr "" "программа pg_ctl не смогла запустить целевой сервер, либо к нему не удалось " "подключиться\n" -#: server.c:360 +#: server.c:362 #, c-format msgid "out of memory\n" msgstr "нехватка памяти\n" -#: server.c:373 +#: server.c:375 #, c-format msgid "libpq environment variable %s has a non-local server value: %s\n" msgstr "" @@ -2020,17 +2020,17 @@ msgstr "" " %s\n" "\n" -#: version.c:257 +#: version.c:288 #, c-format msgid "Checking for hash indexes" msgstr "Проверка хеш-индексов" -#: version.c:335 +#: version.c:366 #, c-format msgid "warning" msgstr "предупреждение" -#: version.c:337 +#: version.c:368 #, c-format msgid "" "\n" @@ -2047,7 +2047,7 @@ msgstr "" "инструкции по выполнению REINDEX.\n" "\n" -#: version.c:343 +#: version.c:374 #, c-format msgid "" "\n" @@ -2068,13 +2068,13 @@ msgstr "" "индексы; до этого никакие хеш-индексы не будут использоваться.\n" "\n" -#: version.c:369 +#: version.c:400 #, c-format msgid "Checking for invalid \"sql_identifier\" user columns" msgstr "" "Проверка неправильных пользовательских столбцов типа \"sql_identifier\"" -#: version.c:379 +#: version.c:410 #, c-format msgid "" "Your installation contains the \"sql_identifier\" data type in user tables.\n" @@ -2093,17 +2093,17 @@ msgstr "" " %s\n" "\n" -#: version.c:403 +#: version.c:434 #, c-format msgid "Checking for extension updates" msgstr "Проверка обновлённых расширений" -#: version.c:455 +#: version.c:486 #, c-format msgid "notice" msgstr "замечание" -#: version.c:456 +#: version.c:487 #, c-format msgid "" "\n" diff --git a/src/bin/pg_upgrade/server.c b/src/bin/pg_upgrade/server.c index 265137e86bf..df165efac40 100644 --- a/src/bin/pg_upgrade/server.c +++ b/src/bin/pg_upgrade/server.c @@ -71,6 +71,8 @@ get_db_conn(ClusterInfo *cluster, const char *db_name) appendPQExpBufferStr(&conn_opts, " host="); appendConnStrVal(&conn_opts, cluster->sockdir); } + if (!protocol_negotiation_supported(cluster)) + appendPQExpBufferStr(&conn_opts, " max_protocol_version=3.0"); conn = PQconnectdb(conn_opts.data); termPQExpBuffer(&conn_opts); diff --git a/src/bin/pg_upgrade/version.c b/src/bin/pg_upgrade/version.c index 20c61072a42..8658436b366 100644 --- a/src/bin/pg_upgrade/version.c +++ b/src/bin/pg_upgrade/version.c @@ -241,6 +241,37 @@ old_9_6_check_for_unknown_data_type_usage(ClusterInfo *cluster) check_ok(); } +/* + * Older servers can't support newer protocol versions, so their connection + * strings will need to lock max_protocol_version to 3.0. + */ +bool +protocol_negotiation_supported(const ClusterInfo *cluster) +{ + /* + * Back-branch-specific complication: in libpq versions prior to PG18, + * max_protocol_version isn't supported. But we also don't need to worry + * about newer protocol versions being used in that case, so just lie and + * return true. + * + * (Checking for a libpq version that's newer than this branch looks very + * strange, but distributions are allowed to link older pg_upgrade + * binaries against the newest release of libpq.) + */ + if (PQlibVersion() < 180000) + return true; + + /* + * The February 2018 patch release (9.3.21, 9.4.16, 9.5.11, 9.6.7, and + * 10.2) added support for NegotiateProtocolVersion. But ClusterInfo only + * has information about the major version number. To ensure we can still + * upgrade older unpatched servers, just assume anything prior to PG11 + * can't negotiate. It's not possible for those servers to make use of + * newer protocols anyway, so nothing is lost. + */ + return (GET_MAJOR_VERSION(cluster->major_version) >= 1100); +} + /* * old_9_6_invalidate_hash_indexes() * 9.6 -> 10 diff --git a/src/bin/pg_verifybackup/po/ru.po b/src/bin/pg_verifybackup/po/ru.po index a91391f3fa9..16c0e4b380e 100644 --- a/src/bin/pg_verifybackup/po/ru.po +++ b/src/bin/pg_verifybackup/po/ru.po @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2020, 2021, 2022, 2023, 2024, 2025 Alexander Lakhin +# SPDX-FileCopyrightText: 2020, 2021, 2022, 2023, 2024, 2025, 2026 Alexander Lakhin msgid "" msgstr "" "Project-Id-Version: pg_verifybackup (PostgreSQL) 13\n" diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index 5dc60109b12..4f73fafe719 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -121,7 +121,7 @@ verify_directory(const char *directory) static void split_path(const char *path, char **dir, char **fname) { - char *sep; + const char *sep; /* split filepath into directory & filename */ sep = strrchr(path, '/'); diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index f9c305fae14..80d14108c28 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -6177,7 +6177,7 @@ findBuiltin(const char *name) static int parseScriptWeight(const char *option, char **script) { - char *sep; + const char *sep; int weight; if ((sep = strrchr(option, WSEP))) diff --git a/src/common/encnames.c b/src/common/encnames.c index 596a23b64da..0588abfb4e9 100644 --- a/src/common/encnames.c +++ b/src/common/encnames.c @@ -61,8 +61,9 @@ static const pg_encname pg_encname_tbl[] = * Japanese, standard OSF */ { "euckr", PG_EUC_KR - }, /* EUC-KR; Extended Unix Code for Korean , KS - * X 1001 standard */ + }, /* EUC-KR; Extended Unix Code for Korean + * precomposed (Wansung) encoding, standard KS + * X 1001 */ { "euctw", PG_EUC_TW }, /* EUC-TW; Extended Unix Code for @@ -119,8 +120,8 @@ static const pg_encname pg_encname_tbl[] = }, /* ISO-8859-9; RFC1345,KXS2 */ { "johab", PG_JOHAB - }, /* JOHAB; Extended Unix Code for simplified - * Chinese */ + }, /* JOHAB; Korean combining (Johab) encoding, + * standard KS X 1001 annex 3 */ { "koi8", PG_KOI8R }, /* _dirty_ alias for KOI8-R (backward @@ -189,7 +190,9 @@ static const pg_encname pg_encname_tbl[] = }, /* alias for WIN1258 */ { "uhc", PG_UHC - }, /* UHC; Korean Windows CodePage 949 */ + }, /* UHC; Unified Hangul Code, Microsoft Windows + * CodePage 949; superset of EUC-KR covering + * all 11,172 precomposed Hangul syllables */ { "unicode", PG_UTF8 }, /* alias for UTF8 */ diff --git a/src/common/fe_memutils.c b/src/common/fe_memutils.c index 21fcce54309..519fc18857e 100644 --- a/src/common/fe_memutils.c +++ b/src/common/fe_memutils.c @@ -19,6 +19,12 @@ #include "postgres_fe.h" +#include "common/int.h" + +static pg_noinline void add_size_error(Size s1, Size s2) pg_attribute_noreturn(); +static pg_noinline void mul_size_error(Size s1, Size s2) pg_attribute_noreturn(); + + static inline void * pg_malloc_internal(size_t size, int flags) { @@ -174,3 +180,185 @@ repalloc(void *pointer, Size size) { return pg_realloc(pointer, size); } + +/* + * Support for safe calculation of memory request sizes + * + * These functions perform the requested calculation, but throw error if the + * result overflows. + * + * An important property of these functions is that if an argument was a + * negative signed int before promotion (implying overflow in calculating it) + * we will detect that as an error. That happens because we reject results + * larger than SIZE_MAX / 2. In the backend we rely on later checks to do + * that, but in frontend we must do it here. + */ +Size +add_size(Size s1, Size s2) +{ + Size result; + + if (unlikely(pg_add_size_overflow(s1, s2, &result) || + result > (SIZE_MAX / 2))) + add_size_error(s1, s2); + return result; +} + +static pg_noinline void +add_size_error(Size s1, Size s2) +{ + fprintf(stderr, _("invalid memory allocation request size %zu + %zu\n"), + s1, s2); + exit(EXIT_FAILURE); +} + +Size +mul_size(Size s1, Size s2) +{ + Size result; + + if (unlikely(pg_mul_size_overflow(s1, s2, &result) || + result > (SIZE_MAX / 2))) + mul_size_error(s1, s2); + return result; +} + +static pg_noinline void +mul_size_error(Size s1, Size s2) +{ + fprintf(stderr, _("invalid memory allocation request size %zu * %zu\n"), + s1, s2); + exit(EXIT_FAILURE); +} + +/* + * pg_malloc_mul + * Equivalent to pg_malloc(mul_size(s1, s2)). + */ +void * +pg_malloc_mul(Size s1, Size s2) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req) || + req > (SIZE_MAX / 2))) + mul_size_error(s1, s2); + return pg_malloc(req); +} + +/* + * pg_malloc0_mul + * Equivalent to pg_malloc0(mul_size(s1, s2)). + * + * This is comparable to standard calloc's behavior. + */ +void * +pg_malloc0_mul(Size s1, Size s2) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req) || + req > (SIZE_MAX / 2))) + mul_size_error(s1, s2); + return pg_malloc0(req); +} + +/* + * pg_malloc_mul_extended + * Equivalent to pg_malloc_extended(mul_size(s1, s2), flags). + */ +void * +pg_malloc_mul_extended(Size s1, Size s2, int flags) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req) || + req > (SIZE_MAX / 2))) + mul_size_error(s1, s2); + return pg_malloc_extended(req, flags); +} + +/* + * pg_realloc_mul + * Equivalent to pg_realloc(p, mul_size(s1, s2)). + */ +void * +pg_realloc_mul(void *p, Size s1, Size s2) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req) || + req > (SIZE_MAX / 2))) + mul_size_error(s1, s2); + return pg_realloc(p, req); +} + +/* + * palloc_mul + * Equivalent to palloc(mul_size(s1, s2)). + */ +void * +palloc_mul(Size s1, Size s2) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req) || + req > (SIZE_MAX / 2))) + mul_size_error(s1, s2); + return palloc(req); +} + +/* + * palloc0_mul + * Equivalent to palloc0(mul_size(s1, s2)). + * + * This is comparable to standard calloc's behavior. + */ +void * +palloc0_mul(Size s1, Size s2) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req) || + req > (SIZE_MAX / 2))) + mul_size_error(s1, s2); + return palloc0(req); +} + +/* + * palloc_mul_extended + * Equivalent to palloc_extended(mul_size(s1, s2), flags). + */ +void * +palloc_mul_extended(Size s1, Size s2, int flags) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req) || + req > (SIZE_MAX / 2))) + mul_size_error(s1, s2); + return palloc_extended(req, flags); +} + +/* + * repalloc_mul + * Equivalent to repalloc(p, mul_size(s1, s2)). + */ +void * +repalloc_mul(void *p, Size s1, Size s2) +{ + /* inline mul_size() for efficiency */ + Size req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req) || + req > (SIZE_MAX / 2))) + mul_size_error(s1, s2); + return repalloc(p, req); +} diff --git a/src/common/pg_lzcompress.c b/src/common/pg_lzcompress.c index ad3970c7a96..afcfa2e523d 100644 --- a/src/common/pg_lzcompress.c +++ b/src/common/pg_lzcompress.c @@ -727,22 +727,33 @@ pglz_decompress(const char *source, int32 slen, char *dest, int32 len; int32 off; + /* + * A match tag is at least 2 bytes; if the length nibble is + * 0x0f the tag is 3 bytes (extended length). Verify we have + * enough source data before reading them. + */ + if (unlikely(sp + 2 > srcend)) + return -1; + len = (sp[0] & 0x0f) + 3; off = ((sp[0] & 0xf0) << 4) | sp[1]; sp += 2; if (len == 18) + { + if (unlikely(sp >= srcend)) + return -1; len += *sp++; + } /* - * Check for corrupt data: if we fell off the end of the - * source, or if we obtained off = 0, or if off is more than - * the distance back to the buffer start, we have problems. - * (We must check for off = 0, else we risk an infinite loop - * below in the face of corrupt data. Likewise, the upper - * limit on off prevents accessing outside the buffer - * boundaries.) + * Check for corrupt data: if we obtained off = 0, or if off + * is more than the distance back to the buffer start, we have + * problems. (We must check for off = 0, else we risk an + * infinite loop below in the face of corrupt data. Likewise, + * the upper limit on off prevents accessing outside the + * buffer boundaries.) */ - if (unlikely(sp > srcend || off == 0 || + if (unlikely(off == 0 || off > (dp - (unsigned char *) dest))) return -1; diff --git a/src/common/protocol_openssl.c b/src/common/protocol_openssl.c index 8d1da8be387..01e9924cd72 100644 --- a/src/common/protocol_openssl.c +++ b/src/common/protocol_openssl.c @@ -114,4 +114,10 @@ SSL_CTX_set_max_proto_version(SSL_CTX *ctx, int version) return 1; /* success */ } -#endif /* !SSL_CTX_set_min_proto_version */ +#else /* !SSL_CTX_set_min_proto_version */ + +/* prevent linker complaints about empty module */ +extern int protocol_openssl_dummy_variable; +int protocol_openssl_dummy_variable = 0; + +#endif /* SSL_CTX_set_min_proto_version */ diff --git a/src/common/psprintf.c b/src/common/psprintf.c index a5a5cb121c5..49b4fc92baa 100644 --- a/src/common/psprintf.c +++ b/src/common/psprintf.c @@ -24,9 +24,6 @@ #include "postgres_fe.h" -/* It's possible we could use a different value for this in frontend code */ -#define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */ - #endif diff --git a/src/common/saslprep.c b/src/common/saslprep.c index b8fe2026f99..ee211dd4ab5 100644 --- a/src/common/saslprep.c +++ b/src/common/saslprep.c @@ -24,10 +24,6 @@ #include "utils/memutils.h" #else #include "postgres_fe.h" - -/* It's possible we could use a different value for this in frontend code */ -#define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */ - #endif #include "common/saslprep.h" diff --git a/src/common/stringinfo.c b/src/common/stringinfo.c index 76ff4d3e245..7469c83e90e 100644 --- a/src/common/stringinfo.c +++ b/src/common/stringinfo.c @@ -24,9 +24,6 @@ #include "postgres_fe.h" -/* It's possible we could use a different value for this in frontend code */ -#define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */ - #endif #include "lib/stringinfo.h" diff --git a/src/common/unicode_norm.c b/src/common/unicode_norm.c index 8f1402e4328..4ca40741fd8 100644 --- a/src/common/unicode_norm.c +++ b/src/common/unicode_norm.c @@ -23,6 +23,7 @@ #include "common/unicode_norm_hashfunc.h" #include "common/unicode_normprops_table.h" #include "port/pg_bswap.h" +#include "utils/memutils.h" #else #include "common/unicode_norm_table.h" #endif @@ -420,10 +421,28 @@ unicode_normalize(UnicodeNormalizationForm form, const pg_wchar *input) /* * Calculate how many characters long the decomposed version will be. + * + * Some characters decompose to quite a few code points, so that the + * decomposed version's size could overrun MaxAllocSize, and even 32-bit + * size_t, even though the input string presumably fits in that. In + * frontend we want to just return NULL in that case, so monitor the sum + * and exit early once we'd need more than MaxAllocSize bytes. */ decomp_size = 0; for (p = input; *p; p++) + { decomp_size += get_decomposed_size(*p, compat); + if (unlikely(decomp_size > MaxAllocSize / sizeof(pg_wchar))) + { +#ifndef FRONTEND + /* Exit loop and let palloc() throw error below */ + break; +#else + /* Just return NULL with no explicit error */ + return NULL; +#endif + } + } decomp_chars = (pg_wchar *) ALLOC((decomp_size + 1) * sizeof(pg_wchar)); if (decomp_chars == NULL) diff --git a/src/include/access/slru.h b/src/include/access/slru.h index 161597c7442..130c41c8632 100644 --- a/src/include/access/slru.h +++ b/src/include/access/slru.h @@ -93,9 +93,7 @@ typedef struct SlruSharedData /* * latest_page_number is the page number of the current end of the log; * this is not critical data, since we use it only to avoid swapping out - * the latest page. (An exception: an accurate latest_page_number is - * needed on pg_multixact/offsets to replay WAL generated with older minor - * versions correctly. See RecordNewMultiXact().) + * the latest page. */ int latest_page_number; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index e81a9d6aec9..af3ce857971 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -218,6 +218,7 @@ extern bool RecoveryInProgress(void); extern RecoveryState GetRecoveryState(void); extern bool XLogInsertAllowed(void); extern XLogRecPtr GetXLogInsertRecPtr(void); +extern XLogRecPtr GetXLogInsertEndRecPtr(void); extern XLogRecPtr GetXLogWriteRecPtr(void); extern uint64 GetSystemIdentifier(void); diff --git a/src/include/c.h b/src/include/c.h index afac24ac1d2..4d0c81583f2 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -155,6 +155,16 @@ #define PG_USED_FOR_ASSERTS_ONLY pg_attribute_unused() #endif +/* + * Our C and C++ compilers may have different ideas about which printf + * archetype best represents what src/port/snprintf.c can do. + */ +#ifndef __cplusplus +#define PG_PRINTF_ATTRIBUTE PG_C_PRINTF_ATTRIBUTE +#else +#define PG_PRINTF_ATTRIBUTE PG_CXX_PRINTF_ATTRIBUTE +#endif + /* GCC and XLC support format attributes */ #if defined(__GNUC__) || defined(__IBMC__) #define pg_attribute_format_arg(a) __attribute__((format_arg(a))) diff --git a/src/include/common/fe_memutils.h b/src/include/common/fe_memutils.h index 63f2b6a802d..5780b53613c 100644 --- a/src/include/common/fe_memutils.h +++ b/src/include/common/fe_memutils.h @@ -9,6 +9,18 @@ #ifndef FE_MEMUTILS_H #define FE_MEMUTILS_H +/* + * Assumed maximum size for allocation requests. + * + * We don't enforce this, so the actual maximum is the platform's SIZE_MAX. + * But it's useful to have it defined in frontend builds, so that common + * code can check for oversized requests without having frontend-vs-backend + * differences. Also, some code relies on MaxAllocSize being no more than + * INT_MAX/2, so rather than setting this to SIZE_MAX, make it the same as + * the backend's value. + */ +#define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */ + /* * Flags for pg_malloc_extended and palloc_extended, deliberately named * the same as the backend flags. @@ -29,6 +41,16 @@ extern void *pg_malloc_extended(size_t size, int flags); extern void *pg_realloc(void *pointer, size_t size); extern void pg_free(void *pointer); +/* + * Support for safe calculation of memory request sizes + */ +extern Size add_size(Size s1, Size s2); +extern Size mul_size(Size s1, Size s2); +extern void *pg_malloc_mul(Size s1, Size s2); +extern void *pg_malloc0_mul(Size s1, Size s2); +extern void *pg_malloc_mul_extended(Size s1, Size s2, int flags); +extern void *pg_realloc_mul(void *p, Size s1, Size s2); + /* * Variants with easier notation and more type safety */ @@ -42,14 +64,15 @@ extern void pg_free(void *pointer); /* * Allocate space for "count" objects of type "type" */ -#define pg_malloc_array(type, count) ((type *) pg_malloc(sizeof(type) * (count))) -#define pg_malloc0_array(type, count) ((type *) pg_malloc0(sizeof(type) * (count))) +#define pg_malloc_array(type, count) ((type *) pg_malloc_mul(sizeof(type), count)) +#define pg_malloc0_array(type, count) ((type *) pg_malloc0_mul(sizeof(type), count)) +#define pg_malloc_array_extended(type, count, flags) ((type *) pg_malloc_mul_extended(sizeof(type), count, flags)) /* * Change size of allocation pointed to by "pointer" to have space for "count" * objects of type "type" */ -#define pg_realloc_array(pointer, type, count) ((type *) pg_realloc(pointer, sizeof(type) * (count))) +#define pg_realloc_array(pointer, type, count) ((type *) pg_realloc_mul(pointer, sizeof(type), count)) /* Equivalent functions, deliberately named the same as backend functions */ extern char *pstrdup(const char *in); @@ -59,12 +82,17 @@ extern void *palloc0(Size size); extern void *palloc_extended(Size size, int flags); extern void *repalloc(void *pointer, Size size); extern void pfree(void *pointer); +extern void *palloc_mul(Size s1, Size s2); +extern void *palloc0_mul(Size s1, Size s2); +extern void *palloc_mul_extended(Size s1, Size s2, int flags); +extern void *repalloc_mul(void *p, Size s1, Size s2); #define palloc_object(type) ((type *) palloc(sizeof(type))) #define palloc0_object(type) ((type *) palloc0(sizeof(type))) -#define palloc_array(type, count) ((type *) palloc(sizeof(type) * (count))) -#define palloc0_array(type, count) ((type *) palloc0(sizeof(type) * (count))) -#define repalloc_array(pointer, type, count) ((type *) repalloc(pointer, sizeof(type) * (count))) +#define palloc_array(type, count) ((type *) palloc_mul(sizeof(type), count)) +#define palloc0_array(type, count) ((type *) palloc0_mul(sizeof(type), count)) +#define palloc_array_extended(type, count, flags) ((type *) palloc_mul_extended(sizeof(type), count, flags)) +#define repalloc_array(pointer, type, count) ((type *) repalloc_mul(pointer, sizeof(type), count)) /* sprintf into a palloc'd buffer --- these are in psprintf.c */ extern char *psprintf(const char *fmt,...) pg_attribute_printf(1, 2); diff --git a/src/include/common/int.h b/src/include/common/int.h index e2617fbc5d2..e22cf9115c9 100644 --- a/src/include/common/int.h +++ b/src/include/common/int.h @@ -438,4 +438,71 @@ pg_mul_u64_overflow(uint64 a, uint64 b, uint64 *result) #endif } +/* + * size_t + */ +static inline bool +pg_add_size_overflow(size_t a, size_t b, size_t *result) +{ +#if defined(HAVE__BUILTIN_OP_OVERFLOW) + return __builtin_add_overflow(a, b, result); +#else + size_t res = a + b; + + if (res < a) + { + *result = 0x5EED; /* to avoid spurious warnings */ + return true; + } + *result = res; + return false; +#endif +} + +static inline bool +pg_sub_size_overflow(size_t a, size_t b, size_t *result) +{ +#if defined(HAVE__BUILTIN_OP_OVERFLOW) + return __builtin_sub_overflow(a, b, result); +#else + if (b > a) + { + *result = 0x5EED; /* to avoid spurious warnings */ + return true; + } + *result = a - b; + return false; +#endif +} + +static inline bool +pg_mul_size_overflow(size_t a, size_t b, size_t *result) +{ +#if defined(HAVE__BUILTIN_OP_OVERFLOW) + return __builtin_mul_overflow(a, b, result); +#else + size_t res = a * b; + + if (a != 0 && b != res / a) + { + *result = 0x5EED; /* to avoid spurious warnings */ + return true; + } + *result = res; + return false; +#endif +} + +/* + * pg_neg_size_overflow is currently omitted, to avoid having to reason about + * the portability of SSIZE_MIN/_MAX before a use case exists. + */ +/* + * static inline bool + * pg_neg_size_overflow(size_t a, ssize_t *result) + * { + * ... + * } + */ + #endif /* COMMON_INT_H */ diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index fd70f021ada..d1f15a92f9d 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -585,6 +585,10 @@ typedef struct ExprEvalStep { bool has_nulls; bool inclause; /* true for IN and false for NOT IN */ + bool null_lhs_result; /* for non-strict lookups, we + * cache what looking up NULL + * returns. */ + bool null_lhs_isnull; struct ScalarArrayOpExprHashTable *elements_tab; FmgrInfo *finfo; /* function's lookup data */ FunctionCallInfo fcinfo_data; /* arguments etc */ diff --git a/src/include/jit/SectionMemoryManager.h b/src/include/jit/SectionMemoryManager.h index 93cf9771570..aac78b5bd7e 100644 --- a/src/include/jit/SectionMemoryManager.h +++ b/src/include/jit/SectionMemoryManager.h @@ -1,5 +1,5 @@ /* - * This is a copy LLVM source code modified by the PostgreSQL project. + * This is a copy of LLVM source code modified by the PostgreSQL project. * See SectionMemoryManager.cpp for notes on provenance and license. */ diff --git a/src/include/jit/llvmjit_backport.h b/src/include/jit/llvmjit_backport.h index 92874f7998c..04851c9b68a 100644 --- a/src/include/jit/llvmjit_backport.h +++ b/src/include/jit/llvmjit_backport.h @@ -8,7 +8,7 @@ #include /* - * LLVM's RuntimeDyld can produce code that crashes on larger memory ARM + * Pre-LLVM 22 RuntimeDyld can produce code that crashes on large memory ARM * systems, because llvm::SectionMemoryManager allocates multiple pieces of * memory that can be placed too far apart for the generated code. See * src/backend/jit/llvm/SectionMemoryManager.cpp for the patched replacement @@ -18,7 +18,7 @@ * We have adjusted it to compile against a range of LLVM versions, but not * further back than 12 for now. */ -#if defined(__aarch64__) && LLVM_VERSION_MAJOR > 11 +#if defined(__aarch64__) && LLVM_VERSION_MAJOR > 11 && LLVM_VERSION_MAJOR < 22 #define USE_LLVM_BACKPORT_SECTION_MEMORY_MANAGER #endif diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 676e7bea16d..ea9adec2b08 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -178,6 +178,10 @@ don't. */ #undef HAVE_DECL_STRTOULL +/* Define to 1 if you have the declaration of `timingsafe_bcmp', and to 0 if + you don't. */ +#undef HAVE_DECL_TIMINGSAFE_BCMP + /* Define to 1 if you have the `dlopen' function. */ #undef HAVE_DLOPEN @@ -669,6 +673,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_TERMIOS_H +/* Define to 1 if you have the `timingsafe_bcmp' function. */ +#undef HAVE_TIMINGSAFE_BCMP + /* Define to 1 if your compiler understands `typeof' or something similar. */ #undef HAVE_TYPEOF @@ -814,6 +821,14 @@ /* Define to the version of this package. */ #undef PACKAGE_VERSION +/* Define to best C++ printf format archetype, usually gnu_printf if + available. */ +#undef PG_CXX_PRINTF_ATTRIBUTE + +/* Define to best C printf format archetype, usually gnu_printf if available. + */ +#undef PG_C_PRINTF_ATTRIBUTE + /* Define to the name of a signed 128-bit integer type. */ #undef PG_INT128_TYPE @@ -833,9 +848,6 @@ /* PostgreSQL minor version number */ #undef PG_MINORVERSION_NUM -/* Define to best printf format archetype, usually gnu_printf if available. */ -#undef PG_PRINTF_ATTRIBUTE - /* Define to 1 to use to define type bool. */ #undef PG_USE_STDBOOL diff --git a/src/include/port.h b/src/include/port.h index 3f67ab18607..a3c789ae29b 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -58,6 +58,7 @@ extern void make_native_path(char *path); extern void cleanup_path(char *path); extern bool path_contains_parent_reference(const char *path); extern bool path_is_relative_and_below_cwd(const char *path); +extern bool path_is_safe_for_extraction(const char *path); extern bool path_is_prefix_of_path(const char *path1, const char *path2); extern char *make_absolute_path(const char *path); extern const char *get_progname(const char *argv0); @@ -519,6 +520,10 @@ extern bool pg_get_user_name(uid_t user_id, char *buffer, size_t buflen); extern bool pg_get_user_home_dir(uid_t user_id, char *buffer, size_t buflen); #endif +#if !HAVE_DECL_TIMINGSAFE_BCMP +extern int timingsafe_bcmp(const void *b1, const void *b2, size_t len); +#endif + extern void pg_qsort(void *base, size_t nel, size_t elsize, int (*cmp) (const void *, const void *)); extern int pg_qsort_strcmp(const void *a, const void *b); diff --git a/src/include/regex/regcustom.h b/src/include/regex/regcustom.h index 100c52d640f..fd6595b15a8 100644 --- a/src/include/regex/regcustom.h +++ b/src/include/regex/regcustom.h @@ -50,8 +50,8 @@ #include #endif +#include "common/int.h" #include "mb/pg_wchar.h" - #include "miscadmin.h" /* needed by rcancelrequested/rstacktoodeep */ @@ -60,8 +60,32 @@ #define MALLOC(n) malloc(n) #define FREE(p) free(VS(p)) #define REALLOC(p,n) realloc(VS(p),n) +#define MALLOC_ARRAY(type, n) \ + ((type *) pg_regex_malloc_array(sizeof(type), n)) +#define REALLOC_ARRAY(p, type, n) \ + ((type *) pg_regex_realloc_array(p, sizeof(type), n)) #define assert(x) Assert(x) +static inline void * +pg_regex_malloc_array(size_t s1, size_t s2) +{ + size_t req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req))) + return NULL; + return malloc(req); +} + +static inline void * +pg_regex_realloc_array(void *p, size_t s1, size_t s2) +{ + size_t req; + + if (unlikely(pg_mul_size_overflow(s1, s2, &req))) + return NULL; + return realloc(p, req); +} + /* internal character type and related */ typedef pg_wchar chr; /* the type itself */ typedef unsigned uchr; /* unsigned type that will hold a chr */ diff --git a/src/include/regex/regguts.h b/src/include/regex/regguts.h index 388f128bed5..294cb6132d8 100644 --- a/src/include/regex/regguts.h +++ b/src/include/regex/regguts.h @@ -76,6 +76,14 @@ #ifndef FREE #define FREE(p) free(VS(p)) #endif +#ifndef MALLOC_ARRAY +/* we don't depend on calloc's zeroing behavior, we do need overflow check */ +#define MALLOC_ARRAY(type, n) ((type *) calloc(sizeof(type), n)) +#endif +#ifndef REALLOC_ARRAY +/* XXX this definition does not provide the desired overflow check */ +#define REALLOC_ARRAY(p, type, n) ((type *) REALLOC(p, sizeof(type) * (n))) +#endif /* want size of a char in bits, and max value in bounded quantifiers */ #ifndef _POSIX2_RE_DUP_MAX @@ -441,6 +449,11 @@ struct cnfa * (the compacted NFA and the colormap). * The scaling here is based on an empirical measurement that very large * NFAs tend to have about 4 arcs/state. + * + * Do not raise this so high as to allow more than INT_MAX/8 states or arcs, + * or you risk integer overflows in various space allocation requests. + * (We could be more defensive in those places, but that's so far beyond the + * practical range of NFA sizes that it doesn't seem worth additional code.) */ #ifndef REG_MAX_COMPILE_SPACE #define REG_MAX_COMPILE_SPACE \ diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h index de9e7c6e73f..f63a8caaf47 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -42,8 +42,6 @@ extern void InitShmemIndex(void); extern HTAB *ShmemInitHash(const char *name, long init_size, long max_size, HASHCTL *infoP, int hash_flags); extern void *ShmemInitStruct(const char *name, Size size, bool *foundPtr); -extern Size add_size(Size s1, Size s2); -extern Size mul_size(Size s1, Size s2); /* ipci.c */ extern void RequestAddinShmemSpace(Size size); diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 1a81e8fc158..4f4106d291d 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -86,6 +86,7 @@ extern bool get_op_hash_functions(Oid opno, extern List *get_op_btree_interpretation(Oid opno); extern bool equality_ops_are_compatible(Oid opno1, Oid opno2); extern bool comparison_ops_are_compatible(Oid opno1, Oid opno2); +extern bool collations_agree_on_equality(Oid coll1, Oid coll2); extern Oid get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype, int16 procnum); extern char *get_attname(Oid relid, AttrNumber attnum, bool missing_ok); diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index 495d1af2010..cbc7bbf00cf 100644 --- a/src/include/utils/memutils.h +++ b/src/include/utils/memutils.h @@ -41,6 +41,7 @@ #define AllocSizeIsValid(size) ((Size) (size) <= MaxAllocSize) +/* Do not make this any bigger; see add_size() and mul_size() */ #define MaxAllocHugeSize (SIZE_MAX / 2) #define AllocHugeSizeIsValid(size) ((Size) (size) <= MaxAllocHugeSize) diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h index a0b62aa7b0c..b619760a192 100644 --- a/src/include/utils/palloc.h +++ b/src/include/utils/palloc.h @@ -78,8 +78,22 @@ extern void *palloc(Size size); extern void *palloc0(Size size); extern void *palloc_extended(Size size, int flags); extern pg_nodiscard void *repalloc(void *pointer, Size size); +extern pg_nodiscard void *repalloc_extended(void *pointer, + Size size, int flags); extern void pfree(void *pointer); +/* + * Support for safe calculation of memory request sizes + */ +extern Size add_size(Size s1, Size s2); +extern Size mul_size(Size s1, Size s2); +extern void *palloc_mul(Size s1, Size s2); +extern void *palloc0_mul(Size s1, Size s2); +extern void *palloc_mul_extended(Size s1, Size s2, int flags); +pg_nodiscard extern void *repalloc_mul(void *p, Size s1, Size s2); +pg_nodiscard extern void *repalloc_mul_extended(void *p, Size s1, Size s2, + int flags); + /* * Variants with easier notation and more type safety */ @@ -93,14 +107,16 @@ extern void pfree(void *pointer); /* * Allocate space for "count" objects of type "type" */ -#define palloc_array(type, count) ((type *) palloc(sizeof(type) * (count))) -#define palloc0_array(type, count) ((type *) palloc0(sizeof(type) * (count))) +#define palloc_array(type, count) ((type *) palloc_mul(sizeof(type), count)) +#define palloc0_array(type, count) ((type *) palloc0_mul(sizeof(type), count)) +#define palloc_array_extended(type, count, flags) ((type *) palloc_mul_extended(sizeof(type), count, flags)) /* * Change size of allocation pointed to by "pointer" to have space for "count" * objects of type "type" */ -#define repalloc_array(pointer, type, count) ((type *) repalloc(pointer, sizeof(type) * (count))) +#define repalloc_array(pointer, type, count) ((type *) repalloc_mul(pointer, sizeof(type), count)) +#define repalloc_array_extended(pointer, type, count, flags) ((type *) repalloc_mul_extended(pointer, sizeof(type), count, flags)) /* * The result of palloc() is always word-aligned, so we can skip testing diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c index f1898dec6a6..dc163d21fe7 100644 --- a/src/interfaces/ecpg/ecpglib/descriptor.c +++ b/src/interfaces/ecpg/ecpglib/descriptor.c @@ -482,6 +482,16 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...) memset(&stmt, 0, sizeof stmt); stmt.lineno = lineno; + /* desperate try to guess something sensible */ + stmt.connection = ecpg_get_connection(NULL); + if (stmt.connection == NULL) + { + ecpg_raise(lineno, ECPG_NO_CONN, ECPG_SQLSTATE_CONNECTION_DOES_NOT_EXIST, + ecpg_gettext("NULL")); + va_end(args); + return false; + } + /* Make sure we do NOT honor the locale for numeric input */ /* since the database gives the standard decimal point */ /* (see comments in execute.c) */ @@ -504,8 +514,6 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...) setlocale(LC_NUMERIC, "C"); #endif - /* desperate try to guess something sensible */ - stmt.connection = ecpg_get_connection(NULL); ecpg_store_result(ECPGresult, index, &stmt, &data_var); #ifdef HAVE_USELOCALE diff --git a/src/interfaces/ecpg/ecpglib/po/ru.po b/src/interfaces/ecpg/ecpglib/po/ru.po index d40783996c2..c5027bf99dd 100644 --- a/src/interfaces/ecpg/ecpglib/po/ru.po +++ b/src/interfaces/ecpg/ecpglib/po/ru.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: ecpglib (PostgreSQL current)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-08-02 11:37+0300\n" +"POT-Creation-Date: 2026-05-10 08:02+0300\n" "PO-Revision-Date: 2019-09-09 13:30+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -25,7 +25,7 @@ msgstr "пустое сообщение" msgid "" msgstr "<ПО_УМОЛЧАНИЮ>" -#: descriptor.c:876 misc.c:119 +#: descriptor.c:490 descriptor.c:884 misc.c:119 msgid "NULL" msgstr "NULL" diff --git a/src/interfaces/ecpg/ecpglib/prepare.c b/src/interfaces/ecpg/ecpglib/prepare.c index ea1146f520f..998c790c176 100644 --- a/src/interfaces/ecpg/ecpglib/prepare.c +++ b/src/interfaces/ecpg/ecpglib/prepare.c @@ -349,8 +349,12 @@ ecpg_deallocate_all_conn(int lineno, enum COMPAT_MODE c, struct connection *con) bool ECPGdeallocate_all(int lineno, int compat, const char *connection_name) { - return ecpg_deallocate_all_conn(lineno, compat, - ecpg_get_connection(connection_name)); + struct connection *con = ecpg_get_connection(connection_name); + + if (!ecpg_init(con, connection_name, lineno)) + return false; + + return ecpg_deallocate_all_conn(lineno, compat, con); } char * @@ -363,13 +367,15 @@ ecpg_prepared(const char *name, struct connection *con) } /* return the prepared statement */ -/* lineno is not used here, but kept in to not break API */ char * ECPGprepared_statement(const char *connection_name, const char *name, int lineno) { - (void) lineno; /* keep the compiler quiet */ + struct connection *con = ecpg_get_connection(connection_name); + + if (!ecpg_init(con, connection_name, lineno)) + return NULL; - return ecpg_prepared(name, ecpg_get_connection(connection_name)); + return ecpg_prepared(name, con); } /* @@ -466,10 +472,18 @@ ecpg_freeStmtCacheEntry(int lineno, int compat, con = ecpg_get_connection(entry->connection); - /* free the 'prepared_statement' list entry */ - this = ecpg_find_prepared_statement(entry->stmtID, con, &prev); - if (this && !deallocate_one(lineno, compat, con, prev, this)) - return -1; + /* + * If the connection is gone, the prepared_statement list it owned is + * already unreachable, so just skip that cleanup. We must still clear + * the cache slot below so it can be reused. + */ + if (con) + { + /* free the 'prepared_statement' list entry */ + this = ecpg_find_prepared_statement(entry->stmtID, con, &prev); + if (this && !deallocate_one(lineno, compat, con, prev, this)) + return -1; + } entry->stmtID[0] = '\0'; diff --git a/src/interfaces/ecpg/pgtypeslib/datetime.c b/src/interfaces/ecpg/pgtypeslib/datetime.c index 1b253747fc4..f43343b4594 100644 --- a/src/interfaces/ecpg/pgtypeslib/datetime.c +++ b/src/interfaces/ecpg/pgtypeslib/datetime.c @@ -335,8 +335,8 @@ PGTYPESdate_defmt_asc(date * d, const char *fmt, const char *str) */ int token[3][2]; int token_values[3] = {-1, -1, -1}; - char *fmt_token_order; - char *fmt_ystart, + const char *fmt_token_order; + const char *fmt_ystart, *fmt_mstart, *fmt_dstart; unsigned int i; diff --git a/src/interfaces/ecpg/test/connect/.gitignore b/src/interfaces/ecpg/test/connect/.gitignore index e0639f3c8d1..02236847444 100644 --- a/src/interfaces/ecpg/test/connect/.gitignore +++ b/src/interfaces/ecpg/test/connect/.gitignore @@ -8,3 +8,5 @@ /test4.c /test5 /test5.c +/test6 +/test6.c diff --git a/src/interfaces/ecpg/test/connect/Makefile b/src/interfaces/ecpg/test/connect/Makefile index 2602d5d286f..17fa2667bf7 100644 --- a/src/interfaces/ecpg/test/connect/Makefile +++ b/src/interfaces/ecpg/test/connect/Makefile @@ -7,6 +7,7 @@ TESTS = test1 test1.c \ test2 test2.c \ test3 test3.c \ test4 test4.c \ - test5 test5.c + test5 test5.c \ + test6 test6.c all: $(TESTS) diff --git a/src/interfaces/ecpg/test/connect/test6.pgc b/src/interfaces/ecpg/test/connect/test6.pgc new file mode 100644 index 00000000000..d2c10dffb03 --- /dev/null +++ b/src/interfaces/ecpg/test/connect/test6.pgc @@ -0,0 +1,68 @@ +/* + * This test verifies that ecpg functions properly handle NULL connections + * (i.e., when a connection name doesn't exist or has been disconnected). + * Before the fix, these operations would cause a segmentation fault. + */ + +#include +#include +#include + +exec sql include ../regression; + +int +main(void) +{ +exec sql begin declare section; + int val1output = 2; + int val1 = 1; + char val2[6] = "data1"; + char *stmt1 = "SELECT * from test1 where a = $1 and b = $2"; +exec sql end declare section; + + ECPGdebug(1, stderr); + + /* Connect to the database */ + exec sql connect to REGRESSDB1 as myconn; + + /* Test 1: Try to get descriptor on a disconnected connection */ + printf("Test 1: Try to get descriptor on a disconnected connection\n"); + exec sql create table test1 (a int, b text); + exec sql insert into test1 (a,b) values (1, 'data1'); + + exec sql allocate descriptor indesc; + exec sql allocate descriptor outdesc; + + exec sql prepare foo2 from :stmt1; + + exec sql set descriptor indesc value 1 DATA = :val1; + exec sql set descriptor indesc value 2 DATA = :val2; + + exec sql execute foo2 using sql descriptor indesc into sql descriptor outdesc; + + exec sql rollback; + exec sql disconnect; + exec sql get descriptor outdesc value 1 :val1output = DATA; + printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode); + + /* Test 2: Try to deallocate all on a non-existent connection */ + printf("Test 2: deallocate all with non-existent connection\n"); + exec sql at nonexistent deallocate all; + printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode); + + /* Test 3: deallocate on disconnected connection */ + printf("Test 3: deallocate all on disconnected connection\n"); + exec sql deallocate all; + printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode); + + /* Test 4: Use prepared statement from non-existent connection */ + printf("Test 4: Use prepared statement from non-existent connection\n"); + exec sql at nonexistent prepare stmt1 FROM "SELECT 1"; + exec sql at nonexistent declare cur1 cursor for stmt1; + exec sql at nonexistent open cur1; + printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode); + + printf("All tests completed !\n"); + + return 0; +} diff --git a/src/interfaces/ecpg/test/ecpg_schedule b/src/interfaces/ecpg/test/ecpg_schedule index 363eced2dfb..c5d5939bd46 100644 --- a/src/interfaces/ecpg/test/ecpg_schedule +++ b/src/interfaces/ecpg/test/ecpg_schedule @@ -13,6 +13,7 @@ test: connect/test2 test: connect/test3 test: connect/test4 test: connect/test5 +test: connect/test6 test: pgtypeslib/dt_test test: pgtypeslib/dt_test2 test: pgtypeslib/num_test diff --git a/src/interfaces/ecpg/test/expected/connect-test6.c b/src/interfaces/ecpg/test/expected/connect-test6.c new file mode 100644 index 00000000000..eed3c46a38c --- /dev/null +++ b/src/interfaces/ecpg/test/expected/connect-test6.c @@ -0,0 +1,146 @@ +/* Processed by ecpg (regression mode) */ +/* These include files are added by the preprocessor */ +#include +#include +#include +/* End of automatic include section */ +#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y)) + +#line 1 "test6.pgc" +/* + * This test verifies that ecpg functions properly handle NULL connections + * (i.e., when a connection name doesn't exist or has been disconnected). + * Before the fix, these operations would cause a segmentation fault. + */ + +#include +#include +#include + + +#line 1 "regression.h" + + + + + + +#line 11 "test6.pgc" + + +int +main(void) +{ +/* exec sql begin declare section */ + + + + + +#line 17 "test6.pgc" + int val1output = 2 ; + +#line 18 "test6.pgc" + int val1 = 1 ; + +#line 19 "test6.pgc" + char val2 [ 6 ] = "data1" ; + +#line 20 "test6.pgc" + char * stmt1 = "SELECT * from test1 where a = $1 and b = $2" ; +/* exec sql end declare section */ +#line 21 "test6.pgc" + + + ECPGdebug(1, stderr); + + /* Connect to the database */ + { ECPGconnect(__LINE__, 0, "ecpg1_regression" , NULL, NULL , "myconn", 0); } +#line 26 "test6.pgc" + + + /* Test 1: Try to get descriptor on a disconnected connection */ + printf("Test 1: Try to get descriptor on a disconnected connection\n"); + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "create table test1 ( a int , b text )", ECPGt_EOIT, ECPGt_EORT);} +#line 30 "test6.pgc" + + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "insert into test1 ( a , b ) values ( 1 , 'data1' )", ECPGt_EOIT, ECPGt_EORT);} +#line 31 "test6.pgc" + + + ECPGallocate_desc(__LINE__, "indesc"); +#line 33 "test6.pgc" + + ECPGallocate_desc(__LINE__, "outdesc"); +#line 34 "test6.pgc" + + + { ECPGprepare(__LINE__, NULL, 0, "foo2", stmt1);} +#line 36 "test6.pgc" + + + { ECPGset_desc(__LINE__, "indesc", 1,ECPGd_data, + ECPGt_int,&(val1),(long)1,(long)1,sizeof(int), ECPGd_EODT); +} +#line 38 "test6.pgc" + + { ECPGset_desc(__LINE__, "indesc", 2,ECPGd_data, + ECPGt_char,(val2),(long)6,(long)1,(6)*sizeof(char), ECPGd_EODT); +} +#line 39 "test6.pgc" + + + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_execute, "foo2", + ECPGt_descriptor, "indesc", 1L, 1L, 1L, + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, + ECPGt_descriptor, "outdesc", 1L, 1L, 1L, + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);} +#line 41 "test6.pgc" + + + { ECPGtrans(__LINE__, NULL, "rollback");} +#line 43 "test6.pgc" + + { ECPGdisconnect(__LINE__, "CURRENT");} +#line 44 "test6.pgc" + + { ECPGget_desc(__LINE__, "outdesc", 1,ECPGd_data, + ECPGt_int,&(val1output),(long)1,(long)1,sizeof(int), ECPGd_EODT); +} +#line 45 "test6.pgc" + + printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode); + + /* Test 2: Try to deallocate all on a non-existent connection */ + printf("Test 2: deallocate all with non-existent connection\n"); + { ECPGdeallocate_all(__LINE__, 0, "nonexistent");} +#line 50 "test6.pgc" + + printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode); + + /* Test 3: deallocate on disconnected connection */ + printf("Test 3: deallocate all on disconnected connection\n"); + { ECPGdeallocate_all(__LINE__, 0, NULL);} +#line 55 "test6.pgc" + + printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode); + + /* Test 4: Use prepared statement from non-existent connection */ + printf("Test 4: Use prepared statement from non-existent connection\n"); + { ECPGprepare(__LINE__, "nonexistent", 0, "stmt1", "SELECT 1");} +#line 60 "test6.pgc" + + /* declare cur1 cursor for $1 */ +#line 61 "test6.pgc" + + { ECPGdo(__LINE__, 0, 1, "nonexistent", 0, ECPGst_normal, "declare cur1 cursor for $1", + ECPGt_char_variable,(ECPGprepared_statement("nonexistent", "stmt1", __LINE__)),(long)1,(long)1,(1)*sizeof(char), + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT);} +#line 62 "test6.pgc" + + printf("sqlca.sqlcode = %ld\n", sqlca.sqlcode); + + printf("All tests completed !\n"); + + return 0; +} diff --git a/src/interfaces/ecpg/test/expected/connect-test6.stderr b/src/interfaces/ecpg/test/expected/connect-test6.stderr new file mode 100644 index 00000000000..8784d5a9d40 --- /dev/null +++ b/src/interfaces/ecpg/test/expected/connect-test6.stderr @@ -0,0 +1,50 @@ +[NO_PID]: ECPGdebug: set to 1 +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ECPGconnect: opening database ecpg1_regression on port +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 30: query: create table test1 ( a int , b text ); with 0 parameter(s) on connection myconn +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 30: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_process_output on line 30: OK: CREATE TABLE +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 31: query: insert into test1 ( a , b ) values ( 1 , 'data1' ); with 0 parameter(s) on connection myconn +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 31: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_process_output on line 31: OK: INSERT 0 1 +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: prepare_common on line 36: name foo2; query: "SELECT * from test1 where a = $1 and b = $2" +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 41: query: SELECT * from test1 where a = $1 and b = $2; with 2 parameter(s) on connection myconn +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 41: using PQexecPrepared for "SELECT * from test1 where a = $1 and b = $2" +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_free_params on line 41: parameter 1 = 1 +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_free_params on line 41: parameter 2 = data1 +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_process_output on line 41: correctly got 1 tuples with 2 fields +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_process_output on line 41: putting result (1 tuples) into descriptor outdesc +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ECPGtrans on line 43: action "rollback"; connection "myconn" +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: deallocate_one on line 0: name foo2 +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_finish: connection myconn closed +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ECPGget_desc: reading items for tuple 1 +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: raising sqlcode -220 on line 45: connection "NULL" does not exist on line 45 +[NO_PID]: sqlca: code: -220, state: 08003 +[NO_PID]: raising sqlcode -220 on line 50: connection "nonexistent" does not exist on line 50 +[NO_PID]: sqlca: code: -220, state: 08003 +[NO_PID]: raising sqlcode -220 on line 55: connection "NULL" does not exist on line 55 +[NO_PID]: sqlca: code: -220, state: 08003 +[NO_PID]: raising sqlcode -220 on line 60: connection "nonexistent" does not exist on line 60 +[NO_PID]: sqlca: code: -220, state: 08003 +[NO_PID]: raising sqlcode -220 on line 63: connection "nonexistent" does not exist on line 63 +[NO_PID]: sqlca: code: -220, state: 08003 +[NO_PID]: raising sqlcode -220 on line 62: connection "nonexistent" does not exist on line 62 +[NO_PID]: sqlca: code: -220, state: 08003 diff --git a/src/interfaces/ecpg/test/expected/connect-test6.stdout b/src/interfaces/ecpg/test/expected/connect-test6.stdout new file mode 100644 index 00000000000..bf9a2e91051 --- /dev/null +++ b/src/interfaces/ecpg/test/expected/connect-test6.stdout @@ -0,0 +1,9 @@ +Test 1: Try to get descriptor on a disconnected connection +sqlca.sqlcode = -220 +Test 2: deallocate all with non-existent connection +sqlca.sqlcode = -220 +Test 3: deallocate all on disconnected connection +sqlca.sqlcode = -220 +Test 4: Use prepared statement from non-existent connection +sqlca.sqlcode = -220 +All tests completed ! diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c index cd66e9757ba..a06218f9d77 100644 --- a/src/interfaces/libpq/fe-auth-scram.c +++ b/src/interfaces/libpq/fe-auth-scram.c @@ -648,7 +648,7 @@ read_server_first_message(fe_scram_state *state, char *input) /* Verify immediately that the server used our part of the nonce */ if (strlen(nonce) < strlen(state->client_nonce) || - memcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0) + timingsafe_bcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0) { appendPQExpBufferStr(&conn->errorMessage, libpq_gettext("invalid SCRAM response (nonce mismatch)\n")); @@ -901,7 +901,8 @@ verify_server_signature(fe_scram_state *state, bool *match, pg_hmac_free(ctx); /* signature processed, so now check after it */ - if (memcmp(expected_ServerSignature, state->ServerSignature, SCRAM_KEY_LEN) != 0) + if (timingsafe_bcmp(expected_ServerSignature, state->ServerSignature, + SCRAM_KEY_LEN) != 0) *match = false; else *match = true; diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index b2331699bed..0eb5ceeaa69 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -24,6 +24,7 @@ #include #endif +#include "common/int.h" #include "libpq-fe.h" #include "libpq-int.h" #include "mb/pg_wchar.h" @@ -2893,6 +2894,20 @@ PQfn(PGconn *conn, int result_is_int, const PQArgBlock *args, int nargs) +{ + return PQnfn(conn, fnid, result_buf, -1, result_len, + result_is_int, args, nargs); +} + +/* + * PQnfn + * Private version of PQfn() with verification that returned data fits in + * result_buf when result_is_int == 0. Setting buf_size to -1 disables + * this verification. + */ +PGresult * +PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len, + int result_is_int, const PQArgBlock *args, int nargs) { *result_len = 0; @@ -2924,7 +2939,7 @@ PQfn(PGconn *conn, } return pqFunctionCall3(conn, fnid, - result_buf, result_len, + result_buf, buf_size, result_len, result_is_int, args, nargs); } @@ -4113,27 +4128,6 @@ PQescapeString(char *to, const char *from, size_t length) } -/* - * Frontend version of the backend's add_size(), intended to be API-compatible - * with the pg_add_*_overflow() helpers. Stores the result into *dst on success. - * Returns true instead if the addition overflows. - * - * TODO: move to common/int.h - */ -static bool -add_size_overflow(size_t s1, size_t s2, size_t *dst) -{ - size_t result; - - result = s1 + s2; - if (result < s1 || result < s2) - return true; - - *dst = result; - return false; -} - - /* * Escape arbitrary strings. If as_ident is true, we escape the result * as an identifier; if false, as a literal. The result is returned in @@ -4218,14 +4212,14 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident) * Allocate output buffer. Protect against overflow, in case the caller * has allocated a large fraction of the available size_t. */ - if (add_size_overflow(input_len, num_quotes, &result_size) || - add_size_overflow(result_size, 3, &result_size)) /* two quotes plus a NUL */ + if (pg_add_size_overflow(input_len, num_quotes, &result_size) || + pg_add_size_overflow(result_size, 3, &result_size)) /* two quotes plus a NUL */ goto overflow; if (!as_ident && num_backslashes > 0) { - if (add_size_overflow(result_size, num_backslashes, &result_size) || - add_size_overflow(result_size, 2, &result_size)) /* for " E" prefix */ + if (pg_add_size_overflow(result_size, num_backslashes, &result_size) || + pg_add_size_overflow(result_size, 2, &result_size)) /* for " E" prefix */ goto overflow; } @@ -4388,9 +4382,9 @@ PQescapeByteaInternal(PGconn *conn, if (use_hex) { /* We prepend "\x" and double each input character. */ - if (add_size_overflow(len, bslash_len + 1, &len) || - add_size_overflow(len, from_length, &len) || - add_size_overflow(len, from_length, &len)) + if (pg_add_size_overflow(len, bslash_len + 1, &len) || + pg_add_size_overflow(len, from_length, &len) || + pg_add_size_overflow(len, from_length, &len)) goto overflow; } else @@ -4400,22 +4394,22 @@ PQescapeByteaInternal(PGconn *conn, { if (*vp < 0x20 || *vp > 0x7e) { - if (add_size_overflow(len, bslash_len + 3, &len)) /* octal "\ooo" */ + if (pg_add_size_overflow(len, bslash_len + 3, &len)) /* octal "\ooo" */ goto overflow; } else if (*vp == '\'') { - if (add_size_overflow(len, 2, &len)) /* double each quote */ + if (pg_add_size_overflow(len, 2, &len)) /* double each quote */ goto overflow; } else if (*vp == '\\') { - if (add_size_overflow(len, bslash_len * 2, &len)) /* double each backslash */ + if (pg_add_size_overflow(len, bslash_len * 2, &len)) /* double each backslash */ goto overflow; } else { - if (add_size_overflow(len, 1, &len)) + if (pg_add_size_overflow(len, 1, &len)) goto overflow; } } diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 075a5ed85bc..51c84bbc8d6 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -275,8 +275,8 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) argv[1].len = 4; argv[1].u.integer = (int) len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, &result_len, 0, argv, 2); + res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, + (void *) buf, len, &result_len, 0, argv, 2); if (PQresultStatus(res) == PGRES_COMMAND_OK) { PQclear(res); @@ -418,8 +418,8 @@ lo_lseek64(PGconn *conn, int fd, pg_int64 offset, int whence) argv[2].len = 4; argv[2].u.integer = whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, &result_len, 0, argv, 3); + res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, + (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) { PQclear(res); @@ -574,8 +574,8 @@ lo_tell64(PGconn *conn, int fd) argv[0].len = 4; argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, &result_len, 0, argv, 1); + res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, + (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) { PQclear(res); diff --git a/src/interfaces/libpq/fe-print.c b/src/interfaces/libpq/fe-print.c index 75ef502235c..ffa04013402 100644 --- a/src/interfaces/libpq/fe-print.c +++ b/src/interfaces/libpq/fe-print.c @@ -33,6 +33,7 @@ #endif #endif +#include "common/int.h" #include "libpq-fe.h" #include "libpq-int.h" @@ -468,27 +469,6 @@ do_field(const PQprintOpt *po, const PGresult *res, } -/* - * Frontend version of the backend's add_size(), intended to be API-compatible - * with the pg_add_*_overflow() helpers. Stores the result into *dst on success. - * Returns true instead if the addition overflows. - * - * TODO: move to common/int.h - */ -static bool -add_size_overflow(size_t s1, size_t s2, size_t *dst) -{ - size_t result; - - result = s1 + s2; - if (result < s1 || result < s2) - return true; - - *dst = result; - return false; -} - - static char * do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax, const char **fieldNames, unsigned char *fieldNotNum, @@ -509,20 +489,20 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax, for (; n < nFields; n++) { /* Field plus separator, plus 2 extra '-' in standard format. */ - if (add_size_overflow(tot, fieldMax[n], &tot) || - add_size_overflow(tot, fs_len, &tot) || - (po->standard && add_size_overflow(tot, 2, &tot))) + if (pg_add_size_overflow(tot, fieldMax[n], &tot) || + pg_add_size_overflow(tot, fs_len, &tot) || + (po->standard && pg_add_size_overflow(tot, 2, &tot))) goto overflow; } if (po->standard) { /* An extra separator at the front and back. */ - if (add_size_overflow(tot, fs_len, &tot) || - add_size_overflow(tot, fs_len, &tot) || - add_size_overflow(tot, 2, &tot)) + if (pg_add_size_overflow(tot, fs_len, &tot) || + pg_add_size_overflow(tot, fs_len, &tot) || + pg_add_size_overflow(tot, 2, &tot)) goto overflow; } - if (add_size_overflow(tot, 1, &tot)) /* terminator */ + if (pg_add_size_overflow(tot, 1, &tot)) /* terminator */ goto overflow; border = malloc(tot); diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 0b710be5a97..e85975d944f 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -27,6 +27,7 @@ #endif #endif +#include "common/int.h" #include "libpq-fe.h" #include "libpq-int.h" #include "mb/pg_wchar.h" @@ -1960,7 +1961,7 @@ pqEndcopy3(PGconn *conn) */ PGresult * pqFunctionCall3(PGconn *conn, Oid fnid, - int *result_buf, int *actual_result_len, + int *result_buf, int buf_size, int *actual_result_len, int result_is_int, const PQArgBlock *args, int nargs) { @@ -2094,6 +2095,18 @@ pqFunctionCall3(PGconn *conn, Oid fnid, } else { + /* + * If the server returned too much data for the + * buffer, something fishy is going on. Abandon ship. + */ + if (buf_size != -1 && *actual_result_len > buf_size) + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("server returned too much data\n")); + handleSyncLoss(conn, id, *actual_result_len); + return pqPrepareAsyncResult(conn); + } + if (pqGetnchar((char *) result_buf, *actual_result_len, conn)) @@ -2212,26 +2225,6 @@ pqBuildStartupPacket3(PGconn *conn, int *packetlen, return startpacket; } -/* - * Frontend version of the backend's add_size(), intended to be API-compatible - * with the pg_add_*_overflow() helpers. Stores the result into *dst on success. - * Returns true instead if the addition overflows. - * - * TODO: move to common/int.h - */ -static bool -add_size_overflow(size_t s1, size_t s2, size_t *dst) -{ - size_t result; - - result = s1 + s2; - if (result < s1 || result < s2) - return true; - - *dst = result; - return false; -} - /* * Build a startup packet given a filled-in PGconn structure. * @@ -2264,11 +2257,11 @@ build_startup_packet(const PGconn *conn, char *packet, do { \ if (packet) \ strcpy(packet + packet_len, optname); \ - if (add_size_overflow(packet_len, strlen(optname) + 1, &packet_len)) \ + if (pg_add_size_overflow(packet_len, strlen(optname) + 1, &packet_len)) \ return 0; \ if (packet) \ strcpy(packet + packet_len, optval); \ - if (add_size_overflow(packet_len, strlen(optval) + 1, &packet_len)) \ + if (pg_add_size_overflow(packet_len, strlen(optval) + 1, &packet_len)) \ return 0; \ } while(0) @@ -2304,7 +2297,7 @@ build_startup_packet(const PGconn *conn, char *packet, /* Add trailing terminator */ if (packet) packet[packet_len] = '\0'; - if (add_size_overflow(packet_len, 1, &packet_len)) + if (pg_add_size_overflow(packet_len, 1, &packet_len)) return 0; return packet_len; diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index bfa5f438b35..0456c04857e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -686,6 +686,9 @@ extern int pqRowProcessor(PGconn *conn, const char **errmsgp); extern void pqCommandQueueAdvance(PGconn *conn, bool isReadyForQuery, bool gotSync); extern int PQsendQueryContinue(PGconn *conn, const char *query); +extern PGresult *PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, + int *result_len, int result_is_int, + const PQArgBlock *args, int nargs); /* === in fe-protocol3.c === */ @@ -700,7 +703,8 @@ extern int pqGetline3(PGconn *conn, char *s, int maxlen); extern int pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize); extern int pqEndcopy3(PGconn *conn); extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid, - int *result_buf, int *actual_result_len, + int *result_buf, int buf_size, + int *actual_result_len, int result_is_int, const PQArgBlock *args, int nargs); diff --git a/src/interfaces/libpq/po/fr.po b/src/interfaces/libpq/po/fr.po index d6d00787936..33c85fba84e 100644 --- a/src/interfaces/libpq/po/fr.po +++ b/src/interfaces/libpq/po/fr.po @@ -12,8 +12,8 @@ msgid "" msgstr "" "Project-Id-Version: PostgreSQL 15\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-02-15 22:15+0000\n" -"PO-Revision-Date: 2025-02-16 09:12+0100\n" +"POT-Creation-Date: 2026-03-19 23:20+0000\n" +"PO-Revision-Date: 2026-03-22 17:44+0100\n" "Last-Translator: Guillaume Lelarge \n" "Language-Team: French \n" "Language: fr\n" @@ -21,7 +21,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 3.5\n" +"X-Generator: Poedit 3.8\n" #: ../../port/thread.c:100 ../../port/thread.c:136 #, c-format @@ -72,16 +72,17 @@ msgstr "n'a pas pu générer le nonce\n" #: fe-auth-scram.c:636 fe-auth-scram.c:662 fe-auth-scram.c:677 #: fe-auth-scram.c:727 fe-auth-scram.c:766 fe-auth.c:290 fe-auth.c:362 #: fe-auth.c:398 fe-auth.c:623 fe-auth.c:799 fe-auth.c:1152 fe-auth.c:1322 -#: fe-connect.c:909 fe-connect.c:1458 fe-connect.c:1627 fe-connect.c:2978 -#: fe-connect.c:4827 fe-connect.c:5088 fe-connect.c:5207 fe-connect.c:5459 -#: fe-connect.c:5540 fe-connect.c:5639 fe-connect.c:5895 fe-connect.c:5924 -#: fe-connect.c:5996 fe-connect.c:6020 fe-connect.c:6038 fe-connect.c:6139 -#: fe-connect.c:6148 fe-connect.c:6506 fe-connect.c:6656 fe-connect.c:6922 -#: fe-exec.c:710 fe-exec.c:978 fe-exec.c:1326 fe-exec.c:3165 fe-exec.c:3357 -#: fe-exec.c:4197 fe-exec.c:4364 fe-gssapi-common.c:111 fe-lobj.c:884 -#: fe-protocol3.c:968 fe-protocol3.c:983 fe-protocol3.c:1016 -#: fe-protocol3.c:1724 fe-protocol3.c:2127 fe-secure-common.c:112 -#: fe-secure-gssapi.c:500 fe-secure-openssl.c:455 fe-secure-openssl.c:1252 +#: fe-connect.c:910 fe-connect.c:1459 fe-connect.c:1628 fe-connect.c:2979 +#: fe-connect.c:4828 fe-connect.c:5103 fe-connect.c:5222 fe-connect.c:5474 +#: fe-connect.c:5555 fe-connect.c:5654 fe-connect.c:5910 fe-connect.c:5939 +#: fe-connect.c:6011 fe-connect.c:6035 fe-connect.c:6053 fe-connect.c:6154 +#: fe-connect.c:6163 fe-connect.c:6521 fe-connect.c:6671 fe-connect.c:6939 +#: fe-exec.c:715 fe-exec.c:983 fe-exec.c:1331 fe-exec.c:3170 fe-exec.c:3362 +#: fe-exec.c:4236 fe-exec.c:4430 fe-gssapi-common.c:111 fe-lobj.c:884 +#: fe-protocol3.c:969 fe-protocol3.c:984 fe-protocol3.c:1017 +#: fe-protocol3.c:1738 fe-protocol3.c:2141 fe-secure-common.c:112 +#: fe-secure-gssapi.c:512 fe-secure-gssapi.c:687 fe-secure-openssl.c:455 +#: fe-secure-openssl.c:1252 msgid "out of memory\n" msgstr "mémoire épuisée\n" @@ -127,8 +128,8 @@ msgstr "message SCRAM malformé (problème à la fin du server-final-message)\n" msgid "malformed SCRAM message (invalid server signature)\n" msgstr "message SCRAM malformé (signature serveur invalide)\n" -#: fe-auth-scram.c:935 fe-exec.c:527 fe-protocol3.c:207 fe-protocol3.c:232 -#: fe-protocol3.c:256 fe-protocol3.c:274 fe-protocol3.c:355 fe-protocol3.c:728 +#: fe-auth-scram.c:935 fe-exec.c:527 fe-protocol3.c:208 fe-protocol3.c:233 +#: fe-protocol3.c:257 fe-protocol3.c:275 fe-protocol3.c:356 fe-protocol3.c:729 msgid "out of memory" msgstr "mémoire épuisée" @@ -270,387 +271,392 @@ msgstr "la valeur de password_encryption est trop longue\n" msgid "unrecognized password encryption algorithm \"%s\"\n" msgstr "algorithme de chiffrement du mot de passe « %s » non reconnu\n" -#: fe-connect.c:1092 +#: fe-connect.c:1093 #, c-format msgid "could not match %d host names to %d hostaddr values\n" msgstr "n'a pas pu faire correspondre les %d noms d'hôte aux %d valeurs hostaddr\n" -#: fe-connect.c:1178 +#: fe-connect.c:1179 #, c-format msgid "could not match %d port numbers to %d hosts\n" msgstr "n'a pas pu faire correspondre les %d numéros de port aux %d hôtes\n" -#: fe-connect.c:1271 fe-connect.c:1297 fe-connect.c:1339 fe-connect.c:1348 -#: fe-connect.c:1381 fe-connect.c:1425 +#: fe-connect.c:1272 fe-connect.c:1298 fe-connect.c:1340 fe-connect.c:1349 +#: fe-connect.c:1382 fe-connect.c:1426 #, c-format msgid "invalid %s value: \"%s\"\n" msgstr "valeur %s invalide : « %s »\n" -#: fe-connect.c:1318 +#: fe-connect.c:1319 #, c-format msgid "sslmode value \"%s\" invalid when SSL support is not compiled in\n" msgstr "valeur sslmode « %s » invalide si le support SSL n'est pas compilé initialement\n" -#: fe-connect.c:1366 +#: fe-connect.c:1367 msgid "invalid SSL protocol version range\n" msgstr "intervalle de version invalide pour le protocole SSL\n" -#: fe-connect.c:1391 +#: fe-connect.c:1392 #, c-format msgid "gssencmode value \"%s\" invalid when GSSAPI support is not compiled in\n" msgstr "valeur gssencmode « %s » invalide si le support GSSAPI n'est pas compilé\n" -#: fe-connect.c:1651 +#: fe-connect.c:1652 #, c-format msgid "could not set socket to TCP no delay mode: %s\n" msgstr "n'a pas pu activer le mode TCP sans délai pour la socket : %s\n" -#: fe-connect.c:1713 +#: fe-connect.c:1714 #, c-format msgid "connection to server on socket \"%s\" failed: " msgstr "la connexion au serveur sur le socket « %s » a échoué : " -#: fe-connect.c:1740 +#: fe-connect.c:1741 #, c-format msgid "connection to server at \"%s\" (%s), port %s failed: " msgstr "la connexion au serveur sur « %s » (%s), port %s a échoué : " -#: fe-connect.c:1745 +#: fe-connect.c:1746 #, c-format msgid "connection to server at \"%s\", port %s failed: " msgstr "la connexion au serveur sur « %s », port %s a échoué : " -#: fe-connect.c:1770 +#: fe-connect.c:1771 msgid "\tIs the server running locally and accepting connections on that socket?\n" msgstr "\tLe serveur est-il actif localement et accepte-t-il les connexions sur ce socket ?\n" -#: fe-connect.c:1774 +#: fe-connect.c:1775 msgid "\tIs the server running on that host and accepting TCP/IP connections?\n" msgstr "\tLe serveur est-il actif sur cet hôte et accepte-t-il les connexions ?\n" -#: fe-connect.c:1838 +#: fe-connect.c:1839 #, c-format msgid "invalid integer value \"%s\" for connection option \"%s\"\n" msgstr "valeur entière « %s » invalide pour l'option de connexion « %s »\n" -#: fe-connect.c:1868 fe-connect.c:1903 fe-connect.c:1939 fe-connect.c:2039 -#: fe-connect.c:2652 +#: fe-connect.c:1869 fe-connect.c:1904 fe-connect.c:1940 fe-connect.c:2040 +#: fe-connect.c:2653 #, c-format msgid "%s(%s) failed: %s\n" msgstr "échec de %s(%s) : %s\n" -#: fe-connect.c:2004 +#: fe-connect.c:2005 #, c-format msgid "%s(%s) failed: error code %d\n" msgstr "échec de %s(%s) : code d'erreur %d\n" -#: fe-connect.c:2319 +#: fe-connect.c:2320 msgid "invalid connection state, probably indicative of memory corruption\n" msgstr "état de connexion invalide, indique probablement une corruption de mémoire\n" -#: fe-connect.c:2398 +#: fe-connect.c:2399 #, c-format msgid "invalid port number: \"%s\"\n" msgstr "numéro de port invalide : « %s »\n" -#: fe-connect.c:2414 +#: fe-connect.c:2415 #, c-format msgid "could not translate host name \"%s\" to address: %s\n" msgstr "n'a pas pu traduire le nom d'hôte « %s » en adresse : %s\n" -#: fe-connect.c:2427 +#: fe-connect.c:2428 #, c-format msgid "could not parse network address \"%s\": %s\n" msgstr "n'a pas pu analyser l'adresse réseau « %s » : %s\n" -#: fe-connect.c:2440 +#: fe-connect.c:2441 #, c-format msgid "Unix-domain socket path \"%s\" is too long (maximum %d bytes)\n" msgstr "Le chemin du socket de domaine Unix, « %s », est trop (maximum %d octets)\n" -#: fe-connect.c:2455 +#: fe-connect.c:2456 #, c-format msgid "could not translate Unix-domain socket path \"%s\" to address: %s\n" msgstr "" "n'a pas pu traduire le chemin de la socket du domaine Unix « %s » en adresse :\n" "%s\n" -#: fe-connect.c:2581 +#: fe-connect.c:2582 #, c-format msgid "could not create socket: %s\n" msgstr "n'a pas pu créer la socket : %s\n" -#: fe-connect.c:2612 +#: fe-connect.c:2613 #, c-format msgid "could not set socket to nonblocking mode: %s\n" msgstr "n'a pas pu activer le mode non-bloquant pour la socket : %s\n" -#: fe-connect.c:2622 +#: fe-connect.c:2623 #, c-format msgid "could not set socket to close-on-exec mode: %s\n" msgstr "n'a pas pu paramétrer la socket en mode close-on-exec : %s\n" -#: fe-connect.c:2780 +#: fe-connect.c:2781 #, c-format msgid "could not get socket error status: %s\n" msgstr "n'a pas pu déterminer le statut d'erreur de la socket : %s\n" -#: fe-connect.c:2808 +#: fe-connect.c:2809 #, c-format msgid "could not get client address from socket: %s\n" msgstr "n'a pas pu obtenir l'adresse du client depuis la socket : %s\n" -#: fe-connect.c:2847 +#: fe-connect.c:2848 msgid "requirepeer parameter is not supported on this platform\n" msgstr "le paramètre requirepeer n'est pas supporté sur cette plateforme\n" -#: fe-connect.c:2850 +#: fe-connect.c:2851 #, c-format msgid "could not get peer credentials: %s\n" msgstr "n'a pas pu obtenir l'authentification de l'autre : %s\n" -#: fe-connect.c:2864 +#: fe-connect.c:2865 #, c-format msgid "requirepeer specifies \"%s\", but actual peer user name is \"%s\"\n" msgstr "requirepeer indique « %s » mais le nom de l'utilisateur réel est « %s »\n" -#: fe-connect.c:2906 +#: fe-connect.c:2907 #, c-format msgid "could not send GSSAPI negotiation packet: %s\n" msgstr "n'a pas pu transmettre le paquet de négociation GSSAPI : %s\n" -#: fe-connect.c:2918 +#: fe-connect.c:2919 msgid "GSSAPI encryption required but was impossible (possibly no credential cache, no server support, or using a local socket)\n" msgstr "le chiffrage avec GSSAPI était requis, mais impossible (potentiellement pas de cache, de support serveur ou de socket local)\n" -#: fe-connect.c:2960 +#: fe-connect.c:2961 #, c-format msgid "could not send SSL negotiation packet: %s\n" msgstr "n'a pas pu transmettre le paquet de négociation SSL : %s\n" -#: fe-connect.c:2991 +#: fe-connect.c:2992 #, c-format msgid "could not send startup packet: %s\n" msgstr "n'a pas pu transmettre le paquet de démarrage : %s\n" -#: fe-connect.c:3067 +#: fe-connect.c:3068 msgid "server does not support SSL, but SSL was required\n" msgstr "le serveur ne supporte pas SSL alors que SSL était réclamé\n" -#: fe-connect.c:3085 +#: fe-connect.c:3086 msgid "server sent an error response during SSL exchange\n" msgstr "le serveur a envoyé une erreur lors de l'échange SSL\n" -#: fe-connect.c:3091 +#: fe-connect.c:3092 #, c-format msgid "received invalid response to SSL negotiation: %c\n" msgstr "a reçu une réponse invalide à la négociation SSL : %c\n" -#: fe-connect.c:3112 +#: fe-connect.c:3113 msgid "received unencrypted data after SSL response\n" msgstr "a reçu des données non chiffrées après la réponse SSL\n" -#: fe-connect.c:3193 +#: fe-connect.c:3194 msgid "server doesn't support GSSAPI encryption, but it was required\n" msgstr "le serveur ne supporte pas le chiffrage GSSAPI alors qu'il était réclamé\n" -#: fe-connect.c:3205 +#: fe-connect.c:3206 #, c-format msgid "received invalid response to GSSAPI negotiation: %c\n" msgstr "a reçu une réponse invalide à la négociation GSSAPI : %c\n" -#: fe-connect.c:3224 +#: fe-connect.c:3225 msgid "received unencrypted data after GSSAPI encryption response\n" msgstr "a reçu des données non chiffrées après la réponse de chiffrement GSSAPI\n" -#: fe-connect.c:3289 fe-connect.c:3314 +#: fe-connect.c:3290 fe-connect.c:3315 #, c-format msgid "expected authentication request from server, but received %c\n" msgstr "" "attendait une requête d'authentification en provenance du serveur, mais a\n" " reçu %c\n" -#: fe-connect.c:3521 +#: fe-connect.c:3522 msgid "unexpected message from server during startup\n" msgstr "message inattendu du serveur lors du démarrage\n" -#: fe-connect.c:3613 +#: fe-connect.c:3614 msgid "session is read-only\n" msgstr "la session est en lecture seule\n" -#: fe-connect.c:3616 +#: fe-connect.c:3617 msgid "session is not read-only\n" msgstr "la session n'est pas en lecture seule\n" -#: fe-connect.c:3670 +#: fe-connect.c:3671 msgid "server is in hot standby mode\n" msgstr "le serveur est dans le mode hot standby\n" -#: fe-connect.c:3673 +#: fe-connect.c:3674 msgid "server is not in hot standby mode\n" msgstr "le serveur n'est pas dans le mode hot standby\n" -#: fe-connect.c:3791 fe-connect.c:3843 +#: fe-connect.c:3792 fe-connect.c:3844 #, c-format msgid "\"%s\" failed\n" msgstr "échec de « %s »\n" -#: fe-connect.c:3857 +#: fe-connect.c:3858 #, c-format msgid "invalid connection state %d, probably indicative of memory corruption\n" msgstr "" "état de connexion invalide (%d), indiquant probablement une corruption de\n" " mémoire\n" -#: fe-connect.c:4840 +#: fe-connect.c:4841 #, c-format msgid "invalid LDAP URL \"%s\": scheme must be ldap://\n" msgstr "URL LDAP « %s » invalide : le schéma doit être ldap://\n" -#: fe-connect.c:4855 +#: fe-connect.c:4856 #, c-format msgid "invalid LDAP URL \"%s\": missing distinguished name\n" msgstr "URL LDAP « %s » invalide : le « distinguished name » manque\n" -#: fe-connect.c:4867 fe-connect.c:4925 +#: fe-connect.c:4868 fe-connect.c:4926 #, c-format msgid "invalid LDAP URL \"%s\": must have exactly one attribute\n" msgstr "URL LDAP « %s » invalide : doit avoir exactement un attribut\n" -#: fe-connect.c:4879 fe-connect.c:4941 +#: fe-connect.c:4880 fe-connect.c:4942 #, c-format msgid "invalid LDAP URL \"%s\": must have search scope (base/one/sub)\n" msgstr "URL LDAP « %s » invalide : doit avoir une échelle de recherche (base/un/sous)\n" -#: fe-connect.c:4891 +#: fe-connect.c:4892 #, c-format msgid "invalid LDAP URL \"%s\": no filter\n" msgstr "URL LDAP « %s » invalide : aucun filtre\n" -#: fe-connect.c:4913 +#: fe-connect.c:4914 #, c-format msgid "invalid LDAP URL \"%s\": invalid port number\n" msgstr "URL LDAP « %s » invalide : numéro de port invalide\n" -#: fe-connect.c:4951 +#: fe-connect.c:4952 msgid "could not create LDAP structure\n" msgstr "n'a pas pu créer la structure LDAP\n" -#: fe-connect.c:5027 +#: fe-connect.c:5028 #, c-format msgid "lookup on LDAP server failed: %s\n" msgstr "échec de la recherche sur le serveur LDAP : %s\n" -#: fe-connect.c:5038 +#: fe-connect.c:5039 msgid "more than one entry found on LDAP lookup\n" msgstr "plusieurs entrées trouvées pendant la recherche LDAP\n" -#: fe-connect.c:5039 fe-connect.c:5051 +#: fe-connect.c:5040 fe-connect.c:5052 msgid "no entry found on LDAP lookup\n" msgstr "aucune entrée trouvée pendant la recherche LDAP\n" -#: fe-connect.c:5062 fe-connect.c:5075 +#: fe-connect.c:5063 fe-connect.c:5076 msgid "attribute has no values on LDAP lookup\n" msgstr "l'attribut n'a pas de valeur après la recherche LDAP\n" -#: fe-connect.c:5127 fe-connect.c:5146 fe-connect.c:5678 +#: fe-connect.c:5090 +#, c-format +msgid "connection info string size exceeds the maximum allowed (%d)\n" +msgstr "la longueur de la chaîne de connexion dépasse le maximum autorisé (%d)\n" + +#: fe-connect.c:5142 fe-connect.c:5161 fe-connect.c:5693 #, c-format msgid "missing \"=\" after \"%s\" in connection info string\n" msgstr "« = » manquant après « %s » dans la chaîne des paramètres de connexion\n" -#: fe-connect.c:5219 fe-connect.c:5863 fe-connect.c:6639 +#: fe-connect.c:5234 fe-connect.c:5878 fe-connect.c:6654 #, c-format msgid "invalid connection option \"%s\"\n" msgstr "option de connexion « %s » invalide\n" -#: fe-connect.c:5235 fe-connect.c:5727 +#: fe-connect.c:5250 fe-connect.c:5742 msgid "unterminated quoted string in connection info string\n" msgstr "guillemets non refermés dans la chaîne des paramètres de connexion\n" -#: fe-connect.c:5316 +#: fe-connect.c:5331 #, c-format msgid "definition of service \"%s\" not found\n" msgstr "définition du service « %s » introuvable\n" -#: fe-connect.c:5342 +#: fe-connect.c:5357 #, c-format msgid "service file \"%s\" not found\n" msgstr "fichier de service « %s » introuvable\n" -#: fe-connect.c:5356 +#: fe-connect.c:5371 #, c-format msgid "line %d too long in service file \"%s\"\n" msgstr "ligne %d trop longue dans le fichier service « %s »\n" -#: fe-connect.c:5427 fe-connect.c:5471 +#: fe-connect.c:5442 fe-connect.c:5486 #, c-format msgid "syntax error in service file \"%s\", line %d\n" msgstr "erreur de syntaxe dans le fichier service « %s », ligne %d\n" -#: fe-connect.c:5438 +#: fe-connect.c:5453 #, c-format msgid "nested service specifications not supported in service file \"%s\", line %d\n" msgstr "spécifications imbriquées de service non supportées dans le fichier service « %s », ligne %d\n" -#: fe-connect.c:6159 +#: fe-connect.c:6174 #, c-format msgid "invalid URI propagated to internal parser routine: \"%s\"\n" msgstr "URI invalide propagée à la routine d'analyse interne : « %s »\n" -#: fe-connect.c:6236 +#: fe-connect.c:6251 #, c-format msgid "end of string reached when looking for matching \"]\" in IPv6 host address in URI: \"%s\"\n" msgstr "" "fin de chaîne atteinte lors de la recherche du « ] » correspondant dans\n" "l'adresse IPv6 de l'hôte indiquée dans l'URI : « %s »\n" -#: fe-connect.c:6243 +#: fe-connect.c:6258 #, c-format msgid "IPv6 host address may not be empty in URI: \"%s\"\n" msgstr "l'adresse IPv6 de l'hôte ne peut pas être vide dans l'URI : « %s »\n" -#: fe-connect.c:6258 +#: fe-connect.c:6273 #, c-format msgid "unexpected character \"%c\" at position %d in URI (expected \":\" or \"/\"): \"%s\"\n" msgstr "" "caractère « %c » inattendu à la position %d de l'URI (caractère « : » ou\n" "« / » attendu) : « %s »\n" -#: fe-connect.c:6388 +#: fe-connect.c:6403 #, c-format msgid "extra key/value separator \"=\" in URI query parameter: \"%s\"\n" msgstr "séparateur « = » de clé/valeur en trop dans le paramètre de requête URI : « %s »\n" -#: fe-connect.c:6408 +#: fe-connect.c:6423 #, c-format msgid "missing key/value separator \"=\" in URI query parameter: \"%s\"\n" msgstr "séparateur « = » de clé/valeur manquant dans le paramètre de requête URI : « %s »\n" -#: fe-connect.c:6460 +#: fe-connect.c:6475 #, c-format msgid "invalid URI query parameter: \"%s\"\n" msgstr "paramètre de la requête URI invalide : « %s »\n" -#: fe-connect.c:6534 +#: fe-connect.c:6549 #, c-format msgid "invalid percent-encoded token: \"%s\"\n" msgstr "jeton encodé en pourcentage invalide : « %s »\n" -#: fe-connect.c:6544 +#: fe-connect.c:6559 #, c-format msgid "forbidden value %%00 in percent-encoded value: \"%s\"\n" msgstr "valeur %%00 interdite dans la valeur codée en pourcentage : « %s »\n" -#: fe-connect.c:6914 +#: fe-connect.c:6931 msgid "connection pointer is NULL\n" msgstr "le pointeur de connexion est NULL\n" -#: fe-connect.c:7202 +#: fe-connect.c:7219 #, c-format msgid "WARNING: password file \"%s\" is not a plain file\n" msgstr "ATTENTION : le fichier de mots de passe « %s » n'est pas un fichier texte\n" -#: fe-connect.c:7211 +#: fe-connect.c:7228 #, c-format msgid "WARNING: password file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n" msgstr "" @@ -658,159 +664,161 @@ msgstr "" "lecture pour le groupe ou universel ; les droits devraient être u=rw (0600)\n" "ou inférieur\n" -#: fe-connect.c:7319 +#: fe-connect.c:7336 #, c-format msgid "password retrieved from file \"%s\"\n" msgstr "mot de passe récupéré dans le fichier « %s »\n" -#: fe-exec.c:466 fe-exec.c:3431 +#: fe-exec.c:466 fe-exec.c:3436 #, c-format msgid "row number %d is out of range 0..%d" msgstr "le numéro de ligne %d est en dehors des limites 0..%d" -#: fe-exec.c:528 fe-protocol3.c:1932 +#: fe-exec.c:528 fe-protocol3.c:1946 #, c-format msgid "%s" msgstr "%s" -#: fe-exec.c:836 +#: fe-exec.c:841 msgid "write to server failed\n" msgstr "échec en écriture vers le serveur\n" -#: fe-exec.c:877 +#: fe-exec.c:882 msgid "no error text available\n" msgstr "aucun texte d'erreur disponible\n" -#: fe-exec.c:966 +#: fe-exec.c:971 msgid "NOTICE" msgstr "NOTICE" -#: fe-exec.c:1024 +#: fe-exec.c:1029 msgid "PGresult cannot support more than INT_MAX tuples" msgstr "PGresult ne supporte pas plus de INT_MAX lignes" -#: fe-exec.c:1036 +#: fe-exec.c:1041 msgid "size_t overflow" msgstr "saturation de size_t" -#: fe-exec.c:1450 fe-exec.c:1521 fe-exec.c:1570 +#: fe-exec.c:1455 fe-exec.c:1526 fe-exec.c:1575 msgid "command string is a null pointer\n" msgstr "la chaîne de commande est un pointeur nul\n" -#: fe-exec.c:1457 fe-exec.c:2908 +#: fe-exec.c:1462 fe-exec.c:2913 #, c-format msgid "%s not allowed in pipeline mode\n" msgstr "%s non autorisé dans le mode pipeline\n" -#: fe-exec.c:1527 fe-exec.c:1576 fe-exec.c:1672 +#: fe-exec.c:1532 fe-exec.c:1581 fe-exec.c:1677 #, c-format msgid "number of parameters must be between 0 and %d\n" msgstr "le nombre de paramètres doit être compris entre 0 et %d\n" -#: fe-exec.c:1564 fe-exec.c:1666 +#: fe-exec.c:1569 fe-exec.c:1671 msgid "statement name is a null pointer\n" msgstr "le nom de l'instruction est un pointeur nul\n" -#: fe-exec.c:1710 fe-exec.c:3276 +#: fe-exec.c:1715 fe-exec.c:3281 msgid "no connection to the server\n" msgstr "aucune connexion au serveur\n" -#: fe-exec.c:1719 fe-exec.c:3285 +#: fe-exec.c:1724 fe-exec.c:3290 msgid "another command is already in progress\n" msgstr "une autre commande est déjà en cours\n" -#: fe-exec.c:1750 +#: fe-exec.c:1755 msgid "cannot queue commands during COPY\n" msgstr "ne peut pas mettre en queue les commandes lors du COPY\n" -#: fe-exec.c:1868 +#: fe-exec.c:1873 msgid "length must be given for binary parameter\n" msgstr "la longueur doit être indiquée pour les paramètres binaires\n" -#: fe-exec.c:2183 +#: fe-exec.c:2188 #, c-format msgid "unexpected asyncStatus: %d\n" msgstr "asyncStatus inattendu : %d\n" -#: fe-exec.c:2341 +#: fe-exec.c:2346 msgid "synchronous command execution functions are not allowed in pipeline mode\n" msgstr "les fonctions d'exécution de commande synchrone ne sont pas autorisées en mode pipeline\n" -#: fe-exec.c:2358 +#: fe-exec.c:2363 msgid "COPY terminated by new PQexec" msgstr "COPY terminé par un nouveau PQexec" -#: fe-exec.c:2375 +#: fe-exec.c:2380 msgid "PQexec not allowed during COPY BOTH\n" msgstr "PQexec non autorisé pendant COPY BOTH\n" -#: fe-exec.c:2603 fe-exec.c:2659 fe-exec.c:2728 fe-protocol3.c:1863 +#: fe-exec.c:2608 fe-exec.c:2664 fe-exec.c:2733 fe-protocol3.c:1877 msgid "no COPY in progress\n" msgstr "aucun COPY en cours\n" -#: fe-exec.c:2917 +#: fe-exec.c:2922 msgid "connection in wrong state\n" msgstr "connexion dans un état erroné\n" -#: fe-exec.c:2961 +#: fe-exec.c:2966 msgid "cannot enter pipeline mode, connection not idle\n" msgstr "ne peut pas entrer dans le mode pipeline, connexion active\n" -#: fe-exec.c:2998 fe-exec.c:3022 +#: fe-exec.c:3003 fe-exec.c:3027 msgid "cannot exit pipeline mode with uncollected results\n" msgstr "ne peut pas sortir du mode pipeline avec des résultats non récupérés\n" -#: fe-exec.c:3003 +#: fe-exec.c:3008 msgid "cannot exit pipeline mode while busy\n" msgstr "ne peut pas sortir du mode pipeline alors qu'il est occupé\n" -#: fe-exec.c:3015 +#: fe-exec.c:3020 msgid "cannot exit pipeline mode while in COPY\n" msgstr "ne peut pas sortir du mode pipeline pendant un COPY\n" -#: fe-exec.c:3209 +#: fe-exec.c:3214 msgid "cannot send pipeline when not in pipeline mode\n" msgstr "ne peut pas envoyer le pipeline lorsqu'il n'est pas en mode pipeline\n" -#: fe-exec.c:3320 +#: fe-exec.c:3325 msgid "invalid ExecStatusType code" msgstr "code ExecStatusType invalide" -#: fe-exec.c:3347 +#: fe-exec.c:3352 msgid "PGresult is not an error result\n" msgstr "PGresult n'est pas un résultat d'erreur\n" -#: fe-exec.c:3415 fe-exec.c:3438 +#: fe-exec.c:3420 fe-exec.c:3443 #, c-format msgid "column number %d is out of range 0..%d" msgstr "le numéro de colonne %d est en dehors des limites 0..%d" -#: fe-exec.c:3453 +#: fe-exec.c:3458 #, c-format msgid "parameter number %d is out of range 0..%d" msgstr "le numéro de paramètre %d est en dehors des limites 0..%d" -#: fe-exec.c:3764 +#: fe-exec.c:3769 #, c-format msgid "could not interpret result from server: %s" msgstr "n'a pas pu interpréter la réponse du serveur : %s" -#: fe-exec.c:4043 -msgid "incomplete multibyte character" -msgstr "caractère multi-octets incomplet" - -#: fe-exec.c:4046 -msgid "invalid multibyte character" -msgstr "caractère multi-octets invalide" - -#: fe-exec.c:4157 +#: fe-exec.c:4049 fe-exec.c:4185 msgid "incomplete multibyte character\n" msgstr "caractère multi-octet incomplet\n" -#: fe-exec.c:4177 +#: fe-exec.c:4052 fe-exec.c:4205 msgid "invalid multibyte character\n" msgstr "caractère multi-octets invalide\n" +#: fe-exec.c:4308 +#, c-format +msgid "escaped string size exceeds the maximum allowed (%zu)\n" +msgstr "la taille de la chaîne de caractères une fois échappée dépasse le maximum autorisé (%zu)\n" + +#: fe-exec.c:4486 +#, c-format +msgid "escaped bytea size exceeds the maximum allowed (%zu)\n" +msgstr "la taille du bytea une fois échappé dépasse le maximum autorisé (%zu)\n" + #: fe-gssapi-common.c:124 msgid "GSSAPI name import error" msgstr "erreur d'import du nom GSSAPI" @@ -865,11 +873,11 @@ msgstr "entier de taille %lu non supporté par pqGetInt" msgid "integer of size %lu not supported by pqPutInt" msgstr "entier de taille %lu non supporté par pqPutInt" -#: fe-misc.c:576 fe-misc.c:822 +#: fe-misc.c:602 fe-misc.c:848 msgid "connection not open\n" msgstr "la connexion n'est pas active\n" -#: fe-misc.c:755 fe-secure-openssl.c:213 fe-secure-openssl.c:326 +#: fe-misc.c:781 fe-secure-openssl.c:213 fe-secure-openssl.c:326 #: fe-secure.c:262 fe-secure.c:430 #, c-format msgid "" @@ -881,152 +889,152 @@ msgstr "" "\tLe serveur s'est peut-être arrêté anormalement avant ou durant le\n" "\ttraitement de la requête.\n" -#: fe-misc.c:1008 +#: fe-misc.c:1034 msgid "timeout expired\n" msgstr "le délai est dépassé\n" -#: fe-misc.c:1053 +#: fe-misc.c:1079 msgid "invalid socket\n" msgstr "socket invalide\n" -#: fe-misc.c:1076 +#: fe-misc.c:1102 #, c-format msgid "%s() failed: %s\n" msgstr "échec de %s() : %s\n" -#: fe-protocol3.c:184 +#: fe-protocol3.c:185 #, c-format msgid "message type 0x%02x arrived from server while idle" msgstr "le message de type 0x%02x est arrivé alors que le serveur était en attente" -#: fe-protocol3.c:388 +#: fe-protocol3.c:389 msgid "server sent data (\"D\" message) without prior row description (\"T\" message)\n" msgstr "" "le serveur a envoyé des données (message « D ») sans description préalable\n" "de la ligne (message « T »)\n" -#: fe-protocol3.c:431 +#: fe-protocol3.c:432 #, c-format msgid "unexpected response from server; first received character was \"%c\"\n" msgstr "réponse inattendue du serveur, le premier caractère reçu étant « %c »\n" -#: fe-protocol3.c:456 +#: fe-protocol3.c:457 #, c-format msgid "message contents do not agree with length in message type \"%c\"\n" msgstr "" "le contenu du message ne correspond pas avec la longueur du type de message\n" "« %c »\n" -#: fe-protocol3.c:476 +#: fe-protocol3.c:477 #, c-format msgid "lost synchronization with server: got message type \"%c\", length %d\n" msgstr "" "synchronisation perdue avec le serveur : a reçu le type de message « %c »,\n" "longueur %d\n" -#: fe-protocol3.c:528 fe-protocol3.c:568 +#: fe-protocol3.c:529 fe-protocol3.c:569 msgid "insufficient data in \"T\" message" msgstr "données insuffisantes dans le message « T »" -#: fe-protocol3.c:639 fe-protocol3.c:845 +#: fe-protocol3.c:640 fe-protocol3.c:846 msgid "out of memory for query result" msgstr "mémoire épuisée pour le résultat de la requête" -#: fe-protocol3.c:708 +#: fe-protocol3.c:709 msgid "insufficient data in \"t\" message" msgstr "données insuffisantes dans le message « t »" -#: fe-protocol3.c:767 fe-protocol3.c:799 fe-protocol3.c:817 +#: fe-protocol3.c:768 fe-protocol3.c:800 fe-protocol3.c:818 msgid "insufficient data in \"D\" message" msgstr "données insuffisantes dans le message « D »" -#: fe-protocol3.c:773 +#: fe-protocol3.c:774 msgid "unexpected field count in \"D\" message" msgstr "nombre de champs inattendu dans le message « D »" -#: fe-protocol3.c:1029 +#: fe-protocol3.c:1030 msgid "no error message available\n" msgstr "aucun message d'erreur disponible\n" #. translator: %s represents a digit string -#: fe-protocol3.c:1077 fe-protocol3.c:1096 +#: fe-protocol3.c:1078 fe-protocol3.c:1097 #, c-format msgid " at character %s" msgstr " au caractère %s" -#: fe-protocol3.c:1109 +#: fe-protocol3.c:1110 #, c-format msgid "DETAIL: %s\n" msgstr "DÉTAIL : %s\n" -#: fe-protocol3.c:1112 +#: fe-protocol3.c:1113 #, c-format msgid "HINT: %s\n" msgstr "ASTUCE : %s\n" -#: fe-protocol3.c:1115 +#: fe-protocol3.c:1116 #, c-format msgid "QUERY: %s\n" msgstr "REQUÊTE : %s\n" -#: fe-protocol3.c:1122 +#: fe-protocol3.c:1123 #, c-format msgid "CONTEXT: %s\n" msgstr "CONTEXTE : %s\n" -#: fe-protocol3.c:1131 +#: fe-protocol3.c:1132 #, c-format msgid "SCHEMA NAME: %s\n" msgstr "NOM DE SCHÉMA : %s\n" -#: fe-protocol3.c:1135 +#: fe-protocol3.c:1136 #, c-format msgid "TABLE NAME: %s\n" msgstr "NOM DE TABLE : %s\n" -#: fe-protocol3.c:1139 +#: fe-protocol3.c:1140 #, c-format msgid "COLUMN NAME: %s\n" msgstr "NOM DE COLONNE : %s\n" -#: fe-protocol3.c:1143 +#: fe-protocol3.c:1144 #, c-format msgid "DATATYPE NAME: %s\n" msgstr "NOM DU TYPE DE DONNÉES : %s\n" -#: fe-protocol3.c:1147 +#: fe-protocol3.c:1148 #, c-format msgid "CONSTRAINT NAME: %s\n" msgstr "NOM DE CONTRAINTE : %s\n" -#: fe-protocol3.c:1159 +#: fe-protocol3.c:1160 msgid "LOCATION: " msgstr "EMPLACEMENT : " -#: fe-protocol3.c:1161 +#: fe-protocol3.c:1162 #, c-format msgid "%s, " msgstr "%s, " -#: fe-protocol3.c:1163 +#: fe-protocol3.c:1164 #, c-format msgid "%s:%s" msgstr "%s : %s" -#: fe-protocol3.c:1358 +#: fe-protocol3.c:1372 #, c-format msgid "LINE %d: " msgstr "LIGNE %d : " -#: fe-protocol3.c:1757 +#: fe-protocol3.c:1771 msgid "PQgetline: not doing text COPY OUT\n" msgstr "PQgetline : ne va pas réaliser un COPY OUT au format texte\n" -#: fe-protocol3.c:2134 +#: fe-protocol3.c:2148 msgid "protocol error: no function result\n" msgstr "erreur de protocole : aucun résultat de fonction\n" -#: fe-protocol3.c:2146 +#: fe-protocol3.c:2160 #, c-format msgid "protocol error: id=0x%x\n" msgstr "erreur de protocole : id=0x%x\n" @@ -1058,44 +1066,40 @@ msgstr "le certificat serveur pour « %s » ne correspond pas au nom d'hôte « msgid "could not get server's host name from server certificate\n" msgstr "n'a pas pu récupérer le nom d'hôte du serveur à partir du certificat serveur\n" -#: fe-secure-gssapi.c:194 +#: fe-secure-gssapi.c:201 msgid "GSSAPI wrap error" msgstr "erreur d'emballage GSSAPI" -#: fe-secure-gssapi.c:202 +#: fe-secure-gssapi.c:209 msgid "outgoing GSSAPI message would not use confidentiality\n" msgstr "le message sortant GSSAPI n'utiliserait pas la confidentialité\n" -#: fe-secure-gssapi.c:210 +#: fe-secure-gssapi.c:217 fe-secure-gssapi.c:715 #, c-format msgid "client tried to send oversize GSSAPI packet (%zu > %zu)\n" msgstr "le client a essayé d'envoyer un paquet GSSAPI trop gros (%zu > %zu)\n" -#: fe-secure-gssapi.c:350 fe-secure-gssapi.c:594 +#: fe-secure-gssapi.c:357 fe-secure-gssapi.c:607 #, c-format msgid "oversize GSSAPI packet sent by the server (%zu > %zu)\n" msgstr "paquet GSSAPI trop gros envoyé par le serveur (%zu > %zu)\n" -#: fe-secure-gssapi.c:389 +#: fe-secure-gssapi.c:396 msgid "GSSAPI unwrap error" msgstr "erreur de dépaquetage GSSAPI" -#: fe-secure-gssapi.c:399 +#: fe-secure-gssapi.c:406 msgid "incoming GSSAPI message did not use confidentiality\n" msgstr "le message entrant GSSAPI n'a pas utilisé pas la confidentialité\n" -#: fe-secure-gssapi.c:640 +#: fe-secure-gssapi.c:653 msgid "could not initiate GSSAPI security context" msgstr "n'a pas pu initier le contexte de sécurité GSSAPI" -#: fe-secure-gssapi.c:668 +#: fe-secure-gssapi.c:703 msgid "GSSAPI size check error" msgstr "erreur de vérification de la taille GSSAPI" -#: fe-secure-gssapi.c:679 -msgid "GSSAPI context establishment error" -msgstr "erreur d'établissement du contexte GSSAPI" - #: fe-secure-openssl.c:218 fe-secure-openssl.c:331 fe-secure-openssl.c:1492 #, c-format msgid "SSL SYSCALL error: %s\n" @@ -1299,5 +1303,14 @@ msgstr "n'a pas pu transmettre les données au serveur : %s\n" msgid "unrecognized socket error: 0x%08X/%d" msgstr "erreur de socket non reconnue : 0x%08X/%d" +#~ msgid "GSSAPI context establishment error" +#~ msgstr "erreur d'établissement du contexte GSSAPI" + +#~ msgid "incomplete multibyte character" +#~ msgstr "caractère multi-octets incomplet" + +#~ msgid "invalid multibyte character" +#~ msgstr "caractère multi-octets invalide" + #~ msgid "keepalives parameter must be an integer\n" #~ msgstr "le paramètre keepalives doit être un entier\n" diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile index 01588d016a0..f07d0e31770 100644 --- a/src/pl/plperl/GNUmakefile +++ b/src/pl/plperl/GNUmakefile @@ -57,7 +57,7 @@ SHLIB_LINK = $(perl_embed_ldflags) REGRESS_OPTS = --dbname=$(PL_TESTDB) --dlpath=$(top_builddir)/src/test/regress REGRESS = plperl_setup plperl plperl_lc plperl_trigger plperl_shared \ - plperl_elog plperl_util plperl_init plperlu plperl_array \ + plperl_elog plperl_unicode plperl_util plperl_init plperlu plperl_array \ plperl_call plperl_transaction plperl_env # if Perl can support two interpreters in one backend, # test plperl-and-plperlu cases diff --git a/src/pl/plperl/expected/plperl_elog.out b/src/pl/plperl/expected/plperl_elog.out index a6d35cb79c4..3f9449a9659 100644 --- a/src/pl/plperl/expected/plperl_elog.out +++ b/src/pl/plperl/expected/plperl_elog.out @@ -97,16 +97,3 @@ NOTICE: caught die 2 (1 row) --- Test non-ASCII error messages --- --- Note: this test case is known to fail if the database encoding is --- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to --- U+00A0 (no-break space) in those encodings. However, testing with --- plain ASCII data would be rather useless, so we must live with that. -SET client_encoding TO UTF8; -create or replace function error_with_nbsp() returns void language plperl as $$ - elog(ERROR, "this message contains a no-break space"); -$$; -select error_with_nbsp(); -ERROR: this message contains a no-break space at line 2. -CONTEXT: PL/Perl function "error_with_nbsp" diff --git a/src/pl/plperl/expected/plperl_elog_1.out b/src/pl/plperl/expected/plperl_elog_1.out index 85aa460ec4c..34d5d5836da 100644 --- a/src/pl/plperl/expected/plperl_elog_1.out +++ b/src/pl/plperl/expected/plperl_elog_1.out @@ -97,16 +97,3 @@ NOTICE: caught die 2 (1 row) --- Test non-ASCII error messages --- --- Note: this test case is known to fail if the database encoding is --- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to --- U+00A0 (no-break space) in those encodings. However, testing with --- plain ASCII data would be rather useless, so we must live with that. -SET client_encoding TO UTF8; -create or replace function error_with_nbsp() returns void language plperl as $$ - elog(ERROR, "this message contains a no-break space"); -$$; -select error_with_nbsp(); -ERROR: this message contains a no-break space at line 2. -CONTEXT: PL/Perl function "error_with_nbsp" diff --git a/src/pl/plperl/expected/plperl_unicode.out b/src/pl/plperl/expected/plperl_unicode.out new file mode 100644 index 00000000000..3c48f2e9611 --- /dev/null +++ b/src/pl/plperl/expected/plperl_unicode.out @@ -0,0 +1,18 @@ +-- Test non-ASCII error messages +-- +-- This test case would fail if the database encoding is EUC_CN, EUC_JP, +-- EUC_KR, or EUC_TW, for lack of any equivalent to U+00A0 (no-break space) in +-- those encodings. However, testing with plain ASCII data would be rather +-- useless, so we must live with that. +SELECT getdatabaseencoding() IN ('EUC_CN', 'EUC_JP', 'EUC_KR', 'EUC_TW') + AS skip_test \gset +\if :skip_test +\quit +\endif +SET client_encoding TO UTF8; +create or replace function error_with_nbsp() returns void language plperl as $$ + elog(ERROR, "this message contains a no-break space"); +$$; +select error_with_nbsp(); +ERROR: this message contains a no-break space at line 2. +CONTEXT: PL/Perl function "error_with_nbsp" diff --git a/src/pl/plperl/expected/plperl_unicode_1.out b/src/pl/plperl/expected/plperl_unicode_1.out new file mode 100644 index 00000000000..761de04b1ee --- /dev/null +++ b/src/pl/plperl/expected/plperl_unicode_1.out @@ -0,0 +1,10 @@ +-- Test non-ASCII error messages +-- +-- This test case would fail if the database encoding is EUC_CN, EUC_JP, +-- EUC_KR, or EUC_TW, for lack of any equivalent to U+00A0 (no-break space) in +-- those encodings. However, testing with plain ASCII data would be rather +-- useless, so we must live with that. +SELECT getdatabaseencoding() IN ('EUC_CN', 'EUC_JP', 'EUC_KR', 'EUC_TW') + AS skip_test \gset +\if :skip_test +\quit diff --git a/src/pl/plperl/sql/plperl_elog.sql b/src/pl/plperl/sql/plperl_elog.sql index 9ea1350069b..032fd8b8ba7 100644 --- a/src/pl/plperl/sql/plperl_elog.sql +++ b/src/pl/plperl/sql/plperl_elog.sql @@ -76,18 +76,3 @@ return $a + $b; $$; select indirect_die_caller(); - --- Test non-ASCII error messages --- --- Note: this test case is known to fail if the database encoding is --- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to --- U+00A0 (no-break space) in those encodings. However, testing with --- plain ASCII data would be rather useless, so we must live with that. - -SET client_encoding TO UTF8; - -create or replace function error_with_nbsp() returns void language plperl as $$ - elog(ERROR, "this message contains a no-break space"); -$$; - -select error_with_nbsp(); diff --git a/src/pl/plperl/sql/plperl_unicode.sql b/src/pl/plperl/sql/plperl_unicode.sql new file mode 100644 index 00000000000..7e1ad745cdd --- /dev/null +++ b/src/pl/plperl/sql/plperl_unicode.sql @@ -0,0 +1,19 @@ +-- Test non-ASCII error messages +-- +-- This test case would fail if the database encoding is EUC_CN, EUC_JP, +-- EUC_KR, or EUC_TW, for lack of any equivalent to U+00A0 (no-break space) in +-- those encodings. However, testing with plain ASCII data would be rather +-- useless, so we must live with that. +SELECT getdatabaseencoding() IN ('EUC_CN', 'EUC_JP', 'EUC_KR', 'EUC_TW') + AS skip_test \gset +\if :skip_test +\quit +\endif + +SET client_encoding TO UTF8; + +create or replace function error_with_nbsp() returns void language plperl as $$ + elog(ERROR, "this message contains a no-break space"); +$$; + +select error_with_nbsp(); diff --git a/src/pl/plpgsql/src/expected/plpgsql_trap.out b/src/pl/plpgsql/src/expected/plpgsql_trap.out index 90cf6c28956..c37ac3b5c69 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_trap.out +++ b/src/pl/plpgsql/src/expected/plpgsql_trap.out @@ -138,13 +138,11 @@ select * from foo; drop table foo; create function trap_timeout() returns void as $$ begin - declare x int; begin - -- we assume this will take longer than 1 second: - select count(*) into x from generate_series(1, 1000000000000); + perform pg_sleep(10); exception when others then - raise notice 'caught others?'; + raise notice 'caught others: %', sqlerrm; when query_canceled then raise notice 'nyeah nyeah, can''t stop me'; end; @@ -157,7 +155,7 @@ set statement_timeout to 1000; select trap_timeout(); NOTICE: nyeah nyeah, can't stop me ERROR: end of function -CONTEXT: PL/pgSQL function trap_timeout() line 15 at RAISE +CONTEXT: PL/pgSQL function trap_timeout() line 13 at RAISE rollback; -- Test for pass-by-ref values being stored in proper context create function test_variable_storage() returns text as $$ diff --git a/src/pl/plpgsql/src/sql/plpgsql_trap.sql b/src/pl/plpgsql/src/sql/plpgsql_trap.sql index c6c1ad894b5..731d592040e 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_trap.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_trap.sql @@ -85,13 +85,11 @@ drop table foo; create function trap_timeout() returns void as $$ begin - declare x int; begin - -- we assume this will take longer than 1 second: - select count(*) into x from generate_series(1, 1000000000000); + perform pg_sleep(10); exception when others then - raise notice 'caught others?'; + raise notice 'caught others: %', sqlerrm; when query_canceled then raise notice 'nyeah nyeah, can''t stop me'; end; diff --git a/src/pl/plpython/expected/plpython_unicode.out b/src/pl/plpython/expected/plpython_unicode.out index fd54b0b88e8..bd8d9c561c9 100644 --- a/src/pl/plpython/expected/plpython_unicode.out +++ b/src/pl/plpython/expected/plpython_unicode.out @@ -1,11 +1,16 @@ -- -- Unicode handling -- --- Note: this test case is known to fail if the database encoding is --- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to --- U+00A0 (no-break space) in those encodings. However, testing with --- plain ASCII data would be rather useless, so we must live with that. +-- This test case would fail if the database encoding is EUC_CN, EUC_JP, +-- EUC_KR, or EUC_TW, for lack of any equivalent to U+00A0 (no-break space) in +-- those encodings. However, testing with plain ASCII data would be rather +-- useless, so we must live with that. -- +SELECT getdatabaseencoding() IN ('EUC_CN', 'EUC_JP', 'EUC_KR', 'EUC_TW') + AS skip_test \gset +\if :skip_test +\quit +\endif SET client_encoding TO UTF8; CREATE TABLE unicode_test ( testvalue text NOT NULL diff --git a/src/pl/plpython/expected/plpython_unicode_1.out b/src/pl/plpython/expected/plpython_unicode_1.out new file mode 100644 index 00000000000..f8b21fd7eb7 --- /dev/null +++ b/src/pl/plpython/expected/plpython_unicode_1.out @@ -0,0 +1,12 @@ +-- +-- Unicode handling +-- +-- This test case would fail if the database encoding is EUC_CN, EUC_JP, +-- EUC_KR, or EUC_TW, for lack of any equivalent to U+00A0 (no-break space) in +-- those encodings. However, testing with plain ASCII data would be rather +-- useless, so we must live with that. +-- +SELECT getdatabaseencoding() IN ('EUC_CN', 'EUC_JP', 'EUC_KR', 'EUC_TW') + AS skip_test \gset +\if :skip_test +\quit diff --git a/src/pl/plpython/sql/plpython_unicode.sql b/src/pl/plpython/sql/plpython_unicode.sql index 14f7b4e0053..f45844b906e 100644 --- a/src/pl/plpython/sql/plpython_unicode.sql +++ b/src/pl/plpython/sql/plpython_unicode.sql @@ -1,11 +1,16 @@ -- -- Unicode handling -- --- Note: this test case is known to fail if the database encoding is --- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to --- U+00A0 (no-break space) in those encodings. However, testing with --- plain ASCII data would be rather useless, so we must live with that. +-- This test case would fail if the database encoding is EUC_CN, EUC_JP, +-- EUC_KR, or EUC_TW, for lack of any equivalent to U+00A0 (no-break space) in +-- those encodings. However, testing with plain ASCII data would be rather +-- useless, so we must live with that. -- +SELECT getdatabaseencoding() IN ('EUC_CN', 'EUC_JP', 'EUC_KR', 'EUC_TW') + AS skip_test \gset +\if :skip_test +\quit +\endif SET client_encoding TO UTF8; diff --git a/src/pl/tcl/expected/pltcl_unicode.out b/src/pl/tcl/expected/pltcl_unicode.out index eea7d70664f..d33afd7548f 100644 --- a/src/pl/tcl/expected/pltcl_unicode.out +++ b/src/pl/tcl/expected/pltcl_unicode.out @@ -1,11 +1,16 @@ -- -- Unicode handling -- --- Note: this test case is known to fail if the database encoding is --- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to --- U+00A0 (no-break space) in those encodings. However, testing with --- plain ASCII data would be rather useless, so we must live with that. +-- This test case would fail if the database encoding is EUC_CN, EUC_JP, +-- EUC_KR, or EUC_TW, for lack of any equivalent to U+00A0 (no-break space) in +-- those encodings. However, testing with plain ASCII data would be rather +-- useless, so we must live with that. -- +SELECT getdatabaseencoding() IN ('EUC_CN', 'EUC_JP', 'EUC_KR', 'EUC_TW') + AS skip_test \gset +\if :skip_test +\quit +\endif SET client_encoding TO UTF8; CREATE TABLE unicode_test ( testvalue text NOT NULL diff --git a/src/pl/tcl/expected/pltcl_unicode_1.out b/src/pl/tcl/expected/pltcl_unicode_1.out new file mode 100644 index 00000000000..f8b21fd7eb7 --- /dev/null +++ b/src/pl/tcl/expected/pltcl_unicode_1.out @@ -0,0 +1,12 @@ +-- +-- Unicode handling +-- +-- This test case would fail if the database encoding is EUC_CN, EUC_JP, +-- EUC_KR, or EUC_TW, for lack of any equivalent to U+00A0 (no-break space) in +-- those encodings. However, testing with plain ASCII data would be rather +-- useless, so we must live with that. +-- +SELECT getdatabaseencoding() IN ('EUC_CN', 'EUC_JP', 'EUC_KR', 'EUC_TW') + AS skip_test \gset +\if :skip_test +\quit diff --git a/src/pl/tcl/sql/pltcl_unicode.sql b/src/pl/tcl/sql/pltcl_unicode.sql index f0006046127..a09e4998b2b 100644 --- a/src/pl/tcl/sql/pltcl_unicode.sql +++ b/src/pl/tcl/sql/pltcl_unicode.sql @@ -1,11 +1,16 @@ -- -- Unicode handling -- --- Note: this test case is known to fail if the database encoding is --- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to --- U+00A0 (no-break space) in those encodings. However, testing with --- plain ASCII data would be rather useless, so we must live with that. +-- This test case would fail if the database encoding is EUC_CN, EUC_JP, +-- EUC_KR, or EUC_TW, for lack of any equivalent to U+00A0 (no-break space) in +-- those encodings. However, testing with plain ASCII data would be rather +-- useless, so we must live with that. -- +SELECT getdatabaseencoding() IN ('EUC_CN', 'EUC_JP', 'EUC_KR', 'EUC_TW') + AS skip_test \gset +\if :skip_test +\quit +\endif SET client_encoding TO UTF8; diff --git a/src/port/getopt.c b/src/port/getopt.c index 207c2836d35..3e101a7217e 100644 --- a/src/port/getopt.c +++ b/src/port/getopt.c @@ -71,7 +71,7 @@ int getopt(int nargc, char *const *nargv, const char *ostr) { static char *place = EMSG; /* option letter processing */ - char *oli; /* option letter list index */ + const char *oli; /* option letter list index */ if (!*place) { /* update scanning pointer */ diff --git a/src/port/getopt_long.c b/src/port/getopt_long.c index c9892769883..70bb3147c0a 100644 --- a/src/port/getopt_long.c +++ b/src/port/getopt_long.c @@ -59,7 +59,7 @@ getopt_long(int argc, char *const argv[], const struct option *longopts, int *longindex) { static char *place = EMSG; /* option letter processing */ - char *oli; /* option letter list index */ + const char *oli; /* option letter list index */ if (!*place) { /* update scanning pointer */ diff --git a/src/port/path.c b/src/port/path.c index 87395cd5c53..b384f5a3f87 100644 --- a/src/port/path.c +++ b/src/port/path.c @@ -626,6 +626,23 @@ path_is_relative_and_below_cwd(const char *path) return true; } +/* + * Detect whether a path is safe for use during archive extraction. + * + * This applies canonicalize_path(), then it checks that the path does + * not contain any parent directory references. + */ +bool +path_is_safe_for_extraction(const char *path) +{ + char buf[MAXPGPATH]; + + strlcpy(buf, path, sizeof(buf)); + canonicalize_path(buf); + + return path_is_relative_and_below_cwd(buf); +} + /* * Detect whether path1 is a prefix of path2 (including equality). * diff --git a/src/port/timingsafe_bcmp.c b/src/port/timingsafe_bcmp.c new file mode 100644 index 00000000000..288865f50d1 --- /dev/null +++ b/src/port/timingsafe_bcmp.c @@ -0,0 +1,43 @@ +/* + * src/port/timingsafe_bcmp.c + * + * $OpenBSD: timingsafe_bcmp.c,v 1.3 2015/08/31 02:53:57 guenther Exp $ + */ + +/* + * Copyright (c) 2010 Damien Miller. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "c.h" + +#ifdef USE_SSL +#include +#endif + +int +timingsafe_bcmp(const void *b1, const void *b2, size_t n) +{ +#ifdef USE_SSL + return CRYPTO_memcmp(b1, b2, n); +#else + const unsigned char *p1 = b1, + *p2 = b2; + int ret = 0; + + for (; n > 0; n--) + ret |= *p1++ ^ *p2++; + return (ret != 0); +#endif +} diff --git a/src/port/win32setlocale.c b/src/port/win32setlocale.c index aadd09a4e9a..301d26b34aa 100644 --- a/src/port/win32setlocale.c +++ b/src/port/win32setlocale.c @@ -119,9 +119,9 @@ map_locale(const struct locale_map *map, const char *locale) const char *needle_start = map[i].locale_name_start; const char *needle_end = map[i].locale_name_end; const char *replacement = map[i].replacement; - char *match; - char *match_start = NULL; - char *match_end = NULL; + const char *match; + const char *match_start = NULL; + const char *match_end = NULL; match = strstr(locale, needle_start); if (match) @@ -148,7 +148,7 @@ map_locale(const struct locale_map *map, const char *locale) /* Found a match. Replace the matched string. */ int matchpos = match_start - locale; int replacementlen = strlen(replacement); - char *rest = match_end; + const char *rest = match_end; int restlen = strlen(rest); /* check that the result fits in the static buffer */ diff --git a/src/test/Makefile b/src/test/Makefile index 69ef074d75e..7b702923cbc 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -12,7 +12,7 @@ subdir = src/test top_builddir = ../.. include $(top_builddir)/src/Makefile.global -SUBDIRS = perl regress isolation modules authentication recovery subscription +SUBDIRS = perl postmaster regress isolation modules authentication recovery subscription ifeq ($(with_icu),yes) SUBDIRS += icu diff --git a/src/test/isolation/expected/insert-conflict-do-update-4.out b/src/test/isolation/expected/insert-conflict-do-update-4.out index 6e96e0d12da..80af2798a94 100644 --- a/src/test/isolation/expected/insert-conflict-do-update-4.out +++ b/src/test/isolation/expected/insert-conflict-do-update-4.out @@ -12,11 +12,11 @@ step insert1: INSERT INTO upsert VALUES (1, 11, 111) step update2a: UPDATE upsert SET i = i + 10 WHERE i = 1; step c2: COMMIT; step insert1: <... completed> -step select1: SELECT * FROM upsert; +step select1: SELECT * FROM upsert ORDER BY i; i| j| k --+--+--- -11|10|100 1|11|111 +11|10|100 (2 rows) step c1: COMMIT; @@ -33,7 +33,7 @@ step insert1: INSERT INTO upsert VALUES (1, 11, 111) step update2b: UPDATE upsert SET i = i + 150 WHERE i = 1; step c2: COMMIT; step insert1: <... completed> -step select1: SELECT * FROM upsert; +step select1: SELECT * FROM upsert ORDER BY i; i| j| k ---+--+--- 1|11|111 @@ -54,7 +54,7 @@ step insert1: INSERT INTO upsert VALUES (1, 11, 111) step delete2: DELETE FROM upsert WHERE i = 1; step c2: COMMIT; step insert1: <... completed> -step select1: SELECT * FROM upsert; +step select1: SELECT * FROM upsert ORDER BY i; i| j| k -+--+--- 1|11|111 diff --git a/src/test/isolation/expected/merge-update.out b/src/test/isolation/expected/merge-update.out index f5f7e3ba19b..5170f6ee019 100644 --- a/src/test/isolation/expected/merge-update.out +++ b/src/test/isolation/expected/merge-update.out @@ -355,3 +355,28 @@ step c1: COMMIT; step pa_merge2c_dup: <... completed> ERROR: MERGE command cannot affect row a second time step a2: ABORT; + +starting permutation: merge2a c1 s1beginrr merge1 c2 +step merge2a: + MERGE INTO target t + USING (SELECT 1 as key, 'merge2a' as val) s + ON s.key = t.key + WHEN NOT MATCHED THEN + INSERT VALUES (s.key, s.val) + WHEN MATCHED THEN + UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; + +step c1: COMMIT; +step s1beginrr: BEGIN ISOLATION LEVEL REPEATABLE READ; +step merge1: + MERGE INTO target t + USING (SELECT 1 as key, 'merge1' as val) s + ON s.key = t.key + WHEN NOT MATCHED THEN + INSERT VALUES (s.key, s.val) + WHEN MATCHED THEN + UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; + +step c2: COMMIT; +step merge1: <... completed> +ERROR: could not serialize access due to concurrent update diff --git a/src/test/isolation/specs/insert-conflict-do-update-4.spec b/src/test/isolation/specs/insert-conflict-do-update-4.spec index 6297b1d1d6c..a62531660d3 100644 --- a/src/test/isolation/specs/insert-conflict-do-update-4.spec +++ b/src/test/isolation/specs/insert-conflict-do-update-4.spec @@ -23,7 +23,7 @@ session s1 setup { BEGIN ISOLATION LEVEL READ COMMITTED; } step insert1 { INSERT INTO upsert VALUES (1, 11, 111) ON CONFLICT (i) DO UPDATE SET k = EXCLUDED.k; } -step select1 { SELECT * FROM upsert; } +step select1 { SELECT * FROM upsert ORDER BY i; } step c1 { COMMIT; } session s2 diff --git a/src/test/isolation/specs/merge-update.spec b/src/test/isolation/specs/merge-update.spec index 3ccd4664498..293cdb46b56 100644 --- a/src/test/isolation/specs/merge-update.spec +++ b/src/test/isolation/specs/merge-update.spec @@ -78,6 +78,7 @@ step "pa_merge3" } step "c1" { COMMIT; } step "a1" { ABORT; } +step "s1beginrr" { BEGIN ISOLATION LEVEL REPEATABLE READ; } session "s2" setup @@ -167,3 +168,4 @@ permutation "pa_merge2" "c1" "pa_merge2a" "pa_select2" "c2" # succeeds permutation "pa_merge3" "pa_merge2b_when" "c1" "pa_select2" "c2" # WHEN not satisfied by updated tuple permutation "pa_merge1" "pa_merge2b_when" "c1" "pa_select2" "c2" # WHEN satisfied by updated tuple permutation "pa_merge1" "pa_merge2c_dup" "c1" "a2" +permutation "merge2a" "c1" "s1beginrr" "merge1" "c2" diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 317fedddb0c..32c02b8f6fb 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -15,6 +15,7 @@ SUBDIRS = \ snapshot_too_old \ spgist_name_ops \ test_bloomfilter \ + test_custom_types \ test_ddl_deparse \ test_escape \ test_extensions \ diff --git a/src/test/modules/test_custom_types/.gitignore b/src/test/modules/test_custom_types/.gitignore new file mode 100644 index 00000000000..5dcb3ff9723 --- /dev/null +++ b/src/test/modules/test_custom_types/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/src/test/modules/test_custom_types/Makefile b/src/test/modules/test_custom_types/Makefile new file mode 100644 index 00000000000..e1b582b26ea --- /dev/null +++ b/src/test/modules/test_custom_types/Makefile @@ -0,0 +1,20 @@ +# src/test/modules/test_custom_types/Makefile + +MODULES = test_custom_types + +EXTENSION = test_custom_types +DATA = test_custom_types--1.0.sql +PGFILEDESC = "test_custom_types - tests for dummy custom types" + +REGRESS = test_custom_types + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_custom_types +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_custom_types/README b/src/test/modules/test_custom_types/README new file mode 100644 index 00000000000..a37d2db577e --- /dev/null +++ b/src/test/modules/test_custom_types/README @@ -0,0 +1,9 @@ +test_custom_types +================= + +This module contains a set of custom data types, with some of the following +patterns: + +- typanalyze function registered to a custom type, returning false. +- typanalyze function registered to a custom type, registering invalid stats + data. diff --git a/src/test/modules/test_custom_types/expected/test_custom_types.out b/src/test/modules/test_custom_types/expected/test_custom_types.out new file mode 100644 index 00000000000..2fb6b0b10ee --- /dev/null +++ b/src/test/modules/test_custom_types/expected/test_custom_types.out @@ -0,0 +1,174 @@ +-- Tests with various custom types +CREATE EXTENSION test_custom_types; +-- Test comparison functions +SELECT '42'::int_custom = '42'::int_custom AS eq_test; + eq_test +--------- + t +(1 row) + +SELECT '42'::int_custom <> '42'::int_custom AS nt_test; + nt_test +--------- + f +(1 row) + +SELECT '42'::int_custom < '100'::int_custom AS lt_test; + lt_test +--------- + t +(1 row) + +SELECT '100'::int_custom > '42'::int_custom AS gt_test; + gt_test +--------- + t +(1 row) + +SELECT '42'::int_custom <= '100'::int_custom AS le_test; + le_test +--------- + t +(1 row) + +SELECT '100'::int_custom >= '42'::int_custom AS ge_test; + ge_test +--------- + t +(1 row) + +-- Create a table with the int_custom type +CREATE TABLE test_table ( + id int, + data int_custom +); +INSERT INTO test_table VALUES (1, '42'), (2, '100'), (3, '200'); +-- Verify data was inserted correctly +SELECT * FROM test_table ORDER BY id; + id | data +----+------ + 1 | 42 + 2 | 100 + 3 | 200 +(3 rows) + +-- Dummy function used for expression evaluations. +-- Note that this function does not use a SQL-standard function body on +-- purpose, so as external statistics can be loaded from it. +CREATE OR REPLACE FUNCTION func_int_custom (p_value int_custom) + RETURNS int_custom LANGUAGE plpgsql AS $$ + BEGIN + RETURN p_value; + END; $$; +-- Switch type to use typanalyze function that always returns false. +ALTER TYPE int_custom SET (ANALYZE = int_custom_typanalyze_false); +-- Extended statistics with an attribute that cannot be analyzed. +-- This includes all statistics kinds. +CREATE STATISTICS test_stats ON data, id FROM test_table; +-- Computation of the stats fails, no data generated. +ANALYZE test_table; +WARNING: statistics object "public.test_stats" could not be computed for relation "public.test_table" +SELECT stxname, stxdexpr IS NULL as expr_stats_is_null + FROM pg_statistic_ext s + LEFT JOIN pg_statistic_ext_data d ON s.oid = d.stxoid + WHERE stxname = 'test_stats'; + stxname | expr_stats_is_null +------------+-------------------- + test_stats | t +(1 row) + +DROP STATISTICS test_stats; +-- Extended statistics with an expression that cannot be analyzed. +CREATE STATISTICS test_stats ON func_int_custom(data), (id) FROM test_table; +-- Computation of the stats fails, no data generated. +ANALYZE test_table; +WARNING: statistics object "public.test_stats" could not be computed for relation "public.test_table" +SELECT stxname, stxdexpr IS NULL as expr_stats_is_null + FROM pg_statistic_ext s + LEFT JOIN pg_statistic_ext_data d ON s.oid = d.stxoid + WHERE stxname = 'test_stats'; + stxname | expr_stats_is_null +------------+-------------------- + test_stats | t +(1 row) + +DROP STATISTICS test_stats; +-- There should be no data stored for the expression. +SELECT tablename, + statistics_name, + null_frac, + avg_width + FROM pg_stats_ext_exprs WHERE statistics_name = 'test_stats' \gx +(0 rows) + +-- Switch type to use typanalyze function that generates invalid data. +ALTER TYPE int_custom SET (ANALYZE = int_custom_typanalyze_invalid); +-- Extended statistics with an attribute that generates invalid stats. +CREATE STATISTICS test_stats ON data, id FROM test_table; +-- Computation of the stats fails, no data generated. +ANALYZE test_table; +SELECT stxname, stxdexpr IS NULL as expr_stats_is_null + FROM pg_statistic_ext s + LEFT JOIN pg_statistic_ext_data d ON s.oid = d.stxoid + WHERE stxname = 'test_stats'; + stxname | expr_stats_is_null +------------+-------------------- + test_stats | t +(1 row) + +DROP STATISTICS test_stats; +-- Extended statistics with an expression that generates invalid data. +CREATE STATISTICS test_stats ON func_int_custom(data), (id) FROM test_table; +-- Computation of the stats fails, some data generated. +ANALYZE test_table; +SELECT stxname, stxdexpr IS NULL as expr_stats_is_null + FROM pg_statistic_ext s + LEFT JOIN pg_statistic_ext_data d ON s.oid = d.stxoid + WHERE stxname = 'test_stats'; + stxname | expr_stats_is_null +------------+-------------------- + test_stats | f +(1 row) + +-- There should be some data stored for the expression, stored as NULL. +SELECT tablename, + statistics_name, + null_frac, + avg_width, + n_distinct, + most_common_vals, + most_common_freqs, + histogram_bounds, + correlation, + most_common_elems, + most_common_elem_freqs, + elem_count_histogram + FROM pg_stats_ext_exprs WHERE statistics_name = 'test_stats' \gx +-[ RECORD 1 ]----------+----------- +tablename | test_table +statistics_name | test_stats +null_frac | +avg_width | +n_distinct | +most_common_vals | +most_common_freqs | +histogram_bounds | +correlation | +most_common_elems | +most_common_elem_freqs | +elem_count_histogram | + +-- Run a query able to load the extended stats, including the NULL data. +SELECT COUNT(*) FROM test_table GROUP BY (func_int_custom(data)); + count +------- + 1 + 1 + 1 +(3 rows) + +DROP STATISTICS test_stats; +-- Cleanup +DROP FUNCTION func_int_custom; +DROP TABLE test_table; +DROP EXTENSION test_custom_types; diff --git a/src/test/modules/test_custom_types/meson.build b/src/test/modules/test_custom_types/meson.build new file mode 100644 index 00000000000..3d4f433dd51 --- /dev/null +++ b/src/test/modules/test_custom_types/meson.build @@ -0,0 +1,33 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +test_custom_types_sources = files( + 'test_custom_types.c', +) + +if host_system == 'windows' + test_custom_types_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_custom_types', + '--FILEDESC', 'test_custom_types - tests for dummy custom types',]) +endif + +test_custom_types = shared_module('test_custom_types', + test_custom_types_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_custom_types + +test_install_data += files( + 'test_custom_types.control', + 'test_custom_types--1.0.sql', +) + +tests += { + 'name': 'test_custom_types', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'test_custom_types', + ], + }, +} diff --git a/src/test/modules/test_custom_types/sql/test_custom_types.sql b/src/test/modules/test_custom_types/sql/test_custom_types.sql new file mode 100644 index 00000000000..206a17c667f --- /dev/null +++ b/src/test/modules/test_custom_types/sql/test_custom_types.sql @@ -0,0 +1,104 @@ +-- Tests with various custom types + +CREATE EXTENSION test_custom_types; + +-- Test comparison functions +SELECT '42'::int_custom = '42'::int_custom AS eq_test; +SELECT '42'::int_custom <> '42'::int_custom AS nt_test; +SELECT '42'::int_custom < '100'::int_custom AS lt_test; +SELECT '100'::int_custom > '42'::int_custom AS gt_test; +SELECT '42'::int_custom <= '100'::int_custom AS le_test; +SELECT '100'::int_custom >= '42'::int_custom AS ge_test; + +-- Create a table with the int_custom type +CREATE TABLE test_table ( + id int, + data int_custom +); +INSERT INTO test_table VALUES (1, '42'), (2, '100'), (3, '200'); + +-- Verify data was inserted correctly +SELECT * FROM test_table ORDER BY id; + +-- Dummy function used for expression evaluations. +-- Note that this function does not use a SQL-standard function body on +-- purpose, so as external statistics can be loaded from it. +CREATE OR REPLACE FUNCTION func_int_custom (p_value int_custom) + RETURNS int_custom LANGUAGE plpgsql AS $$ + BEGIN + RETURN p_value; + END; $$; + +-- Switch type to use typanalyze function that always returns false. +ALTER TYPE int_custom SET (ANALYZE = int_custom_typanalyze_false); + +-- Extended statistics with an attribute that cannot be analyzed. +-- This includes all statistics kinds. +CREATE STATISTICS test_stats ON data, id FROM test_table; +-- Computation of the stats fails, no data generated. +ANALYZE test_table; +SELECT stxname, stxdexpr IS NULL as expr_stats_is_null + FROM pg_statistic_ext s + LEFT JOIN pg_statistic_ext_data d ON s.oid = d.stxoid + WHERE stxname = 'test_stats'; +DROP STATISTICS test_stats; + +-- Extended statistics with an expression that cannot be analyzed. +CREATE STATISTICS test_stats ON func_int_custom(data), (id) FROM test_table; +-- Computation of the stats fails, no data generated. +ANALYZE test_table; +SELECT stxname, stxdexpr IS NULL as expr_stats_is_null + FROM pg_statistic_ext s + LEFT JOIN pg_statistic_ext_data d ON s.oid = d.stxoid + WHERE stxname = 'test_stats'; +DROP STATISTICS test_stats; +-- There should be no data stored for the expression. +SELECT tablename, + statistics_name, + null_frac, + avg_width + FROM pg_stats_ext_exprs WHERE statistics_name = 'test_stats' \gx + +-- Switch type to use typanalyze function that generates invalid data. +ALTER TYPE int_custom SET (ANALYZE = int_custom_typanalyze_invalid); + +-- Extended statistics with an attribute that generates invalid stats. +CREATE STATISTICS test_stats ON data, id FROM test_table; +-- Computation of the stats fails, no data generated. +ANALYZE test_table; +SELECT stxname, stxdexpr IS NULL as expr_stats_is_null + FROM pg_statistic_ext s + LEFT JOIN pg_statistic_ext_data d ON s.oid = d.stxoid + WHERE stxname = 'test_stats'; +DROP STATISTICS test_stats; + +-- Extended statistics with an expression that generates invalid data. +CREATE STATISTICS test_stats ON func_int_custom(data), (id) FROM test_table; +-- Computation of the stats fails, some data generated. +ANALYZE test_table; +SELECT stxname, stxdexpr IS NULL as expr_stats_is_null + FROM pg_statistic_ext s + LEFT JOIN pg_statistic_ext_data d ON s.oid = d.stxoid + WHERE stxname = 'test_stats'; +-- There should be some data stored for the expression, stored as NULL. +SELECT tablename, + statistics_name, + null_frac, + avg_width, + n_distinct, + most_common_vals, + most_common_freqs, + histogram_bounds, + correlation, + most_common_elems, + most_common_elem_freqs, + elem_count_histogram + FROM pg_stats_ext_exprs WHERE statistics_name = 'test_stats' \gx +-- Run a query able to load the extended stats, including the NULL data. +SELECT COUNT(*) FROM test_table GROUP BY (func_int_custom(data)); +DROP STATISTICS test_stats; + +-- Cleanup +DROP FUNCTION func_int_custom; +DROP TABLE test_table; +DROP EXTENSION test_custom_types; diff --git a/src/test/modules/test_custom_types/test_custom_types--1.0.sql b/src/test/modules/test_custom_types/test_custom_types--1.0.sql new file mode 100644 index 00000000000..ce0e905d636 --- /dev/null +++ b/src/test/modules/test_custom_types/test_custom_types--1.0.sql @@ -0,0 +1,164 @@ +/* src/test/modules/test_custom_types/test_custom_types--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_custom_types" to load this file. \quit + +-- +-- Input/output functions for int_custom type +-- +CREATE FUNCTION int_custom_in(cstring) +RETURNS int_custom +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION int_custom_out(int_custom) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- +-- Typanalyze function that returns false +-- +CREATE FUNCTION int_custom_typanalyze_false(internal) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +-- +-- Typanalyze function that returns invalid stats +-- +CREATE FUNCTION int_custom_typanalyze_invalid(internal) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +-- +-- The int_custom type definition +-- +-- This type is identical to int4 in storage, and is used in subsequent +-- tests to have different properties. +-- +CREATE TYPE int_custom ( + INPUT = int_custom_in, + OUTPUT = int_custom_out, + LIKE = int4 +); + +-- +-- Comparison functions for int_custom +-- +-- These are required to create a btree operator class, which is needed +-- for the type to be usable in extended statistics objects. +-- +CREATE FUNCTION int_custom_eq(int_custom, int_custom) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION int_custom_ne(int_custom, int_custom) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION int_custom_lt(int_custom, int_custom) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION int_custom_le(int_custom, int_custom) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION int_custom_gt(int_custom, int_custom) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION int_custom_ge(int_custom, int_custom) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION int_custom_cmp(int_custom, int_custom) +RETURNS integer +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Operators for int_custom, for btree operator class +CREATE OPERATOR = ( + LEFTARG = int_custom, + RIGHTARG = int_custom, + FUNCTION = int_custom_eq, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE OPERATOR <> ( + LEFTARG = int_custom, + RIGHTARG = int_custom, + FUNCTION = int_custom_ne, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE OPERATOR < ( + LEFTARG = int_custom, + RIGHTARG = int_custom, + FUNCTION = int_custom_lt, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = int_custom, + RIGHTARG = int_custom, + FUNCTION = int_custom_le, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +CREATE OPERATOR > ( + LEFTARG = int_custom, + RIGHTARG = int_custom, + FUNCTION = int_custom_gt, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = int_custom, + RIGHTARG = int_custom, + FUNCTION = int_custom_ge, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargesel, + JOIN = scalargejoinsel +); + +-- +-- Btree operator class for int_custom +-- +-- This is required for the type to be usable in extended statistics objects, +-- for attributes and expressions. +-- +CREATE OPERATOR CLASS int_custom_ops + DEFAULT FOR TYPE int_custom USING btree AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 int_custom_cmp(int_custom, int_custom); diff --git a/src/test/modules/test_custom_types/test_custom_types.c b/src/test/modules/test_custom_types/test_custom_types.c new file mode 100644 index 00000000000..67e25a80b56 --- /dev/null +++ b/src/test/modules/test_custom_types/test_custom_types.c @@ -0,0 +1,184 @@ +/*-------------------------------------------------------------------------- + * + * test_custom_types.c + * Test module for a set of functions for custom types. + * + * The custom type used in this module is similar to int4 for simplicity, + * except that it is able to use various typanalyze functions to enforce + * check patterns with ANALYZE. + * + * Copyright (c) 1996-2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_custom_types/test_custom_types.c + * + *-------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "commands/vacuum.h" +#include "utils/builtins.h" + +PG_MODULE_MAGIC; + +/* Function declarations */ +PG_FUNCTION_INFO_V1(int_custom_in); +PG_FUNCTION_INFO_V1(int_custom_out); +PG_FUNCTION_INFO_V1(int_custom_typanalyze_false); +PG_FUNCTION_INFO_V1(int_custom_typanalyze_invalid); +PG_FUNCTION_INFO_V1(int_custom_eq); +PG_FUNCTION_INFO_V1(int_custom_ne); +PG_FUNCTION_INFO_V1(int_custom_lt); +PG_FUNCTION_INFO_V1(int_custom_le); +PG_FUNCTION_INFO_V1(int_custom_gt); +PG_FUNCTION_INFO_V1(int_custom_ge); +PG_FUNCTION_INFO_V1(int_custom_cmp); + +/* + * int_custom_in - input function for int_custom type + * + * Converts a string to a int_custom (which is just an int32 internally). + */ +Datum +int_custom_in(PG_FUNCTION_ARGS) +{ + char *num = PG_GETARG_CSTRING(0); + + PG_RETURN_INT32(pg_strtoint32(num)); +} + +/* + * int_custom_out - output function for int_custom type + * + * Converts a int_custom to a string. + */ +Datum +int_custom_out(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + char *result = (char *) palloc(12); /* sign, 10 digits, '\0' */ + + pg_ltoa(arg1, result); + PG_RETURN_CSTRING(result); +} + +/* + * int_custom_typanalyze_false - typanalyze function that returns false + * + * This function returns false, to simulate a type that cannot be analyzed. + */ +Datum +int_custom_typanalyze_false(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(false); +} + +/* + * Callback used to compute invalid statistics. + */ +static void +int_custom_invalid_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, + int samplerows, double totalrows) +{ + /* We are not valid, and do not want to be. */ + stats->stats_valid = false; +} + +/* + * int_custom_typanalyze_invalid + * + * This function sets some invalid stats data, letting the caller know that + * we are safe for an analyze, returning true. + */ +Datum +int_custom_typanalyze_invalid(PG_FUNCTION_ARGS) +{ + VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0); + Form_pg_attribute attr = stats->attr; + + /* If the attstattarget column is negative, use the default value */ + /* NB: it is okay to scribble on stats->attr since it's a copy */ + if (attr->attstattarget < 0) + attr->attstattarget = default_statistics_target; + + /* Buggy number, no need to care as long as it is positive */ + stats->minrows = 300; + + /* Set callback to compute some invalid stats */ + stats->compute_stats = int_custom_invalid_stats; + + PG_RETURN_BOOL(true); +} + +/* + * Comparison functions for int_custom type + */ +Datum +int_custom_eq(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 == arg2); +} + +Datum +int_custom_ne(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 != arg2); +} + +Datum +int_custom_lt(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 < arg2); +} + +Datum +int_custom_le(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 <= arg2); +} + +Datum +int_custom_gt(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 > arg2); +} + +Datum +int_custom_ge(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 >= arg2); +} + +Datum +int_custom_cmp(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + if (arg1 < arg2) + PG_RETURN_INT32(-1); + else if (arg1 > arg2) + PG_RETURN_INT32(1); + else + PG_RETURN_INT32(0); +} diff --git a/src/test/modules/test_custom_types/test_custom_types.control b/src/test/modules/test_custom_types/test_custom_types.control new file mode 100644 index 00000000000..d25a74ef5c4 --- /dev/null +++ b/src/test/modules/test_custom_types/test_custom_types.control @@ -0,0 +1,5 @@ +# test_custom_types extension +comment = 'Tests for dummy custom types' +default_version = '1.0' +module_pathname = '$libdir/test_custom_types' +relocatable = true diff --git a/src/test/modules/test_ddl_deparse/Makefile b/src/test/modules/test_ddl_deparse/Makefile index 3a57a95c849..4576ed31e20 100644 --- a/src/test/modules/test_ddl_deparse/Makefile +++ b/src/test/modules/test_ddl_deparse/Makefile @@ -27,6 +27,7 @@ REGRESS = test_ddl_deparse \ alter_type_enum \ opfamily \ defprivs \ + textsearch \ matviews EXTRA_INSTALL = contrib/pg_stat_statements diff --git a/src/test/modules/test_ddl_deparse/expected/textsearch.out b/src/test/modules/test_ddl_deparse/expected/textsearch.out new file mode 100644 index 00000000000..da0d89e9704 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/textsearch.out @@ -0,0 +1,5 @@ +CREATE TEXT SEARCH CONFIGURATION evttrig_tscfg (COPY = pg_catalog.simple); +NOTICE: DDL test: type simple, tag CREATE TEXT SEARCH CONFIGURATION +ALTER TEXT SEARCH CONFIGURATION evttrig_tscfg + DROP MAPPING FOR word; +NOTICE: DDL test: type alter text search configuration, tag ALTER TEXT SEARCH CONFIGURATION diff --git a/src/test/modules/test_ddl_deparse/sql/textsearch.sql b/src/test/modules/test_ddl_deparse/sql/textsearch.sql new file mode 100644 index 00000000000..633899a31cb --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/textsearch.sql @@ -0,0 +1,3 @@ +CREATE TEXT SEARCH CONFIGURATION evttrig_tscfg (COPY = pg_catalog.simple); +ALTER TEXT SEARCH CONFIGURATION evttrig_tscfg + DROP MAPPING FOR word; diff --git a/src/test/modules/test_ginpostinglist/test_ginpostinglist.c b/src/test/modules/test_ginpostinglist/test_ginpostinglist.c index 9a230097db1..decc63da34e 100644 --- a/src/test/modules/test_ginpostinglist/test_ginpostinglist.c +++ b/src/test/modules/test_ginpostinglist/test_ginpostinglist.c @@ -72,12 +72,12 @@ test_itemptr_pair(BlockNumber blk, OffsetNumber off, int maxsize) ItemPointerGetOffsetNumber(&decoded_itemptrs[0])); if (ndecoded == 2 && - !ItemPointerEquals(&orig_itemptrs[0], &decoded_itemptrs[0])) + !ItemPointerEquals(&orig_itemptrs[1], &decoded_itemptrs[1])) { elog(ERROR, "mismatch on second itemptr: (%u, %d) vs (%u, %d)", - 0, 1, - ItemPointerGetBlockNumber(&decoded_itemptrs[0]), - ItemPointerGetOffsetNumber(&decoded_itemptrs[0])); + blk, off, + ItemPointerGetBlockNumber(&decoded_itemptrs[1]), + ItemPointerGetOffsetNumber(&decoded_itemptrs[1])); } } diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm index da05ce708db..9062ec2f7b5 100644 --- a/src/test/perl/PostgreSQL/Test/Cluster.pm +++ b/src/test/perl/PostgreSQL/Test/Cluster.pm @@ -286,6 +286,82 @@ sub connstr =pod +=item $node->raw_connect() + +Open a raw TCP or Unix domain socket connection to the server. This is +used by low-level protocol and connection limit tests. + +=cut + +sub raw_connect +{ + my ($self) = @_; + my $pgport = $self->port; + my $pghost = $self->host; + + my $socket; + if ($PostgreSQL::Test::Utils::use_unix_sockets) + { + require IO::Socket::UNIX; + my $path = "$pghost/.s.PGSQL.$pgport"; + + $socket = IO::Socket::UNIX->new( + Type => SOCK_STREAM(), + Peer => $path, + ) or die "Cannot create socket - $IO::Socket::errstr\n"; + } + else + { + $socket = IO::Socket::INET->new( + PeerHost => $pghost, + PeerPort => $pgport, + Proto => 'tcp' + ) or die "Cannot create socket - $IO::Socket::errstr\n"; + } + return $socket; +} + +=pod + +=item $node->raw_connect_works() + +Check if raw_connect() function works on this platform. This should +be called to SKIP any tests that require raw_connect(). + +This tries to connect to the server, to test whether it works or not,, +so the server is up and running. Otherwise this can return 0 even if +there's nothing wrong with raw_connect() itself. + +Notably, raw_connect() does not work on Unix domain sockets on +Strawberry perl 5.26.3.1 on Windows, which we use in Cirrus CI images +as of this writing. It dies with "not implemented on this +architecture". + +=cut + +sub raw_connect_works +{ + my ($self) = @_; + + # If we're using Unix domain sockets, we need a working + # IO::Socket::UNIX implementation. + if ($PostgreSQL::Test::Utils::use_unix_sockets) + { + eval { + my $sock = $self->raw_connect(); + $sock->close(); + }; + if ($@ =~ /not implemented/) + { + diag "IO::Socket::UNIX does not work: $@"; + return 0; + } + } + return 1; +} + +=pod + =item $node->group_access() Does the data dir allow group access? diff --git a/src/test/postmaster/.gitignore b/src/test/postmaster/.gitignore new file mode 100644 index 00000000000..871e943d50e --- /dev/null +++ b/src/test/postmaster/.gitignore @@ -0,0 +1,2 @@ +# Generated by test suite +/tmp_check/ diff --git a/src/test/postmaster/Makefile b/src/test/postmaster/Makefile new file mode 100644 index 00000000000..58d2b235830 --- /dev/null +++ b/src/test/postmaster/Makefile @@ -0,0 +1,23 @@ +#------------------------------------------------------------------------- +# +# Makefile for src/test/postmaster +# +# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group +# Portions Copyright (c) 1994, Regents of the University of California +# +# src/test/postmaster/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/test/postmaster +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +check: + $(prove_check) + +installcheck: + $(prove_installcheck) + +clean distclean maintainer-clean: + rm -rf tmp_check diff --git a/src/test/postmaster/README b/src/test/postmaster/README new file mode 100644 index 00000000000..7e47bf5cff0 --- /dev/null +++ b/src/test/postmaster/README @@ -0,0 +1,27 @@ +src/test/postmaster/README + +Regression tests for postmaster +=============================== + +This directory contains a test suite for postmaster's handling of +connections, connection limits, and startup/shutdown sequence. + + +Running the tests +================= + +NOTE: You must have given the --enable-tap-tests argument to configure. + +Run + make check +or + make installcheck +You can use "make installcheck" if you previously did "make install". +In that case, the code in the installation tree is tested. With +"make check", a temporary installation tree is built from the current +sources and then tested. + +Either way, this test initializes, starts, and stops a test Postgres +cluster. + +See src/test/perl/README for more info about running these tests. diff --git a/src/test/postmaster/t/004_negotiate.pl b/src/test/postmaster/t/004_negotiate.pl new file mode 100644 index 00000000000..f4fbae41137 --- /dev/null +++ b/src/test/postmaster/t/004_negotiate.pl @@ -0,0 +1,81 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +# Test the negotiation of combined SSL and GSS requests. This test +# relies on both SSL and GSS requests to be rejected first, followed +# by more requests. + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; +use Time::HiRes qw(usleep); + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->append_conf('postgresql.conf', "log_min_messages = debug2"); +$node->append_conf('postgresql.conf', + "log_connections = 'on'"); +$node->start; + +if (!$node->raw_connect_works()) +{ + plan skip_all => "this test requires working raw_connect()"; +} + +my $sock = $node->raw_connect(); + +# SSLRequest: packet length followed by NEGOTIATE_SSL_CODE. +my $ssl_request = pack("Nnn", 8, 1234, 5679); + +# GSSENCRequest: packet length followed by NEGOTIATE_GSS_CODE. +my $gss_request = pack("Nnn", 8, 1234, 5680); + +# Send SSLRequest, reject or bypass. +$sock->send($ssl_request); +my $reply = ""; +$sock->recv($reply, 1); +if ($reply ne 'N') +{ + $sock->close(); + plan skip_all => + "server accepted SSL; test requires SSL to be rejected"; +} + +# Send GSSENCRequest, reject or bypass test. +$sock->send($gss_request); +$reply = ""; +$sock->recv($reply, 1); +if ($reply ne 'N') +{ + $sock->close(); + plan skip_all => + "server accepted GSS; test requires GSS to be rejected"; +} + +my $log_offset = -s $node->logfile; + +# Send a second SSLRequest, now that we know that both SSL and GSS have +# been rejected for this connection. We are done with both requests, so +# extra requests will be rejected and fail with an invalid protocol +# version, and the connection should be closed by the server. +$sock->send($ssl_request); + +# Try to read a response, there should be nothing, and certainly not an +# extra 'N' message indicating a rejection. +$reply = ""; +my $bytes = $sock->recv($reply, 1024); +isnt($reply, 'N', + "server does not re-enter SSL negotiation after SSL+GSS were both tried"); + +$sock->close(); +$node->wait_for_log(qr/FATAL: .* unsupported frontend protocol 1234.5679/, + $log_offset); + +# Check extra connection with a simple query. +my $result = $node->safe_psql('postgres', 'select 1;'); +is($result, '1', 'server able to accept connection'); + +$node->stop; + +done_testing(); diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl index 24f048aca6b..769c18d841d 100644 --- a/src/test/recovery/t/004_timeline_switch.pl +++ b/src/test/recovery/t/004_timeline_switch.pl @@ -37,11 +37,10 @@ $node_primary->safe_psql('postgres', "CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a"); -# Wait until standby has replayed enough data on standby 1 -$node_primary->wait_for_catchup($node_standby_1); - -# Stop and remove primary -$node_primary->teardown_node; +# Cleanly stop and remove primary. A clean stop is required so as all +# the records generated on the primary are received and flushed by the two +# standbys. +$node_primary->stop; # promote standby 1 using "pg_promote", switching it to a new timeline my $psql_out = ''; diff --git a/src/test/recovery/t/009_twophase.pl b/src/test/recovery/t/009_twophase.pl index ad9b5371dd0..61fa2b19361 100644 --- a/src/test/recovery/t/009_twophase.pl +++ b/src/test/recovery/t/009_twophase.pl @@ -218,7 +218,7 @@ sub configure_and_reload SAVEPOINT s1; INSERT INTO t_009_tbl VALUES (22, 'issued to ${cur_primary_name}'); PREPARE TRANSACTION 'xact_009_10';"); -$cur_primary->teardown_node; +$cur_primary->stop; $cur_standby->promote; # change roles diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 361619b015e..11b394c6d71 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -3735,6 +3735,16 @@ Referenced by: ALTER TABLE test_add_column ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10); NOTICE: column "c5" of relation "test_add_column" already exists, skipping +ALTER TABLE test_add_column + ADD c6 integer; -- omit COLUMN +ALTER TABLE test_add_column + ADD IF NOT EXISTS c6 integer; +NOTICE: column "c6" of relation "test_add_column" already exists, skipping +ALTER TABLE test_add_column + DROP c6; -- omit COLUMN +ALTER TABLE test_add_column + DROP IF EXISTS c6; +NOTICE: column "c6" of relation "test_add_column" does not exist, skipping \d test_add_column* Table "public.test_add_column" Column | Type | Collation | Nullable | Default diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out index b7d78e32ee0..1c4d9ab16c9 100644 --- a/src/test/regress/expected/collate.icu.utf8.out +++ b/src/test/regress/expected/collate.icu.utf8.out @@ -1391,6 +1391,310 @@ SELECT string_to_array('ABCDEFGHI' COLLATE case_sensitive, NULL, 'b'); {A,B,C,D,E,F,G,H,I} (1 row) +-- +-- A unique index under one collation does not prove uniqueness under +-- another, so the planner must not use such a proof for any optimization. +-- +-- Ensure that we do not use inner-unique join execution +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM test1cs t1, test3cs t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + QUERY PLAN +---------------------------------------------------------------------- + Sort + Output: t1.x, t2.x + Sort Key: t1.x COLLATE case_sensitive, t2.x COLLATE case_sensitive + -> Hash Join + Output: t1.x, t2.x + Hash Cond: ((t2.x)::text = (t1.x)::text) + -> Seq Scan on collate_tests.test3cs t2 + Output: t2.x + -> Hash + Output: t1.x + -> Seq Scan on collate_tests.test1cs t1 + Output: t1.x +(12 rows) + +SELECT * FROM test1cs t1, test3cs t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + x | x +-----+----- + abc | abc + abc | ABC + ABC | abc + ABC | ABC + def | def + ghi | ghi +(6 rows) + +-- Ensure that left-join is not removed +EXPLAIN (COSTS OFF) +SELECT t1.* FROM test3cs t1 + LEFT JOIN test3cs t2 ON t1.x = t2.x COLLATE case_insensitive +ORDER BY 1; + QUERY PLAN +------------------------------------------ + Sort + Sort Key: t1.x COLLATE case_sensitive + -> Hash Left Join + Hash Cond: (t1.x = (t2.x)::text) + -> Seq Scan on test3cs t1 + -> Hash + -> Seq Scan on test3cs t2 +(7 rows) + +SELECT t1.* FROM test3cs t1 + LEFT JOIN test3cs t2 ON t1.x = t2.x COLLATE case_insensitive +ORDER BY 1; + x +----- + abc + abc + ABC + ABC + def + ghi +(6 rows) + +-- Ensure that self-join is not removed +EXPLAIN (COSTS OFF) +SELECT * FROM test3cs t1, test3cs t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + QUERY PLAN +---------------------------------------------------------------------- + Sort + Sort Key: t1.x COLLATE case_sensitive, t2.x COLLATE case_sensitive + -> Hash Join + Hash Cond: ((t1.x)::text = (t2.x)::text) + -> Seq Scan on test3cs t1 + -> Hash + -> Seq Scan on test3cs t2 +(7 rows) + +SELECT * FROM test3cs t1, test3cs t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + x | x +-----+----- + abc | abc + abc | ABC + ABC | abc + ABC | ABC + def | def + ghi | ghi +(6 rows) + +-- Ensure that semijoin is not reduced to innerjoin +EXPLAIN (COSTS OFF) +SELECT * FROM test3cs t1 + WHERE EXISTS (SELECT 1 FROM test3cs t2 WHERE t1.x = t2.x COLLATE case_insensitive) +ORDER BY 1; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.x COLLATE case_sensitive + -> Hash Semi Join + Hash Cond: ((t1.x)::text = (t2.x)::text) + -> Seq Scan on test3cs t1 + -> Hash + -> Seq Scan on test3cs t2 +(7 rows) + +SELECT * FROM test3cs t1 + WHERE EXISTS (SELECT 1 FROM test3cs t2 WHERE t1.x = t2.x COLLATE case_insensitive) +ORDER BY 1; + x +----- + abc + ABC + def + ghi +(4 rows) + +-- +-- A DISTINCT / GROUP BY / set-op on a subquery does not prove uniqueness +-- under a different collation, so the planner must not use such a proof for +-- any optimization. +-- +-- Ensure that we do not use inner-unique join execution +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM test1cs t1, (SELECT DISTINCT x FROM test3cs) t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + QUERY PLAN +--------------------------------------------------------------------------- + Sort + Output: t1.x, test3cs.x + Sort Key: t1.x COLLATE case_sensitive, test3cs.x COLLATE case_sensitive + -> Hash Join + Output: t1.x, test3cs.x + Hash Cond: ((t1.x)::text = (test3cs.x)::text) + -> Seq Scan on collate_tests.test1cs t1 + Output: t1.x + -> Hash + Output: test3cs.x + -> HashAggregate + Output: test3cs.x + Group Key: test3cs.x + -> Seq Scan on collate_tests.test3cs + Output: test3cs.x +(15 rows) + +SELECT * FROM test1cs t1, (SELECT DISTINCT x FROM test3cs) t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + x | x +-----+----- + abc | abc + abc | ABC + ABC | abc + ABC | ABC + def | def + ghi | ghi +(6 rows) + +-- Same with GROUP BY +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM test1cs t1, (SELECT x FROM test3cs GROUP BY x) t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + QUERY PLAN +--------------------------------------------------------------------------- + Sort + Output: t1.x, test3cs.x + Sort Key: t1.x COLLATE case_sensitive, test3cs.x COLLATE case_sensitive + -> Hash Join + Output: t1.x, test3cs.x + Hash Cond: ((t1.x)::text = (test3cs.x)::text) + -> Seq Scan on collate_tests.test1cs t1 + Output: t1.x + -> Hash + Output: test3cs.x + -> HashAggregate + Output: test3cs.x + Group Key: test3cs.x + -> Seq Scan on collate_tests.test3cs + Output: test3cs.x +(15 rows) + +SELECT * FROM test1cs t1, (SELECT x FROM test3cs GROUP BY x) t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + x | x +-----+----- + abc | abc + abc | ABC + ABC | abc + ABC | ABC + def | def + ghi | ghi +(6 rows) + +-- Same with set-op +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM test1cs t1, (SELECT x FROM test3cs UNION SELECT x FROM test3cs) t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + QUERY PLAN +--------------------------------------------------------------------------- + Sort + Output: t1.x, test3cs.x + Sort Key: t1.x COLLATE case_sensitive, test3cs.x COLLATE case_sensitive + -> Hash Join + Output: t1.x, test3cs.x + Hash Cond: ((test3cs.x)::text = (t1.x)::text) + -> HashAggregate + Output: test3cs.x + Group Key: test3cs.x + -> Append + -> Seq Scan on collate_tests.test3cs + Output: test3cs.x + -> Seq Scan on collate_tests.test3cs test3cs_1 + Output: test3cs_1.x + -> Hash + Output: t1.x + -> Seq Scan on collate_tests.test1cs t1 + Output: t1.x +(18 rows) + +SELECT * FROM test1cs t1, (SELECT x FROM test3cs UNION SELECT x FROM test3cs) t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + x | x +-----+----- + abc | abc + abc | ABC + ABC | abc + ABC | ABC + def | def + ghi | ghi +(6 rows) + +-- Ensure that left-join is not removed +EXPLAIN (COSTS OFF) +SELECT t1.* FROM test3cs t1 + LEFT JOIN (SELECT DISTINCT x FROM test3cs) t2 ON t1.x = t2.x COLLATE case_insensitive +ORDER BY 1; + QUERY PLAN +----------------------------------------------- + Sort + Sort Key: t1.x COLLATE case_sensitive + -> Hash Left Join + Hash Cond: (t1.x = (test3cs.x)::text) + -> Seq Scan on test3cs t1 + -> Hash + -> HashAggregate + Group Key: test3cs.x + -> Seq Scan on test3cs +(9 rows) + +SELECT t1.* FROM test3cs t1 + LEFT JOIN (SELECT DISTINCT x FROM test3cs) t2 ON t1.x = t2.x COLLATE case_insensitive +ORDER BY 1; + x +----- + abc + abc + ABC + ABC + def + ghi +(6 rows) + +-- Ensure that semijoin is not reduced to innerjoin +EXPLAIN (COSTS OFF) +SELECT * FROM test3cs t1 + WHERE EXISTS (SELECT 1 FROM (SELECT DISTINCT x FROM test3cs) t2 + WHERE t1.x = t2.x COLLATE case_insensitive) +ORDER BY 1; + QUERY PLAN +------------------------------------------------------- + Sort + Sort Key: t1.x COLLATE case_sensitive + -> Hash Semi Join + Hash Cond: ((t1.x)::text = (test3cs.x)::text) + -> Seq Scan on test3cs t1 + -> Hash + -> HashAggregate + Group Key: test3cs.x + -> Seq Scan on test3cs +(9 rows) + +SELECT * FROM test3cs t1 + WHERE EXISTS (SELECT 1 FROM (SELECT DISTINCT x FROM test3cs) t2 + WHERE t1.x = t2.x COLLATE case_insensitive) +ORDER BY 1; + x +----- + abc + ABC + def + ghi +(4 rows) + CREATE TABLE test1ci (x text COLLATE case_insensitive); CREATE TABLE test2ci (x text COLLATE case_insensitive); CREATE TABLE test3ci (x text COLLATE case_insensitive); diff --git a/src/test/regress/expected/compression_pglz.out b/src/test/regress/expected/compression_pglz.out new file mode 100644 index 00000000000..0ef49d42506 --- /dev/null +++ b/src/test/regress/expected/compression_pglz.out @@ -0,0 +1,65 @@ +-- +-- Tests for PGLZ compression +-- +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX +\set regresslib :libdir '/regress' :dlsuffix +CREATE FUNCTION test_pglz_compress(bytea) + RETURNS bytea + AS :'regresslib' LANGUAGE C STRICT; +CREATE FUNCTION test_pglz_decompress(bytea, int4, bool) + RETURNS bytea + AS :'regresslib' LANGUAGE C STRICT; +-- Round-trip with pglz: compress then decompress. +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 400, false) = + decode(repeat('abcd', 100), 'escape') AS roundtrip_ok; + roundtrip_ok +-------------- + t +(1 row) + +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 400, true) = + decode(repeat('abcd', 100), 'escape') AS roundtrip_ok; + roundtrip_ok +-------------- + t +(1 row) + +-- Decompression with rawsize too large, fails to fill the destination +-- buffer. +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 500, true); +ERROR: pglz_decompress failed +-- Decompression with rawsize too small, fails with source not fully +-- consumed. +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 100, true); +ERROR: pglz_decompress failed +-- Corrupted compressed data. Set control bit with read of a match tag, +-- no data follows. +SELECT length(test_pglz_decompress('\x01'::bytea, 1024, false)) AS ctrl_only_len; + ctrl_only_len +--------------- + 0 +(1 row) + +SELECT test_pglz_decompress('\x01'::bytea, 1024, true); +ERROR: pglz_decompress failed +-- Corrupted compressed data. Set control bit with read of a match tag, +-- 1 byte follows. +SELECT test_pglz_decompress('\x01ff'::bytea, 1024, false); +ERROR: pglz_decompress failed +SELECT test_pglz_decompress('\x01ff'::bytea, 1024, true); +ERROR: pglz_decompress failed +-- Corrupted compressed data. Set control bit with match tag where length +-- nibble is 3 bytes (extended length), no data follows. +SELECT test_pglz_decompress('\x010f01'::bytea, 1024, false); +ERROR: pglz_decompress failed +SELECT test_pglz_decompress('\x010f01'::bytea, 1024, true); +ERROR: pglz_decompress failed +-- Clean up +DROP FUNCTION test_pglz_compress; +DROP FUNCTION test_pglz_decompress; diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index d082b0b6b41..e3e771c900e 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -32,6 +32,20 @@ COMMENT ON INDEX six_wrong IS 'bad index'; ERROR: relation "six_wrong" does not exist COMMENT ON INDEX six IS 'good index'; COMMENT ON INDEX six IS NULL; +SELECT obj_description('six'::regclass, 'pg_class') IS NULL AS six_comment_is_null; + six_comment_is_null +--------------------- + t +(1 row) + +COMMENT ON INDEX six IS 'add the comment back'; +COMMENT ON INDEX six IS ''; -- empty string removes the comment, same as NULL +SELECT obj_description('six'::regclass, 'pg_class') IS NULL AS six_comment_is_null; + six_comment_is_null +--------------------- + t +(1 row) + -- -- BTREE partial indices -- diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out index 4e67d727603..53597c5c8d9 100644 --- a/src/test/regress/expected/create_role.out +++ b/src/test/regress/expected/create_role.out @@ -52,6 +52,34 @@ CREATE ROLE regress_plainrole; CREATE ROLE regress_rolecreator CREATEROLE; -- ok, roles with CREATEROLE can create new roles with privilege they lack CREATE ROLE regress_tenant CREATEDB CREATEROLE LOGIN INHERIT CONNECTION LIMIT 5; +COMMENT ON ROLE regress_tenant IS 'some comment'; +SELECT shobj_description('regress_tenant'::regrole, 'pg_authid') IS NOT NULL AS has_comment; + has_comment +------------- + t +(1 row) + +COMMENT ON ROLE regress_tenant IS NULL; +SELECT shobj_description('regress_tenant'::regrole, 'pg_authid') IS NULL AS no_comment; + no_comment +------------ + t +(1 row) + +COMMENT ON ROLE regress_tenant IS 'add the comment back'; +SELECT shobj_description('regress_tenant'::regrole, 'pg_authid') IS NOT NULL AS has_comment; + has_comment +------------- + t +(1 row) + +COMMENT ON ROLE regress_tenant IS ''; -- empty string removes the comment, same as NULL +SELECT shobj_description('regress_tenant'::regrole, 'pg_authid') IS NULL AS no_comment; + no_comment +------------ + t +(1 row) + -- ok, regress_tenant can create objects within the database SET SESSION AUTHORIZATION regress_tenant; CREATE TABLE tenant_table (i integer); diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out index 6bfc6d040ff..f49f2684a3a 100644 --- a/src/test/regress/expected/create_table_like.out +++ b/src/test/regress/expected/create_table_like.out @@ -533,3 +533,34 @@ DROP TYPE ctlty1; DROP VIEW ctlv1; DROP TABLE IF EXISTS ctlt4, ctlt10, ctlt11, ctlt11a, ctlt12; NOTICE: table "ctlt10" does not exist, skipping +-- LIKE ... INCLUDING STATISTICS with dropped columns in the parent, +-- so stxkeys attnums are not contiguous. +CREATE TABLE ctl_stats3_parent (a int, b int, c int); +ALTER TABLE ctl_stats3_parent DROP COLUMN b; +CREATE STATISTICS ctl_stats3_stat ON a, c FROM ctl_stats3_parent; +CREATE TABLE ctl_stats3_child (LIKE ctl_stats3_parent INCLUDING STATISTICS); +CREATE TABLE ctl_stats4_parent (a int, b int, c int, d int); +ALTER TABLE ctl_stats4_parent DROP COLUMN b; +CREATE STATISTICS ctl_stats4_stat ON a, c FROM ctl_stats4_parent; +CREATE TABLE ctl_stats4_child (LIKE ctl_stats4_parent INCLUDING STATISTICS); +SELECT s.stxrelid::regclass AS relation, + array_agg(a.attname ORDER BY u.ord) AS stats_columns +FROM pg_statistic_ext s +CROSS JOIN LATERAL + unnest(s.stxkeys::int2[]) WITH ORDINALITY AS u(attnum, ord) +JOIN pg_attribute a + ON a.attrelid = s.stxrelid AND a.attnum = u.attnum +WHERE s.stxrelid IN ('ctl_stats3_child'::regclass, + 'ctl_stats4_child'::regclass) +GROUP BY s.stxrelid +ORDER BY s.stxrelid::regclass::text; + relation | stats_columns +------------------+--------------- + ctl_stats3_child | {a,c} + ctl_stats4_child | {a,c} +(2 rows) + +DROP TABLE ctl_stats3_parent; +DROP TABLE ctl_stats3_child; +DROP TABLE ctl_stats4_parent; +DROP TABLE ctl_stats4_child; diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out index 889489ab895..299c47811e3 100644 --- a/src/test/regress/expected/expressions.out +++ b/src/test/regress/expected/expressions.out @@ -353,42 +353,177 @@ default for type myint using hash as operator 1 = (myint, myint), function 1 myinthash(myint); create table inttest (a myint); -insert into inttest values(1::myint),(null); --- try an array with enough elements to cause hashing -select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null); - a ---- - 1 - -(2 rows) +insert into inttest values (null), (0::myint), (1::myint); +-- Test EEOP_HASHED_SCALARARRAYOP against EEOP_SCALARARRAYOP. Ensure the +-- result of non-hashed vs hashed is the same. +select + a, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed +from inttest; + a | not_hashed | hashed +---+------------+-------- + | | + 0 | f | f + 1 | t | t +(3 rows) -select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null); - a ---- -(0 rows) - -select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null); - a ---- -(0 rows) - --- ensure the result matched with the non-hashed version. We simply remove --- some array elements so that we don't reach the hashing threshold. -select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null); - a ---- - 1 - -(2 rows) +select + a, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed + from inttest; + a | not_hashed | hashed +---+------------+-------- + | t | t + 0 | | + 1 | t | t +(3 rows) -select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null); - a ---- -(0 rows) +select + a, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed +from inttest; + a | not_hashed | hashed +---+------------+-------- + | | + 0 | t | t + 1 | f | f +(3 rows) -select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null); - a ---- -(0 rows) +select + a, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed +from inttest; + a | not_hashed | hashed +---+------------+-------- + | f | f + 0 | | + 1 | f | f +(3 rows) + +-- Now make the equal function return false when given two NULLs +create or replace function myinteq(myint, myint) returns bool as $$ +begin + if $1 is null and $2 is null then + return false; + else + return $1::int = $2::int; + end if; +end; +$$ language plpgsql immutable; +-- And try the same again to ensure EEOP_HASHED_SCALARARRAYOP does the same +-- thing as EEOP_SCALARARRAYOP. +select + a, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed +from inttest; + a | not_hashed | hashed +---+------------+-------- + | | + 0 | f | f + 1 | t | t +(3 rows) + +select + a, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed + from inttest; + a | not_hashed | hashed +---+------------+-------- + | | + 0 | | + 1 | t | t +(3 rows) + +select + a, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed +from inttest; + a | not_hashed | hashed +---+------------+-------- + | | + 0 | t | t + 1 | f | f +(3 rows) + +select + a, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed +from inttest; + a | not_hashed | hashed +---+------------+-------- + | | + 0 | | + 1 | f | f +(3 rows) + +-- Try again with an equality function that treats NULLs as equal to 0. +create or replace function myinteq(myint, myint) returns bool as $$ +begin + if $1 is null and $2 is null then + return false; + else + return coalesce($1::int,0) = coalesce($2::int, 0); + end if; +end; +$$ language plpgsql immutable; +select + a, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed, + a in (0::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed_zero, + a in (0::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed_zero +from inttest; + a | not_hashed | hashed | not_hashed_zero | hashed_zero +---+------------+--------+-----------------+------------- + | f | f | t | t + 0 | f | f | t | t + 1 | t | t | t | t +(3 rows) + +select + a, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed + from inttest; + a | not_hashed | hashed +---+------------+-------- + | f | f + 0 | t | t + 1 | t | t +(3 rows) + +select + a, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed, + a not in (0::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed_zero, + a not in (0::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed_zero +from inttest; + a | not_hashed | hashed | not_hashed_zero | hashed_zero +---+------------+--------+-----------------+------------- + | t | t | f | f + 0 | t | t | f | f + 1 | f | f | f | f +(3 rows) + +select + a, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed +from inttest; + a | not_hashed | hashed +---+------------+-------- + | t | t + 0 | f | f + 1 | f | f +(3 rows) rollback; diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out index 25e71631a72..94b35323145 100644 --- a/src/test/regress/expected/foreign_data.out +++ b/src/test/regress/expected/foreign_data.out @@ -106,6 +106,13 @@ ERROR: conflicting or redundant options LINE 1: ...GN DATA WRAPPER test_fdw HANDLER test_fdw_handler HANDLER in... ^ CREATE FOREIGN DATA WRAPPER test_fdw HANDLER test_fdw_handler; +-- should preserve dependency on test_fdw_handler +ALTER FOREIGN DATA WRAPPER test_fdw VALIDATOR postgresql_fdw_validator; +WARNING: changing the foreign-data wrapper validator can cause the options for dependent objects to become invalid +DROP FUNCTION test_fdw_handler(); -- ERROR +ERROR: cannot drop function test_fdw_handler() because other objects depend on it +DETAIL: foreign-data wrapper test_fdw depends on function test_fdw_handler() +HINT: Use DROP ... CASCADE to drop the dependent objects too. DROP FOREIGN DATA WRAPPER test_fdw; -- ALTER FOREIGN DATA WRAPPER ALTER FOREIGN DATA WRAPPER foo OPTIONS (nonexistent 'fdw'); -- ERROR @@ -828,10 +835,13 @@ COMMENT ON COLUMN ft1.c1 IS NULL; ALTER FOREIGN TABLE ft1 ADD COLUMN c4 integer; ALTER FOREIGN TABLE ft1 ADD COLUMN c5 integer DEFAULT 0; ALTER FOREIGN TABLE ft1 ADD COLUMN c6 integer; +ALTER FOREIGN TABLE ft1 ADD COLUMN IF NOT EXISTS c6 integer; +NOTICE: column "c6" of relation "ft1" already exists, skipping ALTER FOREIGN TABLE ft1 ADD COLUMN c7 integer NOT NULL; ALTER FOREIGN TABLE ft1 ADD COLUMN c8 integer; ALTER FOREIGN TABLE ft1 ADD COLUMN c9 integer; ALTER FOREIGN TABLE ft1 ADD COLUMN c10 integer OPTIONS (p1 'v1'); +ALTER FOREIGN TABLE ft1 ADD c11 integer; ALTER FOREIGN TABLE ft1 ALTER COLUMN c4 SET DEFAULT 0; ALTER FOREIGN TABLE ft1 ALTER COLUMN c5 DROP DEFAULT; ALTER FOREIGN TABLE ft1 ALTER COLUMN c6 SET NOT NULL; @@ -863,6 +873,7 @@ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STORAGE PLAIN; c8 | text | | | | (p2 'V2') | plain | | c9 | integer | | | | | plain | | c10 | integer | | | | (p1 'v1') | plain | | + c11 | integer | | | | | plain | | Check constraints: "ft1_c2_check" CHECK (c2 <> ''::text) "ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date) @@ -894,6 +905,7 @@ ERROR: column "no_column" of relation "ft1" does not exist ALTER FOREIGN TABLE ft1 DROP COLUMN IF EXISTS no_column; NOTICE: column "no_column" of relation "ft1" does not exist, skipping ALTER FOREIGN TABLE ft1 DROP COLUMN c9; +ALTER FOREIGN TABLE ft1 DROP c11; ALTER FOREIGN TABLE ft1 ADD COLUMN c11 serial; ALTER FOREIGN TABLE ft1 SET SCHEMA foreign_schema; ALTER FOREIGN TABLE ft1 SET TABLESPACE ts; -- ERROR @@ -928,6 +940,8 @@ ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c4 integer; NOTICE: relation "doesnt_exist_ft1" does not exist, skipping ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c6 integer; NOTICE: relation "doesnt_exist_ft1" does not exist, skipping +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN IF NOT EXISTS c6 integer; +NOTICE: relation "doesnt_exist_ft1" does not exist, skipping ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c7 integer NOT NULL; NOTICE: relation "doesnt_exist_ft1" does not exist, skipping ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c8 integer; @@ -936,6 +950,8 @@ ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c9 integer; NOTICE: relation "doesnt_exist_ft1" does not exist, skipping ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c10 integer OPTIONS (p1 'v1'); NOTICE: relation "doesnt_exist_ft1" does not exist, skipping +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD c11 integer; +NOTICE: relation "doesnt_exist_ft1" does not exist, skipping ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c6 SET NOT NULL; NOTICE: relation "doesnt_exist_ft1" does not exist, skipping ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c7 DROP NOT NULL; @@ -957,10 +973,14 @@ ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 OWNER TO regress_test_role; NOTICE: relation "doesnt_exist_ft1" does not exist, skipping ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@'); NOTICE: relation "doesnt_exist_ft1" does not exist, skipping +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP COLUMN no_column; +NOTICE: relation "doesnt_exist_ft1" does not exist, skipping ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP COLUMN IF EXISTS no_column; NOTICE: relation "doesnt_exist_ft1" does not exist, skipping ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP COLUMN c9; NOTICE: relation "doesnt_exist_ft1" does not exist, skipping +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP c11; +NOTICE: relation "doesnt_exist_ft1" does not exist, skipping ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 SET SCHEMA foreign_schema; NOTICE: relation "doesnt_exist_ft1" does not exist, skipping ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 RENAME c1 TO foreign_column_1; diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out index 20e8ada242c..9ebaab9411c 100644 --- a/src/test/regress/expected/generated.out +++ b/src/test/regress/expected/generated.out @@ -1155,3 +1155,37 @@ CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED); c | integer | | | x | integer | | | generated always as (b * 2) stored +-- rule actions referring to generated columns: +-- NEW.b in a rule action should reflect the generated column's new value +CREATE TABLE gtest_rule (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +CREATE TABLE gtest_rule_log (op text, old_b int, new_b int); +CREATE RULE gtest_rule_upd AS ON UPDATE TO gtest_rule + DO ALSO INSERT INTO gtest_rule_log VALUES ('UPD', OLD.b, NEW.b); +CREATE RULE gtest_rule_ins AS ON INSERT TO gtest_rule + DO ALSO INSERT INTO gtest_rule_log VALUES ('INS', NULL, NEW.b); +INSERT INTO gtest_rule (a) VALUES (1); +UPDATE gtest_rule SET a = 10; +UPDATE gtest_rule SET a = (SELECT max(b) FROM gtest_rule); +SELECT * FROM gtest_rule_log; + op | old_b | new_b +-----+-------+------- + INS | | 2 + UPD | 2 | 20 + UPD | 20 | 40 +(3 rows) + +DROP RULE gtest_rule_upd ON gtest_rule; +DROP RULE gtest_rule_ins ON gtest_rule; +DROP TABLE gtest_rule_log; +-- rule quals referring to generated columns: +-- NEW.b in the rule qual should reflect the generated column's new value +CREATE RULE gtest_rule_qual AS ON UPDATE TO gtest_rule WHERE NEW.b > 100 + DO INSTEAD NOTHING; +UPDATE gtest_rule SET a = 100; +SELECT * FROM gtest_rule; + a | b +----+---- + 20 | 40 +(1 row) + +DROP TABLE gtest_rule; diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out index e17879b794f..77d667ed9b4 100644 --- a/src/test/regress/expected/indexing.out +++ b/src/test/regress/expected/indexing.out @@ -540,6 +540,111 @@ select relname, indisvalid from pg_class join pg_index on indexrelid = oid idxpart_a_idx | t (3 rows) +drop table idxpart; +-- Verify that re-attaching an already-attached partition index can +-- validate the parent index if it was still invalid, including +-- indirect ancestors in subpartitions. +create table idxpart (a int, b int) partition by range (a); +create table idxpart1 partition of idxpart for values from (0) to (1000) partition by range (a); +create table idxpart11 partition of idxpart1 for values from (0) to (500); +-- Partitioned table with no partitions +create table idxpart2 partition of idxpart for values from (1000) to (2000) partition by range (a); +-- create parent indexes +create index on only idxpart ((a/b)); +create index on only idxpart1 ((a/b)); +create index on only idxpart2 ((a/b)); +-- fail, leaves behind an invalid index on the leaf partition +insert into idxpart11 values (1, 0); +create index concurrently on idxpart11 ((a/b)); +ERROR: division by zero +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; + relname | indisvalid +--------------------+------------ + idxpart11_expr_idx | f + idxpart1_expr_idx | f + idxpart2_expr_idx | t + idxpart_expr_idx | f +(4 rows) + +-- attach the indexes; parents stay invalid +alter index idxpart1_expr_idx attach partition idxpart11_expr_idx; +alter index idxpart_expr_idx attach partition idxpart1_expr_idx; +alter index idxpart_expr_idx attach partition idxpart2_expr_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; + relname | indisvalid +--------------------+------------ + idxpart11_expr_idx | f + idxpart1_expr_idx | f + idxpart2_expr_idx | t + idxpart_expr_idx | f +(4 rows) + +-- fix the index on the leaf partition +delete from idxpart11 where b = 0; +reindex index concurrently idxpart11_expr_idx; +-- reattach the leaf partition index; parents should now be valid +alter index idxpart1_expr_idx attach partition idxpart11_expr_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; + relname | indisvalid +--------------------+------------ + idxpart11_expr_idx | t + idxpart1_expr_idx | t + idxpart2_expr_idx | t + idxpart_expr_idx | t +(4 rows) + +drop table idxpart; +-- Verify that re-attaching does not validate the parent when another +-- child index is still invalid. +create table idxpart (a int, b int) partition by range (a); +create table idxpart1 partition of idxpart for values from (0) to (500); +create table idxpart2 partition of idxpart for values from (500) to (1000); +create index on only idxpart ((a/b)); +-- create invalid indexes on both children +insert into idxpart1 values (1, 0); +insert into idxpart2 values (501, 0); +create index concurrently on idxpart1 ((a/b)); +ERROR: division by zero +create index concurrently on idxpart2 ((a/b)); +ERROR: division by zero +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; + relname | indisvalid +-------------------+------------ + idxpart1_expr_idx | f + idxpart2_expr_idx | f + idxpart_expr_idx | f +(3 rows) + +-- attach both; parent stays invalid +alter index idxpart_expr_idx attach partition idxpart1_expr_idx; +alter index idxpart_expr_idx attach partition idxpart2_expr_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; + relname | indisvalid +-------------------+------------ + idxpart1_expr_idx | f + idxpart2_expr_idx | f + idxpart_expr_idx | f +(3 rows) + +-- fix only idxpart1's index, leave idxpart2's still invalid +delete from idxpart1 where b = 0; +reindex index concurrently idxpart1_expr_idx; +-- re-attach the fixed child; parent should stay invalid +alter index idxpart_expr_idx attach partition idxpart1_expr_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; + relname | indisvalid +-------------------+------------ + idxpart1_expr_idx | t + idxpart2_expr_idx | f + idxpart_expr_idx | f +(3 rows) + drop table idxpart; -- verify dependency handling during ALTER TABLE DETACH PARTITION create table idxpart (a int) partition by range (a); diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out index ac2eb84c3af..0afede8a399 100644 --- a/src/test/regress/expected/multirangetypes.out +++ b/src/test/regress/expected/multirangetypes.out @@ -3084,6 +3084,22 @@ select _textrange1(textrange2('a','z')) @> 'b'::text; drop type textrange1; drop type textrange2; -- +-- CREATE TYPE checks for CREATE on multirange schema +-- +create role regress_mr; +create schema mr_sch; +set role regress_mr; +create type mytype as range (subtype=int4, multirange_type_name=mr_sch.mr_type); +ERROR: permission denied for schema mr_sch +reset role; +grant create on schema mr_sch to regress_mr; +set role regress_mr; +create type mytype as range (subtype=int4, multirange_type_name=mr_sch.mr_type); +reset role; +drop type mytype; +drop schema mr_sch; +drop role regress_mr; +-- -- Test polymorphic type system -- create function anyarray_anymultirange_func(a anyarray, r anymultirange) @@ -3236,6 +3252,9 @@ select *, row_to_json(upper(t)) as u from {["(5,6)","(7,8)")} | {"a":7,"b":8} (2 rows) +-- this must be rejected to avoid self-inclusion issues: +alter type two_ints add attribute c two_ints_multirange; +ERROR: composite type two_ints cannot be made a member of itself drop type two_ints cascade; NOTICE: drop cascades to type two_ints_range -- diff --git a/src/test/regress/expected/prepared_xacts.out b/src/test/regress/expected/prepared_xacts.out index ba8e3ccc6c9..94404b5574b 100644 --- a/src/test/regress/expected/prepared_xacts.out +++ b/src/test/regress/expected/prepared_xacts.out @@ -1,3 +1,7 @@ +SELECT current_setting('max_prepared_transactions')::integer < 2 AS skip_test \gset +\if :skip_test +\quit +\endif -- -- PREPARED TRANSACTIONS (two-phase commit) -- @@ -265,6 +269,5 @@ SELECT gid FROM pg_prepared_xacts; -- Clean up DROP TABLE pxtest2; -DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled -ERROR: table "pxtest3" does not exist +-- pxtest3 was already dropped DROP TABLE pxtest4; diff --git a/src/test/regress/expected/prepared_xacts_1.out b/src/test/regress/expected/prepared_xacts_1.out index 2cd50ad9470..a21314768c3 100644 --- a/src/test/regress/expected/prepared_xacts_1.out +++ b/src/test/regress/expected/prepared_xacts_1.out @@ -1,266 +1,3 @@ --- --- PREPARED TRANSACTIONS (two-phase commit) --- --- We can't readily test persistence of prepared xacts within the --- regression script framework, unfortunately. Note that a crash --- isn't really needed ... stopping and starting the postmaster would --- be enough, but we can't even do that here. --- create a simple table that we'll use in the tests -CREATE TABLE pxtest1 (foobar VARCHAR(10)); -INSERT INTO pxtest1 VALUES ('aaa'); --- Test PREPARE TRANSACTION -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -UPDATE pxtest1 SET foobar = 'bbb' WHERE foobar = 'aaa'; -SELECT * FROM pxtest1; - foobar --------- - bbb -(1 row) - -PREPARE TRANSACTION 'foo1'; -ERROR: prepared transactions are disabled -HINT: Set max_prepared_transactions to a nonzero value. -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - --- Test pg_prepared_xacts system view -SELECT gid FROM pg_prepared_xacts; - gid ------ -(0 rows) - --- Test ROLLBACK PREPARED -ROLLBACK PREPARED 'foo1'; -ERROR: prepared transaction with identifier "foo1" does not exist -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - -SELECT gid FROM pg_prepared_xacts; - gid ------ -(0 rows) - --- Test COMMIT PREPARED -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -INSERT INTO pxtest1 VALUES ('ddd'); -SELECT * FROM pxtest1; - foobar --------- - aaa - ddd -(2 rows) - -PREPARE TRANSACTION 'foo2'; -ERROR: prepared transactions are disabled -HINT: Set max_prepared_transactions to a nonzero value. -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - -COMMIT PREPARED 'foo2'; -ERROR: prepared transaction with identifier "foo2" does not exist -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - --- Test duplicate gids -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd'; -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - -PREPARE TRANSACTION 'foo3'; -ERROR: prepared transactions are disabled -HINT: Set max_prepared_transactions to a nonzero value. -SELECT gid FROM pg_prepared_xacts; - gid ------ -(0 rows) - -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -INSERT INTO pxtest1 VALUES ('fff'); --- This should fail, because the gid foo3 is already in use -PREPARE TRANSACTION 'foo3'; -ERROR: prepared transactions are disabled -HINT: Set max_prepared_transactions to a nonzero value. -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - -ROLLBACK PREPARED 'foo3'; -ERROR: prepared transaction with identifier "foo3" does not exist -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - --- Test serialization failure (SSI) -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd'; -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - -PREPARE TRANSACTION 'foo4'; -ERROR: prepared transactions are disabled -HINT: Set max_prepared_transactions to a nonzero value. -SELECT gid FROM pg_prepared_xacts; - gid ------ -(0 rows) - -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - --- This should fail, because the two transactions have a write-skew anomaly -INSERT INTO pxtest1 VALUES ('fff'); -PREPARE TRANSACTION 'foo5'; -ERROR: prepared transactions are disabled -HINT: Set max_prepared_transactions to a nonzero value. -SELECT gid FROM pg_prepared_xacts; - gid ------ -(0 rows) - -ROLLBACK PREPARED 'foo4'; -ERROR: prepared transaction with identifier "foo4" does not exist -SELECT gid FROM pg_prepared_xacts; - gid ------ -(0 rows) - --- Clean up -DROP TABLE pxtest1; --- Test detection of session-level and xact-level locks on same object -BEGIN; -SELECT pg_advisory_lock(1); - pg_advisory_lock ------------------- - -(1 row) - -SELECT pg_advisory_xact_lock_shared(1); - pg_advisory_xact_lock_shared ------------------------------- - -(1 row) - -PREPARE TRANSACTION 'foo6'; -- fails -ERROR: prepared transactions are disabled -HINT: Set max_prepared_transactions to a nonzero value. --- Test subtransactions -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; - CREATE TABLE pxtest2 (a int); - INSERT INTO pxtest2 VALUES (1); - SAVEPOINT a; - INSERT INTO pxtest2 VALUES (2); - ROLLBACK TO a; - SAVEPOINT b; - INSERT INTO pxtest2 VALUES (3); -PREPARE TRANSACTION 'regress-one'; -ERROR: prepared transactions are disabled -HINT: Set max_prepared_transactions to a nonzero value. -CREATE TABLE pxtest3(fff int); --- Test shared invalidation -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; - DROP TABLE pxtest3; - CREATE TABLE pxtest4 (a int); - INSERT INTO pxtest4 VALUES (1); - INSERT INTO pxtest4 VALUES (2); - DECLARE foo CURSOR FOR SELECT * FROM pxtest4; - -- Fetch 1 tuple, keeping the cursor open - FETCH 1 FROM foo; - a ---- - 1 -(1 row) - -PREPARE TRANSACTION 'regress-two'; -ERROR: prepared transactions are disabled -HINT: Set max_prepared_transactions to a nonzero value. --- No such cursor -FETCH 1 FROM foo; -ERROR: cursor "foo" does not exist --- Table doesn't exist, the creation hasn't been committed yet -SELECT * FROM pxtest2; -ERROR: relation "pxtest2" does not exist -LINE 1: SELECT * FROM pxtest2; - ^ --- There should be two prepared transactions -SELECT gid FROM pg_prepared_xacts; - gid ------ -(0 rows) - --- pxtest3 should be locked because of the pending DROP -begin; -lock table pxtest3 in access share mode nowait; -rollback; --- Disconnect, we will continue testing in a different backend -\c - --- There should still be two prepared transactions -SELECT gid FROM pg_prepared_xacts; - gid ------ -(0 rows) - --- pxtest3 should still be locked because of the pending DROP -begin; -lock table pxtest3 in access share mode nowait; -rollback; --- Commit table creation -COMMIT PREPARED 'regress-one'; -ERROR: prepared transaction with identifier "regress-one" does not exist -\d pxtest2 -SELECT * FROM pxtest2; -ERROR: relation "pxtest2" does not exist -LINE 1: SELECT * FROM pxtest2; - ^ --- There should be one prepared transaction -SELECT gid FROM pg_prepared_xacts; - gid ------ -(0 rows) - --- Commit table drop -COMMIT PREPARED 'regress-two'; -ERROR: prepared transaction with identifier "regress-two" does not exist -SELECT * FROM pxtest3; - fff ------ -(0 rows) - --- There should be no prepared transactions -SELECT gid FROM pg_prepared_xacts; - gid ------ -(0 rows) - --- Clean up -DROP TABLE pxtest2; -ERROR: table "pxtest2" does not exist -DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled -DROP TABLE pxtest4; -ERROR: table "pxtest4" does not exist +SELECT current_setting('max_prepared_transactions')::integer < 2 AS skip_test \gset +\if :skip_test +\quit diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index ffcba306067..8195deb995e 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -583,6 +583,8 @@ SELECT (n_tup_ins + n_tup_upd) > 0 AS has_data FROM pg_stat_all_tables t (1 row) +-- stats_reset may not be set for datid=0 and shared objects in +-- pg_stat_database, so reset once. SELECT pg_stat_reset_single_table_counters('pg_shdescription'::regclass); pg_stat_reset_single_table_counters ------------------------------------- @@ -596,6 +598,22 @@ SELECT (n_tup_ins + n_tup_upd) > 0 AS has_data FROM pg_stat_all_tables f (1 row) +SELECT stats_reset AS shared_db_reset_before + FROM pg_stat_database WHERE datid = 0 \gset +-- Second reset for comparison. +SELECT pg_stat_reset_single_table_counters('pg_shdescription'::regclass); + pg_stat_reset_single_table_counters +------------------------------------- + +(1 row) + +SELECT stats_reset > :'shared_db_reset_before'::timestamptz AS has_updated + FROM pg_stat_database WHERE datid = 0; + has_updated +------------- + t +(1 row) + -- set back comment \if :{?description_before} COMMENT ON DATABASE :"datname" IS :'description_before'; diff --git a/src/test/regress/expected/tsearch.out b/src/test/regress/expected/tsearch.out index 5d3d9d6250b..6055ab9fbaa 100644 --- a/src/test/regress/expected/tsearch.out +++ b/src/test/regress/expected/tsearch.out @@ -2017,6 +2017,16 @@ NOTICE: text-search query doesn't contain lexemes: "" foo bar (1 row) +-- Test for large values of StartSel, StopSel and FragmentDelimiter +SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'), + 'StartSel=' || repeat('x', 32768)); +ERROR: value for "StartSel" is too long +SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'), + 'StopSel=' || repeat('x', 32768)); +ERROR: value for "StopSel" is too long +SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'), + 'FragmentDelimiter=' || repeat('x', 32768)); +ERROR: value for "FragmentDelimiter" is too long --Rewrite sub system CREATE TABLE test_tsquery (txtkeyword TEXT, txtsample TEXT); \set ECHO none diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out index d626591a05d..b240cd468f7 100644 --- a/src/test/regress/expected/window.out +++ b/src/test/regress/expected/window.out @@ -1343,6 +1343,97 @@ SELECT pg_get_viewdef('v_window'); FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i); (1 row) +-- test overflow frame specifications +SELECT sum(unique1) over (rows between current row and 9223372036854775807 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10; + sum | unique1 | four +-----+---------+------ + 41 | 4 | 0 + 39 | 2 | 2 + 38 | 1 | 1 + 32 | 6 | 2 + 23 | 9 | 1 + 15 | 8 | 0 + 10 | 5 | 1 + 7 | 3 | 3 + 0 | 7 | 3 + | 0 | 0 +(10 rows) + +SELECT sum(unique1) over (rows between 9223372036854775807 following and 1 following), + unique1, four +FROM tenk1 WHERE unique1 < 10; + sum | unique1 | four +-----+---------+------ + | 4 | 0 + | 2 | 2 + | 1 | 1 + | 6 | 2 + | 9 | 1 + | 8 | 0 + | 5 | 1 + | 3 | 3 + | 7 | 3 + | 0 | 0 +(10 rows) + +SELECT last_value(unique1) over (ORDER BY four rows between current row and 9223372036854775807 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10; + last_value | unique1 | four +------------+---------+------ + 7 | 0 | 0 + 7 | 8 | 0 + 7 | 4 | 0 + 7 | 5 | 1 + 7 | 9 | 1 + 7 | 1 | 1 + 7 | 6 | 2 + 7 | 2 | 2 + 7 | 3 | 3 + | 7 | 3 +(10 rows) + +-- These test GROUPS mode with an offset large enough to cause overflow when +-- added to currentgroup. Although the overflow doesn't produce visibly wrong +-- results (due to the incremental nature of group pointer advancement), we +-- still need to protect against it as signed integer overflow is undefined +-- behavior in C. +SELECT sum(unique1) over (ORDER BY four groups between current row and 9223372036854775807 following), + unique1, four +FROM tenk1 WHERE unique1 < 10; + sum | unique1 | four +-----+---------+------ + 45 | 0 | 0 + 45 | 8 | 0 + 45 | 4 | 0 + 33 | 5 | 1 + 33 | 9 | 1 + 33 | 1 | 1 + 18 | 6 | 2 + 18 | 2 | 2 + 10 | 3 | 3 + 10 | 7 | 3 +(10 rows) + +SELECT sum(unique1) over (ORDER BY four groups between 9223372036854775807 following and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10; + sum | unique1 | four +-----+---------+------ + | 0 | 0 + | 8 | 0 + | 4 | 0 + | 5 | 1 + | 9 | 1 + | 1 | 1 + | 6 | 2 + | 2 | 2 + | 3 | 3 + | 7 | 3 +(10 rows) + -- RANGE offset PRECEDING/FOLLOWING tests SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding), unique1, four diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index b7ecdb504ea..03034a694fa 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -131,7 +131,12 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr # The stats test resets stats, so nothing else needing stats access can be in # this group. # ---------- -test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression memoize stats +test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain memoize stats + +# ---------- +# Another group of parallel tests (compression) +# ---------- +test: compression compression_pglz # event_trigger cannot run concurrently with any test that runs DDL # oidjoins is read-only, though, and should run late for best coverage diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 3a248c6306b..81d763470b5 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -536,7 +536,7 @@ static const char * get_expectfile(const char *testname, const char *file) { - char *file_type; + const char *file_type; _resultmap *rm; /* diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c index 9666d889aab..6e69b2748fe 100644 --- a/src/test/regress/regress.c +++ b/src/test/regress/regress.c @@ -28,6 +28,7 @@ #include "catalog/pg_type.h" #include "commands/sequence.h" #include "commands/trigger.h" +#include "common/pg_lzcompress.h" #include "executor/executor.h" #include "executor/spi.h" #include "funcapi.h" @@ -1471,3 +1472,68 @@ get_columns_length(PG_FUNCTION_ARGS) PG_RETURN_INT32(column_offset); } + +/* + * test_pglz_compress + * + * Compress the input using pglz_compress(). Only the "always" strategy is + * currently supported. + * + * Returns the compressed data, or NULL if compression fails. + */ +PG_FUNCTION_INFO_V1(test_pglz_compress); +Datum +test_pglz_compress(PG_FUNCTION_ARGS) +{ + bytea *input = PG_GETARG_BYTEA_PP(0); + char *source = VARDATA_ANY(input); + int32 slen = VARSIZE_ANY_EXHDR(input); + int32 maxout = PGLZ_MAX_OUTPUT(slen); + bytea *result; + int32 clen; + + result = (bytea *) palloc(maxout + VARHDRSZ); + clen = pglz_compress(source, slen, VARDATA(result), + PGLZ_strategy_always); + if (clen < 0) + PG_RETURN_NULL(); + + SET_VARSIZE(result, clen + VARHDRSZ); + PG_RETURN_BYTEA_P(result); +} + +/* + * test_pglz_decompress + * + * Decompress the input using pglz_decompress(). + * + * The second argument is the expected uncompressed data size. The third + * argument is here for the check_complete flag. + * + * Returns the decompressed data, or raises an error if decompression fails. + */ +PG_FUNCTION_INFO_V1(test_pglz_decompress); +Datum +test_pglz_decompress(PG_FUNCTION_ARGS) +{ + bytea *input = PG_GETARG_BYTEA_PP(0); + int32 rawsize = PG_GETARG_INT32(1); + bool check_complete = PG_GETARG_BOOL(2); + char *source = VARDATA_ANY(input); + int32 slen = VARSIZE_ANY_EXHDR(input); + bytea *result; + int32 dlen; + + if (rawsize < 0) + elog(ERROR, "rawsize must not be negative"); + + result = (bytea *) palloc(rawsize + VARHDRSZ); + + dlen = pglz_decompress(source, slen, VARDATA(result), + rawsize, check_complete); + if (dlen < 0) + elog(ERROR, "pglz_decompress failed"); + + SET_VARSIZE(result, dlen + VARHDRSZ); + PG_RETURN_BYTEA_P(result); +} diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index a5ad6bc0bbe..9894b159a1a 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -2305,6 +2305,14 @@ ALTER TABLE test_add_column \d test_add_column ALTER TABLE test_add_column ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10); +ALTER TABLE test_add_column + ADD c6 integer; -- omit COLUMN +ALTER TABLE test_add_column + ADD IF NOT EXISTS c6 integer; +ALTER TABLE test_add_column + DROP c6; -- omit COLUMN +ALTER TABLE test_add_column + DROP IF EXISTS c6; \d test_add_column* DROP TABLE test_add_column; \d test_add_column* diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql index 397b96bb7d0..8058bf9a997 100644 --- a/src/test/regress/sql/collate.icu.utf8.sql +++ b/src/test/regress/sql/collate.icu.utf8.sql @@ -528,6 +528,109 @@ CREATE UNIQUE INDEX ON test3cs (x); -- ok SELECT string_to_array('ABC,DEF,GHI' COLLATE case_sensitive, ',', 'abc'); SELECT string_to_array('ABCDEFGHI' COLLATE case_sensitive, NULL, 'b'); +-- +-- A unique index under one collation does not prove uniqueness under +-- another, so the planner must not use such a proof for any optimization. +-- + +-- Ensure that we do not use inner-unique join execution +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM test1cs t1, test3cs t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + +SELECT * FROM test1cs t1, test3cs t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + +-- Ensure that left-join is not removed +EXPLAIN (COSTS OFF) +SELECT t1.* FROM test3cs t1 + LEFT JOIN test3cs t2 ON t1.x = t2.x COLLATE case_insensitive +ORDER BY 1; + +SELECT t1.* FROM test3cs t1 + LEFT JOIN test3cs t2 ON t1.x = t2.x COLLATE case_insensitive +ORDER BY 1; + +-- Ensure that self-join is not removed +EXPLAIN (COSTS OFF) +SELECT * FROM test3cs t1, test3cs t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + +SELECT * FROM test3cs t1, test3cs t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + +-- Ensure that semijoin is not reduced to innerjoin +EXPLAIN (COSTS OFF) +SELECT * FROM test3cs t1 + WHERE EXISTS (SELECT 1 FROM test3cs t2 WHERE t1.x = t2.x COLLATE case_insensitive) +ORDER BY 1; + +SELECT * FROM test3cs t1 + WHERE EXISTS (SELECT 1 FROM test3cs t2 WHERE t1.x = t2.x COLLATE case_insensitive) +ORDER BY 1; + +-- +-- A DISTINCT / GROUP BY / set-op on a subquery does not prove uniqueness +-- under a different collation, so the planner must not use such a proof for +-- any optimization. +-- + +-- Ensure that we do not use inner-unique join execution +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM test1cs t1, (SELECT DISTINCT x FROM test3cs) t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + +SELECT * FROM test1cs t1, (SELECT DISTINCT x FROM test3cs) t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + +-- Same with GROUP BY +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM test1cs t1, (SELECT x FROM test3cs GROUP BY x) t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + +SELECT * FROM test1cs t1, (SELECT x FROM test3cs GROUP BY x) t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + +-- Same with set-op +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM test1cs t1, (SELECT x FROM test3cs UNION SELECT x FROM test3cs) t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + +SELECT * FROM test1cs t1, (SELECT x FROM test3cs UNION SELECT x FROM test3cs) t2 +WHERE t1.x = t2.x COLLATE case_insensitive +ORDER BY 1, 2; + +-- Ensure that left-join is not removed +EXPLAIN (COSTS OFF) +SELECT t1.* FROM test3cs t1 + LEFT JOIN (SELECT DISTINCT x FROM test3cs) t2 ON t1.x = t2.x COLLATE case_insensitive +ORDER BY 1; + +SELECT t1.* FROM test3cs t1 + LEFT JOIN (SELECT DISTINCT x FROM test3cs) t2 ON t1.x = t2.x COLLATE case_insensitive +ORDER BY 1; + +-- Ensure that semijoin is not reduced to innerjoin +EXPLAIN (COSTS OFF) +SELECT * FROM test3cs t1 + WHERE EXISTS (SELECT 1 FROM (SELECT DISTINCT x FROM test3cs) t2 + WHERE t1.x = t2.x COLLATE case_insensitive) +ORDER BY 1; + +SELECT * FROM test3cs t1 + WHERE EXISTS (SELECT 1 FROM (SELECT DISTINCT x FROM test3cs) t2 + WHERE t1.x = t2.x COLLATE case_insensitive) +ORDER BY 1; + CREATE TABLE test1ci (x text COLLATE case_insensitive); CREATE TABLE test2ci (x text COLLATE case_insensitive); CREATE TABLE test3ci (x text COLLATE case_insensitive); diff --git a/src/test/regress/sql/compression_pglz.sql b/src/test/regress/sql/compression_pglz.sql new file mode 100644 index 00000000000..a44af02afb7 --- /dev/null +++ b/src/test/regress/sql/compression_pglz.sql @@ -0,0 +1,53 @@ +-- +-- Tests for PGLZ compression +-- + +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +CREATE FUNCTION test_pglz_compress(bytea) + RETURNS bytea + AS :'regresslib' LANGUAGE C STRICT; +CREATE FUNCTION test_pglz_decompress(bytea, int4, bool) + RETURNS bytea + AS :'regresslib' LANGUAGE C STRICT; + +-- Round-trip with pglz: compress then decompress. +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 400, false) = + decode(repeat('abcd', 100), 'escape') AS roundtrip_ok; +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 400, true) = + decode(repeat('abcd', 100), 'escape') AS roundtrip_ok; + +-- Decompression with rawsize too large, fails to fill the destination +-- buffer. +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 500, true); + +-- Decompression with rawsize too small, fails with source not fully +-- consumed. +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 100, true); + +-- Corrupted compressed data. Set control bit with read of a match tag, +-- no data follows. +SELECT length(test_pglz_decompress('\x01'::bytea, 1024, false)) AS ctrl_only_len; +SELECT test_pglz_decompress('\x01'::bytea, 1024, true); + +-- Corrupted compressed data. Set control bit with read of a match tag, +-- 1 byte follows. +SELECT test_pglz_decompress('\x01ff'::bytea, 1024, false); +SELECT test_pglz_decompress('\x01ff'::bytea, 1024, true); + +-- Corrupted compressed data. Set control bit with match tag where length +-- nibble is 3 bytes (extended length), no data follows. +SELECT test_pglz_decompress('\x010f01'::bytea, 1024, false); +SELECT test_pglz_decompress('\x010f01'::bytea, 1024, true); + +-- Clean up +DROP FUNCTION test_pglz_compress; +DROP FUNCTION test_pglz_decompress; diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index e7d1170c8b4..83dfdbf32d5 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -45,6 +45,10 @@ CREATE INDEX six ON shighway USING btree (name text_ops); COMMENT ON INDEX six_wrong IS 'bad index'; COMMENT ON INDEX six IS 'good index'; COMMENT ON INDEX six IS NULL; +SELECT obj_description('six'::regclass, 'pg_class') IS NULL AS six_comment_is_null; +COMMENT ON INDEX six IS 'add the comment back'; +COMMENT ON INDEX six IS ''; -- empty string removes the comment, same as NULL +SELECT obj_description('six'::regclass, 'pg_class') IS NULL AS six_comment_is_null; -- -- BTREE partial indices diff --git a/src/test/regress/sql/create_role.sql b/src/test/regress/sql/create_role.sql index 292dc087975..5841b57e7cb 100644 --- a/src/test/regress/sql/create_role.sql +++ b/src/test/regress/sql/create_role.sql @@ -55,6 +55,14 @@ CREATE ROLE regress_rolecreator CREATEROLE; -- ok, roles with CREATEROLE can create new roles with privilege they lack CREATE ROLE regress_tenant CREATEDB CREATEROLE LOGIN INHERIT CONNECTION LIMIT 5; +COMMENT ON ROLE regress_tenant IS 'some comment'; +SELECT shobj_description('regress_tenant'::regrole, 'pg_authid') IS NOT NULL AS has_comment; +COMMENT ON ROLE regress_tenant IS NULL; +SELECT shobj_description('regress_tenant'::regrole, 'pg_authid') IS NULL AS no_comment; +COMMENT ON ROLE regress_tenant IS 'add the comment back'; +SELECT shobj_description('regress_tenant'::regrole, 'pg_authid') IS NOT NULL AS has_comment; +COMMENT ON ROLE regress_tenant IS ''; -- empty string removes the comment, same as NULL +SELECT shobj_description('regress_tenant'::regrole, 'pg_authid') IS NULL AS no_comment; -- ok, regress_tenant can create objects within the database SET SESSION AUTHORIZATION regress_tenant; diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql index 04008a027b8..7c7206c66ee 100644 --- a/src/test/regress/sql/create_table_like.sql +++ b/src/test/regress/sql/create_table_like.sql @@ -223,3 +223,29 @@ DROP SEQUENCE ctlseq1; DROP TYPE ctlty1; DROP VIEW ctlv1; DROP TABLE IF EXISTS ctlt4, ctlt10, ctlt11, ctlt11a, ctlt12; + +-- LIKE ... INCLUDING STATISTICS with dropped columns in the parent, +-- so stxkeys attnums are not contiguous. +CREATE TABLE ctl_stats3_parent (a int, b int, c int); +ALTER TABLE ctl_stats3_parent DROP COLUMN b; +CREATE STATISTICS ctl_stats3_stat ON a, c FROM ctl_stats3_parent; +CREATE TABLE ctl_stats3_child (LIKE ctl_stats3_parent INCLUDING STATISTICS); +CREATE TABLE ctl_stats4_parent (a int, b int, c int, d int); +ALTER TABLE ctl_stats4_parent DROP COLUMN b; +CREATE STATISTICS ctl_stats4_stat ON a, c FROM ctl_stats4_parent; +CREATE TABLE ctl_stats4_child (LIKE ctl_stats4_parent INCLUDING STATISTICS); +SELECT s.stxrelid::regclass AS relation, + array_agg(a.attname ORDER BY u.ord) AS stats_columns +FROM pg_statistic_ext s +CROSS JOIN LATERAL + unnest(s.stxkeys::int2[]) WITH ORDINALITY AS u(attnum, ord) +JOIN pg_attribute a + ON a.attrelid = s.stxrelid AND a.attnum = u.attnum +WHERE s.stxrelid IN ('ctl_stats3_child'::regclass, + 'ctl_stats4_child'::regclass) +GROUP BY s.stxrelid +ORDER BY s.stxrelid::regclass::text; +DROP TABLE ctl_stats3_parent; +DROP TABLE ctl_stats3_child; +DROP TABLE ctl_stats4_parent; +DROP TABLE ctl_stats4_child; diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql index aebd08fa052..89e2976e45d 100644 --- a/src/test/regress/sql/expressions.sql +++ b/src/test/regress/sql/expressions.sql @@ -191,16 +191,108 @@ default for type myint using hash as function 1 myinthash(myint); create table inttest (a myint); -insert into inttest values(1::myint),(null); - --- try an array with enough elements to cause hashing -select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null); -select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null); -select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null); --- ensure the result matched with the non-hashed version. We simply remove --- some array elements so that we don't reach the hashing threshold. -select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null); -select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null); -select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null); +insert into inttest values (null), (0::myint), (1::myint); + +-- Test EEOP_HASHED_SCALARARRAYOP against EEOP_SCALARARRAYOP. Ensure the +-- result of non-hashed vs hashed is the same. +select + a, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed +from inttest; + +select + a, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed + from inttest; + +select + a, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed +from inttest; + +select + a, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed +from inttest; + +-- Now make the equal function return false when given two NULLs +create or replace function myinteq(myint, myint) returns bool as $$ +begin + if $1 is null and $2 is null then + return false; + else + return $1::int = $2::int; + end if; +end; +$$ language plpgsql immutable; + +-- And try the same again to ensure EEOP_HASHED_SCALARARRAYOP does the same +-- thing as EEOP_SCALARARRAYOP. +select + a, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed +from inttest; + +select + a, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed + from inttest; + +select + a, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed +from inttest; + +select + a, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed +from inttest; + +-- Try again with an equality function that treats NULLs as equal to 0. +create or replace function myinteq(myint, myint) returns bool as $$ +begin + if $1 is null and $2 is null then + return false; + else + return coalesce($1::int,0) = coalesce($2::int, 0); + end if; +end; +$$ language plpgsql immutable; + +select + a, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed, + a in (0::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed_zero, + a in (0::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed_zero +from inttest; + +select + a, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed + from inttest; + +select + a, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed, + a not in (0::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed_zero, + a not in (0::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed_zero +from inttest; + +select + a, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed +from inttest; rollback; diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql index aa147b14a90..0080bfb8856 100644 --- a/src/test/regress/sql/foreign_data.sql +++ b/src/test/regress/sql/foreign_data.sql @@ -67,6 +67,11 @@ CREATE FUNCTION invalid_fdw_handler() RETURNS int LANGUAGE SQL AS 'SELECT 1;'; CREATE FOREIGN DATA WRAPPER test_fdw HANDLER invalid_fdw_handler; -- ERROR CREATE FOREIGN DATA WRAPPER test_fdw HANDLER test_fdw_handler HANDLER invalid_fdw_handler; -- ERROR CREATE FOREIGN DATA WRAPPER test_fdw HANDLER test_fdw_handler; + +-- should preserve dependency on test_fdw_handler +ALTER FOREIGN DATA WRAPPER test_fdw VALIDATOR postgresql_fdw_validator; +DROP FUNCTION test_fdw_handler(); -- ERROR + DROP FOREIGN DATA WRAPPER test_fdw; -- ALTER FOREIGN DATA WRAPPER @@ -383,10 +388,12 @@ COMMENT ON COLUMN ft1.c1 IS NULL; ALTER FOREIGN TABLE ft1 ADD COLUMN c4 integer; ALTER FOREIGN TABLE ft1 ADD COLUMN c5 integer DEFAULT 0; ALTER FOREIGN TABLE ft1 ADD COLUMN c6 integer; +ALTER FOREIGN TABLE ft1 ADD COLUMN IF NOT EXISTS c6 integer; ALTER FOREIGN TABLE ft1 ADD COLUMN c7 integer NOT NULL; ALTER FOREIGN TABLE ft1 ADD COLUMN c8 integer; ALTER FOREIGN TABLE ft1 ADD COLUMN c9 integer; ALTER FOREIGN TABLE ft1 ADD COLUMN c10 integer OPTIONS (p1 'v1'); +ALTER FOREIGN TABLE ft1 ADD c11 integer; ALTER FOREIGN TABLE ft1 ALTER COLUMN c4 SET DEFAULT 0; ALTER FOREIGN TABLE ft1 ALTER COLUMN c5 DROP DEFAULT; @@ -419,6 +426,7 @@ ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@'); ALTER FOREIGN TABLE ft1 DROP COLUMN no_column; -- ERROR ALTER FOREIGN TABLE ft1 DROP COLUMN IF EXISTS no_column; ALTER FOREIGN TABLE ft1 DROP COLUMN c9; +ALTER FOREIGN TABLE ft1 DROP c11; ALTER FOREIGN TABLE ft1 ADD COLUMN c11 serial; ALTER FOREIGN TABLE ft1 SET SCHEMA foreign_schema; ALTER FOREIGN TABLE ft1 SET TABLESPACE ts; -- ERROR @@ -430,10 +438,12 @@ ALTER FOREIGN TABLE foreign_schema.ft1 RENAME TO foreign_table_1; -- alter noexisting table ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c4 integer; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c6 integer; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN IF NOT EXISTS c6 integer; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c7 integer NOT NULL; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c8 integer; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c9 integer; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD COLUMN c10 integer OPTIONS (p1 'v1'); +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ADD c11 integer; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c6 SET NOT NULL; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 ALTER COLUMN c7 DROP NOT NULL; @@ -447,8 +457,10 @@ ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP CONSTRAINT IF EXISTS no_cons ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP CONSTRAINT ft1_c1_check; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 OWNER TO regress_test_role; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@'); +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP COLUMN no_column; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP COLUMN IF EXISTS no_column; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP COLUMN c9; +ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 DROP c11; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 SET SCHEMA foreign_schema; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 RENAME c1 TO foreign_column_1; ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 RENAME TO foreign_table_1; diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql index d6236bd174c..75d4e9cb6d9 100644 --- a/src/test/regress/sql/generated.sql +++ b/src/test/regress/sql/generated.sql @@ -624,3 +624,27 @@ ALTER TABLE gtest28a DROP COLUMN a; CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED); \d gtest28* + +-- rule actions referring to generated columns: +-- NEW.b in a rule action should reflect the generated column's new value +CREATE TABLE gtest_rule (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +CREATE TABLE gtest_rule_log (op text, old_b int, new_b int); +CREATE RULE gtest_rule_upd AS ON UPDATE TO gtest_rule + DO ALSO INSERT INTO gtest_rule_log VALUES ('UPD', OLD.b, NEW.b); +CREATE RULE gtest_rule_ins AS ON INSERT TO gtest_rule + DO ALSO INSERT INTO gtest_rule_log VALUES ('INS', NULL, NEW.b); +INSERT INTO gtest_rule (a) VALUES (1); +UPDATE gtest_rule SET a = 10; +UPDATE gtest_rule SET a = (SELECT max(b) FROM gtest_rule); +SELECT * FROM gtest_rule_log; +DROP RULE gtest_rule_upd ON gtest_rule; +DROP RULE gtest_rule_ins ON gtest_rule; +DROP TABLE gtest_rule_log; + +-- rule quals referring to generated columns: +-- NEW.b in the rule qual should reflect the generated column's new value +CREATE RULE gtest_rule_qual AS ON UPDATE TO gtest_rule WHERE NEW.b > 100 + DO INSTEAD NOTHING; +UPDATE gtest_rule SET a = 100; +SELECT * FROM gtest_rule; +DROP TABLE gtest_rule; diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql index a48a3177faf..f0b0dcc4783 100644 --- a/src/test/regress/sql/indexing.sql +++ b/src/test/regress/sql/indexing.sql @@ -246,6 +246,65 @@ select relname, indisvalid from pg_class join pg_index on indexrelid = oid where relname like 'idxpart%' order by relname; drop table idxpart; +-- Verify that re-attaching an already-attached partition index can +-- validate the parent index if it was still invalid, including +-- indirect ancestors in subpartitions. +create table idxpart (a int, b int) partition by range (a); +create table idxpart1 partition of idxpart for values from (0) to (1000) partition by range (a); +create table idxpart11 partition of idxpart1 for values from (0) to (500); +-- Partitioned table with no partitions +create table idxpart2 partition of idxpart for values from (1000) to (2000) partition by range (a); +-- create parent indexes +create index on only idxpart ((a/b)); +create index on only idxpart1 ((a/b)); +create index on only idxpart2 ((a/b)); +-- fail, leaves behind an invalid index on the leaf partition +insert into idxpart11 values (1, 0); +create index concurrently on idxpart11 ((a/b)); +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +-- attach the indexes; parents stay invalid +alter index idxpart1_expr_idx attach partition idxpart11_expr_idx; +alter index idxpart_expr_idx attach partition idxpart1_expr_idx; +alter index idxpart_expr_idx attach partition idxpart2_expr_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +-- fix the index on the leaf partition +delete from idxpart11 where b = 0; +reindex index concurrently idxpart11_expr_idx; +-- reattach the leaf partition index; parents should now be valid +alter index idxpart1_expr_idx attach partition idxpart11_expr_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +drop table idxpart; + +-- Verify that re-attaching does not validate the parent when another +-- child index is still invalid. +create table idxpart (a int, b int) partition by range (a); +create table idxpart1 partition of idxpart for values from (0) to (500); +create table idxpart2 partition of idxpart for values from (500) to (1000); +create index on only idxpart ((a/b)); +-- create invalid indexes on both children +insert into idxpart1 values (1, 0); +insert into idxpart2 values (501, 0); +create index concurrently on idxpart1 ((a/b)); +create index concurrently on idxpart2 ((a/b)); +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +-- attach both; parent stays invalid +alter index idxpart_expr_idx attach partition idxpart1_expr_idx; +alter index idxpart_expr_idx attach partition idxpart2_expr_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +-- fix only idxpart1's index, leave idxpart2's still invalid +delete from idxpart1 where b = 0; +reindex index concurrently idxpart1_expr_idx; +-- re-attach the fixed child; parent should stay invalid +alter index idxpart_expr_idx attach partition idxpart1_expr_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +drop table idxpart; + -- verify dependency handling during ALTER TABLE DETACH PARTITION create table idxpart (a int) partition by range (a); create table idxpart1 (like idxpart); diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql index 1abcaeddb5c..a67f458f961 100644 --- a/src/test/regress/sql/multirangetypes.sql +++ b/src/test/regress/sql/multirangetypes.sql @@ -693,6 +693,22 @@ select _textrange1(textrange2('a','z')) @> 'b'::text; drop type textrange1; drop type textrange2; +-- +-- CREATE TYPE checks for CREATE on multirange schema +-- +create role regress_mr; +create schema mr_sch; +set role regress_mr; +create type mytype as range (subtype=int4, multirange_type_name=mr_sch.mr_type); +reset role; +grant create on schema mr_sch to regress_mr; +set role regress_mr; +create type mytype as range (subtype=int4, multirange_type_name=mr_sch.mr_type); +reset role; +drop type mytype; +drop schema mr_sch; +drop role regress_mr; + -- -- Test polymorphic type system -- @@ -787,6 +803,9 @@ select *, row_to_json(upper(t)) as u from (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))), (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t); +-- this must be rejected to avoid self-inclusion issues: +alter type two_ints add attribute c two_ints_multirange; + drop type two_ints cascade; -- diff --git a/src/test/regress/sql/prepared_xacts.sql b/src/test/regress/sql/prepared_xacts.sql index 2f0bb55bb49..6800fcd0a19 100644 --- a/src/test/regress/sql/prepared_xacts.sql +++ b/src/test/regress/sql/prepared_xacts.sql @@ -1,3 +1,8 @@ +SELECT current_setting('max_prepared_transactions')::integer < 2 AS skip_test \gset +\if :skip_test +\quit +\endif + -- -- PREPARED TRANSACTIONS (two-phase commit) -- @@ -160,5 +165,5 @@ SELECT gid FROM pg_prepared_xacts; -- Clean up DROP TABLE pxtest2; -DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled +-- pxtest3 was already dropped DROP TABLE pxtest4; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 73c8d38b15f..1d020298fdc 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -309,9 +309,17 @@ COMMIT; -- check that the stats are reset. SELECT (n_tup_ins + n_tup_upd) > 0 AS has_data FROM pg_stat_all_tables WHERE relid = 'pg_shdescription'::regclass; +-- stats_reset may not be set for datid=0 and shared objects in +-- pg_stat_database, so reset once. SELECT pg_stat_reset_single_table_counters('pg_shdescription'::regclass); SELECT (n_tup_ins + n_tup_upd) > 0 AS has_data FROM pg_stat_all_tables WHERE relid = 'pg_shdescription'::regclass; +SELECT stats_reset AS shared_db_reset_before + FROM pg_stat_database WHERE datid = 0 \gset +-- Second reset for comparison. +SELECT pg_stat_reset_single_table_counters('pg_shdescription'::regclass); +SELECT stats_reset > :'shared_db_reset_before'::timestamptz AS has_updated + FROM pg_stat_database WHERE datid = 0; -- set back comment \if :{?description_before} diff --git a/src/test/regress/sql/tsearch.sql b/src/test/regress/sql/tsearch.sql index c931f1a06f3..3bf9924c705 100644 --- a/src/test/regress/sql/tsearch.sql +++ b/src/test/regress/sql/tsearch.sql @@ -569,6 +569,14 @@ SELECT ts_headline('english', SELECT ts_headline('english', 'foo bar', to_tsquery('english', '')); +-- Test for large values of StartSel, StopSel and FragmentDelimiter +SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'), + 'StartSel=' || repeat('x', 32768)); +SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'), + 'StopSel=' || repeat('x', 32768)); +SELECT ts_headline('english', 'foo barbar', to_tsquery('english', 'foo'), + 'FragmentDelimiter=' || repeat('x', 32768)); + --Rewrite sub system CREATE TABLE test_tsquery (txtkeyword TEXT, txtsample TEXT); diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql index edf17e50a47..184756b16ff 100644 --- a/src/test/regress/sql/window.sql +++ b/src/test/regress/sql/window.sql @@ -326,6 +326,32 @@ CREATE TEMP VIEW v_window AS SELECT pg_get_viewdef('v_window'); +-- test overflow frame specifications +SELECT sum(unique1) over (rows between current row and 9223372036854775807 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10; + +SELECT sum(unique1) over (rows between 9223372036854775807 following and 1 following), + unique1, four +FROM tenk1 WHERE unique1 < 10; + +SELECT last_value(unique1) over (ORDER BY four rows between current row and 9223372036854775807 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10; + +-- These test GROUPS mode with an offset large enough to cause overflow when +-- added to currentgroup. Although the overflow doesn't produce visibly wrong +-- results (due to the incremental nature of group pointer advancement), we +-- still need to protect against it as signed integer overflow is undefined +-- behavior in C. +SELECT sum(unique1) over (ORDER BY four groups between current row and 9223372036854775807 following), + unique1, four +FROM tenk1 WHERE unique1 < 10; + +SELECT sum(unique1) over (ORDER BY four groups between 9223372036854775807 following and unbounded following), + unique1, four +FROM tenk1 WHERE unique1 < 10; + -- RANGE offset PRECEDING/FOLLOWING tests SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding), diff --git a/src/timezone/data/tzdata.zi b/src/timezone/data/tzdata.zi index c56f67c02f6..53082964703 100644 --- a/src/timezone/data/tzdata.zi +++ b/src/timezone/data/tzdata.zi @@ -1,4 +1,5 @@ -# version 2025c +# version 2026b +# redo posix_only # This zic input file is in the public domain. R d 1916 o - Jun 14 23s 1 S R d 1916 1919 - O Su>=1 23s 0 - @@ -1304,8 +1305,8 @@ R MT 1974 o - S 16 0s 0 - R MT 1975 1979 - Ap Su>=15 2 1 S R MT 1975 1980 - S Su>=15 2 0 - R MT 1980 o - Mar 31 2 1 S -R MD 1997 ma - Mar lastSu 2 1 S -R MD 1997 ma - O lastSu 3 0 - +R MD 1997 2021 - Mar lastSu 2 1 S +R MD 1997 2021 - O lastSu 3 0 - R O 1918 1919 - S 16 2s 0 - R O 1919 o - Ap 15 2s 1 S R O 1944 o - Ap 3 2s 1 S @@ -2965,7 +2966,9 @@ Z America/Toronto -5:17:32 - LMT 1895 -5 C E%sT Z America/Vancouver -8:12:28 - LMT 1884 -8 Va P%sT 1987 --8 C P%sT +-8 C P%sT 2026 Mar 9 +-8 1 PDT 2026 N 1 2 +-7 - MST Z America/Whitehorse -9:0:12 - LMT 1900 Au 20 -9 Y Y%sT 1965 -9 Yu Y%sT 1966 F 27 @@ -3666,7 +3669,8 @@ Z Europe/Chisinau 1:55:20 - LMT 1880 3 R MSK/MSD 1990 May 6 2 2 R EE%sT 1992 2 e EE%sT 1997 -2 MD EE%sT +2 MD EE%sT 2022 +2 E EE%sT Z Europe/Dublin -0:25:21 - LMT 1880 Au 2 -0:25:21 - DMT 1916 May 21 2s -0:25:21 1 IST 1916 O 1 2s diff --git a/src/timezone/strftime.c b/src/timezone/strftime.c index dd6c7db8695..3c545026a5b 100644 --- a/src/timezone/strftime.c +++ b/src/timezone/strftime.c @@ -122,6 +122,13 @@ static char *_yconv(int, int, bool, bool, char *, char const *); * Convert timestamp t to string s, a caller-allocated buffer of size maxsize, * using the given format pattern. * + * Unlike standard strftime(), we guarantee to provide a null-terminated + * result even on failure, so long as maxsize > 0. If we overrun the buffer, + * return an empty string rather than risking mis-encoded multibyte output. + * (Since this module only supports C locale, you might think multibyte + * characters are impossible --- but the time zone name printed by %Z comes + * from outside and could contain such.) + * * See also timestamptz_to_str. */ size_t @@ -135,11 +142,15 @@ pg_strftime(char *s, size_t maxsize, const char *format, const struct pg_tm *t) if (!p) { errno = EOVERFLOW; + if (maxsize > 0) + *s = '\0'; return 0; } if (p == s + maxsize) { errno = ERANGE; + if (maxsize > 0) + *s = '\0'; return 0; } *p = '\0'; diff --git a/src/timezone/zic.c b/src/timezone/zic.c index 0ea6ead2db3..6ccd739dcd8 100644 --- a/src/timezone/zic.c +++ b/src/timezone/zic.c @@ -2629,7 +2629,7 @@ doabbr(char *abbr, struct zone const *zp, char const *letters, bool isdst, zic_t save, bool doquotes) { char *cp; - char *slashp; + char const *slashp; size_t len; char const *format = zp->z_format; diff --git a/src/tools/make_ctags b/src/tools/make_ctags index 9e952ce916f..ad027c71e3d 100755 --- a/src/tools/make_ctags +++ b/src/tools/make_ctags @@ -1,16 +1,54 @@ #!/bin/sh -# src/tools/make_ctags +# src/tools/make_ctags [-e] [-n] +# If -e is specified, generate tags files for emacs. +# If -n is specified, don't create symbolic links of tags file. +usage="Usage: $0 [-e][-n]" +if [ $# -gt 2 ] +then echo $usage + exit 1 +fi -command -v ctags >/dev/null || \ - { echo "'ctags' program not found" 1>&2; exit 1; } +EMACS_MODE= +NO_SYMLINK= +IS_EXUBERANT= +PROG="ctags" +TAGS_OPT="-a -f" +TAGS_FILE="tags" +FLAGS= +IGNORE_IDENTIFIES= -trap "rm -f /tmp/$$" 0 1 2 3 15 -rm -f ./tags +while [ $# -gt 0 ] +do + if [ $1 = "-e" ] + then EMACS_MODE="Y" + elif [ $1 = "-n" ] + then NO_SYMLINK="Y" + else + echo $usage + exit 1 + fi + shift +done + +if [ ! "$EMACS_MODE" ] +then (command -v ctags >/dev/null) || \ + { echo "'ctags' program not found" 1>&2; exit 1; } +fi -IS_EXUBERANT="" ctags --version 2>&1 | grep Exuberant && IS_EXUBERANT="Y" +if [ "$EMACS_MODE" ] +then TAGS_FILE="TAGS" + if [ "$IS_EXUBERANT" ] + then PROG="ctags -e" + else (command -v etags >/dev/null) || \ + { echo "neither 'etags' nor exuberant 'ctags' program not found" 1>&2; exit 1; } + PROG="etags" + TAGS_OPT="-a -o" + fi +fi + # List of kinds supported by Exuberant Ctags 5.8 # generated by ctags --list-kinds # --c-kinds was called --c-types before 2003 @@ -31,12 +69,23 @@ ctags --version 2>&1 | grep Exuberant && IS_EXUBERANT="Y" if [ "$IS_EXUBERANT" ] then FLAGS="--c-kinds=+dfmstuv" -else FLAGS="-dt" +elif [ ! "$EMACS_MODE" ] +then FLAGS="-dt" +fi + +# Use -I option to ignore a macro +if [ "$IS_EXUBERANT" ] +then IGNORE_IDENTIFIES="-I pg_node_attr+" fi +trap "ret=$?; rm -rf /tmp/$$; exit $ret" 0 1 2 3 15 +rm -f ./$TAGS_FILE + # this is outputting the tags into the file 'tags', and appending -find `pwd`/ -type f -name '*.[chyl]' -print | - xargs ctags -a -f tags "$FLAGS" +find `pwd`/ \( -name tmp_install -prune -o -name tmp_check -prune \) \ + -o \( -name "*.[chly]" -o -iname "*makefile*" -o -name "*.mk" -o -name "*.in" \ + -o -name "*.sql" -o -name "*.p[lm]" \) -type f -print | + xargs $PROG $TAGS_OPT $TAGS_FILE $FLAGS $IGNORE_IDENTIFIES # Sorting non-Exuberant ctags file allows for fast searching of the tags file. # Since etags file has a header that we cannot sort in with the other entries @@ -44,10 +93,13 @@ find `pwd`/ -type f -name '*.[chyl]' -print | if [ ! "$IS_EXUBERANT" -a ! "$EMACS_MODE" ] then LC_ALL=C export LC_ALL - sort tags >/tmp/$$ && mv /tmp/$$ tags + sort $TAGS_FILE >/tmp/$$ && mv /tmp/$$ $TAGS_FILE fi -find . \( -name 'CVS' -prune \) -o \( -name .git -prune \) -o -type d -print | -while read DIR -do [ "$DIR" != "." ] && ln -f -s `echo "$DIR" | sed 's;/[^/]*;/..;g'`/tags "$DIR"/tags -done +# create symbolic links +if [ ! "$NO_SYMLINK" ] +then find . \( -name 'CVS' -prune \) -o \( -name .git -prune \) -o -type d -print | + while read DIR + do [ "$DIR" != "." ] && ln -f -s `echo "$DIR" | sed 's;/[^/]*;/..;g'`/$TAGS_FILE "$DIR"/$TAGS_FILE + done +fi diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 3cbc8a83984..e0108a43225 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -106,7 +106,7 @@ sub mkvcbuild pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c pg_strong_random.c pgcheckdir.c pgmkdirp.c pgsleep.c pgstrcasecmp.c pqsignal.c mkdtemp.c qsort.c qsort_arg.c bsearch_arg.c quotes.c system.c - strerror.c tar.c + strerror.c tar.c timingsafe_bcmp.c win32common.c win32env.c win32error.c win32fseek.c win32ntdll.c win32security.c win32setlocale.c win32stat.c); diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm index 4ce258ef6c3..3a6159edcd6 100644 --- a/src/tools/msvc/Solution.pm +++ b/src/tools/msvc/Solution.pm @@ -253,6 +253,7 @@ sub GenerateFiles HAVE_DECL_STRNLEN => 1, HAVE_DECL_STRTOLL => 1, HAVE_DECL_STRTOULL => 1, + HAVE_DECL_TIMINGSAFE_BCMP => 0, HAVE_DLOPEN => undef, HAVE_EDITLINE_HISTORY_H => undef, HAVE_EDITLINE_READLINE_H => undef, @@ -415,6 +416,7 @@ sub GenerateFiles HAVE_SYS_UIO_H => undef, HAVE_SYS_UN_H => undef, HAVE_TERMIOS_H => undef, + HAVE_TIMINGSAFE_BCMP => undef, HAVE_TYPEOF => undef, HAVE_UCRED_H => undef, HAVE_UINT64 => undef, @@ -463,13 +465,14 @@ sub GenerateFiles PACKAGE_TARNAME => lc qq{"$package_name"}, PACKAGE_URL => qq{"$package_url"}, PACKAGE_VERSION => qq{"$package_version"}, + PG_CXX_PRINTF_ATTRIBUTE => undef, + PG_C_PRINTF_ATTRIBUTE => undef, PG_INT128_TYPE => undef, PG_INT64_TYPE => 'long long int', PG_KRB_SRVNAM => qq{"postgres"}, PG_MAJORVERSION => qq{"$majorver"}, PG_MAJORVERSION_NUM => $majorver, PG_MINORVERSION_NUM => $minorver, - PG_PRINTF_ATTRIBUTE => undef, PG_USE_STDBOOL => 1, PG_VERSION => qq{"$package_version$extraver"}, PG_VERSION_NUM => sprintf("%d%04d", $majorver, $minorver), diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index bf6bcb13a4f..848692753d1 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -577,6 +577,7 @@ DisableTimeoutParams DiscardMode DiscardStmt DistanceValue +DistinctColInfo DistinctExpr DoStmt DocRepresentation