diff --git a/.abi-compliance-history b/.abi-compliance-history new file mode 100644 index 00000000000..a4bf3336cac --- /dev/null +++ b/.abi-compliance-history @@ -0,0 +1,94 @@ +# 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# " $ABIBREAKGITHASH -1 --date=iso +# +# Be sure to replace "" with details of your change and +# why it is deemed acceptable. + +8d9a97e0bb6d820dac553848f0d5d8cc3f3e219d +# +# Avoid name collision with NOT NULL constraints +# 2026-02-21 12:22:08 +0100 +# +# AddRelationNotNullConstraints() needs to receive the names of constraints +# already created, so that it can avoid those names when assigning names to +# not-null constraints. The purpose of this function is rather obscure, and +# furthermore it's new in 18, so I don't expect any third-party code to break. +# +# Discussion: https://postgr.es/m/19393-6a82427485a744cf@postgresql.org + +33e3de6d77e87d6c3c6f8f878dd8de42d37c3b8f +# +# Add file_extend_method=posix_fallocate,write_zeros. +# 2026-02-06 17:38:39 +1300 +# +# Modifying GUC tables isn't really an ABI break: the relevant object has +# incomplete type so its layout is unknown to other C translation units. +# +# Discussion: https://www.postgresql.org/message-id/flat/e1f0cd3b-0164-45f5-9705-e922e59df90f%40dunslane.net#8a350b54012c0042f9869d288e978cfe + +492a69e1407029f8c673484f44aa719a63323d77 +# +# Reject ADD CONSTRAINT NOT NULL if name mismatches existing constraint +# 2026-02-03 12:33:29 +0100 +# +# This commit added the constraint name as a parameter to +# AdjustNotNullInheritance(), which it needed to verify that the new name +# matches the existing one. PGXN contained no calls to that function. + +c6ce4dcf9d3b7a8a89aca386124c473f98fc329e +# +# Fix trigger transition table capture for MERGE in CTE queries. +# 2026-01-24 11:30:48 +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. + +bae8ca82fd00603ebafa0658640d6e4dfe20af92 +# +# Revisit cosmetics of "For inplace update, send nontransactional invalidations." +# 2025-12-15 12:19:53 -0800 +# +# This removed a CacheInvalidateHeapTupleInplace() parameter. PGXN contained +# no calls to that function. + +00eb646ea43410e5df77fed96f4a981e66811796 +# +# 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. + +c8af5019bee5c57502db830f8005a01cba60fee0 +# +# Fix lookups in pg_{clear,restore}_{attribute,relation}_stats(). +# 2025-10-15 12:47:33 -0500 +# +# This commit replaced two functions related to lookups/privilege checks for +# the new stats stuff in v18 with RangeVarGetRelidExtended(). These functions +# were not intended for use elsewhere, exist in exactly one release (18.0), and +# do not have any known third-party callers. + +9bbcec6030a2744d83311370ec92213fbd76e514 +# +# Translation updates +# 2025-09-22 14:18:56 +0200 +# +# This is the original ABI baseline point for REL_18_STABLE. diff --git a/.cirrus.star b/.cirrus.star index d7a07e0ca7d..4b71ee92e49 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,15 +55,77 @@ def main(): else: output += "\n# REPO_CI_CONFIG_URL was not set\n" - # Add 3) + # Add 4) # output += config_from(".cirrus.tasks.yml") # PR-only format check output += config_from(".cirrus.format.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', 'netbsd', 'openbsd'] + + 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', + 'netbsd', + 'openbsd', + '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 92057006c93..1f32c53bf83 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -72,13 +72,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" @@ -104,6 +104,7 @@ task: configure_script: | su postgres <<-EOF + set -e meson setup \ --buildtype=debug \ --auto-features=disabled \ @@ -112,6 +113,7 @@ task: EOF build_script: | su postgres <<-EOF + set -e ninja -C build -j${BUILD_JOBS} ${MBUILD_TARGET} EOF upload_caches: ccache @@ -121,6 +123,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} \ @@ -167,7 +170,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 @@ -195,6 +198,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 \ @@ -207,6 +211,7 @@ task: test_world_script: | su postgres <<-EOF + set -e ulimit -c unlimited meson test $MTEST_ARGS --num-processes ${TEST_JOBS} EOF @@ -231,6 +236,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 @@ -239,7 +245,6 @@ task: task: depends_on: SanityCheck - trigger_type: manual env: # Below are experimentally derived to be a decent choice. @@ -257,7 +262,9 @@ task: matrix: - name: NetBSD - Meson - only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*netbsd.*' + # See REPO_CI_AUTOMATIC_TRIGGER_TASKS in .cirrus.star + trigger_type: $CI_TRIGGER_TYPE_NETBSD + only_if: $CI_NETBSD_ENABLED env: OS_NAME: netbsd IMAGE_FAMILY: pg-ci-netbsd-postgres @@ -274,13 +281,16 @@ task: <<: *netbsd_task_template - name: OpenBSD - Meson - only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*openbsd.*' + # See REPO_CI_AUTOMATIC_TRIGGER_TASKS in .cirrus.star + trigger_type: $CI_TRIGGER_TYPE_OPENBSD + only_if: $CI_OPENBSD_ENABLED env: OS_NAME: openbsd IMAGE_FAMILY: pg-ci-openbsd-postgres PKGCONFIG_PATH: '/usr/lib/pkgconfig:/usr/local/lib/pkgconfig' UUID: -Duuid=e2fs TCL: -Dtcl_version=tcl86 + CORE_DUMP_EXECUTABLE_DIR: $CIRRUS_WORKING_DIR/build/tmp_install/usr/local/pgsql/bin setup_additional_packages_script: | #pkg_add -I ... # Always core dump to ${CORE_DUMP_DIR} @@ -313,6 +323,7 @@ task: # And other uuid options are not available on NetBSD. configure_script: | su postgres <<-EOF + set -e meson setup \ --buildtype=debugoptimized \ --pkg-config-path ${PKGCONFIG_PATH} \ @@ -327,10 +338,8 @@ task: test_world_script: | su postgres <<-EOF + set -e ulimit -c unlimited - # Otherwise tests will fail on OpenBSD, due to inability to start enough - # processes. - ulimit -p 256 meson test $MTEST_ARGS --num-processes ${TEST_JOBS} EOF @@ -341,7 +350,7 @@ task: # ${CORE_DUMP_DIR}, they may not obey this. So, move core files to the # ${CORE_DUMP_DIR} directory. find build/ -type f -name '*.core' -exec mv '{}' ${CORE_DUMP_DIR} \; - src/tools/ci/cores_backtrace.sh ${OS_NAME} ${CORE_DUMP_DIR} + src/tools/ci/cores_backtrace.sh ${OS_NAME} ${CORE_DUMP_DIR} ${CORE_DUMP_EXECUTABLE_DIR} # configure feature flags, shared between the task running the linux tests and @@ -376,7 +385,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" @@ -397,7 +406,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS @@ -405,8 +414,6 @@ task: LDFLAGS: $SANITIZER_FLAGS CC: ccache gcc CXX: ccache g++ - # GCC emits a warning for llvm-14, so switch to a newer one. - LLVM_CONFIG: llvm-config-16 LINUX_CONFIGURE_FEATURES: *LINUX_CONFIGURE_FEATURES LINUX_MESON_FEATURES: *LINUX_MESON_FEATURES @@ -414,7 +421,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} @@ -453,7 +460,7 @@ task: # - Uses address sanitizer, sanitizer failures are typically printed in # the server log # - Configures postgres with a small segment size - - name: Linux - Debian Bookworm - Autoconf + - name: Linux - Debian Trixie - Autoconf env: SANITIZER_FLAGS: -fsanitize=address @@ -467,6 +474,7 @@ task: # that. configure_script: | su postgres <<-EOF + set -e ./configure \ --enable-cassert --enable-injection-points --enable-debug \ --enable-tap-tests --enable-nls \ @@ -476,13 +484,14 @@ task: \ ${LINUX_CONFIGURE_FEATURES} \ \ - CLANG="ccache clang-16" + CLANG="ccache clang" EOF build_script: su postgres -c "make -s -j${BUILD_JOBS} world-bin" upload_caches: ccache test_world_script: | su postgres <<-EOF + set -e ulimit -c unlimited # default is 0 make -s ${CHECK} ${CHECKFLAGS} -j${TEST_JOBS} EOF @@ -495,7 +504,7 @@ task: # are typically printed in the server log # - Test both 64bit and 32 bit builds # - uses io_method=io_uring - - name: Linux - Debian Bookworm - Meson + - name: Linux - Debian Trixie - Meson env: CCACHE_MAXSIZE: "400M" # tests two different builds @@ -505,6 +514,7 @@ task: configure_script: | su postgres <<-EOF + set -e meson setup \ --buildtype=debug \ -Dcassert=true -Dinjection_points=true \ @@ -516,6 +526,7 @@ task: # locally. configure_32_script: | su postgres <<-EOF + set -e export CC='ccache gcc -m32' meson setup \ --buildtype=debug \ @@ -523,19 +534,21 @@ 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 \ -Dlibnuma=disabled \ build-32 EOF build_script: | su postgres <<-EOF + set -e ninja -C build -j${BUILD_JOBS} ${MBUILD_TARGET} ninja -C build -t missingdeps EOF build_32_script: | su postgres <<-EOF + set -e ninja -C build-32 -j${BUILD_JOBS} ${MBUILD_TARGET} ninja -C build -t missingdeps EOF @@ -544,6 +557,7 @@ task: test_world_script: | su postgres <<-EOF + set -e ulimit -c unlimited meson test $MTEST_ARGS --num-processes ${TEST_JOBS} EOF @@ -556,6 +570,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 @@ -573,16 +588,16 @@ task: # SPECIAL: # - Enables --clone for pg_upgrade and pg_combinebackup 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 @@ -613,7 +628,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 @@ -701,7 +716,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: @@ -719,7 +734,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 ... @@ -730,10 +745,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% 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% build build_script: | vcvarsall x64 @@ -753,15 +767,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 @@ -815,15 +827,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 @@ -833,9 +844,6 @@ task: LINUX_CONFIGURE_FEATURES: *LINUX_CONFIGURE_FEATURES LINUX_MESON_FEATURES: *LINUX_MESON_FEATURES - # GCC emits a warning for llvm-14, so switch to a newer one. - LLVM_CONFIG: llvm-config-16 - <<: *linux_task_template sysinfo_script: | @@ -871,7 +879,7 @@ task: --cache gcc.cache \ --enable-dtrace \ ${LINUX_CONFIGURE_FEATURES} \ - CC="ccache gcc" CXX="ccache g++" CLANG="ccache clang-16" + CC="ccache gcc" CXX="ccache g++" CLANG="ccache clang" make -s -j${BUILD_JOBS} clean time make -s -j${BUILD_JOBS} world-bin @@ -882,7 +890,7 @@ task: --cache gcc.cache \ --enable-cassert \ ${LINUX_CONFIGURE_FEATURES} \ - CC="ccache gcc" CXX="ccache g++" CLANG="ccache clang-16" + CC="ccache gcc" CXX="ccache g++" CLANG="ccache clang" make -s -j${BUILD_JOBS} clean time make -s -j${BUILD_JOBS} world-bin @@ -892,7 +900,7 @@ task: time ./configure \ --cache clang.cache \ ${LINUX_CONFIGURE_FEATURES} \ - CC="ccache clang" CXX="ccache clang++-16" CLANG="ccache clang-16" + CC="ccache clang" CXX="ccache clang++" CLANG="ccache clang" make -s -j${BUILD_JOBS} clean time make -s -j${BUILD_JOBS} world-bin @@ -904,7 +912,7 @@ task: --enable-cassert \ --enable-dtrace \ ${LINUX_CONFIGURE_FEATURES} \ - CC="ccache clang" CXX="ccache clang++-16" CLANG="ccache clang-16" + CC="ccache clang" CXX="ccache clang++" CLANG="ccache clang" make -s -j${BUILD_JOBS} clean time make -s -j${BUILD_JOBS} world-bin @@ -912,11 +920,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 @@ -928,7 +936,7 @@ task: docs_build_script: | time ./configure \ --cache gcc.cache \ - CC="ccache gcc" CXX="ccache g++" CLANG="ccache clang-16" + CC="ccache gcc" CXX="ccache g++" CLANG="ccache clang" make -s -j${BUILD_JOBS} clean time make -s -j${BUILD_JOBS} -C doc @@ -938,16 +946,13 @@ 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-16" + CC="gcc" CXX"=g++" CLANG="clang" make -s -j${BUILD_JOBS} clean time make -s headerscheck EXTRAFLAGS='-fmax-errors=10' headers_cpluspluscheck_script: | diff --git a/.cirrus.yml b/.cirrus.yml index 33c6e481d74..3f75852e84e 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/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 00000000000..170e20de21e --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +# Minimal configuration for getting started +language: "en-US" +reviews: + profile: "chill" + high_level_summary: true + auto_review: + enabled: true + drafts: false + base_branches: ["master", "IVORY_REL_5_STABLE"] diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 8048afd1a80..88aa34ab4b2 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -14,6 +14,15 @@ # # $ git log --pretty=format:"%H # %cd%n# %s" $PGINDENTGITHASH -1 --date=iso +2795f5a5428fd292996fd155f16a57b0db3df8f4 # 2025-10-21 09:56:26 -0500 +# Re-pgindent brin.c. + +17a5ca58eb119a33e81e57b72618236538932167 # 2025-09-13 14:50:02 -0500 +# Re-pgindent nbtpreprocesskeys.c after commit 796962922e. + +07448b3969d55a2081cdafafc23f68df3392f220 # 2025-07-01 15:24:19 +0200 +# Fix indentation in pg_numa code + b27644bade0348d0dafd3036c47880a349fe9332 # 2025-06-15 13:04:24 -0400 # Sync typedefs.list with the buildfarm. diff --git a/.gitattributes b/.gitattributes index 8df6b75e653..4e26bbfb145 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,8 +12,8 @@ *.xsl 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/.github/workflows/build.yml b/.github/workflows/build.yml index ffddd94454f..9cb6073522f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,9 +2,9 @@ name: build on: push: - branches: [ master , IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] + branches: [ master , IVORY_REL_5_STABLE, IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] pull_request: - branches: [ master , IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] + branches: [ master , IVORY_REL_5_STABLE, IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] jobs: build: @@ -19,6 +19,9 @@ jobs: if: ${{ matrix.os == 'ubuntu-latest' }} run: | sudo apt-get update + sudo apt-get install -y gcc-14 g++-14 + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100 sudo apt-get install -y build-essential git lcov bison flex \ libkrb5-dev libssl-dev libldap-dev libpam-dev python3-dev \ tcl-dev libperl-dev gettext libxml2-dev libxslt-dev \ @@ -35,4 +38,4 @@ jobs: --with-ossp-uuid --with-libxml --with-libxslt --with-perl \ --with-icu --with-libnuma --enable-injection-points - name: compile - run: make + run: make CFLAGS="$CFLAGS -Wshadow=compatible-local -Werror=missing-variable-declarations -Werror=maybe-uninitialized -Werror=unused-value -Werror=unused-but-set-variable -Werror=missing-prototypes -Werror=unused-variable" diff --git a/.github/workflows/contrib_regression.yml b/.github/workflows/contrib_regression.yml index 953880c7ca2..fc12c1b7815 100644 --- a/.github/workflows/contrib_regression.yml +++ b/.github/workflows/contrib_regression.yml @@ -2,9 +2,9 @@ name: contrib_regression on: push: - branches: [ master , IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] + branches: [ master , IVORY_REL_5_STABLE, IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] pull_request: - branches: [ master , IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] + branches: [ master , IVORY_REL_5_STABLE, IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] jobs: contrib_regression: @@ -51,4 +51,3 @@ jobs: with: name: results path: ${{ github.workspace }}/pg_regression.tar.gz - diff --git a/.github/workflows/meson_build.yml b/.github/workflows/meson_build.yml index fe13e1dbccb..63c192ba388 100644 --- a/.github/workflows/meson_build.yml +++ b/.github/workflows/meson_build.yml @@ -2,9 +2,9 @@ name: meson_build on: push: - branches: [ master , IVORY_REL_4_STABLE ] + branches: [ master , IVORY_REL_5_STABLE, IVORY_REL_4_STABLE ] pull_request: - branches: [ master , IVORY_REL_4_STABLE ] + branches: [ master , IVORY_REL_5_STABLE, IVORY_REL_4_STABLE ] jobs: meson_build: diff --git a/.github/workflows/oracle_pg_regression.yml b/.github/workflows/oracle_pg_regression.yml index 9a8ca6084d8..8dcf711ed03 100644 --- a/.github/workflows/oracle_pg_regression.yml +++ b/.github/workflows/oracle_pg_regression.yml @@ -2,9 +2,9 @@ name: oracle_pg_regression on: push: - branches: [ master , IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] + branches: [ master , IVORY_REL_5_STABLE, IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] pull_request: - branches: [ master , IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] + branches: [ master , IVORY_REL_5_STABLE, IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] jobs: oracle_pg_regression: @@ -58,4 +58,3 @@ jobs: with: name: results path: ${{ github.workspace }}/oracle_pg_regression.tar.gz - diff --git a/.github/workflows/oracle_regression.yml b/.github/workflows/oracle_regression.yml index 6efd612f06d..44f0c23feeb 100644 --- a/.github/workflows/oracle_regression.yml +++ b/.github/workflows/oracle_regression.yml @@ -2,9 +2,9 @@ name: oracle_regression on: push: - branches: [ master , IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] + branches: [ master , IVORY_REL_5_STABLE, IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] pull_request: - branches: [ master , IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] + branches: [ master , IVORY_REL_5_STABLE, IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] jobs: oracle_regression: @@ -55,4 +55,3 @@ jobs: with: name: results path: ${{ github.workspace }}/oracle_regression.tar.gz - diff --git a/.github/workflows/pg_regression.yml b/.github/workflows/pg_regression.yml index c8423df1210..4e305e197a7 100644 --- a/.github/workflows/pg_regression.yml +++ b/.github/workflows/pg_regression.yml @@ -2,9 +2,9 @@ name: pg_regression on: push: - branches: [ master , IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] + branches: [ master , IVORY_REL_5_STABLE, IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] pull_request: - branches: [ master , IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] + branches: [ master , IVORY_REL_5_STABLE, IVORY_REL_4_STABLE , IVORYSQL_REL_1_STABLE] jobs: pg_regression: @@ -55,4 +55,3 @@ jobs: with: name: results path: ${{ github.workspace }}/pg_regression.tar.gz - diff --git a/COPYRIGHT b/COPYRIGHT index 3b79b1b4ca0..0a397648dcd 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,7 +1,7 @@ PostgreSQL Database Management System (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/GNUmakefile.in b/GNUmakefile.in index a279aecacc3..96fb275d62e 100644 --- a/GNUmakefile.in +++ b/GNUmakefile.in @@ -158,7 +158,11 @@ headerscheck: submake-generated-headers cpluspluscheck: submake-generated-headers $(top_srcdir)/src/tools/pginclude/headerscheck --cplusplus $(top_srcdir) $(abs_top_builddir) +# ivorysql code format +code-format: submake-generated-headers + @bash tools/enable-git-hooks.sh + #.PHONY: dist distdir distcheck docs install-docs world check-world install-world installcheck-world headerscheck cpluspluscheck -.PHONY: dist distcheck docs install-docs world check-world install-world installcheck-world headerscheck cpluspluscheck oracle-pg-check-world oracle-check-world oracle-installcheck-world all-check all-check-world all-installcheck-world +.PHONY: dist distcheck docs install-docs world check-world install-world installcheck-world headerscheck cpluspluscheck oracle-pg-check-world oracle-check-world oracle-installcheck-world all-check all-check-world all-installcheck-world code-format diff --git a/Makefile b/Makefile index 977ffe37491..ffa50b5548b 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ all check oracle-check all-check install installdirs installcheck oracle-install @if [ ! -f GNUmakefile ] ; then \ echo "You need to run the 'configure' program first. Please see"; \ - echo "" ; \ + echo "" ; \ false ; \ fi @IFS=':' ; \ @@ -44,7 +44,3 @@ all check oracle-check all-check install installdirs installcheck oracle-install false; \ fi - -.PHONY: enable-git-hooks -enable-git-hooks: - @bash tools/enable-git-hooks.sh diff --git a/README.md b/README.md index d7da90170fe..52d253ce9e9 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,8 @@ Furthermore, for more detailed installation instructions, please refer to the [I - [Source code installation](https://docs.ivorysql.org/en/ivorysql-doc/v4.5/v4.5/6#Source-code-installation) ## Developer Formatting hooks and CI: -- A pre-commit formatting hook is provided at `.githooks/pre-commit`. Enable it with `git config core.hooksPath .githooks`, or run `make enable-git-hooks` (equivalently `bash tools/enable-git-hooks.sh`). -- The hook depends only on in-tree tools `src/tools/pgindent` and `src/tools/pg_bsd_indent`. On commit it formats staged C/C++ files with pgindent and re-adds them to the index. +- A pre-commit formatting hook is provided at `.githooks/pre-commit`. Enable it with `git config core.hooksPath .githooks`, or run `make code-format` (equivalently `bash tools/enable-git-hooks.sh`). +- The hook depends only on in-tree tools `src/tools/pgindent` and `src/tools/pg_bsd_indent`. On commit it formats staged C/C++ files with pgindent and re-adds them to the staged area. - A Cirrus workflow `FormatCheck` runs `pgindent --check` on files changed in a PR. ## Contributing to the IvorySQL diff --git a/README_CN.md b/README_CN.md index 3dffb0fff01..5fad9cc4b9c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -18,7 +18,7 @@ IvorySQL 项目采用 Apache 2.0 许可协议发布,并鼓励各种形式的 ## 开发者代码格式化 - 提交前自动格式化(推荐): - - 已克隆仓库:在仓库根目录执行 `make enable-git-hooks`(或 `bash tools/enable-git-hooks.sh`) + - 已克隆仓库:在仓库根目录执行 `make code-format`(或 `bash tools/enable-git-hooks.sh`) - 提交时行为:Git 钩子会自动用 `pgindent` 格式化已暂存的 C/C++ 文件并回加到暂存区,未通过二次校验会阻止提交。 - PR 阶段:Cirrus 将运行 `FormatCheck`(pgindent --check)对差异文件做只读校验。 diff --git a/config/c-compiler.m4 b/config/c-compiler.m4 index da40bd6a647..8de232ec050 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 @@ -38,6 +38,42 @@ ac_c_werror_flag=$ac_save_c_werror_flag ])# PGAC_TEST_PRINTF_ARCHETYPE +# 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 fa4bedd9370..9d6fe8199e3 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/config/programs.m4 b/config/programs.m4 index 0ad1e58b48d..e57fe4907b8 100644 --- a/config/programs.m4 +++ b/config/programs.m4 @@ -284,20 +284,26 @@ AC_DEFUN([PGAC_CHECK_STRIP], AC_DEFUN([PGAC_CHECK_LIBCURL], [ + # libcurl compiler/linker flags are kept separate from the global flags, so + # they have to be added back temporarily for the following tests. + pgac_save_CPPFLAGS=$CPPFLAGS + pgac_save_LDFLAGS=$LDFLAGS + pgac_save_LIBS=$LIBS + + CPPFLAGS="$CPPFLAGS $LIBCURL_CPPFLAGS" + LDFLAGS="$LDFLAGS $LIBCURL_LDFLAGS" + AC_CHECK_HEADER(curl/curl.h, [], [AC_MSG_ERROR([header file is required for --with-libcurl])]) + + # LIBCURL_LDLIBS is determined here. Like the compiler flags, it should not + # pollute the global LIBS setting. AC_CHECK_LIB(curl, curl_multi_init, [ AC_DEFINE([HAVE_LIBCURL], [1], [Define to 1 if you have the `curl' library (-lcurl).]) AC_SUBST(LIBCURL_LDLIBS, -lcurl) ], [AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])]) - pgac_save_CPPFLAGS=$CPPFLAGS - pgac_save_LDFLAGS=$LDFLAGS - pgac_save_LIBS=$LIBS - - CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS" - LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS" LIBS="$LIBCURL_LDLIBS $LIBS" # Check to see whether the current platform supports threadsafe Curl diff --git a/configure b/configure index 66dc47aea4a..d2343c8d800 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 18beta1. +# Generated by GNU Autoconf 2.69 for PostgreSQL 18.4. # # Report bugs to . # @@ -11,7 +11,7 @@ # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. # -# Portions Copyright (c) 2023-2025, IvorySQL Global Development Team +# Portions Copyright (c) 2023-2026, IvorySQL Global Development Team # Copyright (c) 1996-2025, PostgreSQL Global Development Group ## -------------------- ## ## M4sh Initialization. ## @@ -583,8 +583,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='PostgreSQL' PACKAGE_TARNAME='postgresql' -PACKAGE_VERSION='18beta1' -PACKAGE_STRING='PostgreSQL 18beta1' +PACKAGE_VERSION='18.4' +PACKAGE_STRING='PostgreSQL 18.4' PACKAGE_BUGREPORT='pgsql-bugs@lists.postgresql.org' PACKAGE_URL='https://www.postgresql.org/' @@ -1473,7 +1473,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 18beta1 to adapt to many kinds of systems. +\`configure' configures PostgreSQL 18.4 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1538,7 +1538,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of PostgreSQL 18beta1:";; + short | recursive ) echo "Configuration of PostgreSQL 18.4:";; esac cat <<\_ACEOF @@ -1733,7 +1733,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -PostgreSQL configure 18beta1 +PostgreSQL configure 18.4 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2486,7 +2486,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 18beta1, which was +It was created by PostgreSQL $as_me 18.4, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -5342,7 +5342,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 @@ -9584,12 +9584,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 @@ -9814,12 +9814,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 @@ -9955,12 +9955,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 @@ -12865,6 +12865,15 @@ fi if test "$with_libcurl" = yes ; then + # libcurl compiler/linker flags are kept separate from the global flags, so + # they have to be added back temporarily for the following tests. + pgac_save_CPPFLAGS=$CPPFLAGS + pgac_save_LDFLAGS=$LDFLAGS + pgac_save_LIBS=$LIBS + + CPPFLAGS="$CPPFLAGS $LIBCURL_CPPFLAGS" + LDFLAGS="$LDFLAGS $LIBCURL_LDFLAGS" + ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default" if test "x$ac_cv_header_curl_curl_h" = xyes; then : @@ -12873,6 +12882,9 @@ else fi + + # LIBCURL_LDLIBS is determined here. Like the compiler flags, it should not + # pollute the global LIBS setting. { $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_multi_init in -lcurl" >&5 $as_echo_n "checking for curl_multi_init in -lcurl... " >&6; } if ${ac_cv_lib_curl_curl_multi_init+:} false; then : @@ -12922,12 +12934,6 @@ else fi - pgac_save_CPPFLAGS=$CPPFLAGS - pgac_save_LDFLAGS=$LDFLAGS - pgac_save_LIBS=$LIBS - - CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS" - LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS" LIBS="$LIBCURL_LDLIBS $LIBS" # Check to see whether the current platform supports threadsafe Curl @@ -13457,6 +13463,23 @@ fi fi +if test "$with_liburing" = yes; then + _LIBS="$LIBS" + LIBS="$LIBURING_LIBS $LIBS" + for ac_func in io_uring_queue_init_mem +do : + ac_fn_c_check_func "$LINENO" "io_uring_queue_init_mem" "ac_cv_func_io_uring_queue_init_mem" +if test "x$ac_cv_func_io_uring_queue_init_mem" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_IO_URING_QUEUE_INIT_MEM 1 +_ACEOF + +fi +done + + LIBS="$_LIBS" +fi + if test "$with_lz4" = yes ; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for LZ4_compress_default in -llz4" >&5 $as_echo_n "checking for LZ4_compress_default in -llz4... " >&6; } @@ -14962,8 +14985,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 @@ -15023,7 +15046,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 @@ -16783,7 +16896,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" @@ -17690,7 +17803,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 : @@ -17723,77 +17836,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 +#include 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 +#include 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 @@ -17805,7 +17920,7 @@ int main () { unsigned int exx[4] = {0, 0, 0, 0}; - __get_cpuidex(exx[0], 7, 0); + __cpuidex(exx, 7, 0); ; return 0; @@ -17821,10 +17936,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 @@ -19005,7 +19121,7 @@ Use --without-tcl to disable building PL/Tcl." "$LINENO" 5 fi # now that we have TCL_INCLUDE_SPEC, we can check for 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 : @@ -19074,7 +19190,7 @@ fi # check for 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 : @@ -19618,9 +19734,15 @@ else cc_string=$CC fi +# IvorySQL version +PACKAGE_IVORYSQL_VERSION='5.4' + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_IVORYSQL_VERSION "$PACKAGE_IVORYSQL_VERSION" +_ACEOF cat >>confdefs.h <<_ACEOF -#define PG_VERSION_STR "PostgreSQL $PG_VERSION on $host, compiled by $cc_string, `expr $ac_cv_sizeof_void_p \* 8`-bit" +#define PG_VERSION_STR "PostgreSQL $PG_VERSION (IvorySQL $PACKAGE_IVORYSQL_VERSION) on $host, compiled by $cc_string, `expr $ac_cv_sizeof_void_p \* 8`-bit" _ACEOF @@ -20214,7 +20336,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 18beta1, which was +This file was extended by PostgreSQL $as_me 18.4, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -20285,7 +20407,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 18beta1 +PostgreSQL config.status 18.4 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 2138df308f0..66f9e2254b5 100644 --- a/configure.ac +++ b/configure.ac @@ -17,18 +17,22 @@ dnl Read the Autoconf manual for details. dnl m4_pattern_forbid(^PGAC_)dnl to catch undefined macros -AC_INIT([PostgreSQL], [18beta1], [pgsql-bugs@lists.postgresql.org], [], [https://www.postgresql.org/]) +AC_INIT([PostgreSQL], [18.4], [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 recommended. You can remove the check from 'configure.ac' but it is then your responsibility whether the result works or not.])]) AC_COPYRIGHT([Copyright (c) 1996-2025, PostgreSQL Global Development Group]) +AC_COPYRIGHT([Portions Copyright (c), 2023-2025, IvorySQL Global Development Team]) AC_CONFIG_SRCDIR([src/backend/access/common/heaptuple.c]) AC_CONFIG_AUX_DIR(config) AC_PREFIX_DEFAULT(/usr/local/pgsql) AC_DEFINE_UNQUOTED(CONFIGURE_ARGS, ["$ac_configure_args"], [Saved arguments from configure]) +# IvorySQL version +PACKAGE_IVORYSQL_VERSION='5.4' + [PG_MAJORVERSION=`expr "$PACKAGE_VERSION" : '\([0-9][0-9]*\)'`] [PG_MINORVERSION=`expr "$PACKAGE_VERSION" : '.*\.\([0-9][0-9]*\)'`] test -n "$PG_MINORVERSION" || PG_MINORVERSION=0 @@ -36,6 +40,7 @@ AC_SUBST(PG_MAJORVERSION) AC_DEFINE_UNQUOTED(PG_MAJORVERSION, "$PG_MAJORVERSION", [PostgreSQL major version as a string]) AC_DEFINE_UNQUOTED(PG_MAJORVERSION_NUM, $PG_MAJORVERSION, [PostgreSQL major version number]) AC_DEFINE_UNQUOTED(PG_MINORVERSION_NUM, $PG_MINORVERSION, [PostgreSQL minor version number]) +AC_DEFINE_UNQUOTED([PACKAGE_IVORYSQL_VERSION], "$PACKAGE_IVORYSQL_VERSION" , [IvorySQL version number]) PGAC_ARG_REQ(with, extra-version, [STRING], [append STRING to version], [PG_VERSION="$PACKAGE_VERSION$withval"], @@ -1186,12 +1191,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 @@ -1235,12 +1240,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 @@ -1260,12 +1265,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 @@ -1503,6 +1508,13 @@ if test "$with_libxslt" = yes ; then AC_CHECK_LIB(xslt, xsltCleanupGlobals, [], [AC_MSG_ERROR([library 'xslt' is required for XSLT support])]) fi +if test "$with_liburing" = yes; then + _LIBS="$LIBS" + LIBS="$LIBURING_LIBS $LIBS" + AC_CHECK_FUNCS([io_uring_queue_init_mem]) + LIBS="$_LIBS" +fi + if test "$with_lz4" = yes ; then AC_CHECK_LIB(lz4, LZ4_compress_default, [], [AC_MSG_ERROR([library 'lz4' is required for LZ4 support])]) fi @@ -1757,6 +1769,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 @@ -2020,7 +2033,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, [], @@ -2120,7 +2133,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 ], [[unsigned int exx[4] = {0, 0, 0, 0}; @@ -2130,8 +2143,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 ], + [[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 ], [[unsigned int exx[4] = {0, 0, 0, 0}; @@ -2141,28 +2167,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 ], - [[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 ], - [[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 ], + [[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 @@ -2424,7 +2440,7 @@ Use --without-tcl to disable building PL/Tcl.]) fi # now that we have TCL_INCLUDE_SPEC, we can check for ac_save_CPPFLAGS=$CPPFLAGS - CPPFLAGS="$TCL_INCLUDE_SPEC $CPPFLAGS" + CPPFLAGS="$CPPFLAGS $TCL_INCLUDE_SPEC" AC_CHECK_HEADER(tcl.h, [], [AC_MSG_ERROR([header file is required for Tcl])]) CPPFLAGS=$ac_save_CPPFLAGS fi @@ -2461,7 +2477,7 @@ fi # check for 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 is required for Python])]) CPPFLAGS=$ac_save_CPPFLAGS fi @@ -2543,7 +2559,7 @@ else fi AC_DEFINE_UNQUOTED(PG_VERSION_STR, - ["PostgreSQL $PG_VERSION on $host, compiled by $cc_string, `expr $ac_cv_sizeof_void_p \* 8`-bit"], + ["PostgreSQL $PG_VERSION (IvorySQL $PACKAGE_IVORYSQL_VERSION) on $host, compiled by $cc_string, `expr $ac_cv_sizeof_void_p \* 8`-bit"], [A string containing the version number, platform, and C compiler]) # Supply a numeric version string for use by 3rd party add-ons diff --git a/contrib/amcheck/t/002_cic.pl b/contrib/amcheck/t/002_cic.pl index 6a0c4f61125..983d0b3cbdd 100644 --- a/contrib/amcheck/t/002_cic.pl +++ b/contrib/amcheck/t/002_cic.pl @@ -64,5 +64,29 @@ ) }); +# 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 +my $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 f11c43a0ed7..3de1c06c7cf 100644 --- a/contrib/amcheck/verify_nbtree.c +++ b/contrib/amcheck/verify_nbtree.c @@ -92,9 +92,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; /* @@ -382,7 +384,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\"", @@ -433,54 +434,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); @@ -555,13 +548,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 */ @@ -569,16 +561,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 @@ -602,14 +593,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); } @@ -721,7 +709,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\"", @@ -2270,7 +2258,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; diff --git a/contrib/auto_explain/Makefile b/contrib/auto_explain/Makefile index efd127d3cae..32355b12db6 100644 --- a/contrib/auto_explain/Makefile +++ b/contrib/auto_explain/Makefile @@ -6,6 +6,9 @@ OBJS = \ auto_explain.o PGFILEDESC = "auto_explain - logging facility for execution plans" +REGRESS = alter_reset +ORA_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 00000000000..ec355189806 --- /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 92dc9df6f7c..a9b45cc235f 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 00000000000..bf621454ec2 --- /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 4a8b8c7ac29..0eec948e002 100644 --- a/contrib/basic_archive/basic_archive.c +++ b/contrib/basic_archive/basic_archive.c @@ -90,13 +90,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 @@ -115,17 +113,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/Makefile b/contrib/btree_gist/Makefile index 68190ac5e46..7ac2df26c10 100644 --- a/contrib/btree_gist/Makefile +++ b/contrib/btree_gist/Makefile @@ -34,7 +34,7 @@ DATA = btree_gist--1.0--1.1.sql \ btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql \ btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \ btree_gist--1.5--1.6.sql btree_gist--1.6--1.7.sql \ - btree_gist--1.7--1.8.sql btree_gist--1.8--1.9.sql + btree_gist--1.7--1.8.sql PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes" REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \ diff --git a/contrib/btree_gist/btree_gist--1.7--1.8.sql b/contrib/btree_gist/btree_gist--1.7--1.8.sql index 8f79365a461..22316dc3f56 100644 --- a/contrib/btree_gist/btree_gist--1.7--1.8.sql +++ b/contrib/btree_gist/btree_gist--1.7--1.8.sql @@ -3,6 +3,203 @@ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.8'" to load this file. \quit +-- Add sortsupport functions + +CREATE FUNCTION gbt_bit_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_varbit_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_bool_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_bytea_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_cash_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_date_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_enum_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_float4_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_float8_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_inet_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_int2_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_int4_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_int8_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_intv_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_macaddr_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_macad8_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_numeric_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_oid_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_text_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_bpchar_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_time_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_ts_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +CREATE FUNCTION gbt_uuid_sortsupport(internal) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; + +ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD + FUNCTION 11 (bit, bit) gbt_bit_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD + FUNCTION 11 (varbit, varbit) gbt_varbit_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_bool_ops USING gist ADD + FUNCTION 11 (bool, bool) gbt_bool_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD + FUNCTION 11 (bytea, bytea) gbt_bytea_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD + FUNCTION 11 (money, money) gbt_cash_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_date_ops USING gist ADD + FUNCTION 11 (date, date) gbt_date_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_enum_ops USING gist ADD + FUNCTION 11 (anyenum, anyenum) gbt_enum_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD + FUNCTION 11 (float4, float4) gbt_float4_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD + FUNCTION 11 (float8, float8) gbt_float8_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD + FUNCTION 11 (inet, inet) gbt_inet_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD + FUNCTION 11 (cidr, cidr) gbt_inet_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD + FUNCTION 11 (int2, int2) gbt_int2_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD + FUNCTION 11 (int4, int4) gbt_int4_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD + FUNCTION 11 (int8, int8) gbt_int8_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD + FUNCTION 11 (interval, interval) gbt_intv_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD + FUNCTION 11 (macaddr, macaddr) gbt_macaddr_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_macaddr8_ops USING gist ADD + FUNCTION 11 (macaddr8, macaddr8) gbt_macad8_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD + FUNCTION 11 (numeric, numeric) gbt_numeric_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD + FUNCTION 11 (oid, oid) gbt_oid_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_text_ops USING gist ADD + FUNCTION 11 (text, text) gbt_text_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD + FUNCTION 11 (bpchar, bpchar) gbt_bpchar_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_time_ops USING gist ADD + FUNCTION 11 (time, time) gbt_time_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD + FUNCTION 11 (timetz, timetz) gbt_time_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD + FUNCTION 11 (timestamp, timestamp) gbt_ts_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD + FUNCTION 11 (timestamptz, timestamptz) gbt_ts_sortsupport (internal) ; + +ALTER OPERATOR FAMILY gist_uuid_ops USING gist ADD + FUNCTION 11 (uuid, uuid) gbt_uuid_sortsupport (internal) ; + +-- Add translate_cmptype functions + CREATE FUNCTION gist_translate_cmptype_btree(int) RETURNS smallint AS 'MODULE_PATHNAME' diff --git a/contrib/btree_gist/btree_gist--1.8--1.9.sql b/contrib/btree_gist/btree_gist--1.8--1.9.sql deleted file mode 100644 index 4b38749bf5f..00000000000 --- a/contrib/btree_gist/btree_gist--1.8--1.9.sql +++ /dev/null @@ -1,197 +0,0 @@ -/* contrib/btree_gist/btree_gist--1.7--1.8.sql */ - --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.9'" to load this file. \quit - -CREATE FUNCTION gbt_bit_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_varbit_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_bool_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_bytea_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_cash_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_date_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_enum_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_float4_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_float8_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_inet_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_int2_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_int4_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_int8_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_intv_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_macaddr_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_macad8_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_numeric_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_oid_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_text_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_bpchar_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_time_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_ts_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -CREATE FUNCTION gbt_uuid_sortsupport(internal) -RETURNS void -AS 'MODULE_PATHNAME' -LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT; - -ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD - FUNCTION 11 (bit, bit) gbt_bit_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD - FUNCTION 11 (varbit, varbit) gbt_varbit_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_bool_ops USING gist ADD - FUNCTION 11 (bool, bool) gbt_bool_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD - FUNCTION 11 (bytea, bytea) gbt_bytea_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD - FUNCTION 11 (money, money) gbt_cash_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_date_ops USING gist ADD - FUNCTION 11 (date, date) gbt_date_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_enum_ops USING gist ADD - FUNCTION 11 (anyenum, anyenum) gbt_enum_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD - FUNCTION 11 (float4, float4) gbt_float4_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD - FUNCTION 11 (float8, float8) gbt_float8_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD - FUNCTION 11 (inet, inet) gbt_inet_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD - FUNCTION 11 (cidr, cidr) gbt_inet_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD - FUNCTION 11 (int2, int2) gbt_int2_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD - FUNCTION 11 (int4, int4) gbt_int4_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD - FUNCTION 11 (int8, int8) gbt_int8_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD - FUNCTION 11 (interval, interval) gbt_intv_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD - FUNCTION 11 (macaddr, macaddr) gbt_macaddr_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_macaddr8_ops USING gist ADD - FUNCTION 11 (macaddr8, macaddr8) gbt_macad8_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD - FUNCTION 11 (numeric, numeric) gbt_numeric_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD - FUNCTION 11 (oid, oid) gbt_oid_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_text_ops USING gist ADD - FUNCTION 11 (text, text) gbt_text_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD - FUNCTION 11 (bpchar, bpchar) gbt_bpchar_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_time_ops USING gist ADD - FUNCTION 11 (time, time) gbt_time_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD - FUNCTION 11 (timetz, timetz) gbt_time_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD - FUNCTION 11 (timestamp, timestamp) gbt_ts_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD - FUNCTION 11 (timestamptz, timestamptz) gbt_ts_sortsupport (internal) ; - -ALTER OPERATOR FAMILY gist_uuid_ops USING gist ADD - FUNCTION 11 (uuid, uuid) gbt_uuid_sortsupport (internal) ; diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control index 69d9341a0ad..abf66538f32 100644 --- a/contrib/btree_gist/btree_gist.control +++ b/contrib/btree_gist/btree_gist.control @@ -1,6 +1,6 @@ # btree_gist extension comment = 'support for indexing common datatypes in GiST' -default_version = '1.9' +default_version = '1.8' module_pathname = '$libdir/btree_gist' relocatable = true trusted = true diff --git a/contrib/btree_gist/btree_utils_var.c b/contrib/btree_gist/btree_utils_var.c index d9df2356cd1..96cc08f9054 100644 --- a/contrib/btree_gist/btree_utils_var.c +++ b/contrib/btree_gist/btree_utils_var.c @@ -115,36 +115,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 { @@ -154,7 +165,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 */ diff --git a/contrib/btree_gist/meson.build b/contrib/btree_gist/meson.build index 89932dd3844..f4fa9574f1f 100644 --- a/contrib/btree_gist/meson.build +++ b/contrib/btree_gist/meson.build @@ -51,7 +51,6 @@ install_data( 'btree_gist--1.5--1.6.sql', 'btree_gist--1.6--1.7.sql', 'btree_gist--1.7--1.8.sql', - 'btree_gist--1.8--1.9.sql', kwargs: contrib_data_args, ) diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index 4fb70bfc76a..de837ce09f9 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -2665,7 +2665,7 @@ dblink_connstr_has_required_scram_options(const char *connstr) PQconninfoFree(options); } - has_scram_keys = has_scram_client_key && has_scram_server_key && MyProcPort->has_scram_keys; + has_scram_keys = has_scram_client_key && has_scram_server_key && MyProcPort != NULL && MyProcPort->has_scram_keys; return (has_scram_keys && has_require_auth); } @@ -2698,7 +2698,7 @@ dblink_security_check(PGconn *conn, const char *connname, const char *connstr) * only added if UseScramPassthrough is set, and the user is not allowed * to add the SCRAM keys on fdw and user mapping options. */ - if (MyProcPort->has_scram_keys && dblink_connstr_has_required_scram_options(connstr)) + if (MyProcPort != NULL && MyProcPort->has_scram_keys && dblink_connstr_has_required_scram_options(connstr)) return; #ifdef ENABLE_GSS @@ -2771,7 +2771,7 @@ dblink_connstr_check(const char *connstr) if (dblink_connstr_has_pw(connstr)) return; - if (MyProcPort->has_scram_keys && dblink_connstr_has_required_scram_options(connstr)) + if (MyProcPort != NULL && MyProcPort->has_scram_keys && dblink_connstr_has_required_scram_options(connstr)) return; #ifdef ENABLE_GSS @@ -2931,7 +2931,7 @@ get_connect_string(const char *servername) * the user overwrites these options we can ereport on * dblink_connstr_check and dblink_security_check. */ - if (MyProcPort->has_scram_keys && UseScramPassthrough(foreign_server, user_mapping)) + if (MyProcPort != NULL && MyProcPort->has_scram_keys && UseScramPassthrough(foreign_server, user_mapping)) appendSCRAMKeysInfo(&buf); foreach(cell, fdw->options) diff --git a/contrib/dict_xsyn/dict_xsyn.c b/contrib/dict_xsyn/dict_xsyn.c index 1ec5285d6d1..c4eb2ec8bce 100644 --- a/contrib/dict_xsyn/dict_xsyn.c +++ b/contrib/dict_xsyn/dict_xsyn.c @@ -54,14 +54,14 @@ find_word(char *in, char **end) *end = NULL; while (*in && isspace((unsigned char) *in)) - in += pg_mblen(in); + in += pg_mblen_cstr(in); if (!*in || *in == '#') return NULL; start = in; while (*in && !isspace((unsigned char) *in)) - in += pg_mblen(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 62c4c0c1e2b..f57574f558f 100644 --- a/contrib/file_fdw/expected/file_fdw.out +++ b/contrib/file_fdw/expected/file_fdw.out @@ -456,6 +456,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/expected/ivy_file_fdw.out b/contrib/file_fdw/expected/ivy_file_fdw.out index 5c6845c84a3..dfbb8ec85b4 100644 --- a/contrib/file_fdw/expected/ivy_file_fdw.out +++ b/contrib/file_fdw/expected/ivy_file_fdw.out @@ -1,6 +1,7 @@ -- -- Test foreign-data wrapper file_fdw. -- +\set EXECUTE_RUN_PREPARE on -- directory paths are passed to us in environment variables \getenv abs_srcdir PG_ABS_SRCDIR -- Clean up in case a prior regression run failed @@ -446,6 +447,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 01431f5da8d..f9c8577fb13 100644 --- a/contrib/file_fdw/sql/file_fdw.sql +++ b/contrib/file_fdw/sql/file_fdw.sql @@ -242,6 +242,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/file_fdw/sql/ivy_file_fdw.sql b/contrib/file_fdw/sql/ivy_file_fdw.sql index 2f91c878534..6e6dd88038d 100644 --- a/contrib/file_fdw/sql/ivy_file_fdw.sql +++ b/contrib/file_fdw/sql/ivy_file_fdw.sql @@ -2,6 +2,7 @@ -- Test foreign-data wrapper file_fdw. -- +\set EXECUTE_RUN_PREPARE on -- directory paths are passed to us in environment variables \getenv abs_srcdir PG_ABS_SRCDIR @@ -243,6 +244,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/gb18030_2022/.gitignore b/contrib/gb18030_2022/.gitignore new file mode 100644 index 00000000000..4e85009fe53 --- /dev/null +++ b/contrib/gb18030_2022/.gitignore @@ -0,0 +1,4 @@ +# Generated files +# Generated subdirectories +/log/ +/results/ diff --git a/contrib/gb18030_2022/Maps/UCS_to_GB18030.pl b/contrib/gb18030_2022/Maps/UCS_to_GB18030.pl index 9d20bb2b6b2..65ff22b997e 100644 --- a/contrib/gb18030_2022/Maps/UCS_to_GB18030.pl +++ b/contrib/gb18030_2022/Maps/UCS_to_GB18030.pl @@ -10,7 +10,7 @@ # http://source.icu-project.org/repos/icu/data/trunk/charset/data/xml/ # # -# Portions Copyright (c) 2023-2025, IvorySQL Global Development Team +# Portions Copyright (c) 2023-2026, IvorySQL Global Development Team # # Identification: # contrib/gb18030_2022/Maps/UCS_to_GB18030.pl @@ -53,4 +53,4 @@ } close($in); -print_conversion_tables($this_script, "GB18030_2022", \@mapping); \ No newline at end of file +print_conversion_tables($this_script, "GB18030_2022", \@mapping); diff --git a/contrib/gb18030_2022/Maps/convutils.pm b/contrib/gb18030_2022/Maps/convutils.pm index 5bafdff1f89..a2f74e371d1 100644 --- a/contrib/gb18030_2022/Maps/convutils.pm +++ b/contrib/gb18030_2022/Maps/convutils.pm @@ -1,6 +1,6 @@ # ------------------------------------------------ # -# Portions Copyright (c) 2023-2025, IvorySQL Global Development Team +# Portions Copyright (c) 2023-2026, IvorySQL Global Development Team # # Identification: # contrib/gb18030_2022/Maps/convutils.pm diff --git a/contrib/gb18030_2022/Maps/gb-18030-2022.xml b/contrib/gb18030_2022/Maps/gb-18030-2022.xml index f4190cb1a87..5623b376f1e 100644 --- a/contrib/gb18030_2022/Maps/gb-18030-2022.xml +++ b/contrib/gb18030_2022/Maps/gb-18030-2022.xml @@ -63544,4 +63544,4 @@ - \ No newline at end of file + diff --git a/contrib/gb18030_2022/expected/gb18030_2022_and_utf8_1.out b/contrib/gb18030_2022/expected/gb18030_2022_and_utf8_1.out index 4a2bbd0b044..c918341b4f0 100644 --- a/contrib/gb18030_2022/expected/gb18030_2022_and_utf8_1.out +++ b/contrib/gb18030_2022/expected/gb18030_2022_and_utf8_1.out @@ -962,4 +962,4 @@ show shared_preload_libraries; ivorysql_ora | 1.0 | sys | Oracle Compatible extenison on Postgres Database plisql | 1.0 | pg_catalog | PL/iSQL procedural language plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language -(4 rows) \ No newline at end of file +(4 rows) diff --git a/contrib/gb18030_2022/gb18030_2022--1.0.sql b/contrib/gb18030_2022/gb18030_2022--1.0.sql index acd690d3724..9dac5306c68 100644 --- a/contrib/gb18030_2022/gb18030_2022--1.0.sql +++ b/contrib/gb18030_2022/gb18030_2022--1.0.sql @@ -1,4 +1,4 @@ /* contrib/gb18030_2022/gb18030_2022--1.0.sql */ --complain if script is sourced in psql rather than via ALTER EXTENSION \echo Use "CREATE EXTENSION gb18030_2022" to load this file. \quit -LOAD 'gb18030_2022'; \ No newline at end of file +LOAD 'gb18030_2022'; diff --git a/contrib/gb18030_2022/gb18030_2022.control b/contrib/gb18030_2022/gb18030_2022.control index 7e6ca22e03f..e26cd737199 100644 --- a/contrib/gb18030_2022/gb18030_2022.control +++ b/contrib/gb18030_2022/gb18030_2022.control @@ -3,4 +3,4 @@ default_version = '1.0' module_pathname = '$libdir/gb18030_2022' relocatable = false schema = pg_catalog -trusted = true \ No newline at end of file +trusted = true diff --git a/contrib/gb18030_2022/meson.build b/contrib/gb18030_2022/meson.build index 9342ca10273..148341a04d1 100644 --- a/contrib/gb18030_2022/meson.build +++ b/contrib/gb18030_2022/meson.build @@ -1,4 +1,4 @@ -# Portions Copyright (c) 2023-2025, IvorySQL Global Development Team +# Portions Copyright (c) 2023-2026, IvorySQL Global Development Team gb18030_2022_src_files = files( 'utf8_and_gb18030_2022.c' diff --git a/contrib/gb18030_2022/sql/copy.sql b/contrib/gb18030_2022/sql/copy.sql index 78fb0afcec9..1fd566fdea6 100644 --- a/contrib/gb18030_2022/sql/copy.sql +++ b/contrib/gb18030_2022/sql/copy.sql @@ -39,4 +39,4 @@ select * from copy_csv_18030; drop table copy_csv_18030; \c postgres -drop database gb18030_copy; \ No newline at end of file +drop database gb18030_copy; diff --git a/contrib/gb18030_2022/utf8_and_gb18030_2022.c b/contrib/gb18030_2022/utf8_and_gb18030_2022.c index 9c1a0e355e4..80eee6716fe 100644 --- a/contrib/gb18030_2022/utf8_and_gb18030_2022.c +++ b/contrib/gb18030_2022/utf8_and_gb18030_2022.c @@ -6,7 +6,7 @@ * support encoding conversion between gb18030_2022 and utf8 using radix tree in ./Maps/\*.map. * * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * Identification: * contrib/gb18030_2022 @@ -22,8 +22,8 @@ PG_MODULE_MAGIC; -gb18030_2022_to_utf8_hook_type pre_gb18030_2022_to_utf8_hook = NULL; -utf8_to_gb18030_2022_hook_type pre_utf8_to_gb18030_2022_hook = NULL; +static gb18030_2022_to_utf8_hook_type pre_gb18030_2022_to_utf8_hook = NULL; +static utf8_to_gb18030_2022_hook_type pre_utf8_to_gb18030_2022_hook = NULL; int gb18030_2022_to_utf8(const unsigned char *iso, int len, unsigned char *utf, bool noError); diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c index 4f867e4bd1f..fece61e293b 100644 --- a/contrib/hstore/hstore_io.c +++ b/contrib/hstore/hstore_io.c @@ -67,7 +67,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; @@ -385,7 +385,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 31393b4fa50..2727a5f2ceb 100644 --- a/contrib/hstore_plperl/hstore_plperl.c +++ b/contrib/hstore_plperl/hstore_plperl.c @@ -121,7 +121,7 @@ plperl_to_hstore(PG_FUNCTION_ARGS) pcount = hv_iterinit(hv); - pairs = palloc(pcount * sizeof(Pairs)); + pairs = palloc_array(Pairs, pcount); i = 0; while ((he = hv_iternext(hv))) diff --git a/contrib/hstore_plpython/hstore_plpython.c b/contrib/hstore_plpython/hstore_plpython.c index e2bfc6da38e..b0af13945bb 100644 --- a/contrib/hstore_plpython/hstore_plpython.c +++ b/contrib/hstore_plpython/hstore_plpython.c @@ -150,7 +150,7 @@ plpython_to_hstore(PG_FUNCTION_ARGS) Py_ssize_t i; Pairs *pairs; - pairs = palloc(pcount * sizeof(*pairs)); + pairs = palloc_array(Pairs, pcount); for (i = 0; i < pcount; i++) { diff --git a/contrib/intarray/_int_bool.c b/contrib/intarray/_int_bool.c index 2b2c3f4029e..26ba0b8a3b6 100644 --- a/contrib/intarray/_int_bool.c +++ b/contrib/intarray/_int_bool.c @@ -434,37 +434,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; } @@ -515,6 +555,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; @@ -524,8 +565,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 6c3b7ace146..60fd163668f 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/fmgrprotos.h" #include "utils/lsyscache.h" @@ -170,14 +171,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); } /* @@ -326,7 +338,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/ivorysql_ora/.gitignore b/contrib/ivorysql_ora/.gitignore new file mode 100644 index 00000000000..7da67cb0cc9 --- /dev/null +++ b/contrib/ivorysql_ora/.gitignore @@ -0,0 +1,5 @@ +# Generated files +/ivorysql_ora--1.0.sql +# Generated subdirectories +/log/ +/results/ diff --git a/contrib/ivorysql_ora/expected/ora_datetime.out b/contrib/ivorysql_ora/expected/ora_datetime.out index a21a9ad638d..dd1435e4148 100644 --- a/contrib/ivorysql_ora/expected/ora_datetime.out +++ b/contrib/ivorysql_ora/expected/ora_datetime.out @@ -280,7 +280,7 @@ CREATE INDEX TEST_DATE_BRIN ON TEST_DATE USING BRIN (A); -- Build tables and test 'NLS_TIMESTAMP_FORMAT' parameter, don't do the validity check. CREATE TABLE TEST_TIMESTAMP(a TIMESTAMP); SET NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS.FF9'; --- effective number of fractional seconds is 6,the part of excess is 0 +-- effective number of fractional seconds is 6, the part of excess is 0 INSERT INTO TEST_TIMESTAMP VALUES('1990-1-19 11:11:11.123456789'); INSERT INTO TEST_TIMESTAMP VALUES('1990-2-19'); -- hour、minute、second、fractional second is zero. INSERT INTO TEST_TIMESTAMP VALUES('1990-2-19 11:11:11'); @@ -550,7 +550,7 @@ CREATE INDEX TEST_TIMESTAMP_BRIN ON TEST_TIMESTAMP USING BRIN (A); -- Build tables and test 'NLS_TIMESTAMP_TZ_FORMAT' parameter, don't do the validity check. CREATE TABLE TEST_TIMESTAMPTZ(a TIMESTAMP WITH TIME ZONE); SET NLS_TIMESTAMP_TZ_FORMAT='YYYY-MM-DD HH24:MI:SS.FF9'; --- effective number of fractional seconds is 6,the part of excess is 0 +-- effective number of fractional seconds is 6, the part of excess is 0 INSERT INTO TEST_TIMESTAMPTZ VALUES('1990-1-19 11:11:11.123456789'); INSERT INTO TEST_TIMESTAMPTZ VALUES('1990-2-19'); -- hour、minute、second、fractional second is zero. INSERT INTO TEST_TIMESTAMPTZ VALUES('1990-2-19 11:11:11'); diff --git a/contrib/ivorysql_ora/expected/ora_datetime_datatype_functions.out b/contrib/ivorysql_ora/expected/ora_datetime_datatype_functions.out index 1c782bab721..a4159256839 100644 --- a/contrib/ivorysql_ora/expected/ora_datetime_datatype_functions.out +++ b/contrib/ivorysql_ora/expected/ora_datetime_datatype_functions.out @@ -1739,10 +1739,10 @@ select * from timestpwtz_tb; drop table timestpwtz_tb; alter session set NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH.MI.SS.FF5 AM'; CREATE TABLE timestp_tb(timestp_clo timestamp(7)); -WARNING: TIMESTAMP(7) effective number of fractional seconds is 6,the part of excess is 0 +WARNING: TIMESTAMP(7) effective number of fractional seconds is 6, the part of excess is 0 LINE 1: CREATE TABLE timestp_tb(timestp_clo timestamp(7)); ^ -WARNING: TIMESTAMP(7) effective number of fractional seconds is 6,the part of excess is 0 +WARNING: TIMESTAMP(7) effective number of fractional seconds is 6, the part of excess is 0 insert into timestp_tb values ('2022-08-19 03.37.05 PM'); select * from timestp_tb; timestp_clo diff --git a/contrib/ivorysql_ora/gensql.pl b/contrib/ivorysql_ora/gensql.pl index a5b153e9046..c987e31bb48 100755 --- a/contrib/ivorysql_ora/gensql.pl +++ b/contrib/ivorysql_ora/gensql.pl @@ -21,7 +21,7 @@ # specified by DATA in Makefile usings SQL files specified # in ivorysql_ora_merge_sqls. # -# Portions Copyright (c) 2023-2025, IvorySQL Global Development Team +# Portions Copyright (c) 2023-2026, IvorySQL Global Development Team # # Identification: # contrib/ivorysql_ora/gensql.pl diff --git a/contrib/ivorysql_ora/meson.build b/contrib/ivorysql_ora/meson.build index d16cba8d7bd..e7f9541c7e7 100644 --- a/contrib/ivorysql_ora/meson.build +++ b/contrib/ivorysql_ora/meson.build @@ -1,5 +1,5 @@ # Copyright (c) 2022-2024, PostgreSQL Global Development Group -# Portions Copyright (c) 2023-2025, IvorySQL Global Development Team +# Portions Copyright (c) 2023-2026, IvorySQL Global Development Team ivorysql_ora_sources = files( 'src/ivorysql_ora.c', diff --git a/contrib/ivorysql_ora/preload_ora_misc.sql b/contrib/ivorysql_ora/preload_ora_misc.sql index 6f90c62c472..f9522823c8c 100644 --- a/contrib/ivorysql_ora/preload_ora_misc.sql +++ b/contrib/ivorysql_ora/preload_ora_misc.sql @@ -6,7 +6,9 @@ insert into dual values('X'); GRANT SELECT ON dual TO PUBLIC; -- --- ROWID type +-- Oracle-compatible ROWID and UROWID types +-- ROWID: Composite type containing row object ID and row number +-- UROWID: Universal ROWID, compatible with ROWID -- CREATE TYPE sys.rowid AS(rowoid OID, rowno bigint); CREATE TYPE sys.urowid AS(rowoid OID, rowno bigint); diff --git a/contrib/ivorysql_ora/sql/ora_datetime.sql b/contrib/ivorysql_ora/sql/ora_datetime.sql index 138ef43cb09..1c3850a34a1 100644 --- a/contrib/ivorysql_ora/sql/ora_datetime.sql +++ b/contrib/ivorysql_ora/sql/ora_datetime.sql @@ -143,7 +143,7 @@ CREATE TABLE TEST_TIMESTAMP(a TIMESTAMP); SET NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS.FF9'; --- effective number of fractional seconds is 6,the part of excess is 0 +-- effective number of fractional seconds is 6, the part of excess is 0 INSERT INTO TEST_TIMESTAMP VALUES('1990-1-19 11:11:11.123456789'); INSERT INTO TEST_TIMESTAMP VALUES('1990-2-19'); -- hour、minute、second、fractional second is zero. @@ -276,7 +276,7 @@ CREATE TABLE TEST_TIMESTAMPTZ(a TIMESTAMP WITH TIME ZONE); SET NLS_TIMESTAMP_TZ_FORMAT='YYYY-MM-DD HH24:MI:SS.FF9'; --- effective number of fractional seconds is 6,the part of excess is 0 +-- effective number of fractional seconds is 6, the part of excess is 0 INSERT INTO TEST_TIMESTAMPTZ VALUES('1990-1-19 11:11:11.123456789'); INSERT INTO TEST_TIMESTAMPTZ VALUES('1990-2-19'); -- hour、minute、second、fractional second is zero. diff --git a/contrib/ivorysql_ora/src/builtin_functions/builtin_functions--1.0.sql b/contrib/ivorysql_ora/src/builtin_functions/builtin_functions--1.0.sql index af9b87b862a..c4f6b5a2354 100644 --- a/contrib/ivorysql_ora/src/builtin_functions/builtin_functions--1.0.sql +++ b/contrib/ivorysql_ora/src/builtin_functions/builtin_functions--1.0.sql @@ -970,7 +970,7 @@ IMMUTABLE; */ /* SESSIONID */ CREATE SEQUENCE sys.userenv_sessionid_sequence; -GRANT ALL ON SEQUENCE sys.userenv_sessionid_sequence to public; +GRANT SELECT, USAGE ON SEQUENCE sys.userenv_sessionid_sequence TO public; CREATE OR REPLACE FUNCTION sys.get_sessionid() RETURNS number AS $$ DECLARE @@ -1228,159 +1228,159 @@ AS $$ $$ LANGUAGE SQL IMMUTABLE STRICT; /* Begin - SYS_CONTEXT */ -create or replace function sys.sys_context(a varchar2, b varchar2) -return varchar2 as $$ -declare +CREATE OR REPLACE FUNCTION sys.sys_context(a varchar2, b varchar2) +RETURNS varchar2 AS $$ +DECLARE res varchar2; -begin - if upper(a) = 'USERENV' then - case upper(b) - when 'CURRENT_SCHEMA' then - select current_schema() into res; - when 'CURRENT_SCHEMAID' then - select current_schema()::regnamespace::oid into res; - when 'SESSION_USER' then - select session_user into res; - when 'SESSION_USERID' then - select session_user::regrole::oid into res; - when 'PROXY_USER' then - select session_user into res; - when 'PROXY_USERID' then - select session_user::regrole::oid into res; - when 'CURRENT_USER' then - select current_user into res; - when 'CURRENT_USERID' then - select current_user::regrole::oid into res; - when 'CURRENT_EDITION_NAME' then - select version() into res; - when 'CLIENT_PROGRAM_NAME' then - select application_name into res from pg_stat_activity where pid = pg_backend_pid(); - when 'IP_ADDRESS' then - select client_addr into res from pg_stat_activity where pid = pg_backend_pid(); - when 'HOST' then - select client_hostname into res from pg_stat_activity where pid = pg_backend_pid(); - when 'ISDBA' then - select sys.get_isdba() into res; - when 'LANGUAGE' then - select sys.get_language() into res; - when 'LANG' then - select sys.get_lang() into res; - when 'NLS_CURRENCY' then - select null into res;-- show nls_currency into res; - when 'NLS_DATE_FORMAT' then - show nls_date_format into res; - when 'NLS_DATE_LANGUAGE' then - select null into res;-- show nls_iso_currency into res; - when 'NLS_SORT' then - select null into res;-- show nls_sort into res; - when 'NLS_TERRITORY' then - select null into res;-- show nls_territory into res; - when 'ORACLE_HOME' then - show data_directory into res; - when 'PLATFORM_SLASH' then - select - case - when substring((select setting from pg_settings where name = 'data_directory') from 1 for 1) = '/' then 'LINUX' - else 'WINDOWS' - end into res; - when 'DB_NAME' then - select current_database into res; - when 'SESSION_DEFAULT_COLLATION' then - select null into res;-- show default_collation into res; - when 'SID' then - select sys.get_sid() into res; - when 'ACTION' then select NULL into res; - when 'IS_APPLICATION_ROOT' then select NULL into res; - when 'IS_APPLICATION_PDB' then select NULL into res; - when 'AUDITED_CURSORID' then select NULL into res; - when 'AUTHENTICATED_IDENTITY' then select NULL into res; - when 'AUTHENTICATION_DATA' then select NULL into res; - when 'AUTHENTICATION_METHOD' then select NULL into res; - when 'BG_JOB_ID' then select NULL into res; - when 'CDB_DOMAIN' then select NULL into res; - when 'CDB_NAME' then select NULL into res; - when 'CLIENT_IDENTIFIER' then select NULL into res; - when 'CLIENT_INFO' then - select sys.get_client_info() into res; - when 'CON_ID' then select NULL into res; - when 'CON_NAME' then select NULL into res; - when 'CURRENT_BIND' then select NULL into res; - when 'CURRENT_EDITION_ID' then select NULL into res; - when 'CURRENT_SQL' then select NULL into res; - when 'CURRENT_SQL1' then select NULL into res; - when 'CURRENT_SQL2' then select NULL into res; - when 'CURRENT_SQL3' then select NULL into res; - when 'CURRENT_SQL4' then select NULL into res; - when 'CURRENT_SQL5' then select NULL into res; - when 'CURRENT_SQL6' then select NULL into res; - when 'CURRENT_SQL7' then select NULL into res; - when 'CURRENT_SQL_LENGTH' then select NULL into res; - when 'DATABASE_ROLE' then select NULL into res; - when 'DB_DOMAIN' then select NULL into res; - when 'DB_SUPPLEMENTAL_LOG_LEVEL' then select NULL into res; - when 'DB_UNIQUE_NAME' then select NULL into res; - when 'DBLINK_INFO' then select NULL into res; - when 'DRAIN_STATUS' then select NULL into res; - when 'ENTRYID' then - select sys.get_entryid() into res; - when 'ENTERPRISE_IDENTITY' then select NULL into res; - when 'FG_JOB_ID' then select NULL into res; - when 'GLOBAL_CONTEXT_MEMORY' then select NULL into res; - when 'GLOBAL_UID' then select NULL into res; - when 'IDENTIFICATION_TYPE' then select NULL into res; - when 'INSTANCE' then select NULL into res; - when 'INSTANCE_NAME' then select NULL into res; - when 'IS_APPLY_SERVER' then select 'FALSE' into res; - when 'IS_DG_ROLLING_UPGRADE' then select 'FALSE' into res; - when 'LDAP_SERVER_TYPE' then select NULL into res; - when 'MODULE' then select NULL into res; - when 'NETWORK_PROTOCOL' then select NULL into res; - when 'NLS_CALENDAR' then select NULL into res; - when 'OS_USER' then select NULL into res; - when 'POLICY_INVOKER' then select NULL into res; - when 'PROXY_ENTERPRISE_IDENTITY' then select NULL into res; - when 'SCHEDULER_JOB' then select NULL into res; - when 'SERVER_HOST' then select NULL into res; - when 'SERVICE_NAME' then select NULL into res; - when 'SESSION_EDITION_ID' then select NULL into res; - when 'SESSION_EDITION_NAME' then select NULL into res; - when 'SESSIONID' then - select sys.get_sessionid() into res; - when 'STATEMENTID' then select NULL into res; - when 'TERMINAL' then - select sys.get_terminal() into res; - when 'UNIFIED_AUDIT_SESSIONID' then select NULL into res; - else - RAISE EXCEPTION 'invalid USERENV parameter: %', b; - end case; - elsif upper(a) = 'SYS_SESSION_ROLES' then - case upper(b) - when 'DBA' then - select sys.get_isdba() into res; - when 'LOGIN' then - select case when rolcanlogin = 't' then 'TRUE' else 'FALSE' end into res from pg_roles where oid = current_user::regrole::oid; - when 'CREATEROLE' then - select case when rolcreaterole = 't' then 'TRUE' else 'FALSE' end into res from pg_roles where oid = current_user::regrole::oid; - when 'CREATEDB' then - select case when rolcreatedb = 't' then 'TRUE' else 'FALSE' end into res from pg_roles where oid = current_user::regrole::oid; - else +BEGIN + IF upper(a) = 'USERENV' THEN + CASE upper(b) + WHEN 'CURRENT_SCHEMA' THEN + SELECT current_schema() INTO res; + WHEN 'CURRENT_SCHEMAID' THEN + SELECT current_schema()::regnamespace::oid INTO res; + WHEN 'SESSION_USER' THEN + SELECT session_user INTO res; + WHEN 'SESSION_USERID' THEN + SELECT session_user::regrole::oid INTO res; + WHEN 'PROXY_USER' THEN + SELECT session_user INTO res; + WHEN 'PROXY_USERID' THEN + SELECT session_user::regrole::oid INTO res; + WHEN 'CURRENT_USER' THEN + SELECT current_user INTO res; + WHEN 'CURRENT_USERID' THEN + SELECT current_user::regrole::oid INTO res; + WHEN 'CURRENT_EDITION_NAME' THEN + SELECT version() INTO res; + WHEN 'CLIENT_PROGRAM_NAME' THEN + SELECT application_name INTO res FROM pg_stat_activity WHERE pid = pg_backend_pid(); + WHEN 'IP_ADDRESS' THEN + SELECT client_addr INTO res FROM pg_stat_activity WHERE pid = pg_backend_pid(); + WHEN 'HOST' THEN + SELECT client_hostname INTO res FROM pg_stat_activity WHERE pid = pg_backend_pid(); + WHEN 'ISDBA' THEN + SELECT sys.get_isdba() INTO res; + WHEN 'LANGUAGE' THEN + SELECT sys.get_language() INTO res; + WHEN 'LANG' THEN + SELECT sys.get_lang() INTO res; + WHEN 'NLS_CURRENCY' THEN + SELECT null INTO res; + WHEN 'NLS_DATE_FORMAT' THEN + SELECT current_setting('nls_date_format') INTO res; + WHEN 'NLS_DATE_LANGUAGE' THEN + SELECT null INTO res; + WHEN 'NLS_SORT' THEN + SELECT null INTO res; + WHEN 'NLS_TERRITORY' THEN + SELECT null INTO res; + WHEN 'ORACLE_HOME' THEN + SELECT current_setting('data_directory') INTO res; + WHEN 'PLATFORM_SLASH' THEN + SELECT + CASE + WHEN substring((SELECT setting FROM pg_settings WHERE name = 'data_directory') FROM 1 for 1) = '/' THEN 'LINUX' + ELSE 'WINDOWS' + END INTO res; + WHEN 'DB_NAME' THEN + SELECT current_database() INTO res; + WHEN 'SESSION_DEFAULT_COLLATION' THEN + SELECT null INTO res; + WHEN 'SID' THEN + SELECT sys.get_sid() INTO res; + WHEN 'ACTION' THEN SELECT NULL INTO res; + WHEN 'IS_APPLICATION_ROOT' THEN SELECT NULL INTO res; + WHEN 'IS_APPLICATION_PDB' THEN SELECT NULL INTO res; + WHEN 'AUDITED_CURSORID' THEN SELECT NULL INTO res; + WHEN 'AUTHENTICATED_IDENTITY' THEN SELECT NULL INTO res; + WHEN 'AUTHENTICATION_DATA' THEN SELECT NULL INTO res; + WHEN 'AUTHENTICATION_METHOD' THEN SELECT NULL INTO res; + WHEN 'BG_JOB_ID' THEN SELECT NULL INTO res; + WHEN 'CDB_DOMAIN' THEN SELECT NULL INTO res; + WHEN 'CDB_NAME' THEN SELECT NULL INTO res; + WHEN 'CLIENT_IDENTIFIER' THEN SELECT NULL INTO res; + WHEN 'CLIENT_INFO' THEN + SELECT sys.get_client_info() INTO res; + WHEN 'CON_ID' THEN SELECT NULL INTO res; + WHEN 'CON_NAME' THEN SELECT NULL INTO res; + WHEN 'CURRENT_BIND' THEN SELECT NULL INTO res; + WHEN 'CURRENT_EDITION_ID' THEN SELECT NULL INTO res; + WHEN 'CURRENT_SQL' THEN SELECT NULL INTO res; + WHEN 'CURRENT_SQL1' THEN SELECT NULL INTO res; + WHEN 'CURRENT_SQL2' THEN SELECT NULL INTO res; + WHEN 'CURRENT_SQL3' THEN SELECT NULL INTO res; + WHEN 'CURRENT_SQL4' THEN SELECT NULL INTO res; + WHEN 'CURRENT_SQL5' THEN SELECT NULL INTO res; + WHEN 'CURRENT_SQL6' THEN SELECT NULL INTO res; + WHEN 'CURRENT_SQL7' THEN SELECT NULL INTO res; + WHEN 'CURRENT_SQL_LENGTH' THEN SELECT NULL INTO res; + WHEN 'DATABASE_ROLE' THEN SELECT NULL INTO res; + WHEN 'DB_DOMAIN' THEN SELECT NULL INTO res; + WHEN 'DB_SUPPLEMENTAL_LOG_LEVEL' THEN SELECT NULL INTO res; + WHEN 'DB_UNIQUE_NAME' THEN SELECT NULL INTO res; + WHEN 'DBLINK_INFO' THEN SELECT NULL INTO res; + WHEN 'DRAIN_STATUS' THEN SELECT NULL INTO res; + WHEN 'ENTRYID' THEN + SELECT sys.get_entryid() INTO res; + WHEN 'ENTERPRISE_IDENTITY' THEN SELECT NULL INTO res; + WHEN 'FG_JOB_ID' THEN SELECT NULL INTO res; + WHEN 'GLOBAL_CONTEXT_MEMORY' THEN SELECT NULL INTO res; + WHEN 'GLOBAL_UID' THEN SELECT NULL INTO res; + WHEN 'IDENTIFICATION_TYPE' THEN SELECT NULL INTO res; + WHEN 'INSTANCE' THEN SELECT NULL INTO res; + WHEN 'INSTANCE_NAME' THEN SELECT NULL INTO res; + WHEN 'IS_APPLY_SERVER' THEN SELECT 'FALSE' INTO res; + WHEN 'IS_DG_ROLLING_UPGRADE' THEN SELECT 'FALSE' INTO res; + WHEN 'LDAP_SERVER_TYPE' THEN SELECT NULL INTO res; + WHEN 'MODULE' THEN SELECT NULL INTO res; + WHEN 'NETWORK_PROTOCOL' THEN SELECT NULL INTO res; + WHEN 'NLS_CALENDAR' THEN SELECT NULL INTO res; + WHEN 'OS_USER' THEN SELECT NULL INTO res; + WHEN 'POLICY_INVOKER' THEN SELECT NULL INTO res; + WHEN 'PROXY_ENTERPRISE_IDENTITY' THEN SELECT NULL INTO res; + WHEN 'SCHEDULER_JOB' THEN SELECT NULL INTO res; + WHEN 'SERVER_HOST' THEN SELECT NULL INTO res; + WHEN 'SERVICE_NAME' THEN SELECT NULL INTO res; + WHEN 'SESSION_EDITION_ID' THEN SELECT NULL INTO res; + WHEN 'SESSION_EDITION_NAME' THEN SELECT NULL INTO res; + WHEN 'SESSIONID' THEN + SELECT sys.get_sessionid() INTO res; + WHEN 'STATEMENTID' THEN SELECT NULL INTO res; + WHEN 'TERMINAL' THEN + SELECT sys.get_terminal() INTO res; + WHEN 'UNIFIED_AUDIT_SESSIONID' THEN SELECT NULL INTO res; + ELSE + RAISE EXCEPTION 'invalid USERENV parameter: %', b; + END CASE; + ELSIF upper(a) = 'SYS_SESSION_ROLES' THEN + CASE upper(b) + WHEN 'DBA' THEN + SELECT sys.get_isdba() INTO res; + WHEN 'LOGIN' THEN + SELECT CASE WHEN rolcanlogin = 't' THEN 'TRUE' ELSE 'FALSE' END INTO res FROM pg_roles WHERE oid = current_user::regrole::oid; + WHEN 'CREATEROLE' THEN + SELECT CASE WHEN rolcreaterole = 't' THEN 'TRUE' ELSE 'FALSE' END INTO res FROM pg_roles WHERE oid = current_user::regrole::oid; + WHEN 'CREATEDB' THEN + SELECT CASE WHEN rolcreatedb = 't' THEN 'TRUE' ELSE 'FALSE' END INTO res FROM pg_roles WHERE oid = current_user::regrole::oid; + ELSE RAISE EXCEPTION 'invalid SYS_SESSION_ROLES parameter: %', b; - end case; - else - select current_setting(a||'.'||b, true) into res; - end if; - return res; -end; + END CASE; + ELSE + SELECT current_setting(a||'.'||b, true) INTO res; + END IF; + RETURN res; +END; $$ LANGUAGE plisql SECURITY INVOKER; -create or replace function sys.sys_context(a varchar2, b varchar2, c number) -return varchar2 as $$ -declare +CREATE or REPLACE FUNCTION sys.sys_context(a varchar2, b varchar2, c number) +RETURNS varchar2 AS $$ +DECLARE res varchar2; -begin - select left(sys.sys_context(a, b), c::integer) into res; - return res; -end; +BEGIN + SELECT left(sys.sys_context(a, b), c::integer) INTO res; + RETURN res; +END; $$ LANGUAGE plisql SECURITY INVOKER; /* End - SYS_CONTEXT */ diff --git a/contrib/ivorysql_ora/src/builtin_functions/character_datatype_functions.c b/contrib/ivorysql_ora/src/builtin_functions/character_datatype_functions.c index 609e4082f05..74bb01d1be8 100644 --- a/contrib/ivorysql_ora/src/builtin_functions/character_datatype_functions.c +++ b/contrib/ivorysql_ora/src/builtin_functions/character_datatype_functions.c @@ -18,7 +18,7 @@ * This file contains the implementation of Oracle's * character data type related built-in functions. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/builtin_functions/character_datatype_functions.c * diff --git a/contrib/ivorysql_ora/src/builtin_functions/datetime_datatype_functions.c b/contrib/ivorysql_ora/src/builtin_functions/datetime_datatype_functions.c index 3f8128df410..b814a30bdc5 100644 --- a/contrib/ivorysql_ora/src/builtin_functions/datetime_datatype_functions.c +++ b/contrib/ivorysql_ora/src/builtin_functions/datetime_datatype_functions.c @@ -1,12 +1,12 @@ /*------------------------------------------------------------------------- * Copyright 2025 IvorySQL Global Development Team - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,7 +18,7 @@ * This file contains the implementation of Oracle's * datetime data type related built-in functions. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/builtin_functions/datetime_datatype_functions.c * @@ -104,7 +104,7 @@ do { \ #define ROUND_MDAY(_tm_) \ do { if (rounded) _tm_->tm_mday += _tm_->tm_hour >= 12?1:0; } while(0) - /* Note: this is used to copy pg_tm to fmt_tm, so not quite a bitwise copy */ + /* Note: this is used to copy pg_tm to fmt_tm, so not quite a bitwise copy */ #define COPY_tm(_DST, _SRC) \ do { \ (_DST)->tm_sec = (_SRC)->tm_sec; \ @@ -122,38 +122,38 @@ typedef struct WeekDays { int encoding; const char *names[7]; -} WeekDays; +} WeekDays; static const WeekDays WEEKDAYS[] = { - { PG_UTF8, - {"\xe6\x98\x9f\xe6\x9c\x9f\xe6\x97\xa5", - "\xe6\x98\x9f\xe6\x9c\x9f\xe4\xb8\x80", - "\xe6\x98\x9f\xe6\x9c\x9f\xe4\xba\x8c", - "\xe6\x98\x9f\xe6\x9c\x9f\xe4\xb8\x89", - "\xe6\x98\x9f\xe6\x9c\x9f\xe5\x9b\x9b", - "\xe6\x98\x9f\xe6\x9c\x9f\xe4\xba\x94", - "\xe6\x98\x9f\xe6\x9c\x9f\xe5\x85\xad" - } + {PG_UTF8, + {"\xe6\x98\x9f\xe6\x9c\x9f\xe6\x97\xa5", + "\xe6\x98\x9f\xe6\x9c\x9f\xe4\xb8\x80", + "\xe6\x98\x9f\xe6\x9c\x9f\xe4\xba\x8c", + "\xe6\x98\x9f\xe6\x9c\x9f\xe4\xb8\x89", + "\xe6\x98\x9f\xe6\x9c\x9f\xe5\x9b\x9b", + "\xe6\x98\x9f\xe6\x9c\x9f\xe4\xba\x94", + "\xe6\x98\x9f\xe6\x9c\x9f\xe5\x85\xad" + } }, - { PG_GBK, - {"\320\307\306\332\310\325", - "\320\307\306\332\322\273", - "\320\307\306\332\266\376", - "\320\307\306\332\310\375", - "\320\307\306\332\313\304", - "\320\307\306\332\316\345", - "\320\307\306\332\301\371" - } + {PG_GBK, + {"\320\307\306\332\310\325", + "\320\307\306\332\322\273", + "\320\307\306\332\266\376", + "\320\307\306\332\310\375", + "\320\307\306\332\313\304", + "\320\307\306\332\316\345", + "\320\307\306\332\301\371" + } } }; static const int month_days[] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; -const char *const ora_days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", +static const char *const ora_days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", NULL}; #define CASE_fmt_YYYY case 0: case 1: case 2: case 3: case 4: case 5: case 6: @@ -167,9 +167,9 @@ const char *const ora_days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", #define CASE_fmt_CC case 22: case 23: #define CASE_fmt_DDD case 24: case 25: case 26: #define CASE_fmt_HH case 27: case 28: case 29: -#define CASE_fmt_MI case 30: +#define CASE_fmt_MI case 30: -const char *const date_fmt[] = +static const char *const date_fmt[] = { "Y", "Yy", "Yyy", "Yyyy", "Year", "Syyyy", "syear", "I", "Iy", "Iyy", "Iyyy", @@ -195,23 +195,23 @@ const char *const date_fmt[] = #define CASE_timezone_10 case 15: case 16: #define CASE_timezone_11 case 17: -const char *const date_timezone[] = +static const char *const date_timezone[] = { "GMT", "ADT", "NST", "AST", "EDT", "CDT", - "EST","CST", "MDT", "MST", "PDT", "PST", + "EST", "CST", "MDT", "MST", "PDT", "PST", "YDT", "HDT", "YST", "BDT", "HST", "BST", NULL }; static int64 sys_time_zone(void); -static int days_of_month(int y, int m); +static int days_of_month(int y, int m); static void tm_round(struct pg_tm *tm, text *fmt); -static Timestamp iso_year (int y, int m, int d); +static Timestamp iso_year(int y, int m, int d); static pg_tz *ora_make_timezone(char **newval); static void tm_trunc(struct pg_tm *tm, text *fmt); -static int ora_seq_prefix_search(const char *name, const char *const array[], int max); -static int weekday_search(const WeekDays *weekdays, const char *str, int len); -static int ora_timezone_name_to_num(const char *name); +static int ora_seq_prefix_search(const char *name, const char *const array[], int max); +static int weekday_search(const WeekDays * weekdays, const char *str, int len); +static int ora_timezone_name_to_num(const char *name); /* * sysdate returns the current date and time set for the operating @@ -228,7 +228,7 @@ sysdate(PG_FUNCTION_ARGS) struct pg_tm tt, *tm = &tt; fsec_t fsec; - int64 systimezone; + int64 systimezone; systimezone = sys_time_zone(); timestamp = timestamp + systimezone * 1000000; @@ -241,7 +241,7 @@ sysdate(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); - + if (tm2timestamp(tm, 0, NULL, &result) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), @@ -267,54 +267,56 @@ ora_current_date(PG_FUNCTION_ARGS) int tz; if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); - + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + if (tm2timestamp(tm, 0, NULL, &result) != 0) ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); PG_RETURN_TIMESTAMP(result); } -/* - * Returns the current date and time in the session time zone, +/* + * Returns the current date and time in the session time zone, * in a value of data type TIMESTAMP WITH TIME ZONE. */ Datum ora_current_timestamp(PG_FUNCTION_ARGS) { TimestampTz timestamp = GetCurrentTransactionStartTimestamp(); - int argsnum = PG_NARGS(); + int argsnum = PG_NARGS(); + if (argsnum == 1) { - int n = PG_GETARG_INT32(0); - if(n > MAX_TIMESTAMP_PRECISION && n <= ORACLE_MAX_TIMESTAMP_PRECISION) + int n = PG_GETARG_INT32(0); + + if (n > MAX_TIMESTAMP_PRECISION && n <= ORACLE_MAX_TIMESTAMP_PRECISION) { ereport(WARNING, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("TIMESTAMP(%d) effective number of fractional seconds is 6,the part of excess is 0", - n))); + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("TIMESTAMP(%d) effective number of fractional seconds is 6, the part of excess is 0", + n))); n = MAX_TIMESTAMP_PRECISION; } - OraAdjustTimestampForTypmod((Timestamp *)×tamp, n); + OraAdjustTimestampForTypmod((Timestamp *) ×tamp, n); } PG_RETURN_TIMESTAMPTZ(timestamp); } -/* +/* * Returns the current date and time in the session time zone in a value of data type TIMESTAMP. */ Datum ora_local_timestamp(PG_FUNCTION_ARGS) { - Timestamp timestamp = GetCurrentTransactionStartTimestamp(); - int argsnum = PG_NARGS(); + Timestamp timestamp = GetCurrentTransactionStartTimestamp(); + int argsnum = PG_NARGS(); struct pg_tm tt, - *tm = &tt; + *tm = &tt; fsec_t fsec; int tz; const char *tzn; @@ -333,33 +335,36 @@ ora_local_timestamp(PG_FUNCTION_ARGS) if (argsnum == 1) { - int n = PG_GETARG_INT32(0); - if(n > MAX_TIMESTAMP_PRECISION && n <= ORACLE_MAX_TIMESTAMP_PRECISION) + int n = PG_GETARG_INT32(0); + + if (n > MAX_TIMESTAMP_PRECISION && n <= ORACLE_MAX_TIMESTAMP_PRECISION) { ereport(WARNING, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("TIMESTAMP(%d) effective number of fractional seconds is 6,the part of excess is 0", - n))); + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("TIMESTAMP(%d) effective number of fractional seconds is 6, the part of excess is 0", + n))); n = MAX_TIMESTAMP_PRECISION; } - OraAdjustTimestampForTypmod((Timestamp *)×tamp, n); + OraAdjustTimestampForTypmod((Timestamp *) ×tamp, n); } PG_RETURN_TIMESTAMP(timestamp); } -/* - * Returns the date of the last day of the month that contains date. +/* + * Returns the date of the last day of the month that contains date. * The return type is always DATE, regardless of the data type of date. */ Datum last_day(PG_FUNCTION_ARGS) { - Timestamp time = PG_GETARG_TIMESTAMP(0); + Timestamp time = PG_GETARG_TIMESTAMP(0); Timestamp date; - int y = 0, m = 0, d = 0; - int last_day = 0; - Timestamp result; - + int y = 0, + m = 0, + d = 0; + int last_day = 0; + Timestamp result; + TMODULO(time, date, USECS_PER_DAY); if (time < INT64CONST(0)) { @@ -368,7 +373,7 @@ last_day(PG_FUNCTION_ARGS) } date += POSTGRES_EPOCH_JDATE; - j2date((int)date, &y, &m, &d); + j2date((int) date, &y, &m, &d); last_day = days_of_month(y, m); result = date2j(y, m, last_day) - POSTGRES_EPOCH_JDATE; @@ -378,21 +383,23 @@ last_day(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMP(result); } -/* +/* * Returns the date 'time' plus 'num' months. * The return type is always DATE, regardless of the data type of 'time'. */ Datum add_months(PG_FUNCTION_ARGS) { - Timestamp time = PG_GETARG_TIMESTAMP(0); + Timestamp time = PG_GETARG_TIMESTAMP(0); Timestamp date; - int y = 0, m = 0, d = 0; - int days; - Timestamp result; - div_t v; - bool last_day; - int64 n; + int y = 0, + m = 0, + d = 0; + int days; + Timestamp result; + div_t v; + bool last_day; + int64 n; Numeric num = PG_GETARG_NUMERIC(1); n = DatumGetInt32(DirectFunctionCall1(numeric_int8, NumericGetDatum(num))); @@ -405,7 +412,7 @@ add_months(PG_FUNCTION_ARGS) } date += POSTGRES_EPOCH_JDATE; - j2date((int)date, &y, &m, &d); + j2date((int) date, &y, &m, &d); last_day = (d == days_of_month(y, m)); v = div(y * 12 + m - 1 + n, 12); @@ -431,13 +438,15 @@ add_months(PG_FUNCTION_ARGS) Datum ora_round(PG_FUNCTION_ARGS) { - fsec_t fsec; - struct pg_tm tt, *tm = &tt; - Timestamp timestamp = PG_GETARG_TIMESTAMP(0); - Timestamp result; - text *fmt = NULL; - int argsnum = PG_NARGS(); - if(argsnum == 1) + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + Timestamp timestamp = PG_GETARG_TIMESTAMP(0); + Timestamp result; + text *fmt = NULL; + int argsnum = PG_NARGS(); + + if (argsnum == 1) fmt = cstring_to_text("DDD"); else fmt = PG_GETARG_TEXT_PP(1); @@ -447,8 +456,8 @@ ora_round(PG_FUNCTION_ARGS) if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); tm_round(tm, fmt); @@ -467,14 +476,16 @@ ora_round(PG_FUNCTION_ARGS) Datum ora_trunc(PG_FUNCTION_ARGS) { - Timestamp timestamp = PG_GETARG_TIMESTAMP(0); - Timestamp result; - text *fmt = NULL; - fsec_t fsec; - struct pg_tm tt, *tm = &tt; - - int argsnum = PG_NARGS(); - if(argsnum == 1) + Timestamp timestamp = PG_GETARG_TIMESTAMP(0); + Timestamp result; + text *fmt = NULL; + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + + int argsnum = PG_NARGS(); + + if (argsnum == 1) fmt = cstring_to_text("DDD"); else fmt = PG_GETARG_TEXT_PP(1); @@ -484,8 +495,8 @@ ora_trunc(PG_FUNCTION_ARGS) if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); tm_trunc(tm, fmt); fsec = 0; @@ -498,18 +509,18 @@ ora_trunc(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMP(result); } -/* +/* * Internal implementation of Oracle's NEXT_DAY function. * First weekday specified by int type. */ Datum next_day_by_index(PG_FUNCTION_ARGS) { - Timestamp day = PG_GETARG_TIMESTAMP(0); - int idx = PG_GETARG_INT32(1); + Timestamp day = PG_GETARG_TIMESTAMP(0); + int idx = PG_GETARG_INT32(1); Timestamp date; - int off = 0; - Timestamp result; + int off = 0; + Timestamp result; CHECK_SEQ_SEARCH((idx < 1 || 7 < idx) ? -1 : 0, "DAY/Day/day"); TMODULO(day, date, USECS_PER_DAY); @@ -521,8 +532,8 @@ next_day_by_index(PG_FUNCTION_ARGS) date += POSTGRES_EPOCH_JDATE; /* j2day returns 0..6 as Sun..Sat */ - off = (idx - 1) - j2day((int)date); - date = (off <= 0) ? date+off+7 : date + off; + off = (idx - 1) - j2day((int) date); + date = (off <= 0) ? date + off + 7 : date + off; result = date - POSTGRES_EPOCH_JDATE; result = result * USECS_PER_DAY + day; @@ -530,23 +541,23 @@ next_day_by_index(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMP(result); } -/* +/* * Internal implementation of Oracle's NEXT_DAY function. * First weekday specified by character type. */ Datum next_day(PG_FUNCTION_ARGS) { - Timestamp day = PG_GETARG_TIMESTAMP(0); - text *day_txt = PG_GETARG_TEXT_PP(1); + Timestamp day = PG_GETARG_TIMESTAMP(0); + text *day_txt = PG_GETARG_TEXT_PP(1); const char *str = VARDATA_ANY(day_txt); - int len = VARSIZE_ANY_EXHDR(day_txt); - char *changstr =NULL; - int changstrlen =0; - Timestamp date; - int off = 0; - Timestamp result; - int d = -1; + int len = VARSIZE_ANY_EXHDR(day_txt); + char *changstr = NULL; + int changstrlen = 0; + Timestamp date; + int off = 0; + Timestamp result; + int d = -1; if (len >= 3 && (d = ora_seq_prefix_search(str, ora_days, 3)) >= 0) goto found; @@ -556,7 +567,7 @@ next_day(PG_FUNCTION_ARGS) if ((d = weekday_search(&WEEKDAYS[0], changstr, changstrlen)) >= 0) goto found; - + CHECK_SEQ_SEARCH(-1, "DAY/Day/day"); found: @@ -569,8 +580,8 @@ next_day(PG_FUNCTION_ARGS) date += POSTGRES_EPOCH_JDATE; /* j2day returns 0..6 as Sun..Sat */ - off = d - j2day((int)date); - date = (off <= 0) ? date+off+7 : date + off; + off = d - j2day((int) date); + date = (off <= 0) ? date + off + 7 : date + off; result = date - POSTGRES_EPOCH_JDATE; result = result * USECS_PER_DAY + day; @@ -587,36 +598,36 @@ next_day(PG_FUNCTION_ARGS) Datum ora_new_time(PG_FUNCTION_ARGS) { - Timestamp day = PG_GETARG_TIMESTAMP(0); - text *txtimezone1 = PG_GETARG_TEXT_PP(1); - text *txtimezone2 = PG_GETARG_TEXT_PP(2); + Timestamp day = PG_GETARG_TIMESTAMP(0); + text *txtimezone1 = PG_GETARG_TEXT_PP(1); + text *txtimezone2 = PG_GETARG_TEXT_PP(2); - char *strtimezone1 = text_to_cstring(txtimezone1); - char *strtimezone2 = text_to_cstring(txtimezone2); - Timestamp result; + char *strtimezone1 = text_to_cstring(txtimezone1); + char *strtimezone2 = text_to_cstring(txtimezone2); + Timestamp result; struct pg_tm tt, - *tm = &tt; + *tm = &tt; fsec_t fsec; int tz1; int tz2; - - if((tz1 = ora_timezone_name_to_num(strtimezone1)) == -1) + + if ((tz1 = ora_timezone_name_to_num(strtimezone1)) == -1) { ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("Invalid time zone"))); + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("Invalid time zone"))); } - if((tz2 = ora_timezone_name_to_num(strtimezone2)) == -1) + if ((tz2 = ora_timezone_name_to_num(strtimezone2)) == -1) { ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("Invalid time zone"))); + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("Invalid time zone"))); } if (timestamp2tm(day, NULL, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); tz1 = tz1 - tz2; tm2timestamp(tm, fsec, &tz1, &result); @@ -630,65 +641,70 @@ ora_new_time(PG_FUNCTION_ARGS) Datum ora_tz_offset(PG_FUNCTION_ARGS) { - text *res; - bool ispositive = true; - pg_tz *timezonedat; + text *res; + bool ispositive = true; + pg_tz *timezonedat; struct pg_tm tt, - *tm = &tt; + *tm = &tt; fsec_t fsec; - int64 tz; - int hourdat,mindate; - char *reschar = (char *) palloc(8); - text *txtimezone1 = PG_GETARG_TEXT_PP(0); + int64 tz; + int hourdat, + mindate; + char *reschar = (char *) palloc(8); + text *txtimezone1 = PG_GETARG_TEXT_PP(0); - char *strtimezone1 = text_to_cstring(txtimezone1); + char *strtimezone1 = text_to_cstring(txtimezone1); TimestampTz timestamp = GetCurrentTimestamp(); /* to get pg_tz struct data of char */ - if((timezonedat = ora_make_timezone(&strtimezone1)) == NULL) + if ((timezonedat = ora_make_timezone(&strtimezone1)) == NULL) { ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timezone is not ok"))); + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timezone is not ok"))); } if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); tz = DetermineTimeZoneOffset(tm, timezonedat); - if(tz <= 0) + if (tz <= 0) { tz = tz * (-1); ispositive = false; } hourdat = tz / 3600; - mindate = (tz - hourdat*3600) / 60; + mindate = (tz - hourdat * 3600) / 60; - snprintf(reschar,8,"%s%02d:%02d",ispositive ? "-":"+",hourdat,mindate); + snprintf(reschar, 8, "%s%02d:%02d", ispositive ? "-" : "+", hourdat, mindate); res = cstring_to_text(reschar); PG_RETURN_TEXT_P(res); } -/* +/* * Returns number of months between dates 'time1' and 'time2'. */ Datum months_between(PG_FUNCTION_ARGS) { - Timestamp time1 = PG_GETARG_TIMESTAMP(0); - Timestamp time2 = PG_GETARG_TIMESTAMP(1); + Timestamp time1 = PG_GETARG_TIMESTAMP(0); + Timestamp time2 = PG_GETARG_TIMESTAMP(1); - Timestamp date1; - Timestamp date2; + Timestamp date1; + Timestamp date2; - int y1, m1, d1; - int y2, m2, d2; + int y1, + m1, + d1; + int y2, + m2, + d2; - float8 result; + float8 result; TMODULO(time1, date1, USECS_PER_DAY); if (time1 < INT64CONST(0)) @@ -704,10 +720,10 @@ months_between(PG_FUNCTION_ARGS) } date1 += POSTGRES_EPOCH_JDATE; - j2date((int)date1, &y1, &m1, &d1); + j2date((int) date1, &y1, &m1, &d1); date2 += POSTGRES_EPOCH_JDATE; - j2date((int)date2, &y2, &m2, &d2); + j2date((int) date2, &y2, &m2, &d2); /* Ignore day components for last days, or based on a 31-day month. */ if (d1 == days_of_month(y1, m1) && d2 == days_of_month(y2, m2)) @@ -716,7 +732,7 @@ months_between(PG_FUNCTION_ARGS) { result = (y1 - y2) * 12 + (m1 - m2) + (d1 - d2) / 31.0; /* If the days are different, you need to compare the time difference */ - if(d1 != d2) + if (d1 != d2) result += ((time1 - time2) / 31.0) / USECS_PER_DAY; } PG_RETURN_FLOAT8(result); @@ -728,27 +744,27 @@ months_between(PG_FUNCTION_ARGS) Datum ora_from_tz(PG_FUNCTION_ARGS) { - Timestamp day = PG_GETARG_TIMESTAMP(0); - text *day_txt = PG_GETARG_TEXT_PP(1); + Timestamp day = PG_GETARG_TIMESTAMP(0); + text *day_txt = PG_GETARG_TEXT_PP(1); TimestampTz result; struct pg_tm tt, - *tm = &tt; - pg_tz *timezonedat; - char *day_char = text_to_cstring(day_txt); + *tm = &tt; + pg_tz *timezonedat; + char *day_char = text_to_cstring(day_txt); fsec_t fsec; int tz; - if((timezonedat = ora_make_timezone(&day_char)) == NULL) + if ((timezonedat = ora_make_timezone(&day_char)) == NULL) { ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timezone is not ok"))); + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timezone is not ok"))); } if (timestamp2tm(day, NULL, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); tz = DetermineTimeZoneOffset(tm, timezonedat); tm2timestamp(tm, fsec, &tz, &result); @@ -757,14 +773,14 @@ ora_from_tz(PG_FUNCTION_ARGS) } /* - * Extracts the UTC(Coordinated Universal Time--formerly Greenwich Mean Time) + * Extracts the UTC(Coordinated Universal Time--formerly Greenwich Mean Time) * from a datetime value with time zone offset or time zone region name. */ Datum ora_sys_extract_utc(PG_FUNCTION_ARGS) { TimestampTz timestamp = PG_GETARG_TIMESTAMP(0); - Timestamp result = timestamp; + Timestamp result = timestamp; PG_RETURN_TIMESTAMP(result); } @@ -775,10 +791,10 @@ ora_sys_extract_utc(PG_FUNCTION_ARGS) Datum ora_sessiontimezone(PG_FUNCTION_ARGS) { - char *res; - text *out; + char *res; + text *out; - res = (char *)pg_get_timezone_name(session_timezone); + res = (char *) pg_get_timezone_name(session_timezone); out = cstring_to_text(res); PG_RETURN_TEXT_P(out); @@ -792,8 +808,8 @@ ora_sessiontimezone(PG_FUNCTION_ARGS) Datum to_oradate1(PG_FUNCTION_ARGS) { - text *date_txt = PG_GETARG_TEXT_P(0); - Oid collid = PG_GET_COLLATION(); + text *date_txt = PG_GETARG_TEXT_P(0); + Oid collid = PG_GET_COLLATION(); Timestamp result; struct pg_tm tm; fsec_t fsec; @@ -813,9 +829,9 @@ to_oradate1(PG_FUNCTION_ARGS) Datum to_oradate2(PG_FUNCTION_ARGS) { - text *date_txt = PG_GETARG_TEXT_P(0); - text *fmt = PG_GETARG_TEXT_P(1); - Oid collid = PG_GET_COLLATION(); + text *date_txt = PG_GETARG_TEXT_P(0); + text *fmt = PG_GETARG_TEXT_P(1); + Oid collid = PG_GET_COLLATION(); Timestamp result; struct pg_tm tm; fsec_t fsec; @@ -838,13 +854,14 @@ to_oradate3(PG_FUNCTION_ARGS) text *date_txt = PG_GETARG_TEXT_P(0); text *fmt = PG_GETARG_TEXT_P(1); text *nlsparam = PG_GETARG_TEXT_P(2); - Oid collid = PG_GET_COLLATION(); + Oid collid = PG_GET_COLLATION(); Timestamp result; struct pg_tm tm; fsec_t fsec; /* TODO */ - char *p; + char *p; + p = text_to_cstring(nlsparam); #if 0 @@ -855,9 +872,9 @@ to_oradate3(PG_FUNCTION_ARGS) #endif if (p && strlen(p) != 0) - /* make compiler quiet */ + /* make compiler quiet */ - ora_do_to_timestamp(date_txt, fmt, collid, false, &tm, &fsec, NULL, NULL, NULL, true); + ora_do_to_timestamp(date_txt, fmt, collid, false, &tm, &fsec, NULL, NULL, NULL, true); /* no timezone and no fractional second */ fsec = 0; @@ -878,7 +895,7 @@ Datum to_oratimestamp1(PG_FUNCTION_ARGS) { text *date_txt = PG_GETARG_TEXT_P(0); - Oid collid = PG_GET_COLLATION(); + Oid collid = PG_GET_COLLATION(); Timestamp result; struct pg_tm tm; fsec_t fsec; @@ -899,7 +916,7 @@ to_oratimestamp2(PG_FUNCTION_ARGS) { text *date_txt = PG_GETARG_TEXT_P(0); text *fmt = PG_GETARG_TEXT_P(1); - Oid collid = PG_GET_COLLATION(); + Oid collid = PG_GET_COLLATION(); Timestamp result; struct pg_tm tm; fsec_t fsec; @@ -921,23 +938,23 @@ to_oratimestamp3(PG_FUNCTION_ARGS) text *date_txt = PG_GETARG_TEXT_P(0); text *fmt = PG_GETARG_TEXT_P(1); text *nlsparam = PG_GETARG_TEXT_P(2); - Oid collid = PG_GET_COLLATION(); + Oid collid = PG_GET_COLLATION(); Timestamp result; struct pg_tm tm; fsec_t fsec; - char *p; + char *p; + p = text_to_cstring(nlsparam); /* - if (strlen(p) != 0) - ereport(WARNING, - (errcode(ERRCODE_UNTERMINATED_C_STRING), - errmsg("function \"to_date\" not support the parameter of \"nlsparam\"."))); - */ + * if (strlen(p) != 0) ereport(WARNING, + * (errcode(ERRCODE_UNTERMINATED_C_STRING), errmsg("function \"to_date\" + * not support the parameter of \"nlsparam\"."))); + */ if (p && strlen(p) != 0) - /* make compiler quiet */ + /* make compiler quiet */ - ora_do_to_timestamp(date_txt, fmt, collid, false, &tm, &fsec, NULL, NULL, NULL, true); + ora_do_to_timestamp(date_txt, fmt, collid, false, &tm, &fsec, NULL, NULL, NULL, true); /* no time zone */ if (tm2timestamp(&tm, fsec, NULL, &result) != 0) @@ -957,7 +974,7 @@ Datum to_oratimestamptz1(PG_FUNCTION_ARGS) { text *date_txt = PG_GETARG_TEXT_P(0); - Oid collid = PG_GET_COLLATION(); + Oid collid = PG_GET_COLLATION(); Timestamp result; int tz; struct pg_tm tm; @@ -968,7 +985,7 @@ to_oratimestamptz1(PG_FUNCTION_ARGS) if (tm.tm_zone) { - int dterr = DecodeTimezone((char *)tm.tm_zone, &tz); + int dterr = DecodeTimezone((char *) tm.tm_zone, &tz); if (dterr) DateTimeParseError(dterr, &extra, text_to_cstring(date_txt), "timestamptz", NULL); @@ -983,13 +1000,13 @@ to_oratimestamptz1(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMP(result); } - + Datum to_oratimestamptz2(PG_FUNCTION_ARGS) { text *date_txt = PG_GETARG_TEXT_P(0); text *fmt = PG_GETARG_TEXT_P(1); - Oid collid = PG_GET_COLLATION(); + Oid collid = PG_GET_COLLATION(); Timestamp result; int tz; struct pg_tm tm; @@ -1000,7 +1017,7 @@ to_oratimestamptz2(PG_FUNCTION_ARGS) if (tm.tm_zone) { - int dterr = DecodeTimezone((char *)tm.tm_zone, &tz); + int dterr = DecodeTimezone((char *) tm.tm_zone, &tz); if (dterr) DateTimeParseError(dterr, &extra, text_to_cstring(date_txt), "timestamptz", NULL); @@ -1009,13 +1026,13 @@ to_oratimestamptz2(PG_FUNCTION_ARGS) tz = DetermineTimeZoneOffset(&tm, session_timezone); #if 0 - if(tm.tm_gmtoff == -1) + if (tm.tm_gmtoff == -1) { tm.tm_gmtoff = 0; tz = DetermineTimeZoneOffset(&tm, session_timezone); } else - tz = (int)tm.tm_gmtoff * (-1); + tz = (int) tm.tm_gmtoff * (-1); #endif if (tm2timestamp(&tm, fsec, &tz, &result) != 0) @@ -1032,30 +1049,30 @@ to_oratimestamptz3(PG_FUNCTION_ARGS) text *date_txt = PG_GETARG_TEXT_P(0); text *fmt = PG_GETARG_TEXT_P(1); text *nlsparam = PG_GETARG_TEXT_P(2); - Oid collid = PG_GET_COLLATION(); + Oid collid = PG_GET_COLLATION(); Timestamp result; int tz; struct pg_tm tm; fsec_t fsec; DateTimeErrorExtra extra; - char *p; + char *p; + p = text_to_cstring(nlsparam); if (p && strlen(p) != 0) - /* make compile quiet */ + /* make compile quiet */ - /* - if (strlen(p) != 0) - ereport(WARNING, - (errcode(ERRCODE_UNTERMINATED_C_STRING), - errmsg("function \"to_date\" not support the parameter of \"nlsparam\"."))); - */ + /* + * if (strlen(p) != 0) ereport(WARNING, + * (errcode(ERRCODE_UNTERMINATED_C_STRING), errmsg("function + * \"to_date\" not support the parameter of \"nlsparam\"."))); + */ - ora_do_to_timestamp(date_txt, fmt, collid, false, &tm, &fsec, NULL, NULL, NULL, true); + ora_do_to_timestamp(date_txt, fmt, collid, false, &tm, &fsec, NULL, NULL, NULL, true); if (tm.tm_zone) { - int dterr = DecodeTimezone((char *)tm.tm_zone, &tz); + int dterr = DecodeTimezone((char *) tm.tm_zone, &tz); if (dterr) DateTimeParseError(dterr, &extra, text_to_cstring(date_txt), "timestamptz", NULL); @@ -1074,18 +1091,18 @@ to_oratimestamptz3(PG_FUNCTION_ARGS) /* * Compatible with Oracle's TO_CHAR(DATETIME) function. * - * Converts a DATETIME or INTERVAL data type to a value of VARCHAR2 data + * Converts a DATETIME or INTERVAL data type to a value of VARCHAR2 data * type in the format specified by the date format fmt. */ Datum oradate_to_char1(PG_FUNCTION_ARGS) { Timestamp dt = PG_GETARG_TIMESTAMP(0); - VarChar *res; + VarChar *res; res = DatumGetVarCharP(DirectFunctionCall2(timestamp_to_char, - TimestampGetDatum(dt), - PointerGetDatum(cstring_to_text(nls_date_format)))); + TimestampGetDatum(dt), + PointerGetDatum(cstring_to_text(nls_date_format)))); PG_RETURN_VARCHAR_P(res); } @@ -1095,11 +1112,11 @@ oradate_to_char2(PG_FUNCTION_ARGS) { Timestamp dt = PG_GETARG_TIMESTAMP(0); text *fmt = PG_GETARG_TEXT_P(1); - VarChar *res; + VarChar *res; res = DatumGetVarCharP(DirectFunctionCall2(timestamp_to_char, - TimestampGetDatum(dt), - PointerGetDatum(fmt))); + TimestampGetDatum(dt), + PointerGetDatum(fmt))); PG_RETURN_VARCHAR_P(res); } @@ -1109,17 +1126,17 @@ oradate_to_char3(PG_FUNCTION_ARGS) { Timestamp dt = PG_GETARG_TIMESTAMP(0); text *fmt = PG_GETARG_TEXT_P(1); - VarChar *res; + VarChar *res; /* - ereport(WARNING, - (errcode(ERRCODE_UNTERMINATED_C_STRING), - errmsg("function \"to_char\" not support the parameter of \"nlsparam\"."))); - */ + * ereport(WARNING, (errcode(ERRCODE_UNTERMINATED_C_STRING), + * errmsg("function \"to_char\" not support the parameter of + * \"nlsparam\"."))); + */ res = DatumGetVarCharP(DirectFunctionCall2(timestamp_to_char, - TimestampGetDatum(dt), - PointerGetDatum(fmt))); + TimestampGetDatum(dt), + PointerGetDatum(fmt))); PG_RETURN_VARCHAR_P(res); } @@ -1135,7 +1152,7 @@ oratimestamp_to_char1(PG_FUNCTION_ARGS) struct pg_tm *tm; int thisdate; - VarChar *vres; + VarChar *vres; if ((VARSIZE(fmt) - VARHDRSZ) <= 0 || TIMESTAMP_NOT_FINITE(dt)) PG_RETURN_NULL(); @@ -1157,7 +1174,7 @@ oratimestamp_to_char1(PG_FUNCTION_ARGS) if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION()))) PG_RETURN_NULL(); - vres = (VarChar *)res; + vres = (VarChar *) res; PG_RETURN_VARCHAR_P(vres); } @@ -1171,7 +1188,7 @@ oratimestamp_to_char2(PG_FUNCTION_ARGS) struct pg_tm pgtm; struct pg_tm *tm; int thisdate; - VarChar *vres; + VarChar *vres; if ((VARSIZE(fmt) - VARHDRSZ) <= 0 || TIMESTAMP_NOT_FINITE(dt)) PG_RETURN_NULL(); @@ -1193,7 +1210,7 @@ oratimestamp_to_char2(PG_FUNCTION_ARGS) if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION()))) PG_RETURN_NULL(); - vres = (VarChar *)res; + vres = (VarChar *) res; PG_RETURN_VARCHAR_P(vres); } @@ -1207,7 +1224,7 @@ oratimestamp_to_char3(PG_FUNCTION_ARGS) struct pg_tm pgtm; struct pg_tm *tm; int thisdate; - VarChar *vres; + VarChar *vres; if ((VARSIZE(fmt) - VARHDRSZ) <= 0 || TIMESTAMP_NOT_FINITE(dt)) PG_RETURN_NULL(); @@ -1229,7 +1246,7 @@ oratimestamp_to_char3(PG_FUNCTION_ARGS) if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION()))) PG_RETURN_NULL(); - vres = (VarChar *)res; + vres = (VarChar *) res; PG_RETURN_VARCHAR_P(vres); } @@ -1237,11 +1254,11 @@ Datum oratimestamptz_to_char1(PG_FUNCTION_ARGS) { TimestampTz dt = PG_GETARG_TIMESTAMP(0); - VarChar *res; + VarChar *res; res = DatumGetVarCharP(DirectFunctionCall2(timestamptz_to_char, - TimestampGetDatum(dt), - PointerGetDatum(cstring_to_text(nls_timestamp_tz_format)))); + TimestampGetDatum(dt), + PointerGetDatum(cstring_to_text(nls_timestamp_tz_format)))); PG_RETURN_VARCHAR_P(res); } @@ -1251,10 +1268,11 @@ oratimestamptz_to_char2(PG_FUNCTION_ARGS) { TimestampTz dt = PG_GETARG_TIMESTAMP(0); text *fmt = PG_GETARG_TEXT_P(1); - VarChar *res; + VarChar *res; + res = DatumGetVarCharP(DirectFunctionCall2(timestamptz_to_char, - TimestampGetDatum(dt), - PointerGetDatum(fmt))); + TimestampGetDatum(dt), + PointerGetDatum(fmt))); PG_RETURN_VARCHAR_P(res); } @@ -1263,17 +1281,17 @@ oratimestamptz_to_char3(PG_FUNCTION_ARGS) { TimestampTz dt = PG_GETARG_TIMESTAMP(0); text *fmt = PG_GETARG_TEXT_P(1); - VarChar *res; + VarChar *res; /* - ereport(WARNING, - (errcode(ERRCODE_UNTERMINATED_C_STRING), - errmsg("function \"to_char\" not support the parameter of \"nlsparam\"."))); - */ + * ereport(WARNING, (errcode(ERRCODE_UNTERMINATED_C_STRING), + * errmsg("function \"to_char\" not support the parameter of + * \"nlsparam\"."))); + */ res = DatumGetVarCharP(DirectFunctionCall2(timestamptz_to_char, - TimestampGetDatum(dt), - PointerGetDatum(fmt))); + TimestampGetDatum(dt), + PointerGetDatum(fmt))); PG_RETURN_VARCHAR_P(res); } @@ -1281,12 +1299,12 @@ Datum oratimestampltz_to_char1(PG_FUNCTION_ARGS) { TimestampTz dt = PG_GETARG_TIMESTAMP(0); - text *res; - + text *res; + res = DatumGetTextP(DirectFunctionCall2(timestamptz_to_char, - TimestampGetDatum(dt), - PointerGetDatum(cstring_to_text(nls_timestamp_format)))); - + TimestampGetDatum(dt), + PointerGetDatum(cstring_to_text(nls_timestamp_format)))); + PG_RETURN_VARCHAR_P(res); } @@ -1295,11 +1313,12 @@ oratimestampltz_to_char2(PG_FUNCTION_ARGS) { TimestampTz dt = PG_GETARG_TIMESTAMP(0); text *fmt = PG_GETARG_TEXT_P(1); - text *res; + text *res; + res = DatumGetTextP(DirectFunctionCall2(timestamptz_to_char, - TimestampGetDatum(dt), - PointerGetDatum(fmt))); - + TimestampGetDatum(dt), + PointerGetDatum(fmt))); + PG_RETURN_VARCHAR_P(res); } @@ -1308,18 +1327,18 @@ oratimestampltz_to_char3(PG_FUNCTION_ARGS) { TimestampTz dt = PG_GETARG_TIMESTAMP(0); text *fmt = PG_GETARG_TEXT_P(1); - text *res; + text *res; /* - ereport(WARNING, - (errcode(ERRCODE_UNTERMINATED_C_STRING), - errmsg("function \"to_char\" not support the parameter of \"nlsparam\"."))); - */ + * ereport(WARNING, (errcode(ERRCODE_UNTERMINATED_C_STRING), + * errmsg("function \"to_char\" not support the parameter of + * \"nlsparam\"."))); + */ res = DatumGetTextP(DirectFunctionCall2(timestamptz_to_char, - TimestampGetDatum(dt), - PointerGetDatum(fmt))); - + TimestampGetDatum(dt), + PointerGetDatum(fmt))); + PG_RETURN_VARCHAR_P(res); } @@ -1327,14 +1346,15 @@ Datum oradsinterval_to_char1(PG_FUNCTION_ARGS) { Interval *intervaldate = PG_GETARG_INTERVAL_P(0); - text *res; - char *aa; - VarChar *vres; + text *res; + char *aa; + VarChar *vres; + aa = DatumGetCString(DirectFunctionCall1(dsinterval_out, - IntervalPGetDatum(intervaldate))); + IntervalPGetDatum(intervaldate))); res = cstring_to_text(aa); - vres = (VarChar *)res; + vres = (VarChar *) res; PG_RETURN_VARCHAR_P(vres); } @@ -1342,40 +1362,41 @@ Datum orayminterval_to_char1(PG_FUNCTION_ARGS) { Interval *intervaldate = PG_GETARG_INTERVAL_P(0); - text *res; - char *aa; - VarChar *vres; + text *res; + char *aa; + VarChar *vres; + aa = DatumGetCString(DirectFunctionCall1(yminterval_out, - IntervalPGetDatum(intervaldate))); + IntervalPGetDatum(intervaldate))); res = cstring_to_text(aa); - vres = (VarChar *)res; + vres = (VarChar *) res; PG_RETURN_VARCHAR_P(vres); } -/* +/* * Compatible oracle 'TO_YMINTERVAL' function. */ Datum to_yminterval(PG_FUNCTION_ARGS) { - text *interval_txt = PG_GETARG_TEXT_P(0); - char *interval_str; - int32 typmod; + text *interval_txt = PG_GETARG_TEXT_P(0); + char *interval_str; + int32 typmod; Interval *result; typmod = INTERVAL_TYPMOD(ORACLE_MAX_INTERVAL_PRECISION, INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH)); interval_str = text_to_cstring(interval_txt); - result = DatumGetIntervalP(DirectFunctionCall3(yminterval_in, - CStringGetDatum(interval_str), + result = DatumGetIntervalP(DirectFunctionCall3(yminterval_in, + CStringGetDatum(interval_str), ObjectIdGetDatum(InvalidOid), Int32GetDatum(typmod))); PG_RETURN_INTERVAL_P(result); } -/* +/* * Compatible oracle 'NUMTOYMINTERVAL' function * * if 'interval_unit' is 'month' then do round() according to @@ -1383,15 +1404,15 @@ to_yminterval(PG_FUNCTION_ARGS) * * if 'interval_unit' is 'year' then 'interval_val' multiply 12 and * do round() according to the first place after the decimal point. - * + * */ Datum numtoyminterval(PG_FUNCTION_ARGS) { - float8 interval_val = PG_GETARG_FLOAT8(0); - text *interval_unit = PG_GETARG_TEXT_P(1); - char *interval_unit_str; - int interval_unit_len = 0; + float8 interval_val = PG_GETARG_FLOAT8(0); + text *interval_unit = PG_GETARG_TEXT_P(1); + char *interval_unit_str; + int interval_unit_len = 0; Interval *result; interval_unit_str = text_to_cstring(interval_unit); @@ -1424,10 +1445,10 @@ numtoyminterval(PG_FUNCTION_ARGS) /* positive */ if (interval_val > 0) { - if( interval_val < 0.01) + if (interval_val < 0.01) interval_val = 0; - - if(interval_val >= 0.01 && interval_val < 1) + + if (interval_val >= 0.01 && interval_val < 1) interval_val = 1; } @@ -1436,19 +1457,19 @@ numtoyminterval(PG_FUNCTION_ARGS) { if (interval_val > -0.5 && interval_val < 0) interval_val = 0; - + if (interval_val <= -0.5 && interval_val > -1) interval_val = 1; } /* round to first place after the decimal point */ if (interval_val > 0) - interval_val = ((int)(interval_val*10) + 5) / 10; + interval_val = ((int) (interval_val * 10) + 5) / 10; if (interval_val < 0) { interval_val = interval_val * (-1); - interval_val = ((int)(interval_val*10) + 5) / 10; + interval_val = ((int) (interval_val * 10) + 5) / 10; interval_val = interval_val * (-1); } @@ -1457,19 +1478,19 @@ numtoyminterval(PG_FUNCTION_ARGS) result->month = interval_val; result->day = 0; result->time = 0; - + PG_RETURN_INTERVAL_P(result); } -/* +/* * Compatible oracle TO_DSINTERVAL function. */ Datum to_dsinterval(PG_FUNCTION_ARGS) { - text *interval_txt = PG_GETARG_TEXT_P(0); - char *interval_str; - int32 typmod; + text *interval_txt = PG_GETARG_TEXT_P(0); + char *interval_str; + int32 typmod; Interval *result; typmod = INTERVAL_DS_TYPMOD(ORACLE_MAX_INTERVAL_PRECISION, @@ -1489,16 +1510,16 @@ to_dsinterval(PG_FUNCTION_ARGS) PG_RETURN_INTERVAL_P(result); } -/* +/* * Compatible oracle 'NUMTODSINTERVAL' function */ Datum numtodsinterval(PG_FUNCTION_ARGS) { - float8 interval_val = PG_GETARG_FLOAT8(0); - text *interval_unit = PG_GETARG_TEXT_P(1); - char *interval_unit_str; - int interval_unit_len = 0; + float8 interval_val = PG_GETARG_FLOAT8(0); + text *interval_unit = PG_GETARG_TEXT_P(1); + char *interval_unit_str; + int interval_unit_len = 0; Interval *result; interval_unit_str = text_to_cstring(interval_unit); @@ -1539,8 +1560,8 @@ numtodsinterval(PG_FUNCTION_ARGS) result = (Interval *) palloc(sizeof(Interval)); result->month = 0; - result->day = (int)(interval_val / USECS_PER_DAY); - result->time = (interval_val - ((int)(interval_val / USECS_PER_DAY)) * USECS_PER_DAY); + result->day = (int) (interval_val / USECS_PER_DAY); + result->time = (interval_val - ((int) (interval_val / USECS_PER_DAY)) * USECS_PER_DAY); PG_RETURN_INTERVAL_P(result); } @@ -1552,36 +1573,37 @@ ora_make_timezone(char **newval) long gmtoffset; int hours = 0; int minu = 0; - int res = 0; + int res = 0; /* * Try it as a numeric number of hours (possibly fractional). */ - res = sscanf(*newval,"%d:%d",&hours,&minu); - if(res == 2) + res = sscanf(*newval, "%d:%d", &hours, &minu); + if (res == 2) { - if(hours < -12 || hours >14) + if (hours < -12 || hours > 14) ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timezone hour between -12 and 14"))); - if(minu < 0 || minu > 59 || (hours == 14 && minu > 0)) + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timezone hour between -12 and 14"))); + if (minu < 0 || minu > 59 || (hours == 14 && minu > 0)) ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timezone minute between 0 and 59"))); - if(hours < 0) + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timezone minute between 0 and 59"))); + if (hours < 0) minu *= -1; gmtoffset = -(hours * SECS_PER_HOUR + minu * SECS_PER_MINUTE); new_tz = pg_tzset_offset(gmtoffset); } else { - if(res > 0) + if (res > 0) ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timezone type is error"))); + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timezone type is error"))); + /* - * Otherwise assume it is a timezone name, and try to load it. - */ + * Otherwise assume it is a timezone name, and try to load it. + */ new_tz = pg_tzset(*newval); if (!new_tz) { @@ -1592,8 +1614,8 @@ ora_make_timezone(char **newval) if (!pg_tz_acceptable(new_tz)) { ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("time zone \"%s\" appears to use leap seconds", + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("time zone \"%s\" appears to use leap seconds", *newval))); } } @@ -1605,6 +1627,7 @@ ora_make_timezone(char **newval) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("UTC timezone offset is out of range."))); } + /* * Pass back data for assign_timezone to use */ @@ -1615,27 +1638,32 @@ ora_make_timezone(char **newval) * Make a timestamp according to the year, month and day. */ static Timestamp -iso_year (int y, int m, int d) +iso_year(int y, int m, int d) { - Timestamp result, result2, day; - int off; + Timestamp result, + result2, + day; + int off; - result = DATE2J(y,1,1); - day = DATE2J(y,m,d); + result = DATE2J(y, 1, 1); + day = DATE2J(y, m, d); off = 4 - J2DAY(result); - result += off + ((off >= 0) ? - 3: + 4); // to monday + result += off + ((off >= 0) ? -3 : +4); + /* to monday */ if (result > day) { - result = DATE2J(y-1,1,1); + result = DATE2J(y - 1, 1, 1); off = 4 - J2DAY(result); - result += off + ((off >= 0) ? - 3: + 4); // to monday + result += off + ((off >= 0) ? -3 : +4); + /* to monday */ } if (((day - result) / 7 + 1) > 52) { - result2 = DATE2J(y+1,1,1); + result2 = DATE2J(y + 1, 1, 1); off = 4 - J2DAY(result2); - result2 += off + ((off >= 0) ? - 3: + 4); // to monday + result2 += off + ((off >= 0) ? -3 : +4); + /* to monday */ if (day >= result2) return result2; } @@ -1650,20 +1678,22 @@ static int64 sys_time_zone() { -#ifdef _WIN64 - size_t a; - char time_zone[128]; - long diff_secs; +#ifdef _WIN64 + size_t a; + char time_zone[128]; + long diff_secs; + _get_tzname(&a, time_zone, 128, 0); _get_timezone(&diff_secs); - return (int64)diff_secs * (-1); -#else - struct tm *gmt; - time_t t; - t = time(NULL); + return (int64) diff_secs * (-1); +#else + struct tm *gmt; + time_t t; + + t = time(NULL); gmt = localtime(&t); - return (int64)gmt->tm_gmtoff; -#endif + return (int64) gmt->tm_gmtoff; +#endif } /* @@ -1672,7 +1702,7 @@ sys_time_zone() static int days_of_month(int y, int m) { - int days; + int days; if (m < 0 || 12 < m) ereport(ERROR, @@ -1681,14 +1711,14 @@ days_of_month(int y, int m) days = month_days[m - 1]; if (m == 2 && (y % 400 == 0 || (y % 4 == 0 && y % 100 != 0))) - days += 1; /* February 29 in leap year */ + days += 1; /* February 29 in leap year */ return days; } static int ora_seq_search(const char *name, const char *const array[], int max) { - int i; + int i; if (!*name) return -1; @@ -1699,62 +1729,68 @@ ora_seq_search(const char *name, const char *const array[], int max) pg_strncasecmp(name, array[i], max) == 0) return i; } - return -1; /* not found */ + return -1; /* not found */ } static Timestamp ora_date_round(Timestamp day, int f) { - int y, m, d, z; - Timestamp result; + int y, + m, + d, + z; + Timestamp result; j2date(day + POSTGRES_EPOCH_JDATE, &y, &m, &d); switch (f) { - CASE_fmt_CC - if (y > 0) - result = DATE2J((y/100)*100+(day < DATE2J((y/100)*100+50,1,1) ?1:101),1,1); + CASE_fmt_CC + if (y > 0) + result = DATE2J((y / 100) * 100 + (day < DATE2J((y / 100) * 100 + 50, 1, 1) ? 1 : 101), 1, 1); else - result = DATE2J((y/100)*100+(day < DATE2J((y/100)*100-50+1,1,1) ?-99:1),1,1); + result = DATE2J((y / 100) * 100 + (day < DATE2J((y / 100) * 100 - 50 + 1, 1, 1) ? -99 : 1), 1, 1); break; - CASE_fmt_YYYY - result = DATE2J(y+(day= 52) + if (((day - DATE2J(y, 1, 1)) / 7 + 1) >= 52) { - bool overl = ((date2j(y+2,1,1)-date2j(y+1,1,1)) == 366); - bool isSaturday = (J2DAY(day) == 6); + bool overl = ((date2j(y + 2, 1, 1) - date2j(y + 1, 1, 1)) == 366); + bool isSaturday = (J2DAY(day) == 6); - Timestamp iy2 = iso_year(y+2, 1, 8); - Timestamp day1 = DATE2J(y+1,1,1); - // exception saturdays + Timestamp iy2 = iso_year(y + 2, 1, 8); + Timestamp day1 = DATE2J(y + 1, 1, 1); + + /* exception saturdays */ if (iy1 >= (day1) && day >= day1 - 2 && isSaturday) { - result = overl?iy2:iy1; + result = overl ? iy2 : iy1; } - // iso year stars in last year and day >= iso year + /* iso year stars in last year and day >= iso year */ else if (iy1 <= (day1) && day >= iy1 - 3) { - Timestamp cmp = iy1 - (iy1 < day1?0:1); - int d1 = J2DAY(day1); - // some exceptions + Timestamp cmp = iy1 - (iy1 < day1 ? 0 : 1); + int d1 = J2DAY(day1); + + /* some exceptions */ if ((day >= cmp - 2) && (!(d1 == 3 && overl))) { - // if year don't starts in thursday + /* if year don't starts in thursday */ if ((d1 < 4 && J2DAY(day) != 5 && !isSaturday) - ||(d1 == 2 && isSaturday && overl)) + || (d1 == 2 && isSaturday && overl)) { result = iy2; } @@ -1764,43 +1800,45 @@ ora_date_round(Timestamp day, int f) } break; } - CASE_fmt_MON - result = DATE2J(y,m+(day= 52) + CASE_fmt_IW { - // only for last iso week - Timestamp isoyear = iso_year(y+1, 1, 8); - if (isoyear > (DATE2J(y+1,1,1)-1)) - if (day > isoyear - 7) - { - int tmpd = J2DAY(day); - result -= (tmpd == 0 || tmpd > 4?7:0); - } + z = (day - iso_year(y, m, d)) % 7; + result = day - z + (z < 4 ? 0 : 7); + if (((day - DATE2J(y, 1, 1)) / 7 + 1) >= 52) + { + /* only for last iso week */ + Timestamp isoyear = iso_year(y + 1, 1, 8); + + if (isoyear > (DATE2J(y + 1, 1, 1) - 1)) + if (day > isoyear - 7) + { + int tmpd = J2DAY(day); + + result -= (tmpd == 0 || tmpd > 4 ? 7 : 0); + } + } + break; } + CASE_fmt_W + z = (day - DATE2J(y, m, 1)) % 7; + result = day - z + (z < 4 ? 0 : 7); break; - } - CASE_fmt_W - z = (day - DATE2J(y,m,1)) % 7; - result = day - z + (z < 4?0:7); - break; - CASE_fmt_DAY - z = J2DAY(day); + CASE_fmt_DAY + z = J2DAY(day); if (y > 0) - result = day - z + (z < 4?0:7); + result = day - z + (z < 4 ? 0 : 7); else - result = day + (5 - (z>0?(z>1?z:z+7):7)); + result = day + (5 - (z > 0 ? (z > 1 ? z : z + 7) : 7)); break; - CASE_fmt_Q - result = DATE2J(y,((m-1)/3)*3+(day<(DATE2J(y,((m-1)/3)*3+2,16))?1:4),1); + CASE_fmt_Q + result = DATE2J(y, ((m - 1) / 3) * 3 + (day < (DATE2J(y, ((m - 1) / 3) * 3 + 2, 16)) ? 1 : 4), 1); break; default: result = day; @@ -1811,83 +1849,83 @@ ora_date_round(Timestamp day, int f) static void tm_round(struct pg_tm *tm, text *fmt) { - int f; - bool rounded = true; + int f; + bool rounded = true; f = ora_seq_search(VARDATA_ANY(fmt), date_fmt, VARSIZE_ANY_EXHDR(fmt)); CHECK_SEQ_SEARCH(f, "round/trunc format string"); - // set rounding rule + /* set rounding rule */ switch (f) { - CASE_fmt_IYYY - NOT_ROUND_MDAY(tm->tm_mday < 8 && tm->tm_mon == 1); - NOT_ROUND_MDAY(tm->tm_mday == 30 && tm->tm_mon == 6); - if (tm->tm_mday >= 28 && tm->tm_mon == 12 && tm->tm_hour >= 12) - { - Timestamp isoyear = iso_year(tm->tm_year+1, 1, 8); - Timestamp day0 = DATE2J(tm->tm_year+1,1,1); - Timestamp dayc = DATE2J(tm->tm_year, tm->tm_mon, tm->tm_mday); - - if ((isoyear <= day0) || (day0 <= dayc + 2)) + CASE_fmt_IYYY + NOT_ROUND_MDAY(tm->tm_mday < 8 && tm->tm_mon == 1); + NOT_ROUND_MDAY(tm->tm_mday == 30 && tm->tm_mon == 6); + if (tm->tm_mday >= 28 && tm->tm_mon == 12 && tm->tm_hour >= 12) { - rounded = false; + Timestamp isoyear = iso_year(tm->tm_year + 1, 1, 8); + Timestamp day0 = DATE2J(tm->tm_year + 1, 1, 1); + Timestamp dayc = DATE2J(tm->tm_year, tm->tm_mon, tm->tm_mday); + + if ((isoyear <= day0) || (day0 <= dayc + 2)) + { + rounded = false; + } } - } - break; - CASE_fmt_YYYY - NOT_ROUND_MDAY(tm->tm_mday == 30 && tm->tm_mon == 6); - break; - CASE_fmt_MON - NOT_ROUND_MDAY(tm->tm_mday == 15); - break; - CASE_fmt_Q - NOT_ROUND_MDAY(tm->tm_mday == 15 && tm->tm_mon == ((tm->tm_mon-1)/3)*3+2); - break; - CASE_fmt_WW - CASE_fmt_IW - // last day in year - NOT_ROUND_MDAY(DATE2J(tm->tm_year, tm->tm_mon, tm->tm_mday) == - (DATE2J(tm->tm_year+1, 1,1) - 1)); - break; - CASE_fmt_W - // last day in month - NOT_ROUND_MDAY(DATE2J(tm->tm_year, tm->tm_mon, tm->tm_mday) == - (DATE2J(tm->tm_year, tm->tm_mon+1,1) - 1)); - break; + break; + CASE_fmt_YYYY + NOT_ROUND_MDAY(tm->tm_mday == 30 && tm->tm_mon == 6); + break; + CASE_fmt_MON + NOT_ROUND_MDAY(tm->tm_mday == 15); + break; + CASE_fmt_Q + NOT_ROUND_MDAY(tm->tm_mday == 15 && tm->tm_mon == ((tm->tm_mon - 1) / 3) * 3 + 2); + break; + CASE_fmt_WW + CASE_fmt_IW + /* last day in year */ + NOT_ROUND_MDAY(DATE2J(tm->tm_year, tm->tm_mon, tm->tm_mday) == + (DATE2J(tm->tm_year + 1, 1, 1) - 1)); + break; + CASE_fmt_W + /* last day in month */ + NOT_ROUND_MDAY(DATE2J(tm->tm_year, tm->tm_mon, tm->tm_mday) == + (DATE2J(tm->tm_year, tm->tm_mon + 1, 1) - 1)); + break; } switch (f) { - // easier convert to date - CASE_fmt_IW - CASE_fmt_DAY - CASE_fmt_IYYY - CASE_fmt_WW - CASE_fmt_W - CASE_fmt_CC - CASE_fmt_MON - CASE_fmt_YYYY - CASE_fmt_Q - ROUND_MDAY(tm); - j2date(ora_date_round(DATE2J(tm->tm_year, tm->tm_mon, tm->tm_mday), f) - + POSTGRES_EPOCH_JDATE, - &tm->tm_year, &tm->tm_mon, &tm->tm_mday); - tm->tm_hour = 0; - tm->tm_min = 0; - break; - CASE_fmt_DDD - tm->tm_mday += (tm->tm_hour >= 12)?1:0; - tm->tm_hour = 0; - tm->tm_min = 0; - break; - CASE_fmt_MI - tm->tm_min += (tm->tm_sec >= 30)?1:0; - break; - CASE_fmt_HH - tm->tm_hour += (tm->tm_min >= 30)?1:0; - tm->tm_min = 0; - break; + /* easier convert to date */ + CASE_fmt_IW + CASE_fmt_DAY + CASE_fmt_IYYY + CASE_fmt_WW + CASE_fmt_W + CASE_fmt_CC + CASE_fmt_MON + CASE_fmt_YYYY + CASE_fmt_Q + ROUND_MDAY(tm); + j2date(ora_date_round(DATE2J(tm->tm_year, tm->tm_mon, tm->tm_mday), f) + + POSTGRES_EPOCH_JDATE, + &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + tm->tm_hour = 0; + tm->tm_min = 0; + break; + CASE_fmt_DDD + tm->tm_mday += (tm->tm_hour >= 12) ? 1 : 0; + tm->tm_hour = 0; + tm->tm_min = 0; + break; + CASE_fmt_MI + tm->tm_min += (tm->tm_sec >= 30) ? 1 : 0; + break; + CASE_fmt_HH + tm->tm_hour += (tm->tm_min >= 30) ? 1 : 0; + tm->tm_min = 0; + break; } tm->tm_sec = 0; @@ -1896,45 +1934,47 @@ tm_round(struct pg_tm *tm, text *fmt) static Timestamp ora_date_trunc(Timestamp day, int f) { - int y, m, d; - Timestamp result; + int y, + m, + d; + Timestamp result; j2date(day + POSTGRES_EPOCH_JDATE, &y, &m, &d); switch (f) { - CASE_fmt_CC - if (y > 0) - result = DATE2J((y/100)*100+1,1,1); - else - result = DATE2J(-((99 - (y - 1)) / 100) * 100 + 1,1,1); - break; - CASE_fmt_YYYY - result = DATE2J(y,1,1); - break; - CASE_fmt_IYYY - result = iso_year(y,m,d); - break; - CASE_fmt_MON - result = DATE2J(y,m,1); - break; - CASE_fmt_WW - result = day - (day - DATE2J(y,1,1)) % 7; - break; - CASE_fmt_IW - result = day - (day - iso_year(y,m,d)) % 7; - break; - CASE_fmt_W - result = day - (day - DATE2J(y,m,1)) % 7; - break; - CASE_fmt_DAY - result = day - J2DAY(day); - break; - CASE_fmt_Q - result = DATE2J(y,((m-1)/3)*3+1,1); - break; - default: - result = day; + CASE_fmt_CC + if (y > 0) + result = DATE2J((y / 100) * 100 + 1, 1, 1); + else + result = DATE2J(-((99 - (y - 1)) / 100) * 100 + 1, 1, 1); + break; + CASE_fmt_YYYY + result = DATE2J(y, 1, 1); + break; + CASE_fmt_IYYY + result = iso_year(y, m, d); + break; + CASE_fmt_MON + result = DATE2J(y, m, 1); + break; + CASE_fmt_WW + result = day - (day - DATE2J(y, 1, 1)) % 7; + break; + CASE_fmt_IW + result = day - (day - iso_year(y, m, d)) % 7; + break; + CASE_fmt_W + result = day - (day - DATE2J(y, m, 1)) % 7; + break; + CASE_fmt_DAY + result = day - J2DAY(day); + break; + CASE_fmt_Q + result = DATE2J(y, ((m - 1) / 3) * 3 + 1, 1); + break; + default: + result = day; } return result; @@ -1943,7 +1983,7 @@ ora_date_trunc(Timestamp day, int f) static void tm_trunc(struct pg_tm *tm, text *fmt) { - int f; + int f; f = ora_seq_search(VARDATA_ANY(fmt), date_fmt, VARSIZE_ANY_EXHDR(fmt)); CHECK_SEQ_SEARCH(f, "round/trunc format string"); @@ -1952,49 +1992,49 @@ tm_trunc(struct pg_tm *tm, text *fmt) switch (f) { - CASE_fmt_IYYY - CASE_fmt_WW - CASE_fmt_W - CASE_fmt_IW - CASE_fmt_DAY - CASE_fmt_CC - j2date(ora_date_trunc(DATE2J(tm->tm_year, tm->tm_mon, tm->tm_mday), f) - + POSTGRES_EPOCH_JDATE, - &tm->tm_year, &tm->tm_mon, &tm->tm_mday); - tm->tm_hour = 0; - tm->tm_min = 0; - break; - CASE_fmt_YYYY - tm->tm_mon = 1; - tm->tm_mday = 1; - tm->tm_hour = 0; - tm->tm_min = 0; - break; - CASE_fmt_Q - tm->tm_mon = (3*((tm->tm_mon - 1)/3)) + 1; - tm->tm_mday = 1; - tm->tm_hour = 0; - tm->tm_min = 0; - break; - CASE_fmt_MON - tm->tm_mday = 1; - tm->tm_hour = 0; - tm->tm_min = 0; - break; - CASE_fmt_DDD - tm->tm_hour = 0; - tm->tm_min = 0; - break; - CASE_fmt_HH - tm->tm_min = 0; - break; + CASE_fmt_IYYY + CASE_fmt_WW + CASE_fmt_W + CASE_fmt_IW + CASE_fmt_DAY + CASE_fmt_CC + j2date(ora_date_trunc(DATE2J(tm->tm_year, tm->tm_mon, tm->tm_mday), f) + + POSTGRES_EPOCH_JDATE, + &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + tm->tm_hour = 0; + tm->tm_min = 0; + break; + CASE_fmt_YYYY + tm->tm_mon = 1; + tm->tm_mday = 1; + tm->tm_hour = 0; + tm->tm_min = 0; + break; + CASE_fmt_Q + tm->tm_mon = (3 * ((tm->tm_mon - 1) / 3)) + 1; + tm->tm_mday = 1; + tm->tm_hour = 0; + tm->tm_min = 0; + break; + CASE_fmt_MON + tm->tm_mday = 1; + tm->tm_hour = 0; + tm->tm_min = 0; + break; + CASE_fmt_DDD + tm->tm_hour = 0; + tm->tm_min = 0; + break; + CASE_fmt_HH + tm->tm_min = 0; + break; } } static int ora_seq_prefix_search(const char *name, const char *const array[], int max) { - int i; + int i; if (!*name) return -1; @@ -2004,71 +2044,74 @@ ora_seq_prefix_search(const char *name, const char *const array[], int max) if (pg_strncasecmp(name, array[i], max) == 0) return i; } - return -1; /* not found */ + return -1; /* not found */ } static int -weekday_search(const WeekDays *weekdays, const char *str, int len) +weekday_search(const WeekDays * weekdays, const char *str, int len) { - int i; + int i; for (i = 0; i < 7; i++) { - int n = strlen(weekdays->names[i]); + int n = strlen(weekdays->names[i]); + if (n > len) - continue; /* too short */ + continue; /* too short */ if (pg_strncasecmp(weekdays->names[i], str, n) == 0) return i; } - return -1; /* not found */ + return -1; /* not found */ } -static int ora_timezone_name_to_num(const char *name) +static int +ora_timezone_name_to_num(const char *name) { - int i; - if(strlen(name) != 3) + int i; + + if (strlen(name) != 3) return -1; - for(i = 0; date_timezone[i] != NULL; ++i) + for (i = 0; date_timezone[i] != NULL; ++i) { - if(pg_strncasecmp(name, date_timezone[i], 3) == 0) + if (pg_strncasecmp(name, date_timezone[i], 3) == 0) break; } switch (i) { - CASE_timezone_0 - return 0; + CASE_timezone_0 + return 0; break; - CASE_timezone_3 - return 3*3600; + CASE_timezone_3 + return 3 * 3600; break; - CASE_timezone_35 - return 3.5*3600; + CASE_timezone_35 + return 3.5 * 3600; break; - CASE_timezone_4 - return 4*3600; + CASE_timezone_4 + return 4 * 3600; break; - CASE_timezone_5 - return 5*3600; + CASE_timezone_5 + return 5 * 3600; break; - CASE_timezone_6 - return 6*3600; + CASE_timezone_6 + return 6 * 3600; break; - CASE_timezone_7 - return 7*3600; + CASE_timezone_7 + return 7 * 3600; break; - CASE_timezone_8 - return 8*3600; + CASE_timezone_8 + return 8 * 3600; break; - CASE_timezone_9 - return 9*3600; + CASE_timezone_9 + return 9 * 3600; break; - CASE_timezone_10 - return 10*3600; + CASE_timezone_10 + return 10 * 3600; break; - CASE_timezone_11 - return 11*3600; + CASE_timezone_11 + return 11 * 3600; break; default: return -1; diff --git a/contrib/ivorysql_ora/src/builtin_functions/misc_functions.c b/contrib/ivorysql_ora/src/builtin_functions/misc_functions.c index 44130fc9aab..75e984d33ac 100644 --- a/contrib/ivorysql_ora/src/builtin_functions/misc_functions.c +++ b/contrib/ivorysql_ora/src/builtin_functions/misc_functions.c @@ -18,7 +18,7 @@ * This file contains the implementation of Oracle's * datatype-independent built-in functions. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/builtin_functions/misc_functions.c * diff --git a/contrib/ivorysql_ora/src/builtin_functions/numeric_datatype_functions.c b/contrib/ivorysql_ora/src/builtin_functions/numeric_datatype_functions.c index d4c0dbc03bf..ba59c33badc 100644 --- a/contrib/ivorysql_ora/src/builtin_functions/numeric_datatype_functions.c +++ b/contrib/ivorysql_ora/src/builtin_functions/numeric_datatype_functions.c @@ -18,7 +18,7 @@ * This file contains the implementation of Oracle's * numeric data type related built-in functions. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/builtin_functions/numeric_datatype_functions.c * diff --git a/contrib/ivorysql_ora/src/datatype/binary_double.c b/contrib/ivorysql_ora/src/datatype/binary_double.c index f9796e37c59..68921db2b77 100644 --- a/contrib/ivorysql_ora/src/datatype/binary_double.c +++ b/contrib/ivorysql_ora/src/datatype/binary_double.c @@ -17,7 +17,7 @@ * * Compatible with Oracle's binary_double data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/binary_double.c * diff --git a/contrib/ivorysql_ora/src/datatype/binary_float.c b/contrib/ivorysql_ora/src/datatype/binary_float.c index a1f4e6b81ce..5cc1e8015b2 100644 --- a/contrib/ivorysql_ora/src/datatype/binary_float.c +++ b/contrib/ivorysql_ora/src/datatype/binary_float.c @@ -17,7 +17,7 @@ * * Compatible with Oracle's binary_float data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/binary_float.c * diff --git a/contrib/ivorysql_ora/src/datatype/common_datatypes.c b/contrib/ivorysql_ora/src/datatype/common_datatypes.c index 56b4d795d09..fcbd7a9eb34 100644 --- a/contrib/ivorysql_ora/src/datatype/common_datatypes.c +++ b/contrib/ivorysql_ora/src/datatype/common_datatypes.c @@ -17,7 +17,7 @@ * * This file contains Common support routines for datatypes. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/common_datatypes.c * diff --git a/contrib/ivorysql_ora/src/datatype/compatible_oracle_precedence.c b/contrib/ivorysql_ora/src/datatype/compatible_oracle_precedence.c index 457e0d78e23..d11385a701e 100644 --- a/contrib/ivorysql_ora/src/datatype/compatible_oracle_precedence.c +++ b/contrib/ivorysql_ora/src/datatype/compatible_oracle_precedence.c @@ -17,7 +17,7 @@ * * Compatible with Oracle data type precedence. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/compatible_oracle_precedence.c * diff --git a/contrib/ivorysql_ora/src/datatype/dsinterval.c b/contrib/ivorysql_ora/src/datatype/dsinterval.c index a379b6820fc..87d7588cd26 100644 --- a/contrib/ivorysql_ora/src/datatype/dsinterval.c +++ b/contrib/ivorysql_ora/src/datatype/dsinterval.c @@ -17,7 +17,7 @@ * * Compatible with Oracle's INTERVAL DAY TO SECOND data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/dsinterval.c * diff --git a/contrib/ivorysql_ora/src/datatype/oracharbyte.c b/contrib/ivorysql_ora/src/datatype/oracharbyte.c index 9fc89cf8f8e..06a3d328982 100644 --- a/contrib/ivorysql_ora/src/datatype/oracharbyte.c +++ b/contrib/ivorysql_ora/src/datatype/oracharbyte.c @@ -17,7 +17,7 @@ * * Compatible with Oracle's CHAR(n byte) data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/oracharbyte.c * diff --git a/contrib/ivorysql_ora/src/datatype/oracharchar.c b/contrib/ivorysql_ora/src/datatype/oracharchar.c index f68e92f3017..78213a667a5 100644 --- a/contrib/ivorysql_ora/src/datatype/oracharchar.c +++ b/contrib/ivorysql_ora/src/datatype/oracharchar.c @@ -17,7 +17,7 @@ * * Compatible with Oracle's CHAR(n char) data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/oracharchar.c * diff --git a/contrib/ivorysql_ora/src/datatype/oradate.c b/contrib/ivorysql_ora/src/datatype/oradate.c index 508761adbaa..29f6fdd5753 100644 --- a/contrib/ivorysql_ora/src/datatype/oradate.c +++ b/contrib/ivorysql_ora/src/datatype/oradate.c @@ -1,12 +1,12 @@ /*------------------------------------------------------------------------- * Copyright 2025 IvorySQL Global Development Team - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,7 +17,7 @@ * * Compatible with Oracle's DATE data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/oradate.c * @@ -166,16 +166,16 @@ timestamp2timestamptz(Timestamp timestamp) Datum oradate_in(PG_FUNCTION_ARGS) { - char *str = PG_GETARG_CSTRING(0); - Oid collid = PG_GET_COLLATION(); + char *str = PG_GETARG_CSTRING(0); + Oid collid = PG_GET_COLLATION(); Timestamp result; - struct pg_tm tm; + struct pg_tm tm; fsec_t fsec; if (strcmp(nls_date_format, "pg") == 0 || DATETIME_IGNORE_NLS(datetime_ignore_nls_mask, ORADATE_MASK)) { return DirectFunctionCall1(date_timestamp, - DirectFunctionCall1(date_in, CStringGetDatum(str))); + DirectFunctionCall1(date_in, CStringGetDatum(str))); } else { @@ -206,8 +206,8 @@ oradate_out(PG_FUNCTION_ARGS) text *date_str; date_str = DatumGetTextP(DirectFunctionCall2(timestamp_to_char, - TimestampGetDatum(timestamp), - PointerGetDatum(cstring_to_text(nls_date_format)))); + TimestampGetDatum(timestamp), + PointerGetDatum(cstring_to_text(nls_date_format)))); result = text_to_cstring(date_str); PG_RETURN_CSTRING(result); @@ -589,7 +589,7 @@ oradate_cmp_oratimestampltz(PG_FUNCTION_ARGS) } /***************************************************************************** - * Hash index support procedure + * Hash index support procedure *****************************************************************************/ Datum oradate_hash(PG_FUNCTION_ARGS) @@ -744,4 +744,3 @@ oradate_oratimestampltz(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp)); } - diff --git a/contrib/ivorysql_ora/src/datatype/oratimestamp.c b/contrib/ivorysql_ora/src/datatype/oratimestamp.c index a3d319554f3..3b88c199186 100644 --- a/contrib/ivorysql_ora/src/datatype/oratimestamp.c +++ b/contrib/ivorysql_ora/src/datatype/oratimestamp.c @@ -1,12 +1,12 @@ /*------------------------------------------------------------------------- * Copyright 2025 IvorySQL Global Development Team - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,7 +17,7 @@ * * Compatible with Oracle's TIMESTAMP data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/oratimestamp.c * @@ -163,8 +163,8 @@ OraAdjustTimestampForTypmod(Timestamp *time, int32 typmod) if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("timestamp(%d) precision must be between %d and %d", - typmod, 0, MAX_TIMESTAMP_PRECISION))); + errmsg("timestamp(%d) precision must be between %d and %d", + typmod, 0, MAX_TIMESTAMP_PRECISION))); /* * Note: this round-to-nearest code is not completely consistent about @@ -214,15 +214,15 @@ anytimestamp_typmodin(bool istz, ArrayType *ta) { ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("TIMESTAMP(%d)%s effective number of fractional seconds is 6,the part of excess is 0", - *tl, (istz ? " WITH TIME ZONE" : "")))); + errmsg("TIMESTAMP(%d)%s effective number of fractional seconds is 6, the part of excess is 0", + *tl, (istz ? " WITH TIME ZONE" : "")))); typmod = *tl; } else if (*tl > ORACLE_MAX_TIMESTAMP_PRECISION) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("the precision of datetime out of rang"))); + errmsg("the precision of datetime out of range"))); } else typmod = *tl; @@ -288,7 +288,7 @@ oratimestamp_in(PG_FUNCTION_ARGS) Oid typelem = PG_GETARG_OID(1); #endif int32 typmod = PG_GETARG_INT32(2); - Oid collid = PG_GET_COLLATION(); + Oid collid = PG_GET_COLLATION(); Timestamp result; struct pg_tm tm; fsec_t fsec; @@ -296,9 +296,9 @@ oratimestamp_in(PG_FUNCTION_ARGS) if (strcmp(nls_timestamp_format, "pg") == 0 || DATETIME_IGNORE_NLS(datetime_ignore_nls_mask, ORATIMESTAMP_MASK)) { return DirectFunctionCall3(timestamp_in, - CStringGetDatum(str), - ObjectIdGetDatum(InvalidOid), - Int32GetDatum(typmod)); + CStringGetDatum(str), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(typmod)); } else { @@ -330,8 +330,8 @@ oratimestamp_out(PG_FUNCTION_ARGS) text *date_str; date_str = DatumGetTextP(DirectFunctionCall2(timestamp_to_char, - TimestampGetDatum(timestamp), - PointerGetDatum(cstring_to_text(nls_timestamp_format)))); + TimestampGetDatum(timestamp), + PointerGetDatum(cstring_to_text(nls_timestamp_format)))); result = text_to_cstring(date_str); PG_RETURN_CSTRING(result); @@ -717,7 +717,7 @@ oratimestamp_cmp_oratimestampltz(PG_FUNCTION_ARGS) } /***************************************************************************** - * Hash index support procedure + * Hash index support procedure *****************************************************************************/ Datum oratimestamp_hash(PG_FUNCTION_ARGS) @@ -775,7 +775,7 @@ oratimestamp(PG_FUNCTION_ARGS) /* No work if typmod is invalid */ if (typmod == -1) PG_RETURN_TIMESTAMP(source); - + OraAdjustTimestampForTypmod(&source, typmod); PG_RETURN_TIMESTAMP(source); } @@ -851,7 +851,7 @@ Datum oradate_mi_oratimestampltz(PG_FUNCTION_ARGS) { Timestamp dt1 = PG_GETARG_TIMESTAMP(0); - TimestampTz dt2 = PG_GETARG_TIMESTAMP(1); + TimestampTz dt2 = PG_GETARG_TIMESTAMP(1); Timestamp timestamp; struct pg_tm tt, *tm = &tt; @@ -883,7 +883,7 @@ oradate_mi_oratimestampltz(PG_FUNCTION_ARGS) result->day = 0; result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours, - IntervalPGetDatum(result))); + IntervalPGetDatum(result))); PG_RETURN_INTERVAL_P(result); } @@ -897,7 +897,7 @@ oradate_mi_oratimestampltz(PG_FUNCTION_ARGS) Datum oratimestampltz_mi_oradate(PG_FUNCTION_ARGS) { - TimestampTz dt1 = PG_GETARG_TIMESTAMP(0); + TimestampTz dt1 = PG_GETARG_TIMESTAMP(0); Timestamp dt2 = PG_GETARG_TIMESTAMP(1); Timestamp timestamp; struct pg_tm tt, @@ -930,7 +930,7 @@ oratimestampltz_mi_oradate(PG_FUNCTION_ARGS) result->day = 0; result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours, - IntervalPGetDatum(result))); + IntervalPGetDatum(result))); PG_RETURN_INTERVAL_P(result); } @@ -995,7 +995,7 @@ oratimestamp_mi(PG_FUNCTION_ARGS) result->day = 0; result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours, - IntervalPGetDatum(result))); + IntervalPGetDatum(result))); PG_RETURN_INTERVAL_P(result); } @@ -1050,7 +1050,7 @@ oradate_pl_interval(PG_FUNCTION_ARGS) /* Compatible oracle oradate dont have fsec */ fsec = 0; - + if (tm2timestamp(tm, fsec, NULL, ×tamp) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), @@ -1076,7 +1076,7 @@ oradate_pl_interval(PG_FUNCTION_ARGS) /* Compatible oracle oradate dont have fsec */ fsec = 0; - + if (tm2timestamp(tm, fsec, NULL, ×tamp) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), @@ -1456,7 +1456,7 @@ oradate_mi_number(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMP(result); } -/* +/* * Compatible oracle * The arithmetic operation of oratimestamp plus number, Result * type is oradate. @@ -1481,7 +1481,7 @@ oratimestamp_pl_number(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); - + /* Convert timestamp to date ignore time zone and fractional second */ if (tm2timestamp(tm, 0, NULL, &result) != 0) ereport(ERROR, @@ -1490,14 +1490,14 @@ oratimestamp_pl_number(PG_FUNCTION_ARGS) addend = DatumGetFloat8(DirectFunctionCall1(numeric_float8, NumericGetDatum(num))); addend = rint(addend * USECS_PER_DAY); - + result = result + addend; } PG_RETURN_TIMESTAMP(result); } -/* +/* * Compatible oracle * The arithmetic operation of number plus oratimestamp, Result * type is oradate. @@ -1531,7 +1531,7 @@ number_pl_oratimestamp(PG_FUNCTION_ARGS) addend = DatumGetFloat8(DirectFunctionCall1(numeric_float8, NumericGetDatum(num))); addend = rint(addend * USECS_PER_DAY); - + result = result + addend; } @@ -1588,7 +1588,7 @@ oratimestamp_mi_number(PG_FUNCTION_ARGS) Datum oratimestamptz_mi_number(PG_FUNCTION_ARGS) { - TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); Numeric num = PG_GETARG_NUMERIC(1); float8 mi; Timestamp result; @@ -1605,7 +1605,7 @@ oratimestamptz_mi_number(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); - + /* Convert timestamp to date ignore fractional second */ if (tm2timestamp(tm, 0, NULL, &result) != 0) ereport(ERROR, @@ -1628,7 +1628,7 @@ oratimestamptz_mi_number(PG_FUNCTION_ARGS) Datum oratimestamptz_pl_number(PG_FUNCTION_ARGS) { - TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); Numeric num = PG_GETARG_NUMERIC(1); float8 addend; Timestamp result; @@ -1646,7 +1646,7 @@ oratimestamptz_pl_number(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); - + /* Convert timestamp to date ignore time zone and fractional second */ if (tm2timestamp(tm, 0, NULL, &result) != 0) ereport(ERROR, @@ -1655,7 +1655,7 @@ oratimestamptz_pl_number(PG_FUNCTION_ARGS) addend = DatumGetFloat8(DirectFunctionCall1(numeric_float8, NumericGetDatum(num))); addend = rint(addend * USECS_PER_DAY); - + result = result + addend; } @@ -1671,7 +1671,7 @@ Datum number_pl_oratimestamptz(PG_FUNCTION_ARGS) { Numeric num = PG_GETARG_NUMERIC(0); - TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1); + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1); float8 addend; Timestamp result; struct pg_tm tt, @@ -1697,10 +1697,9 @@ number_pl_oratimestamptz(PG_FUNCTION_ARGS) addend = DatumGetFloat8(DirectFunctionCall1(numeric_float8, NumericGetDatum(num))); addend = rint(addend * USECS_PER_DAY); - + result = result + addend; } PG_RETURN_TIMESTAMP(result); } - diff --git a/contrib/ivorysql_ora/src/datatype/oratimestampltz.c b/contrib/ivorysql_ora/src/datatype/oratimestampltz.c index 4805096e42c..17bcebddbf0 100644 --- a/contrib/ivorysql_ora/src/datatype/oratimestampltz.c +++ b/contrib/ivorysql_ora/src/datatype/oratimestampltz.c @@ -1,12 +1,12 @@ /*------------------------------------------------------------------------- * Copyright 2025 IvorySQL Global Development Team - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,7 +17,7 @@ * * Compatible with Oracle's TIMESTAMP WITH LOCAL TIME ZONE data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/oratimestampltz.c * @@ -126,15 +126,15 @@ anytimestamp_typmodin(bool istz, ArrayType *ta) { ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("TIMESTAMP(%d)%s effective number of fractional seconds is 6,the part of excess is 0", - *tl, (istz ? " WITH TIME ZONE" : "")))); + errmsg("TIMESTAMP(%d)%s effective number of fractional seconds is 6, the part of excess is 0", + *tl, (istz ? " WITH TIME ZONE" : "")))); typmod = *tl; } else if (*tl > ORACLE_MAX_TIMESTAMP_PRECISION) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("the precision of datetime out of rang"))); + errmsg("the precision of datetime out of range"))); } else typmod = *tl; @@ -197,18 +197,19 @@ oratimestampltz_in(PG_FUNCTION_ARGS) char *str = PG_GETARG_CSTRING(0); #ifdef NOT_USED - Oid typelem = PG_GETARG_OID(1); + Oid typelem = PG_GETARG_OID(1); #endif int32 typmod = PG_GETARG_INT32(2); - Oid collid = PG_GET_COLLATION(); - TimestampTz result; + Oid collid = PG_GET_COLLATION(); + TimestampTz result; struct pg_tm tm; fsec_t fsec; int tz; if (strcmp(nls_timestamp_format, "pg") == 0 || DATETIME_IGNORE_NLS(datetime_ignore_nls_mask, ORATIMESTAMPLTZ_MASK)) { - Datum datum; + Datum datum; + datum = DirectFunctionCall3(timestamp_in, CStringGetDatum(str), ObjectIdGetDatum(InvalidOid), Int32GetDatum(typmod)); PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(DatumGetTimestamp(datum))); } @@ -235,13 +236,13 @@ oratimestampltz_in(PG_FUNCTION_ARGS) Datum oratimestampltz_out(PG_FUNCTION_ARGS) { - TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); char *result; text *date_str; date_str = DatumGetTextP(DirectFunctionCall2(timestamptz_to_char, - TimestampTzGetDatum(timestamp), - PointerGetDatum(cstring_to_text(nls_timestamp_format)))); + TimestampTzGetDatum(timestamp), + PointerGetDatum(cstring_to_text(nls_timestamp_format)))); result = text_to_cstring(date_str); PG_RETURN_CSTRING(result); @@ -577,13 +578,13 @@ oratimestampltz_ge_oratimestamptz(PG_FUNCTION_ARGS) Datum oratimestampltz(PG_FUNCTION_ARGS) { - TimestampTz source = PG_GETARG_TIMESTAMPTZ(0); + TimestampTz source = PG_GETARG_TIMESTAMPTZ(0); int32 typmod = PG_GETARG_INT32(1); /* No work if typmod is invalid */ if (typmod == -1) PG_RETURN_TIMESTAMPTZ(source); - + OraAdjustTimestampForTypmod(&source, typmod); PG_RETURN_TIMESTAMPTZ(source); } @@ -634,7 +635,7 @@ oratimestampltz_cmp_oratimestamptz(PG_FUNCTION_ARGS) } /***************************************************************************** - * Hash index support procedure + * Hash index support procedure *****************************************************************************/ Datum oratimestampltz_hash(PG_FUNCTION_ARGS) @@ -735,4 +736,3 @@ oratimestampltz_oratimestamp(PG_FUNCTION_ARGS) } PG_RETURN_TIMESTAMP(result); } - diff --git a/contrib/ivorysql_ora/src/datatype/oratimestamptz.c b/contrib/ivorysql_ora/src/datatype/oratimestamptz.c index 2ed46819043..9393e0a763b 100644 --- a/contrib/ivorysql_ora/src/datatype/oratimestamptz.c +++ b/contrib/ivorysql_ora/src/datatype/oratimestamptz.c @@ -1,12 +1,12 @@ /*------------------------------------------------------------------------- * Copyright 2025 IvorySQL Global Development Team - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,7 +17,7 @@ * * Compatible with Oracle's TIMESTAMP WITH TIME ZONE data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/oratimestamptz.c * @@ -126,15 +126,15 @@ anytimestamp_typmodin(bool istz, ArrayType *ta) { ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("TIMESTAMP(%d)%s effective number of fractional seconds is 6,the part of excess is 0", - *tl, (istz ? " WITH TIME ZONE" : "")))); + errmsg("TIMESTAMP(%d)%s effective number of fractional seconds is 6, the part of excess is 0", + *tl, (istz ? " WITH TIME ZONE" : "")))); typmod = *tl; } else if (*tl > ORACLE_MAX_TIMESTAMP_PRECISION) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("the precision of datetime out of rang"))); + errmsg("the precision of datetime out of range"))); } else typmod = *tl; @@ -193,24 +193,24 @@ timestamp2timestamptz(Timestamp timestamp) Datum oratimestamptz_in(PG_FUNCTION_ARGS) { - char *str = PG_GETARG_CSTRING(0); + char *str = PG_GETARG_CSTRING(0); #ifdef NOT_USED - Oid typelem = PG_GETARG_OID(1); + Oid typelem = PG_GETARG_OID(1); #endif - int32 typmod = PG_GETARG_INT32(2); - Oid collid = PG_GET_COLLATION(); + int32 typmod = PG_GETARG_INT32(2); + Oid collid = PG_GET_COLLATION(); if (strcmp(nls_timestamp_tz_format, "pg") == 0 || DATETIME_IGNORE_NLS(datetime_ignore_nls_mask, ORATIMESTAMPTZ_MASK)) { return DirectFunctionCall3(timestamptz_in, - CStringGetDatum(str), - ObjectIdGetDatum(InvalidOid), - Int32GetDatum(typmod)); + CStringGetDatum(str), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(typmod)); } else { - TimestampTz result; + TimestampTz result; struct pg_tm tm; fsec_t fsec; int tz; @@ -220,7 +220,7 @@ oratimestamptz_in(PG_FUNCTION_ARGS) if (tm.tm_zone) { - int dterr = DecodeTimezone((char *)tm.tm_zone, &tz); + int dterr = DecodeTimezone((char *) tm.tm_zone, &tz); if (dterr) DateTimeParseError(dterr, &extra, str, "timestamptz", NULL); @@ -247,7 +247,7 @@ oratimestamptz_in(PG_FUNCTION_ARGS) Datum oratimestamptz_out(PG_FUNCTION_ARGS) { - TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); if (strcmp(nls_timestamp_tz_format, "pg") != 0) { @@ -255,9 +255,9 @@ oratimestamptz_out(PG_FUNCTION_ARGS) text *date_str; date_str = DatumGetTextP(DirectFunctionCall2(timestamptz_to_char, - TimestampTzGetDatum(timestamp), - PointerGetDatum(cstring_to_text(nls_timestamp_tz_format)))); - + TimestampTzGetDatum(timestamp), + PointerGetDatum(cstring_to_text(nls_timestamp_tz_format)))); + result = text_to_cstring(date_str); PG_RETURN_CSTRING(result); } @@ -595,13 +595,13 @@ oratimestamptz_ge_oratimestampltz(PG_FUNCTION_ARGS) Datum oratimestamptz(PG_FUNCTION_ARGS) { - TimestampTz source = PG_GETARG_TIMESTAMPTZ(0); + TimestampTz source = PG_GETARG_TIMESTAMPTZ(0); int32 typmod = PG_GETARG_INT32(1); /* No work if typmod is invalid */ if (typmod == -1) PG_RETURN_TIMESTAMPTZ(source); - + OraAdjustTimestampForTypmod(&source, typmod); PG_RETURN_TIMESTAMPTZ(source); } @@ -652,7 +652,7 @@ oratimestamptz_cmp_oratimestampltz(PG_FUNCTION_ARGS) } /***************************************************************************** - * Hash index support procedure + * Hash index support procedure *****************************************************************************/ Datum oratimestamptz_hash(PG_FUNCTION_ARGS) diff --git a/contrib/ivorysql_ora/src/datatype/oravarcharbyte.c b/contrib/ivorysql_ora/src/datatype/oravarcharbyte.c index d349d0a7077..f1ddb1744d7 100644 --- a/contrib/ivorysql_ora/src/datatype/oravarcharbyte.c +++ b/contrib/ivorysql_ora/src/datatype/oravarcharbyte.c @@ -17,7 +17,7 @@ * * Compatible with Oracle's VARCHAR2(n byte) data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/oravarcharbyte.c * diff --git a/contrib/ivorysql_ora/src/datatype/oravarcharchar.c b/contrib/ivorysql_ora/src/datatype/oravarcharchar.c index f42ef834a22..3ada0a11c87 100644 --- a/contrib/ivorysql_ora/src/datatype/oravarcharchar.c +++ b/contrib/ivorysql_ora/src/datatype/oravarcharchar.c @@ -17,7 +17,7 @@ * * Compatible with Oracle's VARCHAR2(n char) data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/oravarcharchar.c * diff --git a/contrib/ivorysql_ora/src/datatype/raw_long.c b/contrib/ivorysql_ora/src/datatype/raw_long.c index 8abdcef63f3..b55c297d18c 100644 --- a/contrib/ivorysql_ora/src/datatype/raw_long.c +++ b/contrib/ivorysql_ora/src/datatype/raw_long.c @@ -17,7 +17,7 @@ * * Auxiliary routine for compatible with Oracle's LONG RAW data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/raw_long.c * diff --git a/contrib/ivorysql_ora/src/datatype/yminterval.c b/contrib/ivorysql_ora/src/datatype/yminterval.c index 907f087ad78..b62f87cb3a7 100644 --- a/contrib/ivorysql_ora/src/datatype/yminterval.c +++ b/contrib/ivorysql_ora/src/datatype/yminterval.c @@ -17,7 +17,7 @@ * * Compatible with Oracle's INTERVAL YEAR TO MONTH data type. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/datatype/yminterval.c * diff --git a/contrib/ivorysql_ora/src/guc/guc.c b/contrib/ivorysql_ora/src/guc/guc.c index 3632671592d..650e7693328 100644 --- a/contrib/ivorysql_ora/src/guc/guc.c +++ b/contrib/ivorysql_ora/src/guc/guc.c @@ -1,12 +1,12 @@ /*------------------------------------------------------------------------- * Copyright 2025 IvorySQL Global Development Team - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,7 +17,7 @@ * * Ivorysql_ora GUC variables define. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/guc/guc.c * @@ -32,17 +32,6 @@ #include "../include/guc.h" -#if 0 -static const struct config_enum_entry nls_length_semantics_options[] = -{ - {"char", NLS_LENGTH_SEMANTICS_CHAR, false}, - {"byte", NLS_LENGTH_SEMANTICS_BYTE, false}, - {NULL, 0, false} -}; - -/* GUC variables */ -int nls_length_semantics = NLS_LENGTH_SEMANTICS_BYTE; -#endif /* * Define various GUC. @@ -50,17 +39,5 @@ int nls_length_semantics = NLS_LENGTH_SEMANTICS_BYTE; void IvorysqlOraDefineGucs(void) { -#if 0 - DefineCustomEnumVariable( - "ivorysql_ora.nls_length_semantics", - gettext_noop("Compatible Oracle NLS parameter for charater data type"), - NULL, - &nls_length_semantics, - NLS_LENGTH_SEMANTICS_BYTE, nls_length_semantics_options, - PGC_USERSET, - GUC_NOT_IN_SAMPLE, - NULL, - NULL, - NULL); -#endif + } diff --git a/contrib/ivorysql_ora/src/include/common_datatypes.h b/contrib/ivorysql_ora/src/include/common_datatypes.h index 551261f300d..a0f920e7ae7 100644 --- a/contrib/ivorysql_ora/src/include/common_datatypes.h +++ b/contrib/ivorysql_ora/src/include/common_datatypes.h @@ -17,7 +17,7 @@ * * This file contains extern declarations for datatype common routines. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/include/common_datatypes.h * diff --git a/contrib/ivorysql_ora/src/include/guc.h b/contrib/ivorysql_ora/src/include/guc.h index ae94091061f..1d60e673902 100644 --- a/contrib/ivorysql_ora/src/include/guc.h +++ b/contrib/ivorysql_ora/src/include/guc.h @@ -17,7 +17,7 @@ * * This file contains extern declarations for GUC. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/include/guc.h * diff --git a/contrib/ivorysql_ora/src/include/ivorysql_ora.h b/contrib/ivorysql_ora/src/include/ivorysql_ora.h index 3cc4295de11..a55f40372b0 100644 --- a/contrib/ivorysql_ora/src/include/ivorysql_ora.h +++ b/contrib/ivorysql_ora/src/include/ivorysql_ora.h @@ -17,7 +17,7 @@ * * This file contains extern declarations for ivorysql_ora itself. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/include/ivorysql_ora.h * diff --git a/contrib/ivorysql_ora/src/ivorysql_ora.c b/contrib/ivorysql_ora/src/ivorysql_ora.c index 4ec0fc6ec20..d9738afbadd 100644 --- a/contrib/ivorysql_ora/src/ivorysql_ora.c +++ b/contrib/ivorysql_ora/src/ivorysql_ora.c @@ -17,7 +17,7 @@ * * Ivorysql_ora main entrypoint. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/ivorysql_ora.c * diff --git a/contrib/ivorysql_ora/src/merge/ora_merge.c b/contrib/ivorysql_ora/src/merge/ora_merge.c index 2d01b369f2a..f472df7c10f 100644 --- a/contrib/ivorysql_ora/src/merge/ora_merge.c +++ b/contrib/ivorysql_ora/src/merge/ora_merge.c @@ -15,7 +15,7 @@ * * Compatible with Oracle's Merge command. * - * Copyright (c) 2024-2025, IvorySQL Global Development Team + * Copyright (c) 2024-2026, IvorySQL Global Development Team * * contrib/ivorysql_ora/src/merge/ora_merge.c * diff --git a/contrib/ivorysql_ora/src/sysview/sysview--1.0.sql b/contrib/ivorysql_ora/src/sysview/sysview--1.0.sql index e92e5b5e00e..21c2d05fc5b 100644 --- a/contrib/ivorysql_ora/src/sysview/sysview--1.0.sql +++ b/contrib/ivorysql_ora/src/sysview/sysview--1.0.sql @@ -1086,7 +1086,7 @@ SELECT decode(bitand(s.flags, 16), 16, 'Y', 'N') AS scale_flag, decode(bitand(s.flags, 2048), 2048, 'Y', 'N') AS extend_flag,null AS shared_flag, decode(bitand(s.flags, 64), 64, 'Y', 'N') AS session_flag,null AS keep_value - FROM PG_SEQUENCE s,pg_class c where s.seqrelid = c.oid and c.relowner::regrole = current_user::regrole; + FROM PG_SEQUENCE s,pg_class c where s.seqrelid = c.oid and pg_get_userbyid(c.relowner) = current_user; CREATE OR REPLACE VIEW SYS.DBA_VIEWS AS SELECT diff --git a/contrib/ivorysql_ora/src/sysview/sysview_functions.c b/contrib/ivorysql_ora/src/sysview/sysview_functions.c index d112e8f6413..54cb93bd7bc 100755 --- a/contrib/ivorysql_ora/src/sysview/sysview_functions.c +++ b/contrib/ivorysql_ora/src/sysview/sysview_functions.c @@ -19,7 +19,7 @@ * Function which converts all-uppercase text to all-lowercase text * and vice versa. * - * Copyright (c) 2023-2025, IvorySQL Global Development Team + * Copyright (c) 2023-2026, IvorySQL Global Development Team * * Identification: * contrib/ivorysql_ora/src/sysview/sysview_functions.c diff --git a/contrib/ivorysql_ora/src/xml_functions/ora_xml_functions.c b/contrib/ivorysql_ora/src/xml_functions/ora_xml_functions.c index d65015fd633..564cf574414 100644 --- a/contrib/ivorysql_ora/src/xml_functions/ora_xml_functions.c +++ b/contrib/ivorysql_ora/src/xml_functions/ora_xml_functions.c @@ -18,7 +18,7 @@ * Abstract: * This file implements the C functions that support the XML SQL functions * - * Copyright (c) 2023-2025, IvorySQL Global Development Team + * Copyright (c) 2023-2026, IvorySQL Global Development Team * * Identification: * contrib/ivorysql_ora/src/xml_functions/ora_xml_functions.c diff --git a/contrib/ltree/crc32.c b/contrib/ltree/crc32.c index 134f46a805e..3bcdaa3dce1 100644 --- a/contrib/ltree/crc32.c +++ b/contrib/ltree/crc32.c @@ -10,31 +10,77 @@ #include "postgres.h" #include "ltree.h" +#include "crc32.h" +#include "utils/pg_crc.h" #ifdef LOWER_NODE -#include -#define TOLOWER(x) tolower((unsigned char) (x)) -#else -#define TOLOWER(x) (x) +#include "catalog/pg_collation.h" +#include "utils/pg_locale.h" #endif -#include "crc32.h" -#include "utils/pg_crc.h" +#ifdef LOWER_NODE unsigned int ltree_crc32_sz(const char *buf, int size) { pg_crc32 crc; const char *p = buf; + const char *end = buf + size; + static pg_locale_t locale = NULL; + + if (!locale) + locale = pg_newlocale_from_collation(DEFAULT_COLLATION_OID); INIT_TRADITIONAL_CRC32(crc); - while (size > 0) + if (locale->ctype_is_c) { - char c = (char) TOLOWER(*p); + while (size > 0) + { + char c = pg_ascii_tolower(*p); + + COMP_TRADITIONAL_CRC32(crc, &c, 1); + size--; + p++; + } + } + else + { + while (size > 0) + { + char foldstr[UNICODE_CASEMAP_BUFSZ]; + int srclen = pg_mblen_range(p, end); + size_t foldlen; + + /* fold one codepoint at a time */ + foldlen = pg_strfold(foldstr, UNICODE_CASEMAP_BUFSZ, p, srclen, + locale); + + COMP_TRADITIONAL_CRC32(crc, foldstr, foldlen); + + size -= srclen; + p += srclen; + } + } + FIN_TRADITIONAL_CRC32(crc); + return (unsigned int) crc; +} + +#else - COMP_TRADITIONAL_CRC32(crc, &c, 1); +unsigned int +ltree_crc32_sz(const char *buf, int size) +{ + pg_crc32 crc; + const char *p = buf; + + INIT_TRADITIONAL_CRC32(crc); + while (size > 0) + { + COMP_TRADITIONAL_CRC32(crc, p, 1); size--; p++; } FIN_TRADITIONAL_CRC32(crc); return (unsigned int) crc; } + +#endif /* !LOWER_NODE */ diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c index a6466f575fd..e15e726ad09 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,85 @@ 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; + static pg_locale_t locale = NULL; + char *fpred; /* casefolded predicate */ + size_t fpred_len = pred_len; + char *flabel; /* casefolded label */ + size_t flabel_len = label_len; + size_t 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; + + if (!locale) + locale = pg_newlocale_from_collation(DEFAULT_COLLATION_OID); + + if (locale->ctype_is_c) + { + if (pred_len > label_len || (!prefix && pred_len != label_len)) + return false; + + for (int i = 0; i < pred_len; i++) + { + if (pg_ascii_tolower(pred[i]) != pg_ascii_tolower(label[i])) + return false; + } - res = strncmp(al, bl, s); + return true; + } - pfree(al); - pfree(bl); + /* + * Slow path for case-insensitive comparison: case fold and then compare. + * This path is necessary even if pred_len > label_len, because the byte + * lengths may change after casefolding. + */ + + fpred = palloc(fpred_len + 1); + len = pg_strfold(fpred, fpred_len + 1, pred, pred_len, locale); + if (len > fpred_len) + { + /* grow buffer if needed and retry */ + fpred_len = len; + fpred = repalloc(fpred, fpred_len + 1); + len = pg_strfold(fpred, fpred_len + 1, pred, pred_len, locale); + } + Assert(len <= fpred_len); + fpred_len = len; + + flabel = palloc(flabel_len + 1); + len = pg_strfold(flabel, flabel_len + 1, label, label_len, locale); + if (len > flabel_len) + { + /* grow buffer if needed and retry */ + flabel_len = len; + flabel = repalloc(flabel, flabel_len + 1); + len = pg_strfold(flabel, flabel_len + 1, label, label_len, locale); + } + Assert(len <= flabel_len); + flabel_len = len; + + 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 +175,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 5e0761641d3..226c1cb2115 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 */ @@ -207,10 +207,11 @@ bool ltree_execute(ITEM *curitem, void *checkval, int ltree_compare(const ltree *a, const ltree *b); bool inner_isparent(const ltree *c, const ltree *p); -bool compare_subnode(ltree_level *t, char *qn, int len, - 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_io.c b/contrib/ltree/ltree_io.c index 9f109cfe648..5192a184d02 100644 --- a/contrib/ltree/ltree_io.c +++ b/contrib/ltree/ltree_io.c @@ -7,6 +7,7 @@ #include +#include "common/int.h" #include "crc32.h" #include "libpq/pqformat.h" #include "ltree.h" @@ -54,7 +55,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; @@ -69,7 +70,7 @@ parse_ltree(const char *buf, struct Node *escontext) ptr = buf; while (*ptr) { - charlen = pg_mblen(ptr); + charlen = pg_mblen_cstr(ptr); switch (state) { @@ -291,7 +292,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++; @@ -311,7 +312,7 @@ parse_lquery(const char *buf, struct Node *escontext) ptr = buf; while (*ptr) { - charlen = pg_mblen(ptr); + charlen = pg_mblen_cstr(ptr); switch (state) { @@ -344,7 +345,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; @@ -542,7 +548,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/ltxtquery_io.c b/contrib/ltree/ltxtquery_io.c index fe0cae22277..5eca740cb35 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) { @@ -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 002102c9c75..0e6612ff77a 100644 --- a/contrib/ltree/ltxtquery_op.c +++ b/contrib/ltree/ltxtquery_op.c @@ -58,19 +58,18 @@ checkcondition_str(void *checkval, ITEM *val) ltree_level *level = LTREE_FIRST(((CHKVAL *) checkval)->node); int tlen = ((CHKVAL *) checkval)->node->numlevel; char *op = ((CHKVAL *) checkval)->operand + val->distance; - 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/meson.build b/contrib/meson.build index cf6c32954c2..fc188a153f4 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -1,4 +1,4 @@ -# Portions Copyright (c) 2023-2025, IvorySQL Global Development Team +# Portions Copyright (c) 2023-2026, IvorySQL Global Development Team # Copyright (c) 2022-2025, PostgreSQL Global Development Group contrib_mod_args = pg_mod_args diff --git a/contrib/ora_btree_gin/.gitignore b/contrib/ora_btree_gin/.gitignore new file mode 100644 index 00000000000..7da67cb0cc9 --- /dev/null +++ b/contrib/ora_btree_gin/.gitignore @@ -0,0 +1,5 @@ +# Generated files +/ivorysql_ora--1.0.sql +# Generated subdirectories +/log/ +/results/ diff --git a/contrib/ora_btree_gin/meson.build b/contrib/ora_btree_gin/meson.build index ac505872829..7f843018c07 100644 --- a/contrib/ora_btree_gin/meson.build +++ b/contrib/ora_btree_gin/meson.build @@ -1,5 +1,5 @@ # Copyright (c) 2022-2024, PostgreSQL Global Development Group -# Portions Copyright (c) 2023-2025, IvorySQL Global Development Team +# Portions Copyright (c) 2023-2026, IvorySQL Global Development Team btree_gin_sources = files( 'ora_btree_gin.c', diff --git a/contrib/ora_btree_gist/btree_gist.h b/contrib/ora_btree_gist/btree_gist.h index 6885b7d2ebb..8d89745ac85 100644 --- a/contrib/ora_btree_gist/btree_gist.h +++ b/contrib/ora_btree_gist/btree_gist.h @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * contrib/btree_gist/btree_gist.h */ #ifndef __BTREE_GIST_H__ diff --git a/contrib/ora_btree_gist/btree_utils_num.h b/contrib/ora_btree_gist/btree_utils_num.h index 38c51a4567e..c75934c9fb6 100644 --- a/contrib/ora_btree_gist/btree_utils_num.h +++ b/contrib/ora_btree_gist/btree_utils_num.h @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * contrib/btree_gist/btree_utils_num.h */ #ifndef __BTREE_UTILS_NUM_H__ diff --git a/contrib/ora_btree_gist/btree_utils_var.h b/contrib/ora_btree_gist/btree_utils_var.h index c1c83ccb5c3..10fe05e0a78 100644 --- a/contrib/ora_btree_gist/btree_utils_var.h +++ b/contrib/ora_btree_gist/btree_utils_var.h @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * contrib/btree_gist/btree_utils_var.h */ #ifndef __BTREE_UTILS_VAR_H__ diff --git a/contrib/ora_btree_gist/meson.build b/contrib/ora_btree_gist/meson.build index 926b972713e..2976ceff019 100644 --- a/contrib/ora_btree_gist/meson.build +++ b/contrib/ora_btree_gist/meson.build @@ -1,5 +1,5 @@ # Copyright (c) 2022-2024, PostgreSQL Global Development Group -# Portions Copyright (c) 2023-2025, IvorySQL Global Development Team +# Portions Copyright (c) 2023-2026, IvorySQL Global Development Team btree_gist_sources = files( 'btree_binary_double.c', diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c index c1997a02780..8743afc66d0 100644 --- a/contrib/pageinspect/heapfuncs.c +++ b/contrib/pageinspect/heapfuncs.c @@ -86,7 +86,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 4b007f6e1b0..7d6af3f55eb 100644 --- a/contrib/pg_buffercache/pg_buffercache_pages.c +++ b/contrib/pg_buffercache/pg_buffercache_pages.c @@ -194,6 +194,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); @@ -320,7 +322,6 @@ pg_buffercache_numa_pages(PG_FUNCTION_ARGS) uint64 os_page_count; int pages_per_buffer; int max_entries; - volatile uint64 touch pg_attribute_unused(); char *startptr, *endptr; @@ -365,7 +366,7 @@ pg_buffercache_numa_pages(PG_FUNCTION_ARGS) /* Used to determine the NUMA node for all OS pages at once */ os_page_ptrs = palloc0(sizeof(void *) * os_page_count); - os_page_status = palloc(sizeof(uint64) * os_page_count); + os_page_status = palloc(sizeof(int) * os_page_count); /* Fill pointers for all the memory pages. */ idx = 0; @@ -375,7 +376,7 @@ pg_buffercache_numa_pages(PG_FUNCTION_ARGS) /* Only need to touch memory once per backend process lifetime */ if (firstNumaTouch) - pg_numa_touch_mem_if_required(touch, ptr); + pg_numa_touch_mem_if_required(ptr); } Assert(idx == os_page_count); @@ -525,8 +526,18 @@ pg_buffercache_numa_pages(PG_FUNCTION_ARGS) values[1] = Int64GetDatum(fctx->record[i].page_num); nulls[1] = false; - values[2] = Int32GetDatum(fctx->record[i].numa_node); - nulls[2] = false; + /* status is valid node number */ + if (fctx->record[i].numa_node >= 0) + { + values[2] = Int32GetDatum(fctx->record[i].numa_node); + nulls[2] = false; + } + else + { + /* some kind of error (e.g. pages moved to swap) */ + values[2] = (Datum) 0; + nulls[2] = true; + } /* Build and return the tuple. */ tuple = heap_form_tuple(fctx->tupdesc, values, nulls); @@ -561,6 +572,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 @@ -621,6 +634,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_overexplain/expected/ivy_pg_overexplain.out b/contrib/pg_overexplain/expected/ivy_pg_overexplain.out index c190fe1df65..f2fb042b79f 100644 --- a/contrib/pg_overexplain/expected/ivy_pg_overexplain.out +++ b/contrib/pg_overexplain/expected/ivy_pg_overexplain.out @@ -292,9 +292,9 @@ $$); false + false + + - 1 3 4 + - none + + + 1 3 4 + + none + + (1 row) diff --git a/contrib/pg_overexplain/expected/pg_overexplain.out b/contrib/pg_overexplain/expected/pg_overexplain.out index 6de02323d7c..0c1d32d386e 100644 --- a/contrib/pg_overexplain/expected/pg_overexplain.out +++ b/contrib/pg_overexplain/expected/pg_overexplain.out @@ -291,13 +291,130 @@ $$); false + false + + - 1 3 4 + - none + + + 1 3 4 + + none + + (1 row) +-- Test JSON format with RANGE_TABLE to verify valid JSON structure. +SELECT explain_filter($$ +EXPLAIN (RANGE_TABLE, FORMAT JSON, COSTS OFF) +SELECT genus, array_agg(name ORDER BY name) FROM vegetables GROUP BY genus +$$); + explain_filter +---------------------------------------------------------------- + [ + + { + + "Plan": { + + "Node Type": "Aggregate", + + "Strategy": "Sorted", + + "Partial Mode": "Simple", + + "Parallel Aware": false, + + "Async Capable": false, + + "Disabled": false, + + "Group Key": ["vegetables.genus"], + + "Plans": [ + + { + + "Node Type": "Sort", + + "Parent Relationship": "Outer", + + "Parallel Aware": false, + + "Async Capable": false, + + "Disabled": false, + + "Sort Key": ["vegetables.genus", "vegetables.name"],+ + "Plans": [ + + { + + "Node Type": "Append", + + "Parent Relationship": "Outer", + + "Parallel Aware": false, + + "Async Capable": false, + + "Disabled": false, + + "Append RTIs": "1", + + "Subplans Removed": 0, + + "Plans": [ + + { + + "Node Type": "Seq Scan", + + "Parent Relationship": "Member", + + "Parallel Aware": false, + + "Async Capable": false, + + "Relation Name": "brassica", + + "Alias": "vegetables_1", + + "Disabled": false, + + "Scan RTI": 3 + + }, + + { + + "Node Type": "Seq Scan", + + "Parent Relationship": "Member", + + "Parallel Aware": false, + + "Async Capable": false, + + "Relation Name": "daucus", + + "Alias": "vegetables_2", + + "Disabled": false, + + "Scan RTI": 4 + + } + + ] + + } + + ] + + } + + ] + + }, + + "Range Table": [ + + { + + "RTI": 1, + + "Kind": "relation", + + "Inherited": true, + + "In From Clause": true, + + "Eref": "vegetables (id, name, genus)", + + "Relation": "vegetables", + + "Relation Kind": "partitioned_table", + + "Relation Lock Mode": "AccessShareLock", + + "Permission Info Index": 1, + + "Security Barrier": false, + + "Lateral": false + + }, + + { + + "RTI": 2, + + "Kind": "group", + + "Inherited": false, + + "In From Clause": false, + + "Eref": "\"*GROUP*\" (genus)", + + "Security Barrier": false, + + "Lateral": false + + }, + + { + + "RTI": 3, + + "Kind": "relation", + + "Inherited": false, + + "In From Clause": true, + + "Alias": "vegetables (id, name, genus)", + + "Eref": "vegetables (id, name, genus)", + + "Relation": "brassica", + + "Relation Kind": "relation", + + "Relation Lock Mode": "AccessShareLock", + + "Security Barrier": false, + + "Lateral": false + + }, + + { + + "RTI": 4, + + "Kind": "relation", + + "Inherited": false, + + "In From Clause": true, + + "Alias": "vegetables (id, name, genus)", + + "Eref": "vegetables (id, name, genus)", + + "Relation": "daucus", + + "Relation Kind": "relation", + + "Relation Lock Mode": "AccessShareLock", + + "Security Barrier": false, + + "Lateral": false + + } + + ], + + "Unprunable RTIs": "1 3 4", + + "Result RTIs": "none" + + } + + ] +(1 row) + -- Test just the DEBUG option. Verify that it shows information about -- disabled nodes, parallel safety, and the parallelModeNeeded flag. SET enable_seqscan = false; diff --git a/contrib/pg_overexplain/pg_overexplain.c b/contrib/pg_overexplain/pg_overexplain.c index de824566f8c..78f128562e6 100644 --- a/contrib/pg_overexplain/pg_overexplain.c +++ b/contrib/pg_overexplain/pg_overexplain.c @@ -658,7 +658,14 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es) ExplainCloseGroup("Range Table Entry", NULL, true, es); } - /* Print PlannedStmt fields that contain RTIs. */ + /* Close the Range Table array before emitting PlannedStmt-level fields. */ + ExplainCloseGroup("Range Table", "Range Table", false, es); + + /* + * Print PlannedStmt fields that contain RTIs. These are properties of + * the PlannedStmt, not of individual RTEs, so they belong outside the + * Range Table array. + */ if (es->format != EXPLAIN_FORMAT_TEXT || !bms_is_empty(plannedstmt->unprunableRelids)) overexplain_bitmapset("Unprunable RTIs", plannedstmt->unprunableRelids, @@ -666,9 +673,6 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es) if (es->format != EXPLAIN_FORMAT_TEXT || plannedstmt->resultRelations != NIL) overexplain_intlist("Result RTIs", plannedstmt->resultRelations, es); - - /* Close group, we're all done */ - ExplainCloseGroup("Range Table", "Range Table", false, es); } /* diff --git a/contrib/pg_overexplain/sql/pg_overexplain.sql b/contrib/pg_overexplain/sql/pg_overexplain.sql index 42e275ac2f9..4703ac3e3d7 100644 --- a/contrib/pg_overexplain/sql/pg_overexplain.sql +++ b/contrib/pg_overexplain/sql/pg_overexplain.sql @@ -66,6 +66,12 @@ EXPLAIN (DEBUG, RANGE_TABLE, FORMAT XML, COSTS OFF) SELECT genus, array_agg(name ORDER BY name) FROM vegetables GROUP BY genus $$); +-- Test JSON format with RANGE_TABLE to verify valid JSON structure. +SELECT explain_filter($$ +EXPLAIN (RANGE_TABLE, FORMAT JSON, COSTS OFF) +SELECT genus, array_agg(name ORDER BY name) FROM vegetables GROUP BY genus +$$); + -- Test just the DEBUG option. Verify that it shows information about -- disabled nodes, parallel safety, and the parallelModeNeeded flag. SET enable_seqscan = false; diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c index b968933ea8b..5b519a2c854 100644 --- a/contrib/pg_prewarm/pg_prewarm.c +++ b/contrib/pg_prewarm/pg_prewarm.c @@ -16,9 +16,11 @@ #include #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" @@ -71,6 +73,8 @@ pg_prewarm(PG_FUNCTION_ARGS) char *ttype; PrewarmType ptype; AclResult aclresult; + char relkind; + Oid privOid; /* Basic sanity checking. */ if (PG_ARGISNULL(0)) @@ -106,9 +110,43 @@ 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)); @@ -233,8 +271,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/t/001_basic.pl b/contrib/pg_prewarm/t/001_basic.pl index d87493d4347..caf524d9592 100644 --- a/contrib/pg_prewarm/t/001_basic.pl +++ b/contrib/pg_prewarm/t/001_basic.pl @@ -11,7 +11,7 @@ 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->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 @@ 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/ivy_squashing.out b/contrib/pg_stat_statements/expected/ivy_squashing.out index cb316616a09..80961df2943 100644 --- a/contrib/pg_stat_statements/expected/ivy_squashing.out +++ b/contrib/pg_stat_statements/expected/ivy_squashing.out @@ -1,6 +1,7 @@ -- -- Const squashing functionality -- +\set EXECUTE_RUN_PREPARE on CREATE EXTENSION pg_stat_statements; -- -- Simple Lists @@ -812,6 +813,23 @@ SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; select where $1 IN ($2 /*, ... */) | 2 (2 rows) +-- IN and ANY clauses with Vars are not squashed. +SELECT * FROM test_squash a, test_squash b WHERE a.id IN (1, 2, 3, b.id, b.id + 1); + id | data | id | data +----+------+----+------ +(0 rows) + +SELECT * FROM test_squash a, test_squash b WHERE a.id = ANY (array[1, ((b.id + b.id * 2)), 5]); + id | data | id | data +----+------+----+------ +(0 rows) + +SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4) \bind 1 2 3 1 +; + id | data | id | data +----+------+----+------ +(0 rows) + -- -- cleanup -- diff --git a/contrib/pg_stat_statements/expected/ivy_utility.out b/contrib/pg_stat_statements/expected/ivy_utility.out index dd873f0fc09..6dbef1429b0 100644 --- a/contrib/pg_stat_statements/expected/ivy_utility.out +++ b/contrib/pg_stat_statements/expected/ivy_utility.out @@ -1,6 +1,7 @@ -- -- Utility commands -- +\set EXECUTE_RUN_PREPARE on -- These tests require track_utility to be enabled. SET pg_stat_statements.track_utility = TRUE; SELECT pg_stat_statements_reset() IS NOT NULL AS t; diff --git a/contrib/pg_stat_statements/expected/planning.out b/contrib/pg_stat_statements/expected/planning.out index 9effd11fdc8..6c99fe0c4c6 100644 --- a/contrib/pg_stat_statements/expected/planning.out +++ b/contrib/pg_stat_statements/expected/planning.out @@ -1,6 +1,7 @@ -- -- Information related to planning -- +\set EXECUTE_RUN_PREPARE on -- These tests require track_planning to be enabled. SET pg_stat_statements.track_planning = TRUE; SELECT pg_stat_statements_reset() IS NOT NULL AS t; diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out index 75c896f3885..47b86fcedb9 100644 --- a/contrib/pg_stat_statements/expected/select.out +++ b/contrib/pg_stat_statements/expected/select.out @@ -1,6 +1,7 @@ -- -- SELECT statements -- +\set EXECUTE_RUN_PREPARE on CREATE EXTENSION pg_stat_statements; SET pg_stat_statements.track_utility = FALSE; SET pg_stat_statements.track_planning = TRUE; @@ -459,6 +460,102 @@ SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%FETCH FIRST%'; 2 (1 row) +-- GROUP BY, HAVING, GROUPING +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY a; + count +------- + 1 +(1 row) + +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY b; + count +------- + 1 +(1 row) + +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY a, b; + count +------- + 1 +(1 row) + +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY b, a; + count +------- + 1 +(1 row) + +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY GROUPING SETS(a, ()); + count +------- + 1 + 1 +(2 rows) + +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY GROUPING SETS(b, ()); + count +------- + 1 + 1 +(2 rows) + +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY a HAVING a = 1; + count +------- + 1 +(1 row) + +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY a HAVING a = 2; + count +------- +(0 rows) + +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY b HAVING b = 1; + count +------- +(0 rows) + +SELECT GROUPING(a) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY a; + grouping +---------- + 0 +(1 row) + +SELECT GROUPING(b) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY b; + grouping +---------- + 0 +(1 row) + +SELECT GROUPING(b) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY a, b; + grouping +---------- + 0 +(1 row) + +SELECT GROUPING(b) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY b, a; + grouping +---------- + 0 +(1 row) + +SELECT calls, query FROM pg_stat_statements WHERE query LIKE '%GROUP BY%' ORDER BY query COLLATE "C"; + calls | query +-------+------------------------------------------------------------------------------------------- + 1 | SELECT COUNT(*) FROM (VALUES ($1::INT, $2::INT)) AS t(a, b) GROUP BY GROUPING SETS(a, ()) + 1 | SELECT COUNT(*) FROM (VALUES ($1::INT, $2::INT)) AS t(a, b) GROUP BY GROUPING SETS(b, ()) + 1 | SELECT COUNT(*) FROM (VALUES ($1::INT, $2::INT)) AS t(a, b) GROUP BY a + 2 | SELECT COUNT(*) FROM (VALUES ($1::INT, $2::INT)) AS t(a, b) GROUP BY a HAVING a = $3 + 1 | SELECT COUNT(*) FROM (VALUES ($1::INT, $2::INT)) AS t(a, b) GROUP BY a, b + 1 | SELECT COUNT(*) FROM (VALUES ($1::INT, $2::INT)) AS t(a, b) GROUP BY b + 1 | SELECT COUNT(*) FROM (VALUES ($1::INT, $2::INT)) AS t(a, b) GROUP BY b HAVING b = $3 + 1 | SELECT COUNT(*) FROM (VALUES ($1::INT, $2::INT)) AS t(a, b) GROUP BY b, a + 1 | SELECT GROUPING(a) FROM (VALUES ($1::INT, $2::INT)) AS t(a, b) GROUP BY a + 1 | SELECT GROUPING(b) FROM (VALUES ($1::INT, $2::INT)) AS t(a, b) GROUP BY a, b + 1 | SELECT GROUPING(b) FROM (VALUES ($1::INT, $2::INT)) AS t(a, b) GROUP BY b + 1 | SELECT GROUPING(b) FROM (VALUES ($1::INT, $2::INT)) AS t(a, b) GROUP BY b, a +(12 rows) + -- GROUP BY [DISTINCT] SELECT a, b, c FROM (VALUES (1, 2, 3), (4, NULL, 6), (7, 8, 9)) AS t (a, b, c) @@ -548,7 +645,7 @@ SELECT ( SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%SELECT GROUPING%'; count ------- - 2 + 6 (1 row) SELECT pg_stat_statements_reset() IS NOT NULL AS t; diff --git a/contrib/pg_stat_statements/expected/squashing.out b/contrib/pg_stat_statements/expected/squashing.out index f952f47ef7b..6963a434db9 100644 --- a/contrib/pg_stat_statements/expected/squashing.out +++ b/contrib/pg_stat_statements/expected/squashing.out @@ -809,6 +809,104 @@ SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; select where $1 IN ($2 /*, ... */) | 2 (2 rows) +-- composite function with row expansion +create table test_composite(x integer); +CREATE FUNCTION composite_f(a integer[], out x integer, out y integer) returns +record as $$ begin + x = a[1]; + y = a[2]; + end; +$$ language plpgsql; +SELECT pg_stat_statements_reset() IS NOT NULL AS t; + t +--- + t +(1 row) + +SELECT ((composite_f(array[1, 2]))).* FROM test_composite; + x | y +---+--- +(0 rows) + +SELECT ((composite_f(array[1, 2, 3]))).* FROM test_composite; + x | y +---+--- +(0 rows) + +SELECT ((composite_f(array[1, 2, 3]))).*, 1, 2, 3, ((composite_f(array[1, 2, 3]))).*, 1, 2 +FROM test_composite +WHERE x IN (1, 2, 3); + x | y | ?column? | ?column? | ?column? | x | y | ?column? | ?column? +---+---+----------+----------+----------+---+---+----------+---------- +(0 rows) + +SELECT ((composite_f(array[1, $1, 3]))).*, 1 FROM test_composite \bind 1 +; + x | y | ?column? +---+---+---------- +(0 rows) + +-- ROW() expression with row expansion +SELECT (ROW(ARRAY[1,2])).*; + f1 +------- + {1,2} +(1 row) + +SELECT (ROW(ARRAY[1, 2], ARRAY[1, 2, 3])).*; + f1 | f2 +-------+--------- + {1,2} | {1,2,3} +(1 row) + +SELECT 1, 2, (ROW(ARRAY[1, 2], ARRAY[1, 2, 3])).*, 3, 4; + ?column? | ?column? | f1 | f2 | ?column? | ?column? +----------+----------+-------+---------+----------+---------- + 1 | 2 | {1,2} | {1,2,3} | 3 | 4 +(1 row) + +SELECT (ROW(ARRAY[1, 2], ARRAY[1, $1, 3])).*, 1 \bind 1 +; + f1 | f2 | ?column? +-------+---------+---------- + {1,2} | {1,1,3} | 1 +(1 row) + +-- IN and ANY clauses with Vars are not squashed. +SELECT * FROM test_squash a, test_squash b WHERE a.id IN (1, 2, 3, b.id, b.id + 1); + id | data | id | data +----+------+----+------ +(0 rows) + +SELECT * FROM test_squash a, test_squash b WHERE a.id = ANY (array[1, ((b.id + b.id * 2)), 5]); + id | data | id | data +----+------+----+------ +(0 rows) + +SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4) \bind 1 2 3 1 +; + id | data | id | data +----+------+----+------ +(0 rows) + +SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls +-------------------------------------------------------------------------------------------------------------+------- + SELECT $1, $2, (ROW(ARRAY[$3 /*, ... */], ARRAY[$4 /*, ... */])).*, $5, $6 | 1 + SELECT ((composite_f(array[$1 /*, ... */]))).* FROM test_composite | 2 + SELECT ((composite_f(array[$1 /*, ... */]))).*, $2 FROM test_composite | 1 + SELECT ((composite_f(array[$1 /*, ... */]))).*, $2, $3, $4, ((composite_f(array[$5 /*, ... */]))).*, $6, $7+| 1 + FROM test_composite +| + WHERE x IN ($8 /*, ... */) | + SELECT (ROW(ARRAY[$1 /*, ... */])).* | 1 + SELECT (ROW(ARRAY[$1 /*, ... */], ARRAY[$2 /*, ... */])).* | 1 + SELECT (ROW(ARRAY[$1 /*, ... */], ARRAY[$2 /*, ... */])).*, $3 | 1 + SELECT * FROM test_squash a, test_squash b WHERE a.id = ANY (array[$1, ((b.id + b.id * $2)), $3]) | 1 + SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4) | 1 + SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4) | 1 + SELECT pg_stat_statements_reset() IS NOT NULL AS t | 1 +(11 rows) + -- -- cleanup -- @@ -818,3 +916,5 @@ DROP TABLE test_squash_numeric; DROP TABLE test_squash_bigint; DROP TABLE test_squash_cast CASCADE; DROP TABLE test_squash_jsonb; +DROP TABLE test_composite; +DROP FUNCTION composite_f; diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 52f3790c4c9..e3dce9613ad 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -34,7 +34,7 @@ * in the file to be read or written while holding only shared lock. * * - * Portions Copyright (c) 2023-2025, IvorySQL Global Development Team + * Portions Copyright (c) 2023-2026, IvorySQL Global Development Team * Copyright (c) 2008-2025, PostgreSQL Global Development Group * * IDENTIFICATION @@ -805,7 +805,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)) @@ -829,7 +829,8 @@ pgss_shmem_shutdown(int code, Datum arg) (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"); @@ -1798,7 +1799,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); } } @@ -2013,7 +2015,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 */ @@ -2300,7 +2303,7 @@ qtext_store(const char *query, int query_len, } /* - * Read the external query text file into a malloc'd buffer. + * Read the external query text file into a palloc'd buffer. * * Returns NULL (without throwing an error) if unable to read, eg * file not there or insufficient memory. @@ -2342,7 +2345,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) @@ -2381,7 +2384,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; } @@ -2592,7 +2595,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 @@ -2609,7 +2612,8 @@ gc_qtexts(void) /* 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 @@ -2922,9 +2926,8 @@ generate_normalized_query(JumbleState *jstate, const char *query, * have originated from within the authoritative parser, this should not be * a problem. * - * Duplicate constant pointers are possible, and will have their lengths - * marked as '-1', so that they are later ignored. (Actually, we assume the - * lengths were initialized as -1 to start with, and don't change them here.) + * Multiple constants can have the same location. We reset lengths of those + * past the first to -1 so that they can later be ignored. * * If query_loc > 0, then "query" has been advanced by that much compared to * the original string start, so we need to translate the provided locations @@ -2955,8 +2958,6 @@ standard_fill_in_constant_lengths(JumbleState *jstate, const char *query, core_yy_extra_type yyextra; core_YYSTYPE yylval; YYLTYPE yylloc; - int last_loc = -1; - int i; /* * Sort the records by location so that we can process them in order while @@ -2977,23 +2978,29 @@ standard_fill_in_constant_lengths(JumbleState *jstate, const char *query, yyextra.escape_string_warning = false; /* Search for each constant, in sequence */ - for (i = 0; i < jstate->clocations_count; i++) + for (int i = 0; i < jstate->clocations_count; i++) { - int loc = locs[i].location; + int loc; int tok; - /* Adjust recorded location if we're dealing with partial string */ - loc -= query_loc; - - Assert(loc >= 0); + /* Ignore constants after the first one in the same location */ + if (i > 0 && locs[i].location == locs[i - 1].location) + { + locs[i].length = -1; + continue; + } if (locs[i].squashed) continue; /* squashable list, ignore */ - if (loc <= last_loc) - continue; /* Duplicate constant, ignore */ + /* Adjust recorded location if we're dealing with partial string */ + loc = locs[i].location - query_loc; + Assert(loc >= 0); - /* Lex tokens until we find the desired constant */ + /* + * We have a valid location for a constant that's not a dupe. Lex + * tokens until we find the desired constant. + */ for (;;) { tok = core_yylex(&yylval, &yylloc, yyscanner); @@ -3039,8 +3046,6 @@ standard_fill_in_constant_lengths(JumbleState *jstate, const char *query, /* If we hit end-of-string, give up, leaving remaining lengths -1 */ if (tok == 0) break; - - last_loc = loc; } scanner_finish(yyscanner); diff --git a/contrib/pg_stat_statements/sql/ivy_squashing.sql b/contrib/pg_stat_statements/sql/ivy_squashing.sql index 259bb151dd0..4ac3a291b8f 100644 --- a/contrib/pg_stat_statements/sql/ivy_squashing.sql +++ b/contrib/pg_stat_statements/sql/ivy_squashing.sql @@ -1,6 +1,7 @@ -- -- Const squashing functionality -- +\set EXECUTE_RUN_PREPARE on CREATE EXTENSION pg_stat_statements; -- @@ -294,6 +295,12 @@ select where '1' IN ('1'::int::text, '2'::int::text); select where '1' = ANY (array['1'::int::text, '2'::int::text]); SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; +-- IN and ANY clauses with Vars are not squashed. +SELECT * FROM test_squash a, test_squash b WHERE a.id IN (1, 2, 3, b.id, b.id + 1); +SELECT * FROM test_squash a, test_squash b WHERE a.id = ANY (array[1, ((b.id + b.id * 2)), 5]); +SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4) \bind 1 2 3 1 +; + -- -- cleanup -- diff --git a/contrib/pg_stat_statements/sql/ivy_utility.sql b/contrib/pg_stat_statements/sql/ivy_utility.sql index cbfce169357..b6d68de1a72 100644 --- a/contrib/pg_stat_statements/sql/ivy_utility.sql +++ b/contrib/pg_stat_statements/sql/ivy_utility.sql @@ -1,6 +1,7 @@ -- -- Utility commands -- +\set EXECUTE_RUN_PREPARE on -- These tests require track_utility to be enabled. SET pg_stat_statements.track_utility = TRUE; diff --git a/contrib/pg_stat_statements/sql/planning.sql b/contrib/pg_stat_statements/sql/planning.sql index 46f5d9b951c..9bfcff07d4f 100644 --- a/contrib/pg_stat_statements/sql/planning.sql +++ b/contrib/pg_stat_statements/sql/planning.sql @@ -1,6 +1,7 @@ -- -- Information related to planning -- +\set EXECUTE_RUN_PREPARE on -- These tests require track_planning to be enabled. SET pg_stat_statements.track_planning = TRUE; diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql index 11662cde08c..3dc185e4930 100644 --- a/contrib/pg_stat_statements/sql/select.sql +++ b/contrib/pg_stat_statements/sql/select.sql @@ -1,6 +1,7 @@ -- -- SELECT statements -- +\set EXECUTE_RUN_PREPARE on CREATE EXTENSION pg_stat_statements; SET pg_stat_statements.track_utility = FALSE; @@ -158,6 +159,22 @@ FETCH FIRST 2 ROW ONLY; SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%FETCH FIRST%'; +-- GROUP BY, HAVING, GROUPING +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY a; +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY b; +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY a, b; +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY b, a; +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY GROUPING SETS(a, ()); +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY GROUPING SETS(b, ()); +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY a HAVING a = 1; +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY a HAVING a = 2; +SELECT COUNT(*) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY b HAVING b = 1; +SELECT GROUPING(a) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY a; +SELECT GROUPING(b) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY b; +SELECT GROUPING(b) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY a, b; +SELECT GROUPING(b) FROM (VALUES (1::INT, 2::INT)) AS t(a, b) GROUP BY b, a; +SELECT calls, query FROM pg_stat_statements WHERE query LIKE '%GROUP BY%' ORDER BY query COLLATE "C"; + -- GROUP BY [DISTINCT] SELECT a, b, c FROM (VALUES (1, 2, 3), (4, NULL, 6), (7, 8, 9)) AS t (a, b, c) diff --git a/contrib/pg_stat_statements/sql/squashing.sql b/contrib/pg_stat_statements/sql/squashing.sql index 53138d125a9..2100f2d83fd 100644 --- a/contrib/pg_stat_statements/sql/squashing.sql +++ b/contrib/pg_stat_statements/sql/squashing.sql @@ -291,6 +291,36 @@ select where '1' IN ('1'::int::text, '2'::int::text); select where '1' = ANY (array['1'::int::text, '2'::int::text]); SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; +-- composite function with row expansion +create table test_composite(x integer); +CREATE FUNCTION composite_f(a integer[], out x integer, out y integer) returns +record as $$ begin + x = a[1]; + y = a[2]; + end; +$$ language plpgsql; +SELECT pg_stat_statements_reset() IS NOT NULL AS t; +SELECT ((composite_f(array[1, 2]))).* FROM test_composite; +SELECT ((composite_f(array[1, 2, 3]))).* FROM test_composite; +SELECT ((composite_f(array[1, 2, 3]))).*, 1, 2, 3, ((composite_f(array[1, 2, 3]))).*, 1, 2 +FROM test_composite +WHERE x IN (1, 2, 3); +SELECT ((composite_f(array[1, $1, 3]))).*, 1 FROM test_composite \bind 1 +; +-- ROW() expression with row expansion +SELECT (ROW(ARRAY[1,2])).*; +SELECT (ROW(ARRAY[1, 2], ARRAY[1, 2, 3])).*; +SELECT 1, 2, (ROW(ARRAY[1, 2], ARRAY[1, 2, 3])).*, 3, 4; +SELECT (ROW(ARRAY[1, 2], ARRAY[1, $1, 3])).*, 1 \bind 1 +; + +-- IN and ANY clauses with Vars are not squashed. +SELECT * FROM test_squash a, test_squash b WHERE a.id IN (1, 2, 3, b.id, b.id + 1); +SELECT * FROM test_squash a, test_squash b WHERE a.id = ANY (array[1, ((b.id + b.id * 2)), 5]); +SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4) \bind 1 2 3 1 +; +SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; + -- -- cleanup -- @@ -300,3 +330,5 @@ DROP TABLE test_squash_numeric; DROP TABLE test_squash_bigint; DROP TABLE test_squash_cast CASCADE; DROP TABLE test_squash_jsonb; +DROP TABLE test_composite; +DROP FUNCTION composite_f; diff --git a/contrib/pg_trgm/Makefile b/contrib/pg_trgm/Makefile index 5fb927d2650..b169c9084f4 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 ORA_REGRESS = ivy_pg_trgm pg_word_trgm pg_strict_word_trgm ifdef USE_PGXS diff --git a/contrib/pg_trgm/data/trgm_utf8.data b/contrib/pg_trgm/data/trgm_utf8.data new file mode 100644 index 00000000000..713856e76a6 --- /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/ivy_pg_trgm.out b/contrib/pg_trgm/expected/ivy_pg_trgm.out index 84882a74c3d..aef6dd80e2e 100644 --- a/contrib/pg_trgm/expected/ivy_pg_trgm.out +++ b/contrib/pg_trgm/expected/ivy_pg_trgm.out @@ -4694,6 +4694,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 varchar2(1024) COLLATE "C"); create index t_trgm_idx on t_test_trgm using gin (t gin_trgm_ops); @@ -4732,6 +4749,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 %> ''::text) AND ((t)::text %> '%qwerty%'::text)) + -> Bitmap Index Scan on t_trgm_idx + Index Cond: (((t)::text %> ''::text) AND ((t)::text %> '%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; @@ -4747,6 +4781,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 ------- @@ -4759,6 +4799,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 varchar2(1024) COLLATE "C"); insert into test2 values ('abcdef'); diff --git a/contrib/pg_trgm/expected/pg_trgm.out b/contrib/pg_trgm/expected/pg_trgm.out index 0b70d9de256..04da98170ab 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 00000000000..0768e7d6a83 --- /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 00000000000..8505c4fa552 --- /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 a31aa5c574d..b808215f74d 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/ivy_pg_trgm.sql b/contrib/pg_trgm/sql/ivy_pg_trgm.sql index 46d6f25ae51..03249c1c53a 100644 --- a/contrib/pg_trgm/sql/ivy_pg_trgm.sql +++ b/contrib/pg_trgm/sql/ivy_pg_trgm.sql @@ -81,6 +81,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 varchar2(1024) COLLATE "C"); create index t_trgm_idx on t_test_trgm using gin (t gin_trgm_ops); @@ -91,14 +94,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 varchar2(1024) COLLATE "C"); diff --git a/contrib/pg_trgm/sql/pg_trgm.sql b/contrib/pg_trgm/sql/pg_trgm.sql index 340c9891899..44debced6d5 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 00000000000..0dd962ced83 --- /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 ca017585369..ca23aad4dd9 100644 --- a/contrib/pg_trgm/trgm.h +++ b/contrib/pg_trgm/trgm.h @@ -47,7 +47,7 @@ typedef char trgm[3]; } while(0) extern int (*CMPTRGM) (const void *a, const void *b); -#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)==' ') ) #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 5ba895217b0..43afd11fa67 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. */ diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c index 29b39ec8a4c..fa83cc7aa29 100644 --- a/contrib/pg_trgm/trgm_op.c +++ b/contrib/pg_trgm/trgm_op.c @@ -66,6 +66,78 @@ typedef uint8 TrgmBound; #define WORD_SIMILARITY_STRICT 0x02 /* force bounds of extent to match * word bounds */ +/* + * A growable array of trigrams + * + * The actual array of trigrams is in 'datum'. Note that the other fields in + * 'datum', i.e. datum->flags and the varlena length, are not kept up to date + * when items are added to the growable array. We merely reserve the space + * for them here. You must fill those other fields before using 'datum' as a + * proper TRGM datum. + */ +typedef struct +{ + TRGM *datum; /* trigram array */ + int length; /* number of trigrams in the array */ + int allocated; /* allocated size of 'datum' (# of trigrams) */ +} growable_trgm_array; + +/* + * Allocate a new growable array. + * + * 'slen' is the size of the source string that we're extracting the trigrams + * from. It is used to choose the initial size of the array. + */ +static void +init_trgm_array(growable_trgm_array *arr, int slen) +{ + size_t init_size; + + /* + * In the extreme case, the input string consists entirely of one + * character words, like "a b c", where each word is expanded to two + * trigrams. This is not a strict upper bound though, because when + * IGNORECASE is defined, we convert the input string to lowercase before + * extracting the trigrams, which in rare cases can expand one input + * character into multiple characters. + */ + init_size = (size_t) slen + 1; + + /* + * Guard against possible overflow in the palloc request. (We don't worry + * about the additive constants, since palloc can detect requests that are + * a little above MaxAllocSize --- we just need to prevent integer + * overflow in the multiplications.) + */ + if (init_size > MaxAllocSize / sizeof(trgm)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("out of memory"))); + + arr->datum = palloc(CALCGTSIZE(ARRKEY, init_size)); + arr->allocated = init_size; + arr->length = 0; +} + +/* Make sure the array can hold at least 'needed' more trigrams */ +static void +enlarge_trgm_array(growable_trgm_array *arr, int needed) +{ + size_t new_needed = (size_t) arr->length + needed; + + if (new_needed > arr->allocated) + { + /* Guard against possible overflow, like in init_trgm_array */ + if (new_needed > MaxAllocSize / sizeof(trgm)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("out of memory"))); + + arr->datum = repalloc(arr->datum, CALCGTSIZE(ARRKEY, new_needed)); + arr->allocated = new_needed; + } +} + /* * Module load callback */ @@ -220,22 +292,31 @@ comp_trgm(const void *a, const void *b) * endword points to the character after word */ static char * -find_word(char *str, int lenstr, char **endword, int *charlen) +find_word(char *str, int lenstr, char **endword) { 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); - (*charlen)++; + int clen = pg_mblen_range(*endword, endstr); + + if (!ISWORDCHR(*endword, clen)) + break; + *endword += clen; } return beginword; @@ -269,78 +350,138 @@ compact_trigram(trgm *tptr, char *str, int bytelen) } /* - * Adds trigrams from words (already padded). + * Adds trigrams from the word in 'str' (already padded if necessary). */ -static trgm * -make_trigrams(trgm *tptr, char *str, int bytelen, int charlen) +static void +make_trigrams(growable_trgm_array *dst, char *str, int bytelen) { + trgm *tptr; char *ptr = str; - if (charlen < 3) - return tptr; + if (bytelen < 3) + return; - 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); + /* max number of trigrams = strlen - 2 */ + enlarge_trgm_array(dst, bytelen - 2); + tptr = GETARR(dst->datum) + dst->length; - while ((ptr - str) + lenfirst + lenmiddle + lenlast <= bytelen) + if (pg_encoding_max_length(GetDatabaseEncoding()) == 1) + { + while (ptr < str + bytelen - 2) { - compact_trigram(tptr, ptr, lenfirst + lenmiddle + lenlast); - - ptr += lenfirst; + CPTRGM(tptr, ptr); + ptr++; tptr++; - - lenfirst = lenmiddle; - lenmiddle = lenlast; - lenlast = pg_mblen(ptr + lenfirst + lenmiddle); } } else { - /* Fast path when there are no multibyte characters */ - Assert(bytelen == charlen); + int lenfirst, + lenmiddle, + lenlast; + char *endptr; - while (ptr - str < bytelen - 2 /* number of trigrams = strlen - 2 */ ) + /* + * Fast path as long as there are no multibyte characters + */ + if (!IS_HIGHBIT_SET(ptr[0]) && !IS_HIGHBIT_SET(ptr[1])) { - CPTRGM(tptr, ptr); - ptr++; + while (!IS_HIGHBIT_SET(ptr[2])) + { + CPTRGM(tptr, ptr); + ptr++; + tptr++; + + if (ptr == str + bytelen - 2) + goto done; + } + + lenfirst = 1; + lenmiddle = 1; + lenlast = pg_mblen_unbounded(ptr + 2); + } + else + { + lenfirst = pg_mblen_unbounded(ptr); + if (ptr + lenfirst >= str + bytelen) + goto done; + lenmiddle = pg_mblen_unbounded(ptr + lenfirst); + if (ptr + lenfirst + lenmiddle >= str + bytelen) + goto done; + lenlast = pg_mblen_unbounded(ptr + lenfirst + lenmiddle); + } + + /* + * Slow path to handle any remaining multibyte characters + * + * As we go, 'ptr' points to the beginning of the current + * three-character string and 'endptr' points to just past it. + */ + endptr = ptr + lenfirst + lenmiddle + lenlast; + while (endptr <= str + bytelen) + { + compact_trigram(tptr, ptr, endptr - ptr); tptr++; + + /* Advance to the next character */ + if (endptr == str + bytelen) + break; + ptr += lenfirst; + lenfirst = lenmiddle; + lenmiddle = lenlast; + lenlast = pg_mblen_unbounded(endptr); + endptr += lenlast; } } - return tptr; +done: + dst->length = tptr - GETARR(dst->datum); + Assert(dst->length <= dst->allocated); } /* * Make array of trigrams without sorting and removing duplicate items. * - * trg: where to return the array of trigrams. + * dst: where to return the array of trigrams. * str: source string, of length slen bytes. - * bounds: where to return bounds of trigrams (if needed). - * - * Returns length of the generated array. + * bounds_p: where to return bounds of trigrams (if needed). */ -static int -generate_trgm_only(trgm *trg, char *str, int slen, TrgmBound *bounds) +static void +generate_trgm_only(growable_trgm_array *dst, char *str, int slen, TrgmBound **bounds_p) { - trgm *tptr; + size_t buflen; char *buf; - int charlen, - bytelen; + int bytelen; char *bword, *eword; + TrgmBound *bounds = NULL; + int bounds_allocated = 0; - if (slen + LPADDING + RPADDING < 3 || slen == 0) - return 0; + init_trgm_array(dst, slen); - tptr = trg; + /* + * If requested, allocate an array for the bounds, with the same size as + * the trigram array. + */ + if (bounds_p) + { + bounds_allocated = dst->allocated; + bounds = *bounds_p = palloc0_array(TrgmBound, bounds_allocated); + } - /* Allocate a buffer for case-folded, blank-padded words */ - buf = (char *) palloc(slen * pg_database_encoding_max_length() + 4); + if (slen + LPADDING + RPADDING < 3 || slen == 0) + return; + /* + * Allocate a buffer for case-folded, blank-padded words. + * + * As an initial guess, allocate a buffer large enough to hold the + * original string with padding, which is always enough when compiled with + * !IGNORECASE. If the case-folding produces a string longer than the + * original, we'll grow the buffer. + */ + buflen = (size_t) slen + 4; + buf = (char *) palloc(buflen); if (LPADDING > 0) { *buf = ' '; @@ -349,52 +490,59 @@ generate_trgm_only(trgm *trg, char *str, int slen, TrgmBound *bounds) } eword = str; - while ((bword = find_word(eword, slen - (eword - str), &eword, &charlen)) != NULL) + while ((bword = find_word(eword, slen - (eword - str), &eword)) != NULL) { + int oldlen; + + /* Convert word to lower case before extracting trigrams from it */ #ifdef IGNORECASE - bword = str_tolower(bword, eword - bword, DEFAULT_COLLATION_OID); - bytelen = strlen(bword); + { + char *lowered; + + lowered = str_tolower(bword, eword - bword, DEFAULT_COLLATION_OID); + bytelen = strlen(lowered); + + /* grow the buffer if necessary */ + if (bytelen > buflen - 4) + { + pfree(buf); + buflen = (size_t) bytelen + 4; + buf = (char *) palloc(buflen); + if (LPADDING > 0) + { + *buf = ' '; + if (LPADDING > 1) + *(buf + 1) = ' '; + } + } + memcpy(buf + LPADDING, lowered, bytelen); + pfree(lowered); + } #else bytelen = eword - bword; -#endif - memcpy(buf + LPADDING, bword, bytelen); - -#ifdef IGNORECASE - pfree(bword); #endif buf[LPADDING + bytelen] = ' '; buf[LPADDING + bytelen + 1] = ' '; /* Calculate trigrams marking their bounds if needed */ + oldlen = dst->length; + make_trigrams(dst, buf, bytelen + LPADDING + RPADDING); if (bounds) - bounds[tptr - trg] |= TRGM_BOUND_LEFT; - tptr = make_trigrams(tptr, buf, bytelen + LPADDING + RPADDING, - charlen + LPADDING + RPADDING); - if (bounds) - bounds[tptr - trg - 1] |= TRGM_BOUND_RIGHT; + { + if (bounds_allocated < dst->length) + { + bounds = *bounds_p = repalloc0_array(bounds, TrgmBound, bounds_allocated, dst->allocated); + bounds_allocated = dst->allocated; + } + + bounds[oldlen] |= TRGM_BOUND_LEFT; + bounds[dst->length - 1] |= TRGM_BOUND_RIGHT; + } } pfree(buf); - - return tptr - trg; -} - -/* - * Guard against possible overflow in the palloc requests below. (We - * don't worry about the additive constants, since palloc can detect - * requests that are a little above MaxAllocSize --- we just need to - * prevent integer overflow in the multiplications.) - */ -static void -protect_out_of_mem(int slen) -{ - if ((Size) (slen / 2) >= (MaxAllocSize / (sizeof(trgm) * 3)) || - (Size) slen >= (MaxAllocSize / pg_database_encoding_max_length())) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("out of memory"))); } /* @@ -408,19 +556,14 @@ TRGM * generate_trgm(char *str, int slen) { TRGM *trg; + growable_trgm_array arr; int len; - protect_out_of_mem(slen); - - trg = (TRGM *) palloc(TRGMHDRSIZE + sizeof(trgm) * (slen / 2 + 1) * 3); + generate_trgm_only(&arr, str, slen, NULL); + len = arr.length; + trg = arr.datum; trg->flag = ARRKEY; - len = generate_trgm_only(GETARR(trg), str, slen, NULL); - SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, len)); - - if (len == 0) - return trg; - /* * Make trigrams unique. */ @@ -675,8 +818,8 @@ calc_word_similarity(char *str1, int slen1, char *str2, int slen2, { bool *found; pos_trgm *ptrg; - trgm *trg1; - trgm *trg2; + growable_trgm_array trg1; + growable_trgm_array trg2; int len1, len2, len, @@ -685,27 +828,21 @@ calc_word_similarity(char *str1, int slen1, char *str2, int slen2, ulen1; int *trg2indexes; float4 result; - TrgmBound *bounds; - - protect_out_of_mem(slen1 + slen2); + TrgmBound *bounds = NULL; /* Make positional trigrams */ - trg1 = (trgm *) palloc(sizeof(trgm) * (slen1 / 2 + 1) * 3); - trg2 = (trgm *) palloc(sizeof(trgm) * (slen2 / 2 + 1) * 3); - if (flags & WORD_SIMILARITY_STRICT) - bounds = (TrgmBound *) palloc0(sizeof(TrgmBound) * (slen2 / 2 + 1) * 3); - else - bounds = NULL; - len1 = generate_trgm_only(trg1, str1, slen1, NULL); - len2 = generate_trgm_only(trg2, str2, slen2, bounds); + generate_trgm_only(&trg1, str1, slen1, NULL); + len1 = trg1.length; + generate_trgm_only(&trg2, str2, slen2, (flags & WORD_SIMILARITY_STRICT) ? &bounds : NULL); + len2 = trg2.length; - ptrg = make_positional_trgm(trg1, len1, trg2, len2); + ptrg = make_positional_trgm(GETARR(trg1.datum), len1, GETARR(trg2.datum), len2); len = len1 + len2; qsort(ptrg, len, sizeof(pos_trgm), comp_ptrgm); - pfree(trg1); - pfree(trg2); + pfree(trg1.datum); + pfree(trg2.datum); /* * Merge positional trigrams array: enumerate each trigram and find its @@ -761,20 +898,20 @@ calc_word_similarity(char *str1, int slen1, char *str2, int slen2, * str: source string, of length lenstr bytes (need not be null-terminated) * buf: where to return the substring (must be long enough) * *bytelen: receives byte length of the found substring - * *charlen: receives character length of the found substring * * Returns pointer to end+1 of the found substring in the source string. - * Returns NULL if no word found (in which case buf, bytelen, charlen not set) + * Returns NULL if no word found (in which case buf, bytelen is not set) * * If the found word is bounded by non-word characters or string boundaries * then this function will include corresponding padding spaces into buf. */ static const char * get_wildcard_part(const char *str, int lenstr, - char *buf, int *bytelen, int *charlen) + char *buf, int *bytelen) { 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; @@ -787,11 +924,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; @@ -802,12 +941,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; } /* @@ -820,18 +959,13 @@ get_wildcard_part(const char *str, int lenstr, * Add left padding spaces if preceding character wasn't wildcard * meta-character. */ - *charlen = 0; if (!in_leading_wildcard_meta) { if (LPADDING > 0) { *s++ = ' '; - (*charlen)++; if (LPADDING > 1) - { *s++ = ' '; - (*charlen)++; - } } } @@ -840,15 +974,14 @@ 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)++; s += clen; } else @@ -873,10 +1006,9 @@ 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)++; s += clen; } else @@ -894,12 +1026,8 @@ get_wildcard_part(const char *str, int lenstr, if (RPADDING > 0) { *s++ = ' '; - (*charlen)++; if (RPADDING > 1) - { *s++ = ' '; - (*charlen)++; - } } } @@ -918,24 +1046,21 @@ TRGM * generate_wildcard_trgm(const char *str, int slen) { TRGM *trg; - char *buf, - *buf2; - trgm *tptr; + growable_trgm_array arr; + char *buf; int len, - charlen, bytelen; const char *eword; - protect_out_of_mem(slen); - - trg = (TRGM *) palloc(TRGMHDRSIZE + sizeof(trgm) * (slen / 2 + 1) * 3); - trg->flag = ARRKEY; - SET_VARSIZE(trg, TRGMHDRSIZE); - if (slen + LPADDING + RPADDING < 3 || slen == 0) + { + trg = (TRGM *) palloc(TRGMHDRSIZE); + trg->flag = ARRKEY; + SET_VARSIZE(trg, TRGMHDRSIZE); return trg; + } - tptr = GETARR(trg); + init_trgm_array(&arr, slen); /* Allocate a buffer for blank-padded, but not yet case-folded, words */ buf = palloc(sizeof(char) * (slen + 4)); @@ -945,39 +1070,41 @@ generate_wildcard_trgm(const char *str, int slen) */ eword = str; while ((eword = get_wildcard_part(eword, slen - (eword - str), - buf, &bytelen, &charlen)) != NULL) + buf, &bytelen)) != NULL) { + char *word; + #ifdef IGNORECASE - buf2 = str_tolower(buf, bytelen, DEFAULT_COLLATION_OID); - bytelen = strlen(buf2); + word = str_tolower(buf, bytelen, DEFAULT_COLLATION_OID); + bytelen = strlen(word); #else - buf2 = buf; + word = buf; #endif /* * count trigrams */ - tptr = make_trigrams(tptr, buf2, bytelen, charlen); + make_trigrams(&arr, word, bytelen); #ifdef IGNORECASE - pfree(buf2); + pfree(word); #endif } pfree(buf); - if ((len = tptr - GETARR(trg)) == 0) - return trg; - /* * Make trigrams unique. */ + trg = arr.datum; + len = arr.length; if (len > 1) { qsort(GETARR(trg), len, sizeof(trgm), comp_trgm); len = qunique(GETARR(trg), len, sizeof(trgm), comp_trgm); } + trg->flag = ARRKEY; SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, len)); return trg; diff --git a/contrib/pg_trgm/trgm_regexp.c b/contrib/pg_trgm/trgm_regexp.c index 149f9eb259c..b21b959b6c8 100644 --- a/contrib/pg_trgm/trgm_regexp.c +++ b/contrib/pg_trgm/trgm_regexp.c @@ -483,7 +483,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); @@ -808,10 +808,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; @@ -823,13 +824,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 @@ -837,11 +840,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 @@ -858,12 +861,12 @@ convertPgWchar(pg_wchar c, trgm_mb_char *result) */ #ifdef IGNORECASE { - char *lowerCased = str_tolower(s, strlen(s), DEFAULT_COLLATION_OID); + char *lowerCased = str_tolower(s, clen, DEFAULT_COLLATION_OID); if (strcmp(lowerCased, s) != 0) { pfree(lowerCased); - return false; + return 0; } pfree(lowerCased); } @@ -871,7 +874,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 fdbcb687a87..a3aed470ecb 100644 --- a/contrib/pgcrypto/Makefile +++ b/contrib/pgcrypto/Makefile @@ -44,11 +44,12 @@ 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 crypt-shacrypt + pgp-pubkey-decrypt pgp-pubkey-encrypt pgp-pubkey-session \ + pgp-info crypt-shacrypt ORA_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 $(CF_PGP_TESTS) \ + pgp-armor ivy-pgp-decrypt pgp-encrypt $(CF_PGP_TESTS) \ pgp-pubkey-decrypt pgp-pubkey-encrypt pgp-info ivy-crypt-shacrypt ifdef USE_PGXS diff --git a/contrib/pgcrypto/crypt-sha.c b/contrib/pgcrypto/crypt-sha.c index 7ec21771a83..e8f32bc3896 100644 --- a/contrib/pgcrypto/crypt-sha.c +++ b/contrib/pgcrypto/crypt-sha.c @@ -328,7 +328,7 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid character in salt string: \"%.*s\"", - pg_mblen(ep), ep)); + pg_mblen_cstr(ep), ep)); } else { diff --git a/contrib/pgcrypto/expected/hmac-md5_2.out b/contrib/pgcrypto/expected/hmac-md5_2.out new file mode 100644 index 00000000000..08cdf95d532 --- /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/ivy-pgp-decrypt.out b/contrib/pgcrypto/expected/ivy-pgp-decrypt.out new file mode 100644 index 00000000000..279477104a7 --- /dev/null +++ b/contrib/pgcrypto/expected/ivy-pgp-decrypt.out @@ -0,0 +1,441 @@ +-- +-- pgp decrypt tests +-- +-- Checking ciphers +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.blowfish.sha1.mdc.s2k3.z0 + +jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS +yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE= +=JcP+ +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest +UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA== +=XtrP +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k3.z0 + +jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI +5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g== +=rCZt +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k3.z0 + +jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/ +lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog== +=fB6S +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking MDC modes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.nomdc.s2k3.z0 + +jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+ +u9YkgfJfsuRJmgQ9tmo= +=60ui +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE +8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q== +=moGf +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking hashes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.md5.mdc.s2k3.z0 + +jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO +KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA== +=NyLk +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m +/PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw== +=FxbQ +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking S2K modes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k0.z0 + +jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw +Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw== +=YvkV +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k1.z0 + +jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK +4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz +=dbXm +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl +z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg== +=VJKg +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k0.z0 + +jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E +Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw== +=cg+i +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k1.z0 + +jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9 +7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt +=aHmC +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k3.z0 + +jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv +q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw== +=K0LS +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k0.z0 + +jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu +rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w== +=RGts +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k1.z0 + +jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO ++KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR +=SUrU +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k3.z0 + +jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+ +4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA== +=XZrG +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking longer passwords +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi +tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw== +=XKKG +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6 +CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg== +=gWDh +-----END PGP MESSAGE----- +'), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt +FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q== +=OxOF +-----END PGP MESSAGE----- +'), 'x'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking various data +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8 +Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg== +=W/ik +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + digest +-------------------------------------------- + \x0225e3ede6f2587b076d021a189ff60aad67e066 +(1 row) + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat2.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB +SaV9L04ky1qECNDx3XjnoKLC+H7IOQ== +=Fxen +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + digest +-------------------------------------------- + \xda39a3ee5e6b4b0d3255bfef95601890afd80709 +(1 row) + +select digest(pgp_sym_decrypt_bytea(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat3.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8 +gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk +73Hb8m1yRhQK +=ivrD +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + digest +-------------------------------------------- + \x5e5c135efc0dd00633efc6dfd6e731ea408a5b4c +(1 row) + +-- Checking CRLF +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: crlf mess + +ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms +a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs +=mBP9 +-----END PGP MESSAGE----- +'), 'key', 'convert-crlf=0'), 'sha1'); + digest +-------------------------------------------- + \x9353062be7720f1446d30b9e75573a4833886784 +(1 row) + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: crlf mess + +ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms +a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs +=mBP9 +-----END PGP MESSAGE----- +'), 'key', 'convert-crlf=1'), 'sha1'); + digest +-------------------------------------------- + \x7efefcab38467f7484d6fa43dc86cf5281bd78e2 +(1 row) + +-- check BUG #11905, problem with messages 6 less than a power of 2. +select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530); + ?column? +---------- + t +(1 row) + +-- Negative tests +-- Decryption with a certain incorrect key yields an apparent Literal Data +-- packet reporting its content to be binary data. Ciphertext source: +-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave +-- rise to that property. +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV +VsxxqLSPzNLAeIspJk5G +=mSd/ +-----END PGP MESSAGE----- +'), 'wrong-key', 'debug=1'); +NOTICE: dbg: prefix_init: corrupt prefix +NOTICE: dbg: parse_literal_data: data type=b +NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr +ERROR: Wrong key or corrupt data +-- Routine text/binary mismatch. +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/iSQL 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. +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP +GXsd65oYJZp3Khz0qfyn +=Nmpq +-----END PGP MESSAGE----- +'), 'wrong-key', 'debug=1'); +NOTICE: dbg: prefix_init: corrupt prefix +NOTICE: dbg: parse_compressed_data: bzip2 unsupported +NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr +ERROR: Wrong key or corrupt data +-- Routine use of BZip2 compression. Ciphertext source: +-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \ +-- --personal-cipher-preferences aes --no-emit-version --batch \ +-- --symmetric --passphrase key --armor +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe +QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R +UCAAw2JRIISttRHMfDpDuZJpvYo= +=AZ9M +-----END PGP MESSAGE----- +'), 'key', 'debug=1'); +NOTICE: dbg: parse_compressed_data: bzip2 unsupported +ERROR: Unsupported compression algorithm diff --git a/contrib/pgcrypto/expected/ivy-pgp-decrypt_1.out b/contrib/pgcrypto/expected/ivy-pgp-decrypt_1.out new file mode 100644 index 00000000000..c4fc7c8c20f --- /dev/null +++ b/contrib/pgcrypto/expected/ivy-pgp-decrypt_1.out @@ -0,0 +1,437 @@ +-- +-- pgp decrypt tests +-- +-- Checking ciphers +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.blowfish.sha1.mdc.s2k3.z0 + +jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS +yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE= +=JcP+ +-----END PGP MESSAGE----- +'), 'foobar'); +ERROR: Wrong key or corrupt data +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest +UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA== +=XtrP +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k3.z0 + +jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI +5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g== +=rCZt +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k3.z0 + +jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/ +lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog== +=fB6S +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking MDC modes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.nomdc.s2k3.z0 + +jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+ +u9YkgfJfsuRJmgQ9tmo= +=60ui +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE +8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q== +=moGf +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking hashes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.md5.mdc.s2k3.z0 + +jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO +KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA== +=NyLk +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m +/PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw== +=FxbQ +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking S2K modes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k0.z0 + +jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw +Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw== +=YvkV +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k1.z0 + +jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK +4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz +=dbXm +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl +z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg== +=VJKg +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k0.z0 + +jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E +Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw== +=cg+i +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k1.z0 + +jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9 +7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt +=aHmC +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k3.z0 + +jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv +q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw== +=K0LS +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k0.z0 + +jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu +rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w== +=RGts +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k1.z0 + +jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO ++KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR +=SUrU +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k3.z0 + +jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+ +4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA== +=XZrG +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking longer passwords +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi +tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw== +=XKKG +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6 +CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg== +=gWDh +-----END PGP MESSAGE----- +'), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt +FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q== +=OxOF +-----END PGP MESSAGE----- +'), 'x'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking various data +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8 +Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg== +=W/ik +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + digest +-------------------------------------------- + \x0225e3ede6f2587b076d021a189ff60aad67e066 +(1 row) + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat2.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB +SaV9L04ky1qECNDx3XjnoKLC+H7IOQ== +=Fxen +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + digest +-------------------------------------------- + \xda39a3ee5e6b4b0d3255bfef95601890afd80709 +(1 row) + +select digest(pgp_sym_decrypt_bytea(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat3.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8 +gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk +73Hb8m1yRhQK +=ivrD +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + digest +-------------------------------------------- + \x5e5c135efc0dd00633efc6dfd6e731ea408a5b4c +(1 row) + +-- Checking CRLF +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: crlf mess + +ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms +a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs +=mBP9 +-----END PGP MESSAGE----- +'), 'key', 'convert-crlf=0'), 'sha1'); + digest +-------------------------------------------- + \x9353062be7720f1446d30b9e75573a4833886784 +(1 row) + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: crlf mess + +ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms +a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs +=mBP9 +-----END PGP MESSAGE----- +'), 'key', 'convert-crlf=1'), 'sha1'); + digest +-------------------------------------------- + \x7efefcab38467f7484d6fa43dc86cf5281bd78e2 +(1 row) + +-- check BUG #11905, problem with messages 6 less than a power of 2. +select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530); + ?column? +---------- + t +(1 row) + +-- Negative tests +-- Decryption with a certain incorrect key yields an apparent Literal Data +-- packet reporting its content to be binary data. Ciphertext source: +-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave +-- rise to that property. +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV +VsxxqLSPzNLAeIspJk5G +=mSd/ +-----END PGP MESSAGE----- +'), 'wrong-key', 'debug=1'); +NOTICE: dbg: prefix_init: corrupt prefix +NOTICE: dbg: parse_literal_data: data type=b +NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr +ERROR: Wrong key or corrupt data +-- Routine text/binary mismatch. +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/iSQL 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. +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP +GXsd65oYJZp3Khz0qfyn +=Nmpq +-----END PGP MESSAGE----- +'), 'wrong-key', 'debug=1'); +NOTICE: dbg: prefix_init: corrupt prefix +NOTICE: dbg: parse_compressed_data: bzip2 unsupported +NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr +ERROR: Wrong key or corrupt data +-- Routine use of BZip2 compression. Ciphertext source: +-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \ +-- --personal-cipher-preferences aes --no-emit-version --batch \ +-- --symmetric --passphrase key --armor +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe +QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R +UCAAw2JRIISttRHMfDpDuZJpvYo= +=AZ9M +-----END PGP MESSAGE----- +'), 'key', 'debug=1'); +NOTICE: dbg: parse_compressed_data: bzip2 unsupported +ERROR: Unsupported compression algorithm diff --git a/contrib/pgcrypto/expected/md5_2.out b/contrib/pgcrypto/expected/md5_2.out new file mode 100644 index 00000000000..51bdaa86f32 --- /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 eb049ba9d44..8ce6466f2e9 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. diff --git a/contrib/pgcrypto/expected/pgp-decrypt_1.out b/contrib/pgcrypto/expected/pgp-decrypt_1.out index 80a4c48613d..ee57ad43cb7 100644 --- a/contrib/pgcrypto/expected/pgp-decrypt_1.out +++ b/contrib/pgcrypto/expected/pgp-decrypt_1.out @@ -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. diff --git a/contrib/pgcrypto/expected/pgp-pubkey-session.out b/contrib/pgcrypto/expected/pgp-pubkey-session.out new file mode 100644 index 00000000000..e57cb8fab99 --- /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 7d5ef9b6d32..3927c54458f 100644 --- a/contrib/pgcrypto/meson.build +++ b/contrib/pgcrypto/meson.build @@ -52,6 +52,7 @@ pgcrypto_regress = [ 'pgp-encrypt-md5', 'pgp-pubkey-decrypt', 'pgp-pubkey-encrypt', + 'pgp-pubkey-session', 'pgp-info', 'crypt-shacrypt' ] diff --git a/contrib/pgcrypto/pgp-pgsql.c b/contrib/pgcrypto/pgp-pgsql.c index 7c9f4c7b39b..464ca6e06c9 100644 --- a/contrib/pgcrypto/pgp-pgsql.c +++ b/contrib/pgcrypto/pgp-pgsql.c @@ -631,6 +631,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 +733,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 a0a5738a40e..2a13aa3e6ad 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/px.c b/contrib/pgcrypto/px.c index d35ccca7774..ad0914991b1 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 4b81fceab8e..a09533a3582 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 00000000000..999350bb2bc --- /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/ivy-pgp-decrypt.sql b/contrib/pgcrypto/sql/ivy-pgp-decrypt.sql new file mode 100644 index 00000000000..b499bf757b0 --- /dev/null +++ b/contrib/pgcrypto/sql/ivy-pgp-decrypt.sql @@ -0,0 +1,330 @@ +-- +-- pgp decrypt tests +-- + +-- Checking ciphers +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.blowfish.sha1.mdc.s2k3.z0 + +jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS +yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE= +=JcP+ +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest +UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA== +=XtrP +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k3.z0 + +jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI +5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g== +=rCZt +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k3.z0 + +jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/ +lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog== +=fB6S +-----END PGP MESSAGE----- +'), 'foobar'); + +-- Checking MDC modes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.nomdc.s2k3.z0 + +jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+ +u9YkgfJfsuRJmgQ9tmo= +=60ui +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE +8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q== +=moGf +-----END PGP MESSAGE----- +'), 'foobar'); + +-- Checking hashes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.md5.mdc.s2k3.z0 + +jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO +KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA== +=NyLk +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m +/PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw== +=FxbQ +-----END PGP MESSAGE----- +'), 'foobar'); + +-- Checking S2K modes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k0.z0 + +jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw +Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw== +=YvkV +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k1.z0 + +jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK +4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz +=dbXm +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl +z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg== +=VJKg +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k0.z0 + +jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E +Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw== +=cg+i +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k1.z0 + +jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9 +7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt +=aHmC +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k3.z0 + +jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv +q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw== +=K0LS +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k0.z0 + +jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu +rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w== +=RGts +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k1.z0 + +jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO ++KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR +=SUrU +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k3.z0 + +jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+ +4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA== +=XZrG +-----END PGP MESSAGE----- +'), 'foobar'); + +-- Checking longer passwords +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi +tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw== +=XKKG +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6 +CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg== +=gWDh +-----END PGP MESSAGE----- +'), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt +FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q== +=OxOF +-----END PGP MESSAGE----- +'), 'x'); + +-- Checking various data +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8 +Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg== +=W/ik +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat2.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB +SaV9L04ky1qECNDx3XjnoKLC+H7IOQ== +=Fxen +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + +select digest(pgp_sym_decrypt_bytea(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat3.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8 +gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk +73Hb8m1yRhQK +=ivrD +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + +-- Checking CRLF +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: crlf mess + +ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms +a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs +=mBP9 +-----END PGP MESSAGE----- +'), 'key', 'convert-crlf=0'), 'sha1'); + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: crlf mess + +ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms +a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs +=mBP9 +-----END PGP MESSAGE----- +'), 'key', 'convert-crlf=1'), 'sha1'); + +-- check BUG #11905, problem with messages 6 less than a power of 2. +select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530); + + +-- Negative tests + +-- Decryption with a certain incorrect key yields an apparent Literal Data +-- packet reporting its content to be binary data. Ciphertext source: +-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave +-- rise to that property. +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV +VsxxqLSPzNLAeIspJk5G +=mSd/ +-----END PGP MESSAGE----- +'), 'wrong-key', 'debug=1'); + +-- 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. +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP +GXsd65oYJZp3Khz0qfyn +=Nmpq +-----END PGP MESSAGE----- +'), 'wrong-key', 'debug=1'); + +-- Routine use of BZip2 compression. Ciphertext source: +-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \ +-- --personal-cipher-preferences aes --no-emit-version --batch \ +-- --symmetric --passphrase key --armor +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe +QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R +UCAAw2JRIISttRHMfDpDuZJpvYo= +=AZ9M +-----END PGP MESSAGE----- +'), 'key', 'debug=1'); diff --git a/contrib/pgcrypto/sql/pgp-decrypt.sql b/contrib/pgcrypto/sql/pgp-decrypt.sql index 49a0267bbcb..b499bf757b0 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. diff --git a/contrib/pgcrypto/sql/pgp-pubkey-session.sql b/contrib/pgcrypto/sql/pgp-pubkey-session.sql new file mode 100644 index 00000000000..51792f1f4d8 --- /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 0d9c2b0b653..dbe7a190aaf 100644 --- a/contrib/pgstattuple/pgstattuple.c +++ b/contrib/pgstattuple/pgstattuple.c @@ -424,7 +424,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; @@ -458,10 +458,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; @@ -502,17 +508,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 5dcb3ff9723..b4903eba657 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 2bcd04effac..995bc776eeb 100644 --- a/contrib/postgres_fdw/Makefile +++ b/contrib/postgres_fdw/Makefile @@ -18,6 +18,8 @@ DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql postgres_fdw--1.1--1.2.s REGRESS = postgres_fdw query_cancel ORA_REGRESS = ivy_postgres_fdw +ISOLATION = eval_plan_qual +ISOLATION_OPTS = --load-extension=postgres_fdw TAP_TESTS = 1 ifdef USE_PGXS diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c index 47b20f84607..738edc2c8ee 100644 --- a/contrib/postgres_fdw/connection.c +++ b/contrib/postgres_fdw/connection.c @@ -462,7 +462,7 @@ pgfdw_security_check(const char **keywords, const char **values, UserMapping *us * assume that UseScramPassthrough is also true since SCRAM options are * only set when UseScramPassthrough is enabled. */ - if (MyProcPort->has_scram_keys && pgfdw_has_required_scram_options(keywords, values)) + if (MyProcPort != NULL && MyProcPort->has_scram_keys && pgfdw_has_required_scram_options(keywords, values)) return; ereport(ERROR, @@ -568,7 +568,7 @@ connect_pg_server(ForeignServer *server, UserMapping *user) n++; /* Add required SCRAM pass-through connection options if it's enabled. */ - if (MyProcPort->has_scram_keys && UseScramPassthrough(server, user)) + if (MyProcPort != NULL && MyProcPort->has_scram_keys && UseScramPassthrough(server, user)) { int len; int encoded_len; @@ -660,8 +660,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) @@ -743,7 +744,7 @@ check_conn_params(const char **keywords, const char **values, UserMapping *user) * assume that UseScramPassthrough is also true since SCRAM options are * only set when UseScramPassthrough is enabled. */ - if (MyProcPort->has_scram_keys && pgfdw_has_required_scram_options(keywords, values)) + if (MyProcPort != NULL && MyProcPort->has_scram_keys && pgfdw_has_required_scram_options(keywords, values)) return; ereport(ERROR, @@ -1341,6 +1342,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) @@ -1351,15 +1357,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))); } @@ -2557,7 +2560,7 @@ pgfdw_has_required_scram_options(const char **keywords, const char **values) } } - has_scram_keys = has_scram_client_key && has_scram_server_key && MyProcPort->has_scram_keys; + has_scram_keys = has_scram_client_key && has_scram_server_key && MyProcPort != NULL && MyProcPort->has_scram_keys; return (has_scram_keys && has_require_auth); } diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 29d58ea9931..f5b2f899741 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -8,8 +8,8 @@ * well as functions to construct the query text to be sent. The latter * functionality is annoyingly duplicative of ruleutils.c, but there are * enough special considerations that it seems best to keep this separate. - * One saving grace is that we only need deparse logic for node types that - * we consider safe to send. + * One saving grace is that deparse logic is required only for node types + * considered safe to send. * * We assume that the remote session's search_path is exactly "pg_catalog", * and thus we need schema-qualify all and only names outside pg_catalog. @@ -2976,7 +2976,7 @@ deparseVar(Var *node, deparse_expr_cxt *context) qualify_col); else { - /* Treat like a Param */ + /* Treat as a Param */ if (context->params_list) { int pindex = 0; @@ -3409,7 +3409,7 @@ deparseOperatorName(StringInfo buf, Form_pg_operator opform) { char *opname; - /* opname is not a SQL identifier, so we should not quote it. */ +/* opname is not a SQL identifier, so do not quote it. */ opname = NameStr(opform->oprname); /* Print schema name only if it's not pg_catalog */ 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 00000000000..cee4c6ad1a8 --- /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; + +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; + +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; + +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; + +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/ivy_postgres_fdw.out b/contrib/postgres_fdw/expected/ivy_postgres_fdw.out index c8412608d6f..a11e4c3499c 100644 --- a/contrib/postgres_fdw/expected/ivy_postgres_fdw.out +++ b/contrib/postgres_fdw/expected/ivy_postgres_fdw.out @@ -7595,20 +7595,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'::varchar2(1024), 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'::varchar2(1024), 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 | @@ -9339,6 +9350,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 -- =================================================================== @@ -13872,7 +13996,7 @@ ALTER SERVER loopback2 OPTIONS (DROP parallel_abort); -- =================================================================== CREATE TABLE analyze_table (id number(38,0), a varchar2(1024), b bigint); CREATE FOREIGN TABLE analyze_ftable (id number(38,0), a varchar2(1024), 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; @@ -13880,15 +14004,15 @@ 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; @@ -13947,4 +14071,81 @@ SELECT server_name, -- Clean up \set VERBOSITY default RESET debug_discard_caches; +-- =================================================================== +-- 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" +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 | 1970-01-02 00:00:00.000000 -08:00 | 1970-01-02 00:00:00.000000 | 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; RESET ivorysql.enable_emptystring_to_NULL; diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 2185b42bb4f..f975ceff200 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -6489,20 +6489,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 | @@ -8212,6 +8223,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 -- =================================================================== @@ -12515,7 +12639,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; @@ -12523,15 +12647,15 @@ 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; @@ -12590,3 +12714,79 @@ SELECT server_name, -- Clean up \set VERBOSITY default RESET debug_discard_caches; +-- =================================================================== +-- 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 8b29be24dee..c96030ca089 100644 --- a/contrib/postgres_fdw/meson.build +++ b/contrib/postgres_fdw/meson.build @@ -41,6 +41,12 @@ tests += { ], 'regress_args': ['--dlpath', meson.build_root() / 'src/test/regress'], }, + 'isolation': { + 'specs': [ + 'eval_plan_qual', + ], + 'regress_args': ['--load-extension=postgres_fdw'], + }, 'tap': { 'tests': [ 't/001_auth_scram.pl', diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c index c2f936640bc..6d5795b9fa9 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -531,7 +531,7 @@ process_pgfdw_appname(const char *appname) appendStringInfoString(&buf, application_name); break; case 'c': - appendStringInfo(&buf, INT64_HEX_FORMAT ".%x", MyStartTime, MyProcPid); + appendStringInfo(&buf, "%" PRIx64 ".%x", MyStartTime, MyProcPid); break; case 'C': appendStringInfoString(&buf, cluster_name); 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 00000000000..9f52270db69 --- /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/ivy_postgres_fdw.sql b/contrib/postgres_fdw/sql/ivy_postgres_fdw.sql index 084bdbc0924..afcd5d9ed56 100644 --- a/contrib/postgres_fdw/sql/ivy_postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/ivy_postgres_fdw.sql @@ -1677,9 +1677,15 @@ 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) @@ -2349,6 +2355,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 -- =================================================================== @@ -4388,7 +4472,7 @@ ALTER SERVER loopback2 OPTIONS (DROP parallel_abort); CREATE TABLE analyze_table (id number(38,0), a varchar2(1024), b bigint); CREATE FOREIGN TABLE analyze_ftable (id number(38,0), a varchar2(1024), 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; @@ -4399,19 +4483,19 @@ ANALYZE analyze_table; ALTER SERVER loopback OPTIONS (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; @@ -4459,5 +4543,56 @@ SELECT server_name, \set VERBOSITY default RESET debug_discard_caches; +-- =================================================================== +-- test cleanup of failed connections on abort +-- =================================================================== + +CREATE VIEW my_backend_pid (pid) AS SELECT pg_backend_pid(); +CREATE FOREIGN TABLE remote_backend_pid (pid int) + SERVER loopback OPTIONS (table_name 'my_backend_pid'); +CREATE FUNCTION wait_for_backend_termination(int) RETURNS void AS $$ + BEGIN + WHILE (SELECT count(*) FROM pg_stat_activity WHERE pid = $1) > 0 + LOOP + PERFORM pg_stat_clear_snapshot(); + END LOOP; + END +$$ LANGUAGE plpgsql; +/ + +SET client_min_messages = 'ERROR'; + +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +SAVEPOINT s; +SELECT pid AS remote_pid FROM remote_backend_pid \gset +SELECT pg_terminate_backend(:remote_pid); +SELECT wait_for_backend_termination(:remote_pid); +ROLLBACK TO SAVEPOINT s; +SELECT pid FROM remote_backend_pid; +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +FETCH c; +ABORT; + +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +FETCH c; +SAVEPOINT s; +SELECT pid AS remote_pid FROM remote_backend_pid \gset +SELECT pg_terminate_backend(:remote_pid); +SELECT wait_for_backend_termination(:remote_pid); +ROLLBACK TO SAVEPOINT s; +SELECT pid FROM remote_backend_pid; +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +CLOSE c; +ABORT; + +RESET client_min_messages; + +DROP FUNCTION wait_for_backend_termination(int); +DROP FOREIGN TABLE remote_backend_pid; +DROP VIEW my_backend_pid; RESET ivorysql.enable_emptystring_to_NULL; diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index e534b40de3c..04a5a1244e8 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -1608,9 +1608,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) @@ -2272,6 +2279,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 -- =================================================================== @@ -4278,7 +4363,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; @@ -4289,19 +4374,19 @@ ANALYZE analyze_table; ALTER SERVER loopback OPTIONS (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; @@ -4348,3 +4433,54 @@ SELECT server_name, -- Clean up \set VERBOSITY default RESET debug_discard_caches; + +-- =================================================================== +-- test cleanup of failed connections on abort +-- =================================================================== + +CREATE VIEW my_backend_pid (pid) AS SELECT pg_backend_pid(); +CREATE FOREIGN TABLE remote_backend_pid (pid int) + SERVER loopback OPTIONS (table_name 'my_backend_pid'); +CREATE FUNCTION wait_for_backend_termination(int) RETURNS void AS $$ + BEGIN + WHILE (SELECT count(*) FROM pg_stat_activity WHERE pid = $1) > 0 + LOOP + PERFORM pg_stat_clear_snapshot(); + END LOOP; + END +$$ LANGUAGE plpgsql; + +SET client_min_messages = 'ERROR'; + +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +SAVEPOINT s; +SELECT pid AS remote_pid FROM remote_backend_pid \gset +SELECT pg_terminate_backend(:remote_pid); +SELECT wait_for_backend_termination(:remote_pid); +ROLLBACK TO SAVEPOINT s; +SELECT pid FROM remote_backend_pid; +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +FETCH c; +ABORT; + +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +FETCH c; +SAVEPOINT s; +SELECT pid AS remote_pid FROM remote_backend_pid \gset +SELECT pg_terminate_backend(:remote_pid); +SELECT wait_for_backend_termination(:remote_pid); +ROLLBACK TO SAVEPOINT s; +SELECT pid FROM remote_backend_pid; +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +CLOSE c; +ABORT; + +RESET client_min_messages; + +DROP FUNCTION wait_for_backend_termination(int); +DROP FOREIGN TABLE remote_backend_pid; +DROP VIEW my_backend_pid; diff --git a/contrib/sepgsql/.gitignore b/contrib/sepgsql/.gitignore index b1778d05bbd..7e240e44c36 100644 --- a/contrib/sepgsql/.gitignore +++ b/contrib/sepgsql/.gitignore @@ -3,5 +3,7 @@ /sepgsql-regtest.if /sepgsql-regtest.pp /tmp -# Generated by test suite +# Generated subdirectories +/log/ +/results/ /tmp_check/ diff --git a/contrib/sepgsql/expected/ddl.out b/contrib/sepgsql/expected/ddl.out index 7e8deae4f93..accb903f5ce 100644 --- a/contrib/sepgsql/expected/ddl.out +++ b/contrib/sepgsql/expected/ddl.out @@ -304,6 +304,8 @@ ALTER TABLE regtest_table_4 ALTER COLUMN y TYPE float; LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="public" permissive=0 LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: ALTER TABLE regtest_table_4 ALTER COLUMN y TYPE float; + ^ LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.y" permissive=0 LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.float8(integer)" permissive=0 @@ -388,7 +390,11 @@ ALTER TABLE regtest_ptable_4 ALTER COLUMN y TYPE float; LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="public" permissive=0 LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: ALTER TABLE regtest_ptable_4 ALTER COLUMN y TYPE float; + ^ LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: ALTER TABLE regtest_ptable_4 ALTER COLUMN y TYPE float; + ^ LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.y" permissive=0 LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 diff --git a/contrib/spi/refint.c b/contrib/spi/refint.c index d5e25e07ae9..ab0b2b291f9 100644 --- a/contrib/spi/refint.c +++ b/contrib/spi/refint.c @@ -171,21 +171,24 @@ check_primary_key(PG_FUNCTION_ARGS) if (plan->nplans <= 0) { SPIPlanPtr pplan; - char sql[8192]; + StringInfoData sql; + + initStringInfo(&sql); /* * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 = * $1 [AND Pkey2 = $2 [...]] */ - snprintf(sql, sizeof(sql), "select 1 from %s where ", relname); - for (i = 0; i < nkeys; i++) + appendStringInfo(&sql, "select 1 from %s where ", relname); + for (i = 1; i <= nkeys; i++) { - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s", - args[i + nkeys + 1], i + 1, (i < nkeys - 1) ? "and " : ""); + appendStringInfo(&sql, "%s = $%d ", args[i + nkeys], i); + if (i < nkeys) + appendStringInfoString(&sql, "and "); } /* Prepare plan for query */ - pplan = SPI_prepare(sql, nkeys, argtypes); + pplan = SPI_prepare(sql.data, nkeys, argtypes); if (pplan == NULL) /* internal error */ elog(ERROR, "check_primary_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result)); @@ -201,6 +204,8 @@ check_primary_key(PG_FUNCTION_ARGS) sizeof(SPIPlanPtr)); *(plan->splan) = pplan; plan->nplans = 1; + + pfree(sql.data); } /* @@ -423,7 +428,6 @@ check_foreign_key(PG_FUNCTION_ARGS) if (plan->nplans <= 0) { SPIPlanPtr pplan; - char sql[8192]; char **args2 = args; plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext, @@ -431,6 +435,10 @@ check_foreign_key(PG_FUNCTION_ARGS) for (r = 0; r < nrefs; r++) { + StringInfoData sql; + + initStringInfo(&sql); + relname = args2[0]; /*--------- @@ -444,8 +452,7 @@ check_foreign_key(PG_FUNCTION_ARGS) *--------- */ if (action == 'r') - - snprintf(sql, sizeof(sql), "select 1 from %s where ", relname); + appendStringInfo(&sql, "select 1 from %s where ", relname); /*--------- * For 'C'ascade action we construct DELETE query @@ -472,42 +479,23 @@ check_foreign_key(PG_FUNCTION_ARGS) char *nv; int k; - snprintf(sql, sizeof(sql), "update %s set ", relname); + appendStringInfo(&sql, "update %s set ", relname); for (k = 1; k <= nkeys; k++) { - int is_char_type = 0; - char *type; - fn = SPI_fnumber(tupdesc, args_temp[k - 1]); Assert(fn > 0); /* already checked above */ nv = SPI_getvalue(newtuple, tupdesc, fn); - type = SPI_gettype(tupdesc, fn); - - if (strcmp(type, "text") == 0 || - strcmp(type, "varchar") == 0 || - strcmp(type, "char") == 0 || - strcmp(type, "bpchar") == 0 || - strcmp(type, "date") == 0 || - strcmp(type, "timestamp") == 0) - is_char_type = 1; -#ifdef DEBUG_QUERY - elog(DEBUG4, "check_foreign_key Debug value %s type %s %d", - nv, type, is_char_type); -#endif - /* - * is_char_type =1 i set ' ' for define a new value - */ - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), - " %s = %s%s%s %s ", - args2[k], (is_char_type > 0) ? "'" : "", - nv, (is_char_type > 0) ? "'" : "", (k < nkeys) ? ", " : ""); + appendStringInfo(&sql, " %s = %s ", + args2[k], quote_literal_cstr(nv)); + if (k < nkeys) + appendStringInfoString(&sql, ", "); } - strcat(sql, " where "); + appendStringInfoString(&sql, " where "); } else /* DELETE */ - snprintf(sql, sizeof(sql), "delete from %s where ", relname); + appendStringInfo(&sql, "delete from %s where ", relname); } /* @@ -518,25 +506,26 @@ check_foreign_key(PG_FUNCTION_ARGS) */ else if (action == 's') { - snprintf(sql, sizeof(sql), "update %s set ", relname); + appendStringInfo(&sql, "update %s set ", relname); for (i = 1; i <= nkeys; i++) { - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), - "%s = null%s", - args2[i], (i < nkeys) ? ", " : ""); + appendStringInfo(&sql, "%s = null", args2[i]); + if (i < nkeys) + appendStringInfoString(&sql, ", "); } - strcat(sql, " where "); + appendStringInfoString(&sql, " where "); } /* Construct WHERE qual */ for (i = 1; i <= nkeys; i++) { - snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s", - args2[i], i, (i < nkeys) ? "and " : ""); + appendStringInfo(&sql, "%s = $%d ", args2[i], i); + if (i < nkeys) + appendStringInfoString(&sql, "and "); } /* Prepare plan for query */ - pplan = SPI_prepare(sql, nkeys, argtypes); + pplan = SPI_prepare(sql.data, nkeys, argtypes); if (pplan == NULL) /* internal error */ elog(ERROR, "check_foreign_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result)); @@ -552,11 +541,14 @@ check_foreign_key(PG_FUNCTION_ARGS) plan->splan[r] = pplan; args2 += nkeys + 1; /* to the next relation */ + +#ifdef DEBUG_QUERY + elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql.data); +#endif + + pfree(sql.data); } plan->nplans = nrefs; -#ifdef DEBUG_QUERY - elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql); -#endif } /* diff --git a/contrib/tablefunc/expected/ivy_tablefunc.out b/contrib/tablefunc/expected/ivy_tablefunc.out index 6241131054b..df5b77d51de 100644 --- a/contrib/tablefunc/expected/ivy_tablefunc.out +++ b/contrib/tablefunc/expected/ivy_tablefunc.out @@ -14,6 +14,7 @@ SELECT avg(normal_rand)::int, count(*) FROM normal_rand(-1, 250, 0.2); ERROR: number of rows cannot be negative -- -- crosstab() +-- Use 'orarowid' instead of 'rowid' to avoid conflict with system column -- CREATE TABLE ct(id int, rowclass text, orarowid text, attribute text, val text); \copy ct from 'data/ct.data' diff --git a/contrib/tablefunc/sql/ivy_tablefunc.sql b/contrib/tablefunc/sql/ivy_tablefunc.sql index 26ae0840298..1a862629cf6 100644 --- a/contrib/tablefunc/sql/ivy_tablefunc.sql +++ b/contrib/tablefunc/sql/ivy_tablefunc.sql @@ -10,6 +10,7 @@ SELECT avg(normal_rand)::int, count(*) FROM normal_rand(-1, 250, 0.2); -- -- crosstab() +-- Use 'orarowid' instead of 'rowid' to avoid conflict with system column -- CREATE TABLE ct(id int, rowclass text, orarowid text, attribute text, val text); \copy ct from 'data/ct.data' diff --git a/contrib/unaccent/unaccent.c b/contrib/unaccent/unaccent.c index 336ba31047a..84d5e68a860 100644 --- a/contrib/unaccent/unaccent.c +++ b/contrib/unaccent/unaccent.c @@ -156,7 +156,7 @@ initTrie(const char *filename) state = 0; for (ptr = line; *ptr; ptr += ptrlen) { - ptrlen = pg_mblen(ptr); + ptrlen = pg_mblen_cstr(ptr); /* ignore whitespace, but end src or trg */ if (isspace((unsigned char) *ptr)) { @@ -382,6 +382,7 @@ unaccent_lexize(PG_FUNCTION_ARGS) char *srcchar = (char *) PG_GETARG_POINTER(1); int32 len = PG_GETARG_INT32(2); char *srcstart = srcchar; + const char *srcend = srcstart + len; TSLexeme *res; StringInfoData buf; @@ -409,7 +410,7 @@ unaccent_lexize(PG_FUNCTION_ARGS) } else { - matchlen = pg_mblen(srcchar); + matchlen = pg_mblen_range(srcchar, srcend); if (buf.data != NULL) appendBinaryStringInfo(&buf, srcchar, matchlen); } diff --git a/contrib/uuid-ossp/sql/ivy_uuid_ossp.sql b/contrib/uuid-ossp/sql/ivy_uuid_ossp.sql index bf32c2b63e6..ddc385640a3 100644 --- a/contrib/uuid-ossp/sql/ivy_uuid_ossp.sql +++ b/contrib/uuid-ossp/sql/ivy_uuid_ossp.sql @@ -81,4 +81,4 @@ SELECT uuid_version_bits(uuid_generate_v4()), SELECT uuid_generate_v4() <> uuid_generate_v4(); -SELECT sys_guid() <> sys_guid(); \ No newline at end of file +SELECT sys_guid() <> sys_guid(); diff --git a/contrib/uuid-ossp/uuid-ossp--1.0--1.1.sql b/contrib/uuid-ossp/uuid-ossp--1.0--1.1.sql index 000557e0acc..fa005bd99f5 100644 --- a/contrib/uuid-ossp/uuid-ossp--1.0--1.1.sql +++ b/contrib/uuid-ossp/uuid-ossp--1.0--1.1.sql @@ -17,4 +17,4 @@ ALTER FUNCTION uuid_generate_v5(uuid, text) PARALLEL SAFE; CREATE OR REPLACE FUNCTION pg_catalog.sys_guid() RETURNS bytea AS 'MODULE_PATHNAME', 'ora_sys_guid' -VOLATILE STRICT LANGUAGE C; \ No newline at end of file +VOLATILE STRICT LANGUAGE C; diff --git a/contrib/uuid-ossp/uuid-ossp--1.1.sql b/contrib/uuid-ossp/uuid-ossp--1.1.sql index f033283cbcb..d5d06efb166 100644 --- a/contrib/uuid-ossp/uuid-ossp--1.1.sql +++ b/contrib/uuid-ossp/uuid-ossp--1.1.sql @@ -56,4 +56,4 @@ IMMUTABLE STRICT LANGUAGE C PARALLEL SAFE; CREATE OR REPLACE FUNCTION pg_catalog.sys_guid() RETURNS bytea AS 'MODULE_PATHNAME', 'ora_sys_guid' -VOLATILE STRICT LANGUAGE C; \ No newline at end of file +VOLATILE STRICT LANGUAGE C; diff --git a/contrib/uuid-ossp/uuid-ossp.c b/contrib/uuid-ossp/uuid-ossp.c index 596614d814e..8f71f235deb 100644 --- a/contrib/uuid-ossp/uuid-ossp.c +++ b/contrib/uuid-ossp/uuid-ossp.c @@ -562,12 +562,20 @@ Datum ora_sys_guid(PG_FUNCTION_ARGS) { bytea *result; +#ifdef HAVE_UUID_OSSP + uuid_t *uuid; + uuid_rc_t rc; +#elif defined(HAVE_UUID_E2FS) + uuid_t uu; +#else /* BSD */ + int i; + unsigned char byte_array[SYS_GUID_LENGTH]; +#endif + result = (bytea *)palloc(VARHDRSZ + SYS_GUID_LENGTH); SET_VARSIZE(result, VARHDRSZ + SYS_GUID_LENGTH); #ifdef HAVE_UUID_OSSP - uuid_t *uuid; - uuid_rc_t rc; uuid = get_cached_uuid_t(0); rc = uuid_make(uuid, UUID_MAKE_V4, NULL, NULL); if (rc != UUID_RC_OK) { @@ -576,17 +584,14 @@ ora_sys_guid(PG_FUNCTION_ARGS) memcpy(VARDATA(result), (unsigned char *)uuid, SYS_GUID_LENGTH); #elif defined(HAVE_UUID_E2FS) - uuid_t uu; uuid_generate_random(uu); memcpy(VARDATA(result), uu, SYS_GUID_LENGTH); #else /* BSD */ - int i; - unsigned char byte_array[SYS_GUID_LENGTH]; for (i = 0; i < SYS_GUID_LENGTH; i++) { byte_array[i] = (unsigned char)arc4random(); } memcpy(VARDATA(result), byte_array, SYS_GUID_LENGTH); #endif PG_RETURN_BYTEA_P(result); -} \ No newline at end of file +} diff --git a/contrib/xml2/expected/ivy_xml2.out b/contrib/xml2/expected/ivy_xml2.out index d8a1d2804fc..e343537fe8b 100644 --- a/contrib/xml2/expected/ivy_xml2.out +++ b/contrib/xml2/expected/ivy_xml2.out @@ -262,4 +262,14 @@ $$ $$); ERROR: failed to apply stylesheet +-- empty output +select xslt_process('', +$$ +$$); + xslt_process +-------------- + +(1 row) + reset ivorysql.enable_emptystring_to_null; diff --git a/contrib/xml2/expected/ivy_xml2_1.out b/contrib/xml2/expected/ivy_xml2_1.out index 40ce13c6e7a..2e21decc2ba 100644 --- a/contrib/xml2/expected/ivy_xml2_1.out +++ b/contrib/xml2/expected/ivy_xml2_1.out @@ -167,4 +167,10 @@ $$ $$); ERROR: xslt_process() is not available without libxslt +-- empty output +select xslt_process('', +$$ +$$); +ERROR: xslt_process() is not available without libxslt reset ivorysql.enable_emptystring_to_null; diff --git a/contrib/xml2/expected/xml2.out b/contrib/xml2/expected/xml2.out index 3d97b14c3a1..1906fcf33e2 100644 --- a/contrib/xml2/expected/xml2.out +++ b/contrib/xml2/expected/xml2.out @@ -261,3 +261,13 @@ $$ $$); ERROR: failed to apply stylesheet +-- empty output +select xslt_process('', +$$ +$$); + xslt_process +-------------- + +(1 row) + diff --git a/contrib/xml2/expected/xml2_1.out b/contrib/xml2/expected/xml2_1.out index 31e6d33c2df..6e53263a1a4 100644 --- a/contrib/xml2/expected/xml2_1.out +++ b/contrib/xml2/expected/xml2_1.out @@ -206,4 +206,10 @@ $$ $$); ERROR: xslt_process() is not available without libxslt +-- empty output +select xslt_process('', +$$ +$$); +ERROR: xslt_process() is not available without libxslt reset ivorysql.enable_emptystring_to_null; diff --git a/contrib/xml2/sql/ivy_xml2.sql b/contrib/xml2/sql/ivy_xml2.sql index fa1885f7a67..75af3ece5a6 100644 --- a/contrib/xml2/sql/ivy_xml2.sql +++ b/contrib/xml2/sql/ivy_xml2.sql @@ -153,4 +153,10 @@ $$ $$); + +-- empty output +select xslt_process('', +$$ +$$); reset ivorysql.enable_emptystring_to_null; diff --git a/contrib/xml2/sql/xml2.sql b/contrib/xml2/sql/xml2.sql index ef99d164f27..510d18a3679 100644 --- a/contrib/xml2/sql/xml2.sql +++ b/contrib/xml2/sql/xml2.sql @@ -153,3 +153,9 @@ $$ $$); + +-- empty output +select xslt_process('', +$$ +$$); diff --git a/contrib/xml2/xpath.c b/contrib/xml2/xpath.c index 23d3f332dba..2820874cb5e 100644 --- a/contrib/xml2/xpath.c +++ b/contrib/xml2/xpath.c @@ -176,7 +176,7 @@ pgxmlNodeSetToText(xmlNodeSetPtr nodeset, xmlBufferWriteCHAR(buf, toptagname); xmlBufferWriteChar(buf, ">"); } - result = xmlStrdup(buf->content); + result = xmlStrdup(xmlBufferContent(buf)); xmlBufferFree(buf); return result; } diff --git a/contrib/xml2/xslt_proc.c b/contrib/xml2/xslt_proc.c index b720d89f754..8ff7c7f1bbf 100644 --- a/contrib/xml2/xslt_proc.c +++ b/contrib/xml2/xslt_proc.c @@ -176,7 +176,14 @@ xslt_process(PG_FUNCTION_ARGS) if (resstat < 0) PG_RETURN_NULL(); - result = cstring_to_text_with_len((char *) resstr, reslen); + /* + * If an empty string has been returned, resstr would be NULL. In + * this case, assume that the result is an empty string. + */ + if (reslen == 0) + result = cstring_to_text(""); + else + result = cstring_to_text_with_len((char *) resstr, reslen); if (resstr) xmlFree(resstr); diff --git a/doc/src/sgml/amcheck.sgml b/doc/src/sgml/amcheck.sgml index 211a0ae1945..0402172a5ff 100644 --- a/doc/src/sgml/amcheck.sgml +++ b/doc/src/sgml/amcheck.sgml @@ -382,7 +382,7 @@ SET client_min_messages = DEBUG1; verification functions is true, an additional phase of verification is performed against the table associated with the target index relation. This consists of a dummy - CREATE INDEX operation, which checks for the + CREATE INDEX CONCURRENTLY operation, which checks for the presence of all hypothetical new index tuples against a temporary, in-memory summarizing structure (this is built when needed during the basic first phase of verification). The summarizing structure diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 832b616a7bb..105f1a975dc 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -889,16 +889,16 @@ host all all 192.168.0.0/16 ident map=omicro # list of names of administrators. Passwords are required in all cases. # # TYPE DATABASE USER ADDRESS METHOD -local sameuser all md5 -local all /^.*helpdesk$ md5 -local all @admins md5 -local all +support md5 +local sameuser all scram-sha-256 +local all /^.*helpdesk$ scram-sha-256 +local all @admins scram-sha-256 +local all +support scram-sha-256 # The last two lines above can be combined into a single line: -local all @admins,+support md5 +local all @admins,+support scram-sha-256 # The database column can also use lists and file names: -local db1,db2,@demodbs all md5 +local db1,db2,@demodbs all scram-sha-256 @@ -1003,8 +1003,9 @@ local db1,db2,@demodbs all md5 the remainder of the field is treated as a regular expression. (See for details of PostgreSQL's regular expression syntax.) The regular - expression can include a single capture, or parenthesized subexpression, - which can then be referenced in the database-username + expression can include a single capture, or parenthesized subexpression. + The portion of the system user name that matched the capture can then + be referenced in the database-username field as \1 (backslash-one). This allows the mapping of multiple user names in a single line, which is particularly useful for simple syntax substitutions. For example, these entries @@ -1022,12 +1023,11 @@ mymap /^(.*)@otherdomain\.com$ guest If the database-username field starts with a slash (/), the remainder of the field is treated - as a regular expression (see - for details of PostgreSQL's regular - expression syntax). It is not possible to use \1 - to use a capture from regular expression on - system-username for a regular expression - on database-username. + as a regular expression. + When the database-username field is a regular + expression, it is not possible to use \1 within it to + refer to a capture from the system-username + field. diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 4f1ad5f15ab..2f3d95b832b 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1680,7 +1680,7 @@ include_dir 'conf.d' This parameter determines whether the passphrase command set by ssl_passphrase_command will also be called during a configuration reload if a key file needs a passphrase. If this - parameter is off (the default), then + parameter is off (the default), then ssl_passphrase_command will be ignored during a reload and the SSL configuration will not be reloaded if a passphrase is needed. That setting is appropriate for a command that requires a @@ -1688,6 +1688,12 @@ include_dir 'conf.d' running. Setting this parameter to on might be appropriate if the passphrase is obtained from a file, for example. + + This parameter must be set to on when running on + Windows since all connections + will perform a configuration reload due to the different process model + of that platform. + This parameter can only be set in the postgresql.conf file or on the server command line. @@ -1760,7 +1766,8 @@ include_dir 'conf.d' Controls whether huge pages are requested for the main shared memory area. Valid values are try (the default), - on, and off. With + on, and off. + This parameter can only be set at server start. With huge_pages set to try, the server will try to request huge pages, but fall back to the default if that fails. With on, failure to request huge pages @@ -2273,6 +2280,7 @@ include_dir 'conf.d' platform, is generally discouraged because it typically requires non-default kernel settings to allow for large allocations (see ). + This parameter can only be set at server start. @@ -2300,6 +2308,7 @@ include_dir 'conf.d' however, it may be useful for debugging, when the pg_dynshmem directory is stored on a RAM disk, or when other shared memory facilities are not available. + This parameter can only be set at server start. @@ -2401,6 +2410,43 @@ include_dir 'conf.d' + + file_extend_method (enum) + + file_extend_method configuration parameter + + + + + Specifies the method used to extend data files during bulk operations + such as COPY. The first available option is used as + the default, depending on the operating system: + + + + posix_fallocate (Unix) uses the standard POSIX + interface for allocating disk space, but is missing on some systems. + If it is present but the underlying file system doesn't support it, + this option silently falls back to write_zeros. + Current versions of BTRFS are known to disable compression when + this option is used. + This is the default on systems that have the function. + + + + + write_zeros extends files by writing out blocks + of zero bytes. This is the default on systems that don't have the + function posix_fallocate. + + + + The write_zeros method is always used when data + files are extended by 8 blocks or fewer. + + + + max_notify_queue_pages (integer) @@ -2413,6 +2459,7 @@ include_dir 'conf.d' / queue. The default value is 1048576. For 8 KB pages it allows to consume up to 8 GB of disk space. + This parameter can only be set at server start. @@ -2647,7 +2694,7 @@ include_dir 'conf.d' Higher values will have the most impact on higher latency storage where queries otherwise experience noticeable I/O stalls and on devices with high IOPs. Unnecessarily high values may increase I/O - latency for all queries on the system + latency for all queries on the system. @@ -2694,9 +2741,9 @@ include_dir 'conf.d' Controls the largest I/O size in operations that combine I/O, and silently limits the user-settable parameter io_combine_limit. - This parameter can only be set in - the postgresql.conf file or on the server - command line. + This parameter can only be set at server start. + If this value is specified without units, it is taken as blocks, + that is BLCKSZ bytes, typically 8kB. The maximum possible size depends on the operating system and block size, but is typically 1MB on Unix and 128kB on Windows. The default is 128kB. @@ -2716,6 +2763,8 @@ include_dir 'conf.d' higher than the io_max_combine_limit parameter, the lower value will silently be used instead, so both may need to be raised to increase the I/O size. + If this value is specified without units, it is taken as blocks, + that is BLCKSZ bytes, typically 8kB. The maximum possible size depends on the operating system and block size, but is typically 1MB on Unix and 128kB on Windows. The default is 128kB. @@ -3399,8 +3448,9 @@ include_dir 'conf.d' This parameter enables compression of WAL using the specified compression method. When enabled, the PostgreSQL - server compresses full page images written to WAL when - is on or during a base backup. + server compresses full page images written to WAL (e.g. when + is on, during a base backup, + etc.). A compressed page image will be decompressed during WAL replay. The supported methods are pglz, lz4 (if PostgreSQL @@ -3962,6 +4012,7 @@ include_dir 'conf.d' blocks to prefetch. If this value is specified without units, it is taken as bytes. The default is 512kB. + This parameter can only be set at server start. @@ -4618,10 +4669,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows - Invalidate replication slots that have remained idle longer than this - duration. If this value is specified without units, it is taken as - minutes. A value of zero (the default) disables the idle timeout - invalidation mechanism. This parameter can only be set in the + Invalidate replication slots that have remained inactive (not used by + a replication connection) + for longer than this duration. + If this value is specified without units, it is taken as seconds. + A value of zero (the default) disables the idle timeout + invalidation mechanism. This parameter can only be set in the postgresql.conf file or on the server command line. @@ -4685,48 +4738,9 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows - Record commit time of transactions. This parameter - can only be set in postgresql.conf file or on the server - command line. The default value is off. - - - - - - synchronized_standby_slots (string) - - synchronized_standby_slots configuration parameter - - - - - A comma-separated list of streaming replication standby server slot names - that logical WAL sender processes will wait for. Logical WAL sender processes - will send decoded changes to plugins only after the specified replication - slots confirm receiving WAL. This guarantees that logical replication - failover slots do not consume changes until those changes are received - and flushed to corresponding physical standbys. If a - logical replication connection is meant to switch to a physical standby - after the standby is promoted, the physical replication slot for the - standby should be listed here. Note that logical replication will not - proceed if the slots specified in the - synchronized_standby_slots do not exist or are invalidated. - Additionally, the replication management functions - - pg_replication_slot_advance, - - pg_logical_slot_get_changes, and - - pg_logical_slot_peek_changes, - when used with logical failover slots, will block until all - physical slots specified in synchronized_standby_slots have - confirmed WAL receipt. - - - The standbys corresponding to the physical replication slots in - synchronized_standby_slots must configure - sync_replication_slots = true so they can receive - logical failover slot changes from the primary. + Record commit time of transactions. + This parameter can only be set at server start. + The default value is off. @@ -4879,6 +4893,45 @@ ANY num_sync ( + synchronized_standby_slots (string) + + synchronized_standby_slots configuration parameter + + + + + A comma-separated list of streaming replication standby server slot names + that logical WAL sender processes will wait for. Logical WAL sender processes + will send decoded changes to plugins only after the specified replication + slots confirm receiving WAL. This guarantees that logical replication + failover slots do not consume changes until those changes are received + and flushed to corresponding physical standbys. If a + logical replication connection is meant to switch to a physical standby + after the standby is promoted, the physical replication slot for the + standby should be listed here. Note that logical replication will not + proceed if the slots specified in the + synchronized_standby_slots do not exist or are invalidated. + Additionally, the replication management functions + + pg_replication_slot_advance, + + pg_logical_slot_get_changes, and + + pg_logical_slot_peek_changes, + when used with logical failover slots, will block until all + physical slots specified in synchronized_standby_slots have + confirmed WAL receipt. + + + The standbys corresponding to the physical replication slots in + synchronized_standby_slots must configure + sync_replication_slots = true so they can receive + logical failover slot changes from the primary. + + @@ -5904,24 +5957,24 @@ ANY num_sync ( + + default + + + + Specifies the string that represents a default value, + the same as COPY's DEFAULT option. + + + + encoding diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 224d4fe5a9f..1128763d04d 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -11247,10 +11247,10 @@ now() statement (more specifically, the time of receipt of the latest command message from the client). statement_timestamp() and transaction_timestamp() - return the same value during the first command of a transaction, but might - differ during subsequent commands. + return the same value during the first statement of a transaction, but might + differ during subsequent statements. clock_timestamp() returns the actual current time, and - therefore its value changes even within a single SQL command. + therefore its value changes even within a single SQL statement. timeofday() is a historical PostgreSQL function. Like clock_timestamp(), it returns the actual current time, @@ -14419,8 +14419,7 @@ CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple - - + Function @@ -14428,24 +14427,22 @@ CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple Example(s) - - + - - - gen_random_uuid + + gen_random_uuid ( ) uuid - uuidv4 + uuidv4 ( ) uuid - Generate a version 4 (random) UUID. + Generates a version 4 (random) UUID gen_random_uuid() @@ -14454,26 +14451,25 @@ CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple uuidv4() b42410ee-132f-42ee-9e4f-09a6485c95b8 - - + - - - uuidv7 + + uuidv7 ( shift interval ) uuid - Generate a version 7 (time-ordered) UUID. The timestamp is computed using UNIX timestamp - with millisecond precision + sub-millisecond timestamp + random. The optional parameter - shift will shift the computed timestamp by the given interval. + Generates a version 7 (time-ordered) UUID. The timestamp is + computed using UNIX timestamp with millisecond precision + + sub-millisecond timestamp + random. The optional + parameter shift will shift the computed + timestamp by the given interval. uuidv7() 019535d9-3df7-79fb-b466-fa907fa17f9e - - + @@ -14496,8 +14492,7 @@ CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple - - + Function @@ -14505,44 +14500,41 @@ CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple Example(s) - - + - - - uuid_extract_timestamp + + uuid_extract_timestamp ( uuid ) timestamp with time zone - Extracts a timestamp with time zone from UUID - version 1 and 7. For other versions, this function returns null. Note that - the extracted timestamp is not necessarily exactly equal to the time the - UUID was generated; this depends on the implementation that generated the - UUID. + Extracts a timestamp with time zone from a UUID of + version 1 or 7. For other versions, this function returns null. + Note that the extracted timestamp is not necessarily exactly equal + to the time the UUID was generated; this depends on the + implementation that generated the UUID. uuid_extract_timestamp('019535d9-3df7-79fb-b466-&zwsp;fa907fa17f9e'::uuid) 2025-02-23 21:46:24.503-05 - - + - - - uuid_extract_version + + uuid_extract_version ( uuid ) smallint - Extracts the version from a UUID of the variant described by - RFC 9562. For - other variants, this function returns null. For example, for a UUID - generated by gen_random_uuid, this function will + Extracts the version from a UUID of one of the variants described by + RFC + 9562. For other variants, this function returns null. + For example, for a UUID generated + by gen_random_uuid(), this function will return 4. @@ -14552,8 +14544,7 @@ CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple uuid_extract_version('019535d9-3df7-79fb-b466-&zwsp;fa907fa17f9e'::uuid) 7 - - + @@ -17449,7 +17440,7 @@ ERROR: value too long for type character(2) [{"f1":1},2,null,3] - jsonb_strip_nulls('[1,2,null,3,4]', true); + jsonb_strip_nulls('[1,2,null,3,4]', true) [1,2,3,4] @@ -26407,6 +26398,21 @@ SELECT pg_type_is_visible('myschema.widget'::regtype); + + + + pg_get_partition_constraintdef + + pg_get_partition_constraintdef ( table oid ) + text + + + Reconstructs the definition of a partition constraint. + (This is a decompiled reconstruction, not the original text + of the command.) + + + @@ -27687,6 +27693,31 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres} details. + + + + + pg_get_multixact_members + + pg_get_multixact_members ( multixid xid ) + setof record + ( xid xid, + mode text ) + + + Returns the transaction ID and lock mode for each member of the + specified multixact ID. The lock modes forupd, + fornokeyupd, sh, and + keysh correspond to the row-level locks + FOR UPDATE, FOR NO KEY UPDATE, + FOR SHARE, and FOR KEY SHARE, + respectively, as described in . Two + additional modes are specific to multixacts: + nokeyupd, used by updates that do not modify key + columns, and upd, used by updates or deletes that + modify key columns. + + @@ -27695,7 +27726,8 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres} The internal transaction ID type xid is 32 bits wide and wraps around every 4 billion transactions. However, the functions shown in , except - age and mxid_age, use a + age, mxid_age, and + pg_get_multixact_members, use a 64-bit type xid8 that does not wrap around during the life of an installation and can be converted to xid by casting if required; see for details. @@ -29981,7 +30013,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset logical decoding and must be dropped after promotion. See for details. Note that this function is primarily intended for testing and - debugging purposes and should be used with caution. Additionaly, + debugging purposes and should be used with caution. Additionally, this function cannot be executed if sync_replication_slots is enabled and the slotsync @@ -30336,7 +30368,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset pg_relation_filepath. For a relation in the database's default tablespace, the tablespace can be specified as zero. Returns NULL if no relation in the current database - is associated with the given values. + is associated with the given values, or if dealing with a temporary + relation. diff --git a/doc/src/sgml/gist.sgml b/doc/src/sgml/gist.sgml index ee86e170055..423f691dd42 100644 --- a/doc/src/sgml/gist.sgml +++ b/doc/src/sgml/gist.sgml @@ -291,7 +291,7 @@ CREATE INDEX ON my_table USING GIST (my_inet_column inet_ops); speed up building a GiST index. The optional twelfth method stratnum is used to translate compare types (from - src/include/nodes/primnodes.h) into strategy numbers + src/include/access/cmptype.h) into strategy numbers used by the operator class. This lets the core code look up operators for temporal constraint indexes. @@ -1174,7 +1174,7 @@ my_sortsupport(PG_FUNCTION_ARGS) Given a CompareType value from - src/include/nodes/primnodes.h, returns a strategy + src/include/access/cmptype.h, returns a strategy number used by this operator class for matching functionality. The function should return InvalidStrategy if the operator class has no matching strategy. diff --git a/doc/src/sgml/hash.sgml b/doc/src/sgml/hash.sgml index 9e69ef91fe8..34f3b2cb0c1 100644 --- a/doc/src/sgml/hash.sgml +++ b/doc/src/sgml/hash.sgml @@ -125,11 +125,10 @@ Both scanning the index and inserting tuples require locating the bucket where a given tuple ought to be located. To do this, we need the bucket count, highmask, and lowmask from the metapage; however, it's undesirable - for performance reasons to have to have to lock and pin the metapage for - every such operation. Instead, we retain a cached copy of the metapage - in each backend's relcache entry. This will produce the correct bucket - mapping as long as the target bucket hasn't been split since the last - cache refresh. + for performance reasons to have to lock and pin the metapage for every such + operation. Instead, we retain a cached copy of the metapage in each + backend's relcache entry. This will produce the correct bucket mapping as + long as the target bucket hasn't been split since the last cache refresh. diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml index b47d8b4106e..aa256e2ec51 100644 --- a/doc/src/sgml/high-availability.sgml +++ b/doc/src/sgml/high-availability.sgml @@ -857,7 +857,7 @@ archive_cleanup_command = 'pg_archivecleanup /path/to/archive %r' # as a replication standby if the user's password is correctly supplied. # # TYPE DATABASE USER ADDRESS METHOD -host replication foo 192.168.1.100/32 md5 +host replication foo 192.168.1.100/32 scram-sha-256 diff --git a/doc/src/sgml/history.sgml b/doc/src/sgml/history.sgml index 8bfa1db670d..628f13ce123 100644 --- a/doc/src/sgml/history.sgml +++ b/doc/src/sgml/history.sgml @@ -171,7 +171,7 @@ A short tutorial introducing regular SQL features as well as those of Postgres95 was distributed with the - source code + source code. diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index 1aa4741a8ea..63d7e376f19 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -147,7 +147,7 @@ typedef struct IndexAmRoutine ambuild_function ambuild; ambuildempty_function ambuildempty; aminsert_function aminsert; - aminsertcleanup_function aminsertcleanup; + aminsertcleanup_function aminsertcleanup; /* can be NULL */ ambulkdelete_function ambulkdelete; amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ diff --git a/doc/src/sgml/legal.sgml b/doc/src/sgml/legal.sgml index 75c1309cf73..742c0146f7f 100644 --- a/doc/src/sgml/legal.sgml +++ b/doc/src/sgml/legal.sgml @@ -1,9 +1,9 @@ -2025 +2026 - 1996–2025 + 1996–2026 The PostgreSQL Global Development Group @@ -16,7 +16,7 @@ - Portions Copyright © 1996-2025, PostgreSQL Global Development Group + Portions Copyright © 1996-2026, PostgreSQL Global Development Group Portions Copyright © 1994, The Regents of the University of California diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 4b299f5df0e..62a278e61fb 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1,6 +1,6 @@ - + <application>libpq</application> — C Library @@ -2220,7 +2220,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname server does not support the protocol version requested by the client, the connection is automatically downgraded to a lower minor protocol version that the server supports. After the connection attempt has - completed you can use to + completed you can use to find out which exact protocol version was negotiated. @@ -2740,26 +2740,6 @@ char *PQport(const PGconn *conn); - - PQservicePQservice - - - - Returns the service of the active connection. - - -char *PQservice(const PGconn *conn); - - - - - returns NULL if the - conn argument is NULL. - Otherwise, if there was no service provided, it returns an empty string. - - - - PQttyPQtty @@ -7036,15 +7016,20 @@ int PQrequestCancel(PGconn *conn); to send simple function calls to the server. - + - This interface is somewhat obsolete, as one can achieve similar + This interface is unsafe and should not be used. When + result_is_int is set to 0, + PQfn may write data beyond the end of + result_buf, regardless of whether the buffer has + enough space for the requested number of bytes. Furthermore, it is + obsolete, as one can achieve similar performance and greater functionality by setting up a prepared statement to define the function call. Then, executing the statement with binary transmission of parameters and results substitutes for a fast-path function call. - + The function PQfnPQfn @@ -10432,10 +10417,14 @@ typedef struct PGoauthBearerRequest /* Hook outputs */ - /* Callback implementing a custom asynchronous OAuth flow. */ + /* + * Callback implementing a custom asynchronous OAuth flow. The signature is + * platform-dependent: PQ_SOCKTYPE is SOCKET on Windows, and int everywhere + * else. + */ PostgresPollingStatusType (*async) (PGconn *conn, struct PGoauthBearerRequest *request, - SOCKTYPE *altsock); + PQ_SOCKTYPE *altsock); /* Callback to clean up custom allocations. */ void (*cleanup) (PGconn *conn, struct PGoauthBearerRequest *request); @@ -10492,7 +10481,7 @@ typedef struct PGoauthBearerRequest hook. When the callback cannot make further progress without blocking, it should return either PGRES_POLLING_READING or PGRES_POLLING_WRITING after setting - *pgsocket to the file descriptor that will be marked + *altsock to the file descriptor that will be marked ready to read/write when progress can be made again. (This descriptor is then provided to the top-level polling loop via PQsocket().) Return PGRES_POLLING_OK diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml index c32e6bc000d..03e3ed9c170 100644 --- a/doc/src/sgml/logical-replication.sgml +++ b/doc/src/sgml/logical-replication.sgml @@ -68,7 +68,7 @@ Replicating between PostgreSQL instances on different platforms (for - example Linux to Windows) + example Linux to Windows). @@ -89,7 +89,7 @@ The subscriber database behaves in the same way as any other PostgreSQL instance and can be used as a publisher for other databases by defining its - own publications. When the subscriber is treated as read-only by + own publications. When the subscriber is treated as read-only by an application, there will be no conflicts from a single subscription. On the other hand, if there are other writes done either by an application or by other subscribers to the same set of tables, conflicts can arise. @@ -202,8 +202,8 @@ A subscription is the downstream side of logical replication. The node where a subscription is defined is referred to as the subscriber. A subscription defines the connection - to another database and set of publications (one or more) to which it wants - to subscribe. + to another database and the set of publications (one or more) to which it + wants to subscribe. @@ -709,8 +709,8 @@ HINT: To initiate replication, you must manually create the replication slot, e - To confirm that the standby server is indeed ready for failover, follow these - steps to verify that all necessary logical replication slots have been + To confirm that the standby server is indeed ready for failover for a given subscriber, follow these + steps to verify that all the logical replication slots required by that subscriber have been synchronized to the standby server: @@ -764,7 +764,7 @@ HINT: To initiate replication, you must manually create the replication slot, e Check that the logical replication slots identified above exist on the standby server and are ready for failover. -/* standby # */ SELECT slot_name, (synced AND NOT temporary AND NOT conflicting) AS failover_ready +/* standby # */ SELECT slot_name, (synced AND NOT temporary AND invalidation_reason IS NULL) AS failover_ready FROM pg_replication_slots WHERE slot_name IN ('sub1','sub2','sub3', 'pg_16394_sync_16385_7394666715149055164'); @@ -782,10 +782,42 @@ HINT: To initiate replication, you must manually create the replication slot, e If all the slots are present on the standby server and the result (failover_ready) of the above SQL query is true, then - existing subscriptions can continue subscribing to publications now on the - new primary server. + existing subscriptions can continue subscribing to publications on the new + primary server. + + + + The first two steps in the above procedure are meant for a + PostgreSQL subscriber. It is recommended to run + these steps on each subscriber node, that will be served by the designated + standby after failover, to obtain the complete list of replication + slots. This list can then be verified in Step 3 to ensure failover readiness. + Non-PostgreSQL subscribers, on the other hand, may + use their own methods to identify the replication slots used by their + respective subscriptions. + + + + In some cases, such as during a planned failover, it is necessary to confirm + that all subscribers, whether PostgreSQL or + non-PostgreSQL, will be able to continue + replication after failover to a given standby server. In such cases, use the + following SQL, instead of performing the first two steps above, to identify + which replication slots on the primary need to be synced to the standby that + is intended for promotion. This query returns the relevant replication slots + associated with all the failover-enabled subscriptions. + + +/* primary # */ SELECT array_agg(quote_literal(r.slot_name)) AS slots + FROM pg_replication_slots r + WHERE r.failover AND NOT r.temporary; + slots +------- + {'sub1','sub2','sub3', 'pg_16394_sync_16385_7394666715149055164'} +(1 row) + @@ -945,7 +977,7 @@ HINT: To initiate replication, you must manually create the replication slot, e - If the subscriber is in a release prior to 15, copy pre-existing data + If the subscriber is in a release prior to 15, copying pre-existing data doesn't use row filters even if they are defined in the publication. This is because old releases can only copy the entire table data. @@ -1016,28 +1048,28 @@ HINT: To initiate replication, you must manually create the replication slot, e defined) for each publication. 5) AND (c = 'NSW'::text)) + "public.t1" WHERE ((a > 5) AND (c = 'NSW'::text)) - Publication p2 - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root -----------+------------+---------+---------+---------+-----------+---------- - postgres | f | t | t | t | t | f + Publication p2 + Owner | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root +----------+------------+---------+---------+---------+-----------+-------------------+---------- + postgres | f | t | t | t | t | none | f Tables: - "public.t1" - "public.t2" WHERE (e = 99) + "public.t1" + "public.t2" WHERE (e = 99) - Publication p3 - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root -----------+------------+---------+---------+---------+-----------+---------- - postgres | f | t | t | t | t | f + Publication p3 + Owner | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root +----------+------------+---------+---------+---------+-----------+-------------------+---------- + postgres | f | t | t | t | t | none | f Tables: - "public.t2" WHERE (d = 10) - "public.t3" WHERE (g = 10) + "public.t2" WHERE (d = 10) + "public.t3" WHERE (g = 10) ]]> @@ -1459,10 +1491,10 @@ Publications: for each publication. /* pub # */ \dRp+ - Publication p1 - Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root -----------+------------+---------+---------+---------+-----------+---------- - postgres | f | t | t | t | t | f + Publication p1 + Owner | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root +----------+------------+---------+---------+---------+-----------+-------------------+---------- + postgres | f | t | t | t | t | none | f Tables: "public.t1" (id, a, b, d) @@ -1776,7 +1808,7 @@ Publications: update_missing - The tuple to be updated was not found. The update will simply be + The row to be updated was not found. The update will simply be skipped in this scenario. @@ -1797,7 +1829,7 @@ Publications: delete_missing - The tuple to be deleted was not found. The delete will simply be + The row to be deleted was not found. The delete will simply be skipped in this scenario. @@ -1831,8 +1863,8 @@ DETAIL: detailed_explanation. where detail_values is one of: Key (column_name , ...)=(column_value , ...) - existing local tuple (column_name , ...)=(column_value , ...) - remote tuple (column_name , ...)=(column_value , ...) + existing local row (column_name , ...)=(column_value , ...) + remote row (column_name , ...)=(column_value , ...) replica identity {(column_name , ...)=(column_value , ...) | full (column_name , ...)=(column_value , ...)} @@ -1866,32 +1898,32 @@ DETAIL: detailed_explanation. detailed_explanation includes the origin, transaction ID, and commit timestamp of the transaction that - modified the existing local tuple, if available. + modified the existing local row, if available. The Key section includes the key values of the local - tuple that violated a unique constraint for + row that violated a unique constraint for insert_exists, update_exists or multiple_unique_conflicts conflicts. - The existing local tuple section includes the local - tuple if its origin differs from the remote tuple for + The existing local row section includes the local + row if its origin differs from the remote row for update_origin_differs or delete_origin_differs - conflicts, or if the key value conflicts with the remote tuple for + conflicts, or if the key value conflicts with the remote row for insert_exists, update_exists or multiple_unique_conflicts conflicts. - The remote tuple section includes the new tuple from + The remote row section includes the new row from the remote insert or update operation that caused the conflict. Note that - for an update operation, the column value of the new tuple will be null + for an update operation, the column value of the new row will be null if the value is unchanged and toasted. @@ -1899,7 +1931,7 @@ DETAIL: detailed_explanation. The replica identity section includes the replica identity key values that were used to search for the existing local - tuple to be updated or deleted. This may include the full tuple value + row to be updated or deleted. This may include the full row value if the local relation is marked with REPLICA IDENTITY FULL. @@ -1907,7 +1939,7 @@ DETAIL: detailed_explanation. column_name is the column name. - For existing local tuple, remote tuple, + For existing local row, remote row, and replica identity full cases, column names are logged only if the user lacks the privilege to access all columns of the table. If column names are present, they appear in the same order @@ -1964,7 +1996,7 @@ DETAIL: detailed_explanation. ERROR: conflict detected on relation "public.test": conflict=insert_exists DETAIL: Key already exists in unique index "t_pkey", which was modified locally in transaction 740 at 2024-06-26 10:47:04.727375+08. -Key (c)=(1); existing local tuple (1, 'local'); remote tuple (1, 'remote'). +Key (c)=(1); existing local row (1, 'local'); remote row (1, 'remote'). CONTEXT: processing remote data for replication origin "pg_16395" during "INSERT" for replication target relation "public.test" in transaction 725 finished at 0/14C0378 The LSN of the transaction that contains the change violating the constraint and @@ -2156,7 +2188,7 @@ CONTEXT: processing remote data for replication origin "pg_16395" during "INSER Initial Snapshot - The initial data in existing subscribed tables are snapshotted and + The initial data in existing subscribed tables is snapshotted and copied in parallel instances of a special kind of apply process. These special apply processes are dedicated table synchronization workers, spawned for each table to be synchronized. Each table @@ -3183,7 +3215,7 @@ wal_level = logical (the values here depend on your actual network configuration and user you want to use for connecting): -host all repuser 0.0.0.0/0 md5 +host all repuser 0.0.0.0/0 scram-sha-256 diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml index fc288d691b9..e84bfbce71a 100644 --- a/doc/src/sgml/logicaldecoding.sgml +++ b/doc/src/sgml/logicaldecoding.sgml @@ -290,7 +290,7 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU A logical slot will emit each change just once in normal operation. The current position of each slot is persisted only at checkpoint, so in - the case of a crash the slot may return to an earlier LSN, which will + the case of a crash the slot might return to an earlier LSN, which will then cause recent changes to be sent again when the server restarts. Logical decoding clients are responsible for avoiding ill effects from handling the same message more than once. Clients may wish to record @@ -409,7 +409,7 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU should be used with caution. Unlike automatic synchronization, it does not include cyclic retries, making it more prone to synchronization failures, particularly during initial sync scenarios where the required WAL files - or catalog rows for the slot may have already been removed or are at risk + or catalog rows for the slot might have already been removed or are at risk of being removed on the standby. In contrast, automatic synchronization via sync_replication_slots provides continuous slot updates, enabling seamless failover and supporting high availability. @@ -420,18 +420,18 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU When slot synchronization is configured as recommended, and the initial synchronization is performed either automatically or - manually via pg_sync_replication_slot, the standby can persist the - synchronized slot only if the following condition is met: The logical - replication slot on the primary must retain WALs and system catalog - rows that are still available on the standby. This ensures data + manually via pg_sync_replication_slots, the standby + can persist the synchronized slot only if the following condition is met: + The logical replication slot on the primary must retain WALs and system + catalog rows that are still available on the standby. This ensures data integrity and allows logical replication to continue smoothly after promotion. If the required WALs or catalog rows have already been purged from the standby, the slot will not be persisted to avoid data loss. In such cases, the following log message may appear: - LOG: could not synchronize replication slot "failover_slot" - DETAIL: Synchronization could lead to data loss as the remote slot needs WAL at LSN 0/3003F28 and catalog xmin 754, but the standby has LSN 0/3003F28 and catalog xmin 756 +LOG: could not synchronize replication slot "failover_slot" +DETAIL: Synchronization could lead to data loss, because the remote slot needs WAL at LSN 0/3003F28 and catalog xmin 754, but the standby has LSN 0/3003F28 and catalog xmin 756. If the logical replication slot is actively used by a consumer, no manual intervention is needed; the slot will advance automatically, diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml index 600e4b3f2f3..1571a895fc8 100644 --- a/doc/src/sgml/maintenance.sgml +++ b/doc/src/sgml/maintenance.sgml @@ -779,7 +779,10 @@ HINT: Execute a database-wide VACUUM in that database. careful aging management, storage cleanup, and wraparound handling. There is a separate storage area which holds the list of members in each multixact, which also uses a 32-bit counter and which must also - be managed. + be managed. The system function + pg_get_multixact_members() described in + can be used to examine the + transaction IDs associated with a multixact ID. @@ -930,12 +933,16 @@ vacuum threshold = Minimum(vacuum max threshold, vacuum base threshold + vacuum The table is also vacuumed if the number of tuples inserted since the last vacuum has exceeded the defined insert threshold, which is defined as: -vacuum insert threshold = vacuum base insert threshold + vacuum insert scale factor * number of tuples +vacuum insert threshold = vacuum base insert threshold + vacuum insert scale factor * number of tuples * percent of table not frozen where the vacuum insert base threshold is , - and vacuum insert scale factor is - . + the vacuum insert scale factor is + , + the number of tuples is + pg_class.reltuples, + and the percent of the table not frozen is + 1 - pg_class.relallfrozen / pg_class.relpages. Such vacuums may allow portions of the table to be marked as all visible and also allow tuples to be frozen, which can reduce the work required in subsequent vacuums. diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 4265a22d4de..5ac0e54688c 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -3980,6 +3980,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage Estimated number of rows inserted since this table was last vacuumed + (not counting VACUUM FULL) @@ -4066,7 +4067,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage total_vacuum_time double precision - Total time this table has been manually vacuumed, in milliseconds. + Total time this table has been manually vacuumed, in milliseconds + (not counting VACUUM FULL). (This includes the time spent sleeping due to cost-based delays.) @@ -5649,7 +5651,7 @@ FROM pg_stat_get_backend_idset() AS backendid; Total time spent sleeping due to cost-based delay (see - , in milliseconds + ), in milliseconds (if is enabled, otherwise zero). diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml index 537d6014942..6817cf5d215 100644 --- a/doc/src/sgml/pgbuffercache.sgml +++ b/doc/src/sgml/pgbuffercache.sgml @@ -19,10 +19,18 @@ pg_buffercache_pages + + pg_buffercache_numa + + pg_buffercache_summary + + pg_buffercache_usage_counts + + pg_buffercache_evict @@ -37,12 +45,12 @@ This module provides the pg_buffercache_pages() - function (wrapped in the pg_buffercache view), + function (wrapped in the pg_buffercache view), the pg_buffercache_numa_pages() function (wrapped in the pg_buffercache_numa view), the pg_buffercache_summary() function, the pg_buffercache_usage_counts() function, the - pg_buffercache_evict(), the + pg_buffercache_evict() function, the pg_buffercache_evict_relation() function and the pg_buffercache_evict_all() function. @@ -55,7 +63,7 @@ - The pg_buffercache_numa_pages() provides + The pg_buffercache_numa_pages() function provides NUMA node mappings for shared buffer entries. This information is not part of pg_buffercache_pages() itself, as it is much slower to retrieve. @@ -489,7 +497,7 @@ - The <structname>pg_buffercache_evict_relation</structname> Function + The <function>pg_buffercache_evict_relation()</function> Function The pg_buffercache_evict_relation() function is very similar to the pg_buffercache_evict() function. The @@ -507,7 +515,7 @@ - The <structname>pg_buffercache_evict_all</structname> Function + The <function>pg_buffercache_evict_all()</function> Function The pg_buffercache_evict_all() function is very similar to the pg_buffercache_evict() function. The diff --git a/doc/src/sgml/pgoverexplain.sgml b/doc/src/sgml/pgoverexplain.sgml index 21930fbd3bd..e399c1cbad5 100644 --- a/doc/src/sgml/pgoverexplain.sgml +++ b/doc/src/sgml/pgoverexplain.sgml @@ -8,7 +8,7 @@ - The pg_overexplain extends EXPLAIN + The pg_overexplain module extends EXPLAIN with new options that provide additional output. It is mostly intended to assist with debugging of and development of the planner, rather than for general use. Since this module displays internal details of planner data @@ -17,6 +17,21 @@ often as) those data structures change. + + To use it, simply load it into the server. You can load it into an + individual session: + + +LOAD 'pg_overexplain'; + + + You can also preload it into some or all sessions by including + pg_overexplain in + or + in + postgresql.conf. + + EXPLAIN (DEBUG) @@ -24,8 +39,8 @@ The DEBUG option displays miscellaneous information from the plan tree that is not normally shown because it is not expected to be of general interest. For each individual plan node, it will display the - following fields. See Plan in - nodes/plannodes.h for additional documentation of these + following fields. See Plan in + nodes/plannodes.h for additional documentation of these fields. @@ -67,8 +82,8 @@ Once per query, the DEBUG option will display the - following fields. See PlannedStmt in - nodes/plannodes.h for additional detail. + following fields. See PlannedStmt in + nodes/plannodes.h for additional detail. @@ -82,7 +97,7 @@ Flags. A comma-separated list of Boolean structure - member names from the PlannedStmt that are set to + member names from the PlannedStmt that are set to true. It covers the following structure members: hasReturning, hasModifyingCTE, canSetTag, transientPlan, @@ -162,7 +177,7 @@ table entry (e.g. relation, subquery, or join), followed by the contents of various range table entry fields that are not normally part of - EXPLAIN output. Some of these fields are only displayed + EXPLAIN output. Some of these fields are only displayed for certain kinds of range table entries. For example, Eref is displayed for all types of range table entries, but CTE Name is displayed only for range table entries @@ -171,7 +186,7 @@ For more information about range table entries, see the definition of - RangeTblEntry in nodes/plannodes.h. + RangeTblEntry in nodes/parsenodes.h. diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index e937491e6b8..5492a66f7ee 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -4962,13 +4962,13 @@ $$ LANGUAGE plpgsql; Variable substitution currently works only in SELECT, INSERT, UPDATE, - DELETE, and commands containing one of - these (such as EXPLAIN and CREATE TABLE - ... AS SELECT), - because the main SQL engine allows query parameters only in these - commands. To use a non-constant name or value in other statement - types (generically called utility statements), you must construct - the utility statement as a string and EXECUTE it. + DELETE, MERGE and commands + containing one of these (such as EXPLAIN and + CREATE TABLE ... AS SELECT), because the main SQL + engine allows query parameters only in these commands. To use a + non-constant name or value in other statement types (generically called + utility statements), you must construct the utility statement as a string + and EXECUTE it. diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml index cb065bf5f88..bee817ea822 100644 --- a/doc/src/sgml/plpython.sgml +++ b/doc/src/sgml/plpython.sgml @@ -1,6 +1,6 @@ - + PL/Python — Python Procedural Language PL/Python diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 82fe3f93761..f0b29ed8cab 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -537,6 +537,11 @@ The frontend should not respond to this message, but should continue listening for a ReadyForQuery message. + + The PostgreSQL server will always send this + message, but some third party backend implementations of the protocol + that don't support query cancellation are known not to. + @@ -886,6 +891,16 @@ SELCT 1/0; Errors detected at semantic analysis or later, such as a misspelled table or column name, do not have this effect. + + + Lastly, note that all the statements within the Query message will + observe the same value of statement_timestamp(), + since that timestamp is updated only upon receipt of the Query + message. This will result in them all observing the same + value of transaction_timestamp() as well, + except in cases where the query string ends a previously-started + transaction and begins a new one. + @@ -3504,11 +3519,13 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" - Boolean option to enable streaming of in-progress transactions. - It accepts an additional value "parallel" to enable sending extra - information with some messages to be used for parallelisation. - Minimum protocol version 2 is required to turn it on. Minimum protocol - version 4 is required for the "parallel" option. + Option to enable streaming of in-progress transactions. Valid values are + off (the default), on and + parallel. The setting parallel + enables sending extra information with some messages to be used for + parallelization. Minimum protocol version 2 is required to turn it + on. Minimum protocol version 4 is required for the + parallel value. @@ -4146,7 +4163,7 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" message, indicated by the length field. - The maximum key length is 256 bytes. The + The minimum and maximum key length are 4 and 256 bytes, respectively. The PostgreSQL server only sends keys up to 32 bytes, but the larger maximum size allows for future server versions, as well as connection poolers and other middleware, to use @@ -4337,7 +4354,7 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" - Int32(16) + Int32 Length of message contents in bytes, including self. @@ -6081,13 +6098,13 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" - Int32(196608) + Int32(196610) The protocol version number. The most significant 16 bits are the major version number (3 for the protocol described here). The least significant 16 bits are the minor version number - (0 for the protocol described here). + (2 for the protocol described here). diff --git a/doc/src/sgml/query.sgml b/doc/src/sgml/query.sgml index 727a0cb185f..b190f28d41e 100644 --- a/doc/src/sgml/query.sgml +++ b/doc/src/sgml/query.sgml @@ -264,8 +264,18 @@ COPY weather FROM '/home/user/weather.txt'; where the file name for the source file must be available on the machine running the backend process, not the client, since the backend process - reads the file directly. You can read more about the - COPY command in . + reads the file directly. The data inserted above into the weather table + could also be inserted from a file containing (values are separated by a + tab character): + + +San Francisco 46 50 0.25 1994-11-27 +San Francisco 43 57 0.0 1994-11-29 +Hayward 37 54 \N 1994-11-29 + + + You can read more about the COPY command in + . diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml index e2da3cc719f..9d923c00fff 100644 --- a/doc/src/sgml/ref/alter_foreign_table.sgml +++ b/doc/src/sgml/ref/alter_foreign_table.sgml @@ -32,7 +32,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] namewhere action is one of: - ADD [ COLUMN ] column_name data_type [ COLLATE collation ] [ column_constraint [ ... ] ] + ADD [ COLUMN ] [ IF NOT EXISTS ] column_name data_type [ COLLATE collation ] [ column_constraint [ ... ] ] DROP [ COLUMN ] [ IF EXISTS ] column_name [ RESTRICT | CASCADE ] ALTER [ COLUMN ] column_name [ SET DATA ] TYPE data_type [ COLLATE collation ] ALTER [ COLUMN ] column_name SET DEFAULT expression @@ -67,11 +67,13 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name - ADD COLUMN + ADD [ COLUMN ] [ IF NOT EXISTS ] This form adds a new column to the foreign table, using the same syntax as CREATE FOREIGN TABLE. + If IF NOT EXISTS is specified and a column already + exists with this name, no error is thrown. Unlike the case when adding a column to a regular table, nothing happens to the underlying storage: this action simply declares that some new column is now accessible through the foreign table. @@ -80,7 +82,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name - DROP COLUMN [ IF EXISTS ] + DROP [ COLUMN ] [ IF EXISTS ] This form drops a column from a foreign table. diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgml index 1d42d05d858..fb7096c16ea 100644 --- a/doc/src/sgml/ref/alter_index.sgml +++ b/doc/src/sgml/ref/alter_index.sgml @@ -97,6 +97,11 @@ ALTER INDEX ALL IN TABLESPACE name index cannot be dropped by itself, and will automatically be dropped if its parent index is dropped. + + If the named index is already attached to the altered index, the + command will attempt to validate the parent index if the parent is + currently invalid. + diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml index d5ea383e8bc..d7f1a5a7229 100644 --- a/doc/src/sgml/ref/alter_publication.sgml +++ b/doc/src/sgml/ref/alter_publication.sgml @@ -23,15 +23,24 @@ PostgreSQL documentation ALTER PUBLICATION name ADD publication_object [, ...] ALTER PUBLICATION name SET publication_object [, ...] -ALTER PUBLICATION name DROP publication_object [, ...] +ALTER PUBLICATION name DROP publication_drop_object [, ...] ALTER PUBLICATION name SET ( publication_parameter [= value] [, ... ] ) ALTER PUBLICATION name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER PUBLICATION name RENAME TO new_name where publication_object is one of: - TABLE [ ONLY ] table_name [ * ] [ ( column_name [, ... ] ) ] [ WHERE ( expression ) ] [, ... ] + TABLE table_and_columns [, ... ] TABLES IN SCHEMA { schema_name | CURRENT_SCHEMA } [, ... ] + +and publication_drop_object is one of: + + TABLE [ ONLY ] table_name [ * ] [, ... ] + TABLES IN SCHEMA { schema_name | CURRENT_SCHEMA } [, ... ] + +and table_and_columns is: + + [ ONLY ] table_name [ * ] [ ( column_name [, ... ] ) ] [ WHERE ( expression ) ] @@ -57,8 +66,7 @@ ALTER PUBLICATION name RENAME TO DROP TABLES IN SCHEMA will not drop any schema tables that were specified using FOR TABLE/ - ADD TABLE, and the combination of DROP - with a WHERE clause is not allowed. + ADD TABLE. diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml index fdc648d007f..1333a0b559f 100644 --- a/doc/src/sgml/ref/alter_subscription.sgml +++ b/doc/src/sgml/ref/alter_subscription.sgml @@ -272,7 +272,7 @@ ALTER SUBSCRIPTION name RENAME TO < process reports an error if any prepared transactions done by the logical replication worker (from when two_phase parameter was still true) are found. You can resolve - prepared transactions on the publisher node, or manually roll back them + prepared transactions on the publisher node, or manually roll them back on the subscriber, and then try again. The transactions prepared by logical replication worker corresponding to a particular subscription have the following pattern: pg_gid_%u_%u diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index d1696991683..409164bf180 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -157,7 +157,7 @@ WITH ( MODULUS numeric_literal, REM - ADD COLUMN [ IF NOT EXISTS ] + ADD [ COLUMN ] [ IF NOT EXISTS ] This form adds a new column to the table, using the same syntax as @@ -169,7 +169,7 @@ WITH ( MODULUS numeric_literal, REM - DROP COLUMN [ IF EXISTS ] + DROP [ COLUMN ] [ IF EXISTS ] This form drops a column from a table. Indexes and @@ -210,6 +210,8 @@ WITH ( MODULUS numeric_literal, REM When this form is used, the column's statistics are removed, so running ANALYZE on the table afterwards is recommended. + For a virtual generated column, ANALYZE + is not necessary because such columns never have statistics. @@ -240,9 +242,10 @@ WITH ( MODULUS numeric_literal, REM provided none of the records in the table contain a NULL value for the column. Ordinarily this is checked during the ALTER TABLE by scanning the - entire table; however, if a valid CHECK constraint is - found which proves no NULL can exist, then the - table scan is skipped. + entire table, unless NOT VALID is specified; + however, if a valid CHECK constraint exists + (and is not dropped in the same command) which proves no + NULL can exist, then the table scan is skipped. If a column has an invalid not-null constraint, SET NOT NULL validates it. @@ -270,6 +273,15 @@ WITH ( MODULUS numeric_literal, REM in a stored generated column is rewritten and all the future changes will apply the new generation expression. + + + When this form is used on a stored generated column, its statistics + are removed, so running + ANALYZE + on the table afterwards is recommended. + For a virtual generated column, ANALYZE + is not necessary because such columns never have statistics. + @@ -364,24 +376,22 @@ WITH ( MODULUS numeric_literal, REM n_distinct_inherited, which override the number-of-distinct-values estimates made by subsequent ANALYZE - operations. n_distinct affects the statistics for the table - itself, while n_distinct_inherited affects the statistics - gathered for the table plus its inheritance children. When set to a - positive value, ANALYZE will assume that the column contains - exactly the specified number of distinct nonnull values. When set to a - negative value, which must be greater - than or equal to -1, ANALYZE will assume that the number of - distinct nonnull values in the column is linear in the size of the - table; the exact count is to be computed by multiplying the estimated - table size by the absolute value of the given number. For example, - a value of -1 implies that all values in the column are distinct, while - a value of -0.5 implies that each value appears twice on the average. - This can be useful when the size of the table changes over time, since - the multiplication by the number of rows in the table is not performed - until query planning time. Specify a value of 0 to revert to estimating - the number of distinct values normally. For more information on the use - of statistics by the PostgreSQL query - planner, refer to . + operations. n_distinct affects the statistics for the + table itself, while n_distinct_inherited affects the + statistics gathered for the table plus its inheritance children, and for + the statistics gathered for partitioned tables. When the value + specified is a positive value, the query planner will assume that the + column contains exactly the specified number of distinct nonnull values. + Fractional values may also be specified by using values below 0 and + above or equal to -1. This instructs the query planner to estimate the + number of distinct values by multiplying the absolute value of the + specified number by the estimated number of rows in the table. For + example, a value of -1 implies that all values in the column are + distinct, while a value of -0.5 implies that each value appears twice on + average. This can be useful when the size of the table changes over + time. For more information on the use of statistics by the + PostgreSQL query planner, refer to + . Changing per-attribute options acquires a diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index 5b43c56b133..a824b99f826 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -80,14 +80,16 @@ COMMENT ON Description - COMMENT stores a comment about a database object. + COMMENT stores, replaces, or removes the comment on a + database object. - Only one comment string is stored for each object, so to modify a comment, - issue a new COMMENT command for the same object. To remove a - comment, write NULL in place of the text string. - Comments are automatically dropped when their object is dropped. + Only one comment string is stored for each object. Issuing a new + COMMENT command for the same object replaces the + existing comment. Specifying NULL or an empty + string ('') removes the comment. Comments are + automatically dropped when their object is dropped. @@ -266,7 +268,8 @@ COMMENT ON string_literal - The new comment contents, written as a string literal. + The new comment contents, written as a string literal. An empty string + ('') removes the comment. @@ -275,7 +278,7 @@ COMMENT ON NULL - Write NULL to drop the comment. + Write NULL to remove the comment. @@ -362,6 +365,7 @@ COMMENT ON TRANSFORM FOR hstore LANGUAGE plpython3u IS 'Transform between hstore COMMENT ON TRIGGER my_trigger ON my_table IS 'Used for RI'; COMMENT ON TYPE complex IS 'Complex number data type'; COMMENT ON VIEW my_view IS 'View of departmental costs'; +COMMENT ON VIEW my_view IS NULL; diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml index 009fa46532b..adc775b90c5 100644 --- a/doc/src/sgml/ref/create_foreign_table.sgml +++ b/doc/src/sgml/ref/create_foreign_table.sgml @@ -232,9 +232,9 @@ WITH ( MODULUS numeric_literal, REM INCLUDING COMMENTS - Comments for the copied columns and constraints will be - copied. The default behavior is to exclude comments, resulting in - the copied columns and constraints in the new table having no + Comments for the copied columns, constraints, and extended statistics + will be copied. The default behavior is to exclude comments, + resulting in the corresponding objects in the new table having no comments. diff --git a/doc/src/sgml/ref/create_policy.sgml b/doc/src/sgml/ref/create_policy.sgml index e76c342d3da..9065ccb65f9 100644 --- a/doc/src/sgml/ref/create_policy.sgml +++ b/doc/src/sgml/ref/create_policy.sgml @@ -49,6 +49,8 @@ CREATE POLICY name ON WITH CHECK. When a USING expression returns true for a given row then that row is visible to the user, while if false or null is returned then the row is not visible. + Typically, no error occurs when a row is not visible, but see + for exceptions. When a WITH CHECK expression returns true for a row then that row is inserted or updated, while if false or null is returned then an error occurs. @@ -194,8 +196,9 @@ CREATE POLICY name ON SELECT), and will not be available for modification (in an UPDATE - or DELETE). Such rows are silently suppressed; no error - is reported. + or DELETE). Typically, such rows are silently + suppressed; no error is reported (but see + for exceptions). @@ -251,8 +254,10 @@ CREATE POLICY name ON INSERT or UPDATE command attempts to add rows to the table that do not pass the ALL - policy's WITH CHECK expression, the entire - command will be aborted. + policy's WITH CHECK expression (or its + USING expression, if it does not have a + WITH CHECK expression), the entire command will + be aborted. @@ -268,11 +273,52 @@ CREATE POLICY name ON SELECT policy will be returned during a SELECT query, and that queries that require SELECT permissions, such as - UPDATE, will also only see those records + UPDATE, DELETE, and + MERGE, will also only see those records that are allowed by the SELECT policy. A SELECT policy cannot have a WITH CHECK expression, as it only applies in cases where - records are being retrieved from the relation. + records are being retrieved from the relation, except as described + below. + + + If a data-modifying query has a RETURNING clause, + SELECT permissions are required on the relation, + and any newly inserted or updated rows from the relation must satisfy + the relation's SELECT policies in order to be + available to the RETURNING clause. If a newly + inserted or updated row does not satisfy the relation's + SELECT policies, an error will be thrown (inserted + or updated rows to be returned are never + silently ignored). + + + If an INSERT has an ON CONFLICT DO + UPDATE clause, or an ON CONFLICT DO + NOTHING clause with an arbiter index or constraint + specification, then SELECT + permissions are required on the relation, and the rows proposed for + insertion are checked using the relation's SELECT + policies. If a row proposed for insertion does not satisfy the + relation's SELECT policies, an error is thrown + (the INSERT is never silently + avoided). In addition, if the UPDATE path is + taken, the row to be updated and the new updated row are checked + against the relation's SELECT policies, and an + error is thrown if they are not satisfied (an auxiliary + UPDATE is never silently + avoided). + + + A MERGE command requires SELECT + permissions on both the source and target relations, and so each + relation's SELECT policies are applied before they + are joined, and the MERGE actions will only see + those records that are allowed by those policies. In addition, if + an UPDATE action is executed, the target relation's + SELECT policies are applied to the updated row, as + for a standalone UPDATE, except that an error is + thrown if they are not satisfied. @@ -292,10 +338,11 @@ CREATE POLICY name ON - Note that INSERT with ON CONFLICT DO - UPDATE checks INSERT policies' - WITH CHECK expressions only for rows appended - to the relation by the INSERT path. + Note that an INSERT with an ON CONFLICT + DO NOTHING/UPDATE clause will check the + INSERT policies' WITH CHECK + expressions for all rows proposed for insertion, regardless of + whether or not they end up being inserted. @@ -305,12 +352,12 @@ CREATE POLICY name ON Using UPDATE for a policy means that it will apply - to UPDATE, SELECT FOR UPDATE + to UPDATE, SELECT FOR UPDATE, and SELECT FOR SHARE commands, as well as auxiliary ON CONFLICT DO UPDATE clauses of - INSERT commands. - MERGE commands containing UPDATE - actions are affected as well. Since UPDATE + INSERT commands, and MERGE + commands containing UPDATE actions. + Since an UPDATE command involves pulling an existing record and replacing it with a new modified record, UPDATE policies accept both a USING expression and @@ -356,7 +403,8 @@ CREATE POLICY name ON USING expressions, an error will be thrown (the UPDATE path will never be silently - avoided). + avoided). The same applies to an UPDATE action + of a MERGE command. @@ -366,12 +414,18 @@ CREATE POLICY name ON Using DELETE for a policy means that it will apply - to DELETE commands. Only rows that pass this - policy will be seen by a DELETE command. There can - be rows that are visible through a SELECT that are - not available for deletion, if they do not pass the - USING expression for - the DELETE policy. + to DELETE commands and MERGE + commands containing DELETE actions. For a + DELETE command, only rows that pass this policy + will be seen by the DELETE command. There can + be rows that are visible through a SELECT policy + that are not available for deletion, if they do not pass the + USING expression for the DELETE + policy. Note, however, that a DELETE action in a + MERGE command will see rows that are visible + through SELECT policies, and if the + DELETE policy does not pass for such a row, an + error will be thrown. @@ -400,6 +454,15 @@ CREATE POLICY name ON + + summarizes how the different + types of policy apply to specific commands. In the table, + check means that the policy expression is checked and an + error is thrown if it returns false or null, whereas filter + means that the row is silently ignored if the policy expression returns + false or null. + + Policies Applied by Command Type @@ -424,8 +487,8 @@ CREATE POLICY name ON - SELECT - Existing row + SELECT / COPY ... TO + Filter existing row @@ -433,63 +496,121 @@ CREATE POLICY name ON SELECT FOR UPDATE/SHARE - Existing row + Filter existing row - Existing row + Filter existing row - INSERT / MERGE ... THEN INSERT + INSERT + + Check new row  + + If read access is required to either the existing or new row (for + example, a WHERE or RETURNING + clause that refers to columns from the relation). + + + + Check new row - New row + + + UPDATE + + Filter existing row  & + check new row  + + + Filter existing row + Check new row - INSERT ... RETURNING + DELETE - New row + Filter existing row  + + + + + Filter existing row + + + INSERT ... ON CONFLICT + + Check new row  + + If an arbiter index or constraint is specified. + + - If read access is required to the existing or new row (for example, - a WHERE or RETURNING clause - that refers to columns from the relation). + Row proposed for insertion is checked regardless of whether or not a + conflict occurs. - New row + + Check new row  + - UPDATE / MERGE ... THEN UPDATE + ON CONFLICT DO UPDATE - Existing & new rows + Check existing & new rows  + + New row of the auxiliary UPDATE command, which + might be different from the new row of the original + INSERT command. + + - Existing row - New row + Check existing row + + Check new row  + - DELETE + MERGE + Filter source & target rows + + + + + + + MERGE ... THEN INSERT - Existing row + Check new row  + Check new row - Existing row - ON CONFLICT DO UPDATE - Existing & new rows + MERGE ... THEN UPDATE + Check new row + + Check existing row + Check new row + + + + MERGE ... THEN DELETE + + - Existing row - New row + Check existing row diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml index 802630f2df1..b9e12a33c62 100644 --- a/doc/src/sgml/ref/create_publication.sgml +++ b/doc/src/sgml/ref/create_publication.sgml @@ -28,8 +28,12 @@ CREATE PUBLICATION namewhere publication_object is one of: - TABLE [ ONLY ] table_name [ * ] [ ( column_name [, ... ] ) ] [ WHERE ( expression ) ] [, ... ] + TABLE table_and_columns [, ... ] TABLES IN SCHEMA { schema_name | CURRENT_SCHEMA } [, ... ] + +and table_and_columns is: + + [ ONLY ] table_name [ * ] [ ( column_name [, ... ] ) ] [ WHERE ( expression ) ] @@ -151,7 +155,7 @@ CREATE PUBLICATION name - When a partitioned table is published via schema level publication, all + When a partitioned table is published via a schema-level publication, all of its existing and future partitions are implicitly considered to be part of the publication, regardless of whether they are from the publication schema or not. So, even operations that are performed directly on a @@ -174,7 +178,7 @@ CREATE PUBLICATION name This parameter determines which DML operations will be published by - the new publication to the subscribers. The value is + the new publication to the subscribers. The value is a comma-separated list of operations. The allowed operations are insert, update, delete, and truncate. @@ -214,7 +218,7 @@ CREATE PUBLICATION name If the subscriber is from a release prior to 18, then initial table - synchronization won't copy generated columns even if parameter + synchronization won't copy generated columns even if the parameter publish_generated_columns is stored in the publisher. @@ -231,13 +235,15 @@ CREATE PUBLICATION name publish_via_partition_root (boolean) - This parameter determines whether changes in a partitioned table (or - on its partitions) contained in the publication will be published - using the identity and schema of the partitioned table rather than - that of the individual partitions that are actually changed; the - latter is the default. Enabling this allows the changes to be - replicated into a non-partitioned table or a partitioned table - consisting of a different set of partitions. + This parameter controls how changes to a partitioned table (or any of + its partitions) are published. When set to true, + changes are published using the identity and schema of the + root partitioned table. When set to false (the + default), changes are published using the identity and schema of the + individual partitions where the changes actually occurred. Enabling + this option allows the changes to be replicated into a + non-partitioned table or into a partitioned table whose partition + structure differs from that of the publisher. diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index dc000e913c1..a033efce4de 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -672,9 +672,10 @@ WITH ( MODULUS numeric_literal, REM INCLUDING COMMENTS - Comments for the copied columns, constraints, and indexes will be + Comments for the copied columns, check constraints, + not-null constraints, indexes, and extended statistics will be copied. The default behavior is to exclude comments, resulting in - the copied columns and constraints in the new table having no + the corresponding objects in the new table having no comments. @@ -1379,8 +1380,8 @@ WITH ( MODULUS numeric_literal, REM REFERENCES (foreign key) constraints accept this clause. NOT NULL and CHECK constraints are not deferrable. Note that deferrable constraints cannot be used as - conflict arbitrators in an INSERT statement that - includes an ON CONFLICT DO UPDATE clause. + conflict arbiters in an INSERT statement that + includes an ON CONFLICT clause. diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml index 982ab6f3ee4..71869c996e3 100644 --- a/doc/src/sgml/ref/create_trigger.sgml +++ b/doc/src/sgml/ref/create_trigger.sgml @@ -197,9 +197,11 @@ CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER name of the rows inserted, deleted, or modified by the current SQL statement. This feature lets the trigger see a global view of what the statement did, not just one row at a time. This option is only allowed for - an AFTER trigger that is not a constraint trigger; also, if - the trigger is an UPDATE trigger, it must not specify - a column_name list. + an AFTER trigger on a plain table (not a foreign table). + The trigger should not be a constraint trigger. Also, if the trigger is + an UPDATE trigger, it must not specify + a column_name list when using + this option. OLD TABLE may only be specified once, and only for a trigger that can fire on UPDATE or DELETE; it creates a transition relation containing the before-images of all rows diff --git a/doc/src/sgml/ref/createuser.sgml b/doc/src/sgml/ref/createuser.sgml index 5c34c623423..0c061428514 100644 --- a/doc/src/sgml/ref/createuser.sgml +++ b/doc/src/sgml/ref/createuser.sgml @@ -539,7 +539,7 @@ PostgreSQL documentation $ createuser -P -s -e joe Enter password for new role: xyzzy Enter it again: xyzzy -CREATE ROLE joe PASSWORD 'md5b5f5ba1a423792b526f799ae4eb3d59e' SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN; +CREATE ROLE joe PASSWORD 'SCRAM-SHA-256$4096:44560wPMLfjqiAzyPDZ/eQ==$4CA054rZlSFEq8Z3FEhToBTa2X6KnWFxFkPwIbKoDe0=:L/nbSZRCjp6RhOhKK56GoR1zibCCSePKshCJ9lnl3yw=' SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS; In the above example, the new password isn't actually echoed when typed, but we show what was typed for clarity. As you see, the password is diff --git a/doc/src/sgml/ref/delete.sgml b/doc/src/sgml/ref/delete.sgml index 29649f6afd6..f88a03f821e 100644 --- a/doc/src/sgml/ref/delete.sgml +++ b/doc/src/sgml/ref/delete.sgml @@ -323,6 +323,9 @@ DELETE FROM user_logs AS dl USING delete_batch AS del WHERE dl.ctid = del.ctid; + This use of ctid is only safe because + the query is repeatedly run, avoiding the problem of changed + ctids. diff --git a/doc/src/sgml/ref/drop_owned.sgml b/doc/src/sgml/ref/drop_owned.sgml index 46e1c229ec0..efda01a39e8 100644 --- a/doc/src/sgml/ref/drop_owned.sgml +++ b/doc/src/sgml/ref/drop_owned.sgml @@ -33,7 +33,7 @@ DROP OWNED BY { name | CURRENT_ROLE database that are owned by one of the specified roles. Any privileges granted to the given roles on objects in the current database or on shared objects (databases, tablespaces, configuration - parameters, or other roles) will also be revoked. + parameters) will also be revoked. diff --git a/doc/src/sgml/ref/drop_subscription.sgml b/doc/src/sgml/ref/drop_subscription.sgml index d4f54c7170e..6e84bb0a256 100644 --- a/doc/src/sgml/ref/drop_subscription.sgml +++ b/doc/src/sgml/ref/drop_subscription.sgml @@ -49,6 +49,16 @@ DROP SUBSCRIPTION [ IF EXISTS ] nameParameters + + IF EXISTS + + + Do not throw an error if the subscription does not exist. A notice is + issued in this case. + + + + name diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml index 3f139917790..706952e50fc 100644 --- a/doc/src/sgml/ref/insert.sgml +++ b/doc/src/sgml/ref/insert.sgml @@ -114,10 +114,13 @@ INSERT INTO table_name [ AS INSERT privilege on the listed columns. Similarly, when ON CONFLICT DO UPDATE is specified, you only need UPDATE privilege on the column(s) that are - listed to be updated. However, ON CONFLICT DO UPDATE - also requires SELECT privilege on any column whose - values are read in the ON CONFLICT DO UPDATE - expressions or condition. + listed to be updated. However, all forms of ON CONFLICT + also require SELECT privilege on any column whose values + are read. This includes any column mentioned in + conflict_target (including columns referred to + by the arbiter constraint), and any column mentioned in an + ON CONFLICT DO UPDATE expression, + or a WHERE clause condition. @@ -594,6 +597,15 @@ INSERT INTO table_name [ AS + + + While CREATE INDEX CONCURRENTLY or REINDEX + CONCURRENTLY is running on a unique index, INSERT + ... ON CONFLICT statements on the same table may unexpectedly + fail with a unique violation. + + + diff --git a/doc/src/sgml/ref/pg_combinebackup.sgml b/doc/src/sgml/ref/pg_combinebackup.sgml index 330a598f701..9a6d201e0b8 100644 --- a/doc/src/sgml/ref/pg_combinebackup.sgml +++ b/doc/src/sgml/ref/pg_combinebackup.sgml @@ -314,7 +314,7 @@ PostgreSQL documentation To avoid this problem, taking a new full backup after changing the checksum - state of the cluster using is + state of the cluster using is recommended. Otherwise, you can disable and then optionally reenable checksums on the directory produced by pg_combinebackup in order to correct the problem. diff --git a/doc/src/sgml/ref/pg_ctl-ref.sgml b/doc/src/sgml/ref/pg_ctl-ref.sgml index a0287bb81d6..b762b002c02 100644 --- a/doc/src/sgml/ref/pg_ctl-ref.sgml +++ b/doc/src/sgml/ref/pg_ctl-ref.sgml @@ -299,8 +299,9 @@ PostgreSQL documentation Append the server log output to filename. If the file does not - exist, it is created. The umask is set to 077, - so access to the log file is disallowed to other users by default. + exist, it is created. By default, only the cluster owner can + access the log file. If group access is enabled in the cluster, + users in the same group as the cluster owner can also read it. diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index 2ae084b5fa6..f1b076ec46e 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -96,6 +96,18 @@ PostgreSQL documentation light of the limitations listed below. + + + Restoring a dump causes the destination to execute arbitrary code of the + source superusers' choice. Partial dumps and partial restores do not limit + that. If the source superusers are not trusted, the dumped SQL statements + must be inspected before restoring. Non-plain-text dumps can be inspected + by using pg_restore's + option. Note that the client running the dump and restore need not trust + the source or destination superusers. + + + @@ -1252,6 +1264,29 @@ PostgreSQL documentation + + + + + Use the provided string as the psql + \restrict key in the dump output. This can only be + specified for plain-text dumps, i.e., when is + set to plain or the option + is omitted. If no restrict key is specified, + pg_dump will generate a random one as + needed. Keys may contain only alphanumeric characters. + + + This option is primarily intended for testing purposes and other + scenarios that require repeatable output (e.g., comparing dump files). + It is not recommended for general use, as a malicious server with + advance knowledge of the key may be able to inject arbitrary code that + will be executed on the machine that runs + psql with the dump output. + + + + @@ -1354,12 +1389,21 @@ PostgreSQL documentation + + + + + Dump optimizer statistics. + + + + Dump only the statistics, not the schema (data definitions) or data. - Statistics for tables, materialized views, foreign tables, + Optimizer statistics for tables, materialized views, foreign tables, and indexes are dumped. @@ -1440,33 +1484,6 @@ PostgreSQL documentation - - - - - Dump data. This is the default. - - - - - - - - - Dump schema (data definitions). This is the default. - - - - - - - - - Dump statistics. - - - - @@ -1682,11 +1699,12 @@ CREATE DATABASE foo WITH TEMPLATE template0; - If is specified, + When is specified, pg_dump will include most optimizer statistics in the - resulting dump file. However, some statistics may not be included, such as - those created explicitly with or - custom statistics added by an extension. Therefore, it may be useful to + resulting dump file. This does not include all statistics, such as + those created explicitly with , + custom statistics added by an extension, or statistics collected by the + cumulative statistics system. Therefore, it may still be useful to run ANALYZE after restoring from a dump file to ensure optimal performance; see and for more information. diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml index 8ca68da5a55..8834b7ec141 100644 --- a/doc/src/sgml/ref/pg_dumpall.sgml +++ b/doc/src/sgml/ref/pg_dumpall.sgml @@ -16,10 +16,7 @@ PostgreSQL documentation pg_dumpall - - - export a PostgreSQL database cluster as an SQL script or to other formats - + extract a PostgreSQL database cluster into a script file @@ -36,7 +33,7 @@ PostgreSQL documentation pg_dumpall is a utility for writing out (dumping) all PostgreSQL databases - of a cluster into an SQL script file or an archive. The output contains + of a cluster into one script file. The script file contains SQL commands that can be used as input to to restore the databases. It does this by calling for each database in the cluster. @@ -55,16 +52,11 @@ PostgreSQL documentation - Plain text SQL scripts will be written to the standard output. Use the + The SQL script will be written to the standard output. Use the / option or shell operators to redirect it into a file. - - Archives in other formats will be placed in a directory named using the - /, which is required in this case. - - pg_dumpall needs to connect several times to the PostgreSQL server (once per @@ -74,6 +66,16 @@ PostgreSQL documentation linkend="libpq-pgpass"/> for more information. + + + Restoring a dump causes the destination to execute arbitrary code of the + source superusers' choice. Partial dumps and partial restores do not limit + that. If the source superusers are not trusted, the dumped SQL statements + must be inspected before restoring. Note that the client running the dump + and restore need not trust the source or destination superusers. + + + @@ -129,85 +131,10 @@ PostgreSQL documentation Send output to the specified file. If this is omitted, the standard output is used. - Note: This option can only be omitted when is plain - - - - - - Specify the format of dump files. In plain format, all the dump data is - sent in a single text stream. This is the default. - - In all other modes, pg_dumpall first creates two files: - global.dat and map.dat, in the directory - specified by . - The first file contains global data, such as roles and tablespaces. The second - contains a mapping between database oids and names. These files are used by - pg_restore. Data for individual databases is placed in - databases subdirectory, named using the database's oid. - - - - d - directory - - - Output directory-format archives for each database, - suitable for input into pg_restore. The directory - will have database oid as its name. - - - - - - p - plain - - - Output a plain-text SQL script file (the default). - - - - - - c - custom - - - Output a custom-format archive for each database, - suitable for input into pg_restore. The archive - will be named dboid.dmp where dboid is the - oid of the database. - - - - - - t - tar - - - Output a tar-format archive for each database, - suitable for input into pg_restore. The archive - will be named dboid.tar where dboid is the - oid of the database. - - - - - - - Note: see for details - of how the various non plain text archives work. - - - - - @@ -674,6 +601,26 @@ exclude database PATTERN + + + + + Use the provided string as the psql + \restrict key in the dump output. If no restrict + key is specified, pg_dumpall will generate a + random one as needed. Keys may contain only alphanumeric characters. + + + This option is primarily intended for testing purposes and other + scenarios that require repeatable output (e.g., comparing dump files). + It is not recommended for general use, as a malicious server with + advance knowledge of the key may be able to inject arbitrary code that + will be executed on the machine that runs + psql with the dump output. + + + + @@ -688,12 +635,21 @@ exclude database PATTERN + + + + + Dump optimizer statistics. + + + + Dump only the statistics, not the schema (data definitions) or data. - Statistics for tables, materialized views, foreign tables, + Optimizer statistics for tables, materialized views, foreign tables, and indexes are dumped. @@ -723,33 +679,6 @@ exclude database PATTERN - - - - - Dump data. This is the default. - - - - - - - - - Dump schema (data definitions). This is the default. - - - - - - - - - Dump statistics. - - - - @@ -961,11 +890,12 @@ exclude database PATTERN - If is specified, + When is specified, pg_dumpall will include most optimizer statistics in the - resulting dump file. However, some statistics may not be included, such as - those created explicitly with or - custom statistics added by an extension. Therefore, it may be useful to + resulting dump file. This does not include all statistics, such as + those created explicitly with , + custom statistics added by an extension, or statistics collected by the + cumulative statistics system. Therefore, it may still be useful to run ANALYZE on each database after restoring from a dump file to ensure optimal performance. You can also run vacuumdb -a -z to analyze all databases. diff --git a/doc/src/sgml/ref/pg_recvlogical.sgml b/doc/src/sgml/ref/pg_recvlogical.sgml index f68182266a9..263ebdeeab4 100644 --- a/doc/src/sgml/ref/pg_recvlogical.sgml +++ b/doc/src/sgml/ref/pg_recvlogical.sgml @@ -53,6 +53,16 @@ PostgreSQL documentation (ControlC) or SIGTERM signal. + + + When pg_recvlogical receives + a SIGHUP signal, it closes the current output file + and opens a new one using the filename specified by + the option. This allows us to rotate + the output file by first renaming the current file and then sending + a SIGHUP signal to + pg_recvlogical. + diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index b649bd3a5ae..a468a38361a 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -18,9 +18,8 @@ PostgreSQL documentation pg_restore - restore PostgreSQL databases from archives - created by pg_dump or - pg_dumpall + restore a PostgreSQL database from an + archive file created by pg_dump @@ -39,14 +38,13 @@ PostgreSQL documentation pg_restore is a utility for restoring a - PostgreSQL database or cluster from an archive - created by or - in one of the non-plain-text + PostgreSQL database from an archive + created by in one of the non-plain-text formats. It will issue the commands necessary to reconstruct the - database or cluster to the state it was in at the time it was saved. The - archives also allow pg_restore to + database to the state it was in at the time it was saved. The + archive files also allow pg_restore to be selective about what is restored, or even to reorder the items - prior to being restored. The archive formats are designed to be + prior to being restored. The archive files are designed to be portable across architectures. @@ -54,17 +52,10 @@ PostgreSQL documentation pg_restore can operate in two modes. If a database name is specified, pg_restore connects to that database and restores archive contents directly into - the database. - When restoring from a dump made by pg_dumpall, - each database will be created and then the restoration will be run in that - database. - - Otherwise, when a database name is not specified, a script containing the SQL - commands necessary to rebuild the database or cluster is created and written + the database. Otherwise, a script containing the SQL + commands necessary to rebuild the database is created and written to a file or standard output. This script output is equivalent to - the plain text output format of pg_dump or - pg_dumpall. - + the plain text output format of pg_dump. Some of the options controlling the output are therefore analogous to pg_dump options. @@ -77,6 +68,18 @@ PostgreSQL documentation pg_restore will not be able to load the data using COPY statements. + + + + Restoring a dump causes the destination to execute arbitrary code of the + source superusers' choice. Partial dumps and partial restores do not limit + that. If the source superusers are not trusted, the dumped SQL statements + must be inspected before restoring. Non-plain-text dumps can be inspected + by using pg_restore's + option. Note that the client running the dump and restore need not trust + the source or destination superusers. + + @@ -149,8 +152,6 @@ PostgreSQL documentation commands that mention this database. Access privileges for the database itself are also restored, unless is specified. - is required when restoring multiple databases - from an archive created by pg_dumpall. @@ -246,19 +247,6 @@ PostgreSQL documentation - - - - - - Restore only global objects (roles and tablespaces), no databases. - - - This option is only relevant when restoring from an archive made using pg_dumpall. - - - - @@ -603,28 +591,6 @@ PostgreSQL documentation - - - - - Do not restore databases whose name matches - pattern. - Multiple patterns can be excluded by writing multiple - switches. The - pattern parameter is - interpreted as a pattern according to the same rules used by - psql's \d - commands (see ), - so multiple databases can also be excluded by writing wildcard - characters in the pattern. When using wildcards, be careful to - quote the pattern if needed to prevent shell wildcard expansion. - - - This option is only relevant when restoring from an archive made using pg_dumpall. - - - - @@ -842,6 +808,28 @@ PostgreSQL documentation + + + + + Use the provided string as the psql + \restrict key in the dump output. This can only be + specified for SQL script output, i.e., when the + option is used. If no restrict key is specified, + pg_restore will generate a random one as + needed. Keys may contain only alphanumeric characters. + + + This option is primarily intended for testing purposes and other + scenarios that require repeatable output (e.g., comparing dump files). + It is not recommended for general use, as a malicious server with + advance knowledge of the key may be able to inject arbitrary code that + will be executed on the machine that runs + psql with the dump output. + + + + @@ -861,6 +849,16 @@ PostgreSQL documentation + + + + + Output commands to restore statistics, if the archive contains them. + This is the default. + + + + @@ -919,36 +917,6 @@ PostgreSQL documentation - - - - - Output commands to restore data, if the archive contains them. - This is the default. - - - - - - - - - Output commands to restore schema (data definitions), if the archive - contains them. This is the default. - - - - - - - - - Output commands to restore statistics, if the archive contains them. - This is the default. - - - - diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml index 5485033ed8c..c696cec1a1c 100644 --- a/doc/src/sgml/ref/pg_rewind.sgml +++ b/doc/src/sgml/ref/pg_rewind.sgml @@ -97,9 +97,9 @@ PostgreSQL documentation pg_rewind requires that the target server either has the option enabled in postgresql.conf or data checksums enabled when - the cluster was initialized with initdb. Neither of these - are currently on by default. - must also be set to on, but is enabled by default. + the cluster was initialized with initdb (the + default). must also be set to + on, but is enabled by default. diff --git a/doc/src/sgml/ref/pg_walsummary.sgml b/doc/src/sgml/ref/pg_walsummary.sgml index 57b2d24650c..399b6879754 100644 --- a/doc/src/sgml/ref/pg_walsummary.sgml +++ b/doc/src/sgml/ref/pg_walsummary.sgml @@ -31,7 +31,7 @@ PostgreSQL documentation Description pg_walsummary is used to print the contents of - WAL summary files. These binary files are found with the + WAL summary files. These binary files are found in the pg_wal/summaries subdirectory of the data directory, and can be converted to text using this tool. This is not ordinarily necessary, since WAL summary files primarily exist to support diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml index aeeed297437..1445579429f 100644 --- a/doc/src/sgml/ref/pgupgrade.sgml +++ b/doc/src/sgml/ref/pgupgrade.sgml @@ -70,6 +70,14 @@ PostgreSQL documentation pg_upgrade supports upgrades from 9.2.X and later to the current major release of PostgreSQL, including snapshot and beta releases. + + + + Upgrading a cluster causes the destination to execute arbitrary code of the + source superusers' choice. Ensure that the source superusers are trusted + before upgrading. + + @@ -825,10 +833,10 @@ psql --username=postgres --file=script.sql postgres Unless the option is specified, pg_upgrade will transfer most optimizer statistics - from the old cluster to the new cluster. However, some statistics may - not be transferred, such as those created explicitly with or custom statistics added by an - extension. + from the old cluster to the new cluster. This does not transfer + all statistics, such as those created explicitly with + , custom statistics added by + an extension, or statistics collected by the cumulative statistics system. diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 95f4cac2467..bc273f8dce5 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3551,6 +3551,24 @@ SELECT $1 \parse stmt1 + + \restrict restrict_key + + + Enter "restricted" mode with the provided key. In this mode, the only + allowed meta-command is \unrestrict, to exit + restricted mode. The key may contain only alphanumeric characters. + + + This command is primarily intended for use in plain-text dumps + generated by pg_dump, + pg_dumpall, and + pg_restore, but it may be useful elsewhere. + + + + + \s [ filename ] @@ -3802,6 +3820,24 @@ SELECT 1 \bind \sendpipeline + + \unrestrict restrict_key + + + Exit "restricted" mode (i.e., where all other meta-commands are + blocked), provided the specified key matches the one given to + \restrict when restricted mode was entered. + + + This command is primarily intended for use in plain-text dumps + generated by pg_dump, + pg_dumpall, and + pg_restore, but it may be useful elsewhere. + + + + + \unset name diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml index 40cca063946..b523766abe3 100644 --- a/doc/src/sgml/ref/update.sgml +++ b/doc/src/sgml/ref/update.sgml @@ -503,6 +503,9 @@ UPDATE work_item SET status = 'failed' WHERE work_item.ctid = emr.ctid; This command will need to be repeated until no rows remain to be updated. + (This use of ctid is only safe because + the query is repeatedly run, avoiding the problem of changed + ctids.) Use of an ORDER BY clause allows the command to prioritize which rows will be updated; it can also prevent deadlock with other update operations if they use the same ordering. diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml index b0680a61814..bfa3cf56096 100644 --- a/doc/src/sgml/ref/vacuumdb.sgml +++ b/doc/src/sgml/ref/vacuumdb.sgml @@ -282,14 +282,24 @@ PostgreSQL documentation Only analyze relations that are missing statistics for a column, index - expression, or extended statistics object. This option prevents - vacuumdb from deleting existing statistics - so that the query optimizer's choices do not become transiently worse. + expression, or extended statistics object. When used with + , this option prevents + vacuumdb from temporarily replacing existing + statistics with ones generated with lower statistics targets, thus + avoiding transiently worse query optimizer choices. This option can only be used in conjunction with or . + + Note that requires + SELECT privileges on + pg_statistic + and + pg_statistic_ext_data, + which are restricted to superusers by default. + diff --git a/doc/src/sgml/regress.sgml b/doc/src/sgml/regress.sgml index bf4ffb30576..8838fe7f022 100644 --- a/doc/src/sgml/regress.sgml +++ b/doc/src/sgml/regress.sgml @@ -285,75 +285,88 @@ make check-world PG_TEST_EXTRA='kerberos ldap ssl load_balance libpq_encryption' - sepgsql + libpq_encryption - Runs the test suite under contrib/sepgsql. This - requires an SELinux environment that is set up in a specific way; see - . + Runs the test src/interfaces/libpq/t/005_negotiate_encryption.pl. + This opens TCP/IP listen sockets. If PG_TEST_EXTRA + also includes kerberos, additional tests that require + an MIT Kerberos installation are enabled. - ssl + load_balance - Runs the test suite under src/test/ssl. This opens TCP/IP listen sockets. + Runs the test src/interfaces/libpq/t/004_load_balance_dns.pl. + This requires editing the system hosts file and + opens TCP/IP listen sockets. - load_balance + oauth - Runs the test src/interfaces/libpq/t/004_load_balance_dns.pl. - This requires editing the system hosts file and - opens TCP/IP listen sockets. + Runs the test suite under src/test/modules/oauth_validator. + This opens TCP/IP listen sockets for a test server running HTTPS. - libpq_encryption + regress_dump_restore - Runs the test src/interfaces/libpq/t/005_negotiate_encryption.pl. - This opens TCP/IP listen sockets. If PG_TEST_EXTRA - also includes kerberos, additional tests that require - an MIT Kerberos installation are enabled. + Runs an additional test suite in + src/bin/pg_upgrade/t/002_pg_upgrade.pl which + cycles the regression database through pg_dump/ + pg_restore. Not enabled by default because it + is resource intensive. - wal_consistency_checking + sepgsql - Uses wal_consistency_checking=all while running - certain tests under src/test/recovery. Not - enabled by default because it is resource intensive. + Runs the test suite under contrib/sepgsql. This + requires an SELinux environment that is set up in a specific way; see + . - xid_wraparound + ssl - Runs the test suite under src/test/modules/xid_wraparound. - Not enabled by default because it is resource intensive. + Runs the test suite under src/test/ssl. This opens TCP/IP listen sockets. - oauth + wal_consistency_checking - Runs the test suite under src/test/modules/oauth_validator. - This opens TCP/IP listen sockets for a test server running HTTPS. + Uses wal_consistency_checking=all while running + certain tests under src/test/recovery. Not + enabled by default because it is resource intensive. + + + + + + xid_wraparound + + + Runs the test suite under src/test/modules/xid_wraparound. + Not enabled by default because it is resource intensive. diff --git a/doc/src/sgml/release-18.sgml b/doc/src/sgml/release-18.sgml index 66a6817a2be..9537f1932ec 100644 --- a/doc/src/sgml/release-18.sgml +++ b/doc/src/sgml/release-18.sgml @@ -1,12 +1,5148 @@ + + Release 18.4 + + + Release date: + 2026-05-14 + + + + This release contains a variety of fixes from 18.3. + For information about new features in major release 18, see + . + + + + Migration to Version 18.4 + + + A dump/restore is not required for those running 18.X. + + + + However, if you are upgrading from a version earlier than 18.2, + see . + + + + + Changes + + + + + + + Prevent unbounded recursion while processing startup packets + (Michael Paquier) + § + + + + A malicious client could crash the connected backend by alternating + rejected SSL and GSS encryption requests indefinitely. + + + + The PostgreSQL Project thanks Calif.io + (in collaboration with Claude and Anthropic Research) for reporting + this problem. + (CVE-2026-6479) + + + + + + + Fix assorted integer overflows in memory-allocation calculations + (Tom Lane, Nathan Bossart, Heikki Linnakangas) + § + § + § + § + § + § + § + § + § + + + + Various places were incautious about the possibility of integer + overflow in calculations of how much memory to allocate. Overflow + would lead to allocating a too-small buffer which the caller would + then write past the end of. This would at least trigger server + crashes, and probably could be exploited for arbitrary code + execution. In many but by no means all cases, the hazard exists + only in 32-bit builds. + + + + The PostgreSQL Project thanks Xint Code, + Bruce Dang, Sven Klemm, and Pavel Kohout for reporting these problems. + (CVE-2026-6473) + + + + + + + Properly quote subscription names + in pg_createsubscriber (Nathan Bossart) + § + + + + The given subscription name was inserted into SQL commands without + quoting, so that SQL injection could be achieved in the (perhaps + unlikely) case that the subscription name comes from an untrusted + source. + + + + The PostgreSQL Project thanks + Yu Kunpeng for reporting this problem. + (CVE-2026-6476) + + + + + + + Properly quote object names in logical replication origin checks + (Pavel Kohout) + § + + + + ALTER SUBSCRIPTION ... REFRESH PUBLICATION + interpolated schema and relation names into SQL commands without + quoting them, allowing execution of arbitrary SQL on the publisher. + + + + The PostgreSQL Project thanks + Pavel Kohout for reporting this problem. + (CVE-2026-6638) + + + + + + + Reject over-length options in ts_headline() + (Michael Paquier) + § + + + + The StartSel, StopSel + and FragmentDelimiter strings must not exceed + 32Kb in length, but this was not checked for. An over-length value + would typically crash the server. + + + + The PostgreSQL Project thanks + Xint Code for reporting this problem. + (CVE-2026-6473) + + + + + + + Detect faulty input when restoring attribute MCV statistics + (Michael Paquier) + § + + + + The statistics restore functions were insufficiently careful about + validating most-common-value statistics, and would accept values + that could crash the planner later on. + + + + The PostgreSQL Project thanks + Jeroen Gui for reporting this problem. + (CVE-2026-6575) + + + + + + + Guard against malicious time zone names + in timeofday() + and pg_strftime() (Tom Lane) + § + § + + + + A crafted time zone setting could pass % + sequences to snprintf(), potentially causing + crashes or disclosure of server memory. Another path to similar + results was to overflow the limited-size output buffer used + by pg_strftime(). + + + + The PostgreSQL Project thanks + Xint Code for reporting this problem. + (CVE-2026-6474) + + + + + + + When creating a multirange type, ensure the user + has CREATE privilege on the schema specified for + the multirange type (Jelte Fennema-Nio) + § + + + + The multirange type can be put into a different schema than its + parent range type, but we neglected to apply the required privilege + check when doing so. + + + + The PostgreSQL Project thanks + Jelte Fennema-Nio for reporting this problem. + (CVE-2026-6472) + + + + + + + Use timing-safe string comparisons in authentication code + (Michael Paquier) + § + + + + Use timingsafe_bcmp() instead + of memcpy() or strcmp() + when checking passwords, hashes, etc. It is not known whether the + data dependency of those functions is usefully exploitable in any of + these places, but in the interests of safety, replace them. + + + + The PostgreSQL Project thanks + Joe Conway for reporting this problem. + (CVE-2026-6478) + + + + + + + Mark PQfn() as unsafe, and avoid using it + within libpq (Nathan Bossart) + § + + + + For a non-integral result type, PQfn() is not + passed the size of the output buffer, so it cannot check that the + data returned by the server will fit. A malicious server could + therefore overwrite client memory. This is unfixable without an + API change, so mark the function as deprecated. Internally + to libpq, use a variant version that can + apply the missing check. + + + + The PostgreSQL Project thanks + Yu Kunpeng and Martin Heistermann for reporting this problem. + (CVE-2026-6477) + + + + + + + Prevent path traversal in pg_basebackup + and pg_rewind (Michael Paquier) + § + + + + These applications failed to validate output file paths read from + their input, so that a malicious source could overwrite any file + writable by these applications. Constrain where data can be written + by rejecting paths that are absolute or contain parent-directory + references. + + + + The PostgreSQL Project thanks XlabAI Team + of Tencent Xuanwu Lab and Valery Gubanov for reporting this problem. + (CVE-2026-6475) + + + + + + + Guard against field overflow + within contrib/intarray's query_int + type and contrib/ltree's ltxtquery + type (Tom Lane) + § + § + + + + Parsing of these query structures did not check for overflow of + 16-bit fields, so that construction of an invalid query tree was + possible. This can crash the server when executing the query. + + + + The PostgreSQL Project thanks + Xint Code for reporting this problem. + (CVE-2026-6473) + + + + + + + Guard against overly long values + of contrib/ltree's lquery type + (Michael Paquier) + § + + + + Values with more than 64K items caused internal overflows, + potentially resulting in stack smashes or wrong answers. + + + + The PostgreSQL Project thanks + Vergissmeinnicht, A1ex, and Jihe Wang + for reporting this problem. + (CVE-2026-6473) + + + + + + + Prevent SQL injection and buffer overruns + in contrib/spi (Nathan Bossart) + § + + + + check_foreign_key() was insufficiently careful + about quoting key values, and also used fixed-length buffers for + constructing queries. While this module is only meant as example + code, it still shouldn't contain such dangerous errors. + + + + The PostgreSQL Project thanks + Nikolay Samokhvalov for reporting this problem. + (CVE-2026-6637) + + + + + + + Check for nondeterministic collations before assuming that an + equality condition on a collatable type implies uniqueness + (Richard Guo) + § + § + § + § + § + + + + Numerous planner optimizations assume that, for example, at most one + table row can satisfy WHERE x = 'abc' if there is + a unique index on x. However this conclusion is + unsafe in general if the index and the WHERE + clause have different collations attached. It is safe when both + collations are deterministic, because that property essentially + requires that equality of two strings means bitwise equality. But + nondeterministic collations don't act that way, so that optimizing + on the assumption of unique matches can give wrong query answers if + either the WHERE clause or the index has a + nondeterministic collation. + + + + + + + Fix incomplete removal of relation references + in RestrictInfo structs during join removal + (Tom Lane) + § + + + + This oversight has been shown to result in planner failures such as + unexpected FULL JOIN is only supported with merge-joinable or + hash-joinable join conditions errors. It may also have + caused failure to consider valid plans in other cases. + + + + + + + Improve planner's matching of partition key columns to sub-query + outputs (Richard Guo) + § + + + + Strip no-op PlaceHolderVars from operands before comparing them to + partition keys. This change enables partition pruning to succeed in + some cases where it previously failed to recognize that a partition + need not be scanned. + + + + + + + Fix self-join removal to handle join clauses that are bare boolean + columns, e.g. ON t1.boolcol (Andrei Lepikhov, + Tender Wang, Alexander Korotkov) + § + + + + Previously such a case led to a no relation entry + for relid N error. + + + + + + + Fix UPDATE/DELETE ... WHERE CURRENT OF to work on + tables with virtual generated columns (Satyanarayana Narlapuram, + Dean Rasheed) + § + + + + + + + Fix expansion of virtual generated columns + in EXCLUDED column references in INSERT + ... ON CONFLICT (Satyanarayana Narlapuram, Dean Rasheed) + § + + + + + + + Fix incorrect handling of NEW generated columns + in rule actions and rule qualifications (Richard Guo, Dean Rasheed) + § + + + + Previously, such column references would produce NULL + in INSERT cases, or be equivalent to + the OLD value in UPDATE cases. + + + + + + + Fix spurious indexes on virtual generated columns are not + supported errors (Robert Haas) + § + + + + Creation of an expression index could sometimes incorrectly report + this error. + + + + + + + Fix spurious generated columns are not supported in COPY FROM + WHERE conditions errors (Tom Lane) + § + + + + Use of a system column in a COPY FROM WHERE + condition could sometimes incorrectly report this error. + + + + + + + Correctly report a serialization failure + when MERGE encounters a concurrently-updated + tuple in repeatable-read or serializable mode (Tender Wang) + § + + + + Previously, such cases behaved the same as in lower isolation + levels. + + + + + + + Fix CREATE TABLE ... LIKE ... INCLUDING + STATISTICS for cases where the source table has dropped + column(s) (Julien Tachoires) + § + + + + In such cases, extended statistics objects could be copied + incorrectly, or the command could give an incorrect error. + + + + + + + Allow ALTER INDEX ... ATTACH PARTITION to mark + the parent index valid if appropriate (Sami Imseih) + § + + + + There are edge cases in which a partitioned index might remain + marked as invalid even when all its leaf indexes are valid. This + change provides a mechanism whereby a user can correct such a + situation without resorting to manual catalog updates. + + + + + + + Fix ALTER TABLE ... SET NOT NULL to invoke + object-access hook functions only after completing the catalog + change (Artur Zakirov) + § + + + + + + + Fix ALTER FOREIGN DATA WRAPPER to not drop the + wrapper object's dependency on its handler function (Jeff Davis) + § + + + + + + + Fix loss of deferrability of foreign-key triggers (Yasuo Honda) + § + + + + Previously, a foreign key defined as DEFERRABLE INITIALLY + DEFERRED would behave as NOT DEFERRABLE + after being set to NOT ENFORCED status and then + back to ENFORCED. + + + + If you have a foreign key with this problem, it can be repaired + (after installing this update) by again setting it to NOT + ENFORCED and then back to ENFORCED. + + + + + + + Fix WITHOUT OVERLAPS to allow domains (Jian He) + § + + + + UNIQUE/PRIMARY KEY ... WITHOUT OVERLAPS requires + the no-overlap column to be a range or multirange, but it should + allow a domain over such a type too. + + + + + + + Disallow making a composite type be a member of itself via a + multirange (Heikki Linnakangas) + § + + + + We already forbade such cases when the intermediate type is a + domain, array, composite type, or range; but multiranges were + overlooked. + + + + + + + Fix datum-image comparisons to be insensitive to sign-extension + variations (David Rowley) + § + + + + This fixes some situations that previously led to could not + find memoization table entry errors or wrong query results. + + + + + + + Fix incorrect logic for hashed IN/NOT + IN with non-strict equality operator (Chengpeng Yan) + § + + + + The previous coding could crash or give wrong answers. All built-in + data types have strict equality operators, so that this issue could + only arise with an extension data type. + + + + + + + Truncate overly-long locale-specific numeric symbols + in to_char() (Tom Lane) + § + + + + If a locale specified a currency symbol, thousands separator, or + decimal or sign symbol more than 8 bytes long, a buffer overrun was + possible. No such locales exist in the real world, and it's + impractical for an unprivileged attacker to install a malicious + locale definition underneath a Postgres server; but for safety's + sake check for overlength symbols and truncate if needed. + + + + + + + Prevent buffer overruns when parsing an affix file for + an Ispell dictionary (Tom Lane) + § + § + + + + A corrupt or malicious affix file could crash the server. + This is not considered a security issue because text search + configuration files are presumed trustworthy, but it still seems + worth fixing. + + + + + + + Guard against integer overflow in calculations of frame start and + end positions for window aggregates (Richard Guo) + § + + + + Very large user-specified offsets (close to INT64_MAX) could result + in errors or incorrect query results. + + + + + + + Fix array_agg_array_combine() to combine the + arrays' null bitmaps correctly (Dmytro Astapov) + § + + + + This mistake resulted in sometimes-incorrect output from + parallelized array_agg(anyarray) calculations. + + + + + + + Retry sync_file_range() if it returns error + code EINTR (DaeMyung Kang) + § + + + + + + + Fix incorrect behavior + of pg_stat_reset_single_table_counters() on a + shared catalog (Chao Li) + § + + + + Such cases had a side-effect of resetting the + current database's stat_reset_timestamp, which + was unintended. + + + + + + + Update activity statistics when a parallel apply worker is idle + (Zhijie Hou) + § + + + + Previously, statistics from a recently-completed transaction might + go unreported for long intervals, particularly if the workload is + light. + + + + + + + Fix no relation entry for relid 0 failure while + estimating array lengths in set operations (Tender Wang) + § + + + + + + + Fix buffer overread when pglz_decompress() + receives corrupt input (Andrew Dunstan) + § + + + + It was possible to read a few bytes past the end of the input, which + in very unlucky cases might cause a crash. + + + + + + + Fix incremental JSON parser's handling of numeric tokens that cross + input buffer boundaries (Andrew Dunstan) + § + + + + It was possible to accept an incorrectly-formatted number, leading + to failures later. + + + + + + + Prevent bloating relation visibility maps during restore of an + incremental backup (Robert Haas) + § + + + + Restore could append many blocks of zeroes to a visibility map, due + to incorrect computation of the expected file length. This does not + result in data corruption, but it could waste a substantial amount + of disk space. + + + + + + + Use C collation, not the database's default collation, in catalog + cache lookups on text columns (Jeff Davis) + § + + + + This avoids failures in edge cases such as physical replication + startup, where there is no identified database so that a default + collation cannot be determined. + + + + + + + Prevent stuck slotsync worker processes from blocking promotion of a + standby server (Nisha Moond, Ajin Cherian) + § + § + § + + + + A worker process that was vainly waiting for a response from the + primary would delay promotion for an unreasonable amount of time. + + + + + + + Fix excessive log output from idle slotsync worker processes + (Zhijie Hou) + § + + + + + + + Ensure that tuplestore data structures are internally consistent + even after an error (Tom Lane) + § + + + + The code was previously careless about this, which is fine most of + the time but is problematic for the tuplestore backing + a WITH HOLD cursor. In v15 and before this + leads to easily-reproducible crashes; later branches are not known + to be vulnerable, but it seems best to preserve consistency in all. + + + + + + + Make the pg_aios system + view's pid column show NULL not + 0 when an entry has no owning process (ChangAo Chen) + § + + + + + + + Fix premature NULL lag reporting + in pg_stat_replication (Shinya Kato) + § + + + + The lag columns frequently read as NULL even while replication + activity was happening. + + + + + + + Fix under-allocation of shared memory used for parallel btree index + scans (Siddharth Kothari) + § + + + + In edge cases this could result in a server crash. + + + + + + + Avoid rare flush failure when working with non-WAL-logged GiST + indexes (Tomas Vondra) + § + + + + A non-logged GiST index could nonetheless sometimes + produce xlog flush request n/nnnn + is not satisfied errors, due to incorrect selection of + a fake LSN to represent an insertion point. + + + + + + + Fix underestimate of required size of DSA page maps for odd-size + segments (Paul Bunn) + § + + + + This miscalculation led to out-of-bounds accesses and hence server + crashes. + + + + + + + Fix indexing of oldest-multixact arrays in shared memory + (Yura Sokolov) + § + § + + + + This mistake could cause a prepared-but-not-yet-committed + transaction's row locks to appear invisible to other sessions, or + other visibility issues for the results of such a transaction. + With a very small max_connections setting, memory stomps were also + possible. + + + + + + + Fix array overrun when too many EXPLAIN extension + options are installed (Joel Jacobson) + § + + + + + + + Fix possible server crash when processing extended statistics on + expressions of extension data types (Michael Paquier) + § + + + + NULL pointer dereferences were possible if the data type's + typanalyze function does not compute any useful statistics. + No in-core typanalyze function behaves that way, but extensions + could. + + + + + + + Correctly display join alias Vars that are used in GROUP + BY (Tom Lane) + § + + + + In views containing queries like SELECT ... t1 LEFT JOIN t2 + USING (x) GROUP BY x, the GROUP BY + clause could be displayed incorrectly by deparsing, leading to + dump/restore failures. Failures occurred only + if t1.x and t2.x were not of + identical data types and t1.x was the side that + required an implicit coercion. + + + + + + + Fix minor memory leaks in ICU-based string processing (Jeff Davis) + § + + + + + + + If the startup process fails, properly shut down other child + processes before exiting the postmaster (Ayush Tiwari) + § + + + + The handling of this situation relied on a long-obsolete assumption + that no other postmaster children exist while the startup process is + running, so that immediate postmaster exit is acceptable. + Orphaned children would eventually notice the postmaster's death and + exit on their own, but a cleaner shutdown procedure is desirable. + + + + + + + Fix race condition between WAL replay of checkpoints and multixact + ID creations (Heikki Linnakangas) + § + + + + A standby server following WAL from a primary of an older minor + version could get into a crash-and-restart loop complaining + about could not access status of transaction. + + + + + + + Prevent indefinite wait in shutdown of a walsender process + (Anthonin Bonnefoy) + § + § + + + + At shutdown of a cluster that is publishing logical replication + data, the walsender waits for all pending WAL to be written out. + But it did not correctly request that to happen, so that in some + cases this could become an indefinite wait. + + + + + + + Ensure that changes to tables' free space maps are persisted during + recovery (Alexey Makhmutov) + § + + + + Previously, while WAL replay did update the free space map while + replaying operations that should change it, the map page buffer did + not get marked dirty if checksums are enabled, so that the changes + might never get written out. On a standby server, over time this + would result in a map wildly at variance with the table's actual + contents. While the map is only used as a hint, this condition + could cause significant performance degradation for some period of + time after the standby server is promoted to be active, until most + of the map has been repaired by updates. + + + + + + + Fix crashes in some ecpg functions when + called without any established connection (Shruthi Gowda) + § + + + + + + + Harden tar-file parsing logic against archives it can't handle + (Tom Lane) + § + + + + The tar-file reading code used + in pg_basebackup + and pg_verifybackup failed to verify that + the input is a tar file at all, let alone that it fits into the + subset of valid tar files that we can handle. This isn't a problem + for the normal scenario where the input file was generated + by PostgreSQL code, but it can be an + issue if the input has been generated by some + other tar program. + + + + + + + Fix assorted bugs in backup decompression and tar-parsing code + (Andrew Dunstan, Tom Lane, Chao Li) + § + § + § + + + + The decompression and tar-file reading code used + in pg_basebackup + and pg_verifybackup mishandled tar-file + padding data, could corrupt LZ4-compressed data in edge cases, + failed to check for some unusual error conditions, failed to exit + after compression/decompression errors (leading to cascading error + reports), and leaked memory. + + + + + + + In pg_dump, preserve NO + INHERIT property of NOT NULL + constraints (Jian He) + § + + + + Some cases missed printing the NO INHERIT clause. + + + + + + + In pg_dumpall, don't skip + role GRANTs with dangling grantor OIDs + (Tom Lane) + § + + + + Instead, handle such cases by emitting GRANT + without any GRANTED BY clause, as we did before + v16. This avoids losing the grant in foreseeable cases, since + pre-v16 servers didn't prevent dropping the grantor role. Continue + to emit a warning about the missing grantor, but only if the source + server is v16 or later. + + + + + + + In pg_upgrade, take care to use the + correct protocol version when connecting to older source servers + (Jacob Champion) + § + + + + This could be problematic when attempting to upgrade from a pre-2018 + server. + + + + + + + In contrib/basic_archive, allow the archive + directory to be missing at startup (Nathan Bossart) + § + + + + Previously, the setting + of basic_archive.archive_directory was rejected + if it didn't point to an existing directory. This is undesirable + because archiving will be stuck indefinitely, even if the directory + appears later. + + + + + + + Fix contrib/ltree to cope when case-folding + changes a string's byte length (Jeff Davis) + § + § + + + + Previously, lquery patterns specifying case-insensitive + matching might fail to match labels they should match. + + + + + + + Fix mis-structured output + from contrib/pg_overexplain's + RANGE_TABLE option (Satyanarayana Narlapuram) + § + + + + Some fields were misplaced in JSON, YAML, and XML formats, resulting + in structurally invalid output. + + + + + + + In contrib/pg_stat_statements, don't leak + memory if an error occurs while parsing the + pgss_query_texts.stat file (Heikki Linnakangas) + § + + + + + + + In contrib/postgres_fdw, avoid crash due to + premature cleanup of a failed connection (Etsuro Fujita) + § + + + + If a remote connection fails abort cleanup, we can't use it any + longer. But delay closing the connection object until end of + transaction, because there might still be references to it within + data structures such as open cursors. + + + + + + + Update time zone data files to tzdata + release 2026b (Tom Lane) + § + + + + British Columbia (America/Vancouver) will be on year-round UTC-07 + (effectively, permanent DST) beginning in November 2026. This + release assumes that their TZ abbreviation will + be MST from that time forward. That seems likely + to change, but it's unclear what new abbreviation will be used. + Also a historical correction for Moldova: they have followed EU DST + transition times since 2022. + + + + + + + + + + Release 18.3 + + + Release date: + 2026-02-26 + + + + This release contains a small number of fixes from 18.2. + For information about new features in major release 18, see + . + + + + Migration to Version 18.3 + + + A dump/restore is not required for those running 18.X. + + + + However, if you are upgrading from a version earlier than 18.2, + see . + + + + + Changes + + + + + + + Fix failure after replaying a multixid truncation record from WAL + that was generated by an older minor version (Heikki Linnakangas) + § + + + + Erroneous logic for coping with the way that previous versions + handled multixid wraparound led to replay failure, with messages + like could not access status of transaction. + A typical scenario in which this could occur is a standby server of + the latest minor version consuming WAL from a primary server of an + older version. + + + + + + + Avoid incorrect complaint of invalid encoding + when substring() is applied + to toasted data (Noah Misch) + § + § + § + + + + The fix for CVE-2026-2006 was too aggressive and could raise an + error about an incomplete character in cases that are actually + valid. + + + + + + + Fix oversight in the fix for CVE-2026-2007 (Zsolt Parragi) + § + + + + If the bounds array needed to be expanded, because + the input contained more trigrams than the initial guess, + generate_trgm_only didn't return the modified + array pointer to its caller. That would lead to incorrect output + from strict_word_similarity() and related + functions, or in rare cases a crash. The faulty code is reached if + the input string becomes longer when it's converted to lower case. + The only known instances of that occur when an ICU locale is used + with certain single-byte encodings. + + + + + + + Fix the volatility marking + of json_strip_nulls() + and jsonb_strip_nulls() (Andrew Dunstan) + § + + + + These functions have always been considered immutable, but + refactoring in version 18 accidentally marked them stable instead. + That prevents their use in index expressions and could cause + unnecessary repeat evaluations in queries. This fix corrects the + marking in newly-initialized database clusters (including clusters + that are pg_upgrade'd to 18.3 or later). + However it will not help existing clusters made using 18.0 through + 18.2. + + + + If this mistake affects your usage of these functions, the + recommended fix for an existing cluster is a manual catalog update. + As superuser, perform + +UPDATE pg_catalog.pg_proc SET provolatile = 'i' WHERE oid IN ('3261','3262'); + + in each affected database. Update template0 + and template1 as well, so that databases made in + future will have the fix. + + + + + + + Fix computation of the set of potentially-nulling outer joins for + the output of a LATERAL UNION ALL subquery + (Richard Guo) + § + + + + This error could lead to skipping NOT NULL tests + in the mistaken belief that they were unnecessary, resulting in + wrong query output. + + + + + + + Avoid name collisions between user-written constraints and + automatically-named NOT NULL constraints + (Laurenz Albe) + § + + + + As of version 18, NOT NULL constraints have + full-fledged pg_constraint entries, and + therefore require names. The logic for choosing a name for an + unnamed NOT NULL constraint failed to avoid + conflicts with user-written constraints elsewhere in the + same CREATE TABLE statement. + + + + + + + Fix pg_stat_get_backend_wait_event() + and pg_stat_get_backend_wait_event_type() + to report values for auxiliary processes (Heikki Linnakangas) + § + + + + Previously these functions returned NULL for auxiliary processes, + but that's inconsistent with + the pg_stat_activity view. + + + + + + + Fix casting a composite-type variable to a domain type when + returning its value from a PL/pgSQL function (Tom Lane) + § + + + + If the variable's value is NULL, a cache lookup failed for + type 0 error resulted. + + + + + + + Fix potential null pointer dereference + in contrib/hstore's binary input function + (Michael Paquier) + § + + + + hstore's receive function crashed on input containing + duplicate keys. hstore values generated by Postgres + would never contain duplicate keys, so this mistake has gone + unnoticed. The crash could be provoked by malicious or corrupted + data. + + + + + + + + + + Release 18.2 + + + Release date: + 2026-02-12 + + + + This release contains a variety of fixes from 18.1. + For information about new features in major release 18, see + . + + + + Migration to Version 18.2 + + + A dump/restore is not required for those running 18.X. + + + + However, if you have any indexes on ltree columns, it may + be necessary to reindex them after updating. See the sixth changelog + entry below. + + + + + Changes + + + + + + + Guard against unexpected dimensions + of oidvector/int2vector (Tom Lane) + § + + + + These data types are expected to be 1-dimensional arrays containing + no nulls, but there are cast pathways that permit violating those + expectations. Add checks to some functions that were depending on + those expectations without verifying them, and could misbehave in + consequence. + + + + The PostgreSQL Project thanks + Altan Birler for reporting this problem. + (CVE-2026-2003) + + + + + + + Harden selectivity estimators against being attached to operators + that accept unexpected data types (Tom Lane) + § + § + + + + contrib/intarray contained a selectivity + estimation function that could be abused for arbitrary code + execution, because it did not check that its input was of the + expected data type. Third-party extensions should check for similar + hazards and add defenses using the technique intarray now uses. + Since such extension fixes will take time, we now require superuser + privilege to attach a non-built-in selectivity estimator to an + operator. + + + + The PostgreSQL Project thanks + Daniel Firer, as part of zeroday.cloud, for reporting this problem. + (CVE-2026-2004) + + + + + + + Fix buffer overrun in contrib/pgcrypto's + PGP decryption functions (Michael Paquier) + § + + + + Decrypting a crafted message with an overlength session key caused a + buffer overrun, with consequences as bad as arbitrary code + execution. + + + + The PostgreSQL Project thanks + Team Xint Code, as part of zeroday.cloud, for reporting this problem. + (CVE-2026-2005) + + + + + + + Fix inadequate validation of multibyte character lengths + (Thomas Munro, Noah Misch) + § + § + § + § + § + § + + + + Assorted bugs allowed an attacker able to issue crafted SQL to + overrun string buffers, with consequences as bad as arbitrary code + execution. After these fixes, applications may + observe invalid byte sequence for encoding errors + when string functions process invalid text that has been stored in + the database. + + + + The PostgreSQL Project thanks Paul Gerste + and Moritz Sanft, as part of zeroday.cloud, for reporting this + problem. + (CVE-2026-2006) + + + + + + + Harden contrib/pg_trgm against changes in + string lowercasing behavior (Heikki Linnakangas) + § + § + + + + Fix potential buffer overruns arising from the fact that in some + locales lower-casing a string can produce more characters (not + bytes) than were in the original. That behavior is new in version + 18, and so is the bug. + + + + The PostgreSQL Project thanks + Heikki Linnakangas for reporting this problem. + (CVE-2026-2007) + + + + + + + Fix inconsistent case-insensitive matching + in contrib/ltree (Jeff Davis) + § + § + + + + Index-related routines in ltree used a + different implementation of case-folding than the primary operators + did. Their behavior was equivalent only if the default collation + provider was libc and the encoding was single-byte. + + + + To fix, change the code to use case-folding with the database's + default collation. + This change will require reindexing indexes on ltree + columns (regardless of the index access method) unless the database + uses libc as collation provider and its encoding is single-byte. + Without that, searches of such indexes will fail to locate relevant + entries. + + + + + + + When using ALTER TABLE ... ADD CONSTRAINT to add + a not-null constraint with an explicit name, if the column is + already marked NOT NULL, require that the provided name match the + existing constraint name (Álvaro Herrera, Srinath Reddy Sadipiralla) + § + + + + + + + Don't allow CTE references in sub-selects to determine semantic + levels of aggregate functions (Tom Lane) + § + + + + This change undoes a change made two minor releases ago, instead + throwing an error if a sub-select references a CTE that's below the + semantic level that standard SQL rules would assign to the aggregate + based on contained column references and aggregates. The attempted + fix turned out to cause problems of its own, and it's unclear what + to do instead. Since sub-selects within aggregates are disallowed + altogether by the SQL standard, treating such cases as errors seems + sufficient. + + + + + + + Fix trigger transition table capture for MERGE + in CTE queries (Dean Rasheed) + § + + + + When executing a data-modifying CTE query containing both + a MERGE and another DML operation on a table with + statement-level AFTER triggers, the transition + tables passed to the triggers would not include the rows affected by + the MERGE, only those affected by the other + operation(s). + + + + + + + Fix incorrect pruning of rowmarks belonging to non-relation + rangetable entries, such as subqueries (Dean Rasheed) + § + + + + This led to incorrect results if a proposed row update needed to be + modified by EvalPlanQual rechecking, as could happen if there was a + concurrent update to that row. + + + + + + + Fix failure when all children of a partitioned target table + of an update or delete have been pruned (Amit Langote) + § + + + + In such cases, the executor could report could not find junk + ctid column errors, even though nothing needs to be done. + + + + + + + Fix expression evaluation bug for a sub-select within an array + subscript (Andres Freund) + § + + + + + + + Fix text substring search for non-deterministic collations + (Laurenz Albe) + § + + + + When using a non-deterministic collation, we failed to detect a + match occurring at the very end of the searched string. + + + + + + + Avoid possible planner failure when a query contains duplicate + window function calls (Meng Zhang, David Rowley) + § + + + + Confusion over de-duplication of such calls could result in errors + like WindowFunc with winref 2 assigned to WindowAgg with + winref 1. + + + + + + + Fix planner error with set-returning functions and grouping sets + (Richard Guo) + § + + + + When constructing a ProjectSet plan node, the planner failed to + detect that subexpressions involving grouping expressions were + already computed by the input plan. This led to inefficient plans + or errors such as variable not found in subplan target + list. + + + + + + + Avoid incorrect optimization when a subquery's grouping clause + contains a volatile or set-returning function (Richard Guo) + § + + + + The planner was willing to push down outer-query restrictions + referencing such a grouping column, leading to incorrect behavior + due to multiple evaluation of a volatile function, or errors caused + by introduction of a set-returning function into the + subquery's WHERE/HAVING + clauses. + + + + + + + Look through PlaceHolderVar nodes when searching for statistics + about an expression (Richard Guo) + § + + + + This change allows the planner to find relevant statistics about + expressions pulled up from subqueries or used in GROUP + BY, avoiding falling back to a default estimate. + (Arguably we should adjust any found statistics to account for an + increased probability of the value being NULL, but we've never done + the equivalent thing for plain Vars either.) While this restriction + is old, changes in PostgreSQL version 18 + made PlaceHolderVars more common than before, so make the change to + avoid plan regressions in affected cases. + + + + + + + Look through no-op PlaceHolderVar nodes when matching expressions to + indexes (Richard Guo) + § + + + + Because PostgreSQL version 18 uses + PlaceHolderVars in more cases than before, some queries that + formerly could use an index failed to do so. Add logic to prevent + that regression. + + + + + + + Fix planner's conversion of OR clauses to + ScalarArrayOp index conditions (Tender Wang, Tom Lane) + § + + + + The code did not handle RelabelType nodes correctly, and could + generate invalid expressions or fail to perform a valid conversion. + + + + + + + Allow indexscans on partial hash indexes even when the index's + predicate implies the truth of the WHERE clause (Tom Lane) + § + + + + Normally we drop a WHERE clause that is implied by the predicate, + since it's pointless to test it; it must hold for every index + entry. However that can prevent creation of an indexscan plan if + the index is one that requires a WHERE clause on the leading index + key, as hash indexes do. Don't drop implied clauses when + considering such an index. + + + + + + + Do not emit WAL for unlogged BRIN indexes (Kirill Reshke) + § + + + + One seldom-taken code path incorrectly emitted a WAL record + relating to a BRIN index even if the index was marked unlogged. + Crash recovery would then fail to replay that record, complaining + that the file already exists. + + + + + + + Use the correct ordering function in parallel GIN index builds + (Tomas Vondra) + § + + + + The parallel code used the default ordering operator (which is + determined by the column data type's btree opclass), whereas it + should use the ordering function specified by the GIN opclass, if + any. This led to a failure if the data type has no btree opclass, + or to an invalid index if the opclass specifies an ordering function + that doesn't agree with the btree opclass. + + + + + + + Prevent truncation of CLOG that is still needed by + unread NOTIFY messages (Joel Jacobson, Heikki + Linnakangas) + § + § + § + + + + This fix prevents could not access status of + transaction errors when a backend is slow to + absorb NOTIFY messages. + + + + + + + Escalate errors occurring during NOTIFY message + processing to FATAL, i.e. close the connection (Heikki Linnakangas) + § + + + + Formerly, if a backend got an error while absorbing + a NOTIFY message, it would advance past that + message, report the error to the client, and move on. That behavior + was fraught with problems though. One big concern is that the + client has no good way to know that a notification was lost, and + certainly no way to know what was in it. Depending on the + application logic, missing a notification could cause the + application to get stuck waiting, for example. Also, any remaining + messages would not get processed until someone sent a + new NOTIFY. + + + + Also, if the connection is idle at the time of receiving + a NOTIFY signal, any ERROR would be escalated to + FATAL anyway, due to unrelated concerns. Therefore, we've chosen to + make that happen in all cases, for consistency and to provide a + clear signal to the application that it might have missed some + notifications. + + + + + + + Consider grouping expressions when computing a query ID hash + (Jian He) + § + + + + Previously, two queries that were the same except in GROUP + BY expressions would be merged + by contrib/pg_stat_statements and other users + of query IDs. + + + + + + + Fix erroneous counting of updates in EXPLAIN ANALYZE + MERGE with a concurrent update (Dean Rasheed) + § + + + + This situation led to an incorrect count of skipped + tuples in EXPLAIN's output, or to an assertion + failure in an assert-enabled build. + + + + + + + Fix bug in following update chain when locking a tuple (Jasper + Smit) + § + + + + This code path neglected to check the xmin of the first new tuple in + the update chain, making it possible to lock an unrelated tuple if + the original updater aborted and the space was immediately reclaimed + by VACUUM and then re-used. + That could cause unexpected transaction delays or deadlocks. + Errors associated with having identified the wrong tuple have also + been observed. + + + + + + + Fix incorrect handling of incremental backups of large tables + (Robert Haas, Oleg Tkachenko) + § + + + + If a table exceeding 1GB (or in general, the installation's segment + size) is truncated by VACUUM between the base + backup and the incremental + backup, pg_combinebackup could fail with + an error about truncation block length in excess of segment + size. This prevented restoring the incremental backup. + + + + + + + Fix potential backend process crash at process exit due to trying to + release a lock in an already-unmapped shared memory segment + (Rahila Syed) + § + + + + + + + Fix race condition in async I/O code (Andres Freund) + § + + + + It was possible for the result code of an asynchronous I/O operation + to be overwritten before it was fetched. + + + + + + + Guard against incorrect truncation of the multixact log after a + crash (Heikki Linnakangas) + § + + + + + + + Fix possibly mis-encoded result + of pg_stat_get_backend_activity() (Chao Li) + § + + + + The shared-memory buffer holding a session's activity string can + end with an incomplete multibyte character. Readers are supposed + to truncate off any such incomplete character, but this function + failed to do so. + + + + + + + Guard against recursive memory context logging (Fujii Masao) + § + + + + A constant flow of signals requesting memory context logging could + cause recursive execution of the logging code, which in theory could + lead to stack overflow. + + + + + + + Fix memory context usage when reinitializing a parallel execution + context (Jakub Wartak, Jeevan Chalke) + § + + + + This error could result in a crash due to a subsidiary data + structure having a shorter lifespan than the parallel context. + The problem is not known to be reachable using only + core PostgreSQL, but we have reports of + trouble in extensions. + + + + + + + Set next multixid's offset when creating a new multixid, to remove + the wait loop that was needed in corner cases (Andrey Borodin) + § + § + + + + The previous logic could get stuck waiting for an update that would + never occur. + + + + + + + Avoid rewriting data-modifying CTEs more than once (Bernice Southey, + Dean Rasheed) + § + + + + Formerly, when updating an auto-updatable view or a relation with + rules, if the original query had any data-modifying CTEs, the rewriter + would rewrite those CTEs multiple times due to recursion. This was + inefficient and could produce false errors if a CTE included an + update of an always-generated column. + + + + + + + Allow retrying initialization of a DSM registry entry + (Nathan Bossart) + § + + + + If we fail partway through initialization of a dynamic shared memory + entry, allow the next attempt to use that entry to retry + initialization. Previously the entry was left in a + permanently-failed state. + + + + + + + Avoid failure of NUMA status views when a page has been swapped out + (Tomas Vondra) + § + + + + + + + Avoid operation not permitted errors when querying + NUMA page status with older libnuma versions (Tomas Vondra) + § + + + + + + + Fail recovery if WAL does not exist back to the redo point indicated + by the checkpoint record (Nitin Jadhav) + § + + + + Add an explicit check for this before starting recovery, so that no + harm is done and a useful error message is provided. Previously, + recovery might crash or corrupt the database in this situation. + + + + + + + Avoid scribbling on the source query tree during ALTER + PUBLICATION (Sunil S) + § + + + + This error had the visible effect that an event trigger fired for + the query would see only the first publish + option, even if several had been specified. If such a query were + set up as a prepared statement, re-executions would misbehave too. + + + + + + + Pass connection options specified in CREATE SUBSCRIPTION + ... CONNECTION to the publisher's walsender (Fujii Masao) + § + + + + Before this fix, the options connection option + (if any) was ignored, thus for example preventing setting custom + server parameter values in the walsender session. It was intended + for that to work, and it did work before refactoring + in PostgreSQL version 15 broke it, so + restore the previous behavior. + + + + + + + Prevent invalidation of newly created or newly synced replication + slots (Zhijie Hou) + § + § + § + + + + A race condition with a concurrent checkpoint could allow WAL to be + removed that is needed by the replication slot, causing the slot to + immediately get marked invalid. + + + + + + + Fix race condition in computing a replication slot's required xmin + (Zhijie Hou) + § + + + + This could lead to the error cannot build an initial slot + snapshot as oldest safe xid follows snapshot's xmin. + + + + + + + During initial synchronization of a logical replication + subscription, commit the addition of + a pg_replication_origin entry before + starting to copy data (Zhijie Hou) + § + + + + Previously, if the copy step failed, the + new pg_replication_origin entry would be + lost due to transaction rollback. This led to inconsistent state in + shared memory. + + + + + + + Don't advance logical replication progress after a parallel worker + apply failure (Zhijie Hou) + § + + + + The previous behavior allowed transactions to be lost by a + subscriber. + + + + + + + Fix logical replication slotsync worker processes to handle + LOCK_TIMEOUT signals correctly (Zhijie Hou) + § + + + + Previously, timeout signals were effectively ignored. + + + + + + + Fix possible failure with unexpected data beyond EOF + during restart of a streaming replica server (Anthonin Bonnefoy) + § + + + + + + + Fix error reporting for SQL/JSON path type mismatches (Jian He) + § + + + + The code could produce a cache lookup failed for type 0 + error instead of the intended complaint about the path expression + not being of the right type. + + + + + + + Fix erroneous tracking of column position when parsing partition + range bounds (myzhen) + § + + + + This could, for example, lead to the wrong column name being cited + in error messages about casting partition bound values to the + column's data type. + + + + + + + Fix assorted minor errors in error messages (Man Zeng, Tianchen Zhang) + § + § + § + § + § + + + + For example, an error report about mismatched timeline number in a + backup manifest showed the starting timeline number where it meant + to show the ending timeline number. + + + + + + + Fix failure to perform function inlining when doing JIT compilation + with LLVM version 17 or later (Anthonin Bonnefoy) + § + + + + + + + Adjust our JIT code to work with LLVM 21 (Holger Hoffstätte) + § + + + + The previous coding failed to compile on aarch64 machines. + + + + + + + Fix aarch64-specific code to build with old (RHEL7-era) system + header files (Tom Lane) + § + § + + + + + + + Fix incorrect configure probe + for io_uring_queue_init_mem() (Masahiko Sawada) + § + + + + This error resulted in failure to optimize async I/O buffer + allocations in autotools-based builds, though the code did work when + building with meson. The main impact of the omission was + slower-than-necessary backend process exits. + + + + + + + Add new server parameter to + control use of posix_fallocate() (Thomas Munro) + § + + + + PostgreSQL version 16 and later will + use posix_fallocate(), if the platform provides + it, to extend relation files. However, this has been reported to + interact poorly with some file systems: BTRFS compression is + disabled by the use of posix_fallocate(), and + XFS could produce spurious ENOSPC errors in older + Linux kernel versions. To provide a workaround, introduce this new + server parameter. Setting file_extend_method + to write_zeros will cause the server to return to + the old method of extending files by writing blocks of zeroes. + + + + + + + Honor open()'s O_CLOEXEC + flag on Windows (Bryan Green, Thomas Munro) + § + § + § + + + + Make this flag work like it does on POSIX platforms, so that we + don't leak file handles into child processes such as COPY + TO/FROM PROGRAM. While that leakage hasn't caused many + problems, it seems undesirable. + + + + + + + Fix failure to parse long options on the server command line in + Solaris executables built with meson (Tom Lane) + § + + + + + + + Support process title changes on GNU/Hurd (Michael Banck) + § + + + + + + + Fix psql's tab completion + for VACUUM option values (Yugo Nagata) + § + + + + + + + In psql command prompts, + do not show a value for %P (pipeline status) when + there is no server connection (Chao Li) + § + + + + This makes %P act like other prompt escape + sequences whose values depend on the active connection. + + + + + + + Fix pg_dump's logic for collecting + sequence values (Nathan Bossart) + § + § + + + + pg_dump failed if a sequence was dropped + concurrently with the dump, even if the sequence was not among the + database objects to be dumped. Also, if the calling user lacks + privileges to read a sequence's + value, pg_dump emitted incorrect values + rather than failing as expected. + + + + + + + Fix potentially-incorrect quoting + of oauth_validator_libraries values + by pg_dump (ChangAo Chen) + § + + + + pg_dump applied the wrong quoting rule if + it needed to dump a value of this setting. + + + + + + + Avoid pg_dump assertion failure in + binary-upgrade mode (Vignesh C) + § + + + + Failure to handle subscription-relation objects in the object + sorting code triggered an assertion, though there were no serious + ill effects in production builds. + + + + + + + Fix incorrect error handling in pgbench + with multiple \syncpipeline commands + in pipeline mode (Yugo Nagata) + § + + + + If multiple \syncpipeline commands are + encountered after a query error, pgbench + would report failed to exit pipeline mode, or get an + assertion failure in an assert-enabled build. + + + + + + + Make pg_resetwal print the updated value + when changing OldestXID (Heikki Linnakangas) + § + + + + It already did that for every other variable it can change. + + + + + + + Make pg_resetwal allow setting next + multixact xid to 0 or next multixact offset to UINT32_MAX (Maxim + Orlov) + § + + + + These are valid values, so rejecting them was incorrect. In the + worst case, if a pg_upgrade is attempted when exactly at the point + of multixact wraparound, the upgrade would fail. + + + + + + + In contrib/amcheck, use the correct snapshot + for btree index parent checks (Mihail Nikalayeu) + § + § + + + + The previous coding caused spurious errors when examining indexes + created with CREATE INDEX CONCURRENTLY. + + + + + + + Fix contrib/amcheck to + handle half-dead btree index pages correctly + (Heikki Linnakangas) + § + + + + amcheck expected such a page to have a parent + downlink, but it does not, leading to a false error report + about mismatch between parent key and child high key. + + + + + + + Fix contrib/amcheck to + handle incomplete btree root page splits correctly + (Heikki Linnakangas) + § + + + + amcheck could report a false error + about block is not true root. + + + + + + + Fix excessive memory allocation + in contrib/pg_buffercache (David Geier) + § + + + + The code allocated twice as much memory as it needed for NUMA + page status. + + + + + + + Fix edge-case integer overflow + in contrib/intarray's selectivity estimator + for @@ (Chao Li) + § + + + + This could cause poor selectivity estimates to be produced for cases + involving the maximum integer value. + + + + + + + Fix multibyte-encoding issue in contrib/ltree + (Jeff Davis) + § + + + + The previous coding could pass an incomplete multibyte character + to lower(), probably resulting in incorrect + behavior. + + + + + + + Avoid crash in contrib/pg_stat_statements when + an IN list contains both constants and + non-constant expressions (Sami Imseih) + § + + + + + + + Update time zone data files to tzdata + release 2025c (Tom Lane) + § + + + + The only change is in historical data for pre-1976 timestamps in + Baja California. + + + + + + + + + + Release 18.1 + + + Release date: + 2025-11-13 + + + + This release contains a variety of fixes from 18.0. + For information about new features in major release 18, see + . + + + + Migration to Version 18.1 + + + A dump/restore is not required for those running 18.X. + + + + + Changes + + + + + + + Check for CREATE privileges on the schema + in CREATE STATISTICS (Jelte Fennema-Nio) + § + + + + This omission allowed table owners to create statistics in any + schema, potentially leading to unexpected naming conflicts. + + + + The PostgreSQL Project thanks + Jelte Fennema-Nio for reporting this problem. + (CVE-2025-12817) + + + + + + + Avoid integer overflow in allocation-size calculations + within libpq (Jacob Champion) + § + + + + Several places in libpq were not + sufficiently careful about computing the required size of a memory + allocation. Sufficiently large inputs could cause integer overflow, + resulting in an undersized buffer, which would then lead to writing + past the end of the buffer. + + + + The PostgreSQL Project thanks Aleksey + Solovev of Positive Technologies for reporting this problem. + (CVE-2025-12818) + + + + + + + Prevent unrecognized node type errors when a SQL/JSON + function such as JSON_VALUE has + a DEFAULT clause containing + a COLLATE expression (Jian He) + § + § + + + + + + + Avoid incorrect optimization of + variable-free HAVING clauses with grouping sets + (Richard Guo) + § + § + + + + + + + Do not use parallelism in hash right semi joins (Richard Guo) + § + + + + The case does not work reliably due to a race condition in updating + the join's shared hash table. + + + + + + + Avoid possible division-by-zero when creating ordered-append plans + (Richard Guo) + § + + + + This mistake could result in incorrect selection of the cheapest + path, or in an assertion failure in debug builds. + + + + + + + Fix planner failure with index types that can do ordered access but + not index-only scans (Maxime Schoemans) + § + + + + This oversight resulted in errors like no data returned for + index-only scan. The case does not arise with any in-core + index type, but some extensions encountered the problem. + + + + + + + Remove faulty assertion in btree index cleanup (Peter Geoghegan) + § + + + + + + + Avoid possible out-of-memory or invalid memory alloc request + size failures during parallel GIN index build (Tomas Vondra) + § + + + + + + + Ensure that BRIN autosummarization provides a snapshot for index + expressions that need one (Álvaro Herrera) + § + § + + + + Previously, autosummarization would fail for such indexes, and then + leave placeholder index tuples behind, causing the index to bloat + over time. + + + + + + + Fix integer-overflow hazard in BRIN index scans when the table + contains close to 232 pages (Sunil S) + § + + + + This oversight could result in an infinite loop or scanning of + unneeded table pages. + + + + + + + Fix incorrect zero-extension of stored values in JIT-generated tuple + deforming code (David Rowley) + § + + + + When not using JIT, the equivalent code does sign-extension not + zero-extension, leading to a different Datum representation of small + integer data types. This inconsistency was masked in most cases, + but it is known to lead to could not find memoization table + entry errors when using Memoize plan nodes, and there might + be other symptoms. + + + + + + + Fix rare crash when processing hashed GROUPING + SETS queries (David Rowley) + § + + + + + + + Repair faulty hash-table-size-choosing logic in hash joins + (Tomas Vondra) + § + + + + Hash joins sometimes used more memory than intended, or failed to + divide it in an efficient way. + + + + + + + Improve relation lookup logic in statistics manipulation functions + (Nathan Bossart) + § + § + + + + Fix pg_restore_relation_stats(), + pg_clear_relation_stats(), + pg_restore_attribute_stats(), and + pg_clear_attribute_stats() to check + privileges before acquiring lock on the target relation + rather than after. + + + + + + + Fix incorrect logic for caching result-relation information for + triggers (David Rowley, Amit Langote) + § + + + + In cases where partitions' column sets aren't physically identical + to their parent partitioned tables' column sets, this oversight + could lead to crashes. + + + + + + + Fix crash during EvalPlanQual rechecks on partitioned tables (David + Rowley, Amit Langote) + § + + + + + + + Fix EvalPlanQual handling of foreign or custom joins that do not + have an alternative local-join plan prepared for EPQ (Masahiko + Sawada, Etsuro Fujita) + § + + + + In such cases the foreign or custom access method should be invoked + normally, but that did not happen, typically leading to a crash. + + + + + + + Avoid duplicating hash partition constraints during DETACH + CONCURRENTLY (Haiyang Li) + § + + + + ALTER TABLE DETACH PARTITION CONCURRENTLY was + written to add a copy of the partitioning constraint to the + now-detached partition. This was misguided, partially because + non-concurrent DETACH doesn't do that, but mostly + because in the case of hash partitioning the constraint expression + contains references to the parent table's OID. That causes problems + during dump/restore, or if the parent table is dropped + after DETACH. In v19 and later, we'll no longer + create any such copied constraints at all. In released branches, to + minimize the risk of unforeseen consequences, only skip adding a + copied constraint if it is for hash partitioning. + + + + + + + Disallow generated columns in partition keys + (Jian He, Ashutosh Bapat) + § + + + + This was already not allowed, but the check missed some cases, such + as where the column reference is implicit in a whole-row reference. + + + + + + + Disallow generated columns in COPY ... FROM + ... WHERE clauses (Peter Eisentraut, Jian He) + § + + + + Previously, incorrect behavior or an obscure error message resulted + from attempting to reference such a column, since generated columns + have not yet been computed at the point + where WHERE filtering is done. + + + + + + + Prevent setting a column as identity if it has a not-null constraint + but the constraint is marked as invalid (Jian He) + § + + + + Identity columns must be not-null, but the check for that missed + this edge case. + + + + + + + Avoid potential use-after-free in parallel vacuum (Kevin Oommen + Anish) + § + + + + This bug seems to have no consequences in standard builds, but it's + theoretically a hazard. + + + + + + + Fix visibility checking for statistics objects + in pg_temp (Noah Misch) + § + + + + A statistics object located in a temporary schema cannot be named + without schema qualification, + but pg_statistics_obj_is_visible() missed that + memo and could return true regardless. In turn, + functions such as pg_describe_object() could + fail to schema-qualify the object's name as expected. + + + + + + + Fix minor memory leak during WAL replay of database creation + (Nathan Bossart) + § + + + + + + + Fix incorrect reporting of replication lag + in pg_stat_replication view (Fujii Masao) + § + + + + If any standby server's replay LSN stopped advancing, + the write_lag + and flush_lag columns would eventually + stop updating. + + + + + + + Avoid duplicative log messages about + invalid primary_slot_name settings (Fujii Masao) + § + + + + + + + Avoid failures when synchronized_standby_slots + references nonexistent replication slots (Shlok Kyal) + § + + + + + + + Remove the unfinished slot state file after failing to write a + replication slot's state to disk (Michael Paquier) + § + + + + Previously, a failure such as out-of-disk-space resulted in leaving + a temporary state.tmp file behind. That's + problematic because it would block all subsequent attempts to + write the state, requiring manual intervention to clean up. + + + + + + + Fix mishandling of lock timeout signals in parallel apply workers + for logical replication (Hayato Kuroda) + § + + + + The same signal number was being used for both worker shutdown and + lock timeout, leading to confusion. + + + + + + + Avoid unwanted WAL receiver shutdown when switching from streaming + to archive WAL source (Xuneng Zhou) + § + + + + During a timeline change, a standby server's WAL receiver should + remain alive, waiting for a new WAL streaming start point. Instead + it was repeatedly shutting down and immediately getting restarted, + which could confuse status monitoring code. + + + + + + + Fix use-after-free issue in the relation synchronization cache + maintained by the pgoutput logical + decoding plugin (Vignesh C, Masahiko Sawada) + § + + + + An error during logical decoding could result in crashes in + subsequent logical decoding attempts in the same session. + The case is only reachable when pgoutput + is invoked via SQL functions. + + + + + + + Avoid unnecessary invalidation of logical replication slots + (Bertrand Drouvot) + § + + + + + + + Re-establish special case for C collation in + locale setup (Jeff Davis) + § + + + + This fixes a regression in access to shared catalogs early in + backend startup, before a database has been selected. It is not + known to be a problem for any + core PostgreSQL code, but some extensions + were broken. + + + + + + + Fix incorrect printing of messages about failures in checking + whether the user has Windows administrator privilege (Bryan Green) + § + + + + This code would have crashed or at least printed garbage. + No such cases have been reported though, indicating that failure of + these system calls is extremely rare. + + + + + + + Avoid crash when attempting to + test PostgreSQL with certain libsanitizer + options (Emmanuel Sibi, Jacob Champion) + § + + + + + + + Fix false memory-context-checking warnings in debug builds + on 64-bit Windows (David Rowley) + § + + + + + + + Correctly handle GROUP BY DISTINCT in PL/pgSQL + assignment statements (Tom Lane) + § + + + + The parser failed to record the DISTINCT option + in this context, so that the command would act as if it were + plain GROUP BY. + + + + + + + Avoid leaking memory when handling a SQL error within PL/Python + (Tom Lane) + § + + + + This fixes a session-lifespan memory leak introduced in our previous + minor releases. + + + + + + + Fix libpq's handling of socket-related + errors on Windows within its GSSAPI logic (Ning Wu, Tom Lane) + § + + + + The code for encrypting/decrypting transmitted data using GSSAPI did + not correctly recognize error conditions on the connection socket, + since Windows reports those differently than other platforms. This + led to failure to make such connections on Windows. + + + + + + + Fix dumping of non-inherited not-null constraints on inherited table + columns (Dilip Kumar) + § + + + + pg_dump failed to preserve such + constraints when dumping from a pre-v18 server. + + + + + + + Fix pg_dump's sorting of + foreign key constraints (Álvaro Herrera) + § + + + + Ensure consistent ordering of these database objects, as was + already done for other object types. + + + + + + + Fix assorted errors in the data compression logic + in pg_dump + and pg_restore + (Daniel Gustafsson, Tom Lane) + § + § + § + + + + Error checking was missing or incorrect in several places, and there + were also portability issues that would manifest on big-endian + hardware. These problems had been missed because this code is only + used to read compressed TOC files within directory-format + dumps. pg_dump never produces such a + dump; the case can be reached only by manually compressing the TOC + file after the fact, which is a supported thing to do but very + uncommon. + + + + + + + Fix pgbench to error out cleanly if + a COPY operation is started (Anthonin Bonnefoy) + § + + + + pgbench doesn't intend to support this + case, but previously it went into an infinite loop. + + + + + + + Fix pgbench's reporting of multiple + errors (Yugo Nagata) + § + + + + In cases where two successive PQgetResult calls + both fail, pgbench might report the wrong + error message. + + + + + + + In pgbench, fix faulty assertion about + errors in pipeline mode (Yugo Nagata) + § + + + + + + + Fix per-file memory leakage + in pg_combinebackup (Tom Lane) + § + + + + + + + Ensure that contrib/pg_buffercache functions + can be canceled (Satyanarayana Narlapuram, Yuhang Qiu) + § + § + + + + Some code paths were capable of running for a long time without + checking for interrupts. + + + + + + + Fix contrib/pg_prewarm's privilege checks for + indexes (Ayush Vatsa, Nathan Bossart) + § + § + + + + pg_prewarm() requires SELECT + privilege on relations to be prewarmed. However, since indexes have + no SQL privileges of their own, this resulted in non-superusers + being unable to prewarm indexes. Instead, check + for SELECT privilege on the index's table. + + + + + + + In contrib/pg_stat_statements, avoid + crash when two or more constants are marked as having the same + location in the SQL statement text (Sami Imseih, Dmitry Dolgov) + § + + + + + + + Make contrib/pgstattuple more robust about + empty or invalid index pages (Nitin Motiani) + § + + + + Count all-zero pages as free space, and ignore pages that are + invalid according to a check of the page's special-space size. + The code for btree indexes already counted all-zero pages as free, + but the hash and gist code would error out, which has been found to + be much less user-friendly. Similarly, make all three cases agree + on ignoring corrupted pages rather than throwing errors. + + + + + + + Harden our read and write barrier macros to satisfy Clang + (Thomas Munro) + § + + + + We supposed that __atomic_thread_fence() is a + sufficient barrier to prevent the C compiler from re-ordering memory + accesses around it, but it appears that that's not true for Clang, + allowing it to generate incorrect code for at least RISC-V, MIPS, + and LoongArch machines. Add explicit compiler barriers to fix that. + + + + + + + Fix PGXS build infrastructure to support building + NLS po files for extensions (Ryo Matsumura) + § + + + + + + + + Release 18 Release date: - 2025-??-??, CURRENT AS OF 2025-06-20 + 2025-09-25 @@ -21,7 +5157,65 @@ - (to be completed) + An asynchronous I/O (AIO) subsystem that can improve performance of + sequential scans, bitmap heap scans, vacuums, and other operations. + + + + + + pg_upgrade + now retains optimizer statistics. + + + + + + Support for "skip scan" lookups that allow using + multicolumn B-tree indexes in + more cases. + + + + + + uuidv7() + function for generating timestamp-ordered + UUIDs. + + + + + + Virtual + generated columns + that compute their values during read operations. This is now the + default for generated columns. + + + + + + OAuth authentication support. + + + + + + OLD and NEW support for + RETURNING clauses + in , , + , and commands. + + + + + + Temporal constraints, or constraints over ranges, for + PRIMARY KEY, + UNIQUE, and + FOREIGN KEY + constraints. @@ -266,6 +5460,30 @@ Author: Fujii Masao + + + + + Change full text search to use the + default collation provider of the cluster to read configuration files + and dictionaries, rather than always using libc (Peter Eisentraut) + § + + + + Clusters that default to non-libc collation providers (e.g., ICU, + builtin) that behave differently than libc for characters processed + by LC_CTYPE could observe changes in behavior of some full-text + search functions, as well as the extension. + When upgrading such clusters using , it + is recommended to reindex all indexes related to full-text search + and pg_trgm after the upgrade. + + + @@ -583,8 +5801,10 @@ Author: Peter Geoghegan - This allows multi-column btree indexes to be used by queries that - only equality-reference the second or later indexed columns. + This allows multi-column btree indexes to be used in more cases such + as when there are no restrictions on the first or early indexed + columns (or there are non-equality ones), and there are useful + restrictions on later indexed columns. @@ -1181,15 +6401,18 @@ Author: Álvaro Herrera 2025-03-18 [62d712ecf] Introduce squashing of constant lists in query jumbling Author: Álvaro Herrera 2025-03-27 [9fbd53dea] Remove the query_id_squash_values GUC +Author: Álvaro Herrera +Branch: master Release: REL_18_BR [c2da1a5d6] 2025-06-24 19:36:32 +0200 --> Have query id computation - of arrays consider only the first and last array elements (Dmitry + of constant lists consider only the first and last constants (Dmitry Dolgov, Sami Imseih) § § + § @@ -1521,7 +6744,7 @@ Author: Amit Kapila - Allow inactive replication slots to be automatically invalided using + Allow inactive replication slots to be automatically invalidated using server variable (Nisha Moond, Bharath Rupireddy) § @@ -1864,21 +7087,27 @@ Author: Thomas Munro Allow the specification of non-overlapping PRIMARY - KEY and UNIQUE + KEY, + UNIQUE, and + foreign key constraints (Paul A. Jungwirth) § + § - This is specified by WITHOUT OVERLAPS on the - last specified column. + This is specified by WITHOUT OVERLAPS for + PRIMARY KEY and UNIQUE, and by + PERIOD for foreign keys, all applied to the last + specified column. @@ -1930,6 +7159,8 @@ Author: Peter Eisentraut @@ -1940,6 +7171,7 @@ Author: Álvaro Herrera linkend="catalog-pg-constraint">pg_constraint (Álvaro Herrera, Bernd Helmle) § + § @@ -2588,7 +7820,7 @@ Author: Tom Lane - <xref linkend="libpq"/> + <link linkend="libpq">Libpq</link> @@ -2626,20 +7858,6 @@ Author: Heikki Linnakangas - - - - - Add libpq function PQservice() - to return the connection service name (Michael Banck) - § - - - @@ -2738,6 +7958,7 @@ Author: Michael Paquier Allow psql to parse, bind, and close named prepared statements (Anthonin Bonnefoy, Michael Paquier) § + § @@ -2972,8 +8193,9 @@ Author: Nathan Bossart - This option can only be used by - and . + This option can only be run by superusers and can only + be used with options and + . @@ -3048,37 +8270,19 @@ Author: Masahiko Sawada - - - - - Allow to dump in the same output - formats as pg_dump supports (Mahendra - Singh Thalor, Andrew Dunstan) - § - - - - Also modify to handle such dumps. - Previously pg_dumpall only supported - text format. - - - - Add options - , , - and (Jeff Davis) + Add option + (Jeff Davis) § + § @@ -3285,13 +8489,16 @@ Author: Amit Kapila Add pg_createsubscriber option - to remove publications (Shubham Khanna) + to remove publications (Shubham Khanna) § + § @@ -3317,9 +8524,14 @@ Author: Masahiko Sawada Add option - to specify failover slots (Hayato Kuroda) + to specify failover slots (Hayato Kuroda) § + + + Also add option as a synonym + for , and deprecate the latter. +