patches/neon-postgres-17.11.patch
Rama main · réplica de lectura del repositorio original.
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,