Ctrl K
ComunidadGuías…

patches/neon-postgres-17.11.patch

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
diff --git a/.abi-compliance-history b/.abi-compliance-history
new file mode 100644
index 0000000000000000000000000000000000000000..7053a4efb2e9677920342ae1ebf895bb36b4be4e
--- /dev/null
+++ b/.abi-compliance-history
@@ -0,0 +1,109 @@
+# Reference point for ABI compliance checks
+#
+# This file lists commits on the current branch that break ABI compatibility in
+# ways that have been deemed acceptable (e.g., removing an extern function with
+# no third-party uses).  The primary intent of this file is to control the ABI
+# compliance checks on the buildfarm, but it also serves as a central location
+# to document the justification for each.
+#
+# In general, entries should be added reactively after an abi-compliance-check
+# buildfarm failure.  It is important to verify the details of the breakage
+# match expectations, as the first entry listed will become the updated ABI
+# baseline point.
+#
+# Add new entries by adding the output of the following to the top of the file:
+#
+# $ git log --pretty=format:"%H%n#%n# %s%n# %cd%n#%n# <ADD JUSTIFICATION HERE>" $ABIBREAKGITHASH -1 --date=iso
+#
+# Be sure to replace "<ADD JUSTIFICATION HERE>" with details of your change and
+# why it is deemed acceptable.
+
+d1c8aa0b09f18bba1343ce2e4aa537a17a8a972f
+#
+# Check for USAGE privilege on types used by stored expressions.
+# 2026-08-10 06:38:19 -0700
+#
+# The addition of a parameter to AlterDomainAddConstraint() breaks ABI
+# compatibility, but we are unaware of any impacted third-party code.
+
+01992176e08169b7aedf2d61be9e22518b5c6640
+#
+# Add an output_plugin_libraries GUC to bless trusted output plugins
+# 2026-08-10 06:38:18 -0700
+#
+# Addition of a new GUC enlarges ConfigureNamesString[],
+# but that is not harmful.
+
+75a03c569c761fa27ba15975f342b72ab0d28946
+#
+# Preserve the owner of extended statistics rebuilt by ALTER TABLE.
+# 2026-08-10 06:38:18 -0700
+#
+# CreateStatistics()'s signature changes and CreateStatsStmt gains a field,
+# but no known third-party code calls the former or constructs the latter.
+
+586f4266fb4945f6ea3564b9c1bab093eb74bee4
+#
+# Fix ABI break by moving PROCSIG_SLOTSYNC_MESSAGE in ProcSignalReason
+# 2026-04-09 15:30:59 +0900
+#
+# This commit added PROCSIG_SLOTSYNC_MESSAGE just before the final entry,
+# NUM_PROCSIGNALS, in enum ProcSignalReason. This increased the value of
+# NUM_PROCSIGNALS, but no affected third-party code is known.
+
+4dac22aa10d2882c2e6fb465d7c314cc2d8fb754
+#
+# Add file_extend_method=posix_fallocate,write_zeros.
+# 2026-02-06 17:41:42 +1300
+#
+# Modifying GUC tables isn't really an ABI break: the relevant object has
+# incomplete type so its layout is inaccessible from other C translation units.
+#
+# Discussion: https://www.postgresql.org/message-id/flat/e1f0cd3b-0164-45f5-9705-e922e59df90f%40dunslane.net#8a350b54012c0042f9869d288e978cfe
+
+c5fc17ddaccff14bc22217df2b06ed43a5af16ba
+#
+# Fix trigger transition table capture for MERGE in CTE queries.
+# 2026-01-24 11:30:49 +0000
+#
+# This commit changed the TransitionCaptureState structure, replacing
+# the "tcs_private" field with 3 separate fields. This structure can
+# only be built using MakeTransitionCaptureState(), and PGXN contained
+# no calls to MakeTransitionCaptureState() or uses of the
+# TransitionCaptureState structure.
+
+0f69beddea113dd1d6c5b6f6d82df577ef3c21f2
+#
+# For inplace update, send nontransactional invalidations.
+# 2025-12-16 16:13:54 -0800
+#
+# This changed PrepareToInvalidateCacheTuple() parameters.  PGXN contained no
+# calls to that function.
+
+e2fb3dfa817fbe89494a62c100e9cb442f4d6b15
+#
+# Check for CREATE privilege on the schema in CREATE STATISTICS.
+# 2025-11-10 09:00:00 -0600
+#
+# This commit added a parameter to CreateStatistics().  We are unaware of any
+# impacted third-party code.
+
+24f6c1bd41d0631a04cc956cc8cafa0b117ab625
+#
+# Fix the handling of two GUCs during upgrade.
+# 2025-07-11 09:53:34 +0530
+#
+# GUC check function check_max_slot_wal_keep_size() was removed since it was
+# no longer needed. It seems highly improbable that any extension would be
+# calling it.
+
+45c357e0e85d2dffe7af5440806150124a725a01
+#
+# Fix re-distributing previously distributed invalidation messages during logical decoding.
+# 2025-06-16 17:35:58 -0700
+#
+# This is the original ABI baseline point for REL_17_STABLE.  The first entry
+# would ordinarily point to something just before the .0 release, but this file
+# was first added in October 2025, and we're unlikely to act upon ABI breaks in
+# released minor versions, so we've chosen to truncate the ABI history to start
+# with the most recent ABI break documented in the git commit history.
diff --git a/.cirrus.star b/.cirrus.star
index 36233872d1e50837dfa3260ad77c3a63fb93ce43..9018ad1e484622284047e150137512667aeb856b 100644
--- a/.cirrus.star
+++ b/.cirrus.star
@@ -7,7 +7,7 @@ https://github.com/bazelbuild/starlark/blob/master/spec.md
 See also .cirrus.yml and src/tools/ci/README
 """
 
-load("cirrus", "env", "fs")
+load("cirrus", "env", "fs", "re", "yaml")
 
 
 def main():
@@ -18,19 +18,36 @@ def main():
 
     1) the contents of .cirrus.yml
 
-    2) if defined, the contents of the file referenced by the, repository
+    2) computed environment variables
+
+    3) if defined, the contents of the file referenced by the, repository
        level, REPO_CI_CONFIG_GIT_URL variable (see
        https://cirrus-ci.org/guide/programming-tasks/#fs for the accepted
        format)
 
-    3) .cirrus.tasks.yml
+    4) .cirrus.tasks.yml
     """
 
     output = ""
 
     # 1) is evaluated implicitly
 
+
     # Add 2)
+    additional_env = compute_environment_vars()
+    env_fmt = """
+###
+# Computed environment variables start here
+###
+{0}
+###
+# Computed environment variables end here
+###
+"""
+    output += env_fmt.format(yaml.dumps({'env': additional_env}))
+
+
+    # Add 3)
     repo_config_url = env.get("REPO_CI_CONFIG_GIT_URL")
     if repo_config_url != None:
         print("loading additional configuration from \"{}\"".format(repo_config_url))
@@ -38,12 +55,73 @@ def main():
     else:
         output += "\n# REPO_CI_CONFIG_URL was not set\n"
 
-    # Add 3)
+
+    # Add 4)
     output += config_from(".cirrus.tasks.yml")
 
+
     return output
 
 
+def compute_environment_vars():
+    cenv = {}
+
+    ###
+    # Some tasks are manually triggered by default because they might use too
+    # many resources for users of free Cirrus credits, but they can be
+    # triggered automatically by naming them in an environment variable e.g.
+    # REPO_CI_AUTOMATIC_TRIGGER_TASKS="task_name other_task" under "Repository
+    # Settings" on Cirrus CI's website.
+
+    default_manual_trigger_tasks = ['mingw']
+
+    repo_ci_automatic_trigger_tasks = env.get('REPO_CI_AUTOMATIC_TRIGGER_TASKS', '')
+    for task in default_manual_trigger_tasks:
+        name = 'CI_TRIGGER_TYPE_' + task.upper()
+        if repo_ci_automatic_trigger_tasks.find(task) != -1:
+            value = 'automatic'
+        else:
+            value = 'manual'
+        cenv[name] = value
+    ###
+
+    ###
+    # Parse "ci-os-only:" tag in commit message and set
+    # CI_{$OS}_ENABLED variable for each OS
+
+    # We want to disable SanityCheck if testing just a specific OS. This
+    # shortens push-wait-for-ci cycle time a bit when debugging operating
+    # system specific failures. Just treating it as an OS in that case
+    # suffices.
+
+    operating_systems = [
+      'compilerwarnings',
+      'freebsd',
+      'linux',
+      'macos',
+      'mingw',
+      'sanitycheck',
+      'windows',
+    ]
+    commit_message = env.get('CIRRUS_CHANGE_MESSAGE')
+    match_re = r"(^|.*\n)ci-os-only: ([^\n]+)($|\n.*)"
+
+    # re.match() returns an array with a tuple of (matched-string, match_1, ...)
+    m = re.match(match_re, commit_message)
+    if m and len(m) > 0:
+        os_only = m[0][2]
+        os_only_list = re.split(r'[, ]+', os_only)
+    else:
+        os_only_list = operating_systems
+
+    for os in operating_systems:
+        os_enabled = os in os_only_list
+        cenv['CI_{0}_ENABLED'.format(os.upper())] = os_enabled
+    ###
+
+    return cenv
+
+
 def config_from(config_src):
     """return contents of config file `config_src`, surrounded by markers
     indicating start / end of the included file
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index aa7c7dcd34dbd00255cf85954a29cca96efd91f3..c0eb3afe78f4ae50ba26ae2774e36e4a6fc3ad28 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -62,13 +62,13 @@ task:
   # push-wait-for-ci cycle time a bit when debugging operating system specific
   # failures. Uses skip instead of only_if, as cirrus otherwise warns about
   # only_if conditions not matching.
-  skip: $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:.*'
+  skip: $CI_SANITYCHECK_ENABLED == false
 
   env:
     CPUS: 4
     BUILD_JOBS: 8
     TEST_JOBS: 8
-    IMAGE_FAMILY: pg-ci-bookworm
+    IMAGE_FAMILY: pg-ci-trixie
     CCACHE_DIR: ${CIRRUS_WORKING_DIR}/ccache_dir
     # no options enabled, should be small
     CCACHE_MAXSIZE: "150M"
@@ -94,6 +94,7 @@ task:
 
   configure_script: |
     su postgres <<-EOF
+      set -e
       meson setup \
         --buildtype=debug \
         --auto-features=disabled \
@@ -102,6 +103,7 @@ task:
     EOF
   build_script: |
     su postgres <<-EOF
+      set -e
       ninja -C build -j${BUILD_JOBS} ${MBUILD_TARGET}
     EOF
   upload_caches: ccache
@@ -111,6 +113,7 @@ task:
   # tap test that exercises both a frontend binary and the backend.
   test_minimal_script: |
     su postgres <<-EOF
+      set -e
       ulimit -c unlimited
       meson test $MTEST_ARGS --suite setup
       meson test $MTEST_ARGS --num-processes ${TEST_JOBS} \
@@ -144,7 +147,7 @@ task:
   <<: *freebsd_task_template
 
   depends_on: SanityCheck
-  only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*freebsd.*'
+  only_if: $CI_FREEBSD_ENABLED
 
   sysinfo_script: |
     id
@@ -173,6 +176,7 @@ task:
   # already takes longer than other platforms except for windows.
   configure_script: |
     su postgres <<-EOF
+      set -e
       meson setup \
         --buildtype=debug \
         -Dcassert=true -Dinjection_points=true \
@@ -186,6 +190,7 @@ task:
 
   test_world_script: |
     su postgres <<-EOF
+      set -e
       ulimit -c unlimited
       meson test $MTEST_ARGS --num-processes ${TEST_JOBS}
     EOF
@@ -210,6 +215,7 @@ task:
     # during upload, as it doesn't expect artifacts to change size
     stop_running_script: |
       su postgres <<-EOF
+        set -e
         build/tmp_install/usr/local/pgsql/bin/pg_ctl -D build/runningcheck stop || true
       EOF
     <<: *on_failure_meson
@@ -246,7 +252,7 @@ task:
     CPUS: 4
     BUILD_JOBS: 4
     TEST_JOBS: 8 # experimentally derived to be a decent choice
-    IMAGE_FAMILY: pg-ci-bookworm
+    IMAGE_FAMILY: pg-ci-trixie
 
     CCACHE_DIR: /tmp/ccache_dir
     DEBUGINFOD_URLS: "https://debuginfod.debian.net"
@@ -282,7 +288,7 @@ task:
   <<: *linux_task_template
 
   depends_on: SanityCheck
-  only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*linux.*'
+  only_if: $CI_LINUX_ENABLED
 
   ccache_cache:
     folder: ${CCACHE_DIR}
@@ -317,7 +323,7 @@ task:
     #DEBIAN_FRONTEND=noninteractive apt-get -y install ...
 
   matrix:
-    - name: Linux - Debian Bookworm - Autoconf
+    - name: Linux - Debian Trixie - Autoconf
 
       env:
         SANITIZER_FLAGS: -fsanitize=address
@@ -330,6 +336,7 @@ task:
       # that.
       configure_script: |
         su postgres <<-EOF
+          set -e
           ./configure \
             --enable-cassert --enable-injection-points --enable-debug \
             --enable-tap-tests --enable-nls \
@@ -344,6 +351,7 @@ task:
 
       test_world_script: |
         su postgres <<-EOF
+          set -e
           ulimit -c unlimited # default is 0
           make -s ${CHECK} ${CHECKFLAGS} -j${TEST_JOBS}
         EOF
@@ -351,7 +359,7 @@ task:
       on_failure:
         <<: *on_failure_ac
 
-    - name: Linux - Debian Bookworm - Meson
+    - name: Linux - Debian Trixie - Meson
 
       env:
         CCACHE_MAXSIZE: "400M" # tests two different builds
@@ -359,6 +367,7 @@ task:
 
       configure_script: |
         su postgres <<-EOF
+          set -e
           meson setup \
             --buildtype=debug \
             -Dcassert=true -Dinjection_points=true \
@@ -371,6 +380,7 @@ task:
       # locally.
       configure_32_script: |
         su postgres <<-EOF
+          set -e
           export CC='ccache gcc -m32'
           meson setup \
             --buildtype=debug \
@@ -378,7 +388,7 @@ task:
             ${LINUX_MESON_FEATURES} \
             -Dllvm=disabled \
             --pkg-config-path /usr/lib/i386-linux-gnu/pkgconfig/ \
-            -DPERL=perl5.36-i386-linux-gnu \
+            -DPERL=perl5.40-i386-linux-gnu \
             -DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
             build-32
         EOF
@@ -390,6 +400,7 @@ task:
 
       test_world_script: |
         su postgres <<-EOF
+          set -e
           ulimit -c unlimited
           meson test $MTEST_ARGS --num-processes ${TEST_JOBS}
         EOF
@@ -402,6 +413,7 @@ task:
       # from C, prevent that with PYTHONCOERCECLOCALE.
       test_world_32_script: |
         su postgres <<-EOF
+          set -e
           ulimit -c unlimited
           PYTHONCOERCECLOCALE=0 LANG=C meson test $MTEST_ARGS -C build-32 --num-processes ${TEST_JOBS}
         EOF
@@ -414,16 +426,16 @@ task:
 
 
 task:
-  name: macOS - Sonoma - Meson
+  name: macOS - Sequoia - Meson
 
   env:
     CPUS: 4 # always get that much for cirrusci macOS instances
     BUILD_JOBS: $CPUS
-    # Test performance regresses noticably when using all cores. 8 seems to
+    # Test performance regresses noticeably when using all cores. 8 seems to
     # work OK. See
     # https://postgr.es/m/20220927040208.l3shfcidovpzqxfh%40awork3.anarazel.de
     TEST_JOBS: 8
-    IMAGE: ghcr.io/cirruslabs/macos-runner:sonoma
+    IMAGE: ghcr.io/cirruslabs/macos-runner:sequoia
 
     CIRRUS_WORKING_DIR: ${HOME}/pgsql/
     CCACHE_DIR: ${HOME}/ccache
@@ -453,7 +465,7 @@ task:
   <<: *macos_task_template
 
   depends_on: SanityCheck
-  only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*(macos|darwin|osx).*'
+  only_if: $CI_MACOS_ENABLED
 
   sysinfo_script: |
     id
@@ -542,7 +554,7 @@ WINDOWS_ENVIRONMENT_BASE: &WINDOWS_ENVIRONMENT_BASE
 
 
 task:
-  name: Windows - Server 2019, VS 2019 - Meson & ninja
+  name: Windows - Server 2022, VS 2019 - Meson & ninja
   << : *WINDOWS_ENVIRONMENT_BASE
 
   env:
@@ -560,7 +572,7 @@ task:
   <<: *windows_task_template
 
   depends_on: SanityCheck
-  only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*windows.*'
+  only_if: $CI_WINDOWS_ENABLED
 
   setup_additional_packages_script: |
     REM choco install -y --no-progress ...
@@ -571,10 +583,9 @@ task:
     echo 127.0.0.3 pg-loadbalancetest >> c:\Windows\System32\Drivers\etc\hosts
     type c:\Windows\System32\Drivers\etc\hosts
 
-  # Use /DEBUG:FASTLINK to avoid high memory usage during linking
   configure_script: |
     vcvarsall x64
-    meson setup --backend ninja --buildtype debug -Dc_link_args=/DEBUG:FASTLINK -Dcassert=true -Dinjection_points=true -Db_pch=true -Dextra_lib_dirs=c:\openssl\1.1\lib -Dextra_include_dirs=c:\openssl\1.1\include -DTAR=%TAR% -DPG_TEST_EXTRA="%PG_TEST_EXTRA%" build
+    meson setup --backend ninja --buildtype debug -Dcassert=true -Dinjection_points=true -Db_pch=true -Dextra_lib_dirs=c:\openssl\1.1\lib -Dextra_include_dirs=c:\openssl\1.1\include -DTAR=%TAR% -DPG_TEST_EXTRA="%PG_TEST_EXTRA%" build
 
   build_script: |
     vcvarsall x64
@@ -593,15 +604,13 @@ task:
 
 task:
   << : *WINDOWS_ENVIRONMENT_BASE
-  name: Windows - Server 2019, MinGW64 - Meson
-
-  # due to resource constraints we don't run this task by default for now
-  trigger_type: manual
-  # worth using only_if despite being manual, otherwise this task will show up
-  # when e.g. ci-os-only: linux is used.
-  only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*mingw.*'
-  # otherwise it'll be sorted before other tasks
+  name: Windows - Server 2022, MinGW64 - Meson
+
+  # See REPO_CI_AUTOMATIC_TRIGGER_TASKS in .cirrus.star.
+  trigger_type: $CI_TRIGGER_TYPE_MINGW
+
   depends_on: SanityCheck
+  only_if: $CI_MINGW_ENABLED
 
   env:
     TEST_JOBS: 4 # higher concurrency causes occasional failures
@@ -655,15 +664,14 @@ task:
 
   # To limit unnecessary work only run this once the SanityCheck
   # succeeds. This is particularly important for this task as we intentionally
-  # use always: to continue after failures. Task that did not run count as a
-  # success, so we need to recheck SanityChecks's condition here ...
+  # use always: to continue after failures.
   depends_on: SanityCheck
-  only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*'
+  only_if: $CI_COMPILERWARNINGS_ENABLED
 
   env:
     CPUS: 4
     BUILD_JOBS: 4
-    IMAGE_FAMILY: pg-ci-bookworm
+    IMAGE_FAMILY: pg-ci-trixie
 
     # Use larger ccache cache, as this task compiles with multiple compilers /
     # flag combinations
@@ -749,11 +757,11 @@ task:
   always:
     mingw_cross_warning_script: |
       time ./configure \
-        --host=x86_64-w64-mingw32 \
+        --host=x86_64-w64-mingw32ucrt \
         --enable-cassert \
         --without-icu \
-        CC="ccache x86_64-w64-mingw32-gcc" \
-        CXX="ccache x86_64-w64-mingw32-g++"
+        CC="ccache x86_64-w64-mingw32ucrt-gcc" \
+        CXX="ccache x86_64-w64-mingw32ucrt-g++"
       make -s -j${BUILD_JOBS} clean
       time make -s -j${BUILD_JOBS} world-bin
 
@@ -777,14 +785,11 @@ task:
   # - Don't use ccache, the files are uncacheable, polluting ccache's
   #   cache
   # - Use -fmax-errors, as particularly cpluspluscheck can be very verbose
-  # - XXX have to disable ICU to avoid errors:
-  #   https://postgr.es/m/20220323002024.f2g6tivduzrktgfa%40alap3.anarazel.de
   ###
   always:
     headers_headerscheck_script: |
       time ./configure \
         ${LINUX_CONFIGURE_FEATURES} \
-        --without-icu \
         --quiet \
         CC="gcc" CXX"=g++" CLANG="clang"
       make -s -j${BUILD_JOBS} clean
diff --git a/.cirrus.yml b/.cirrus.yml
index a83129ae46d1e37d14d0ac1da1b28a0e3c30651b..f270f61241f81d5ef4fe39145e09bdf6aa505efe 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -10,12 +10,20 @@
 #
 # 1) the contents of this file
 #
-# 2) if defined, the contents of the file referenced by the, repository
+# 2) computed environment variables
+#
+#    Used to enable/disable tasks based on the execution environment. See
+#    .cirrus.star: compute_environment_vars()
+#
+# 3) if defined, the contents of the file referenced by the, repository
 #    level, REPO_CI_CONFIG_GIT_URL variable (see
 #    https://cirrus-ci.org/guide/programming-tasks/#fs for the accepted
 #    format)
 #
-# 3) .cirrus.tasks.yml
+#    This allows running tasks in a different execution environment than the
+#    default, e.g. to have sufficient resources for cfbot.
+#
+# 4) .cirrus.tasks.yml
 #
 # This composition is done by .cirrus.star
 
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 3538e0cdc757cd276689342d69523e7629e5a61e..6a6507df25b11d233f9b605e391c92c5890aaa5f 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -14,6 +14,9 @@
 #
 # $ git log --pretty=format:"%H # %cd%n# %s" $PGINDENTGITHASH -1 --date=iso
 
+c0897125171035e4d4b9fc8cf8a099131ac45281 # 2025-10-21 09:56:26 -0500
+# Re-pgindent brin.c.
+
 c739ae9e288c095cfe1b91ce27a2f2c075ed5da4 # 2024-08-26 16:16:09 -0700
 # Fix identation.
 
diff --git a/.gitattributes b/.gitattributes
index 830313e90f34577601035759f16a1cb3d03895cd..3b94e37229bcf606cf220dd0a8ac0644f5ec6e49 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -7,8 +7,8 @@
 *.x[ms]l	whitespace=space-before-tab,trailing-space,tab-in-indent
 
 # Avoid confusing ASCII underlines with leftover merge conflict markers
-README		conflict-marker-size=32
-README.*	conflict-marker-size=32
+README		conflict-marker-size=48
+README.*	conflict-marker-size=48
 
 # Certain data files that contain special whitespace, and other special cases
 *.data						-whitespace
diff --git a/COPYRIGHT b/COPYRIGHT
index be2d694b038546991249bdc33a0bb6158bf20e0d..0a397648dcd3c2177acc58bd7daecd11ad64be62 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -1,7 +1,7 @@
 PostgreSQL Database Management System
-(formerly known as Postgres, then as Postgres95)
+(also known as Postgres, formerly known as Postgres95)
 
-Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
 
 Portions Copyright (c) 1994, The Regents of the University of California
 
diff --git a/config/c-compiler.m4 b/config/c-compiler.m4
index 10f8c7bd0a9fbb12a2e7c0060560fe385dd9d01b..c9b16a19231a2415f7a4b855f219fcfcdf728b11 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/config/llvm.m4 b/config/llvm.m4
index c6cf8858f645e5a2d3baf0b96a44f5affddc6971..7a3fff50638094f37e23adb640f2dde270be4d21 100644
--- a/config/llvm.m4
+++ b/config/llvm.m4
@@ -4,7 +4,7 @@
 # -----------------
 #
 # Look for the LLVM installation, check that it's new enough, set the
-# corresponding LLVM_{CFLAGS,CXXFLAGS,BINPATH} and LDFLAGS
+# corresponding LLVM_{CFLAGS,CXXFLAGS,BINPATH,LIBS}
 # variables. Also verify that CLANG is available, to transform C
 # into bitcode.
 #
@@ -55,7 +55,7 @@ AC_DEFUN([PGAC_LLVM_SUPPORT],
 
   for pgac_option in `$LLVM_CONFIG --ldflags`; do
     case $pgac_option in
-      -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+      -L*) LLVM_LIBS="$LLVM_LIBS $pgac_option";;
     esac
   done
 
diff --git a/configure b/configure
index 430d17330c60289ce65a21fc93952d8078a783c2..1a0a6fafd182b776233c25ffabb1bf39b47962f6 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 17.5.
+# Generated by GNU Autoconf 2.69 for PostgreSQL 17.11.
 #
 # Report bugs to <pgsql-bugs@lists.postgresql.org>.
 #
@@ -582,8 +582,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='PostgreSQL'
 PACKAGE_TARNAME='postgresql'
-PACKAGE_VERSION='17.5'
-PACKAGE_STRING='PostgreSQL 17.5'
+PACKAGE_VERSION='17.11'
+PACKAGE_STRING='PostgreSQL 17.11'
 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 17.5 to adapt to many kinds of systems.
+\`configure' configures PostgreSQL 17.11 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 17.5:";;
+     short | recursive ) echo "Configuration of PostgreSQL 17.11:";;
    esac
   cat <<\_ACEOF
 
@@ -1693,7 +1693,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-PostgreSQL configure 17.5
+PostgreSQL configure 17.11
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2446,7 +2446,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 17.5, which was
+It was created by PostgreSQL $as_me 17.11, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -5216,7 +5216,7 @@ fi
 
   for pgac_option in `$LLVM_CONFIG --ldflags`; do
     case $pgac_option in
-      -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+      -L*) LLVM_LIBS="$LLVM_LIBS $pgac_option";;
     esac
   done
 
@@ -9056,12 +9056,12 @@ fi
   # Note the user could also set XML2_CFLAGS/XML2_LIBS directly
   for pgac_option in $XML2_CFLAGS; do
     case $pgac_option in
-      -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+      -I*|-D*) INCLUDES="$INCLUDES $pgac_option";;
     esac
   done
   for pgac_option in $XML2_LIBS; do
     case $pgac_option in
-      -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+      -L*) LIBDIRS="$LIBDIRS $pgac_option";;
     esac
   done
 fi
@@ -9286,12 +9286,12 @@ fi
   # note that -llz4 will be added by AC_CHECK_LIB below.
   for pgac_option in $LZ4_CFLAGS; do
     case $pgac_option in
-      -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+      -I*|-D*) INCLUDES="$INCLUDES $pgac_option";;
     esac
   done
   for pgac_option in $LZ4_LIBS; do
     case $pgac_option in
-      -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+      -L*) LIBDIRS="$LIBDIRS $pgac_option";;
     esac
   done
 fi
@@ -9427,12 +9427,12 @@ fi
   # note that -lzstd will be added by AC_CHECK_LIB below.
   for pgac_option in $ZSTD_CFLAGS; do
     case $pgac_option in
-      -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+      -I*|-D*) INCLUDES="$INCLUDES $pgac_option";;
     esac
   done
   for pgac_option in $ZSTD_LIBS; do
     case $pgac_option in
-      -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+      -L*) LIBDIRS="$LIBDIRS $pgac_option";;
     esac
   done
 fi
@@ -14450,8 +14450,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
@@ -14511,7 +14511,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
 
 
@@ -15358,7 +15448,7 @@ fi
 LIBS_including_readline="$LIBS"
 LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
 
-for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strsignal syncfs sync_file_range uselocale wcstombs_l
+for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strsignal syncfs sync_file_range uselocale wcstombs_l
 do :
   as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
@@ -15874,6 +15964,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_CHECK_FUNCS to detect these functions, because it
@@ -15914,6 +16014,19 @@ cat >>confdefs.h <<_ACEOF
 #define HAVE_DECL_STRCHRNUL $ac_have_decl
 _ACEOF
 
+ac_fn_c_check_decl "$LINENO" "memset_s" "ac_cv_have_decl_memset_s" "#define __STDC_WANT_LIB_EXT1__ 1
+#include <string.h>
+"
+if test "x$ac_cv_have_decl_memset_s" = xyes; then :
+  ac_have_decl=1
+else
+  ac_have_decl=0
+fi
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_DECL_MEMSET_S $ac_have_decl
+_ACEOF
+
 
 # This is probably only present on macOS, but may as well check always
 ac_fn_c_check_decl "$LINENO" "F_FULLFSYNC" "ac_cv_have_decl_F_FULLFSYNC" "#include <fcntl.h>
@@ -16033,6 +16146,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
+
 
 
 ac_fn_c_check_func "$LINENO" "pthread_barrier_wait" "ac_cv_func_pthread_barrier_wait"
@@ -16329,7 +16455,7 @@ fi
 
 if test "$with_icu" = yes; then
   ac_save_CPPFLAGS=$CPPFLAGS
-  CPPFLAGS="$ICU_CFLAGS $CPPFLAGS"
+  CPPFLAGS="$CPPFLAGS $ICU_CFLAGS"
 
   # Verify we have ICU's header files
   ac_fn_c_check_header_mongrel "$LINENO" "unicode/ucol.h" "ac_cv_header_unicode_ucol_h" "$ac_includes_default"
@@ -17480,7 +17606,7 @@ $as_echo "#define HAVE_GCC__ATOMIC_INT64_CAS 1" >>confdefs.h
 fi
 
 
-# Check for x86 cpuid instruction
+# Check for __get_cpuid() and __cpuid()
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __get_cpuid" >&5
 $as_echo_n "checking for __get_cpuid... " >&6; }
 if ${pgac_cv__get_cpuid+:} false; then :
@@ -17513,77 +17639,79 @@ if test x"$pgac_cv__get_cpuid" = x"yes"; then
 
 $as_echo "#define HAVE__GET_CPUID 1" >>confdefs.h
 
-fi
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __get_cpuid_count" >&5
-$as_echo_n "checking for __get_cpuid_count... " >&6; }
-if ${pgac_cv__get_cpuid_count+:} false; then :
+else
+  # __cpuid()
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __cpuid" >&5
+$as_echo_n "checking for __cpuid... " >&6; }
+if ${pgac_cv__cpuid+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
-#include <cpuid.h>
+#include <intrin.h>
 int
 main ()
 {
 unsigned int exx[4] = {0, 0, 0, 0};
-  __get_cpuid_count(7, 0, &exx[0], &exx[1], &exx[2], &exx[3]);
+    __cpuid(exx, 1);
 
   ;
   return 0;
 }
 _ACEOF
 if ac_fn_c_try_link "$LINENO"; then :
-  pgac_cv__get_cpuid_count="yes"
+  pgac_cv__cpuid="yes"
 else
-  pgac_cv__get_cpuid_count="no"
+  pgac_cv__cpuid="no"
 fi
 rm -f core conftest.err conftest.$ac_objext \
     conftest$ac_exeext conftest.$ac_ext
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__get_cpuid_count" >&5
-$as_echo "$pgac_cv__get_cpuid_count" >&6; }
-if test x"$pgac_cv__get_cpuid_count" = x"yes"; then
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__cpuid" >&5
+$as_echo "$pgac_cv__cpuid" >&6; }
+  if test x"$pgac_cv__cpuid" = x"yes"; then
 
-$as_echo "#define HAVE__GET_CPUID_COUNT 1" >>confdefs.h
+$as_echo "#define HAVE__CPUID 1" >>confdefs.h
 
+  fi
 fi
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __cpuid" >&5
-$as_echo_n "checking for __cpuid... " >&6; }
-if ${pgac_cv__cpuid+:} false; then :
+# Check for __get_cpuid_count() and __cpuidex() in a similar fashion.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __get_cpuid_count" >&5
+$as_echo_n "checking for __get_cpuid_count... " >&6; }
+if ${pgac_cv__get_cpuid_count+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
-#include <intrin.h>
+#include <cpuid.h>
 int
 main ()
 {
 unsigned int exx[4] = {0, 0, 0, 0};
-  __get_cpuid(exx[0], 1);
+  __get_cpuid_count(7, 0, &exx[0], &exx[1], &exx[2], &exx[3]);
 
   ;
   return 0;
 }
 _ACEOF
 if ac_fn_c_try_link "$LINENO"; then :
-  pgac_cv__cpuid="yes"
+  pgac_cv__get_cpuid_count="yes"
 else
-  pgac_cv__cpuid="no"
+  pgac_cv__get_cpuid_count="no"
 fi
 rm -f core conftest.err conftest.$ac_objext \
     conftest$ac_exeext conftest.$ac_ext
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__cpuid" >&5
-$as_echo "$pgac_cv__cpuid" >&6; }
-if test x"$pgac_cv__cpuid" = x"yes"; then
-
-$as_echo "#define HAVE__CPUID 1" >>confdefs.h
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__get_cpuid_count" >&5
+$as_echo "$pgac_cv__get_cpuid_count" >&6; }
+if test x"$pgac_cv__get_cpuid_count" = x"yes"; then
 
-fi
+$as_echo "#define HAVE__GET_CPUID_COUNT 1" >>confdefs.h
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __cpuidex" >&5
+else
+  # __cpuidex()
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __cpuidex" >&5
 $as_echo_n "checking for __cpuidex... " >&6; }
 if ${pgac_cv__cpuidex+:} false; then :
   $as_echo_n "(cached) " >&6
@@ -17595,7 +17723,7 @@ int
 main ()
 {
 unsigned int exx[4] = {0, 0, 0, 0};
-  __get_cpuidex(exx[0], 7, 0);
+    __cpuidex(exx, 7, 0);
 
   ;
   return 0;
@@ -17611,10 +17739,11 @@ rm -f core conftest.err conftest.$ac_objext \
 fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__cpuidex" >&5
 $as_echo "$pgac_cv__cpuidex" >&6; }
-if test x"$pgac_cv__cpuidex" = x"yes"; then
+  if test x"$pgac_cv__cpuidex" = x"yes"; then
 
 $as_echo "#define HAVE__CPUIDEX 1" >>confdefs.h
 
+  fi
 fi
 
 # Check for XSAVE intrinsics
@@ -18766,7 +18895,7 @@ Use --without-tcl to disable building PL/Tcl." "$LINENO" 5
     fi
     # now that we have TCL_INCLUDE_SPEC, we can check for <tcl.h>
     ac_save_CPPFLAGS=$CPPFLAGS
-    CPPFLAGS="$TCL_INCLUDE_SPEC $CPPFLAGS"
+    CPPFLAGS="$CPPFLAGS $TCL_INCLUDE_SPEC"
     ac_fn_c_check_header_mongrel "$LINENO" "tcl.h" "ac_cv_header_tcl_h" "$ac_includes_default"
 if test "x$ac_cv_header_tcl_h" = xyes; then :
 
@@ -18835,7 +18964,7 @@ fi
 # check for <Python.h>
 if test "$with_python" = yes; then
   ac_save_CPPFLAGS=$CPPFLAGS
-  CPPFLAGS="$python_includespec $CPPFLAGS"
+  CPPFLAGS="$CPPFLAGS $python_includespec"
   ac_fn_c_check_header_mongrel "$LINENO" "Python.h" "ac_cv_header_Python_h" "$ac_includes_default"
 if test "x$ac_cv_header_Python_h" = xyes; then :
 
@@ -19936,7 +20065,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 17.5, which was
+This file was extended by PostgreSQL $as_me 17.11, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -20007,7 +20136,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 17.5
+PostgreSQL config.status 17.11
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff --git a/configure.ac b/configure.ac
index 0782f08ce3e1a063f4e96ba6c615a820d16dbfdf..5c0d9fa3fe33266dad2a538b34ffa2d006a56a0f 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], [17.5], [pgsql-bugs@lists.postgresql.org], [], [https://www.postgresql.org/])
+AC_INIT([PostgreSQL], [17.11], [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
@@ -1052,12 +1052,12 @@ if test "$with_libxml" = yes ; then
   # Note the user could also set XML2_CFLAGS/XML2_LIBS directly
   for pgac_option in $XML2_CFLAGS; do
     case $pgac_option in
-      -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+      -I*|-D*) INCLUDES="$INCLUDES $pgac_option";;
     esac
   done
   for pgac_option in $XML2_LIBS; do
     case $pgac_option in
-      -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+      -L*) LIBDIRS="$LIBDIRS $pgac_option";;
     esac
   done
 fi
@@ -1101,12 +1101,12 @@ if test "$with_lz4" = yes; then
   # note that -llz4 will be added by AC_CHECK_LIB below.
   for pgac_option in $LZ4_CFLAGS; do
     case $pgac_option in
-      -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+      -I*|-D*) INCLUDES="$INCLUDES $pgac_option";;
     esac
   done
   for pgac_option in $LZ4_LIBS; do
     case $pgac_option in
-      -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+      -L*) LIBDIRS="$LIBDIRS $pgac_option";;
     esac
   done
 fi
@@ -1126,12 +1126,12 @@ if test "$with_zstd" = yes; then
   # note that -lzstd will be added by AC_CHECK_LIB below.
   for pgac_option in $ZSTD_CFLAGS; do
     case $pgac_option in
-      -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+      -I*|-D*) INCLUDES="$INCLUDES $pgac_option";;
     esac
   done
   for pgac_option in $ZSTD_LIBS; do
     case $pgac_option in
-      -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+      -L*) LIBDIRS="$LIBDIRS $pgac_option";;
     esac
   done
 fi
@@ -1644,6 +1644,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_STATIC_ASSERT
 PGAC_C_TYPEOF
 PGAC_C_TYPES_COMPATIBLE
@@ -1770,7 +1771,6 @@ AC_CHECK_FUNCS(m4_normalize([
 	inet_pton
 	kqueue
 	mbstowcs_l
-	memset_s
 	posix_fallocate
 	ppoll
 	pthread_is_threaded_np
@@ -1809,13 +1809,15 @@ AC_CHECK_DECLS(posix_fadvise, [], [], [#include <fcntl.h>])
 ]) # fi
 
 AC_CHECK_DECLS(fdatasync, [], [], [#include <unistd.h>])
-AC_CHECK_DECLS([strlcat, strlcpy, strnlen])
+AC_CHECK_DECLS([strlcat, strlcpy, strnlen, timingsafe_bcmp])
 
 # We can't use AC_CHECK_FUNCS to detect these functions, because it
 # won't handle deployment target restrictions on macOS
 AC_CHECK_DECLS([preadv], [], [], [#include <sys/uio.h>])
 AC_CHECK_DECLS([pwritev], [], [], [#include <sys/uio.h>])
 AC_CHECK_DECLS([strchrnul], [], [], [#include <string.h>])
+AC_CHECK_DECLS([memset_s], [], [], [#define __STDC_WANT_LIB_EXT1__ 1
+#include <string.h>])
 
 # This is probably only present on macOS, but may as well check always
 AC_CHECK_DECLS(F_FULLFSYNC, [], [], [#include <fcntl.h>])
@@ -1829,6 +1831,7 @@ AC_REPLACE_FUNCS(m4_normalize([
 	strlcat
 	strlcpy
 	strnlen
+	timingsafe_bcmp
 ]))
 
 AC_REPLACE_FUNCS(pthread_barrier_wait)
@@ -1913,7 +1916,7 @@ fi
 
 if test "$with_icu" = yes; then
   ac_save_CPPFLAGS=$CPPFLAGS
-  CPPFLAGS="$ICU_CFLAGS $CPPFLAGS"
+  CPPFLAGS="$CPPFLAGS $ICU_CFLAGS"
 
   # Verify we have ICU's header files
   AC_CHECK_HEADER(unicode/ucol.h, [],
@@ -2055,7 +2058,7 @@ PGAC_HAVE_GCC__ATOMIC_INT32_CAS
 PGAC_HAVE_GCC__ATOMIC_INT64_CAS
 
 
-# Check for x86 cpuid instruction
+# Check for __get_cpuid() and __cpuid()
 AC_CACHE_CHECK([for __get_cpuid], [pgac_cv__get_cpuid],
 [AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <cpuid.h>],
   [[unsigned int exx[4] = {0, 0, 0, 0};
@@ -2065,8 +2068,21 @@ AC_CACHE_CHECK([for __get_cpuid], [pgac_cv__get_cpuid],
   [pgac_cv__get_cpuid="no"])])
 if test x"$pgac_cv__get_cpuid" = x"yes"; then
   AC_DEFINE(HAVE__GET_CPUID, 1, [Define to 1 if you have __get_cpuid.])
+else
+  # __cpuid()
+  AC_CACHE_CHECK([for __cpuid], [pgac_cv__cpuid],
+  [AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <intrin.h>],
+    [[unsigned int exx[4] = {0, 0, 0, 0};
+    __cpuid(exx, 1);
+    ]])],
+    [pgac_cv__cpuid="yes"],
+    [pgac_cv__cpuid="no"])])
+  if test x"$pgac_cv__cpuid" = x"yes"; then
+    AC_DEFINE(HAVE__CPUID, 1, [Define to 1 if you have __cpuid.])
+  fi
 fi
 
+# Check for __get_cpuid_count() and __cpuidex() in a similar fashion.
 AC_CACHE_CHECK([for __get_cpuid_count], [pgac_cv__get_cpuid_count],
 [AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <cpuid.h>],
   [[unsigned int exx[4] = {0, 0, 0, 0};
@@ -2076,28 +2092,18 @@ AC_CACHE_CHECK([for __get_cpuid_count], [pgac_cv__get_cpuid_count],
   [pgac_cv__get_cpuid_count="no"])])
 if test x"$pgac_cv__get_cpuid_count" = x"yes"; then
   AC_DEFINE(HAVE__GET_CPUID_COUNT, 1, [Define to 1 if you have __get_cpuid_count.])
-fi
-
-AC_CACHE_CHECK([for __cpuid], [pgac_cv__cpuid],
-[AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <intrin.h>],
-  [[unsigned int exx[4] = {0, 0, 0, 0};
-  __get_cpuid(exx[0], 1);
-  ]])],
-  [pgac_cv__cpuid="yes"],
-  [pgac_cv__cpuid="no"])])
-if test x"$pgac_cv__cpuid" = x"yes"; then
-  AC_DEFINE(HAVE__CPUID, 1, [Define to 1 if you have __cpuid.])
-fi
-
-AC_CACHE_CHECK([for __cpuidex], [pgac_cv__cpuidex],
-[AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <intrin.h>],
-  [[unsigned int exx[4] = {0, 0, 0, 0};
-  __get_cpuidex(exx[0], 7, 0);
-  ]])],
-  [pgac_cv__cpuidex="yes"],
-  [pgac_cv__cpuidex="no"])])
-if test x"$pgac_cv__cpuidex" = x"yes"; then
-  AC_DEFINE(HAVE__CPUIDEX, 1, [Define to 1 if you have __cpuidex.])
+else
+  # __cpuidex()
+  AC_CACHE_CHECK([for __cpuidex], [pgac_cv__cpuidex],
+  [AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <intrin.h>],
+    [[unsigned int exx[4] = {0, 0, 0, 0};
+    __cpuidex(exx, 7, 0);
+    ]])],
+    [pgac_cv__cpuidex="yes"],
+    [pgac_cv__cpuidex="no"])])
+  if test x"$pgac_cv__cpuidex" = x"yes"; then
+    AC_DEFINE(HAVE__CPUIDEX, 1, [Define to 1 if you have __cpuidex.])
+  fi
 fi
 
 # Check for XSAVE intrinsics
@@ -2343,7 +2349,7 @@ Use --without-tcl to disable building PL/Tcl.])
     fi
     # now that we have TCL_INCLUDE_SPEC, we can check for <tcl.h>
     ac_save_CPPFLAGS=$CPPFLAGS
-    CPPFLAGS="$TCL_INCLUDE_SPEC $CPPFLAGS"
+    CPPFLAGS="$CPPFLAGS $TCL_INCLUDE_SPEC"
     AC_CHECK_HEADER(tcl.h, [], [AC_MSG_ERROR([header file <tcl.h> is required for Tcl])])
     CPPFLAGS=$ac_save_CPPFLAGS
 fi
@@ -2380,7 +2386,7 @@ fi
 # check for <Python.h>
 if test "$with_python" = yes; then
   ac_save_CPPFLAGS=$CPPFLAGS
-  CPPFLAGS="$python_includespec $CPPFLAGS"
+  CPPFLAGS="$CPPFLAGS $python_includespec"
   AC_CHECK_HEADER(Python.h, [], [AC_MSG_ERROR([header file <Python.h> is required for Python])])
   CPPFLAGS=$ac_save_CPPFLAGS
 fi
diff --git a/contrib/amcheck/expected/check_btree.out b/contrib/amcheck/expected/check_btree.out
index e7fb5f551574a344379a20e79dfc5b055c3a0deb..bab1fda69acc561d0a094e16295f53a5973609f4 100644
--- a/contrib/amcheck/expected/check_btree.out
+++ b/contrib/amcheck/expected/check_btree.out
@@ -178,7 +178,8 @@ SELECT bt_index_check('toasty', true);
 (1 row)
 
 --
--- Check that index expressions and predicates are run as the table's owner
+-- Check that index expressions and predicates are run as the table's owner,
+-- with empty search_path
 --
 TRUNCATE bttest_a;
 INSERT INTO bttest_a SELECT * FROM generate_series(1, 1000);
@@ -186,19 +187,31 @@ ALTER TABLE bttest_a OWNER TO regress_bttest_role;
 -- A dummy index function checking current_user
 CREATE FUNCTION ifun(int8) RETURNS int8 AS $$
 BEGIN
-	ASSERT current_user = 'regress_bttest_role',
+	ASSERT current_setting('search_path') = 'pg_catalog, pg_temp',
+		format('ifun(%s) called with current_schemas %s, search_path %s',
+			$1, current_schemas(true), current_setting('search_path'));
+	ASSERT "current_user"() = 'regress_bttest_role',
 		format('ifun(%s) called by %s', $1, current_user);
 	RETURN $1;
 END;
 $$ LANGUAGE plpgsql IMMUTABLE;
 CREATE INDEX bttest_a_expr_idx ON bttest_a ((ifun(id) + ifun(0)))
 	WHERE ifun(id + 10) > ifun(10);
+BEGIN;
+SET LOCAL check_function_bodies = off;
+CREATE SCHEMA preempt;
+GRANT USAGE ON SCHEMA preempt TO regress_bttest_role;
+SET LOCAL search_path = preempt, pg_catalog, public;
+CREATE FUNCTION "current_user"() RETURNS name AS $$
+	broken
+$$ LANGUAGE sql STABLE PARALLEL SAFE STRICT;
 SELECT bt_index_check('bttest_a_expr_idx', true);
  bt_index_check 
 ----------------
  
 (1 row)
 
+ROLLBACK;
 -- UNIQUE constraint check
 SELECT bt_index_check('bttest_a_idx', heapallindexed => true, checkunique => true);
  bt_index_check 
diff --git a/contrib/amcheck/sql/check_btree.sql b/contrib/amcheck/sql/check_btree.sql
index 0793dbfeebd821f5617e23fa889155b198801c26..9bd484b92a6118ce0e84374fbdba6d082e85f496 100644
--- a/contrib/amcheck/sql/check_btree.sql
+++ b/contrib/amcheck/sql/check_btree.sql
@@ -116,7 +116,8 @@ INSERT INTO toast_bug SELECT repeat('a', 2200);
 SELECT bt_index_check('toasty', true);
 
 --
--- Check that index expressions and predicates are run as the table's owner
+-- Check that index expressions and predicates are run as the table's owner,
+-- with empty search_path
 --
 TRUNCATE bttest_a;
 INSERT INTO bttest_a SELECT * FROM generate_series(1, 1000);
@@ -124,7 +125,10 @@ ALTER TABLE bttest_a OWNER TO regress_bttest_role;
 -- A dummy index function checking current_user
 CREATE FUNCTION ifun(int8) RETURNS int8 AS $$
 BEGIN
-	ASSERT current_user = 'regress_bttest_role',
+	ASSERT current_setting('search_path') = 'pg_catalog, pg_temp',
+		format('ifun(%s) called with current_schemas %s, search_path %s',
+			$1, current_schemas(true), current_setting('search_path'));
+	ASSERT "current_user"() = 'regress_bttest_role',
 		format('ifun(%s) called by %s', $1, current_user);
 	RETURN $1;
 END;
@@ -132,8 +136,16 @@ $$ LANGUAGE plpgsql IMMUTABLE;
 
 CREATE INDEX bttest_a_expr_idx ON bttest_a ((ifun(id) + ifun(0)))
 	WHERE ifun(id + 10) > ifun(10);
-
+BEGIN;
+SET LOCAL check_function_bodies = off;
+CREATE SCHEMA preempt;
+GRANT USAGE ON SCHEMA preempt TO regress_bttest_role;
+SET LOCAL search_path = preempt, pg_catalog, public;
+CREATE FUNCTION "current_user"() RETURNS name AS $$
+	broken
+$$ LANGUAGE sql STABLE PARALLEL SAFE STRICT;
 SELECT bt_index_check('bttest_a_expr_idx', true);
+ROLLBACK;
 
 -- UNIQUE constraint check
 SELECT bt_index_check('bttest_a_idx', heapallindexed => true, checkunique => true);
diff --git a/contrib/amcheck/t/002_cic.pl b/contrib/amcheck/t/002_cic.pl
index 53a3db9745f4061d09e81ea554c707a858db7eba..b49acb2b9ef2ebe74d8e4ddbf594048fe3d65bac 100644
--- a/contrib/amcheck/t/002_cic.pl
+++ b/contrib/amcheck/t/002_cic.pl
@@ -60,5 +60,29 @@ $node->pgbench(
 		  )
 	});
 
+# Test bt_index_parent_check() with indexes created with
+# CREATE INDEX CONCURRENTLY.
+$node->safe_psql('postgres', q(CREATE TABLE quebec(i int primary key)));
+# Insert two rows into index
+$node->safe_psql('postgres',
+	q(INSERT INTO quebec SELECT i FROM generate_series(1, 2) s(i);));
+
+# start background transaction
+my $in_progress_h = $node->background_psql('postgres');
+$in_progress_h->query_safe(q(BEGIN; SELECT pg_current_xact_id();));
+
+# delete one row from table, while background transaction is in progress
+$node->safe_psql('postgres', q(DELETE FROM quebec WHERE i = 1;));
+# create index concurrently, which will skip the deleted row
+$node->safe_psql('postgres',
+	q(CREATE INDEX CONCURRENTLY oscar ON quebec(i);));
+
+# check index using bt_index_parent_check
+$result = $node->psql('postgres',
+	q(SELECT bt_index_parent_check('oscar', heapallindexed => true)));
+is($result, '0', 'bt_index_parent_check for CIC after removed row');
+
+$in_progress_h->quit;
+
 $node->stop;
 done_testing();
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 34990c5cea3f3605bc54966dc94bef2ea632f864..823d8e678092f92cbd4be55fd8f929ea183d803f 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -90,9 +90,11 @@ typedef struct BtreeCheckState
 	BufferAccessStrategy checkstrategy;
 
 	/*
-	 * Info for uniqueness checking. Fill these fields once per index check.
+	 * Info for uniqueness checking. Fill this field and the one below once
+	 * per index check.
 	 */
 	IndexInfo  *indexinfo;
+	/* Table scan snapshot for heapallindexed and checkunique */
 	Snapshot	snapshot;
 
 	/*
@@ -508,7 +510,6 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 	BTMetaPageData *metad;
 	uint32		previouslevel;
 	BtreeLevel	current;
-	Snapshot	snapshot = SnapshotAny;
 
 	if (!readonly)
 		elog(DEBUG1, "verifying consistency of tree structure for index \"%s\"",
@@ -559,54 +560,46 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 		state->heaptuplespresent = 0;
 
 		/*
-		 * Register our own snapshot in !readonly case, rather than asking
+		 * Register our own snapshot for heapallindexed, rather than asking
 		 * table_index_build_scan() to do this for us later.  This needs to
 		 * happen before index fingerprinting begins, so we can later be
 		 * certain that index fingerprinting should have reached all tuples
 		 * returned by table_index_build_scan().
 		 */
-		if (!state->readonly)
-		{
-			snapshot = RegisterSnapshot(GetTransactionSnapshot());
+		state->snapshot = RegisterSnapshot(GetTransactionSnapshot());
 
-			/*
-			 * GetTransactionSnapshot() always acquires a new MVCC snapshot in
-			 * READ COMMITTED mode.  A new snapshot is guaranteed to have all
-			 * the entries it requires in the index.
-			 *
-			 * We must defend against the possibility that an old xact
-			 * snapshot was returned at higher isolation levels when that
-			 * snapshot is not safe for index scans of the target index.  This
-			 * is possible when the snapshot sees tuples that are before the
-			 * index's indcheckxmin horizon.  Throwing an error here should be
-			 * very rare.  It doesn't seem worth using a secondary snapshot to
-			 * avoid this.
-			 */
-			if (IsolationUsesXactSnapshot() && rel->rd_index->indcheckxmin &&
-				!TransactionIdPrecedes(HeapTupleHeaderGetXmin(rel->rd_indextuple->t_data),
-									   snapshot->xmin))
-				ereport(ERROR,
-						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-						 errmsg("index \"%s\" cannot be verified using transaction snapshot",
-								RelationGetRelationName(rel))));
-		}
+		/*
+		 * GetTransactionSnapshot() always acquires a new MVCC snapshot in
+		 * READ COMMITTED mode.  A new snapshot is guaranteed to have all the
+		 * entries it requires in the index.
+		 *
+		 * We must defend against the possibility that an old xact snapshot
+		 * was returned at higher isolation levels when that snapshot is not
+		 * safe for index scans of the target index.  This is possible when
+		 * the snapshot sees tuples that are before the index's indcheckxmin
+		 * horizon.  Throwing an error here should be very rare.  It doesn't
+		 * seem worth using a secondary snapshot to avoid this.
+		 */
+		if (IsolationUsesXactSnapshot() && rel->rd_index->indcheckxmin &&
+			!TransactionIdPrecedes(HeapTupleHeaderGetXmin(rel->rd_indextuple->t_data),
+								   state->snapshot->xmin))
+			ereport(ERROR,
+					errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+					errmsg("index \"%s\" cannot be verified using transaction snapshot",
+						   RelationGetRelationName(rel)));
 	}
 
 	/*
-	 * We need a snapshot to check the uniqueness of the index. For better
-	 * performance take it once per index check. If snapshot already taken
-	 * reuse it.
+	 * We need a snapshot to check the uniqueness of the index.  For better
+	 * performance, take it once per index check.  If one was already taken
+	 * above, use that.
 	 */
 	if (state->checkunique)
 	{
 		state->indexinfo = BuildIndexInfo(state->rel);
-		if (state->indexinfo->ii_Unique)
-		{
-			if (snapshot != SnapshotAny)
-				state->snapshot = snapshot;
-			else
-				state->snapshot = RegisterSnapshot(GetTransactionSnapshot());
-		}
+
+		if (state->indexinfo->ii_Unique && state->snapshot == InvalidSnapshot)
+			state->snapshot = RegisterSnapshot(GetTransactionSnapshot());
 	}
 
 	Assert(!state->rootdescend || state->readonly);
@@ -681,13 +674,12 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 		/*
 		 * Create our own scan for table_index_build_scan(), rather than
 		 * getting it to do so for us.  This is required so that we can
-		 * actually use the MVCC snapshot registered earlier in !readonly
-		 * case.
+		 * actually use the MVCC snapshot registered earlier.
 		 *
 		 * Note that table_index_build_scan() calls heap_endscan() for us.
 		 */
 		scan = table_beginscan_strat(state->heaprel,	/* relation */
-									 snapshot,	/* snapshot */
+									 state->snapshot,	/* snapshot */
 									 0, /* number of keys */
 									 NULL,	/* scan key */
 									 true,	/* buffer access strategy OK */
@@ -695,16 +687,15 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 
 		/*
 		 * Scan will behave as the first scan of a CREATE INDEX CONCURRENTLY
-		 * behaves in !readonly case.
+		 * behaves.
 		 *
 		 * It's okay that we don't actually use the same lock strength for the
-		 * heap relation as any other ii_Concurrent caller would in !readonly
-		 * case.  We have no reason to care about a concurrent VACUUM
-		 * operation, since there isn't going to be a second scan of the heap
-		 * that needs to be sure that there was no concurrent recycling of
-		 * TIDs.
+		 * heap relation as any other ii_Concurrent caller would.  We have no
+		 * reason to care about a concurrent VACUUM operation, since there
+		 * isn't going to be a second scan of the heap that needs to be sure
+		 * that there was no concurrent recycling of TIDs.
 		 */
-		indexinfo->ii_Concurrent = !state->readonly;
+		indexinfo->ii_Concurrent = true;
 
 		/*
 		 * Don't wait for uncommitted tuple xact commit/abort when index is a
@@ -728,14 +719,11 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 								 state->heaptuplespresent, RelationGetRelationName(heaprel),
 								 100.0 * bloom_prop_bits_set(state->filter))));
 
-		if (snapshot != SnapshotAny)
-			UnregisterSnapshot(snapshot);
-
 		bloom_free(state->filter);
 	}
 
 	/* Be tidy: */
-	if (snapshot == SnapshotAny && state->snapshot != InvalidSnapshot)
+	if (state->snapshot != InvalidSnapshot)
 		UnregisterSnapshot(state->snapshot);
 	MemoryContextDelete(state->targetcontext);
 }
@@ -847,7 +835,7 @@ bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level)
 							 errmsg("block %u is not leftmost in index \"%s\"",
 									current, RelationGetRelationName(state->rel))));
 
-				if (level.istruerootlevel && !P_ISROOT(opaque))
+				if (level.istruerootlevel && (!P_ISROOT(opaque) && !P_INCOMPLETE_SPLIT(opaque)))
 					ereport(ERROR,
 							(errcode(ERRCODE_INDEX_CORRUPTED),
 							 errmsg("block %u is not true root in index \"%s\"",
@@ -2367,7 +2355,7 @@ bt_child_highkey_check(BtreeCheckState *state,
 		 * If we visit page with high key, check that it is equal to the
 		 * target key next to corresponding downlink.
 		 */
-		if (!rightsplit && !P_RIGHTMOST(opaque))
+		if (!rightsplit && !P_RIGHTMOST(opaque) && !P_ISHALFDEAD(opaque))
 		{
 			BTPageOpaque topaque;
 			IndexTuple	highkey;
@@ -3000,7 +2988,7 @@ bt_normalize_tuple(BtreeCheckState *state, IndexTuple itup)
 							ItemPointerGetOffsetNumber(&(itup->t_tid)),
 							RelationGetRelationName(state->rel))));
 		else if (!VARATT_IS_COMPRESSED(DatumGetPointer(normalized[i])) &&
-				 VARSIZE(DatumGetPointer(normalized[i])) > TOAST_INDEX_TARGET &&
+				 VARSIZE_ANY(DatumGetPointer(normalized[i])) > TOAST_INDEX_TARGET &&
 				 (att->attstorage == TYPSTORAGE_EXTENDED ||
 				  att->attstorage == TYPSTORAGE_MAIN))
 		{
diff --git a/contrib/auto_explain/Makefile b/contrib/auto_explain/Makefile
index efd127d3cae640904124590ebbd073aff5b729b0..94ab28e7c06b91f06ce295fb1dbc96a84ab592f8 100644
--- a/contrib/auto_explain/Makefile
+++ b/contrib/auto_explain/Makefile
@@ -6,6 +6,8 @@ OBJS = \
 	auto_explain.o
 PGFILEDESC = "auto_explain - logging facility for execution plans"
 
+REGRESS = alter_reset
+
 TAP_TESTS = 1
 
 ifdef USE_PGXS
diff --git a/contrib/auto_explain/expected/alter_reset.out b/contrib/auto_explain/expected/alter_reset.out
new file mode 100644
index 0000000000000000000000000000000000000000..ec355189806ae969ebf9886e2ce3d7c9e4622b1d
--- /dev/null
+++ b/contrib/auto_explain/expected/alter_reset.out
@@ -0,0 +1,19 @@
+--
+-- This tests resetting unknown custom GUCs with reserved prefixes.  There's
+-- nothing specific to auto_explain; this is just a convenient place to put
+-- this test.
+--
+SELECT current_database() AS datname \gset
+CREATE ROLE regress_ae_role;
+ALTER DATABASE :"datname" SET auto_explain.bogus = 1;
+ALTER ROLE regress_ae_role SET auto_explain.bogus = 1;
+ALTER ROLE regress_ae_role IN DATABASE :"datname" SET auto_explain.bogus = 1;
+ALTER SYSTEM SET auto_explain.bogus = 1;
+LOAD 'auto_explain';
+WARNING:  invalid configuration parameter name "auto_explain.bogus", removing it
+DETAIL:  "auto_explain" is now a reserved prefix.
+ALTER DATABASE :"datname" RESET auto_explain.bogus;
+ALTER ROLE regress_ae_role RESET auto_explain.bogus;
+ALTER ROLE regress_ae_role IN DATABASE :"datname" RESET auto_explain.bogus;
+ALTER SYSTEM RESET auto_explain.bogus;
+DROP ROLE regress_ae_role;
diff --git a/contrib/auto_explain/meson.build b/contrib/auto_explain/meson.build
index af1a3b8e3250ec4d2fb11eca648d524b910a1146..4bf2fedf1dd5460940e69036f93b4d4676c5d50c 100644
--- a/contrib/auto_explain/meson.build
+++ b/contrib/auto_explain/meson.build
@@ -20,6 +20,11 @@ tests += {
   'name': 'auto_explain',
   'sd': meson.current_source_dir(),
   'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'alter_reset',
+    ],
+  },
   'tap': {
     'tests': [
       't/001_auto_explain.pl',
diff --git a/contrib/auto_explain/sql/alter_reset.sql b/contrib/auto_explain/sql/alter_reset.sql
new file mode 100644
index 0000000000000000000000000000000000000000..bf621454ec24a0f4fec8df0a594d3ea7ece4ec46
--- /dev/null
+++ b/contrib/auto_explain/sql/alter_reset.sql
@@ -0,0 +1,22 @@
+--
+-- This tests resetting unknown custom GUCs with reserved prefixes.  There's
+-- nothing specific to auto_explain; this is just a convenient place to put
+-- this test.
+--
+
+SELECT current_database() AS datname \gset
+CREATE ROLE regress_ae_role;
+
+ALTER DATABASE :"datname" SET auto_explain.bogus = 1;
+ALTER ROLE regress_ae_role SET auto_explain.bogus = 1;
+ALTER ROLE regress_ae_role IN DATABASE :"datname" SET auto_explain.bogus = 1;
+ALTER SYSTEM SET auto_explain.bogus = 1;
+
+LOAD 'auto_explain';
+
+ALTER DATABASE :"datname" RESET auto_explain.bogus;
+ALTER ROLE regress_ae_role RESET auto_explain.bogus;
+ALTER ROLE regress_ae_role IN DATABASE :"datname" RESET auto_explain.bogus;
+ALTER SYSTEM RESET auto_explain.bogus;
+
+DROP ROLE regress_ae_role;
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 028cf51c25d46f98707b4a391090c7c4b5337bcc..5e1c14a01f1f7d97f5572cf25dfe46ab3ab2822b 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -88,13 +88,11 @@ _PG_archive_module_init(void)
 /*
  * 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
@@ -113,17 +111,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/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index 84ca5eee5012cf49466beb4a170578a4dc15ffcb..602949c20557d96b965de4fe914f403ab58d3bca 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -25,30 +25,36 @@ PG_FUNCTION_INFO_V1(gbt_float4_distance);
 PG_FUNCTION_INFO_V1(gbt_float4_penalty);
 PG_FUNCTION_INFO_V1(gbt_float4_same);
 
+/*
+ * Use the NaN-aware comparators from utils/float.h, so that our results
+ * will agree with standard btree indexes.  Note that penalty and distance
+ * functions below must also cope with NaNs, in particular with the policy
+ * that all NaNs are equal.
+ */
 static bool
 gbt_float4gt(const void *a, const void *b, FmgrInfo *flinfo)
 {
-	return (*((const float4 *) a) > *((const float4 *) b));
+	return float4_gt(*((const float4 *) a), *((const float4 *) b));
 }
 static bool
 gbt_float4ge(const void *a, const void *b, FmgrInfo *flinfo)
 {
-	return (*((const float4 *) a) >= *((const float4 *) b));
+	return float4_ge(*((const float4 *) a), *((const float4 *) b));
 }
 static bool
 gbt_float4eq(const void *a, const void *b, FmgrInfo *flinfo)
 {
-	return (*((const float4 *) a) == *((const float4 *) b));
+	return float4_eq(*((const float4 *) a), *((const float4 *) b));
 }
 static bool
 gbt_float4le(const void *a, const void *b, FmgrInfo *flinfo)
 {
-	return (*((const float4 *) a) <= *((const float4 *) b));
+	return float4_le(*((const float4 *) a), *((const float4 *) b));
 }
 static bool
 gbt_float4lt(const void *a, const void *b, FmgrInfo *flinfo)
 {
-	return (*((const float4 *) a) < *((const float4 *) b));
+	return float4_lt(*((const float4 *) a), *((const float4 *) b));
 }
 
 static int
@@ -56,22 +62,33 @@ gbt_float4key_cmp(const void *a, const void *b, FmgrInfo *flinfo)
 {
 	float4KEY  *ia = (float4KEY *) (((const Nsrt *) a)->t);
 	float4KEY  *ib = (float4KEY *) (((const Nsrt *) b)->t);
+	int			res;
 
-	if (ia->lower == ib->lower)
-	{
-		if (ia->upper == ib->upper)
-			return 0;
-
-		return (ia->upper > ib->upper) ? 1 : -1;
-	}
-
-	return (ia->lower > ib->lower) ? 1 : -1;
+	res = float4_cmp_internal(ia->lower, ib->lower);
+	if (res != 0)
+		return res;
+	return float4_cmp_internal(ia->upper, ib->upper);
 }
 
 static float8
 gbt_float4_dist(const void *a, const void *b, FmgrInfo *flinfo)
 {
-	return GET_FLOAT_DISTANCE(float4, a, b);
+	float8		arg1 = *(const float4 *) a;
+	float8		arg2 = *(const float4 *) b;
+	float8		r;
+
+	r = arg1 - arg2;
+	/* needn't consider isinf case here, must be due to input infinity */
+	if (unlikely(isnan(r)))
+	{
+		if (isnan(arg1) && isnan(arg2))
+			r = 0.0;			/* treat NaNs as equal */
+		else if (isnan(arg1) || isnan(arg2))
+			r = get_float8_infinity();	/* max dist for NaN vs non-NaN */
+		else
+			r = 0.0;			/* must be Inf - Inf case */
+	}
+	return fabs(r);
 }
 
 
@@ -101,7 +118,15 @@ float4_dist(PG_FUNCTION_ARGS)
 	r = a - b;
 	if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
 		float_overflow_error();
-
+	if (unlikely(isnan(r)))
+	{
+		if (isnan(a) && isnan(b))
+			r = 0.0;			/* treat NaNs as equal */
+		else if (isnan(a) || isnan(b))
+			r = get_float4_infinity();	/* max dist for NaN vs non-NaN */
+		else
+			r = 0.0;			/* must be Inf - Inf case */
+	}
 	PG_RETURN_FLOAT4(fabsf(r));
 }
 
@@ -187,7 +212,7 @@ gbt_float4_penalty(PG_FUNCTION_ARGS)
 	float4KEY  *newentry = (float4KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key);
 	float	   *result = (float *) PG_GETARG_POINTER(2);
 
-	penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper);
+	float_penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper);
 
 	PG_RETURN_POINTER(result);
 }
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index 081a719b0061c535e251a4e51fa71004e5728bac..f740d8ecd8557eb9d0a09121c0f997638363b891 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -26,30 +26,36 @@ PG_FUNCTION_INFO_V1(gbt_float8_penalty);
 PG_FUNCTION_INFO_V1(gbt_float8_same);
 
 
+/*
+ * Use the NaN-aware comparators from utils/float.h, so that our results
+ * will agree with standard btree indexes.  Note that penalty and distance
+ * functions below must also cope with NaNs, in particular with the policy
+ * that all NaNs are equal.
+ */
 static bool
 gbt_float8gt(const void *a, const void *b, FmgrInfo *flinfo)
 {
-	return (*((const float8 *) a) > *((const float8 *) b));
+	return float8_gt(*((const float8 *) a), *((const float8 *) b));
 }
 static bool
 gbt_float8ge(const void *a, const void *b, FmgrInfo *flinfo)
 {
-	return (*((const float8 *) a) >= *((const float8 *) b));
+	return float8_ge(*((const float8 *) a), *((const float8 *) b));
 }
 static bool
 gbt_float8eq(const void *a, const void *b, FmgrInfo *flinfo)
 {
-	return (*((const float8 *) a) == *((const float8 *) b));
+	return float8_eq(*((const float8 *) a), *((const float8 *) b));
 }
 static bool
 gbt_float8le(const void *a, const void *b, FmgrInfo *flinfo)
 {
-	return (*((const float8 *) a) <= *((const float8 *) b));
+	return float8_le(*((const float8 *) a), *((const float8 *) b));
 }
 static bool
 gbt_float8lt(const void *a, const void *b, FmgrInfo *flinfo)
 {
-	return (*((const float8 *) a) < *((const float8 *) b));
+	return float8_lt(*((const float8 *) a), *((const float8 *) b));
 }
 
 static int
@@ -57,16 +63,12 @@ gbt_float8key_cmp(const void *a, const void *b, FmgrInfo *flinfo)
 {
 	float8KEY  *ia = (float8KEY *) (((const Nsrt *) a)->t);
 	float8KEY  *ib = (float8KEY *) (((const Nsrt *) b)->t);
+	int			res;
 
-	if (ia->lower == ib->lower)
-	{
-		if (ia->upper == ib->upper)
-			return 0;
-
-		return (ia->upper > ib->upper) ? 1 : -1;
-	}
-
-	return (ia->lower > ib->lower) ? 1 : -1;
+	res = float8_cmp_internal(ia->lower, ib->lower);
+	if (res != 0)
+		return res;
+	return float8_cmp_internal(ia->upper, ib->upper);
 }
 
 static float8
@@ -79,6 +81,15 @@ gbt_float8_dist(const void *a, const void *b, FmgrInfo *flinfo)
 	r = arg1 - arg2;
 	if (unlikely(isinf(r)) && !isinf(arg1) && !isinf(arg2))
 		float_overflow_error();
+	if (unlikely(isnan(r)))
+	{
+		if (isnan(arg1) && isnan(arg2))
+			r = 0.0;			/* treat NaNs as equal */
+		else if (isnan(arg1) || isnan(arg2))
+			r = get_float8_infinity();	/* max dist for NaN vs non-NaN */
+		else
+			r = 0.0;			/* must be Inf - Inf case */
+	}
 	return fabs(r);
 }
 
@@ -109,7 +120,15 @@ float8_dist(PG_FUNCTION_ARGS)
 	r = a - b;
 	if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
 		float_overflow_error();
-
+	if (unlikely(isnan(r)))
+	{
+		if (isnan(a) && isnan(b))
+			r = 0.0;			/* treat NaNs as equal */
+		else if (isnan(a) || isnan(b))
+			r = get_float8_infinity();	/* max dist for NaN vs non-NaN */
+		else
+			r = 0.0;			/* must be Inf - Inf case */
+	}
 	PG_RETURN_FLOAT8(fabs(r));
 }
 
@@ -194,7 +213,7 @@ gbt_float8_penalty(PG_FUNCTION_ARGS)
 	float8KEY  *newentry = (float8KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key);
 	float	   *result = (float *) PG_GETARG_POINTER(2);
 
-	penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper);
+	float_penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper);
 
 	PG_RETURN_POINTER(result);
 }
diff --git a/contrib/btree_gist/btree_utils_num.c b/contrib/btree_gist/btree_utils_num.c
index 346ee837d75f44e1b4aa00fffb408a53601935e1..50b031d1e7431dbb2a6935dfb69ac884962920fa 100644
--- a/contrib/btree_gist/btree_utils_num.c
+++ b/contrib/btree_gist/btree_utils_num.c
@@ -297,8 +297,19 @@ gbt_num_consistent(const GBT_NUMKEY_R *key,
 			retval = tinfo->f_le(query, key->upper, flinfo);
 			break;
 		case BtreeGistNotEqualStrategyNumber:
-			retval = (!(tinfo->f_eq(query, key->lower, flinfo) &&
-						tinfo->f_eq(query, key->upper, flinfo)));
+			if (is_leaf)
+				retval = !(tinfo->f_eq(query, key->lower, flinfo));
+			else
+			{
+				/*
+				 * If the upper/lower bounds are equal, then all entries below
+				 * this node must have exactly that value.  So we can avoid
+				 * descending if the query equals both bounds.  In all other
+				 * cases, we must descend.
+				 */
+				retval = !(tinfo->f_eq(query, key->lower, flinfo) &&
+						   tinfo->f_eq(query, key->upper, flinfo));
+			}
 			break;
 		default:
 			retval = false;
diff --git a/contrib/btree_gist/btree_utils_num.h b/contrib/btree_gist/btree_utils_num.h
index 11e892311eeb3398a30f7082ec3fdeefd066caf3..6d2d656c8d4da3ec4cea8fd419978c99bf6b637c 100644
--- a/contrib/btree_gist/btree_utils_num.h
+++ b/contrib/btree_gist/btree_utils_num.h
@@ -9,6 +9,7 @@
 
 #include "access/gist.h"
 #include "btree_gist.h"
+#include "utils/float.h"
 #include "utils/rel.h"
 
 typedef char GBT_NUMKEY;
@@ -59,21 +60,124 @@ typedef struct
 
 
 /*
- * Note: The factor 0.49 in following macro avoids floating point overflows
+ * Compute penalty for expanding a range olower..oupper to nlower..nupper.
+ *
+ * Although the arguments are declared double, they must not be NaN nor
+ * large enough to risk overflows in the calculations herein.  We only
+ * actually use this for integral data types, so there's no hazard.
+ */
+static inline float
+penalty_num_impl(double olower, double oupper,
+				 double nlower, double nupper,
+				 int natts)
+{
+	float		result = 0.0F;
+	double		tmp = 0.0;
+
+	/* Add penalty for expanding upper bound */
+	if (nupper > oupper)
+		tmp += nupper - oupper;
+	/* Add penalty for expanding lower bound */
+	if (olower > nlower)
+		tmp += olower - nlower;
+	if (tmp > 0.0)
+	{
+		/* Ensure result is non-zero, even if next step underflows to zero */
+		result += FLT_MIN;
+		/* Scale penalty to 0 .. 1 */
+		result += (float) (tmp / (tmp + (oupper - olower)));
+		/* Scale to 0 .. FLT_MAX / (natts + 1) */
+		result *= FLT_MAX / (natts + 1);
+	}
+	return result;
+}
+
+/*
+ * As above, but the input values are float4 or float8, so we must cope
+ * with NaNs, infinities, and overflows.
+ */
+static inline float
+float_penalty_num_impl(double olower, double oupper,
+					   double nlower, double nupper,
+					   int natts)
+{
+	float		result = 0.0F;
+	double		tmp = 0.0;
+
+	/* Add penalty for expanding upper bound */
+	if (float8_gt(nupper, oupper))
+	{
+		double		delta = nupper - oupper;
+
+		if (unlikely(isnan(delta)))
+		{
+			/* oupper couldn't be NaN here, see float8_gt */
+			if (isnan(nupper))
+				delta = FLT_MAX;	/* max penalty for NaN vs non-NaN */
+			else
+				delta = 0.0;	/* must be Inf - Inf case */
+		}
+		else if (delta > FLT_MAX)
+			delta = FLT_MAX;	/* clamp to FLT_MAX, esp for infinity */
+		tmp += delta;
+	}
+	/* Add penalty for expanding lower bound */
+	if (float8_gt(olower, nlower))
+	{
+		double		delta = olower - nlower;
+
+		if (unlikely(isnan(delta)))
+		{
+			/* nlower couldn't be NaN here, see float8_gt */
+			if (isnan(olower))
+				delta = FLT_MAX;	/* max penalty for NaN vs non-NaN */
+			else
+				delta = 0.0;	/* must be Inf - Inf case */
+		}
+		else if (delta > FLT_MAX)
+			delta = FLT_MAX;	/* clamp to FLT_MAX, esp for infinity */
+		tmp += delta;
+	}
+	if (tmp > 0.0)
+	{
+		double		delta = oupper - olower;
+
+		/* Clamp delta (the original range size) to 0 .. FLT_MAX */
+		if (unlikely(isnan(delta)))
+		{
+			/* here, we must deal with olower possibly being NaN */
+			if (isnan(oupper) && isnan(olower))
+				delta = 0.0;	/* treat NaNs as equal */
+			else if (isnan(oupper) || isnan(olower))
+				delta = FLT_MAX;	/* max penalty for NaN vs non-NaN */
+			else
+				delta = 0.0;	/* must be Inf - Inf case */
+		}
+		else if (delta > FLT_MAX)
+			delta = FLT_MAX;	/* clamp to FLT_MAX, esp for infinity */
+		/* Ensure result is non-zero, even if next step underflows to zero */
+		result += FLT_MIN;
+		/* Scale penalty to 0 .. 1 */
+		result += (float) (tmp / (tmp + delta));
+		/* Scale to 0 .. FLT_MAX / (natts + 1) */
+		result *= FLT_MAX / (natts + 1);
+	}
+	return result;
+}
+
+/*
+ * These macros provide backwards-compatible notation for callers.
  */
 #define penalty_num(result,olower,oupper,nlower,nupper) do { \
-  double	tmp = 0.0F; \
-  (*(result))	= 0.0F; \
-  if ( (nupper) > (oupper) ) \
-	  tmp += ( ((double)nupper)*0.49F - ((double)oupper)*0.49F ); \
-  if (	(olower) > (nlower)  ) \
-	  tmp += ( ((double)olower)*0.49F - ((double)nlower)*0.49F ); \
-  if (tmp > 0.0F) \
-  { \
-	(*(result)) += FLT_MIN; \
-	(*(result)) += (float) ( ((double)(tmp)) / ( (double)(tmp) + ( ((double)(oupper))*0.49F - ((double)(olower))*0.49F ) ) ); \
-	(*(result)) *= (FLT_MAX / (((GISTENTRY *) PG_GETARG_POINTER(0))->rel->rd_att->natts + 1)); \
-  } \
+	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0); \
+	*(result) = penalty_num_impl(olower, oupper, nlower, nupper, \
+								 entry->rel->rd_att->natts); \
+} while (0)
+
+#define float_penalty_num(result,olower,oupper,nlower,nupper) do { \
+	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0); \
+	*(result) = float_penalty_num_impl(olower, oupper, nlower, nupper, \
+									   entry->rel->rd_att->natts); \
 } while (0)
 
 
@@ -87,6 +191,7 @@ typedef struct
 	 (ivp)->day * (24.0 * SECS_PER_HOUR) + \
 	 (ivp)->month * (30.0 * SECS_PER_DAY))
 
+/* This macro is not safe to use with actual float inputs, only integers */
 #define GET_FLOAT_DISTANCE(t, arg1, arg2)	fabs( ((float8) *((const t *) (arg1))) - ((float8) *((const t *) (arg2))) )
 
 
diff --git a/contrib/btree_gist/btree_utils_var.c b/contrib/btree_gist/btree_utils_var.c
index 0c0e952f736214d798bdb929942d948e4a21b449..1806a382306ad43b458bcf28980e6854f4d6c2c9 100644
--- a/contrib/btree_gist/btree_utils_var.c
+++ b/contrib/btree_gist/btree_utils_var.c
@@ -116,36 +116,47 @@ gbt_var_leaf2node(GBT_VARKEY *leaf, const gbtree_vinfo *tinfo, FmgrInfo *flinfo)
 
 /*
  * returns the common prefix length of a node key
+ *
+ * If the underlying type is character data, the prefix length may point in
+ * the middle of a multibyte character.
 */
 static int32
 gbt_var_node_cp_len(const GBT_VARKEY *node, const gbtree_vinfo *tinfo)
 {
 	GBT_VARKEY_R r = gbt_var_key_readable(node);
 	int32		i = 0;
-	int32		l = 0;
+	int32		l_left_to_match = 0;
+	int32		l_total = 0;
 	int32		t1len = VARSIZE(r.lower) - VARHDRSZ;
 	int32		t2len = VARSIZE(r.upper) - VARHDRSZ;
 	int32		ml = Min(t1len, t2len);
 	char	   *p1 = VARDATA(r.lower);
 	char	   *p2 = VARDATA(r.upper);
+	const char *end1 = p1 + t1len;
+	const char *end2 = p2 + t2len;
 
 	if (ml == 0)
 		return 0;
 
 	while (i < ml)
 	{
-		if (tinfo->eml > 1 && l == 0)
+		if (tinfo->eml > 1 && l_left_to_match == 0)
 		{
-			if ((l = pg_mblen(p1)) != pg_mblen(p2))
+			l_total = pg_mblen_range(p1, end1);
+			if (l_total != pg_mblen_range(p2, end2))
 			{
 				return i;
 			}
+			l_left_to_match = l_total;
 		}
 		if (*p1 != *p2)
 		{
 			if (tinfo->eml > 1)
 			{
-				return (i - l + 1);
+				int32		l_matched_subset = l_total - l_left_to_match;
+
+				/* end common prefix at final byte of last matching char */
+				return i - l_matched_subset;
 			}
 			else
 			{
@@ -155,7 +166,7 @@ gbt_var_node_cp_len(const GBT_VARKEY *node, const gbtree_vinfo *tinfo)
 
 		p1++;
 		p2++;
-		l--;
+		l_left_to_match--;
 		i++;
 	}
 	return ml;					/* lower == upper */
@@ -561,6 +572,13 @@ gbt_var_consistent(GBT_VARKEY_R *key,
 {
 	bool		retval = false;
 
+	/*
+	 * Remember that f_cmp is for internal pages, f_eq etc for leaf pages, and
+	 * on internal pages we need to check gbt_var_node_pf_match too.
+	 *
+	 * The leaf-page tests use swapped operands (e.g., f_gt(query, lower)
+	 * means "lower < query"), which is why they look reversed.
+	 */
 	switch (strategy)
 	{
 		case BTLessEqualStrategyNumber:
@@ -601,8 +619,20 @@ gbt_var_consistent(GBT_VARKEY_R *key,
 					|| gbt_var_node_pf_match(key, query, tinfo);
 			break;
 		case BtreeGistNotEqualStrategyNumber:
-			retval = !(tinfo->f_eq(query, key->lower, collation, flinfo) &&
-					   tinfo->f_eq(query, key->upper, collation, flinfo));
+			if (is_leaf)
+				retval = !(tinfo->f_eq(query, key->lower, collation, flinfo));
+			else
+			{
+				/*
+				 * If the upper/lower bounds are equal and not truncated, then
+				 * all entries below this node must have exactly that value.
+				 * So we can avoid descending if the query equals both bounds.
+				 * In all other cases, we must descend.
+				 */
+				retval = tinfo->trnc ||
+					!(tinfo->f_cmp(query, key->lower, collation, flinfo) == 0 &&
+					  tinfo->f_cmp(query, key->upper, collation, flinfo) == 0);
+			}
 			break;
 		default:
 			retval = false;
diff --git a/contrib/btree_gist/data/float4.data b/contrib/btree_gist/data/float4.data
index 947955e468009c4f7bcf66552170a5aa11b77d18..af7d09f00d6443d07d3c6d68396b2b4d96ffd470 100644
--- a/contrib/btree_gist/data/float4.data
+++ b/contrib/btree_gist/data/float4.data
@@ -298,6 +298,9 @@
 \N
 2972.381398
 220.199877
+Infinity
+-Infinity
+NaN
 3542.561032
 -2168.024176
 -3305.714558
diff --git a/contrib/btree_gist/data/float8.data b/contrib/btree_gist/data/float8.data
index ff21226e0665384cb14c9e0cdd292685a0d9c37a..b60e22f957f2e81c1d39e84318c3140a7d0c11e0 100644
--- a/contrib/btree_gist/data/float8.data
+++ b/contrib/btree_gist/data/float8.data
@@ -298,6 +298,9 @@
 27770.539968
 13275.355549
 -4267.695804
+Infinity
+-Infinity
+NaN
 \N
 \N
 38915.525185
diff --git a/contrib/btree_gist/expected/float4.out b/contrib/btree_gist/expected/float4.out
index dfe732049e6d6b7b7b5541ca25047c0b55978657..a917b79a63bc75d938499365137f4104f5c217f2 100644
--- a/contrib/btree_gist/expected/float4.out
+++ b/contrib/btree_gist/expected/float4.out
@@ -5,13 +5,13 @@ SET enable_seqscan=on;
 SELECT count(*) FROM float4tmp WHERE a <  -179.0;
  count 
 -------
-   244
+   245
 (1 row)
 
 SELECT count(*) FROM float4tmp WHERE a <= -179.0;
  count 
 -------
-   245
+   246
 (1 row)
 
 SELECT count(*) FROM float4tmp WHERE a  = -179.0;
@@ -23,13 +23,13 @@ SELECT count(*) FROM float4tmp WHERE a  = -179.0;
 SELECT count(*) FROM float4tmp WHERE a >= -179.0;
  count 
 -------
-   303
+   305
 (1 row)
 
 SELECT count(*) FROM float4tmp WHERE a >  -179.0;
  count 
 -------
-   302
+   304
 (1 row)
 
 SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3;
@@ -45,13 +45,13 @@ SET enable_seqscan=off;
 SELECT count(*) FROM float4tmp WHERE a <  -179.0::float4;
  count 
 -------
-   244
+   245
 (1 row)
 
 SELECT count(*) FROM float4tmp WHERE a <= -179.0::float4;
  count 
 -------
-   245
+   246
 (1 row)
 
 SELECT count(*) FROM float4tmp WHERE a  = -179.0::float4;
@@ -63,13 +63,13 @@ SELECT count(*) FROM float4tmp WHERE a  = -179.0::float4;
 SELECT count(*) FROM float4tmp WHERE a >= -179.0::float4;
  count 
 -------
-   303
+   305
 (1 row)
 
 SELECT count(*) FROM float4tmp WHERE a >  -179.0::float4;
  count 
 -------
-   302
+   304
 (1 row)
 
 EXPLAIN (COSTS OFF)
@@ -89,3 +89,38 @@ SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3;
  -158.17741 | 20.822586
 (3 rows)
 
+-- EXCLUDE constraint must block a duplicate NaN, same as it does for finite
+-- values.
+CREATE TABLE float4excl (a float4, EXCLUDE USING gist (a WITH =));
+INSERT INTO float4excl VALUES ('NaN'::float4);
+INSERT INTO float4excl VALUES ('NaN'::float4);  -- expect: violates EXCLUDE
+ERROR:  conflicting key value violates exclusion constraint "float4excl_a_excl"
+DETAIL:  Key (a)=(NaN) conflicts with existing key (a)=(NaN).
+SELECT count(*) FROM float4excl;
+ count 
+-------
+     1
+(1 row)
+
+-- Test double-column index
+CREATE INDEX float4idx2 ON float4tmp USING gist ( a, abs(a) );
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM float4tmp WHERE abs(a) = 179.0::float4;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on float4tmp
+         Recheck Cond: (abs(a) = '179'::real)
+         ->  Bitmap Index Scan on float4idx2
+               Index Cond: (abs(a) = '179'::real)
+(5 rows)
+
+SELECT count(*) FROM float4tmp WHERE abs(a) = 179.0::float4;
+ count 
+-------
+     1
+(1 row)
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
diff --git a/contrib/btree_gist/expected/float8.out b/contrib/btree_gist/expected/float8.out
index ebd0ef3d689a14a40e6ca991352753065c764eba..194bd210ac60ff1929889cdd2a79a1655f782913 100644
--- a/contrib/btree_gist/expected/float8.out
+++ b/contrib/btree_gist/expected/float8.out
@@ -5,13 +5,13 @@ SET enable_seqscan=on;
 SELECT count(*) FROM float8tmp WHERE a <  -1890.0;
  count 
 -------
-   237
+   238
 (1 row)
 
 SELECT count(*) FROM float8tmp WHERE a <= -1890.0;
  count 
 -------
-   238
+   239
 (1 row)
 
 SELECT count(*) FROM float8tmp WHERE a  = -1890.0;
@@ -23,13 +23,13 @@ SELECT count(*) FROM float8tmp WHERE a  = -1890.0;
 SELECT count(*) FROM float8tmp WHERE a >= -1890.0;
  count 
 -------
-   307
+   309
 (1 row)
 
 SELECT count(*) FROM float8tmp WHERE a >  -1890.0;
  count 
 -------
-   306
+   308
 (1 row)
 
 SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3;
@@ -45,13 +45,13 @@ SET enable_seqscan=off;
 SELECT count(*) FROM float8tmp WHERE a <  -1890.0::float8;
  count 
 -------
-   237
+   238
 (1 row)
 
 SELECT count(*) FROM float8tmp WHERE a <= -1890.0::float8;
  count 
 -------
-   238
+   239
 (1 row)
 
 SELECT count(*) FROM float8tmp WHERE a  = -1890.0::float8;
@@ -63,13 +63,13 @@ SELECT count(*) FROM float8tmp WHERE a  = -1890.0::float8;
 SELECT count(*) FROM float8tmp WHERE a >= -1890.0::float8;
  count 
 -------
-   307
+   309
 (1 row)
 
 SELECT count(*) FROM float8tmp WHERE a >  -1890.0::float8;
  count 
 -------
-   306
+   308
 (1 row)
 
 EXPLAIN (COSTS OFF)
@@ -89,3 +89,38 @@ SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3;
   -1769.73634 | 120.26366000000007
 (3 rows)
 
+-- EXCLUDE constraint must block a duplicate NaN, same as it does for finite
+-- values.
+CREATE TABLE float8excl (a float8, EXCLUDE USING gist (a WITH =));
+INSERT INTO float8excl VALUES ('NaN'::float8);
+INSERT INTO float8excl VALUES ('NaN'::float8);  -- expect: violates EXCLUDE
+ERROR:  conflicting key value violates exclusion constraint "float8excl_a_excl"
+DETAIL:  Key (a)=(NaN) conflicts with existing key (a)=(NaN).
+SELECT count(*) FROM float8excl;
+ count 
+-------
+     1
+(1 row)
+
+-- Test double-column index
+CREATE INDEX float8idx2 ON float8tmp USING gist ( a, abs(a) );
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM float8tmp WHERE abs(a) = 1890.0::float8;
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on float8tmp
+         Recheck Cond: (abs(a) = '1890'::double precision)
+         ->  Bitmap Index Scan on float8idx2
+               Index Cond: (abs(a) = '1890'::double precision)
+(5 rows)
+
+SELECT count(*) FROM float8tmp WHERE abs(a) = 1890.0::float8;
+ count 
+-------
+     1
+(1 row)
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
diff --git a/contrib/btree_gist/expected/numeric.out b/contrib/btree_gist/expected/numeric.out
index ae839b8ec839d2598b1dd03262365ea14861c405..34c1e568063f2e0a560b8975c40b5c6973c61a08 100644
--- a/contrib/btree_gist/expected/numeric.out
+++ b/contrib/btree_gist/expected/numeric.out
@@ -7,13 +7,13 @@ SET enable_seqscan=on;
 SELECT count(*) FROM numerictmp WHERE a <  -1890.0;
  count 
 -------
-   505
+   506
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a <= -1890.0;
  count 
 -------
-   506
+   507
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a  = -1890.0;
@@ -25,37 +25,37 @@ SELECT count(*) FROM numerictmp WHERE a  = -1890.0;
 SELECT count(*) FROM numerictmp WHERE a >= -1890.0;
  count 
 -------
-   597
+   599
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a >  -1890.0;
  count 
 -------
-   596
+   598
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a <  'NaN' ;
  count 
 -------
-  1100
+  1102
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a <= 'NaN' ;
  count 
 -------
-  1102
+  1105
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a  = 'NaN' ;
  count 
 -------
-     2
+     3
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a >= 'NaN' ;
  count 
 -------
-     2
+     3
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a >  'NaN' ;
@@ -67,13 +67,13 @@ SELECT count(*) FROM numerictmp WHERE a >  'NaN' ;
 SELECT count(*) FROM numerictmp WHERE a <  0 ;
  count 
 -------
-   523
+   524
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a <= 0 ;
  count 
 -------
-   526
+   527
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a  = 0 ;
@@ -85,13 +85,13 @@ SELECT count(*) FROM numerictmp WHERE a  = 0 ;
 SELECT count(*) FROM numerictmp WHERE a >= 0 ;
  count 
 -------
-   579
+   581
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a >  0 ;
  count 
 -------
-   576
+   578
 (1 row)
 
 CREATE INDEX numericidx ON numerictmp USING gist ( a );
@@ -99,13 +99,13 @@ SET enable_seqscan=off;
 SELECT count(*) FROM numerictmp WHERE a <  -1890.0;
  count 
 -------
-   505
+   506
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a <= -1890.0;
  count 
 -------
-   506
+   507
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a  = -1890.0;
@@ -117,37 +117,37 @@ SELECT count(*) FROM numerictmp WHERE a  = -1890.0;
 SELECT count(*) FROM numerictmp WHERE a >= -1890.0;
  count 
 -------
-   597
+   599
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a >  -1890.0;
  count 
 -------
-   596
+   598
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a <  'NaN' ;
  count 
 -------
-  1100
+  1102
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a <= 'NaN' ;
  count 
 -------
-  1102
+  1105
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a  = 'NaN' ;
  count 
 -------
-     2
+     3
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a >= 'NaN' ;
  count 
 -------
-     2
+     3
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a >  'NaN' ;
@@ -159,13 +159,13 @@ SELECT count(*) FROM numerictmp WHERE a >  'NaN' ;
 SELECT count(*) FROM numerictmp WHERE a <  0 ;
  count 
 -------
-   523
+   524
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a <= 0 ;
  count 
 -------
-   526
+   527
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a  = 0 ;
@@ -177,13 +177,13 @@ SELECT count(*) FROM numerictmp WHERE a  = 0 ;
 SELECT count(*) FROM numerictmp WHERE a >= 0 ;
  count 
 -------
-   579
+   581
 (1 row)
 
 SELECT count(*) FROM numerictmp WHERE a >  0 ;
  count 
 -------
-   576
+   578
 (1 row)
 
 -- Test index-only scans
diff --git a/contrib/btree_gist/sql/float4.sql b/contrib/btree_gist/sql/float4.sql
index 3da1ce953c8d1471d49236d65e4ab88c447c5460..71de5d5cf49d745b18cef5178214f48f9e510792 100644
--- a/contrib/btree_gist/sql/float4.sql
+++ b/contrib/btree_gist/sql/float4.sql
@@ -35,3 +35,20 @@ SELECT count(*) FROM float4tmp WHERE a >  -179.0::float4;
 EXPLAIN (COSTS OFF)
 SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3;
 SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3;
+
+-- EXCLUDE constraint must block a duplicate NaN, same as it does for finite
+-- values.
+CREATE TABLE float4excl (a float4, EXCLUDE USING gist (a WITH =));
+INSERT INTO float4excl VALUES ('NaN'::float4);
+INSERT INTO float4excl VALUES ('NaN'::float4);  -- expect: violates EXCLUDE
+SELECT count(*) FROM float4excl;
+
+-- Test double-column index
+CREATE INDEX float4idx2 ON float4tmp USING gist ( a, abs(a) );
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM float4tmp WHERE abs(a) = 179.0::float4;
+SELECT count(*) FROM float4tmp WHERE abs(a) = 179.0::float4;
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
diff --git a/contrib/btree_gist/sql/float8.sql b/contrib/btree_gist/sql/float8.sql
index e1e819b37f98c18c50b6eb1eb00752d6ef5fe695..a0fc84f94bb8148248034166fed5a9eb22b0665c 100644
--- a/contrib/btree_gist/sql/float8.sql
+++ b/contrib/btree_gist/sql/float8.sql
@@ -35,3 +35,20 @@ SELECT count(*) FROM float8tmp WHERE a >  -1890.0::float8;
 EXPLAIN (COSTS OFF)
 SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3;
 SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3;
+
+-- EXCLUDE constraint must block a duplicate NaN, same as it does for finite
+-- values.
+CREATE TABLE float8excl (a float8, EXCLUDE USING gist (a WITH =));
+INSERT INTO float8excl VALUES ('NaN'::float8);
+INSERT INTO float8excl VALUES ('NaN'::float8);  -- expect: violates EXCLUDE
+SELECT count(*) FROM float8excl;
+
+-- Test double-column index
+CREATE INDEX float8idx2 ON float8tmp USING gist ( a, abs(a) );
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM float8tmp WHERE abs(a) = 1890.0::float8;
+SELECT count(*) FROM float8tmp WHERE abs(a) = 1890.0::float8;
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 755293456fff687183c969aeb9724db24af13c35..bf5fe7a71da4002f21ac31569900c2e03aa54d1e 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -100,7 +100,7 @@ static PGresult *storeQueryResult(volatile storeInfo *sinfo, PGconn *conn, const
 static void storeRow(volatile storeInfo *sinfo, PGresult *res, bool first);
 static remoteConn *getConnectionByName(const char *name);
 static HTAB *createConnHash(void);
-static void createNewConnection(const char *name, remoteConn *rconn);
+static remoteConn *createNewConnection(const char *name);
 static void deleteConnection(const char *name);
 static char **get_pkey_attnames(Relation rel, int16 *indnkeyatts);
 static char **get_text_array_contents(ArrayType *array, int *numitems);
@@ -114,7 +114,8 @@ static Relation get_rel_from_relname(text *relname_text, LOCKMODE lockmode, AclM
 static char *generate_relation_name(Relation rel);
 static void dblink_connstr_check(const char *connstr);
 static bool dblink_connstr_has_pw(const char *connstr);
-static void dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr);
+static void dblink_security_check(PGconn *conn, const char *connname,
+								  const char *connstr);
 static void dblink_res_error(PGconn *conn, const char *conname, PGresult *res,
 							 bool fail, const char *fmt,...) pg_attribute_printf(5, 6);
 static char *get_connect_string(const char *servername);
@@ -137,16 +138,22 @@ static uint32 dblink_we_get_conn = 0;
 static uint32 dblink_we_get_result = 0;
 
 /*
- *	Following is list that holds multiple remote connections.
+ *	Following is hash that holds multiple remote connections.
  *	Calling convention of each dblink function changes to accept
- *	connection name as the first parameter. The connection list is
+ *	connection name as the first parameter. The connection hash is
  *	much like ecpg e.g. a mapping between a name and a PGconn object.
+ *
+ *	To avoid potentially leaking a PGconn object in case of out-of-memory
+ *	errors, we first create the hash entry, then open the PGconn.
+ *	Hence, a hash entry whose rconn.conn pointer is NULL must be
+ *	understood as a leftover from a failed create; it should be ignored
+ *	by lookup operations, and silently replaced by create operations.
  */
 
 typedef struct remoteConnHashEnt
 {
 	char		name[NAMEDATALEN];
-	remoteConn *rconn;
+	remoteConn	rconn;
 } remoteConnHashEnt;
 
 /* initial number of connection hashes */
@@ -225,7 +232,7 @@ dblink_get_conn(char *conname_or_str,
 					 errmsg("could not establish connection"),
 					 errdetail_internal("%s", msg)));
 		}
-		dblink_security_check(conn, rconn, connstr);
+		dblink_security_check(conn, NULL, connstr);
 		if (PQclientEncoding(conn) != GetDatabaseEncoding())
 			PQsetClientEncoding(conn, GetDatabaseEncodingName());
 		freeconn = true;
@@ -288,15 +295,6 @@ dblink_connect(PG_FUNCTION_ARGS)
 	else if (PG_NARGS() == 1)
 		conname_or_str = text_to_cstring(PG_GETARG_TEXT_PP(0));
 
-	if (connname)
-	{
-		rconn = (remoteConn *) MemoryContextAlloc(TopMemoryContext,
-												  sizeof(remoteConn));
-		rconn->conn = NULL;
-		rconn->openCursorCount = 0;
-		rconn->newXactForCursor = false;
-	}
-
 	/* first check for valid foreign data server */
 	connstr = get_connect_string(conname_or_str);
 	if (connstr == NULL)
@@ -309,6 +307,13 @@ dblink_connect(PG_FUNCTION_ARGS)
 	if (dblink_we_connect == 0)
 		dblink_we_connect = WaitEventExtensionNew("DblinkConnect");
 
+	/* if we need a hashtable entry, make that first, since it might fail */
+	if (connname)
+	{
+		rconn = createNewConnection(connname);
+		Assert(rconn->conn == NULL);
+	}
+
 	/* OK to make connection */
 	conn = libpqsrv_connect(connstr, dblink_we_connect);
 
@@ -316,8 +321,8 @@ dblink_connect(PG_FUNCTION_ARGS)
 	{
 		msg = pchomp(PQerrorMessage(conn));
 		libpqsrv_disconnect(conn);
-		if (rconn)
-			pfree(rconn);
+		if (connname)
+			deleteConnection(connname);
 
 		ereport(ERROR,
 				(errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),
@@ -326,16 +331,16 @@ dblink_connect(PG_FUNCTION_ARGS)
 	}
 
 	/* check password actually used if not superuser */
-	dblink_security_check(conn, rconn, connstr);
+	dblink_security_check(conn, connname, connstr);
 
 	/* attempt to set client encoding to match server encoding, if needed */
 	if (PQclientEncoding(conn) != GetDatabaseEncoding())
 		PQsetClientEncoding(conn, GetDatabaseEncodingName());
 
+	/* all OK, save away the conn */
 	if (connname)
 	{
 		rconn->conn = conn;
-		createNewConnection(connname, rconn);
 	}
 	else
 	{
@@ -375,10 +380,7 @@ dblink_disconnect(PG_FUNCTION_ARGS)
 
 	libpqsrv_disconnect(conn);
 	if (rconn)
-	{
 		deleteConnection(conname);
-		pfree(rconn);
-	}
 	else
 		pconn->conn = NULL;
 
@@ -1296,6 +1298,9 @@ dblink_get_connections(PG_FUNCTION_ARGS)
 		hash_seq_init(&status, remoteConnHash);
 		while ((hentry = (remoteConnHashEnt *) hash_seq_search(&status)) != NULL)
 		{
+			/* ignore it if it's not an open connection */
+			if (hentry->rconn.conn == NULL)
+				continue;
 			/* stash away current value */
 			astate = accumArrayResult(astate,
 									  CStringGetTextDatum(hentry->name),
@@ -2533,8 +2538,8 @@ getConnectionByName(const char *name)
 	hentry = (remoteConnHashEnt *) hash_search(remoteConnHash,
 											   key, HASH_FIND, NULL);
 
-	if (hentry)
-		return hentry->rconn;
+	if (hentry && hentry->rconn.conn != NULL)
+		return &hentry->rconn;
 
 	return NULL;
 }
@@ -2551,8 +2556,8 @@ createConnHash(void)
 					   HASH_ELEM | HASH_STRINGS);
 }
 
-static void
-createNewConnection(const char *name, remoteConn *rconn)
+static remoteConn *
+createNewConnection(const char *name)
 {
 	remoteConnHashEnt *hentry;
 	bool		found;
@@ -2566,17 +2571,15 @@ createNewConnection(const char *name, remoteConn *rconn)
 	hentry = (remoteConnHashEnt *) hash_search(remoteConnHash, key,
 											   HASH_ENTER, &found);
 
-	if (found)
-	{
-		libpqsrv_disconnect(rconn->conn);
-		pfree(rconn);
-
+	if (found && hentry->rconn.conn != NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("duplicate connection name")));
-	}
 
-	hentry->rconn = rconn;
+	/* New, or reusable, so initialize the rconn struct to zeroes */
+	memset(&hentry->rconn, 0, sizeof(remoteConn));
+
+	return &hentry->rconn;
 }
 
 static void
@@ -2604,9 +2607,12 @@ deleteConnection(const char *name)
  * We need to make sure that the connection made used credentials
  * which were provided by the user, so check what credentials were
  * used to connect and then make sure that they came from the user.
+ *
+ * On failure, we close "conn" and also delete the hashtable entry
+ * identified by "connname" (if that's not NULL).
  */
 static void
-dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr)
+dblink_security_check(PGconn *conn, const char *connname, const char *connstr)
 {
 	/* Superuser bypasses security check */
 	if (superuser())
@@ -2624,8 +2630,8 @@ dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr)
 
 	/* Otherwise, fail out */
 	libpqsrv_disconnect(conn);
-	if (rconn)
-		pfree(rconn);
+	if (connname)
+		deleteConnection(connname);
 
 	ereport(ERROR,
 			(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
diff --git a/contrib/dict_xsyn/dict_xsyn.c b/contrib/dict_xsyn/dict_xsyn.c
index 62d2d841417ca092a7767acff9e9106ee3697278..b3e17ebf76350beca84c2114cf3bdb1d8121fba3 100644
--- a/contrib/dict_xsyn/dict_xsyn.c
+++ b/contrib/dict_xsyn/dict_xsyn.c
@@ -48,15 +48,15 @@ find_word(char *in, char **end)
 	char	   *start;
 
 	*end = NULL;
-	while (*in && t_isspace(in))
-		in += pg_mblen(in);
+	while (*in && t_isspace_cstr(in))
+		in += pg_mblen_cstr(in);
 
 	if (!*in || *in == '#')
 		return NULL;
 	start = in;
 
-	while (*in && !t_isspace(in))
-		in += pg_mblen(in);
+	while (*in && !t_isspace_cstr(in))
+		in += pg_mblen_cstr(in);
 
 	*end = in;
 
diff --git a/contrib/file_fdw/expected/file_fdw.out b/contrib/file_fdw/expected/file_fdw.out
index 86c148a86ba3afa8d916c6323c0ba99f10f6c17c..25b79479da4c50f6ebaaed7ef549dc62471cac12 100644
--- a/contrib/file_fdw/expected/file_fdw.out
+++ b/contrib/file_fdw/expected/file_fdw.out
@@ -48,6 +48,10 @@ SET ROLE regress_file_fdw_superuser;
 CREATE USER MAPPING FOR regress_file_fdw_superuser SERVER file_server;
 CREATE USER MAPPING FOR regress_no_priv_user SERVER file_server;
 -- validator tests
+CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (foo 'bar');  -- ERROR
+ERROR:  invalid option "foo"
+CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS ("a=b" 'true');  -- ERROR
+ERROR:  invalid option name "a=b": must not contain "="
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'xml');  -- ERROR
 ERROR:  COPY format "xml" not recognized
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', quote ':');          -- ERROR
@@ -411,6 +415,21 @@ SELECT tableoid::regclass, * FROM p2;
  p2       | 2 | xyzzy
 (3 rows)
 
+-- Test DELETE/UPDATE/MERGE on a partitioned table when all partitions
+-- are excluded and only the dummy root result relation remains. The
+-- operation is a no-op but should not fail regardless of whether the
+-- foreign child was processed (pruning off) or not (pruning on).
+DROP TABLE p2;
+SET enable_partition_pruning TO off;
+DELETE FROM pt WHERE false;
+UPDATE pt SET b = 'x' WHERE false;
+MERGE INTO pt t USING (VALUES (1, 'x'::text)) AS s(a, b)
+  ON false WHEN MATCHED THEN UPDATE SET b = s.b;
+SET enable_partition_pruning TO on;
+DELETE FROM pt WHERE false;
+UPDATE pt SET b = 'x' WHERE false;
+MERGE INTO pt t USING (VALUES (1, 'x'::text)) AS s(a, b)
+  ON false WHEN MATCHED THEN UPDATE SET b = s.b;
 DROP TABLE pt;
 -- generated column tests
 \set filename :abs_srcdir '/data/list1.csv'
diff --git a/contrib/file_fdw/sql/file_fdw.sql b/contrib/file_fdw/sql/file_fdw.sql
index f0548e14e18452789cd83b4fa8f6d88a6b9284a9..c5567ea82ea7da3fd5f90a1daffe6797c46a99c9 100644
--- a/contrib/file_fdw/sql/file_fdw.sql
+++ b/contrib/file_fdw/sql/file_fdw.sql
@@ -55,6 +55,8 @@ CREATE USER MAPPING FOR regress_file_fdw_superuser SERVER file_server;
 CREATE USER MAPPING FOR regress_no_priv_user SERVER file_server;
 
 -- validator tests
+CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (foo 'bar');  -- ERROR
+CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS ("a=b" 'true');  -- ERROR
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'xml');  -- ERROR
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', quote ':');          -- ERROR
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', escape ':');         -- ERROR
@@ -224,6 +226,24 @@ UPDATE pt set a = 1 where a = 2; -- ERROR
 SELECT tableoid::regclass, * FROM pt;
 SELECT tableoid::regclass, * FROM p1;
 SELECT tableoid::regclass, * FROM p2;
+
+-- Test DELETE/UPDATE/MERGE on a partitioned table when all partitions
+-- are excluded and only the dummy root result relation remains. The
+-- operation is a no-op but should not fail regardless of whether the
+-- foreign child was processed (pruning off) or not (pruning on).
+DROP TABLE p2;
+SET enable_partition_pruning TO off;
+DELETE FROM pt WHERE false;
+UPDATE pt SET b = 'x' WHERE false;
+MERGE INTO pt t USING (VALUES (1, 'x'::text)) AS s(a, b)
+  ON false WHEN MATCHED THEN UPDATE SET b = s.b;
+
+SET enable_partition_pruning TO on;
+DELETE FROM pt WHERE false;
+UPDATE pt SET b = 'x' WHERE false;
+MERGE INTO pt t USING (VALUES (1, 'x'::text)) AS s(a, b)
+  ON false WHEN MATCHED THEN UPDATE SET b = s.b;
+
 DROP TABLE pt;
 
 -- generated column tests
diff --git a/contrib/fuzzystrmatch/expected/fuzzystrmatch.out b/contrib/fuzzystrmatch/expected/fuzzystrmatch.out
index 3195e1ec3c8bb6bd9ab9f0864a7585a349a4fdd0..aeab031a2edc4c2dfcb6c902555825fae832b865 100644
--- a/contrib/fuzzystrmatch/expected/fuzzystrmatch.out
+++ b/contrib/fuzzystrmatch/expected/fuzzystrmatch.out
@@ -41,6 +41,14 @@ SELECT levenshtein('GUMBO', 'GAMBOL', 2, 1, 1);
            3
 (1 row)
 
+SELECT levenshtein('GUMBO', 'GAMBOL', 1, 1, 2000000000);
+ levenshtein 
+-------------
+           3
+(1 row)
+
+SELECT levenshtein('GUMBO', 'GAMBOL', 2000000000, 2000000000, 2000000000);
+ERROR:  levenshtein distance out of range
 SELECT levenshtein_less_equal('extensive', 'exhaustive', 2);
  levenshtein_less_equal 
 ------------------------
@@ -53,6 +61,12 @@ SELECT levenshtein_less_equal('extensive', 'exhaustive', 4);
                       4
 (1 row)
 
+SELECT levenshtein_less_equal('aaa', 'aaaaa', 1073741824, 0, 1073741824, 10);
+ levenshtein_less_equal 
+------------------------
+                     11
+(1 row)
+
 SELECT metaphone('GUMBO', 4);
  metaphone 
 -----------
diff --git a/contrib/fuzzystrmatch/sql/fuzzystrmatch.sql b/contrib/fuzzystrmatch/sql/fuzzystrmatch.sql
index 0b4bb9be57ec2b230ca37dc55e33aa35b8495726..c1d55f823d7ce4fa03dfb34b2371f558f7cf0726 100644
--- a/contrib/fuzzystrmatch/sql/fuzzystrmatch.sql
+++ b/contrib/fuzzystrmatch/sql/fuzzystrmatch.sql
@@ -11,8 +11,11 @@ SELECT soundex(''), difference('', '');
 
 SELECT levenshtein('GUMBO', 'GAMBOL');
 SELECT levenshtein('GUMBO', 'GAMBOL', 2, 1, 1);
+SELECT levenshtein('GUMBO', 'GAMBOL', 1, 1, 2000000000);
+SELECT levenshtein('GUMBO', 'GAMBOL', 2000000000, 2000000000, 2000000000);
 SELECT levenshtein_less_equal('extensive', 'exhaustive', 2);
 SELECT levenshtein_less_equal('extensive', 'exhaustive', 4);
+SELECT levenshtein_less_equal('aaa', 'aaaaa', 1073741824, 0, 1073741824, 10);
 
 
 SELECT metaphone('GUMBO', 4);
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index 999ddad76d977a0b4ec7d7f46172f56dd6d952b1..b6157699af143c00a91ad8e44345cf1145d97219 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -64,7 +64,7 @@ prssyntaxerror(HSParser *state)
 	errsave(state->escontext,
 			(errcode(ERRCODE_SYNTAX_ERROR),
 			 errmsg("syntax error in hstore, near \"%.*s\" at position %d",
-					pg_mblen(state->ptr), state->ptr,
+					pg_mblen_cstr(state->ptr), state->ptr,
 					(int) (state->ptr - state->begin))));
 	/* In soft error situation, return false as convenience for caller */
 	return false;
@@ -382,7 +382,8 @@ hstoreUniquePairs(Pairs *a, int32 l, int32 *buflen)
 			if (ptr->needfree)
 			{
 				pfree(ptr->key);
-				pfree(ptr->val);
+				if (ptr->val != NULL)
+					pfree(ptr->val);
 			}
 		}
 		else
diff --git a/contrib/hstore_plperl/hstore_plperl.c b/contrib/hstore_plperl/hstore_plperl.c
index 4a1629cad512e1124bbf848503cce09a2762d6cc..4977724a1bffc0bc29c018dfa8c515d7577be588 100644
--- a/contrib/hstore_plperl/hstore_plperl.c
+++ b/contrib/hstore_plperl/hstore_plperl.c
@@ -2,6 +2,7 @@
 
 #include "fmgr.h"
 #include "hstore/hstore.h"
+#include "miscadmin.h"
 #include "plperl.h"
 
 PG_MODULE_MAGIC;
@@ -107,7 +108,15 @@ plperl_to_hstore(PG_FUNCTION_ARGS)
 
 	/* Dereference references recursively. */
 	while (SvROK(in))
+	{
+		/*
+		 * It's possible for circular references to make this an infinite
+		 * loop.  Checking for such a situation seems like much more trouble
+		 * than it's worth, but let's provide a way to break out of the loop.
+		 */
+		CHECK_FOR_INTERRUPTS();
 		in = SvRV(in);
+	}
 
 	/* Now we must have a hash. */
 	if (SvTYPE(in) != SVt_PVHV)
@@ -116,9 +125,10 @@ plperl_to_hstore(PG_FUNCTION_ARGS)
 				 errmsg("cannot transform non-hash Perl value to hstore")));
 	hv = (HV *) in;
 
-	pcount = hv_iterinit(hv);
+	(void) hv_iterinit(hv);
 
-	pairs = palloc(pcount * sizeof(Pairs));
+	pcount = 64;				/* arbitrary initial guess */
+	pairs = palloc_array(Pairs, pcount);
 
 	i = 0;
 	while ((he = hv_iternext(hv)))
@@ -126,6 +136,12 @@ plperl_to_hstore(PG_FUNCTION_ARGS)
 		char	   *key = sv2cstr(HeSVKEY_force(he));
 		SV		   *value = HeVAL(he);
 
+		if (i >= pcount)
+		{
+			pcount *= 2;
+			pairs = repalloc_array(pairs, Pairs, pcount);
+		}
+
 		pairs[i].key = pstrdup(key);
 		pairs[i].keylen = hstoreCheckKeyLen(strlen(pairs[i].key));
 		pairs[i].needfree = true;
@@ -146,7 +162,7 @@ plperl_to_hstore(PG_FUNCTION_ARGS)
 		i++;
 	}
 
-	pcount = hstoreUniquePairs(pairs, pcount, &buflen);
+	pcount = hstoreUniquePairs(pairs, i, &buflen);
 	out = hstorePairs(pairs, pcount, buflen);
 	PG_RETURN_POINTER(out);
 }
diff --git a/contrib/hstore_plpython/expected/hstore_plpython.out b/contrib/hstore_plpython/expected/hstore_plpython.out
index 5fb56a2f65d10fbbdefdb531c3d8c686e26e913c..5f8315e84dd400f04ac2c42d9bfe31cb50c946db 100644
--- a/contrib/hstore_plpython/expected/hstore_plpython.out
+++ b/contrib/hstore_plpython/expected/hstore_plpython.out
@@ -43,6 +43,71 @@ SELECT test1bad();
 ERROR:  not a Python mapping
 CONTEXT:  while creating return value
 PL/Python function "test1bad"
+-- A mapping whose items() raises should be reported as an error, not crash
+-- the backend
+CREATE FUNCTION test1broken() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+    def items(self):
+        raise ValueError('items failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test1broken();
+ERROR:  could not get items from Python mapping
+CONTEXT:  while creating return value
+PL/Python function "test1broken"
+-- Likewise for a mapping whose items() does not return key/value pairs
+CREATE FUNCTION test1malformed() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+    def items(self):
+        return [42]
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test1malformed();
+ERROR:  items() of a Python mapping must return key/value pairs
+CONTEXT:  while creating return value
+PL/Python function "test1malformed"
+-- Likewise for a mapping whose items() yields fewer pairs than its length
+CREATE FUNCTION test1short() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+    def items(self):
+        return []
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test1short();
+ERROR:  items() of a Python mapping must return key/value pairs
+CONTEXT:  while creating return value
+PL/Python function "test1short"
+-- Likewise for a mapping whose __len__() raises
+CREATE FUNCTION test1brokenlen() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+    def __len__(self):
+        raise ValueError('len failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test1brokenlen();
+ERROR:  could not get size of Python mapping
+CONTEXT:  while creating return value
+PL/Python function "test1brokenlen"
 -- test hstore[] -> python
 CREATE FUNCTION test1arr(val hstore[]) RETURNS int
 LANGUAGE plpython3u
diff --git a/contrib/hstore_plpython/hstore_plpython.c b/contrib/hstore_plpython/hstore_plpython.c
index 310f63c30d48193b3944458c6469cc463c97fff6..eae249dd33535d06787e43604366b0c6b8778920 100644
--- a/contrib/hstore_plpython/hstore_plpython.c
+++ b/contrib/hstore_plpython/hstore_plpython.c
@@ -139,7 +139,16 @@ plpython_to_hstore(PG_FUNCTION_ARGS)
 				 errmsg("not a Python mapping")));
 
 	pcount = PyMapping_Size(dict);
+	if (pcount < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("could not get size of Python mapping")));
+
 	items = PyMapping_Items(dict);
+	if (items == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("could not get items from Python mapping")));
 
 	PG_TRY();
 	{
@@ -147,7 +156,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++)
 		{
@@ -156,6 +165,13 @@ plpython_to_hstore(PG_FUNCTION_ARGS)
 			PyObject   *value;
 
 			tuple = PyList_GetItem(items, i);
+
+			/* The mapping's items() must yield key/value pairs */
+			if (tuple == NULL || !PyTuple_Check(tuple) || PyTuple_Size(tuple) < 2)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("items() of a Python mapping must return key/value pairs")));
+
 			key = PyTuple_GetItem(tuple, 0);
 			value = PyTuple_GetItem(tuple, 1);
 
diff --git a/contrib/hstore_plpython/sql/hstore_plpython.sql b/contrib/hstore_plpython/sql/hstore_plpython.sql
index ebd61e6c467ac1921f0c9dae47777e7989df604f..a2b2046380f1a21fc34a373e8821d8f28ca0c851 100644
--- a/contrib/hstore_plpython/sql/hstore_plpython.sql
+++ b/contrib/hstore_plpython/sql/hstore_plpython.sql
@@ -38,6 +38,71 @@ $$;
 SELECT test1bad();
 
 
+-- A mapping whose items() raises should be reported as an error, not crash
+-- the backend
+CREATE FUNCTION test1broken() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+    def items(self):
+        raise ValueError('items failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test1broken();
+
+
+-- Likewise for a mapping whose items() does not return key/value pairs
+CREATE FUNCTION test1malformed() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+    def items(self):
+        return [42]
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test1malformed();
+
+
+-- Likewise for a mapping whose items() yields fewer pairs than its length
+CREATE FUNCTION test1short() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+    def items(self):
+        return []
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test1short();
+
+
+-- Likewise for a mapping whose __len__() raises
+CREATE FUNCTION test1brokenlen() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+    def __len__(self):
+        raise ValueError('len failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test1brokenlen();
+
+
 -- test hstore[] -> python
 CREATE FUNCTION test1arr(val hstore[]) RETURNS int
 LANGUAGE plpython3u
diff --git a/contrib/intarray/_int_bool.c b/contrib/intarray/_int_bool.c
index 8fc6ad87fc7544b11ab5b10238791e903b9c8781..6dfb2b1305f22e3574eacb70e8352a8fe6996895 100644
--- a/contrib/intarray/_int_bool.c
+++ b/contrib/intarray/_int_bool.c
@@ -435,37 +435,77 @@ boolop(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(result);
 }
 
-static void
-findoprnd(ITEM *ptr, int32 *pos)
+/*
+ * Recursively fill the "left" fields of an ITEM array that represents
+ * a valid postfix tree.
+ *
+ *	state: only needed for error reporting
+ *	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.
+ *
+ * Returns true if okay, false if error (the only possible error is
+ * overflow of a "left" field).
+ */
+static bool
+findoprnd(WORKSTATE *state, 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)--;
-		findoprnd(ptr, pos);
+		/* unary operator, likewise easy: operand is just before it */
+		ptr[mypos].left = -1;
+		/* recurse to scan operand */
+		if (!findoprnd(state, ptr, pos))
+			return false;
 	}
 	else
 	{
-		ITEM	   *curitem = &ptr[*pos];
-		int32		tmp = *pos;
+		/* binary operator */
+		int32		delta;
 
-		(*pos)--;
-		findoprnd(ptr, pos);
-		curitem->left = *pos - tmp;
-		findoprnd(ptr, pos);
+		/* recurse to scan right operand */
+		if (!findoprnd(state, ptr, pos))
+			return false;
+		/* 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))
+			ereturn(state->escontext, false,
+					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					 errmsg("query_int expression is too complex")));
+		ptr[mypos].left = (int16) delta;
+		/* recurse to scan left operand */
+		if (!findoprnd(state, ptr, pos))
+			return false;
 	}
+
+	return true;
 }
 
 
@@ -516,6 +556,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;
@@ -525,8 +566,13 @@ bqarr_in(PG_FUNCTION_ARGS)
 		state.str = tmp;
 	}
 
+	/* now fill the "left" fields */
 	pos = query->size - 1;
-	findoprnd(ptr, &pos);
+	if (!findoprnd(&state, ptr, &pos))
+		PG_RETURN_NULL();
+	/* 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/intarray/_int_selfuncs.c b/contrib/intarray/_int_selfuncs.c
index bc1ad3a80487fffc22b187cd812950ce08efc3ea..beda14d33d1d1241a1f49378a193c9516d21e360 100644
--- a/contrib/intarray/_int_selfuncs.c
+++ b/contrib/intarray/_int_selfuncs.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "commands/extension.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -151,7 +152,10 @@ _int_matchsel(PG_FUNCTION_ARGS)
 	 * query_int.
 	 */
 	if (vardata.vartype != INT4ARRAYOID)
+	{
+		ReleaseVariableStats(vardata);
 		PG_RETURN_FLOAT8(DEFAULT_EQ_SEL);
+	}
 
 	/*
 	 * Can't do anything useful if the something is not a constant, either.
@@ -171,14 +175,25 @@ _int_matchsel(PG_FUNCTION_ARGS)
 		PG_RETURN_FLOAT8(0.0);
 	}
 
-	/* The caller made sure the const is a query, so get it now */
+	/*
+	 * Verify that the Const is a query_int, else return a default estimate.
+	 * (This could only fail if someone attached this estimator to the wrong
+	 * operator.)
+	 */
+	if (((Const *) other)->consttype !=
+		get_function_sibling_type(fcinfo->flinfo->fn_oid, "query_int"))
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(DEFAULT_EQ_SEL);
+	}
+
 	query = DatumGetQueryTypeP(((Const *) other)->constvalue);
 
 	/* Empty query matches nothing */
 	if (query->size == 0)
 	{
 		ReleaseVariableStats(vardata);
-		return (Selectivity) 0.0;
+		PG_RETURN_FLOAT8(0.0);
 	}
 
 	/*
@@ -327,7 +342,12 @@ static int
 compare_val_int4(const void *a, const void *b)
 {
 	int32		key = *(int32 *) a;
-	const Datum *t = (const Datum *) b;
+	int32		value = DatumGetInt32(*(const Datum *) b);
 
-	return key - DatumGetInt32(*t);
+	if (key < value)
+		return -1;
+	else if (key > value)
+		return 1;
+	else
+		return 0;
 }
diff --git a/contrib/intarray/expected/_int.out b/contrib/intarray/expected/_int.out
index d0e68d0447fb73d931fe2c89b301b53440094af2..fb4086a95cafa583f9ecb28ed45c7776b22710f9 100644
--- a/contrib/intarray/expected/_int.out
+++ b/contrib/intarray/expected/_int.out
@@ -398,6 +398,21 @@ SELECT '1&(2&(4&(5|!6)))'::query_int;
  1 & 2 & 4 & ( 5 | !6 )
 (1 row)
 
+-- Test for overflow of the int16 "left" field in findoprnd().
+-- This query uses a balanced binary tree to avoid using too much stack.
+DO $$
+DECLARE
+  e text := '1';
+BEGIN
+  FOR i IN 1..15 LOOP
+    e := '(' || e || '&' || e || ')';
+  END LOOP;
+  PERFORM ('0|' || e)::query_int;
+END;
+$$;
+ERROR:  query_int expression is too complex
+CONTEXT:  SQL statement "SELECT ('0|' || e)::query_int"
+PL/pgSQL function inline_code_block line 8 at PERFORM
 -- test non-error-throwing input
 SELECT str as "query_int",
        pg_input_is_valid(str,'query_int') as ok,
diff --git a/contrib/intarray/sql/_int.sql b/contrib/intarray/sql/_int.sql
index 5668ab40704539b7e5598fe4584d3af6344228e3..0d30914725e18d7c4b7f97625b29c0e62e82a583 100644
--- a/contrib/intarray/sql/_int.sql
+++ b/contrib/intarray/sql/_int.sql
@@ -75,6 +75,19 @@ SELECT '1&2&4&5&6'::query_int;
 SELECT '1&(2&(4&(5|6)))'::query_int;
 SELECT '1&(2&(4&(5|!6)))'::query_int;
 
+-- Test for overflow of the int16 "left" field in findoprnd().
+-- This query uses a balanced binary tree to avoid using too much stack.
+DO $$
+DECLARE
+  e text := '1';
+BEGIN
+  FOR i IN 1..15 LOOP
+    e := '(' || e || '&' || e || ')';
+  END LOOP;
+  PERFORM ('0|' || e)::query_int;
+END;
+$$;
+
 -- test non-error-throwing input
 
 SELECT str as "query_int",
diff --git a/contrib/jsonb_plperl/jsonb_plperl.c b/contrib/jsonb_plperl/jsonb_plperl.c
index 2af1e0c02afd3ee242ab10bb8b1f463883752a23..d523b223383a9e75e5b9417ac379318d13edb3fd 100644
--- a/contrib/jsonb_plperl/jsonb_plperl.c
+++ b/contrib/jsonb_plperl/jsonb_plperl.c
@@ -3,6 +3,7 @@
 #include <math.h>
 
 #include "fmgr.h"
+#include "miscadmin.h"
 #include "plperl.h"
 #include "utils/fmgrprotos.h"
 #include "utils/jsonb.h"
@@ -63,6 +64,9 @@ Jsonb_to_SV(JsonbContainer *jsonb)
 	JsonbIterator *it;
 	JsonbIteratorToken r;
 
+	/* this can recurse via JsonbValue_to_SV() */
+	check_stack_depth();
+
 	it = JsonbIteratorInit(jsonb);
 	r = JsonbIteratorNext(&it, &v, true);
 
@@ -176,9 +180,20 @@ SV_to_JsonbValue(SV *in, JsonbParseState **jsonb_state, bool is_elem)
 	dTHX;
 	JsonbValue	out;			/* result */
 
+	/* this can recurse via AV_to_JsonbValue() or HV_to_JsonbValue() */
+	check_stack_depth();
+
 	/* Dereference references recursively. */
 	while (SvROK(in))
+	{
+		/*
+		 * It's possible for circular references to make this an infinite
+		 * loop.  Checking for such a situation seems like much more trouble
+		 * than it's worth, but let's provide a way to break out of the loop.
+		 */
+		CHECK_FOR_INTERRUPTS();
 		in = SvRV(in);
+	}
 
 	switch (SvTYPE(in))
 	{
diff --git a/contrib/jsonb_plpython/expected/jsonb_plpython.out b/contrib/jsonb_plpython/expected/jsonb_plpython.out
index cac963de69c9f363a675fedc1a82d26574005134..8d3f53288091323d599fc40d2dfe18873a5425c5 100644
--- a/contrib/jsonb_plpython/expected/jsonb_plpython.out
+++ b/contrib/jsonb_plpython/expected/jsonb_plpython.out
@@ -304,3 +304,92 @@ SELECT test_dict1();
  {"": 2, "a": 1, "33": 3}
 (1 row)
 
+-- A custom sequence whose __getitem__ raises should be reported as an error,
+-- not crash the backend
+CREATE FUNCTION test_broken_sequence() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C:
+    def __len__(self):
+        return 2
+    def __getitem__(self, i):
+        raise ValueError('getitem failed')
+return C()
+$$;
+SELECT test_broken_sequence();
+ERROR:  could not get element 0 from sequence
+DETAIL:  ValueError: getitem failed
+CONTEXT:  Traceback (most recent call last):
+while creating return value
+PL/Python function "test_broken_sequence"
+-- A mapping whose items() raises should be reported as an error, not crash
+-- the backend
+CREATE FUNCTION test_broken_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+    def items(self):
+        raise ValueError('items failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test_broken_mapping();
+ERROR:  could not get items from Python mapping
+DETAIL:  ValueError: items failed
+CONTEXT:  Traceback (most recent call last):
+while creating return value
+PL/Python function "test_broken_mapping"
+-- Likewise for a mapping whose items() does not return key/value pairs
+CREATE FUNCTION test_malformed_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+    def items(self):
+        return [42]
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test_malformed_mapping();
+ERROR:  items() of a Python mapping must return key/value pairs
+CONTEXT:  while creating return value
+PL/Python function "test_malformed_mapping"
+-- Likewise for a mapping whose items() yields fewer pairs than its length
+CREATE FUNCTION test_short_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+    def items(self):
+        return []
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test_short_mapping();
+ERROR:  items() of a Python mapping must return key/value pairs
+DETAIL:  IndexError: list index out of range
+CONTEXT:  while creating return value
+PL/Python function "test_short_mapping"
+-- Likewise for a mapping whose __len__() raises
+CREATE FUNCTION test_broken_len_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+    def __len__(self):
+        raise ValueError('len failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test_broken_len_mapping();
+ERROR:  could not get size of Python mapping
+DETAIL:  ValueError: len failed
+CONTEXT:  Traceback (most recent call last):
+while creating return value
+PL/Python function "test_broken_len_mapping"
diff --git a/contrib/jsonb_plpython/jsonb_plpython.c b/contrib/jsonb_plpython/jsonb_plpython.c
index a625727c5e8ca1d857c09e394505f5070987279c..3e79eae7614c36f561c32831b3c2d08f4d4bafd8 100644
--- a/contrib/jsonb_plpython/jsonb_plpython.c
+++ b/contrib/jsonb_plpython/jsonb_plpython.c
@@ -1,5 +1,6 @@
 #include "postgres.h"
 
+#include "miscadmin.h"
 #include "plpy_elog.h"
 #include "plpy_typeio.h"
 #include "plpython.h"
@@ -138,6 +139,9 @@ PLyObject_FromJsonbContainer(JsonbContainer *jsonb)
 	JsonbIterator *it;
 	PyObject   *result;
 
+	/* this can recurse via PLyObject_FromJsonbValue() */
+	check_stack_depth();
+
 	it = JsonbIteratorInit(jsonb);
 	r = JsonbIteratorNext(&it, &v, true);
 
@@ -266,7 +270,12 @@ PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
 	JsonbValue *volatile out;
 
 	pcount = PyMapping_Size(obj);
+	if (pcount < 0)
+		PLy_elog(ERROR, "could not get size of Python mapping");
+
 	items = PyMapping_Items(obj);
+	if (items == NULL)
+		PLy_elog(ERROR, "could not get items from Python mapping");
 
 	PG_TRY();
 	{
@@ -278,8 +287,15 @@ PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
 		{
 			JsonbValue	jbvKey;
 			PyObject   *item = PyList_GetItem(items, i);
-			PyObject   *key = PyTuple_GetItem(item, 0);
-			PyObject   *value = PyTuple_GetItem(item, 1);
+			PyObject   *key;
+			PyObject   *value;
+
+			/* The mapping's items() must yield key/value pairs */
+			if (item == NULL || !PyTuple_Check(item) || PyTuple_Size(item) < 2)
+				PLy_elog(ERROR, "items() of a Python mapping must return key/value pairs");
+
+			key = PyTuple_GetItem(item, 0);
+			value = PyTuple_GetItem(item, 1);
 
 			/* Python dictionary can have None as key */
 			if (key == Py_None)
@@ -331,7 +347,10 @@ PLySequence_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
 		for (i = 0; i < pcount; i++)
 		{
 			value = PySequence_GetItem(obj, i);
-			Assert(value);
+
+			/* PySequence_GetItem() can return NULL, with an exception set */
+			if (value == NULL)
+				PLy_elog(ERROR, "could not get element %d from sequence", (int) i);
 
 			(void) PLyObject_ToJsonbValue(value, jsonb_state, true);
 			Py_XDECREF(value);
@@ -408,6 +427,9 @@ PLyObject_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state, bool is_ele
 {
 	JsonbValue *out;
 
+	/* this can recurse via PLyMapping_ToJsonbValue() */
+	check_stack_depth();
+
 	if (!PyUnicode_Check(obj))
 	{
 		if (PySequence_Check(obj))
diff --git a/contrib/jsonb_plpython/sql/jsonb_plpython.sql b/contrib/jsonb_plpython/sql/jsonb_plpython.sql
index 29dc33279a092f5f866030565ccdcab62e60eac1..fd8485c89c124d20b0ac51a26b0d5b4b1e1737ee 100644
--- a/contrib/jsonb_plpython/sql/jsonb_plpython.sql
+++ b/contrib/jsonb_plpython/sql/jsonb_plpython.sql
@@ -181,3 +181,80 @@ return x
 $$;
 
 SELECT test_dict1();
+
+-- A custom sequence whose __getitem__ raises should be reported as an error,
+-- not crash the backend
+CREATE FUNCTION test_broken_sequence() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C:
+    def __len__(self):
+        return 2
+    def __getitem__(self, i):
+        raise ValueError('getitem failed')
+return C()
+$$;
+
+SELECT test_broken_sequence();
+
+-- A mapping whose items() raises should be reported as an error, not crash
+-- the backend
+CREATE FUNCTION test_broken_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+    def items(self):
+        raise ValueError('items failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test_broken_mapping();
+
+-- Likewise for a mapping whose items() does not return key/value pairs
+CREATE FUNCTION test_malformed_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+    def items(self):
+        return [42]
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test_malformed_mapping();
+
+-- Likewise for a mapping whose items() yields fewer pairs than its length
+CREATE FUNCTION test_short_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+    def items(self):
+        return []
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test_short_mapping();
+
+-- Likewise for a mapping whose __len__() raises
+CREATE FUNCTION test_broken_len_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+    def __len__(self):
+        raise ValueError('len failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test_broken_len_mapping();
diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out
index c8eac3f6b21bcaddeb801cb0821cf235b0e40ae4..d4a89f28ce1456a0eea753ae7e03efede5eb5f45 100644
--- a/contrib/ltree/expected/ltree.out
+++ b/contrib/ltree/expected/ltree.out
@@ -1281,6 +1281,21 @@ SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & aw_rw%*'::ltxtquery;
  f
 (1 row)
 
+-- Test for overflow of the int16 "left" field in findoprnd().
+-- This query uses a balanced binary tree to avoid using too much stack.
+DO $$
+DECLARE
+  e text := 'a';
+BEGIN
+  FOR i IN 1..14 LOOP
+    e := '(' || e || '&' || e || ')';
+  END LOOP;
+  PERFORM ('b|' || e)::ltxtquery;
+END;
+$$;
+ERROR:  ltxtquery is too large
+CONTEXT:  SQL statement "SELECT ('b|' || e)::ltxtquery"
+PL/pgSQL function inline_code_block line 8 at PERFORM
 --arrays
 SELECT '{1.2.3}'::ltree[] @> '1.2.3.4';
  ?column? 
@@ -8200,3 +8215,22 @@ FROM (VALUES ('.2.3', 'ltree'),
  !tree & aWdf@* | ltxtquery | t  |                |                                    |                          | 
 (8 rows)
 
+-- Test for overflow of lquery_level.totallen.
+SELECT (repeat('x', 255) || repeat('|' || repeat('x', 255), 256))::lquery;
+ERROR:  lquery level is too large
+DETAIL:  Total size of level exceeds the maximum allowed (65535 bytes).
+--- Test for overflow of lquery_level.numvar, with a set of single-char
+--- variants in one level.
+SELECT (repeat('a|', 65535) || 'a')::lquery;
+ERROR:  lquery level has too many variants
+DETAIL:  Number of variants exceeds the maximum allowed (65535).
+-- Test that ltree_compare() does not overflow with very deep paths.
+WITH s AS (SELECT 'a'::ltree AS v),
+     l AS (SELECT (repeat('a.', 14999) || 'a')::ltree AS v)
+SELECT (l.v > s.v) AS gt_ok, (l.v < s.v) AS lt_ok, (l.v = s.v) AS eq_ok
+  FROM s, l;
+ gt_ok | lt_ok | eq_ok 
+-------+-------+-------
+ t     | f     | f
+(1 row)
+
diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c
index a6466f575fd7df486ec06c05412e563526a25e88..4dc610f52118dafb229dd627b67874e43faf01f0 100644
--- a/contrib/ltree/lquery_op.c
+++ b/contrib/ltree/lquery_op.c
@@ -27,21 +27,21 @@ getlexeme(char *start, char *end, int *len)
 	char	   *ptr;
 
 	while (start < end && t_iseq(start, '_'))
-		start += pg_mblen(start);
+		start += pg_mblen_range(start, end);
 
 	ptr = start;
 	if (ptr >= end)
 		return NULL;
 
 	while (ptr < end && !t_iseq(ptr, '_'))
-		ptr += pg_mblen(ptr);
+		ptr += pg_mblen_range(ptr, end);
 
 	*len = ptr - start;
 	return start;
 }
 
 bool
-compare_subnode(ltree_level *t, char *qn, int len, int (*cmpptr) (const char *, const char *, size_t), 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;
@@ -56,10 +56,8 @@ compare_subnode(ltree_level *t, char *qn, int len, int (*cmpptr) (const char *,
 		isok = false;
 		while ((tn = getlexeme(tn, endt, &lent)) != NULL)
 		{
-			if ((lent == lenq || (lent > lenq && anyend)) &&
-				(*cmpptr) (qn, tn, lenq) == 0)
+			if (ltree_label_match(qn, lenq, tn, lent, prefix, ci))
 			{
-
 				isok = true;
 				break;
 			}
@@ -74,17 +72,41 @@ compare_subnode(ltree_level *t, char *qn, int len, int (*cmpptr) (const char *,
 	return true;
 }
 
-int
-ltree_strncasecmp(const char *a, const char *b, size_t s)
+/*
+ * 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_label_match(const char *pred, size_t pred_len, const char *label,
+				  size_t label_len, bool prefix, bool ci)
 {
-	char	   *al = str_tolower(a, s, DEFAULT_COLLATION_OID);
-	char	   *bl = str_tolower(b, s, DEFAULT_COLLATION_OID);
-	int			res;
+	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;
 
-	res = strncmp(al, bl, s);
+	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);
 
-	pfree(al);
-	pfree(bl);
+	if ((fpred_len == flabel_len || (prefix && fpred_len < flabel_len)) &&
+		strncmp(fpred, flabel, fpred_len) == 0)
+		res = true;
+	else
+		res = false;
+
+	pfree(fpred);
+	pfree(flabel);
 
 	return res;
 }
@@ -109,19 +131,16 @@ checkLevel(lquery_level *curq, ltree_level *curt)
 
 	for (int i = 0; i < curq->numvar; i++)
 	{
-		int			(*cmpptr) (const char *, const char *, size_t);
-
-		cmpptr = (curvar->flag & LVAR_INCASE) ? ltree_strncasecmp : strncmp;
+		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, cmpptr,
-								(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))) &&
-				 (*cmpptr) (curvar->name, curt->name, curvar->len) == 0)
+		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 5e0761641d32ac5db8e925a6c34d699eb21705ec..89c5b932292c6528a43c5b77282776096e095301 100644
--- a/contrib/ltree/ltree.h
+++ b/contrib/ltree/ltree.h
@@ -127,7 +127,7 @@ typedef struct
 #define LQUERY_HASNOT		0x01
 
 /* valid label chars are alphanumerics, underscores and hyphens */
-#define ISLABEL(x) ( t_isalnum(x) || t_iseq(x, '_') || t_iseq(x, '-') )
+#define ISLABEL(x) ( t_isalnum_cstr(x) || t_iseq(x, '_') || t_iseq(x, '-') )
 
 /* full text query */
 
@@ -206,11 +206,13 @@ bool		ltree_execute(ITEM *curitem, void *checkval,
 						  bool calcnot, bool (*chkcond) (void *checkval, ITEM *val));
 
 int			ltree_compare(const ltree *a, const ltree *b);
+float		ltree_compare_distance(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,
-							int (*cmpptr) (const char *, const char *, size_t), bool anyend);
+bool		compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci);
 ltree	   *lca_inner(ltree **a, int len);
-int			ltree_strncasecmp(const char *a, const char *b, size_t s);
+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_gist.c b/contrib/ltree/ltree_gist.c
index 932f69bff2d180a58239db80023c68ebeec6429f..3e48aa382e2c246386847d056e14c9d0fabb10ae 100644
--- a/contrib/ltree/ltree_gist.c
+++ b/contrib/ltree/ltree_gist.c
@@ -264,11 +264,11 @@ ltree_penalty(PG_FUNCTION_ARGS)
 	ltree_gist *newval = (ltree_gist *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key);
 	float	   *penalty = (float *) PG_GETARG_POINTER(2);
 	int			siglen = LTREE_GET_SIGLEN();
-	int32		cmpr,
+	float		cmpr,
 				cmpl;
 
-	cmpl = ltree_compare(LTG_GETLNODE(origval, siglen), LTG_GETLNODE(newval, siglen));
-	cmpr = ltree_compare(LTG_GETRNODE(newval, siglen), LTG_GETRNODE(origval, siglen));
+	cmpl = ltree_compare_distance(LTG_GETLNODE(origval, siglen), LTG_GETLNODE(newval, siglen));
+	cmpr = ltree_compare_distance(LTG_GETRNODE(newval, siglen), LTG_GETRNODE(origval, siglen));
 
 	*penalty = Max(cmpl, 0) + Max(cmpr, 0);
 
diff --git a/contrib/ltree/ltree_io.c b/contrib/ltree/ltree_io.c
index 0a12c77a621f8e44f5a28310f70fc145a2c01b3f..24d2bf67def897c2ec8f4a147f90f29eddd33631 100644
--- a/contrib/ltree/ltree_io.c
+++ b/contrib/ltree/ltree_io.c
@@ -7,6 +7,7 @@
 
 #include <ctype.h>
 
+#include "common/int.h"
 #include "crc32.h"
 #include "libpq/pqformat.h"
 #include "ltree.h"
@@ -55,7 +56,7 @@ parse_ltree(const char *buf, struct Node *escontext)
 	ptr = buf;
 	while (*ptr)
 	{
-		charlen = pg_mblen(ptr);
+		charlen = pg_mblen_cstr(ptr);
 		if (t_iseq(ptr, '.'))
 			num++;
 		ptr += charlen;
@@ -70,7 +71,7 @@ parse_ltree(const char *buf, struct Node *escontext)
 	ptr = buf;
 	while (*ptr)
 	{
-		charlen = pg_mblen(ptr);
+		charlen = pg_mblen_cstr(ptr);
 
 		switch (state)
 		{
@@ -292,7 +293,7 @@ parse_lquery(const char *buf, struct Node *escontext)
 	ptr = buf;
 	while (*ptr)
 	{
-		charlen = pg_mblen(ptr);
+		charlen = pg_mblen_cstr(ptr);
 
 		if (t_iseq(ptr, '.'))
 			num++;
@@ -312,7 +313,7 @@ parse_lquery(const char *buf, struct Node *escontext)
 	ptr = buf;
 	while (*ptr)
 	{
-		charlen = pg_mblen(ptr);
+		charlen = pg_mblen_cstr(ptr);
 
 		switch (state)
 		{
@@ -345,7 +346,12 @@ parse_lquery(const char *buf, struct Node *escontext)
 					lptr++;
 					lptr->start = ptr;
 					state = LQPRS_WAITDELIM;
-					curqlevel->numvar++;
+					if (pg_add_u16_overflow(curqlevel->numvar, 1, &curqlevel->numvar))
+						ereturn(escontext, NULL,
+								(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;
@@ -412,7 +418,7 @@ parse_lquery(const char *buf, struct Node *escontext)
 			case LQPRS_WAITFNUM:
 				if (t_iseq(ptr, ','))
 					state = LQPRS_WAITSNUM;
-				else if (t_isdigit(ptr))
+				else if (t_isdigit_cstr(ptr))
 				{
 					int			low = atoi(ptr);
 
@@ -430,7 +436,7 @@ parse_lquery(const char *buf, struct Node *escontext)
 					UNCHAR;
 				break;
 			case LQPRS_WAITSNUM:
-				if (t_isdigit(ptr))
+				if (t_isdigit_cstr(ptr))
 				{
 					int			high = atoi(ptr);
 
@@ -461,7 +467,7 @@ parse_lquery(const char *buf, struct Node *escontext)
 			case LQPRS_WAITCLOSE:
 				if (t_iseq(ptr, '}'))
 					state = LQPRS_WAITEND;
-				else if (!t_isdigit(ptr))
+				else if (!t_isdigit_cstr(ptr))
 					UNCHAR;
 				break;
 			case LQPRS_WAITND:
@@ -472,7 +478,7 @@ parse_lquery(const char *buf, struct Node *escontext)
 				}
 				else if (t_iseq(ptr, ','))
 					state = LQPRS_WAITSNUM;
-				else if (!t_isdigit(ptr))
+				else if (!t_isdigit_cstr(ptr))
 					UNCHAR;
 				break;
 			case LQPRS_WAITEND:
@@ -543,7 +549,16 @@ parse_lquery(const char *buf, struct Node *escontext)
 			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)
+					ereturn(escontext, NULL,
+							(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/ltree_op.c b/contrib/ltree/ltree_op.c
index 24a21d3ea0173e03232d6cb03e278f7639c67e95..180114c218b7ccde33cabae531453a646d53ba0e 100644
--- a/contrib/ltree/ltree_op.c
+++ b/contrib/ltree/ltree_op.c
@@ -41,6 +41,9 @@ PG_FUNCTION_INFO_V1(ltree2text);
 PG_FUNCTION_INFO_V1(text2ltree);
 PG_FUNCTION_INFO_V1(ltreeparentsel);
 
+/*
+ * btree-comparison function.
+ */
 int
 ltree_compare(const ltree *a, const ltree *b)
 {
@@ -53,18 +56,52 @@ ltree_compare(const ltree *a, const ltree *b)
 	{
 		int			res;
 
-		if ((res = memcmp(al->name, bl->name, Min(al->len, bl->len))) == 0)
+		res = memcmp(al->name, bl->name, Min(al->len, bl->len));
+		if (res == 0)
+		{
+			if (al->len != bl->len)
+				return (int) al->len - (int) bl->len;
+		}
+		else
+			return res;
+
+		an--;
+		bn--;
+		al = LEVEL_NEXT(al);
+		bl = LEVEL_NEXT(bl);
+	}
+
+	return a->numlevel - b->numlevel;
+}
+
+/*
+ * Returns a "distance" between a and b.  If a < b, the distance is negative,
+ * consistent with the ltree_compare() ordering.
+ */
+float
+ltree_compare_distance(const ltree *a, const ltree *b)
+{
+	ltree_level *al = LTREE_FIRST(a);
+	ltree_level *bl = LTREE_FIRST(b);
+	int			an = a->numlevel;
+	int			bn = b->numlevel;
+
+	while (an > 0 && bn > 0)
+	{
+		int			res;
+
+		res = memcmp(al->name, bl->name, Min(al->len, bl->len));
+		if (res == 0)
 		{
 			if (al->len != bl->len)
-				return (al->len - bl->len) * 10 * (an + 1);
+				return (float) (al->len - bl->len) * 10.0 * (an + 1);
 		}
 		else
 		{
 			if (res < 0)
-				res = -1;
+				return -1.0 * 10.0 * (an + 1);
 			else
-				res = 1;
-			return res * 10 * (an + 1);
+				return 1.0 * 10.0 * (an + 1);
 		}
 
 		an--;
@@ -73,7 +110,7 @@ ltree_compare(const ltree *a, const ltree *b)
 		bl = LEVEL_NEXT(bl);
 	}
 
-	return (a->numlevel - b->numlevel) * 10 * (an + 1);
+	return ((float) (a->numlevel - b->numlevel)) * 10.0 * (an + 1);
 }
 
 #define RUNCMP						\
diff --git a/contrib/ltree/ltxtquery_io.c b/contrib/ltree/ltxtquery_io.c
index 121fc55e469ea2280b9f1c2a75b74ecefe6080c2..0f2954f31ba4f4b70c4546025ecbb466d6d46337 100644
--- a/contrib/ltree/ltxtquery_io.c
+++ b/contrib/ltree/ltxtquery_io.c
@@ -64,7 +64,7 @@ gettoken_query(QPRS_STATE *state, int32 *val, int32 *lenval, char **strval, uint
 
 	for (;;)
 	{
-		charlen = pg_mblen(state->buf);
+		charlen = pg_mblen_cstr(state->buf);
 
 		switch (state->state)
 		{
@@ -88,7 +88,7 @@ gettoken_query(QPRS_STATE *state, int32 *val, int32 *lenval, char **strval, uint
 					*lenval = charlen;
 					*flag = 0;
 				}
-				else if (!t_isspace(state->buf))
+				else if (!t_isspace_cstr(state->buf))
 					ereturn(state->escontext, ERR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
 							 errmsg("operand syntax error")));
@@ -294,33 +294,71 @@ makepol(QPRS_STATE *state)
 	return END;
 }
 
-static void
-findoprnd(ITEM *ptr, int32 *pos)
+/*
+ * Recursively fill the "left" fields of an ITEM array that represents
+ * a valid postfix tree.
+ *
+ *	state: only needed for error reporting
+ *	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.
+ *
+ * Returns true if okay, false if error (the only possible error is
+ * overflow of a "left" field).
+ */
+static bool
+findoprnd(QPRS_STATE *state, 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)++;
-		findoprnd(ptr, pos);
+		/* unary operator, likewise easy: operand is just after it */
+		ptr[mypos].left = 1;
+		/* recurse to scan operand */
+		if (!findoprnd(state, ptr, pos))
+			return false;
 	}
 	else
 	{
-		ITEM	   *curitem = &ptr[*pos];
-		int32		tmp = *pos;
-
-		(*pos)++;
-		findoprnd(ptr, pos);
-		curitem->left = *pos - tmp;
-		findoprnd(ptr, pos);
+		/* binary operator */
+		int32		delta;
+
+		/* recurse to scan right operand */
+		if (!findoprnd(state, ptr, pos))
+			return false;
+		/* 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))
+			ereturn(state->escontext, false,
+					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					 errmsg("ltxtquery is too large")));
+		ptr[mypos].left = (int16) delta;
+		/* recurse to scan left operand */
+		if (!findoprnd(state, ptr, pos))
+			return false;
 	}
+
+	return true;
 }
 
 
@@ -396,7 +434,10 @@ queryin(char *buf, struct Node *escontext)
 
 	/* set left operand's position for every operator */
 	pos = 0;
-	findoprnd(ptr, &pos);
+	if (!findoprnd(&state, ptr, &pos))
+		return NULL;
+	/* 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 002102c9c75b69466d79852448ad488a2905890a..0e6612ff77a428113fed2cf745a93d8e4229b56e 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;
-	int			(*cmpptr) (const char *, const char *, size_t);
+	bool		prefix = (val->flag & LVAR_ANYEND);
+	bool		ci = (val->flag & LVAR_INCASE);
 
-	cmpptr = (val->flag & LVAR_INCASE) ? ltree_strncasecmp : strncmp;
 	while (tlen > 0)
 	{
 		if (val->flag & LVAR_SUBLEXEME)
 		{
-			if (compare_subnode(level, op, val->length, cmpptr, (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))) &&
-				 (*cmpptr) (op, level->name, val->length) == 0)
+		else if (ltree_label_match(op, val->length, level->name, level->len,
+								   prefix, ci))
 			return true;
 
 		tlen--;
diff --git a/contrib/ltree/sql/ltree.sql b/contrib/ltree/sql/ltree.sql
index dd705d9d7ca00136dd6b291c1204e41e2c3e2264..bad5647fb42adc8b4e1e01440bf42a9cbea612eb 100644
--- a/contrib/ltree/sql/ltree.sql
+++ b/contrib/ltree/sql/ltree.sql
@@ -252,6 +252,19 @@ SELECT 'tree.awdfg'::ltree @ 'tree & aWdfg@'::ltxtquery;
 SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & aw_qw%*'::ltxtquery;
 SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & aw_rw%*'::ltxtquery;
 
+-- Test for overflow of the int16 "left" field in findoprnd().
+-- This query uses a balanced binary tree to avoid using too much stack.
+DO $$
+DECLARE
+  e text := 'a';
+BEGIN
+  FOR i IN 1..14 LOOP
+    e := '(' || e || '&' || e || ')';
+  END LOOP;
+  PERFORM ('b|' || e)::ltxtquery;
+END;
+$$;
+
 --arrays
 
 SELECT '{1.2.3}'::ltree[] @> '1.2.3.4';
@@ -456,3 +469,16 @@ FROM (VALUES ('.2.3', 'ltree'),
              ('!tree & aWdf@*','ltxtquery'))
       AS a(str,typ),
      LATERAL pg_input_error_info(a.str, a.typ) as errinfo;
+
+-- Test for overflow of lquery_level.totallen.
+SELECT (repeat('x', 255) || repeat('|' || repeat('x', 255), 256))::lquery;
+
+--- Test for overflow of lquery_level.numvar, with a set of single-char
+--- variants in one level.
+SELECT (repeat('a|', 65535) || 'a')::lquery;
+
+-- Test that ltree_compare() does not overflow with very deep paths.
+WITH s AS (SELECT 'a'::ltree AS v),
+     l AS (SELECT (repeat('a.', 14999) || 'a')::ltree AS v)
+SELECT (l.v > s.v) AS gt_ok, (l.v < s.v) AS lt_ok, (l.v = s.v) AS eq_ok
+  FROM s, l;
diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c
index 9fc5f815fda9dad681d9aa4326e8c6e49157f1cb..34e374dcfd96344746ecdf92f377aae9a891de5b 100644
--- a/contrib/pageinspect/heapfuncs.c
+++ b/contrib/pageinspect/heapfuncs.c
@@ -101,7 +101,7 @@ text_to_bits(char *str, int len)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
 					 errmsg("invalid character \"%.*s\" in t_bits string",
-							pg_mblen(str + off), str + off)));
+							pg_mblen_cstr(str + off), str + off)));
 
 		if (off % 8 == 7)
 			bits[off / 8] = byte;
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 3ae0a018e103cb99fefe5053ea15fc845d4c9747..54bcd5799a338f8794e8a701494dfc59f05add1a 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -153,6 +153,8 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
 			BufferDesc *bufHdr;
 			uint32		buf_state;
 
+			CHECK_FOR_INTERRUPTS();
+
 			bufHdr = GetBufferDescriptor(i);
 			/* Lock each buffer header before inspecting. */
 			buf_state = LockBufHdr(bufHdr);
@@ -266,6 +268,8 @@ pg_buffercache_summary(PG_FUNCTION_ARGS)
 		BufferDesc *bufHdr;
 		uint32		buf_state;
 
+		CHECK_FOR_INTERRUPTS();
+
 		/*
 		 * This function summarizes the state of all headers. Locking the
 		 * buffer headers wouldn't provide an improved result as the state of
@@ -326,6 +330,8 @@ pg_buffercache_usage_counts(PG_FUNCTION_ARGS)
 		uint32		buf_state = pg_atomic_read_u32(&bufHdr->state);
 		int			usage_count;
 
+		CHECK_FOR_INTERRUPTS();
+
 		usage_count = BUF_STATE_GET_USAGECOUNT(buf_state);
 		usage_counts[usage_count]++;
 
diff --git a/contrib/pg_prewarm/Makefile b/contrib/pg_prewarm/Makefile
index 9cfde8c4e4fad0761d6c377e1294e2ffa7cbe2a2..617ac8e09b2d8a5193fa8452cfe98e3f1bd3a707 100644
--- a/contrib/pg_prewarm/Makefile
+++ b/contrib/pg_prewarm/Makefile
@@ -10,6 +10,8 @@ EXTENSION = pg_prewarm
 DATA = pg_prewarm--1.1--1.2.sql pg_prewarm--1.1.sql pg_prewarm--1.0--1.1.sql
 PGFILEDESC = "pg_prewarm - preload relation data into system buffer cache"
 
+REGRESS = pg_prewarm
+
 TAP_TESTS = 1
 
 ifdef USE_PGXS
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 5f0403726e72f971d74d2a3f3b5bad3b7f7b8cf1..321a2ba91d497d67d813f3e21dd246abd2348429 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -632,8 +632,15 @@ apw_dump_now(bool is_bgworker, bool dump_unlogged)
 		return 0;
 	}
 
-	block_info_array =
-		(BlockInfoRecord *) palloc(sizeof(BlockInfoRecord) * NBuffers);
+	/*
+	 * With sufficiently large shared_buffers, allocation will exceed 1GB, so
+	 * allow for a huge allocation to prevent outright failure.
+	 *
+	 * (In the future, it might be a good idea to redesign this to use a more
+	 * memory-efficient data structure.)
+	 */
+	block_info_array = (BlockInfoRecord *)
+		palloc_extended((sizeof(BlockInfoRecord) * NBuffers), MCXT_ALLOC_HUGE);
 
 	for (num_blocks = 0, i = 0; i < NBuffers; i++)
 	{
diff --git a/contrib/pg_prewarm/expected/pg_prewarm.out b/contrib/pg_prewarm/expected/pg_prewarm.out
new file mode 100644
index 0000000000000000000000000000000000000000..94e4fa1a9d2372e71badd72b8d6f6e034981c432
--- /dev/null
+++ b/contrib/pg_prewarm/expected/pg_prewarm.out
@@ -0,0 +1,10 @@
+-- Test pg_prewarm extension
+CREATE EXTENSION pg_prewarm;
+-- pg_prewarm() should fail if the target relation has no storage.
+CREATE TABLE test (c1 int) PARTITION BY RANGE (c1);
+SELECT pg_prewarm('test', 'buffer');
+ERROR:  relation "test" does not have storage
+DETAIL:  This operation is not supported for partitioned tables.
+-- Cleanup
+DROP TABLE test;
+DROP EXTENSION pg_prewarm;
diff --git a/contrib/pg_prewarm/meson.build b/contrib/pg_prewarm/meson.build
index da58f70a9f82c05051c31ea88f660d4b2e0cdbea..6880d6d0a219f0a54c60068b592b2bc89822613d 100644
--- a/contrib/pg_prewarm/meson.build
+++ b/contrib/pg_prewarm/meson.build
@@ -29,6 +29,11 @@ tests += {
   'name': 'pg_prewarm',
   'sd': meson.current_source_dir(),
   'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'pg_prewarm',
+    ],
+  },
   'tap': {
     'tests': [
       't/001_basic.pl',
diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c
index 5c859e983c5cac6266e84bd85521ca33bec014bd..b971cb8f917a2a8e56d0f1a0287de250d1d4e039 100644
--- a/contrib/pg_prewarm/pg_prewarm.c
+++ b/contrib/pg_prewarm/pg_prewarm.c
@@ -16,9 +16,11 @@
 #include <unistd.h>
 
 #include "access/relation.h"
+#include "catalog/index.h"
 #include "fmgr.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
+#include "storage/lmgr.h"
 #include "storage/read_stream.h"
 #include "storage/smgr.h"
 #include "utils/acl.h"
@@ -87,6 +89,8 @@ pg_prewarm(PG_FUNCTION_ARGS)
 	char	   *ttype;
 	PrewarmType ptype;
 	AclResult	aclresult;
+	char		relkind;
+	Oid			privOid;
 
 	/* Basic sanity checking. */
 	if (PG_ARGISNULL(0))
@@ -122,12 +126,54 @@ pg_prewarm(PG_FUNCTION_ARGS)
 	forkString = text_to_cstring(forkName);
 	forkNumber = forkname_to_number(forkString);
 
-	/* Open relation and check privileges. */
+	/*
+	 * Open relation and check privileges.  If the relation is an index, we
+	 * must check the privileges on its parent table instead.
+	 */
+	relkind = get_rel_relkind(relOid);
+	if (relkind == RELKIND_INDEX ||
+		relkind == RELKIND_PARTITIONED_INDEX)
+	{
+		privOid = IndexGetRelation(relOid, true);
+
+		/* Lock table before index to avoid deadlock. */
+		if (OidIsValid(privOid))
+			LockRelationOid(privOid, AccessShareLock);
+	}
+	else
+		privOid = relOid;
+
 	rel = relation_open(relOid, AccessShareLock);
-	aclresult = pg_class_aclcheck(relOid, GetUserId(), ACL_SELECT);
+
+	/*
+	 * It's possible that the relation with OID "privOid" was dropped and the
+	 * OID was reused before we locked it.  If that happens, we could be left
+	 * with the wrong parent table OID, in which case we must ERROR.  It's
+	 * possible that such a race would change the outcome of
+	 * get_rel_relkind(), too, but the worst case scenario there is that we'll
+	 * check privileges on the index instead of its parent table, which isn't
+	 * too terrible.
+	 */
+	if (!OidIsValid(privOid) ||
+		(privOid != relOid &&
+		 privOid != IndexGetRelation(relOid, true)))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_TABLE),
+				 errmsg("could not find parent table of index \"%s\"",
+						RelationGetRelationName(rel))));
+
+	aclresult = pg_class_aclcheck(privOid, GetUserId(), ACL_SELECT);
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), get_rel_name(relOid));
 
+	/* Check that the relation has storage. */
+	if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("relation \"%s\" does not have storage",
+						RelationGetRelationName(rel)),
+				 errdetail_relkind_not_supported(rel->rd_rel->relkind)));
+
 	/* Check that the fork exists. */
 	if (!smgrexists(RelationGetSmgr(rel), forkNumber))
 		ereport(ERROR,
@@ -235,8 +281,11 @@ pg_prewarm(PG_FUNCTION_ARGS)
 		read_stream_end(stream);
 	}
 
-	/* Close relation, release lock. */
+	/* Close relation, release locks. */
 	relation_close(rel, AccessShareLock);
 
+	if (privOid != relOid)
+		UnlockRelationOid(privOid, AccessShareLock);
+
 	PG_RETURN_INT64(blocks_done);
 }
diff --git a/contrib/pg_prewarm/sql/pg_prewarm.sql b/contrib/pg_prewarm/sql/pg_prewarm.sql
new file mode 100644
index 0000000000000000000000000000000000000000..c76f2c7916436365de142e42512ea2e9f33fcb0e
--- /dev/null
+++ b/contrib/pg_prewarm/sql/pg_prewarm.sql
@@ -0,0 +1,10 @@
+-- Test pg_prewarm extension
+CREATE EXTENSION pg_prewarm;
+
+-- pg_prewarm() should fail if the target relation has no storage.
+CREATE TABLE test (c1 int) PARTITION BY RANGE (c1);
+SELECT pg_prewarm('test', 'buffer');
+
+-- Cleanup
+DROP TABLE test;
+DROP EXTENSION pg_prewarm;
diff --git a/contrib/pg_prewarm/t/001_basic.pl b/contrib/pg_prewarm/t/001_basic.pl
index 825d3448ee5622939718d3fe2e0ba393044754f7..939dd215e56c817097df0b9386141239ac67bc6b 100644
--- a/contrib/pg_prewarm/t/001_basic.pl
+++ b/contrib/pg_prewarm/t/001_basic.pl
@@ -11,7 +11,7 @@ use Test::More;
 
 my $node = PostgreSQL::Test::Cluster->new('main');
 
-$node->init;
+$node->init('auth_extra' => [ '--create-role', 'test_user' ]);
 $node->append_conf(
 	'postgresql.conf',
 	qq{shared_preload_libraries = 'pg_prewarm'
@@ -23,7 +23,9 @@ $node->start;
 $node->safe_psql("postgres",
 		"CREATE EXTENSION pg_prewarm;\n"
 	  . "CREATE TABLE test(c1 int);\n"
-	  . "INSERT INTO test SELECT generate_series(1, 100);");
+	  . "INSERT INTO test SELECT generate_series(1, 100);\n"
+	  . "CREATE INDEX test_idx ON test(c1);\n"
+	  . "CREATE ROLE test_user LOGIN;");
 
 # test read mode
 my $result =
@@ -42,6 +44,31 @@ ok( (        $stdout =~ qr/^[1-9][0-9]*$/
 		  or $stderr =~ qr/prefetch is not supported by this build/),
 	'prefetch mode succeeded');
 
+# test_user should be unable to prewarm table/index without privileges
+($cmdret, $stdout, $stderr) =
+  $node->psql(
+    "postgres", "SELECT pg_prewarm('test');",
+    extra_params => [ '--username' => 'test_user' ]);
+ok($stderr =~ /permission denied for table test/, 'pg_prewarm failed as expected');
+($cmdret, $stdout, $stderr) =
+  $node->psql(
+    "postgres", "SELECT pg_prewarm('test_idx');",
+    extra_params => [ '--username' => 'test_user' ]);
+ok($stderr =~ /permission denied for index test_idx/, 'pg_prewarm failed as expected');
+
+# test_user should be able to prewarm table/index with privileges
+$node->safe_psql("postgres", "GRANT SELECT ON test TO test_user;");
+$result =
+  $node->safe_psql(
+    "postgres", "SELECT pg_prewarm('test');",
+    extra_params => [ '--username' => 'test_user' ]);
+like($result, qr/^[1-9][0-9]*$/, 'pg_prewarm succeeded as expected');
+$result =
+  $node->safe_psql(
+    "postgres", "SELECT pg_prewarm('test_idx');",
+    extra_params => [ '--username' => 'test_user' ]);
+like($result, qr/^[1-9][0-9]*$/, 'pg_prewarm succeeded as expected');
+
 # test autoprewarm_dump_now()
 $result = $node->safe_psql("postgres", "SELECT autoprewarm_dump_now();");
 like($result, qr/^[1-9][0-9]*$/, 'autoprewarm_dump_now succeeded');
diff --git a/contrib/pg_stat_statements/expected/extended.out b/contrib/pg_stat_statements/expected/extended.out
index dbc786802266292adeede5be9a978b0053ec4d6e..f60cbc7bb2eb6fef05df8b853c0b4c3ed3639f3c 100644
--- a/contrib/pg_stat_statements/expected/extended.out
+++ b/contrib/pg_stat_statements/expected/extended.out
@@ -8,3 +8,61 @@ SELECT query_id IS NOT NULL AS query_id_set
  t
 (1 row)
 
+-- Various parameter numbering patterns
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
+(1 row)
+
+-- Unique query IDs with parameter numbers switched.
+SELECT WHERE ($1::int, 7) IN ((8, $2::int), ($3::int, 9)) \bind '1' '2' '3' \g
+--
+(0 rows)
+
+SELECT WHERE ($2::int, 10) IN ((11, $3::int), ($1::int, 12)) \bind '1' '2' '3' \g
+--
+(0 rows)
+
+SELECT WHERE $1::int IN ($2::int, $3::int) \bind '1' '2' '3' \g
+--
+(0 rows)
+
+SELECT WHERE $2::int IN ($3::int, $1::int) \bind '1' '2' '3' \g
+--
+(0 rows)
+
+SELECT WHERE $3::int IN ($1::int, $2::int) \bind '1' '2' '3' \g
+--
+(0 rows)
+
+-- Two groups of two queries with the same query ID.
+SELECT WHERE '1'::int IN ($1::int, '2'::int) \bind '1' \g
+--
+(1 row)
+
+SELECT WHERE '4'::int IN ($1::int, '5'::int) \bind '2' \g
+--
+(0 rows)
+
+SELECT WHERE $2::int IN ($1::int, '1'::int) \bind '1' '2' \g
+--
+(0 rows)
+
+SELECT WHERE $2::int IN ($1::int, '2'::int) \bind '3' '4' \g
+--
+(0 rows)
+
+SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
+                            query                             | calls 
+--------------------------------------------------------------+-------
+ SELECT WHERE $1::int IN ($2::int, $3::int)                   |     1
+ SELECT WHERE $2::int IN ($1::int, $3::int)                   |     2
+ SELECT WHERE $2::int IN ($1::int, $3::int)                   |     2
+ SELECT WHERE $2::int IN ($3::int, $1::int)                   |     1
+ SELECT WHERE $3::int IN ($1::int, $2::int)                   |     1
+ SELECT WHERE ($1::int, $4) IN (($5, $2::int), ($3::int, $6)) |     1
+ SELECT WHERE ($2::int, $4) IN (($5, $3::int), ($1::int, $6)) |     1
+ SELECT pg_stat_statements_reset() IS NOT NULL AS t           |     1
+(8 rows)
+
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index dd6c756f67d5b2399e084b67100dc57838444c0f..c031003e62419195243c89e9b292f7b2c6fc295d 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -152,6 +152,35 @@ SELECT pg_stat_statements_reset() IS NOT NULL AS t;
  t
 (1 row)
 
+-- normalization of constants and parameters, with constant locations
+-- recorded one or more times.
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
+(1 row)
+
+SELECT WHERE '1' IN ('1'::int, '3'::int::text);
+--
+(1 row)
+
+SELECT WHERE (1, 2) IN ((1, 2), (2, 3));
+--
+(1 row)
+
+SELECT WHERE (3, 4) IN ((5, 6), (8, 7));
+--
+(0 rows)
+
+SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
+                                 query                                  | calls 
+------------------------------------------------------------------------+-------
+ SELECT WHERE $1 IN ($2::int, $3::int::text)                            |     1
+ SELECT WHERE ($1, $2) IN (($3, $4), ($5, $6))                          |     2
+ SELECT pg_stat_statements_reset() IS NOT NULL AS t                     |     1
+ SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C" |     0
+(4 rows)
+
 --
 -- queries with locking clauses
 --
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 12718dfe45c2dc414b40968916ff3c0bd201da79..2030cb12217b814da3cd90a4f42ac8bc062cf84f 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -791,7 +791,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))
@@ -815,7 +815,8 @@ error:
 			(errcode_for_file_access(),
 			 errmsg("could not write file \"%s\": %m",
 					PGSS_DUMP_FILE ".tmp")));
-	free(qbuffer);
+	if (qbuffer)
+		pfree(qbuffer);
 	if (file)
 		FreeFile(file);
 	unlink(PGSS_DUMP_FILE ".tmp");
@@ -1761,7 +1762,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 			pgss->extent != extent ||
 			pgss->gc_count != gc_count)
 		{
-			free(qbuffer);
+			if (qbuffer)
+				pfree(qbuffer);
 			qbuffer = qtext_load_file(&qbuffer_size);
 		}
 	}
@@ -1962,7 +1964,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 
 	LWLockRelease(pgss->lock);
 
-	free(qbuffer);
+	if (qbuffer)
+		pfree(qbuffer);
 }
 
 /* Number of output arguments (columns) for pg_stat_statements_info */
@@ -2269,7 +2272,7 @@ error:
 }
 
 /*
- * 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.
@@ -2311,7 +2314,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)
@@ -2350,7 +2353,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;
 		}
@@ -2565,7 +2568,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
@@ -2582,7 +2585,8 @@ gc_fail:
 	/* clean up resources */
 	if (qfile)
 		FreeFile(qfile);
-	free(qbuffer);
+	if (qbuffer)
+		pfree(qbuffer);
 
 	/*
 	 * Since the contents of the external file are now uncertain, mark all
@@ -2801,6 +2805,7 @@ generate_normalized_query(JumbleState *jstate, const char *query,
 				n_quer_loc = 0, /* Normalized query byte location */
 				last_off = 0,	/* Offset from start for previous tok */
 				last_tok_len = 0;	/* Length (in bytes) of that tok */
+	int			num_constants_replaced = 0;
 
 	/*
 	 * Get constants' lengths (core system only gives us locations).  Note
@@ -2844,7 +2849,8 @@ generate_normalized_query(JumbleState *jstate, const char *query,
 
 		/* And insert a param symbol in place of the constant token */
 		n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d",
-							  i + 1 + jstate->highest_extern_param_id);
+							  num_constants_replaced + 1 + jstate->highest_extern_param_id);
+		num_constants_replaced++;
 
 		quer_loc = off + tok_len;
 		last_off = off;
diff --git a/contrib/pg_stat_statements/sql/extended.sql b/contrib/pg_stat_statements/sql/extended.sql
index 07b6c5a93d673a3b3de185b23269983fb35ade62..1dd49cf2025038b8eaaaac875103015403b49cd8 100644
--- a/contrib/pg_stat_statements/sql/extended.sql
+++ b/contrib/pg_stat_statements/sql/extended.sql
@@ -5,3 +5,19 @@ SET pg_stat_statements.track_utility = FALSE;
 -- This test checks that an execute message sets a query ID.
 SELECT query_id IS NOT NULL AS query_id_set
   FROM pg_stat_activity WHERE pid = pg_backend_pid() \bind \g
+
+-- Various parameter numbering patterns
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+-- Unique query IDs with parameter numbers switched.
+SELECT WHERE ($1::int, 7) IN ((8, $2::int), ($3::int, 9)) \bind '1' '2' '3' \g
+SELECT WHERE ($2::int, 10) IN ((11, $3::int), ($1::int, 12)) \bind '1' '2' '3' \g
+SELECT WHERE $1::int IN ($2::int, $3::int) \bind '1' '2' '3' \g
+SELECT WHERE $2::int IN ($3::int, $1::int) \bind '1' '2' '3' \g
+SELECT WHERE $3::int IN ($1::int, $2::int) \bind '1' '2' '3' \g
+-- Two groups of two queries with the same query ID.
+SELECT WHERE '1'::int IN ($1::int, '2'::int) \bind '1' \g
+SELECT WHERE '4'::int IN ($1::int, '5'::int) \bind '2' \g
+SELECT WHERE $2::int IN ($1::int, '1'::int) \bind '1' '2' \g
+SELECT WHERE $2::int IN ($1::int, '2'::int) \bind '3' '4' \g
+
+SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index eb45cb81ad23f058e405509ee5f3aaf5977d397f..c419c845796070dbf01ed95b8fbca0bce5f847fd 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -58,6 +58,14 @@ DEALLOCATE pgss_test;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
 SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
+-- normalization of constants and parameters, with constant locations
+-- recorded one or more times.
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+SELECT WHERE '1' IN ('1'::int, '3'::int::text);
+SELECT WHERE (1, 2) IN ((1, 2), (2, 3));
+SELECT WHERE (3, 4) IN ((5, 6), (8, 7));
+SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
+
 --
 -- queries with locking clauses
 --
diff --git a/contrib/pg_surgery/expected/heap_surgery.out b/contrib/pg_surgery/expected/heap_surgery.out
index df7d13b09086f4aa1dac042210cca6b0bde46950..42586137d88cdc9dd34847678caf76f876d9bd09 100644
--- a/contrib/pg_surgery/expected/heap_surgery.out
+++ b/contrib/pg_surgery/expected/heap_surgery.out
@@ -134,6 +134,23 @@ select heap_force_kill('htab2'::regclass, ARRAY['(0, 3)']::tid[]);
  
 (1 row)
 
+-- a tid[] larger than 65535 entries must still finish
+create temp table htab3(a int);
+insert into htab3 values (1);
+select heap_force_kill(
+	'htab3'::regclass,
+	array(select '(0,1)'::tid from generate_series(1, 65536)));
+ heap_force_kill 
+-----------------
+ 
+(1 row)
+
+select count(*) from htab3;
+ count 
+-------
+     0
+(1 row)
+
 -- materialized view.
 -- note that we don't commit the transaction, so autovacuum can't interfere.
 begin;
diff --git a/contrib/pg_surgery/heap_surgery.c b/contrib/pg_surgery/heap_surgery.c
index 37dffe3f7d57ac5211aca901465386fbf3347ad1..038ac7c47aabbbd2dab10c1201a7bb0f0f56ec45 100644
--- a/contrib/pg_surgery/heap_surgery.c
+++ b/contrib/pg_surgery/heap_surgery.c
@@ -17,6 +17,7 @@
 #include "access/xloginsert.h"
 #include "catalog/pg_am_d.h"
 #include "catalog/pg_proc_d.h"
+#include "catalog/pg_control.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
 #include "utils/acl.h"
@@ -40,7 +41,7 @@ static Datum heap_force_common(FunctionCallInfo fcinfo,
 							   HeapTupleForceOption heap_force_opt);
 static void sanity_check_tid_array(ArrayType *ta, int *ntids);
 static BlockNumber find_tids_one_page(ItemPointer tids, int ntids,
-									  OffsetNumber *next_start_ptr);
+									  int *next_start_ptr);
 
 /*-------------------------------------------------------------------------
  * heap_force_kill()
@@ -87,7 +88,7 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 	int			ntids,
 				nblocks;
 	Relation	rel;
-	OffsetNumber curr_start_ptr,
+	int			curr_start_ptr,
 				next_start_ptr;
 	bool		include_this_tid[MaxHeapTuplesPerPage];
 
@@ -143,6 +144,7 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 	{
 		Buffer		buf;
 		Buffer		vmbuf = InvalidBuffer;
+		bool		unlock_vmbuf = false;
 		Page		page;
 		BlockNumber blkno;
 		OffsetNumber curoff;
@@ -225,16 +227,20 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 			}
 
 			/* Mark it for processing. */
-			Assert(offno < MaxHeapTuplesPerPage);
-			include_this_tid[offno] = true;
+			Assert(offno <= MaxHeapTuplesPerPage);
+			include_this_tid[offno - 1] = true;
 		}
 
 		/*
-		 * Before entering the critical section, pin the visibility map page
-		 * if it appears to be necessary.
+		 * Before entering the critical section, pin and lock the visibility
+		 * map page if it appears to be necessary.
 		 */
 		if (heap_force_opt == HEAP_FORCE_KILL && PageIsAllVisible(page))
+		{
 			visibilitymap_pin(rel, blkno, &vmbuf);
+			LockBuffer(vmbuf, BUFFER_LOCK_EXCLUSIVE);
+			unlock_vmbuf = true;
+		}
 
 		/* No ereport(ERROR) from here until all the changes are logged. */
 		START_CRIT_SECTION();
@@ -244,7 +250,7 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 		{
 			ItemId		itemid;
 
-			if (!include_this_tid[curoff])
+			if (!include_this_tid[curoff - 1])
 				continue;
 
 			itemid = PageGetItemId(page, curoff);
@@ -263,10 +269,11 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 				 */
 				if (PageIsAllVisible(page))
 				{
+					if (visibilitymap_clear_locked(rel, blkno, vmbuf,
+												   VISIBILITYMAP_VALID_BITS))
+						did_modify_vm = true;
+
 					PageClearAllVisible(page);
-					visibilitymap_clear(rel, blkno, vmbuf,
-										VISIBILITYMAP_VALID_BITS);
-					did_modify_vm = true;
 				}
 			}
 			else
@@ -317,18 +324,29 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 
 			/* XLOG stuff */
 			if (RelationNeedsWAL(rel))
-				log_newpage_buffer(buf, true);
+			{
+				XLogRecPtr	recptr;
+
+				XLogBeginInsert();
+				XLogRegisterBuffer(0, buf, REGBUF_STANDARD | REGBUF_FORCE_IMAGE);
+				/* Include the VM page if it was modified. */
+				if (did_modify_vm)
+					XLogRegisterBuffer(1, vmbuf, REGBUF_FORCE_IMAGE);
+				recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI);
+				if (did_modify_vm)
+					PageSetLSN(BufferGetPage(vmbuf), recptr);
+				PageSetLSN(BufferGetPage(buf), recptr);
+			}
 		}
 
-		/* WAL log the VM page if it was modified. */
-		if (did_modify_vm && RelationNeedsWAL(rel))
-			log_newpage_buffer(vmbuf, false);
-
 		END_CRIT_SECTION();
 
 		UnlockReleaseBuffer(buf);
 
-		if (vmbuf != InvalidBuffer)
+		if (unlock_vmbuf)
+			LockBuffer(vmbuf, BUFFER_LOCK_UNLOCK);
+
+		if (BufferIsValid(vmbuf))
 			ReleaseBuffer(vmbuf);
 
 		/* Update the current_start_ptr before moving to the next page. */
@@ -392,7 +410,7 @@ sanity_check_tid_array(ArrayType *ta, int *ntids)
  * ------------------------------------------------------------------------
  */
 static BlockNumber
-find_tids_one_page(ItemPointer tids, int ntids, OffsetNumber *next_start_ptr)
+find_tids_one_page(ItemPointer tids, int ntids, int *next_start_ptr)
 {
 	int			i;
 	BlockNumber prev_blkno,
diff --git a/contrib/pg_surgery/sql/heap_surgery.sql b/contrib/pg_surgery/sql/heap_surgery.sql
index 6526b27535de4fb69a5517a6cde95de0b0478d16..c4e933da13aea68c93abbefe40e7e3ed0b4f5d4e 100644
--- a/contrib/pg_surgery/sql/heap_surgery.sql
+++ b/contrib/pg_surgery/sql/heap_surgery.sql
@@ -65,6 +65,14 @@ select heap_force_kill('htab2'::regclass, ARRAY[NULL]::tid[]);
 -- but we should be able to kill the one tuple we have
 select heap_force_kill('htab2'::regclass, ARRAY['(0, 3)']::tid[]);
 
+-- a tid[] larger than 65535 entries must still finish
+create temp table htab3(a int);
+insert into htab3 values (1);
+select heap_force_kill(
+	'htab3'::regclass,
+	array(select '(0,1)'::tid from generate_series(1, 65536)));
+select count(*) from htab3;
+
 -- materialized view.
 -- note that we don't commit the transaction, so autovacuum can't interfere.
 begin;
diff --git a/contrib/pg_trgm/Makefile b/contrib/pg_trgm/Makefile
index 1fbdc9ec1ef43c0dda3b5fca12d14b731c2fa996..c1756993ec7ba1bf5f83d73d633d22d0c97e79a9 100644
--- a/contrib/pg_trgm/Makefile
+++ b/contrib/pg_trgm/Makefile
@@ -14,7 +14,7 @@ DATA = pg_trgm--1.5--1.6.sql pg_trgm--1.4--1.5.sql pg_trgm--1.3--1.4.sql \
 	pg_trgm--1.0--1.1.sql
 PGFILEDESC = "pg_trgm - trigram matching"
 
-REGRESS = pg_trgm pg_word_trgm pg_strict_word_trgm
+REGRESS = pg_trgm pg_utf8_trgm pg_word_trgm pg_strict_word_trgm
 
 ifdef USE_PGXS
 PG_CONFIG = pg_config
diff --git a/contrib/pg_trgm/data/trgm_utf8.data b/contrib/pg_trgm/data/trgm_utf8.data
new file mode 100644
index 0000000000000000000000000000000000000000..713856e76a62590f93e7b9bb440ffdf37640d4d8
--- /dev/null
+++ b/contrib/pg_trgm/data/trgm_utf8.data
@@ -0,0 +1,50 @@
+Mathematics
+数学
+गणित
+Matemáticas
+رياضيات
+Mathématiques
+গণিত
+Matemática
+Математика
+ریاضی
+Matematika
+Mathematik
+数学
+Mathematics
+गणित
+గణితం
+Matematik
+கணிதம்
+數學
+Toán học
+Matematika
+数学
+수학
+ریاضی
+Lissafi
+Hisabati
+Matematika
+Matematica
+ریاضی
+ಗಣಿತ
+ગણિત
+คณิตศาสตร์
+ሂሳብ
+गणित
+ਗਣਿਤ
+數學
+数学
+Iṣiro
+數學
+သင်္ချာ
+Herrega
+رياضي
+गणित
+Математика
+Matematyka
+ഗണിതം
+Matematika
+رياضي
+Matematika
+Matematică
diff --git a/contrib/pg_trgm/expected/pg_trgm.out b/contrib/pg_trgm/expected/pg_trgm.out
index 0b70d9de256240ab34281e4d803ddc4c0f08c15a..04da98170ab15ab0e9dc4c58b1d3f38de4ec7cfb 100644
--- a/contrib/pg_trgm/expected/pg_trgm.out
+++ b/contrib/pg_trgm/expected/pg_trgm.out
@@ -4693,6 +4693,23 @@ select count(*) from test_trgm where t like '%99%' and t like '%qw%';
     19
 (1 row)
 
+explain (costs off)
+select count(*) from test_trgm where t %> '' and t %> '%qwerty%';
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on test_trgm
+         Recheck Cond: ((t %> ''::text) AND (t %> '%qwerty%'::text))
+         ->  Bitmap Index Scan on trgm_idx
+               Index Cond: ((t %> ''::text) AND (t %> '%qwerty%'::text))
+(5 rows)
+
+select count(*) from test_trgm where t %> '' and t %> '%qwerty%';
+ count 
+-------
+     0
+(1 row)
+
 -- ensure that pending-list items are handled correctly, too
 create temp table t_test_trgm(t text COLLATE "C");
 create index t_trgm_idx on t_test_trgm using gin (t gin_trgm_ops);
@@ -4731,6 +4748,23 @@ select count(*) from t_test_trgm where t like '%99%' and t like '%qw%';
      1
 (1 row)
 
+explain (costs off)
+select count(*) from t_test_trgm where t %> '' and t %> '%qwerty%';
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on t_test_trgm
+         Recheck Cond: ((t %> ''::text) AND (t %> '%qwerty%'::text))
+         ->  Bitmap Index Scan on t_trgm_idx
+               Index Cond: ((t %> ''::text) AND (t %> '%qwerty%'::text))
+(5 rows)
+
+select count(*) from t_test_trgm where t %> '' and t %> '%qwerty%';
+ count 
+-------
+     0
+(1 row)
+
 -- run the same queries with sequential scan to check the results
 set enable_bitmapscan=off;
 set enable_seqscan=on;
@@ -4746,6 +4780,12 @@ select count(*) from test_trgm where t like '%99%' and t like '%qw%';
     19
 (1 row)
 
+select count(*) from test_trgm where t %> '' and t %> '%qwerty%';
+ count 
+-------
+     0
+(1 row)
+
 select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%';
  count 
 -------
@@ -4758,6 +4798,12 @@ select count(*) from t_test_trgm where t like '%99%' and t like '%qw%';
      1
 (1 row)
 
+select count(*) from t_test_trgm where t %> '' and t %> '%qwerty%';
+ count 
+-------
+     0
+(1 row)
+
 reset enable_bitmapscan;
 create table test2(t text COLLATE "C");
 insert into test2 values ('abcdef');
diff --git a/contrib/pg_trgm/expected/pg_utf8_trgm.out b/contrib/pg_trgm/expected/pg_utf8_trgm.out
new file mode 100644
index 0000000000000000000000000000000000000000..0768e7d6a83209f9e7c41680534f74bd21ff154a
--- /dev/null
+++ b/contrib/pg_trgm/expected/pg_utf8_trgm.out
@@ -0,0 +1,8 @@
+SELECT getdatabaseencoding() <> 'UTF8' AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+-- Index 50 translations of the word "Mathematics"
+CREATE TEMP TABLE mb (s text);
+\copy mb from 'data/trgm_utf8.data'
+CREATE INDEX ON mb USING gist(s gist_trgm_ops);
diff --git a/contrib/pg_trgm/expected/pg_utf8_trgm_1.out b/contrib/pg_trgm/expected/pg_utf8_trgm_1.out
new file mode 100644
index 0000000000000000000000000000000000000000..8505c4fa552628ca1069be78190ee384863055b6
--- /dev/null
+++ b/contrib/pg_trgm/expected/pg_utf8_trgm_1.out
@@ -0,0 +1,3 @@
+SELECT getdatabaseencoding() <> 'UTF8' AS skip_test \gset
+\if :skip_test
+\quit
diff --git a/contrib/pg_trgm/meson.build b/contrib/pg_trgm/meson.build
index dac92135530cda2c76a6b082e6786db0281d7a84..c45e346bcb5c390f8d2c40e980d5624348e68a02 100644
--- a/contrib/pg_trgm/meson.build
+++ b/contrib/pg_trgm/meson.build
@@ -39,6 +39,7 @@ tests += {
   'regress': {
     'sql': [
       'pg_trgm',
+      'pg_utf8_trgm',
       'pg_word_trgm',
       'pg_strict_word_trgm',
     ],
diff --git a/contrib/pg_trgm/sql/pg_trgm.sql b/contrib/pg_trgm/sql/pg_trgm.sql
index 340c9891899f051f19a709cdf53673dcb4a16546..44debced6d581708d327756781e23b56c3cf03b0 100644
--- a/contrib/pg_trgm/sql/pg_trgm.sql
+++ b/contrib/pg_trgm/sql/pg_trgm.sql
@@ -80,6 +80,9 @@ select count(*) from test_trgm where t like '%99%' and t like '%qwerty%';
 explain (costs off)
 select count(*) from test_trgm where t like '%99%' and t like '%qw%';
 select count(*) from test_trgm where t like '%99%' and t like '%qw%';
+explain (costs off)
+select count(*) from test_trgm where t %> '' and t %> '%qwerty%';
+select count(*) from test_trgm where t %> '' and t %> '%qwerty%';
 -- ensure that pending-list items are handled correctly, too
 create temp table t_test_trgm(t text COLLATE "C");
 create index t_trgm_idx on t_test_trgm using gin (t gin_trgm_ops);
@@ -90,14 +93,19 @@ select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%';
 explain (costs off)
 select count(*) from t_test_trgm where t like '%99%' and t like '%qw%';
 select count(*) from t_test_trgm where t like '%99%' and t like '%qw%';
+explain (costs off)
+select count(*) from t_test_trgm where t %> '' and t %> '%qwerty%';
+select count(*) from t_test_trgm where t %> '' and t %> '%qwerty%';
 
 -- run the same queries with sequential scan to check the results
 set enable_bitmapscan=off;
 set enable_seqscan=on;
 select count(*) from test_trgm where t like '%99%' and t like '%qwerty%';
 select count(*) from test_trgm where t like '%99%' and t like '%qw%';
+select count(*) from test_trgm where t %> '' and t %> '%qwerty%';
 select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%';
 select count(*) from t_test_trgm where t like '%99%' and t like '%qw%';
+select count(*) from t_test_trgm where t %> '' and t %> '%qwerty%';
 reset enable_bitmapscan;
 
 create table test2(t text COLLATE "C");
diff --git a/contrib/pg_trgm/sql/pg_utf8_trgm.sql b/contrib/pg_trgm/sql/pg_utf8_trgm.sql
new file mode 100644
index 0000000000000000000000000000000000000000..0dd962ced83105310d48c6f8fd4d620e6a4e8f13
--- /dev/null
+++ b/contrib/pg_trgm/sql/pg_utf8_trgm.sql
@@ -0,0 +1,9 @@
+SELECT getdatabaseencoding() <> 'UTF8' AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+
+-- Index 50 translations of the word "Mathematics"
+CREATE TEMP TABLE mb (s text);
+\copy mb from 'data/trgm_utf8.data'
+CREATE INDEX ON mb USING gist(s gist_trgm_ops);
diff --git a/contrib/pg_trgm/trgm.h b/contrib/pg_trgm/trgm.h
index afb0adb222b9178fa6868c768a631699f7cfee07..1bd41fa19011cee63c9de3da23c0cd52568d905a 100644
--- a/contrib/pg_trgm/trgm.h
+++ b/contrib/pg_trgm/trgm.h
@@ -52,10 +52,10 @@ typedef char trgm[3];
 } while(0)
 
 #ifdef KEEPONLYALNUM
-#define ISWORDCHR(c)	(t_isalnum(c))
+#define ISWORDCHR(c, len)	(t_isalnum_with_len(c, len))
 #define ISPRINTABLECHAR(a)	( isascii( *(unsigned char*)(a) ) && (isalnum( *(unsigned char*)(a) ) || *(unsigned char*)(a)==' ') )
 #else
-#define ISWORDCHR(c)	(!t_isspace(c))
+#define ISWORDCHR(c, len)	(!t_isspace_with_len(c, len))
 #define ISPRINTABLECHAR(a)	( isascii( *(unsigned char*)(a) ) && isprint( *(unsigned char*)(a) ) )
 #endif
 #define ISPRINTABLETRGM(t)	( ISPRINTABLECHAR( ((char*)(t)) ) && ISPRINTABLECHAR( ((char*)(t))+1 ) && ISPRINTABLECHAR( ((char*)(t))+2 ) )
diff --git a/contrib/pg_trgm/trgm_gist.c b/contrib/pg_trgm/trgm_gist.c
index 9ef2e38560a2b1a339f101f299a5fa12fa791bb2..bd9b9237d9c86796ffdeb36f7cc13f805a8a1153 100644
--- a/contrib/pg_trgm/trgm_gist.c
+++ b/contrib/pg_trgm/trgm_gist.c
@@ -699,10 +699,13 @@ gtrgm_penalty(PG_FUNCTION_ARGS)
 	if (ISARRKEY(newval))
 	{
 		char	   *cache = (char *) fcinfo->flinfo->fn_extra;
-		TRGM	   *cachedVal = (TRGM *) (cache + MAXALIGN(siglen));
+		TRGM	   *cachedVal = NULL;
 		Size		newvalsize = VARSIZE(newval);
 		BITVECP		sign;
 
+		if (cache != NULL)
+			cachedVal = (TRGM *) (cache + MAXALIGN(siglen));
+
 		/*
 		 * Cache the sign data across multiple calls with the same newval.
 		 */
@@ -897,7 +900,7 @@ gtrgm_picksplit(PG_FUNCTION_ARGS)
 			else
 				size_alpha = SIGLENBIT(siglen) -
 					sizebitvec((cache[j].allistrue) ? GETSIGN(datum_l) :
-							   GETSIGN(cache[j].sign),
+							   cache[j].sign,
 							   siglen);
 		}
 		else
@@ -910,7 +913,7 @@ gtrgm_picksplit(PG_FUNCTION_ARGS)
 			else
 				size_beta = SIGLENBIT(siglen) -
 					sizebitvec((cache[j].allistrue) ? GETSIGN(datum_r) :
-							   GETSIGN(cache[j].sign),
+							   cache[j].sign,
 							   siglen);
 		}
 		else
diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c
index c509d15ee402f9941ee4e245032577cda5717db3..9c559f7b07da0f4320b7ec3a799990cb001d9ca2 100644
--- a/contrib/pg_trgm/trgm_op.c
+++ b/contrib/pg_trgm/trgm_op.c
@@ -174,18 +174,29 @@ static char *
 find_word(char *str, int lenstr, char **endword, int *charlen)
 {
 	char	   *beginword = str;
+	const char *endstr = str + lenstr;
 
-	while (beginword - str < lenstr && !ISWORDCHR(beginword))
-		beginword += pg_mblen(beginword);
+	while (beginword < endstr)
+	{
+		int			clen = pg_mblen_range(beginword, endstr);
 
-	if (beginword - str >= lenstr)
+		if (ISWORDCHR(beginword, clen))
+			break;
+		beginword += clen;
+	}
+
+	if (beginword >= endstr)
 		return NULL;
 
 	*endword = beginword;
 	*charlen = 0;
-	while (*endword - str < lenstr && ISWORDCHR(*endword))
+	while (*endword < endstr)
 	{
-		*endword += pg_mblen(*endword);
+		int			clen = pg_mblen_range(*endword, endstr);
+
+		if (!ISWORDCHR(*endword, clen))
+			break;
+		*endword += clen;
 		(*charlen)++;
 	}
 
@@ -233,9 +244,9 @@ make_trigrams(trgm *tptr, char *str, int bytelen, int charlen)
 	if (bytelen > charlen)
 	{
 		/* Find multibyte character boundaries and apply compact_trigram */
-		int			lenfirst = pg_mblen(str),
-					lenmiddle = pg_mblen(str + lenfirst),
-					lenlast = pg_mblen(str + lenfirst + lenmiddle);
+		int			lenfirst = pg_mblen_unbounded(str),
+					lenmiddle = pg_mblen_unbounded(str + lenfirst),
+					lenlast = pg_mblen_unbounded(str + lenfirst + lenmiddle);
 
 		while ((ptr - str) + lenfirst + lenmiddle + lenlast <= bytelen)
 		{
@@ -246,7 +257,7 @@ make_trigrams(trgm *tptr, char *str, int bytelen, int charlen)
 
 			lenfirst = lenmiddle;
 			lenmiddle = lenlast;
-			lenlast = pg_mblen(ptr + lenfirst + lenmiddle);
+			lenlast = pg_mblen_unbounded(ptr + lenfirst + lenmiddle);
 		}
 	}
 	else
@@ -726,6 +737,7 @@ get_wildcard_part(const char *str, int lenstr,
 {
 	const char *beginword = str;
 	const char *endword;
+	const char *endstr = str + lenstr;
 	char	   *s = buf;
 	bool		in_leading_wildcard_meta = false;
 	bool		in_trailing_wildcard_meta = false;
@@ -738,11 +750,13 @@ get_wildcard_part(const char *str, int lenstr,
 	 * from this loop to the next one, since we may exit at a word character
 	 * that is in_escape.
 	 */
-	while (beginword - str < lenstr)
+	while (beginword < endstr)
 	{
+		clen = pg_mblen_range(beginword, endstr);
+
 		if (in_escape)
 		{
-			if (ISWORDCHR(beginword))
+			if (ISWORDCHR(beginword, clen))
 				break;
 			in_escape = false;
 			in_leading_wildcard_meta = false;
@@ -753,12 +767,12 @@ get_wildcard_part(const char *str, int lenstr,
 				in_escape = true;
 			else if (ISWILDCARDCHAR(beginword))
 				in_leading_wildcard_meta = true;
-			else if (ISWORDCHR(beginword))
+			else if (ISWORDCHR(beginword, clen))
 				break;
 			else
 				in_leading_wildcard_meta = false;
 		}
-		beginword += pg_mblen(beginword);
+		beginword += clen;
 	}
 
 	/*
@@ -791,12 +805,12 @@ get_wildcard_part(const char *str, int lenstr,
 	 * string boundary.  Strip escapes during copy.
 	 */
 	endword = beginword;
-	while (endword - str < lenstr)
+	while (endword < endstr)
 	{
-		clen = pg_mblen(endword);
+		clen = pg_mblen_range(endword, endstr);
 		if (in_escape)
 		{
-			if (ISWORDCHR(endword))
+			if (ISWORDCHR(endword, clen))
 			{
 				memcpy(s, endword, clen);
 				(*charlen)++;
@@ -824,7 +838,7 @@ get_wildcard_part(const char *str, int lenstr,
 				in_trailing_wildcard_meta = true;
 				break;
 			}
-			else if (ISWORDCHR(endword))
+			else if (ISWORDCHR(endword, clen))
 			{
 				memcpy(s, endword, clen);
 				(*charlen)++;
diff --git a/contrib/pg_trgm/trgm_regexp.c b/contrib/pg_trgm/trgm_regexp.c
index 75d6d1d4a8dd3bdebdf4de3d0b0934f1d60777fa..4d7461a8497748f5d794fd263808693f300271ad 100644
--- a/contrib/pg_trgm/trgm_regexp.c
+++ b/contrib/pg_trgm/trgm_regexp.c
@@ -481,7 +481,7 @@ static TRGM *createTrgmNFAInternal(regex_t *regex, TrgmPackedGraph **graph,
 static void RE_compile(regex_t *regex, text *text_re,
 					   int cflags, Oid collation);
 static void getColorInfo(regex_t *regex, TrgmNFA *trgmNFA);
-static bool convertPgWchar(pg_wchar c, trgm_mb_char *result);
+static int	convertPgWchar(pg_wchar c, trgm_mb_char *result);
 static void transformGraph(TrgmNFA *trgmNFA);
 static void processState(TrgmNFA *trgmNFA, TrgmState *state);
 static void addKey(TrgmNFA *trgmNFA, TrgmState *state, TrgmStateKey *key);
@@ -806,10 +806,11 @@ getColorInfo(regex_t *regex, TrgmNFA *trgmNFA)
 		for (j = 0; j < charsCount; j++)
 		{
 			trgm_mb_char c;
+			int			clen = convertPgWchar(chars[j], &c);
 
-			if (!convertPgWchar(chars[j], &c))
+			if (!clen)
 				continue;		/* ok to ignore it altogether */
-			if (ISWORDCHR(c.bytes))
+			if (ISWORDCHR(c.bytes, clen))
 				colorInfo->wordChars[colorInfo->wordCharsCount++] = c;
 			else
 				colorInfo->containsNonWord = true;
@@ -821,13 +822,15 @@ getColorInfo(regex_t *regex, TrgmNFA *trgmNFA)
 
 /*
  * Convert pg_wchar to multibyte format.
- * Returns false if the character should be ignored completely.
+ * Returns 0 if the character should be ignored completely, else returns its
+ * byte length.
  */
-static bool
+static int
 convertPgWchar(pg_wchar c, trgm_mb_char *result)
 {
 	/* "s" has enough space for a multibyte character and a trailing NUL */
 	char		s[MAX_MULTIBYTE_CHAR_LEN + 1];
+	int			clen;
 
 	/*
 	 * We can ignore the NUL character, since it can never appear in a PG text
@@ -835,11 +838,11 @@ convertPgWchar(pg_wchar c, trgm_mb_char *result)
 	 * reconstructing trigrams.
 	 */
 	if (c == 0)
-		return false;
+		return 0;
 
 	/* Do the conversion, making sure the result is NUL-terminated */
 	memset(s, 0, sizeof(s));
-	pg_wchar2mb_with_len(&c, s, 1);
+	clen = pg_wchar2mb_with_len(&c, s, 1);
 
 	/*
 	 * In IGNORECASE mode, we can ignore uppercase characters.  We assume that
@@ -861,7 +864,7 @@ convertPgWchar(pg_wchar c, trgm_mb_char *result)
 		if (strcmp(lowerCased, s) != 0)
 		{
 			pfree(lowerCased);
-			return false;
+			return 0;
 		}
 		pfree(lowerCased);
 	}
@@ -869,7 +872,7 @@ convertPgWchar(pg_wchar c, trgm_mb_char *result)
 
 	/* Fill result with exactly MAX_MULTIBYTE_CHAR_LEN bytes */
 	memcpy(result->bytes, s, MAX_MULTIBYTE_CHAR_LEN);
-	return true;
+	return clen;
 }
 
 
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 5efa10c334c8009bd8d40655345ac4eca04f03a8..ff5e0eceab8a88b608b55bbc0fcfc96ddf8162ee 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -43,7 +43,7 @@ REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \
 	sha2 des 3des cast5 \
 	crypt-des crypt-md5 crypt-blowfish crypt-xdes \
 	pgp-armor pgp-decrypt pgp-encrypt pgp-encrypt-md5 $(CF_PGP_TESTS) \
-	pgp-pubkey-decrypt pgp-pubkey-encrypt pgp-info
+	pgp-pubkey-decrypt pgp-pubkey-encrypt pgp-pubkey-session pgp-info
 
 EXTRA_CLEAN = gen-rtab
 
diff --git a/contrib/pgcrypto/expected/hmac-md5_2.out b/contrib/pgcrypto/expected/hmac-md5_2.out
new file mode 100644
index 0000000000000000000000000000000000000000..08cdf95d532450d2d641e8b8eb4303956a0a8260
--- /dev/null
+++ b/contrib/pgcrypto/expected/hmac-md5_2.out
@@ -0,0 +1,44 @@
+--
+-- HMAC-MD5
+--
+SELECT hmac(
+'Hi There',
+'\x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'::bytea,
+'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
+-- 2
+SELECT hmac(
+'Jefe',
+'what do ya want for nothing?',
+'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
+-- 3
+SELECT hmac(
+'\xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'::bytea,
+'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea,
+'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
+-- 4
+SELECT hmac(
+'\xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd'::bytea,
+'\x0102030405060708090a0b0c0d0e0f10111213141516171819'::bytea,
+'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
+-- 5
+SELECT hmac(
+'Test With Truncation',
+'\x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c'::bytea,
+'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
+-- 6
+SELECT hmac(
+'Test Using Larger Than Block-Size Key - Hash Key First',
+'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea,
+'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
+-- 7
+SELECT hmac(
+'Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data',
+'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea,
+'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
diff --git a/contrib/pgcrypto/expected/md5_2.out b/contrib/pgcrypto/expected/md5_2.out
new file mode 100644
index 0000000000000000000000000000000000000000..51bdaa86f32b454ec33b85c9f8d9139968a64fc5
--- /dev/null
+++ b/contrib/pgcrypto/expected/md5_2.out
@@ -0,0 +1,17 @@
+--
+-- MD5 message digest
+--
+SELECT digest('', 'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
+SELECT digest('a', 'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
+SELECT digest('abc', 'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
+SELECT digest('message digest', 'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
+SELECT digest('abcdefghijklmnopqrstuvwxyz', 'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
+SELECT digest('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
+SELECT digest('12345678901234567890123456789012345678901234567890123456789012345678901234567890', 'md5');
+ERROR:  Cannot use "md5": No such hash algorithm
diff --git a/contrib/pgcrypto/expected/pgp-decrypt.out b/contrib/pgcrypto/expected/pgp-decrypt.out
index eb049ba9d444381df4f2b093fc99ac99a13e132d..cd62d52242b3756295a70cee68f4e60443ea35f4 100644
--- a/contrib/pgcrypto/expected/pgp-decrypt.out
+++ b/contrib/pgcrypto/expected/pgp-decrypt.out
@@ -315,7 +315,7 @@ SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
  \xda39a3ee5e6b4b0d3255bfef95601890afd80709
 (1 row)
 
-select digest(pgp_sym_decrypt(dearmor('
+select digest(pgp_sym_decrypt_bytea(dearmor('
 -----BEGIN PGP MESSAGE-----
 Comment: dat3.aes.sha1.mdc.s2k3.z0
 
@@ -387,6 +387,28 @@ ERROR:  Wrong key or corrupt data
 select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1');
 NOTICE:  dbg: parse_literal_data: data type=b
 ERROR:  Not text data
+-- NUL byte in text decrypt.  Ciphertext source:
+-- printf 'a\x00\xc' | gpg --homedir /nonexistent \
+--      --personal-compress-preferences uncompressed --textmode \
+--      --personal-cipher-preferences aes --no-emit-version --batch \
+--      --symmetric --passphrase key --armor
+do $$
+begin
+  perform pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0EBwMCXLc8pozB10Fg0jQBVUID59TLvWutJp0j6eh9ZgjqIRzdYaIymFB8y4XH
+vu0YlJP5D5BX7yqZ+Pry7TlDmiFO
+=rV7z
+-----END PGP MESSAGE-----
+'), 'key', 'debug=1');
+exception when others then
+  raise '%',
+    regexp_replace(sqlerrm, 'encoding "[^"]*"', 'encoding [REDACTED]');
+end
+$$;
+ERROR:  invalid byte sequence for encoding [REDACTED]: 0x00
+CONTEXT:  PL/pgSQL function inline_code_block line 12 at RAISE
 -- Decryption with a certain incorrect key yields an apparent BZip2-compressed
 -- plaintext.  Ciphertext source: iterative pgp_sym_encrypt('secret', 'key')
 -- until the random prefix gave rise to that property.
@@ -417,3 +439,29 @@ UCAAw2JRIISttRHMfDpDuZJpvYo=
 '), 'key', 'debug=1');
 NOTICE:  dbg: parse_compressed_data: bzip2 unsupported
 ERROR:  Unsupported compression algorithm
+-- Check ignore-cipher-failure. This message isn't actually encrypted; it was
+-- created with cipher-algo=bf using an OpenSSL that didn't actually support
+-- Blowfish. After the fix for CVE-2026-14663, we no longer create these broken
+-- ciphertexts, but we allow users to return to the previous behavior during
+-- decryption so that the bad wrapper can be stripped.
+--
+-- Note that if Blowfish is supported by the linked OpenSSL, both decryptions
+-- will fail.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key');
+ERROR:  Wrong key or corrupt data
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key', 'ignore-cipher-failure=1');
+ERROR:  Wrong key or corrupt data
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_1.out b/contrib/pgcrypto/expected/pgp-decrypt_1.out
index 80a4c48613d0046e065103fce9e99bfa874254ba..df4d28872b58cac1c06d0b5137da20f07ce7f5e5 100644
--- a/contrib/pgcrypto/expected/pgp-decrypt_1.out
+++ b/contrib/pgcrypto/expected/pgp-decrypt_1.out
@@ -11,7 +11,7 @@ yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
 =JcP+
 -----END PGP MESSAGE-----
 '), 'foobar');
-ERROR:  Wrong key or corrupt data
+ERROR:  encrypt error: Cipher cannot be initialized
 select pgp_sym_decrypt(dearmor('
 -----BEGIN PGP MESSAGE-----
 Comment: dat1.aes.sha1.mdc.s2k3.z0
@@ -311,7 +311,7 @@ SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
  \xda39a3ee5e6b4b0d3255bfef95601890afd80709
 (1 row)
 
-select digest(pgp_sym_decrypt(dearmor('
+select digest(pgp_sym_decrypt_bytea(dearmor('
 -----BEGIN PGP MESSAGE-----
 Comment: dat3.aes.sha1.mdc.s2k3.z0
 
@@ -383,6 +383,28 @@ ERROR:  Wrong key or corrupt data
 select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1');
 NOTICE:  dbg: parse_literal_data: data type=b
 ERROR:  Not text data
+-- NUL byte in text decrypt.  Ciphertext source:
+-- printf 'a\x00\xc' | gpg --homedir /nonexistent \
+--      --personal-compress-preferences uncompressed --textmode \
+--      --personal-cipher-preferences aes --no-emit-version --batch \
+--      --symmetric --passphrase key --armor
+do $$
+begin
+  perform pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0EBwMCXLc8pozB10Fg0jQBVUID59TLvWutJp0j6eh9ZgjqIRzdYaIymFB8y4XH
+vu0YlJP5D5BX7yqZ+Pry7TlDmiFO
+=rV7z
+-----END PGP MESSAGE-----
+'), 'key', 'debug=1');
+exception when others then
+  raise '%',
+    regexp_replace(sqlerrm, 'encoding "[^"]*"', 'encoding [REDACTED]');
+end
+$$;
+ERROR:  invalid byte sequence for encoding [REDACTED]: 0x00
+CONTEXT:  PL/pgSQL function inline_code_block line 12 at RAISE
 -- Decryption with a certain incorrect key yields an apparent BZip2-compressed
 -- plaintext.  Ciphertext source: iterative pgp_sym_encrypt('secret', 'key')
 -- until the random prefix gave rise to that property.
@@ -413,3 +435,33 @@ UCAAw2JRIISttRHMfDpDuZJpvYo=
 '), 'key', 'debug=1');
 NOTICE:  dbg: parse_compressed_data: bzip2 unsupported
 ERROR:  Unsupported compression algorithm
+-- Check ignore-cipher-failure. This message isn't actually encrypted; it was
+-- created with cipher-algo=bf using an OpenSSL that didn't actually support
+-- Blowfish. After the fix for CVE-2026-14663, we no longer create these broken
+-- ciphertexts, but we allow users to return to the previous behavior during
+-- decryption so that the bad wrapper can be stripped.
+--
+-- Note that if Blowfish is supported by the linked OpenSSL, both decryptions
+-- will fail.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key');
+ERROR:  encrypt error: Cipher cannot be initialized
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key', 'ignore-cipher-failure=1');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
diff --git a/contrib/pgcrypto/expected/pgp-encrypt_1.out b/contrib/pgcrypto/expected/pgp-encrypt_1.out
new file mode 100644
index 0000000000000000000000000000000000000000..36b1052809cd0b7e8d06dca3ec5e94b057c8701c
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-encrypt_1.out
@@ -0,0 +1,192 @@
+--
+-- PGP encrypt
+--
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), 'key');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+-- check whether the defaults are ok
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+	'key', 'expect-cipher-algo=aes128,
+		expect-disable-mdc=0,
+		expect-sess-key=0,
+		expect-s2k-mode=3,
+		expect-s2k-digest-algo=sha1,
+		expect-compress-algo=0
+		');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+-- maybe the expect- stuff simply does not work
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+	'key', 'expect-cipher-algo=bf,
+		expect-disable-mdc=1,
+		expect-sess-key=1,
+		expect-s2k-mode=0,
+		expect-s2k-digest-algo=md5,
+		expect-compress-algo=1
+		');
+NOTICE:  pgp_decrypt: unexpected cipher_algo: expected 4 got 7
+NOTICE:  pgp_decrypt: unexpected s2k_mode: expected 0 got 3
+NOTICE:  pgp_decrypt: unexpected s2k_digest_algo: expected 1 got 2
+NOTICE:  pgp_decrypt: unexpected use_sess_key: expected 1 got 0
+NOTICE:  pgp_decrypt: unexpected disable_mdc: expected 1 got 0
+NOTICE:  pgp_decrypt: unexpected compress_algo: expected 1 got 0
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+-- bytea as text
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('Binary', 'baz'), 'baz');
+ERROR:  Not text data
+-- text as bytea
+select encode(pgp_sym_decrypt_bytea(pgp_sym_encrypt('Text', 'baz'), 'baz'), 'escape');
+ encode 
+--------
+ Text
+(1 row)
+
+-- algorithm change
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'),
+	'key', 'expect-cipher-algo=bf');
+ERROR:  encrypt error: Cipher cannot be initialized
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes'),
+	'key', 'expect-cipher-algo=aes128');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'),
+	'key', 'expect-cipher-algo=aes192');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+-- s2k change
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 's2k-mode=0'),
+	'key', 'expect-s2k-mode=0');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 's2k-mode=1'),
+	'key', 'expect-s2k-mode=1');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3'),
+	'key', 'expect-s2k-mode=3');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+-- s2k count change
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'),
+	'key', 'expect-s2k-count=1024');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+-- s2k_count rounds up
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'),
+	'key', 'expect-s2k-count=65000000');
+NOTICE:  pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+-- s2k digest change
+select pgp_sym_decrypt(
+		pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'),
+	'key', 'expect-s2k-digest-algo=sha1');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+-- sess key
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 'sess-key=0'),
+	'key', 'expect-sess-key=0');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 'sess-key=1'),
+	'key', 'expect-sess-key=1');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=bf'),
+	'key', 'expect-sess-key=1, expect-cipher-algo=bf');
+ERROR:  encrypt error: Cipher cannot be initialized
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes192'),
+	'key', 'expect-sess-key=1, expect-cipher-algo=aes192');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+	pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes256'),
+	'key', 'expect-sess-key=1, expect-cipher-algo=aes256');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+-- no mdc
+select pgp_sym_decrypt(
+		pgp_sym_encrypt('Secret.', 'key', 'disable-mdc=1'),
+	'key', 'expect-disable-mdc=1');
+ pgp_sym_decrypt 
+-----------------
+ Secret.
+(1 row)
+
+-- crlf
+select pgp_sym_decrypt_bytea(
+	pgp_sym_encrypt(E'1\n2\n3\r\n', 'key', 'convert-crlf=1'),
+	'key');
+ pgp_sym_decrypt_bytea  
+------------------------
+ \x310d0a320d0a330d0d0a
+(1 row)
+
+-- conversion should be lossless
+select digest(pgp_sym_decrypt(
+  pgp_sym_encrypt(E'\r\n0\n1\r\r\n\n2\r', 'key', 'convert-crlf=1'),
+	'key', 'convert-crlf=1'), 'sha1') as result,
+  digest(E'\r\n0\n1\r\r\n\n2\r', 'sha1') as expect;
+                   result                   |                   expect                   
+--------------------------------------------+--------------------------------------------
+ \x47bde5d88d6ef8770572b9cbb4278b402aa69966 | \x47bde5d88d6ef8770572b9cbb4278b402aa69966
+(1 row)
+
diff --git a/contrib/pgcrypto/expected/pgp-info.out b/contrib/pgcrypto/expected/pgp-info.out
index 90648383730c0f2cac5dbcccc2974fd017d14155..909e7f7851e7c2be958fa81131e1e72f572ee6e9 100644
--- a/contrib/pgcrypto/expected/pgp-info.out
+++ b/contrib/pgcrypto/expected/pgp-info.out
@@ -75,5 +75,6 @@ from encdata order by id;
  B68504FD128E1FF9
  FD0206C409B74875
  FD0206C409B74875
-(5 rows)
+ D936CF64BB73F466
+(6 rows)
 
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out b/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out
index b4b6810a3c5afd4bc742137726c847271b40900b..d3bb5f1b06d915dd53ffe0461130508393ea46d1 100644
--- a/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out
+++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out
@@ -585,6 +585,20 @@ blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
 =PHJ1
 -----END PGP MESSAGE-----
 ');
+-- CVE-2026-14663. This message was created with cipher-algo=bf using an OpenSSL
+-- that didn't actually support Blowfish.
+insert into encdata (id, data) values (6, '
+-----BEGIN PGP MESSAGE-----
+
+wcBOA9k2z2S7c/RmEAP8DYbU6AeEo6riMMdnf2G62BM9gC0Z32ODydewy3Ki8AnSzpwBDAHuDMcr
+P6RJDWvBOVOwgxHEwR7ZHMoFRDJEXdo6rQ9dQpDtbasMLyi6Lm1q+PbEefVd9WkU7fvFAFQx8k3t
+lxrlWg/byoNplc7/hFxIFO8bN+FIlLgilAdApNcD/3Mg2/nd7pczovsYoryf9ib04kQ+SVWs3iNE
+StoyEXT+oaT8u1vAxiY7fzPpQX1pnlHBUXn+v1J6LQL5Bwi5CTqOyDSyaFfgU0gQwTReFjS6L4Fs
+Cv+2cFwbJBGIzr1aI4DLbzSelkmVm4hbOVeET4DJVlUVhhIyy6ZfoXiTEG6s0jMB2JdRGIl0EUQR
+RMsQdABqdt5jU2VjcmV0IG1zZ9MUIIP4SPiU2pM/nF/A1hrltMhn/ZI=
+=Mkdj
+-----END PGP MESSAGE-----
+');
 -- successful decrypt
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=1 and encdata.id=1;
@@ -600,6 +614,13 @@ from keytbl, encdata where keytbl.id=2 and encdata.id=2;
  Secret msg
 (1 row)
 
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+ pgp_pub_decrypt 
+-----------------
+ Secret msg
+(1 row)
+
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=3 and encdata.id=3;
  pgp_pub_decrypt 
@@ -654,3 +675,12 @@ from keytbl, encdata where keytbl.id=5 and encdata.id=1;
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=6 and encdata.id=5;
 ERROR:  Wrong key or corrupt data
+-- Check that ignore-cipher-failure can strip faulty encryption if OpenSSL
+-- doesn't support the cipher. (The decryption will correctly fail both times if
+-- OpenSSL does support it.)
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+ERROR:  Wrong key or corrupt data
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+ERROR:  Wrong key or corrupt data
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out
index f41c6c9893ab1c811a2267e5d1924de5354799cc..ac9307daf5ece131b6c274f5260382e7abf63619 100644
--- a/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out
+++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out
@@ -585,6 +585,20 @@ blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
 =PHJ1
 -----END PGP MESSAGE-----
 ');
+-- CVE-2026-14663. This message was created with cipher-algo=bf using an OpenSSL
+-- that didn't actually support Blowfish.
+insert into encdata (id, data) values (6, '
+-----BEGIN PGP MESSAGE-----
+
+wcBOA9k2z2S7c/RmEAP8DYbU6AeEo6riMMdnf2G62BM9gC0Z32ODydewy3Ki8AnSzpwBDAHuDMcr
+P6RJDWvBOVOwgxHEwR7ZHMoFRDJEXdo6rQ9dQpDtbasMLyi6Lm1q+PbEefVd9WkU7fvFAFQx8k3t
+lxrlWg/byoNplc7/hFxIFO8bN+FIlLgilAdApNcD/3Mg2/nd7pczovsYoryf9ib04kQ+SVWs3iNE
+StoyEXT+oaT8u1vAxiY7fzPpQX1pnlHBUXn+v1J6LQL5Bwi5CTqOyDSyaFfgU0gQwTReFjS6L4Fs
+Cv+2cFwbJBGIzr1aI4DLbzSelkmVm4hbOVeET4DJVlUVhhIyy6ZfoXiTEG6s0jMB2JdRGIl0EUQR
+RMsQdABqdt5jU2VjcmV0IG1zZ9MUIIP4SPiU2pM/nF/A1hrltMhn/ZI=
+=Mkdj
+-----END PGP MESSAGE-----
+');
 -- successful decrypt
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=1 and encdata.id=1;
@@ -595,6 +609,9 @@ from keytbl, encdata where keytbl.id=1 and encdata.id=1;
 
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+ERROR:  encrypt error: Cipher cannot be initialized
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
 ERROR:  Wrong key or corrupt data
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=3 and encdata.id=3;
@@ -650,3 +667,16 @@ from keytbl, encdata where keytbl.id=5 and encdata.id=1;
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=6 and encdata.id=5;
 ERROR:  Wrong key or corrupt data
+-- Check that ignore-cipher-failure can strip faulty encryption if OpenSSL
+-- doesn't support the cipher. (The decryption will correctly fail both times if
+-- OpenSSL does support it.)
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+ERROR:  encrypt error: Cipher cannot be initialized
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+ pgp_pub_decrypt 
+-----------------
+ Secret msg
+(1 row)
+
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-session.out b/contrib/pgcrypto/expected/pgp-pubkey-session.out
new file mode 100644
index 0000000000000000000000000000000000000000..e57cb8fab99c3ec17eac652fcd0213f3d9ff4ac2
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-session.out
@@ -0,0 +1,47 @@
+-- Test for overflow with session key at decrypt.
+-- Data automatically generated by scripts/pgp_session_data.py.
+-- See this file for details explaining how this data is generated.
+SELECT pgp_pub_decrypt_bytea(
+'\xc1c04c030000000000000000020800a46f5b9b1905b49457a6485474f71ed9b46c2527e1
+da08e1f7871e12c3d38828f2076b984a595bf60f616599ca5729d547de06a258bfbbcd30
+94a321e4668cd43010f0ca8ecf931e5d39bda1152c50c367b11c723f270729245d3ebdbd
+0694d320c5a5aa6a405fb45182acb3d7973cbce398e0c5060af7603cfd9ed186ebadd616
+3b50ae42bea5f6d14dda24e6d4687b434c175084515d562e896742b0ba9a1c87d5642e10
+a5550379c71cc490a052ada483b5d96526c0a600fc51755052aa77fdf72f7b4989b920e7
+b90f4b30787a46482670d5caecc7a515a926055ad5509d135702ce51a0e4c1033f2d939d
+8f0075ec3428e17310da37d3d2d7ad1ce99adcc91cd446c366c402ae1ee38250343a7fcc
+0f8bc28020e603d7a4795ef0dcc1c04c030000000000000000020800a46f5b9b1905b494
+57a6485474f71ed9b46c2527e1da08e1f7871e12c3d38828f2076b984a595bf60f616599
+ca5729d547de06a258bfbbcd3094a321e4668cd43010f0ca8ecf931e5d39bda1152c50c3
+67b11c723f270729245d3ebdbd0694d320c5a5aa6a405fb45182acb3d7973cbce398e0c5
+060af7603cfd9ed186ebadd6163b50ae42bea5f6d14dda24e6d4687b434c175084515d56
+2e896742b0ba9a1c87d5642e10a5550379c71cc490a052ada483b5d96526c0a600fc5175
+5052aa77fdf72f7b4989b920e7b90f4b30787a46482670d5caecc7a515a926055ad5509d
+135702ce51a0e4c1033f2d939d8f0075ec3428e17310da37d3d2d7ad1ce99adc'::bytea,
+'\xc7c2d8046965d657020800eef8bf1515adb1a3ee7825f75c668ea8dd3e3f9d13e958f6ad
+9c55adc0c931a4bb00abe1d52cf7bb0c95d537949d277a5292ede375c6b2a67a3bf7d19f
+f975bb7e7be35c2d8300dacba360a0163567372f7dc24000cc7cb6170bedc8f3b1f98c12
+07a6cb4de870a4bc61319b139dcc0e20c368fd68f8fd346d2c0b69c5aed560504e2ec6f1
+23086fe3c5540dc4dd155c0c67257c4ada862f90fe172ace344089da8135e92aca5c2709
+f1c1bc521798bb8c0365841496e709bd184132d387e0c9d5f26dc00fd06c3a76ef66a75c
+138285038684707a847b7bd33cfbefbf1d336be954a8048946af97a66352adef8e8b5ae4
+c4748c6f2510265b7a8267bc370dbb00110100010007ff7e72d4f95d2d39901ac12ca5c5
+18e767e719e72340c3fab51c8c5ab1c40f31db8eaffe43533fa61e2dbca2c3f4396c0847
+e5434756acbb1f68128f4136bb135710c89137d74538908dac77967de9e821c559700dd9
+de5a2727eec1f5d12d5d74869dd1de45ed369d94a8814d23861dd163f8c27744b26b98f0
+239c2e6dd1e3493b8cc976fdc8f9a5e250f715aa4c3d7d5f237f8ee15d242e8fa941d1a0
+ed9550ab632d992a97518d142802cb0a97b251319bf5742db8d9d8cbaa06cdfba2d75bc9
+9d77a51ff20bd5ba7f15d7af6e85b904de2855d19af08d45f39deb85403033c69c767a8e
+74a343b1d6c8911d34ea441ac3850e57808ed3d885835cbe6c79d10400ef16256f3d5c4c
+3341516a2d2aa888df81b603f48a27f3666b40f992a857c1d11ff639cd764a9b42d5a1f8
+58b4aeee36b85508bb5e8b91ef88a7737770b330224479d9b44eae8c631bc43628b69549
+507c0a1af0be0dd7696015abea722b571eb35eefc4ab95595378ec12814727443f625fcd
+183bb9b3bccf53b54dd0e5e7a50400ffe08537b2d4e6074e4a1727b658cfccdec8962302
+25e300c05690de45f7065c3d40d86f544a64d51a3e94424f9851a16d1322ebdb41fa8a45
+3131f3e2dc94e858e6396722643df382680f815e53bcdcde5da622f50530a83b217f1103
+cdd6e5e9babe1e415bbff28d44bd18c95f43bbd04afeb2a2a99af38a571c7540de21df03
+ff62c0a33d9143dd3f639893f47732c11c5a12c6052d1935f4d507b7ae1f76ab0e9a69b8
+7305a7f7c19bd509daf4903bff614bc26d118f03e461469c72c12d3a2bb4f78e4d342ce8
+487723649a01ed2b9eb11c662134502c098d55dfcd361939d8370873422c3da75a515a75
+9ffedfe7df44fb3c20f81650801a30d43b5c90b98b3eee'::bytea);
+ERROR:  Session key too big
diff --git a/contrib/pgcrypto/meson.build b/contrib/pgcrypto/meson.build
index 85948915482c38114e964926eebc8fbda9824a78..59ff85522819d4f1675f255a44f6770604e710a7 100644
--- a/contrib/pgcrypto/meson.build
+++ b/contrib/pgcrypto/meson.build
@@ -51,6 +51,7 @@ pgcrypto_regress = [
   'pgp-encrypt-md5',
   'pgp-pubkey-decrypt',
   'pgp-pubkey-encrypt',
+  'pgp-pubkey-session',
   'pgp-info',
 ]
 
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index 8259de5e393d97195b39ff73c30a406560882d1b..b1692f07913a7195f5e9992a8bb991f36e7c178e 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -800,5 +800,8 @@ px_find_cipher(const char *name, PX_Cipher **res)
 static void
 ResOwnerReleaseOSSLCipher(Datum res)
 {
-	free_openssl_cipher((OSSLCipher *) DatumGetPointer(res));
+	OSSLCipher *cipher = (OSSLCipher *) DatumGetPointer(res);
+
+	cipher->owner = NULL;
+	free_openssl_cipher(cipher);
 }
diff --git a/contrib/pgcrypto/pgp-cfb.c b/contrib/pgcrypto/pgp-cfb.c
index de41e825b0ce85289b9de1763ca8900c3f1e182f..c81534bf1efe21f7bbfa9f48541dbcae61d97c36 100644
--- a/contrib/pgcrypto/pgp-cfb.c
+++ b/contrib/pgcrypto/pgp-cfb.c
@@ -43,6 +43,7 @@ struct PGP_CFB
 	int			pos;
 	int			block_no;
 	int			resync;
+	int			ignore_decrypt_cipher_failure;	/* for CVE-2026-14663 recovery */
 	uint8		fr[PGP_MAX_BLOCK];
 	uint8		fre[PGP_MAX_BLOCK];
 	uint8		encbuf[PGP_MAX_BLOCK];
@@ -50,7 +51,7 @@ struct PGP_CFB
 
 int
 pgp_cfb_create(PGP_CFB **ctx_p, int algo, const uint8 *key, int key_len,
-			   int resync, uint8 *iv)
+			   int resync, uint8 *iv, int ignore_decrypt_cipher_failure)
 {
 	int			res;
 	PX_Cipher  *ciph;
@@ -71,6 +72,7 @@ pgp_cfb_create(PGP_CFB **ctx_p, int algo, const uint8 *key, int key_len,
 	ctx->ciph = ciph;
 	ctx->block_size = px_cipher_block_size(ciph);
 	ctx->resync = resync;
+	ctx->ignore_decrypt_cipher_failure = ignore_decrypt_cipher_failure;
 
 	if (iv)
 		memcpy(ctx->fr, iv, ctx->block_size);
@@ -195,7 +197,7 @@ mix_decrypt_resync(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst)
  */
 static int
 cfb_process(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst,
-			mix_data_t mix_data)
+			mix_data_t mix_data, int ignore_cipher_failure)
 {
 	int			n;
 	int			res;
@@ -221,8 +223,21 @@ cfb_process(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst,
 	while (len > 0)
 	{
 		unsigned	rlen;
+		int			err;
+
+		err = px_cipher_encrypt(ctx->ciph, 0, ctx->fr, ctx->block_size, ctx->fre, &rlen);
+
+		/*
+		 * XXX Ignoring cipher failures is dangerous, but we allow it during
+		 * decryption to return to the behavior prior to the fix for
+		 * CVE-2026-14663. This lets users recover data from a badly-encrypted
+		 * message.
+		 */
+		if (err && !ignore_cipher_failure)
+			ereport(ERROR,
+					(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
+					 errmsg("encrypt error: %s", px_strerror(err))));
 
-		px_cipher_encrypt(ctx->ciph, 0, ctx->fr, ctx->block_size, ctx->fre, &rlen);
 		if (ctx->block_no < 5)
 			ctx->block_no++;
 
@@ -253,7 +268,8 @@ pgp_cfb_encrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst)
 {
 	mix_data_t	mix = ctx->resync ? mix_encrypt_resync : mix_encrypt_normal;
 
-	return cfb_process(ctx, data, len, dst, mix);
+	return cfb_process(ctx, data, len, dst, mix,
+					   0 /* never ignore cipher failures for encrypt */ );
 }
 
 int
@@ -261,5 +277,6 @@ pgp_cfb_decrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst)
 {
 	mix_data_t	mix = ctx->resync ? mix_decrypt_resync : mix_decrypt_normal;
 
-	return cfb_process(ctx, data, len, dst, mix);
+	return cfb_process(ctx, data, len, dst, mix,
+					   ctx->ignore_decrypt_cipher_failure);
 }
diff --git a/contrib/pgcrypto/pgp-decrypt.c b/contrib/pgcrypto/pgp-decrypt.c
index e1ea5b3e58dcfba87cd44be7354364496ac0df2b..887fbc19278b83039a50227242a32700b8c21334 100644
--- a/contrib/pgcrypto/pgp-decrypt.c
+++ b/contrib/pgcrypto/pgp-decrypt.c
@@ -595,7 +595,8 @@ decrypt_key(PGP_Context *ctx, const uint8 *src, int len)
 	PGP_CFB    *cfb;
 
 	res = pgp_cfb_create(&cfb, ctx->s2k_cipher_algo,
-						 ctx->s2k.key, ctx->s2k.key_len, 0, NULL);
+						 ctx->s2k.key, ctx->s2k.key_len, 0, NULL,
+						 ctx->ignore_cipher_failure);
 	if (res < 0)
 		return res;
 
@@ -983,7 +984,8 @@ parse_symenc_data(PGP_Context *ctx, PullFilter *pkt, MBuf *dst)
 	PullFilter *pf_prefix = NULL;
 
 	res = pgp_cfb_create(&cfb, ctx->cipher_algo,
-						 ctx->sess_key, ctx->sess_key_len, 1, NULL);
+						 ctx->sess_key, ctx->sess_key_len, 1, NULL,
+						 ctx->ignore_cipher_failure);
 	if (res < 0)
 		goto out;
 
@@ -1026,7 +1028,8 @@ parse_symenc_mdc_data(PGP_Context *ctx, PullFilter *pkt, MBuf *dst)
 	}
 
 	res = pgp_cfb_create(&cfb, ctx->cipher_algo,
-						 ctx->sess_key, ctx->sess_key_len, 0, NULL);
+						 ctx->sess_key, ctx->sess_key_len, 0, NULL,
+						 ctx->ignore_cipher_failure);
 	if (res < 0)
 		goto out;
 
diff --git a/contrib/pgcrypto/pgp-encrypt.c b/contrib/pgcrypto/pgp-encrypt.c
index f7467c9b1cb1cd24d5abebdc142527e0d9d10d90..968e8c92ce40a2e0c166f2bd73528279dab4a865 100644
--- a/contrib/pgcrypto/pgp-encrypt.c
+++ b/contrib/pgcrypto/pgp-encrypt.c
@@ -174,7 +174,8 @@ encrypt_init(PushFilter *next, void *init_arg, void **priv_p)
 			return res;
 	}
 	res = pgp_cfb_create(&ciph, ctx->cipher_algo,
-						 ctx->sess_key, ctx->sess_key_len, resync, NULL);
+						 ctx->sess_key, ctx->sess_key_len, resync, NULL,
+						 0 /* never ignore cipher failures for encrypt */ );
 	if (res < 0)
 		return res;
 
@@ -505,7 +506,8 @@ symencrypt_sesskey(PGP_Context *ctx, uint8 *dst)
 	uint8		algo = ctx->cipher_algo;
 
 	res = pgp_cfb_create(&cfb, ctx->s2k_cipher_algo,
-						 ctx->s2k.key, ctx->s2k.key_len, 0, NULL);
+						 ctx->s2k.key, ctx->s2k.key_len, 0, NULL,
+						 0 /* never ignore cipher failures for encrypt */ );
 	if (res < 0)
 		return res;
 
diff --git a/contrib/pgcrypto/pgp-pgsql.c b/contrib/pgcrypto/pgp-pgsql.c
index 7c9f4c7b39b88ea812bc32331585513ee995b1ce..c9d10a27f1d1d823f4de629474660dbd376c9152 100644
--- a/contrib/pgcrypto/pgp-pgsql.c
+++ b/contrib/pgcrypto/pgp-pgsql.c
@@ -192,6 +192,8 @@ set_arg(PGP_Context *ctx, char *key, char *val,
 		res = pgp_set_convert_crlf(ctx, atoi(val));
 	else if (strcmp(key, "unicode-mode") == 0)
 		res = pgp_set_unicode_mode(ctx, atoi(val));
+	else if (strcmp(key, "ignore-cipher-failure") == 0)
+		res = pgp_set_ignore_cipher_failure(ctx, atoi(val));
 
 	/*
 	 * The remaining options are for debugging/testing and are therefore not
@@ -631,6 +633,7 @@ pgp_sym_decrypt_text(PG_FUNCTION_ARGS)
 		arg = PG_GETARG_TEXT_PP(2);
 
 	res = decrypt_internal(0, 1, data, key, NULL, arg);
+	pg_verifymbstr(VARDATA_ANY(res), VARSIZE_ANY_EXHDR(res), false);
 
 	PG_FREE_IF_COPY(data, 0);
 	PG_FREE_IF_COPY(key, 1);
@@ -732,6 +735,7 @@ pgp_pub_decrypt_text(PG_FUNCTION_ARGS)
 		arg = PG_GETARG_TEXT_PP(3);
 
 	res = decrypt_internal(1, 1, data, key, psw, arg);
+	pg_verifymbstr(VARDATA_ANY(res), VARSIZE_ANY_EXHDR(res), false);
 
 	PG_FREE_IF_COPY(data, 0);
 	PG_FREE_IF_COPY(key, 1);
diff --git a/contrib/pgcrypto/pgp-pubdec.c b/contrib/pgcrypto/pgp-pubdec.c
index a0a5738a40e893e4c2f65fd57f4b0894bf12880f..2a13aa3e6adffe2da4697fb2c212a9275e8ef1c1 100644
--- a/contrib/pgcrypto/pgp-pubdec.c
+++ b/contrib/pgcrypto/pgp-pubdec.c
@@ -157,6 +157,7 @@ pgp_parse_pubenc_sesskey(PGP_Context *ctx, PullFilter *pkt)
 	uint8	   *msg;
 	int			msglen;
 	PGP_MPI    *m;
+	unsigned	sess_key_len;
 
 	pk = ctx->pub_key;
 	if (pk == NULL)
@@ -220,11 +221,19 @@ pgp_parse_pubenc_sesskey(PGP_Context *ctx, PullFilter *pkt)
 	if (res < 0)
 		goto out;
 
+	sess_key_len = msglen - 3;
+	if (sess_key_len > PGP_MAX_KEY)
+	{
+		px_debug("incorrect session key length=%u", sess_key_len);
+		res = PXE_PGP_KEY_TOO_BIG;
+		goto out;
+	}
+
 	/*
 	 * got sesskey
 	 */
 	ctx->cipher_algo = *msg;
-	ctx->sess_key_len = msglen - 3;
+	ctx->sess_key_len = sess_key_len;
 	memcpy(ctx->sess_key, msg + 1, ctx->sess_key_len);
 
 out:
diff --git a/contrib/pgcrypto/pgp-pubkey.c b/contrib/pgcrypto/pgp-pubkey.c
index 9a6561caf9ddedbb7cb7a22ffd18d4c26a03854a..470e0debbb0e383c03eb86ba876e8b7d086a7084 100644
--- a/contrib/pgcrypto/pgp-pubkey.c
+++ b/contrib/pgcrypto/pgp-pubkey.c
@@ -382,8 +382,15 @@ process_secret_key(PullFilter *pkt, PGP_PubKey **pk_p,
 
 		/*
 		 * create decrypt filter
+		 *
+		 * ignore-cipher-failure doesn't apply here; pgcrypto didn't encrypt
+		 * the secret key to begin with, and any stored encrypted data was
+		 * generated using the public key, so users don't have a reason to
+		 * want to incorrectly decrypt this. We'll ignore failures during
+		 * decryption with the session key, instead.
 		 */
-		res = pgp_cfb_create(&cfb, cipher_algo, s2k.key, s2k.key_len, 0, iv);
+		res = pgp_cfb_create(&cfb, cipher_algo, s2k.key, s2k.key_len, 0, iv,
+							 0 /* don't ignore cipher failures */ );
 		if (res < 0)
 			return res;
 		res = pullf_create(&pf_decrypt, &pgp_decrypt_filter, cfb, pkt);
diff --git a/contrib/pgcrypto/pgp.c b/contrib/pgcrypto/pgp.c
index 8a6a6c2adf1f444240d8f88fcdfce4f1003d02fa..cf3fff90fa2608fe52abd09854643de3573cbf36 100644
--- a/contrib/pgcrypto/pgp.c
+++ b/contrib/pgcrypto/pgp.c
@@ -49,6 +49,7 @@ static int	def_use_sess_key = 0;
 static int	def_text_mode = 0;
 static int	def_unicode_mode = 0;
 static int	def_convert_crlf = 0;
+static int	def_ignore_cipher_failure = 0;
 
 struct digest_info
 {
@@ -204,6 +205,7 @@ pgp_init(PGP_Context **ctx_p)
 	ctx->unicode_mode = def_unicode_mode;
 	ctx->convert_crlf = def_convert_crlf;
 	ctx->text_mode = def_text_mode;
+	ctx->ignore_cipher_failure = def_ignore_cipher_failure;
 
 	*ctx_p = ctx;
 	return 0;
@@ -349,6 +351,13 @@ pgp_set_unicode_mode(PGP_Context *ctx, int mode)
 	return 0;
 }
 
+int
+pgp_set_ignore_cipher_failure(PGP_Context *ctx, int ignore)
+{
+	ctx->ignore_cipher_failure = ignore ? 1 : 0;
+	return 0;
+}
+
 int
 pgp_set_symkey(PGP_Context *ctx, const uint8 *key, int len)
 {
diff --git a/contrib/pgcrypto/pgp.h b/contrib/pgcrypto/pgp.h
index 0bbfd0217bae5147bcafa7a140ddaa40e4cc2436..7b130388fccd08f6348708ae001874227a416e53 100644
--- a/contrib/pgcrypto/pgp.h
+++ b/contrib/pgcrypto/pgp.h
@@ -150,6 +150,9 @@ struct PGP_Context
 	int			convert_crlf;
 	int			unicode_mode;
 
+	/* DANGEROUS recovery aid for CVE-2026-14663. Applies only to decryption. */
+	int			ignore_cipher_failure;
+
 	/*
 	 * internal variables
 	 */
@@ -258,6 +261,7 @@ int			pgp_set_compress_level(PGP_Context *ctx, int level);
 int			pgp_set_text_mode(PGP_Context *ctx, int mode);
 int			pgp_set_unicode_mode(PGP_Context *ctx, int mode);
 int			pgp_get_unicode_mode(PGP_Context *ctx);
+int			pgp_set_ignore_cipher_failure(PGP_Context *ctx, int ignore);
 
 int			pgp_set_symkey(PGP_Context *ctx, const uint8 *key, int len);
 int			pgp_set_pubkey(PGP_Context *ctx, MBuf *keypkt,
@@ -278,7 +282,8 @@ int			pgp_s2k_process(PGP_S2K *s2k, int cipher, const uint8 *key, int key_len);
 
 typedef struct PGP_CFB PGP_CFB;
 int			pgp_cfb_create(PGP_CFB **ctx_p, int algo,
-						   const uint8 *key, int key_len, int resync, uint8 *iv);
+						   const uint8 *key, int key_len, int resync, uint8 *iv,
+						   int ignore_decrypt_cipher_failure);
 void		pgp_cfb_free(PGP_CFB *ctx);
 int			pgp_cfb_encrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);
 int			pgp_cfb_decrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst);
diff --git a/contrib/pgcrypto/px.c b/contrib/pgcrypto/px.c
index d35ccca77746d8dba8ffc360ea8b90c90bba297c..ad0914991b1dd58f87edddb1c3f2f40fb30317f3 100644
--- a/contrib/pgcrypto/px.c
+++ b/contrib/pgcrypto/px.c
@@ -65,6 +65,7 @@ static const struct error_desc px_err_list[] = {
 	{PXE_PGP_UNEXPECTED_PKT, "Unexpected packet in key data"},
 	{PXE_PGP_MATH_FAILED, "Math operation failed"},
 	{PXE_PGP_SHORT_ELGAMAL_KEY, "Elgamal keys must be at least 1024 bits long"},
+	{PXE_PGP_KEY_TOO_BIG, "Session key too big"},
 	{PXE_PGP_UNKNOWN_PUBALGO, "Unknown public-key encryption algorithm"},
 	{PXE_PGP_WRONG_KEY, "Wrong key"},
 	{PXE_PGP_MULTIPLE_KEYS,
diff --git a/contrib/pgcrypto/px.h b/contrib/pgcrypto/px.h
index 471bb4ec7274c216977854dd386a990f44b46ea6..c92b0864e3f35fdda4f7b933cbbec9fe93f67e26 100644
--- a/contrib/pgcrypto/px.h
+++ b/contrib/pgcrypto/px.h
@@ -75,7 +75,7 @@
 /* -108 is unused */
 #define PXE_PGP_MATH_FAILED			-109
 #define PXE_PGP_SHORT_ELGAMAL_KEY	-110
-/* -111 is unused */
+#define PXE_PGP_KEY_TOO_BIG			-111
 #define PXE_PGP_UNKNOWN_PUBALGO		-112
 #define PXE_PGP_WRONG_KEY			-113
 #define PXE_PGP_MULTIPLE_KEYS		-114
diff --git a/contrib/pgcrypto/scripts/pgp_session_data.py b/contrib/pgcrypto/scripts/pgp_session_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..999350bb2bc9153c12bdb9631ad8233ed46e4603
--- /dev/null
+++ b/contrib/pgcrypto/scripts/pgp_session_data.py
@@ -0,0 +1,491 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Generate PGP data to check the session key length of the input data provided
+# to pgp_pub_decrypt_bytea().
+#
+# First, the crafted data is generated from valid RSA data, freshly generated
+# by this script each time it is run, see generate_rsa_keypair().
+# Second, the crafted PGP data is built, see build_message_data() and
+# build_key_data().  Finally, the resulting SQL script is generated.
+#
+# This script generates in stdout the SQL file that is used in the regression
+# tests of pgcrypto.  The following command can be used to regenerate the file
+# which should never be manually manipulated:
+# python3 scripts/pgp_session_data.py > sql/pgp-pubkey-session.sql
+
+import os
+import re
+import struct
+import secrets
+import sys
+import time
+
+# pwn for binary manipulation (p32, p64)
+from pwn import *
+
+# Cryptographic libraries, to craft the PGP data.
+from Crypto.Cipher import AES
+from Crypto.PublicKey import RSA
+from Crypto.Util.number import inverse
+
+# AES key used for session key encryption (16 bytes for AES-128)
+AES_KEY = b'\x01' * 16
+
+def generate_rsa_keypair(key_size: int = 2048) -> dict:
+    """
+    Generate a fresh RSA key pair.
+
+    The generated key includes all components needed for PGP operations:
+    - n: public modulus (p * q)
+    - e: public exponent (typically 65537)
+    - d: private exponent (e^-1 mod phi(n))
+    - p, q: prime factors of n
+    - u: coefficient (p^-1 mod q) for CRT optimization
+
+    The caller can pass the wanted key size in input, for a default of 2048
+    bytes.  This function returns the RSA key components, after performing
+    some validation on them.
+    """
+
+    start_time = time.time()
+
+    # Generate RSA key
+    key = RSA.generate(key_size)
+
+    # Extract all key components
+    rsa_components = {
+        'n': key.n,      # Public modulus (p * q)
+        'e': key.e,      # Public exponent (typically 65537)
+        'd': key.d,      # Private exponent (e^-1 mod phi(n))
+        'p': key.p,      # First prime factor
+        'q': key.q,      # Second prime factor
+        'u': inverse(key.p, key.q)  # Coefficient for CRT: p^-1 mod q
+    }
+
+    # Validate key components for correctness
+    validate_rsa_key(rsa_components)
+
+    return rsa_components
+
+def validate_rsa_key(rsa: dict) -> None:
+    """
+    Validate a generated RSA key.
+
+    This function performs basic validation to ensure the RSA key is properly
+    constructed and all components are consistent, at least mathematically.
+
+    Validations performed:
+    1. n = p * q (modulus is product of primes)
+    2. gcd(e, phi(n)) = 1 (public exponent is coprime to phi(n))
+    3. (d * e) mod(phi(n)) = 1 (private exponent is multiplicative inverse)
+    4. (u * p) (mod q) = 1 (coefficient is correct for CRT)
+    """
+
+    n, e, d, p, q, u = rsa['n'], rsa['e'], rsa['d'], rsa['p'], rsa['q'], rsa['u']
+
+    # Check that n = p * q
+    if n != p * q:
+        raise ValueError("RSA validation failed: n <> p * q")
+
+    # Check that p and q are different
+    if p == q:
+        raise ValueError("RSA validation failed: p = q (not allowed)")
+
+    # Calculate phi(n) = (p-1)(q-1)
+    phi_n = (p - 1) * (q - 1)
+
+    # Check that gcd(e, phi(n)) = 1
+    def gcd(a, b):
+        while b:
+            a, b = b, a % b
+        return a
+
+    if gcd(e, phi_n) != 1:
+        raise ValueError("RSA validation failed: gcd(e, phi(n)) <> 1")
+
+    # Check that (d * e) mod(phi(n)) = 1
+    if (d * e) % phi_n != 1:
+        raise ValueError("RSA validation failed: d * e <> 1 (mod phi(n))")
+
+    # Check that (u * p) (mod q) = 1
+    if (u * p) % q != 1:
+        raise ValueError("RSA validation failed: u * p <> 1 (mod q)")
+
+def mpi_encode(x: int) -> bytes:
+    """
+    Encode an integer as an OpenPGP Multi-Precision Integer (MPI).
+
+    Format (RFC 4880, Section 3.2):
+    - 2 bytes: bit length of the integer (big-endian)
+    - N bytes: the integer in big-endian format
+
+    This is used to encode RSA key components (n, e, d, p, q, u) in PGP
+    packets.
+
+    The integer to encode is given in input, returning an MPI-encoded
+    integer.
+
+    For example:
+        mpi_encode(65537) -> b'\x00\x11\x01\x00\x01'
+        (17 bits, value 0x010001)
+    """
+    if x < 0:
+        raise ValueError("MPI cannot encode negative integers")
+
+    if x == 0:
+        # Special case: zero has 0 bits and empty magnitude
+        bits = 0
+        mag = b""
+    else:
+        # Calculate bit length and convert to bytes
+        bits = x.bit_length()
+        mag = x.to_bytes((bits + 7) // 8, 'big')
+
+    # Pack: 2-byte bit length + magnitude bytes
+    return struct.pack('>H', bits) + mag
+
+def new_packet(tag: int, payload: bytes) -> bytes:
+    """
+    Create a new OpenPGP packet with a proper header.
+
+    OpenPGP packet format (RFC 4880, Section 4.2):
+    - New packet format: 0xC0 | tag
+    - Length encoding depends on payload size:
+      * 0-191: single byte
+      * 192-8383: two bytes (192 + ((length - 192) >> 8), (length - 192) & 0xFF)
+      * 8384+: five bytes (0xFF + 4-byte big-endian length)
+
+    The packet is built from a "tag" (1-63) and some "payload" data.  The
+    result generated is a complete OpenPGP packet.
+
+    For example:
+        new_packet(1, b'data') -> b'\xC1\x04data'
+        (Tag 1, length 4, payload 'data')
+    """
+    # New packet format: set bit 7 and 6, clear bit 5, tag in bits 0-5
+    first = 0xC0 | (tag & 0x3F)
+    ln = len(payload)
+
+    # Encode length according to OpenPGP specification
+    if ln <= 191:
+        # Single byte length for small packets
+        llen = bytes([ln])
+    elif ln <= 8383:
+        # Two-byte length for medium packets
+        ln2 = ln - 192
+        llen = bytes([192 + (ln2 >> 8), ln2 & 0xFF])
+    else:
+        # Five-byte length for large packets
+        llen = bytes([255]) + struct.pack('>I', ln)
+
+    return bytes([first]) + llen + payload
+
+def build_key_data(rsa: dict) -> bytes:
+    """
+    Build the key data, containing an RSA private key.
+
+    The RSA contents should have been generated previously.
+
+    Format (see RFC 4880, Section 5.5.3):
+    - 1 byte: version (4)
+    - 4 bytes: creation time (current Unix timestamp)
+    - 1 byte: public key algorithm (2 = RSA encrypt)
+    - MPI: RSA public modulus n
+    - MPI: RSA public exponent e
+    - 1 byte: string-to-key usage (0 = no encryption)
+    - MPI: RSA private exponent d
+    - MPI: RSA prime p
+    - MPI: RSA prime q
+    - MPI: RSA coefficient u = p^-1 mod q
+    - 2 bytes: checksum of private key material
+
+    This function takes a set of RSA key components in input (n, e, d, p, q, u)
+    and returns a secret key packet.
+    """
+
+    # Public key portion
+    ver = bytes([4])                           # Version 4 key
+    ctime = struct.pack('>I', int(time.time())) # Current Unix timestamp
+    algo = bytes([2])                          # RSA encrypt algorithm
+    n_mpi = mpi_encode(rsa['n'])               # Public modulus
+    e_mpi = mpi_encode(rsa['e'])               # Public exponent
+    pub = ver + ctime + algo + n_mpi + e_mpi
+
+    # Private key portion
+    hide_type = bytes([0])              # No string-to-key encryption
+    d_mpi = mpi_encode(rsa['d'])        # Private exponent
+    p_mpi = mpi_encode(rsa['p'])        # Prime p
+    q_mpi = mpi_encode(rsa['q'])        # Prime q
+    u_mpi = mpi_encode(rsa['u'])        # Coefficient u = p^-1 mod q
+
+    # Calculate checksum of private key material (simple sum mod 65536)
+    private_data = d_mpi + p_mpi + q_mpi + u_mpi
+    cksum = sum(private_data) & 0xFFFF
+
+    secret = hide_type + private_data + struct.pack('>H', cksum)
+    payload = pub + secret
+
+    return new_packet(7, payload)
+
+def pgp_cfb_encrypt_resync(key, plaintext):
+    """
+    Implement OpenPGP CFB mode with resync.
+
+    OpenPGP CFB mode is a variant of standard CFB with a resync operation
+    after the first two blocks.
+
+    Algorithm (RFC 4880, Section 13.9):
+    1. Block 1: FR=zeros, encrypt full block_size bytes
+    2. Block 2: FR=block1, encrypt only 2 bytes
+    3. Resync: FR = block1[2:] + block2
+    4. Remaining blocks: standard CFB mode
+
+    This function uses the following arguments:
+    - key: AES encryption key (16 bytes for AES-128)
+    - plaintext: Data to encrypt
+    """
+    block_size = 16  # AES block size
+    cipher = AES.new(key[:16], AES.MODE_ECB)  # Use ECB for manual CFB
+    ciphertext = b''
+
+    # Block 1: FR=zeros, encrypt full 16 bytes
+    FR = b'\x00' * block_size
+    FRE = cipher.encrypt(FR)  # Encrypt the feedback register
+    block1 = bytes(a ^ b for a, b in zip(FRE, plaintext[0:16]))
+    ciphertext += block1
+
+    # Block 2: FR=block1, encrypt only 2 bytes
+    FR = block1
+    FRE = cipher.encrypt(FR)
+    block2 = bytes(a ^ b for a, b in zip(FRE[0:2], plaintext[16:18]))
+    ciphertext += block2
+
+    # Resync: FR = block1[2:16] + block2[0:2]
+    # This is the key difference from standard CFB mode
+    FR = block1[2:] + block2
+
+    # Block 3+: Continue with standard CFB mode
+    pos = 18
+    while pos < len(plaintext):
+        FRE = cipher.encrypt(FR)
+        chunk_len = min(block_size, len(plaintext) - pos)
+        chunk = plaintext[pos:pos+chunk_len]
+        enc_chunk = bytes(a ^ b for a, b in zip(FRE[:chunk_len], chunk))
+        ciphertext += enc_chunk
+
+        # Update feedback register for next iteration
+        if chunk_len == block_size:
+            FR = enc_chunk
+        else:
+            # Partial block: pad with old FR bytes
+            FR = enc_chunk + FR[chunk_len:]
+        pos += chunk_len
+
+    return ciphertext
+
+def build_literal_data_packet(data: bytes) -> bytes:
+    """
+    Build a literal data packet containing a message.
+
+    Format (RFC 4880, Section 5.9):
+    - 1 byte: data format ('b' = binary, 't' = text, 'u' = UTF-8 text)
+    - 1 byte: filename length (0 = no filename)
+    - N bytes: filename (empty in this case)
+    - 4 bytes: date (current Unix timestamp)
+    - M bytes: literal data
+
+    The data used to build the packet is given in input, with the generated
+    result returned.
+    """
+    body = bytes([
+        ord('b'),                              # Binary data format
+        0,                                     # Filename length (0 = no filename)
+    ]) + struct.pack('>I', int(time.time())) + data  # Current timestamp + data
+
+    return new_packet(11, body)
+
+def build_symenc_data_packet(sess_key: bytes, cipher_algo: int, payload: bytes) -> bytes:
+    """
+    Build a symmetrically-encrypted data packet using AES-128-CFB.
+
+    This packet contains encrypted data using the session key. The format
+    includes a random prefix, for security (see RFC 4880, Section 5.7).
+
+    Packet structure:
+    - Random prefix (block_size bytes)
+    - Prefix repeat (last 2 bytes of prefix repeated)
+    - Encrypted literal data packet
+
+    This function uses the following set of arguments:
+    - sess_key: Session key for encryption
+    - cipher_algo: Cipher algorithm identifier (7 = AES-128)
+    - payload: Data to encrypt (wrapped in literal data packet)
+    """
+    block_size = 16  # AES-128 block size
+    key = sess_key[:16]  # Use first 16 bytes for AES-128
+
+    # Create random prefix + repeat last 2 bytes (total 18 bytes)
+    # This is required by OpenPGP for integrity checking
+    prefix_random = secrets.token_bytes(block_size)
+    prefix = prefix_random + prefix_random[-2:]  # 18 bytes total
+
+    # Wrap payload in literal data packet
+    literal_pkt = build_literal_data_packet(payload)
+
+    # Plaintext = prefix + literal data packet
+    plaintext = prefix + literal_pkt
+
+    # Encrypt using OpenPGP CFB mode with resync
+    ciphertext = pgp_cfb_encrypt_resync(key, plaintext)
+
+    return new_packet(9, ciphertext)
+
+def build_tag1_packet(rsa: dict, sess_key: bytes) -> bytes:
+    """
+    Build a public-key encrypted key.
+
+    This is a very important function, as it is able to create the packet
+    triggering the overflow check.  This function can also be used to create
+    "legit" packet data.
+
+    Format (RFC 4880, Section 5.1):
+    - 1 byte: version (3)
+    - 8 bytes: key ID (0 = any key accepted)
+    - 1 byte: public key algorithm (2 = RSA encrypt)
+    - MPI: RSA-encrypted session key
+
+    This uses in arguments the generated RSA key pair, and the session key
+    to encrypt.  The latter is manipulated to trigger the overflow.
+
+    This function returns a complete packet encrypted by a session key.
+    """
+
+    # Calculate RSA modulus size in bytes
+    n_bytes = (rsa['n'].bit_length() + 7) // 8
+
+    # Session key message format:
+    # - 1 byte: symmetric cipher algorithm (7 = AES-128)
+    # - N bytes: session key
+    # - 2 bytes: checksum (simple sum of session key bytes)
+    algo_byte = bytes([7])  # AES-128 algorithm identifier
+    cksum = sum(sess_key) & 0xFFFF  # 16-bit checksum
+    M = algo_byte + sess_key + struct.pack('>H', cksum)
+
+    # PKCS#1 v1.5 padding construction
+    # Format: 0x02 || PS || 0x00 || M
+    # Total padded message must be exactly n_bytes long.
+    total_len = n_bytes  # Total length must equal modulus size in bytes
+    ps_len = total_len - len(M) - 2  # Subtract 2 for 0x02 and 0x00 bytes
+
+    if ps_len < 8:
+        raise ValueError(f"Padding string too short ({ps_len} bytes); need at least 8 bytes. "
+                        f"Message length: {len(M)}, Modulus size: {n_bytes} bytes")
+
+    # Create padding string with *ALL* bytes being 0xFF (no zero separator!)
+    PS = bytes([0xFF]) * ps_len
+
+    # Construct the complete padded message
+    # Normal PKCS#1 v1.5 padding: 0x02 || PS || 0x00 || M
+    padded = bytes([0x02]) + PS + bytes([0x00]) + M
+
+    # Verify padding construction
+    if len(padded) != n_bytes:
+        raise ValueError(f"Padded message length ({len(padded)}) doesn't match RSA modulus size ({n_bytes})")
+
+    # Convert padded message to integer and encrypt with RSA
+    m_int = int.from_bytes(padded, 'big')
+
+    # Ensure message is smaller than modulus (required for RSA)
+    if m_int >= rsa['n']:
+        raise ValueError("Padded message is larger than RSA modulus")
+
+    # RSA encryption: c = m^e mod n
+    c_int = pow(m_int, rsa['e'], rsa['n'])
+
+    # Encode encrypted result as MPI
+    c_mpi = mpi_encode(c_int)
+
+    # Build complete packet
+    ver = bytes([3])           # Version 3 packet
+    key_id = b"\x00" * 8      # Key ID (0 = any key accepted)
+    algo = bytes([2])         # RSA encrypt algorithm
+    payload = ver + key_id + algo + c_mpi
+
+    return new_packet(1, payload)
+
+def build_message_data(rsa: dict) -> bytes:
+    """
+    This function creates a crafted message, with a long session key
+    length.
+
+    This takes in input the RSA key components generated previously,
+    returning a concatenated set of PGP packets crafted for the purpose
+    of this test.
+    """
+
+    # Base prefix for session key (AES key + padding + size).
+    # Note that the crafted size is the important part for this test.
+    prefix = AES_KEY + b"\x00" * 16 + p32(0x10)
+
+    # Build encrypted data packet, legit.
+    sedata = build_symenc_data_packet(AES_KEY, cipher_algo=7, payload=b"\x0a\x00")
+
+    # Build multiple packets
+    packets = [
+        # First packet, legit.
+        build_tag1_packet(rsa, prefix),
+
+        # Encrypted data packet, legit.
+        sedata,
+
+        # Second packet: information payload.
+        #
+        # This packet contains a longer-crafted session key, able to trigger
+        # the overflow check in pgcrypto.  This is the critical part, and
+        # and you are right to pay a lot of attention here if you are
+        # reading this code.
+        build_tag1_packet(rsa, prefix)
+    ]
+
+    return b"".join(packets)
+
+def main():
+    # Default key size.
+    # This number can be set to a higher number if wanted, like 4096.  We
+    # just do not need to do that here.
+    key_size = 2048
+
+    # Generate fresh RSA key pair
+    rsa = generate_rsa_keypair(key_size)
+
+    # Generate the message data.
+    print("### Building message data", file=sys.stderr)
+    message_data = build_message_data(rsa)
+
+    # Build the key containing the RSA private key
+    print("### Building key data", file=sys.stderr)
+    key_data = build_key_data(rsa)
+
+    # Convert to hexadecimal, for the bytea used in the SQL file.
+    message_data = message_data.hex()
+    key_data = key_data.hex()
+
+    # Split each value into lines of 72 characters, for readability.
+    message_data = re.sub("(.{72})", "\\1\n", message_data, 0, re.DOTALL)
+    key_data = re.sub("(.{72})", "\\1\n", key_data, 0, re.DOTALL)
+
+    # Get the script filename for documentation
+    file_basename = os.path.basename(__file__)
+
+    # Output the SQL test case
+    print(f'''-- Test for overflow with session key at decrypt.
+-- Data automatically generated by scripts/{file_basename}.
+-- See this file for details explaining how this data is generated.
+SELECT pgp_pub_decrypt_bytea(
+'\\x{message_data}'::bytea,
+'\\x{key_data}'::bytea);''',
+          file=sys.stdout)
+
+if __name__ == "__main__":
+    main()
diff --git a/contrib/pgcrypto/sql/pgp-decrypt.sql b/contrib/pgcrypto/sql/pgp-decrypt.sql
index 49a0267bbcbccfa776d387a82cbe853f1b75aa27..7e7dc189e58ec6f2a7df558151b96b8cbde49540 100644
--- a/contrib/pgcrypto/sql/pgp-decrypt.sql
+++ b/contrib/pgcrypto/sql/pgp-decrypt.sql
@@ -228,7 +228,7 @@ SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
 -----END PGP MESSAGE-----
 '), '0123456789abcdefghij'), 'sha1');
 
-select digest(pgp_sym_decrypt(dearmor('
+select digest(pgp_sym_decrypt_bytea(dearmor('
 -----BEGIN PGP MESSAGE-----
 Comment: dat3.aes.sha1.mdc.s2k3.z0
 
@@ -282,6 +282,27 @@ VsxxqLSPzNLAeIspJk5G
 -- Routine text/binary mismatch.
 select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1');
 
+-- NUL byte in text decrypt.  Ciphertext source:
+-- printf 'a\x00\xc' | gpg --homedir /nonexistent \
+--      --personal-compress-preferences uncompressed --textmode \
+--      --personal-cipher-preferences aes --no-emit-version --batch \
+--      --symmetric --passphrase key --armor
+do $$
+begin
+  perform pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0EBwMCXLc8pozB10Fg0jQBVUID59TLvWutJp0j6eh9ZgjqIRzdYaIymFB8y4XH
+vu0YlJP5D5BX7yqZ+Pry7TlDmiFO
+=rV7z
+-----END PGP MESSAGE-----
+'), 'key', 'debug=1');
+exception when others then
+  raise '%',
+    regexp_replace(sqlerrm, 'encoding "[^"]*"', 'encoding [REDACTED]');
+end
+$$;
+
 -- Decryption with a certain incorrect key yields an apparent BZip2-compressed
 -- plaintext.  Ciphertext source: iterative pgp_sym_encrypt('secret', 'key')
 -- until the random prefix gave rise to that property.
@@ -307,3 +328,29 @@ UCAAw2JRIISttRHMfDpDuZJpvYo=
 =AZ9M
 -----END PGP MESSAGE-----
 '), 'key', 'debug=1');
+
+-- Check ignore-cipher-failure. This message isn't actually encrypted; it was
+-- created with cipher-algo=bf using an OpenSSL that didn't actually support
+-- Blowfish. After the fix for CVE-2026-14663, we no longer create these broken
+-- ciphertexts, but we allow users to return to the previous behavior during
+-- decryption so that the bad wrapper can be stripped.
+--
+-- Note that if Blowfish is supported by the linked OpenSSL, both decryptions
+-- will fail.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key');
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBAMC8wIKbtvzJtxi0jABUleCwFJWGCkYKcsNdABqdtXaU2VjcmV0LtMUlnPH3A2QBmZrcucm
+1GPb/s2Bkdg=
+=6aqD
+-----END PGP MESSAGE-----
+'), 'wrong key', 'ignore-cipher-failure=1');
diff --git a/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql b/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql
index 3f2bae9e40bd473e697da17be9cdea1f73834ab6..40a11e0b2dc7c949fcbcf224bda93315dc5d121a 100644
--- a/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql
+++ b/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql
@@ -601,6 +601,21 @@ blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
 -----END PGP MESSAGE-----
 ');
 
+-- CVE-2026-14663. This message was created with cipher-algo=bf using an OpenSSL
+-- that didn't actually support Blowfish.
+insert into encdata (id, data) values (6, '
+-----BEGIN PGP MESSAGE-----
+
+wcBOA9k2z2S7c/RmEAP8DYbU6AeEo6riMMdnf2G62BM9gC0Z32ODydewy3Ki8AnSzpwBDAHuDMcr
+P6RJDWvBOVOwgxHEwR7ZHMoFRDJEXdo6rQ9dQpDtbasMLyi6Lm1q+PbEefVd9WkU7fvFAFQx8k3t
+lxrlWg/byoNplc7/hFxIFO8bN+FIlLgilAdApNcD/3Mg2/nd7pczovsYoryf9ib04kQ+SVWs3iNE
+StoyEXT+oaT8u1vAxiY7fzPpQX1pnlHBUXn+v1J6LQL5Bwi5CTqOyDSyaFfgU0gQwTReFjS6L4Fs
+Cv+2cFwbJBGIzr1aI4DLbzSelkmVm4hbOVeET4DJVlUVhhIyy6ZfoXiTEG6s0jMB2JdRGIl0EUQR
+RMsQdABqdt5jU2VjcmV0IG1zZ9MUIIP4SPiU2pM/nF/A1hrltMhn/ZI=
+=Mkdj
+-----END PGP MESSAGE-----
+');
+
 -- successful decrypt
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=1 and encdata.id=1;
@@ -608,6 +623,9 @@ from keytbl, encdata where keytbl.id=1 and encdata.id=1;
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=2 and encdata.id=2;
 
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=3 and encdata.id=3;
 
@@ -645,3 +663,12 @@ from keytbl, encdata where keytbl.id=5 and encdata.id=1;
 -- test for a short read from prefix_init
 select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
 from keytbl, encdata where keytbl.id=6 and encdata.id=5;
+
+-- Check that ignore-cipher-failure can strip faulty encryption if OpenSSL
+-- doesn't support the cipher. (The decryption will correctly fail both times if
+-- OpenSSL does support it.)
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
+
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '', 'ignore-cipher-failure=1')
+from keytbl, encdata where keytbl.id=1 and encdata.id=6;
diff --git a/contrib/pgcrypto/sql/pgp-pubkey-session.sql b/contrib/pgcrypto/sql/pgp-pubkey-session.sql
new file mode 100644
index 0000000000000000000000000000000000000000..51792f1f4d8564c3f89fbb5ce81bf01c5ac93173
--- /dev/null
+++ b/contrib/pgcrypto/sql/pgp-pubkey-session.sql
@@ -0,0 +1,46 @@
+-- Test for overflow with session key at decrypt.
+-- Data automatically generated by scripts/pgp_session_data.py.
+-- See this file for details explaining how this data is generated.
+SELECT pgp_pub_decrypt_bytea(
+'\xc1c04c030000000000000000020800a46f5b9b1905b49457a6485474f71ed9b46c2527e1
+da08e1f7871e12c3d38828f2076b984a595bf60f616599ca5729d547de06a258bfbbcd30
+94a321e4668cd43010f0ca8ecf931e5d39bda1152c50c367b11c723f270729245d3ebdbd
+0694d320c5a5aa6a405fb45182acb3d7973cbce398e0c5060af7603cfd9ed186ebadd616
+3b50ae42bea5f6d14dda24e6d4687b434c175084515d562e896742b0ba9a1c87d5642e10
+a5550379c71cc490a052ada483b5d96526c0a600fc51755052aa77fdf72f7b4989b920e7
+b90f4b30787a46482670d5caecc7a515a926055ad5509d135702ce51a0e4c1033f2d939d
+8f0075ec3428e17310da37d3d2d7ad1ce99adcc91cd446c366c402ae1ee38250343a7fcc
+0f8bc28020e603d7a4795ef0dcc1c04c030000000000000000020800a46f5b9b1905b494
+57a6485474f71ed9b46c2527e1da08e1f7871e12c3d38828f2076b984a595bf60f616599
+ca5729d547de06a258bfbbcd3094a321e4668cd43010f0ca8ecf931e5d39bda1152c50c3
+67b11c723f270729245d3ebdbd0694d320c5a5aa6a405fb45182acb3d7973cbce398e0c5
+060af7603cfd9ed186ebadd6163b50ae42bea5f6d14dda24e6d4687b434c175084515d56
+2e896742b0ba9a1c87d5642e10a5550379c71cc490a052ada483b5d96526c0a600fc5175
+5052aa77fdf72f7b4989b920e7b90f4b30787a46482670d5caecc7a515a926055ad5509d
+135702ce51a0e4c1033f2d939d8f0075ec3428e17310da37d3d2d7ad1ce99adc'::bytea,
+'\xc7c2d8046965d657020800eef8bf1515adb1a3ee7825f75c668ea8dd3e3f9d13e958f6ad
+9c55adc0c931a4bb00abe1d52cf7bb0c95d537949d277a5292ede375c6b2a67a3bf7d19f
+f975bb7e7be35c2d8300dacba360a0163567372f7dc24000cc7cb6170bedc8f3b1f98c12
+07a6cb4de870a4bc61319b139dcc0e20c368fd68f8fd346d2c0b69c5aed560504e2ec6f1
+23086fe3c5540dc4dd155c0c67257c4ada862f90fe172ace344089da8135e92aca5c2709
+f1c1bc521798bb8c0365841496e709bd184132d387e0c9d5f26dc00fd06c3a76ef66a75c
+138285038684707a847b7bd33cfbefbf1d336be954a8048946af97a66352adef8e8b5ae4
+c4748c6f2510265b7a8267bc370dbb00110100010007ff7e72d4f95d2d39901ac12ca5c5
+18e767e719e72340c3fab51c8c5ab1c40f31db8eaffe43533fa61e2dbca2c3f4396c0847
+e5434756acbb1f68128f4136bb135710c89137d74538908dac77967de9e821c559700dd9
+de5a2727eec1f5d12d5d74869dd1de45ed369d94a8814d23861dd163f8c27744b26b98f0
+239c2e6dd1e3493b8cc976fdc8f9a5e250f715aa4c3d7d5f237f8ee15d242e8fa941d1a0
+ed9550ab632d992a97518d142802cb0a97b251319bf5742db8d9d8cbaa06cdfba2d75bc9
+9d77a51ff20bd5ba7f15d7af6e85b904de2855d19af08d45f39deb85403033c69c767a8e
+74a343b1d6c8911d34ea441ac3850e57808ed3d885835cbe6c79d10400ef16256f3d5c4c
+3341516a2d2aa888df81b603f48a27f3666b40f992a857c1d11ff639cd764a9b42d5a1f8
+58b4aeee36b85508bb5e8b91ef88a7737770b330224479d9b44eae8c631bc43628b69549
+507c0a1af0be0dd7696015abea722b571eb35eefc4ab95595378ec12814727443f625fcd
+183bb9b3bccf53b54dd0e5e7a50400ffe08537b2d4e6074e4a1727b658cfccdec8962302
+25e300c05690de45f7065c3d40d86f544a64d51a3e94424f9851a16d1322ebdb41fa8a45
+3131f3e2dc94e858e6396722643df382680f815e53bcdcde5da622f50530a83b217f1103
+cdd6e5e9babe1e415bbff28d44bd18c95f43bbd04afeb2a2a99af38a571c7540de21df03
+ff62c0a33d9143dd3f639893f47732c11c5a12c6052d1935f4d507b7ae1f76ab0e9a69b8
+7305a7f7c19bd509daf4903bff614bc26d118f03e461469c72c12d3a2bb4f78e4d342ce8
+487723649a01ed2b9eb11c662134502c098d55dfcd361939d8370873422c3da75a515a75
+9ffedfe7df44fb3c20f81650801a30d43b5c90b98b3eee'::bytea);
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 1d012233cbffe981413bff4382df60ad80b24d0a..a59fb42171ecef50e67505ab749afe7ce3afeec4 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -422,7 +422,7 @@ pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
 		/* fully empty page */
 		stat->free_space += BLCKSZ;
 	}
-	else
+	else if (PageGetSpecialSize(page) == MAXALIGN(sizeof(BTPageOpaqueData)))
 	{
 		BTPageOpaque opaque;
 
@@ -456,10 +456,16 @@ pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
 	Buffer		buf;
 	Page		page;
 
-	buf = _hash_getbuf_with_strategy(rel, blkno, HASH_READ, 0, bstrategy);
+	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
+	LockBuffer(buf, HASH_READ);
 	page = BufferGetPage(buf);
 
-	if (PageGetSpecialSize(page) == MAXALIGN(sizeof(HashPageOpaqueData)))
+	if (PageIsNew(page))
+	{
+		/* fully empty page */
+		stat->free_space += BLCKSZ;
+	}
+	else if (PageGetSpecialSize(page) == MAXALIGN(sizeof(HashPageOpaqueData)))
 	{
 		HashPageOpaque opaque;
 
@@ -500,17 +506,23 @@ pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
 
 	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
 	LockBuffer(buf, GIST_SHARE);
-	gistcheckpage(rel, buf);
 	page = BufferGetPage(buf);
-
-	if (GistPageIsLeaf(page))
+	if (PageIsNew(page))
 	{
-		pgstat_index_page(stat, page, FirstOffsetNumber,
-						  PageGetMaxOffsetNumber(page));
+		/* fully empty page */
+		stat->free_space += BLCKSZ;
 	}
-	else
+	else if (PageGetSpecialSize(page) == MAXALIGN(sizeof(GISTPageOpaqueData)))
 	{
-		/* root or node */
+		if (GistPageIsLeaf(page))
+		{
+			pgstat_index_page(stat, page, FirstOffsetNumber,
+							  PageGetMaxOffsetNumber(page));
+		}
+		else
+		{
+			/* root or node */
+		}
 	}
 
 	UnlockReleaseBuffer(buf);
diff --git a/contrib/postgres_fdw/.gitignore b/contrib/postgres_fdw/.gitignore
index 5dcb3ff9723501c3fe639bee1c1435e47a580a6f..b4903eba657fa1a1cf0e406ff35ce5b9c8ba2972 100644
--- a/contrib/postgres_fdw/.gitignore
+++ b/contrib/postgres_fdw/.gitignore
@@ -1,4 +1,6 @@
 # Generated subdirectories
 /log/
 /results/
+/output_iso/
 /tmp_check/
+/tmp_check_iso/
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index b9fa69930529ba28b0e12413090bb9fa62a4a39b..2a4c87ea7c8cd9e65e4463905529f917383dacc5 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -17,6 +17,8 @@ EXTENSION = postgres_fdw
 DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql
 
 REGRESS = postgres_fdw query_cancel
+ISOLATION = eval_plan_qual
+ISOLATION_OPTS = --load-extension=postgres_fdw
 
 ifdef USE_PGXS
 PG_CONFIG = pg_config
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 2fc52b81e13ddb200021a050d5fb068882f331b5..117792bb4adce39363673f8a21579a91fb27823f 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -596,8 +596,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)
@@ -1244,6 +1245,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)
@@ -1254,15 +1260,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/eval_plan_qual.out b/contrib/postgres_fdw/expected/eval_plan_qual.out
new file mode 100644
index 0000000000000000000000000000000000000000..cee4c6ad1a8adc0966c9656b4907baa8d581a374
--- /dev/null
+++ b/contrib/postgres_fdw/expected/eval_plan_qual.out
@@ -0,0 +1,131 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s0_update_l s1_tuplock_l_0 s0_commit s1_commit
+step s0_update_l: UPDATE l SET i = i + 1;
+step s1_tuplock_l_0: 
+    EXPLAIN (VERBOSE, COSTS OFF)
+    SELECT l.* FROM l, ft WHERE l.i = ft.i AND l.i = 123 FOR UPDATE OF l;
+    SELECT l.* FROM l, ft WHERE l.i = ft.i AND l.i = 123 FOR UPDATE OF l;
+ <waiting ...>
+step s0_commit: COMMIT;
+step s1_tuplock_l_0: <... completed>
+QUERY PLAN                                                           
+---------------------------------------------------------------------
+LockRows                                                             
+  Output: l.i, l.v, l.ctid, ft.*                                     
+  ->  Nested Loop                                                    
+        Output: l.i, l.v, l.ctid, ft.*                               
+        ->  Seq Scan on public.l                                     
+              Output: l.i, l.v, l.ctid                               
+              Filter: (l.i = 123)                                    
+        ->  Foreign Scan on public.ft                                
+              Output: ft.*, ft.i                                     
+              Remote SQL: SELECT i, v FROM public.t WHERE ((i = 123))
+(10 rows)
+
+i|v
+-+-
+(0 rows)
+
+step s1_commit: COMMIT;
+
+starting permutation: s0_update_l s1_tuplock_l_1 s0_commit s1_commit
+step s0_update_l: UPDATE l SET i = i + 1;
+step s1_tuplock_l_1: 
+    EXPLAIN (VERBOSE, COSTS OFF)
+    SELECT l.* FROM l, ft WHERE l.i = ft.i AND l.v = 'foo' FOR UPDATE OF l;
+    SELECT l.* FROM l, ft WHERE l.i = ft.i AND l.v = 'foo' FOR UPDATE OF l;
+ <waiting ...>
+step s0_commit: COMMIT;
+step s1_tuplock_l_1: <... completed>
+QUERY PLAN                                                                   
+-----------------------------------------------------------------------------
+LockRows                                                                     
+  Output: l.i, l.v, l.ctid, ft.*                                             
+  ->  Nested Loop                                                            
+        Output: l.i, l.v, l.ctid, ft.*                                       
+        ->  Seq Scan on public.l                                             
+              Output: l.i, l.v, l.ctid                                       
+              Filter: (l.v = 'foo'::text)                                    
+        ->  Foreign Scan on public.ft                                        
+              Output: ft.*, ft.i                                             
+              Remote SQL: SELECT i, v FROM public.t WHERE ((i = $1::integer))
+(10 rows)
+
+i|v
+-+-
+(0 rows)
+
+step s1_commit: COMMIT;
+
+starting permutation: s0_update_a s1_tuplock_a_0 s0_commit s1_commit
+step s0_update_a: UPDATE a SET i = i + 1;
+step s1_tuplock_a_0: 
+    EXPLAIN (VERBOSE, COSTS OFF)
+    SELECT a.i FROM a, fb, fc WHERE a.i = fb.i AND fb.i = fc.i FOR UPDATE OF a;
+    SELECT a.i FROM a, fb, fc WHERE a.i = fb.i AND fb.i = fc.i FOR UPDATE OF a;
+ <waiting ...>
+step s0_commit: COMMIT;
+step s1_tuplock_a_0: <... completed>
+QUERY PLAN                                                                                                                                                                                                              
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+LockRows                                                                                                                                                                                                                
+  Output: a.i, a.ctid, fb.*, fc.*                                                                                                                                                                                       
+  ->  Nested Loop                                                                                                                                                                                                       
+        Output: a.i, a.ctid, fb.*, fc.*                                                                                                                                                                                 
+        Join Filter: (fb.i = a.i)                                                                                                                                                                                       
+        ->  Foreign Scan                                                                                                                                                                                                
+              Output: fb.*, fb.i, fc.*, fc.i                                                                                                                                                                            
+              Relations: (public.fb) INNER JOIN (public.fc)                                                                                                                                                             
+              Remote SQL: SELECT CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.i) END, r2.i, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.i) END, r3.i FROM (public.b r2 INNER JOIN public.c r3 ON (((r2.i = r3.i))))
+              ->  Nested Loop                                                                                                                                                                                           
+                    Output: fb.*, fb.i, fc.*, fc.i                                                                                                                                                                      
+                    Join Filter: (fb.i = fc.i)                                                                                                                                                                          
+                    ->  Foreign Scan on public.fb                                                                                                                                                                       
+                          Output: fb.*, fb.i                                                                                                                                                                            
+                          Remote SQL: SELECT i FROM public.b ORDER BY i ASC NULLS LAST                                                                                                                                  
+                    ->  Foreign Scan on public.fc                                                                                                                                                                       
+                          Output: fc.*, fc.i                                                                                                                                                                            
+                          Remote SQL: SELECT i FROM public.c                                                                                                                                                            
+        ->  Seq Scan on public.a                                                                                                                                                                                        
+              Output: a.i, a.ctid                                                                                                                                                                                       
+(20 rows)
+
+i
+-
+(0 rows)
+
+step s1_commit: COMMIT;
+
+starting permutation: s0_update_a s1_tuplock_a_1 s0_commit s1_commit
+step s0_update_a: UPDATE a SET i = i + 1;
+step s1_tuplock_a_1: 
+    EXPLAIN (VERBOSE, COSTS OFF)
+    SELECT a.i,
+        (SELECT 1 FROM fb, fc WHERE a.i = fb.i AND fb.i = fc.i)
+    FROM a FOR UPDATE;
+    SELECT a.i,
+        (SELECT 1 FROM fb, fc WHERE a.i = fb.i AND fb.i = fc.i)
+    FROM a FOR UPDATE;
+ <waiting ...>
+step s0_commit: COMMIT;
+step s1_tuplock_a_1: <... completed>
+QUERY PLAN                                                                                                                              
+----------------------------------------------------------------------------------------------------------------------------------------
+LockRows                                                                                                                                
+  Output: a.i, ((SubPlan 1)), a.ctid                                                                                                    
+  ->  Seq Scan on public.a                                                                                                              
+        Output: a.i, (SubPlan 1), a.ctid                                                                                                
+        SubPlan 1                                                                                                                       
+          ->  Foreign Scan                                                                                                              
+                Output: 1                                                                                                               
+                Relations: (public.fb) INNER JOIN (public.fc)                                                                           
+                Remote SQL: SELECT NULL FROM (public.b r1 INNER JOIN public.c r2 ON (((r2.i = $1::integer)) AND ((r1.i = $1::integer))))
+(9 rows)
+
+i|?column?
+-+--------
+2|        
+(1 row)
+
+step s1_commit: COMMIT;
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 602067c4e474f4a729d4e3749c6c470f5fd75304..86dbf74d64f0445a82c3b45ad7d8414f15f2447b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -6292,20 +6292,31 @@ UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
 ALTER SERVER loopback OPTIONS (DROP extensions);
 INSERT INTO ft2 (c1,c2,c3)
   SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id;
+-- this will do a remote seqscan, causing unstable result order, so sort
 EXPLAIN (verbose, costs off)
-UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;            -- can't be pushed down
-                                                QUERY PLAN                                                
-----------------------------------------------------------------------------------------------------------
- Update on public.ft2
-   Output: c1, c2, c3, c4, c5, c6, c7, c8
-   Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
-   ->  Foreign Scan on public.ft2
-         Output: 'bar'::text, ctid, ft2.*
-         Filter: (postgres_fdw_abs(ft2.c1) > 2000)
-         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE
-(7 rows)
+WITH cte AS (
+  UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *
+) SELECT * FROM cte ORDER BY c1;          -- can't be pushed down
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: cte.c1, cte.c2, cte.c3, cte.c4, cte.c5, cte.c6, cte.c7, cte.c8
+   Sort Key: cte.c1
+   CTE cte
+     ->  Update on public.ft2
+           Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8
+           Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
+           ->  Foreign Scan on public.ft2
+                 Output: 'bar'::text, ft2.ctid, ft2.*
+                 Filter: (postgres_fdw_abs(ft2.c1) > 2000)
+                 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE
+   ->  CTE Scan on cte
+         Output: cte.c1, cte.c2, cte.c3, cte.c4, cte.c5, cte.c6, cte.c7, cte.c8
+(13 rows)
 
-UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;
+WITH cte AS (
+  UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *
+) SELECT * FROM cte ORDER BY c1;
   c1  | c2 | c3  | c4 | c5 | c6 |     c7     | c8 
 ------+----+-----+----+----+----+------------+----
  2001 |  1 | bar |    |    |    | ft2        | 
@@ -8012,6 +8023,119 @@ DELETE FROM rem1;                 -- can't be pushed down
 (5 rows)
 
 DROP TRIGGER trig_row_after_delete ON rem1;
+-- We are allowed to create transition-table triggers on both kinds of
+-- inheritance even if they contain foreign tables as children, but currently
+-- collecting transition tuples from such foreign tables is not supported.
+CREATE TABLE local_tbl (a text, b int);
+CREATE FOREIGN TABLE foreign_tbl (a text, b int)
+  SERVER loopback OPTIONS (table_name 'local_tbl');
+INSERT INTO foreign_tbl VALUES ('AAA', 42);
+-- Test case for partition hierarchy
+CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
+ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
+CREATE TRIGGER parent_tbl_insert_trig
+  AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+CREATE TRIGGER parent_tbl_update_trig
+  AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+CREATE TRIGGER parent_tbl_delete_trig
+  AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+INSERT INTO parent_tbl VALUES ('AAA', 42);
+ERROR:  cannot collect transition tuples from child foreign tables
+COPY parent_tbl (a, b) FROM stdin;
+ERROR:  cannot collect transition tuples from child foreign tables
+CONTEXT:  COPY parent_tbl, line 1: "AAA	42"
+ALTER SERVER loopback OPTIONS (ADD batch_size '10');
+INSERT INTO parent_tbl VALUES ('AAA', 42);
+ERROR:  cannot collect transition tuples from child foreign tables
+COPY parent_tbl (a, b) FROM stdin;
+ERROR:  cannot collect transition tuples from child foreign tables
+CONTEXT:  COPY parent_tbl, line 1: "AAA	42"
+ALTER SERVER loopback OPTIONS (DROP batch_size);
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE parent_tbl SET b = b + 1;
+                                           QUERY PLAN                                           
+------------------------------------------------------------------------------------------------
+ Update on public.parent_tbl
+   Foreign Update on public.foreign_tbl parent_tbl_1
+     Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
+   ->  Foreign Scan on public.foreign_tbl parent_tbl_1
+         Output: (parent_tbl_1.b + 1), parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.*
+         Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
+(6 rows)
+
+UPDATE parent_tbl SET b = b + 1;
+ERROR:  cannot collect transition tuples from child foreign tables
+EXPLAIN (VERBOSE, COSTS OFF)
+DELETE FROM parent_tbl;
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Delete on public.parent_tbl
+   Foreign Delete on public.foreign_tbl parent_tbl_1
+     Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
+   ->  Foreign Scan on public.foreign_tbl parent_tbl_1
+         Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
+         Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
+(6 rows)
+
+DELETE FROM parent_tbl;
+ERROR:  cannot collect transition tuples from child foreign tables
+ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
+DROP TABLE parent_tbl;
+-- Test case for non-partition hierarchy
+CREATE TABLE parent_tbl (a text, b int);
+ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
+CREATE TRIGGER parent_tbl_update_trig
+  AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+CREATE TRIGGER parent_tbl_delete_trig
+  AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE parent_tbl SET b = b + 1;
+                                              QUERY PLAN                                              
+------------------------------------------------------------------------------------------------------
+ Update on public.parent_tbl
+   Update on public.parent_tbl parent_tbl_1
+   Foreign Update on public.foreign_tbl parent_tbl_2
+     Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
+   ->  Result
+         Output: (parent_tbl.b + 1), parent_tbl.tableoid, parent_tbl.ctid, (NULL::record)
+         ->  Append
+               ->  Seq Scan on public.parent_tbl parent_tbl_1
+                     Output: parent_tbl_1.b, parent_tbl_1.tableoid, parent_tbl_1.ctid, NULL::record
+               ->  Foreign Scan on public.foreign_tbl parent_tbl_2
+                     Output: parent_tbl_2.b, parent_tbl_2.tableoid, parent_tbl_2.ctid, parent_tbl_2.*
+                     Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
+(12 rows)
+
+UPDATE parent_tbl SET b = b + 1;
+ERROR:  cannot collect transition tuples from child foreign tables
+EXPLAIN (VERBOSE, COSTS OFF)
+DELETE FROM parent_tbl;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Delete on public.parent_tbl
+   Delete on public.parent_tbl parent_tbl_1
+   Foreign Delete on public.foreign_tbl parent_tbl_2
+     Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
+   ->  Append
+         ->  Seq Scan on public.parent_tbl parent_tbl_1
+               Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
+         ->  Foreign Scan on public.foreign_tbl parent_tbl_2
+               Output: parent_tbl_2.tableoid, parent_tbl_2.ctid
+               Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
+(10 rows)
+
+DELETE FROM parent_tbl;
+ERROR:  cannot collect transition tuples from child foreign tables
+ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
+DROP TABLE parent_tbl;
+-- Cleanup
+DROP FOREIGN TABLE foreign_tbl;
+DROP TABLE local_tbl;
 -- ===================================================================
 -- test inheritance features
 -- ===================================================================
@@ -11268,6 +11392,72 @@ SELECT * FROM result_tbl ORDER BY a;
 (3 rows)
 
 DELETE FROM result_tbl;
+-- Test ExecAppendAsyncReset code path that drains outstanding async requests
+-- (case where subplans are re-scanned with parameter changes)
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT o.x FROM (VALUES (2505), (3505)) o(x), LATERAL (SELECT a FROM async_pt WHERE a = o.x OR a = 1505 LIMIT 1) s ORDER BY o.x;
+                                                    QUERY PLAN                                                     
+-------------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: "*VALUES*".column1
+   Sort Key: "*VALUES*".column1
+   ->  Nested Loop
+         Output: "*VALUES*".column1
+         ->  Values Scan on "*VALUES*"
+               Output: "*VALUES*".column1
+         ->  Limit
+               Output: NULL::integer
+               ->  Append
+                     ->  Async Foreign Scan on public.async_p1 async_pt_1
+                           Output: NULL::integer
+                           Remote SQL: SELECT NULL FROM public.base_tbl1 WHERE (((a = $1::integer) OR (a = 1505)))
+                     ->  Async Foreign Scan on public.async_p2 async_pt_2
+                           Output: NULL::integer
+                           Remote SQL: SELECT NULL FROM public.base_tbl2 WHERE (((a = $1::integer) OR (a = 1505)))
+                     ->  Async Foreign Scan on public.async_p3 async_pt_3
+                           Output: NULL::integer
+                           Remote SQL: SELECT NULL FROM public.base_tbl3 WHERE (((a = $1::integer) OR (a = 1505)))
+(19 rows)
+
+SELECT o.x FROM (VALUES (2505), (3505)) o(x), LATERAL (SELECT a FROM async_pt WHERE a = o.x OR a = 1505 LIMIT 1) s ORDER BY o.x;
+  x   
+------
+ 2505
+ 3505
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT o.x FROM (VALUES (2505), (3505)) o(x), LATERAL (SELECT a FROM async_pt WHERE a = o.x OR (a = 1505 AND o.x = 2505) LIMIT 1) s ORDER BY o.x;
+                                                                  QUERY PLAN                                                                  
+----------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: "*VALUES*".column1
+   Sort Key: "*VALUES*".column1
+   ->  Nested Loop
+         Output: "*VALUES*".column1
+         ->  Values Scan on "*VALUES*"
+               Output: "*VALUES*".column1
+         ->  Limit
+               Output: NULL::integer
+               ->  Append
+                     ->  Async Foreign Scan on public.async_p1 async_pt_1
+                           Output: NULL::integer
+                           Remote SQL: SELECT NULL FROM public.base_tbl1 WHERE (((a = $1::integer) OR ((a = 1505) AND ($1::integer = 2505))))
+                     ->  Async Foreign Scan on public.async_p2 async_pt_2
+                           Output: NULL::integer
+                           Remote SQL: SELECT NULL FROM public.base_tbl2 WHERE (((a = $1::integer) OR ((a = 1505) AND ($1::integer = 2505))))
+                     ->  Async Foreign Scan on public.async_p3 async_pt_3
+                           Output: NULL::integer
+                           Remote SQL: SELECT NULL FROM public.base_tbl3 WHERE (((a = $1::integer) OR ((a = 1505) AND ($1::integer = 2505))))
+(19 rows)
+
+SELECT o.x FROM (VALUES (2505), (3505)) o(x), LATERAL (SELECT a FROM async_pt WHERE a = o.x OR (a = 1505 AND o.x = 2505) LIMIT 1) s ORDER BY o.x;
+  x   
+------
+ 2505
+ 3505
+(2 rows)
+
 DROP FOREIGN TABLE async_p3;
 DROP TABLE base_tbl3;
 -- Check case where the partitioned table has local/remote partitions
@@ -12048,8 +12238,8 @@ DROP TABLE base_tbl1;
 DROP TABLE base_tbl2;
 DROP TABLE result_tbl;
 DROP TABLE join_tbl;
--- Test that an asynchronous fetch is processed before restarting the scan in
--- ReScanForeignScan
+-- Test ExecAppendAsyncReset code path that drains outstanding async requests
+-- (case where subplans are re-scanned without parameter changes)
 CREATE TABLE base_tbl (a int, b int);
 INSERT INTO base_tbl VALUES (1, 11), (2, 22), (3, 33);
 CREATE FOREIGN TABLE foreign_tbl (b int)
@@ -12308,7 +12498,7 @@ ALTER SERVER loopback2 OPTIONS (DROP parallel_abort);
 -- ===================================================================
 CREATE TABLE analyze_table (id int, a text, b bigint);
 CREATE FOREIGN TABLE analyze_ftable (id int, a text, b bigint)
-       SERVER loopback OPTIONS (table_name 'analyze_rtable1');
+       SERVER loopback OPTIONS (table_name 'analyze_table');
 INSERT INTO analyze_table (SELECT x FROM generate_series(1,1000) x);
 ANALYZE analyze_table;
 SET default_statistics_target = 10;
@@ -12316,15 +12506,91 @@ ANALYZE analyze_table;
 ALTER SERVER loopback OPTIONS (analyze_sampling 'invalid');
 ERROR:  invalid value for string option "analyze_sampling": invalid
 ALTER SERVER loopback OPTIONS (analyze_sampling 'auto');
-ANALYZE analyze_table;
+ANALYZE analyze_ftable;
 ALTER SERVER loopback OPTIONS (SET analyze_sampling 'system');
-ANALYZE analyze_table;
+ANALYZE analyze_ftable;
 ALTER SERVER loopback OPTIONS (SET analyze_sampling 'bernoulli');
-ANALYZE analyze_table;
+ANALYZE analyze_ftable;
 ALTER SERVER loopback OPTIONS (SET analyze_sampling 'random');
-ANALYZE analyze_table;
+ANALYZE analyze_ftable;
 ALTER SERVER loopback OPTIONS (SET analyze_sampling 'off');
-ANALYZE analyze_table;
+ANALYZE analyze_ftable;
 -- cleanup
 DROP FOREIGN TABLE analyze_ftable;
 DROP TABLE analyze_table;
+-- ===================================================================
+-- 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/meson.build b/contrib/postgres_fdw/meson.build
index f0803ee077ee669f8744778b568c605333cbf474..98ef27cd5e01712749afd25151f71aa8af34d596 100644
--- a/contrib/postgres_fdw/meson.build
+++ b/contrib/postgres_fdw/meson.build
@@ -40,4 +40,10 @@ tests += {
     ],
     'regress_args': ['--dlpath', meson.build_root() / 'src/test/regress'],
   },
+  'isolation': {
+    'specs': [
+      'eval_plan_qual',
+    ],
+    'regress_args': ['--load-extension=postgres_fdw'],
+  },
 }
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 7c4b91e01fd727f03ffaf28df2be4707bdff69ea..0377dbb27049c8135fa7f917867886442309bf10 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -239,6 +239,7 @@ typedef struct PgFdwDirectModifyState
 	PGresult   *result;			/* result for query */
 	int			num_tuples;		/* # of result tuples */
 	int			next_tuple;		/* index of next one to return */
+	MemoryContextCallback result_cb;	/* ensures result will get freed */
 	Relation	resultRel;		/* relcache entry for the target relation */
 	AttrNumber *attnoMap;		/* array of attnums of input user columns */
 	AttrNumber	ctidAttno;		/* attnum of input ctid column */
@@ -1649,16 +1650,11 @@ postgresReScanForeignScan(ForeignScanState *node)
 		return;
 
 	/*
-	 * If the node is async-capable, and an asynchronous fetch for it has
-	 * begun, the asynchronous fetch might not have yet completed.  Check if
-	 * the node is async-capable, and an asynchronous fetch for it is still in
-	 * progress; if so, complete the asynchronous fetch before restarting the
-	 * scan.
+	 * If the node is async-capable, any asynchronous fetch made for it should
+	 * have been processed before we get here (see ExecAppendAsyncReset()).
 	 */
-	if (fsstate->async_capable &&
-		fsstate->conn_state->pendingAreq &&
-		fsstate->conn_state->pendingAreq->requestee == (PlanState *) node)
-		fetch_more_data(node);
+	Assert(!fsstate->async_capable || !fsstate->conn_state->pendingAreq ||
+		   fsstate->conn_state->pendingAreq->requestee != (PlanState *) node);
 
 	/*
 	 * If any internal parameters affecting this node have changed, we'd
@@ -2662,6 +2658,17 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
 	dmstate = (PgFdwDirectModifyState *) palloc0(sizeof(PgFdwDirectModifyState));
 	node->fdw_state = (void *) dmstate;
 
+	/*
+	 * We use a memory context callback to ensure that the dmstate's PGresult
+	 * (if any) will be released, even if the query fails somewhere that's
+	 * outside our control.  The callback is always armed for the duration of
+	 * the query; this relies on PQclear(NULL) being a no-op.
+	 */
+	dmstate->result_cb.func = (MemoryContextCallbackFunction) PQclear;
+	dmstate->result_cb.arg = NULL;
+	MemoryContextRegisterResetCallback(CurrentMemoryContext,
+									   &dmstate->result_cb);
+
 	/*
 	 * Identify which user to do the remote access as.  This should match what
 	 * ExecCheckPermissions() does.
@@ -2809,7 +2816,13 @@ postgresEndDirectModify(ForeignScanState *node)
 		return;
 
 	/* Release PGresult */
-	PQclear(dmstate->result);
+	if (dmstate->result)
+	{
+		PQclear(dmstate->result);
+		dmstate->result = NULL;
+		/* ... and don't forget to disable the callback */
+		dmstate->result_cb.arg = NULL;
+	}
 
 	/* Release remote connection */
 	ReleaseConnection(dmstate->conn);
@@ -4578,13 +4591,17 @@ execute_dml_stmt(ForeignScanState *node)
 	/*
 	 * Get the result, and check for success.
 	 *
-	 * We don't use a PG_TRY block here, so be careful not to throw error
-	 * without releasing the PGresult.
+	 * We use a memory context callback to ensure that the PGresult will be
+	 * released, even if the query fails somewhere that's outside our control.
+	 * The callback is already registered, just need to fill in its arg.
 	 */
+	Assert(dmstate->result == NULL);
 	dmstate->result = pgfdw_get_result(dmstate->conn);
+	dmstate->result_cb.arg = dmstate->result;
+
 	if (PQresultStatus(dmstate->result) !=
 		(dmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
-		pgfdw_report_error(ERROR, dmstate->result, dmstate->conn, true,
+		pgfdw_report_error(ERROR, dmstate->result, dmstate->conn, false,
 						   dmstate->query);
 
 	/* Get the number of rows affected. */
@@ -4628,30 +4645,16 @@ get_returning_data(ForeignScanState *node)
 	}
 	else
 	{
-		/*
-		 * On error, be sure to release the PGresult on the way out.  Callers
-		 * do not have PG_TRY blocks to ensure this happens.
-		 */
-		PG_TRY();
-		{
-			HeapTuple	newtup;
-
-			newtup = make_tuple_from_result_row(dmstate->result,
-												dmstate->next_tuple,
-												dmstate->rel,
-												dmstate->attinmeta,
-												dmstate->retrieved_attrs,
-												node,
-												dmstate->temp_cxt);
-			ExecStoreHeapTuple(newtup, slot, false);
-		}
-		PG_CATCH();
-		{
-			PQclear(dmstate->result);
-			PG_RE_THROW();
-		}
-		PG_END_TRY();
+		HeapTuple	newtup;
 
+		newtup = make_tuple_from_result_row(dmstate->result,
+											dmstate->next_tuple,
+											dmstate->rel,
+											dmstate->attinmeta,
+											dmstate->retrieved_attrs,
+											node,
+											dmstate->temp_cxt);
+		ExecStoreHeapTuple(newtup, slot, false);
 		/* Get the updated/deleted tuple. */
 		if (dmstate->rel)
 			resultSlot = slot;
diff --git a/contrib/postgres_fdw/specs/eval_plan_qual.spec b/contrib/postgres_fdw/specs/eval_plan_qual.spec
new file mode 100644
index 0000000000000000000000000000000000000000..9f52270db6984981a1e5e8960820cd0a5443ccc2
--- /dev/null
+++ b/contrib/postgres_fdw/specs/eval_plan_qual.spec
@@ -0,0 +1,102 @@
+# Tests for the EvalPlanQual mechanism involving foreign tables
+
+setup
+{
+    DO $d$
+        BEGIN
+            EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw
+                OPTIONS (dbname '$$||current_database()||$$',
+                         port '$$||current_setting('port')||$$',
+                         use_remote_estimate 'true'
+                )$$;
+        END;
+    $d$;
+    CREATE USER MAPPING FOR PUBLIC SERVER loopback;
+
+    CREATE TABLE l (i int, v text);
+    CREATE TABLE t (i int, v text);
+    CREATE FOREIGN TABLE ft (i int, v text) SERVER loopback OPTIONS (table_name 't');
+
+    INSERT INTO l VALUES (123, 'foo'), (456, 'bar'), (789, 'baz');
+    INSERT INTO t SELECT i, to_char(i, 'FM0000') FROM generate_series(1, 1000) i;
+    CREATE INDEX t_idx ON t (i);
+    ANALYZE l, t;
+
+    CREATE TABLE a (i int);
+    CREATE TABLE b (i int);
+    CREATE TABLE c (i int);
+    CREATE FOREIGN TABLE fb (i int) SERVER loopback OPTIONS (table_name 'b');
+    CREATE FOREIGN TABLE fc (i int) SERVER loopback OPTIONS (table_name 'c');
+
+    INSERT INTO a VALUES (1);
+    INSERT INTO b VALUES (1);
+    INSERT INTO c VALUES (1);
+    ANALYZE a, b, c;
+}
+
+teardown
+{
+    DROP TABLE l;
+    DROP TABLE t;
+    DROP TABLE a;
+    DROP TABLE b;
+    DROP TABLE c;
+    DROP SERVER loopback CASCADE;
+}
+
+session s0
+setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step s0_update_l { UPDATE l SET i = i + 1; }
+step s0_update_a { UPDATE a SET i = i + 1; }
+step s0_commit { COMMIT; }
+
+session s1
+setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
+
+# Test for EPQ with a foreign scan pushing down a qual
+step s1_tuplock_l_0 {
+    EXPLAIN (VERBOSE, COSTS OFF)
+    SELECT l.* FROM l, ft WHERE l.i = ft.i AND l.i = 123 FOR UPDATE OF l;
+    SELECT l.* FROM l, ft WHERE l.i = ft.i AND l.i = 123 FOR UPDATE OF l;
+}
+
+# Same test, except that the qual is parameterized
+step s1_tuplock_l_1 {
+    EXPLAIN (VERBOSE, COSTS OFF)
+    SELECT l.* FROM l, ft WHERE l.i = ft.i AND l.v = 'foo' FOR UPDATE OF l;
+    SELECT l.* FROM l, ft WHERE l.i = ft.i AND l.v = 'foo' FOR UPDATE OF l;
+}
+
+# Test for EPQ with a foreign scan pushing down a join
+step s1_tuplock_a_0 {
+    EXPLAIN (VERBOSE, COSTS OFF)
+    SELECT a.i FROM a, fb, fc WHERE a.i = fb.i AND fb.i = fc.i FOR UPDATE OF a;
+    SELECT a.i FROM a, fb, fc WHERE a.i = fb.i AND fb.i = fc.i FOR UPDATE OF a;
+}
+
+# Same test, except that the join is contained in a SubLink sub-select, not
+# in the main query
+step s1_tuplock_a_1 {
+    EXPLAIN (VERBOSE, COSTS OFF)
+    SELECT a.i,
+        (SELECT 1 FROM fb, fc WHERE a.i = fb.i AND fb.i = fc.i)
+    FROM a FOR UPDATE;
+    SELECT a.i,
+        (SELECT 1 FROM fb, fc WHERE a.i = fb.i AND fb.i = fc.i)
+    FROM a FOR UPDATE;
+}
+
+step s1_commit { COMMIT; }
+
+# This test checks the case of rechecking a pushed-down qual.
+permutation s0_update_l s1_tuplock_l_0 s0_commit s1_commit
+
+# This test checks the same case, except that the qual is parameterized.
+permutation s0_update_l s1_tuplock_l_1 s0_commit s1_commit
+
+# This test checks the case of rechecking a pushed-down join.
+permutation s0_update_a s1_tuplock_a_0 s0_commit s1_commit
+
+# This test exercises EvalPlanQual with a SubLink sub-select (which should
+# be unaffected by any EPQ recheck behavior in the outer query).
+permutation s0_update_a s1_tuplock_a_1 s0_commit s1_commit
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 8acfb78f471cb010f49cd6dc7c29f4d2c504caaa..f25975a8ba8145fb7dee5bbcbcc830bcf1908bd7 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -1572,9 +1572,16 @@ UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
 ALTER SERVER loopback OPTIONS (DROP extensions);
 INSERT INTO ft2 (c1,c2,c3)
   SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id;
+
+-- this will do a remote seqscan, causing unstable result order, so sort
 EXPLAIN (verbose, costs off)
-UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;            -- can't be pushed down
-UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;
+WITH cte AS (
+  UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *
+) SELECT * FROM cte ORDER BY c1;          -- can't be pushed down
+WITH cte AS (
+  UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *
+) SELECT * FROM cte ORDER BY c1;
+
 EXPLAIN (verbose, costs off)
 UPDATE ft2 SET c3 = 'baz'
   FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
@@ -2233,6 +2240,84 @@ EXPLAIN (verbose, costs off)
 DELETE FROM rem1;                 -- can't be pushed down
 DROP TRIGGER trig_row_after_delete ON rem1;
 
+
+-- We are allowed to create transition-table triggers on both kinds of
+-- inheritance even if they contain foreign tables as children, but currently
+-- collecting transition tuples from such foreign tables is not supported.
+
+CREATE TABLE local_tbl (a text, b int);
+CREATE FOREIGN TABLE foreign_tbl (a text, b int)
+  SERVER loopback OPTIONS (table_name 'local_tbl');
+
+INSERT INTO foreign_tbl VALUES ('AAA', 42);
+
+-- Test case for partition hierarchy
+CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a);
+ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA');
+
+CREATE TRIGGER parent_tbl_insert_trig
+  AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+CREATE TRIGGER parent_tbl_update_trig
+  AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+CREATE TRIGGER parent_tbl_delete_trig
+  AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+
+INSERT INTO parent_tbl VALUES ('AAA', 42);
+
+COPY parent_tbl (a, b) FROM stdin;
+AAA	42
+\.
+
+ALTER SERVER loopback OPTIONS (ADD batch_size '10');
+
+INSERT INTO parent_tbl VALUES ('AAA', 42);
+
+COPY parent_tbl (a, b) FROM stdin;
+AAA	42
+\.
+
+ALTER SERVER loopback OPTIONS (DROP batch_size);
+
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE parent_tbl SET b = b + 1;
+UPDATE parent_tbl SET b = b + 1;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+DELETE FROM parent_tbl;
+DELETE FROM parent_tbl;
+
+ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl;
+DROP TABLE parent_tbl;
+
+-- Test case for non-partition hierarchy
+CREATE TABLE parent_tbl (a text, b int);
+ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl;
+
+CREATE TRIGGER parent_tbl_update_trig
+  AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+CREATE TRIGGER parent_tbl_delete_trig
+  AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table
+  FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+
+EXPLAIN (VERBOSE, COSTS OFF)
+UPDATE parent_tbl SET b = b + 1;
+UPDATE parent_tbl SET b = b + 1;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+DELETE FROM parent_tbl;
+DELETE FROM parent_tbl;
+
+ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl;
+DROP TABLE parent_tbl;
+
+-- Cleanup
+DROP FOREIGN TABLE foreign_tbl;
+DROP TABLE local_tbl;
+
 -- ===================================================================
 -- test inheritance features
 -- ===================================================================
@@ -3826,6 +3911,16 @@ INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505;
 SELECT * FROM result_tbl ORDER BY a;
 DELETE FROM result_tbl;
 
+-- Test ExecAppendAsyncReset code path that drains outstanding async requests
+-- (case where subplans are re-scanned with parameter changes)
+EXPLAIN (VERBOSE,