diff --git a/src/test/thread/thread_test.c b/config/thread_test.c similarity index 93% rename from src/test/thread/thread_test.c rename to config/thread_test.c index e1bec01b81ad..ff2eace87d84 100644 --- a/src/test/thread/thread_test.c +++ b/config/thread_test.c @@ -1,12 +1,12 @@ /*------------------------------------------------------------------------- * * thread_test.c - * libc thread test program + * libc threading test program * * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * src/test/thread/thread_test.c + * config/thread_test.c * * This program tests to see if your standard libc functions use * pthread_setspecific()/pthread_getspecific() to be thread-safe. @@ -20,12 +20,7 @@ *------------------------------------------------------------------------- */ -#if !defined(IN_CONFIGURE) && !defined(WIN32) -#include "postgres.h" - -/* we want to know what the native strerror does, not pg_strerror */ -#undef strerror -#endif +/* We cannot use c.h, as port.h will not exist yet */ #include #include @@ -36,6 +31,7 @@ #include #include #include +#include /* CYGWIN requires this for MAXHOSTNAMELEN */ #ifdef __CYGWIN__ @@ -47,25 +43,11 @@ #include #endif - /* Test for POSIX.1c 2-arg sigwait() and fail on single-arg version */ #include int sigwait(const sigset_t *set, int *sig); -#if !defined(ENABLE_THREAD_SAFETY) && !defined(IN_CONFIGURE) && !defined(WIN32) -int -main(int argc, char *argv[]) -{ - fprintf(stderr, "This PostgreSQL build does not support threads.\n"); - fprintf(stderr, "Perhaps rerun 'configure' using '--enable-thread-safety'.\n"); - return 1; -} -#else - -/* This must be down here because this is the code that uses threads. */ -#include - #define TEMP_FILENAME_1 "thread_test.1" #define TEMP_FILENAME_2 "thread_test.2" @@ -119,14 +101,12 @@ main(int argc, char *argv[]) return 1; } -#ifdef IN_CONFIGURE /* Send stdout to 'config.log' */ close(1); dup(5); -#endif #ifdef WIN32 - err = WSAStartup(MAKEWORD(1, 1), &wsaData); + err = WSAStartup(MAKEWORD(2, 2), &wsaData); if (err != 0) { fprintf(stderr, "Cannot start the network subsystem - %d**\nexiting\n", err); @@ -455,5 +435,3 @@ func_call_2(void) pthread_mutex_lock(&init_mutex); /* wait for parent to test */ pthread_mutex_unlock(&init_mutex); } - -#endif /* !ENABLE_THREAD_SAFETY && !IN_CONFIGURE */ diff --git a/configure b/configure index 7f538ec7421d..3e2c344b283c 100755 --- a/configure +++ b/configure @@ -763,7 +763,8 @@ CPP CFLAGS_SL BITCODE_CXXFLAGS BITCODE_CFLAGS -CFLAGS_VECTOR +CFLAGS_VECTORIZE +CFLAGS_UNROLL_LOOPS PERMIT_DECLARATION_AFTER_STATEMENT LLVM_BINPATH LLVM_CXXFLAGS @@ -5432,9 +5433,12 @@ BITCODE_CFLAGS="" user_BITCODE_CXXFLAGS=$BITCODE_CXXFLAGS BITCODE_CXXFLAGS="" -# set CFLAGS_VECTOR from the environment, if available -if test "$ac_env_CFLAGS_VECTOR_set" = set; then - CFLAGS_VECTOR=$ac_env_CFLAGS_VECTOR_value +# set CFLAGS_UNROLL_LOOPS and CFLAGS_VECTORIZE from the environment, if present +if test "$ac_env_CFLAGS_UNROLL_LOOPS_set" = set; then + CFLAGS_UNROLL_LOOPS=$ac_env_CFLAGS_UNROLL_LOOPS_value +fi +if test "$ac_env_CFLAGS_VECTORIZE_set" = set; then + CFLAGS_VECTORIZE=$ac_env_CFLAGS_VECTORIZE_value fi # Some versions of GCC support some additional useful warning flags. @@ -6367,16 +6371,16 @@ if test x"$pgac_cv_prog_CXX_cxxflags__fexcess_precision_standard" = x"yes"; then fi - # Optimization flags for specific files that benefit from vectorization - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CC} supports -funroll-loops, for CFLAGS_VECTOR" >&5 -$as_echo_n "checking whether ${CC} supports -funroll-loops, for CFLAGS_VECTOR... " >&6; } + # Optimization flags for specific files that benefit from loop unrolling + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CC} supports -funroll-loops, for CFLAGS_UNROLL_LOOPS" >&5 +$as_echo_n "checking whether ${CC} supports -funroll-loops, for CFLAGS_UNROLL_LOOPS... " >&6; } if ${pgac_cv_prog_CC_cflags__funroll_loops+:} false; then : $as_echo_n "(cached) " >&6 else pgac_save_CFLAGS=$CFLAGS pgac_save_CC=$CC CC=${CC} -CFLAGS="${CFLAGS_VECTOR} -funroll-loops" +CFLAGS="${CFLAGS_UNROLL_LOOPS} -funroll-loops" ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes cat confdefs.h - <<_ACEOF >conftest.$ac_ext @@ -6403,19 +6407,20 @@ fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_prog_CC_cflags__funroll_loops" >&5 $as_echo "$pgac_cv_prog_CC_cflags__funroll_loops" >&6; } if test x"$pgac_cv_prog_CC_cflags__funroll_loops" = x"yes"; then - CFLAGS_VECTOR="${CFLAGS_VECTOR} -funroll-loops" + CFLAGS_UNROLL_LOOPS="${CFLAGS_UNROLL_LOOPS} -funroll-loops" fi - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CC} supports -ftree-vectorize, for CFLAGS_VECTOR" >&5 -$as_echo_n "checking whether ${CC} supports -ftree-vectorize, for CFLAGS_VECTOR... " >&6; } + # Optimization flags for specific files that benefit from vectorization + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CC} supports -ftree-vectorize, for CFLAGS_VECTORIZE" >&5 +$as_echo_n "checking whether ${CC} supports -ftree-vectorize, for CFLAGS_VECTORIZE... " >&6; } if ${pgac_cv_prog_CC_cflags__ftree_vectorize+:} false; then : $as_echo_n "(cached) " >&6 else pgac_save_CFLAGS=$CFLAGS pgac_save_CC=$CC CC=${CC} -CFLAGS="${CFLAGS_VECTOR} -ftree-vectorize" +CFLAGS="${CFLAGS_VECTORIZE} -ftree-vectorize" ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes cat confdefs.h - <<_ACEOF >conftest.$ac_ext @@ -6442,7 +6447,7 @@ fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_prog_CC_cflags__ftree_vectorize" >&5 $as_echo "$pgac_cv_prog_CC_cflags__ftree_vectorize" >&6; } if test x"$pgac_cv_prog_CC_cflags__ftree_vectorize" = x"yes"; then - CFLAGS_VECTOR="${CFLAGS_VECTOR} -ftree-vectorize" + CFLAGS_VECTORIZE="${CFLAGS_VECTORIZE} -ftree-vectorize" fi @@ -7105,6 +7110,7 @@ fi + # Determine flags used to emit bitcode for JIT inlining. Need to test # for behaviour changing compiler flags, to keep compatibility with # compiler used for normal postgres code. @@ -18437,6 +18443,12 @@ esac ;; esac + case " $LIBOBJS " in + *" win32stat.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS win32stat.$ac_objext" + ;; +esac + $as_echo "#define HAVE_SYMLINK 1" >>confdefs.h @@ -21533,23 +21545,21 @@ $as_echo_n "checking thread safety of required library functions... " >&6; } _CFLAGS="$CFLAGS" _LIBS="$LIBS" -CFLAGS="$CFLAGS $PTHREAD_CFLAGS -DIN_CONFIGURE" +CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$LIBS $PTHREAD_LIBS" if test "$cross_compiling" = yes; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: maybe" >&5 $as_echo "maybe" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** Skipping thread test program because of cross-compile build. -*** Run the program in src/test/thread on the target machine. " >&5 $as_echo "$as_me: WARNING: *** Skipping thread test program because of cross-compile build. -*** Run the program in src/test/thread on the target machine. " >&2;} else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -#include "$srcdir/src/test/thread/thread_test.c" +#include "$srcdir/config/thread_test.c" _ACEOF if ac_fn_c_try_run "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 @@ -21558,9 +21568,8 @@ else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } as_fn_error $? "thread test program failed -This platform is not thread-safe. Check the file 'config.log' or compile -and run src/test/thread/thread_test for the exact reason. -Use --disable-thread-safety to disable thread safety." "$LINENO" 5 +This platform is not thread-safe. Check the file 'config.log' for the +exact reason, or use --disable-thread-safety to disable thread safety." "$LINENO" 5 fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext diff --git a/configure.ac b/configure.ac index c545328648ac..75caefd6204e 100644 --- a/configure.ac +++ b/configure.ac @@ -508,9 +508,12 @@ BITCODE_CFLAGS="" user_BITCODE_CXXFLAGS=$BITCODE_CXXFLAGS BITCODE_CXXFLAGS="" -# set CFLAGS_VECTOR from the environment, if available -if test "$ac_env_CFLAGS_VECTOR_set" = set; then - CFLAGS_VECTOR=$ac_env_CFLAGS_VECTOR_value +# set CFLAGS_UNROLL_LOOPS and CFLAGS_VECTORIZE from the environment, if present +if test "$ac_env_CFLAGS_UNROLL_LOOPS_set" = set; then + CFLAGS_UNROLL_LOOPS=$ac_env_CFLAGS_UNROLL_LOOPS_value +fi +if test "$ac_env_CFLAGS_VECTORIZE_set" = set; then + CFLAGS_VECTORIZE=$ac_env_CFLAGS_VECTORIZE_value fi # Some versions of GCC support some additional useful warning flags. @@ -568,9 +571,10 @@ if test "$GCC" = yes -a "$ICC" = no; then # implicit-fallthrough level 3 (GCC's default). PGAC_PROG_CC_CFLAGS_OPT([-Werror=implicit-fallthrough=3]) PGAC_PROG_CXX_CFLAGS_OPT([-fexcess-precision=standard]) + # Optimization flags for specific files that benefit from loop unrolling + PGAC_PROG_CC_VAR_OPT(CFLAGS_UNROLL_LOOPS, [-funroll-loops]) # Optimization flags for specific files that benefit from vectorization - PGAC_PROG_CC_VAR_OPT(CFLAGS_VECTOR, [-funroll-loops]) - PGAC_PROG_CC_VAR_OPT(CFLAGS_VECTOR, [-ftree-vectorize]) + PGAC_PROG_CC_VAR_OPT(CFLAGS_VECTORIZE, [-ftree-vectorize]) # We want to suppress clang's unhelpful unused-command-line-argument warnings # but gcc won't complain about unrecognized -Wno-foo switches, so we have to # test for the positive form and if that works, add the negative form @@ -623,7 +627,8 @@ elif test "$PORTNAME" = "hpux"; then PGAC_PROG_CXX_CFLAGS_OPT([+Olibmerrno]) fi -AC_SUBST(CFLAGS_VECTOR) +AC_SUBST(CFLAGS_UNROLL_LOOPS) +AC_SUBST(CFLAGS_VECTORIZE) # Determine flags used to emit bitcode for JIT inlining. Need to test # for behaviour changing compiler flags, to keep compatibility with @@ -2166,6 +2171,7 @@ if test "$PORTNAME" = "win32"; then AC_LIBOBJ(win32error) AC_LIBOBJ(win32security) AC_LIBOBJ(win32setlocale) + AC_LIBOBJ(win32stat) AC_DEFINE([HAVE_SYMLINK], 1, [Define to 1 if you have the `symlink' function.]) AC_CHECK_TYPES(MINIDUMP_TYPE, [pgac_minidump_type=yes], [pgac_minidump_type=no], [ @@ -2681,20 +2687,18 @@ AC_MSG_CHECKING([thread safety of required library functions]) _CFLAGS="$CFLAGS" _LIBS="$LIBS" -CFLAGS="$CFLAGS $PTHREAD_CFLAGS -DIN_CONFIGURE" +CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$LIBS $PTHREAD_LIBS" AC_RUN_IFELSE( - [AC_LANG_SOURCE([[#include "$srcdir/src/test/thread/thread_test.c"]])], + [AC_LANG_SOURCE([[#include "$srcdir/config/thread_test.c"]])], [AC_MSG_RESULT(yes)], [AC_MSG_RESULT(no) AC_MSG_ERROR([thread test program failed -This platform is not thread-safe. Check the file 'config.log' or compile -and run src/test/thread/thread_test for the exact reason. -Use --disable-thread-safety to disable thread safety.])], +This platform is not thread-safe. Check the file 'config.log' for the +exact reason, or use --disable-thread-safety to disable thread safety.])], [AC_MSG_RESULT(maybe) AC_MSG_WARN([ *** Skipping thread test program because of cross-compile build. -*** Run the program in src/test/thread on the target machine. ])]) CFLAGS="$_CFLAGS" LIBS="$_LIBS" diff --git a/contrib/Makefile b/contrib/Makefile index 7780482a4932..d4273c1a7cb7 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -30,6 +30,7 @@ SUBDIRS = \ isn \ ltree \ oid2name \ + old_snapshot \ pageinspect \ passwordcheck \ pg_buffercache \ @@ -37,6 +38,7 @@ SUBDIRS = \ pg_prewarm \ pg_standby \ pg_stat_statements \ + pg_surgery \ pg_trgm \ pgcrypto \ pgrowlocks \ diff --git a/contrib/amcheck/Makefile b/contrib/amcheck/Makefile index a2b1b1036b3e..b82f221e50bb 100644 --- a/contrib/amcheck/Makefile +++ b/contrib/amcheck/Makefile @@ -3,13 +3,16 @@ MODULE_big = amcheck OBJS = \ $(WIN32RES) \ + verify_heapam.o \ verify_nbtree.o EXTENSION = amcheck -DATA = amcheck--1.1--1.2.sql amcheck--1.0--1.1.sql amcheck--1.0.sql +DATA = amcheck--1.2--1.3.sql amcheck--1.1--1.2.sql amcheck--1.0--1.1.sql amcheck--1.0.sql PGFILEDESC = "amcheck - function for verifying relation integrity" -REGRESS = check check_btree +REGRESS = check check_btree check_heap + +TAP_TESTS = 1 ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/contrib/amcheck/amcheck--1.2--1.3.sql b/contrib/amcheck/amcheck--1.2--1.3.sql new file mode 100644 index 000000000000..7237ab738ce7 --- /dev/null +++ b/contrib/amcheck/amcheck--1.2--1.3.sql @@ -0,0 +1,30 @@ +/* contrib/amcheck/amcheck--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION amcheck UPDATE TO '1.3'" to load this file. \quit + +-- +-- verify_heapam() +-- +CREATE FUNCTION verify_heapam(relation regclass, + on_error_stop boolean default false, + check_toast boolean default false, + skip text default 'none', + startblock bigint default null, + endblock bigint default null, + blkno OUT bigint, + offnum OUT integer, + attnum OUT integer, + msg OUT text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'verify_heapam' +LANGUAGE C; + +-- Don't want this to be available to public +REVOKE ALL ON FUNCTION verify_heapam(regclass, + boolean, + boolean, + text, + bigint, + bigint) +FROM PUBLIC; diff --git a/contrib/amcheck/amcheck.control b/contrib/amcheck/amcheck.control index c6e310046d4e..ab50931f754a 100644 --- a/contrib/amcheck/amcheck.control +++ b/contrib/amcheck/amcheck.control @@ -1,5 +1,5 @@ # amcheck extension comment = 'functions for verifying relation integrity' -default_version = '1.2' +default_version = '1.3' module_pathname = '$libdir/amcheck' relocatable = true diff --git a/contrib/amcheck/expected/check_heap.out b/contrib/amcheck/expected/check_heap.out new file mode 100644 index 000000000000..882f853d56ac --- /dev/null +++ b/contrib/amcheck/expected/check_heap.out @@ -0,0 +1,194 @@ +CREATE TABLE heaptest (a integer, b text); +REVOKE ALL ON heaptest FROM PUBLIC; +-- Check that invalid skip option is rejected +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'rope'); +ERROR: invalid skip option +HINT: Valid skip options are "all-visible", "all-frozen", and "none". +-- Check specifying invalid block ranges when verifying an empty table +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 5, endblock := 8); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +-- Check that valid options are not rejected nor corruption reported +-- for an empty table, and that skip enum-like parameter is case-insensitive +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'None'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'All-Frozen'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'All-Visible'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'NONE'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'ALL-FROZEN'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'ALL-VISIBLE'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +-- Add some data so subsequent tests are not entirely trivial +INSERT INTO heaptest (a, b) + (SELECT gs, repeat('x', gs) + FROM generate_series(1,50) gs); +-- Check that valid options are not rejected nor corruption reported +-- for a non-empty table +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +CREATE ROLE regress_heaptest_role; +-- verify permissions are checked (error due to function not callable) +SET ROLE regress_heaptest_role; +SELECT * FROM verify_heapam(relation := 'heaptest'); +ERROR: permission denied for function verify_heapam +RESET ROLE; +GRANT EXECUTE ON FUNCTION verify_heapam(regclass, boolean, boolean, text, bigint, bigint) TO regress_heaptest_role; +-- verify permissions are now sufficient +SET ROLE regress_heaptest_role; +SELECT * FROM verify_heapam(relation := 'heaptest'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +RESET ROLE; +-- Check specifying invalid block ranges when verifying a non-empty table. +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 10000); +ERROR: ending block number must be between 0 and 0 +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 10000, endblock := 11000); +ERROR: starting block number must be between 0 and 0 +-- Vacuum freeze to change the xids encountered in subsequent tests +VACUUM FREEZE heaptest; +-- Check that valid options are not rejected nor corruption reported +-- for a non-empty frozen table +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +-- Check that partitioned tables (the parent ones) which don't have visibility +-- maps are rejected +CREATE TABLE test_partitioned (a int, b text default repeat('x', 5000)) + PARTITION BY list (a); +SELECT * FROM verify_heapam('test_partitioned', + startblock := NULL, + endblock := NULL); +ERROR: "test_partitioned" is not a table, materialized view, or TOAST table +-- Check that valid options are not rejected nor corruption reported +-- for an empty partition table (the child one) +CREATE TABLE test_partition partition OF test_partitioned FOR VALUES IN (1); +SELECT * FROM verify_heapam('test_partition', + startblock := NULL, + endblock := NULL); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +-- Check that valid options are not rejected nor corruption reported +-- for a non-empty partition table (the child one) +INSERT INTO test_partitioned (a) (SELECT 1 FROM generate_series(1,1000) gs); +SELECT * FROM verify_heapam('test_partition', + startblock := NULL, + endblock := NULL); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +-- Check that indexes are rejected +CREATE INDEX test_index ON test_partition (a); +SELECT * FROM verify_heapam('test_index', + startblock := NULL, + endblock := NULL); +ERROR: "test_index" is not a table, materialized view, or TOAST table +-- Check that views are rejected +CREATE VIEW test_view AS SELECT 1; +SELECT * FROM verify_heapam('test_view', + startblock := NULL, + endblock := NULL); +ERROR: "test_view" is not a table, materialized view, or TOAST table +-- Check that sequences are rejected +CREATE SEQUENCE test_sequence; +SELECT * FROM verify_heapam('test_sequence', + startblock := NULL, + endblock := NULL); +ERROR: "test_sequence" is not a table, materialized view, or TOAST table +-- Check that foreign tables are rejected +CREATE FOREIGN DATA WRAPPER dummy; +CREATE SERVER dummy_server FOREIGN DATA WRAPPER dummy; +CREATE FOREIGN TABLE test_foreign_table () SERVER dummy_server; +SELECT * FROM verify_heapam('test_foreign_table', + startblock := NULL, + endblock := NULL); +ERROR: "test_foreign_table" is not a table, materialized view, or TOAST table +-- cleanup +DROP TABLE heaptest; +DROP TABLE test_partition; +DROP TABLE test_partitioned; +DROP OWNED BY regress_heaptest_role; -- permissions +DROP ROLE regress_heaptest_role; diff --git a/contrib/amcheck/sql/check_heap.sql b/contrib/amcheck/sql/check_heap.sql new file mode 100644 index 000000000000..c10a25f21cb8 --- /dev/null +++ b/contrib/amcheck/sql/check_heap.sql @@ -0,0 +1,116 @@ +CREATE TABLE heaptest (a integer, b text); +REVOKE ALL ON heaptest FROM PUBLIC; + +-- Check that invalid skip option is rejected +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'rope'); + +-- Check specifying invalid block ranges when verifying an empty table +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0); +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 5, endblock := 8); + +-- Check that valid options are not rejected nor corruption reported +-- for an empty table, and that skip enum-like parameter is case-insensitive +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'None'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'All-Frozen'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'All-Visible'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'NONE'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'ALL-FROZEN'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'ALL-VISIBLE'); + +-- Add some data so subsequent tests are not entirely trivial +INSERT INTO heaptest (a, b) + (SELECT gs, repeat('x', gs) + FROM generate_series(1,50) gs); + +-- Check that valid options are not rejected nor corruption reported +-- for a non-empty table +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible'); +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0); + +CREATE ROLE regress_heaptest_role; + +-- verify permissions are checked (error due to function not callable) +SET ROLE regress_heaptest_role; +SELECT * FROM verify_heapam(relation := 'heaptest'); +RESET ROLE; + +GRANT EXECUTE ON FUNCTION verify_heapam(regclass, boolean, boolean, text, bigint, bigint) TO regress_heaptest_role; + +-- verify permissions are now sufficient +SET ROLE regress_heaptest_role; +SELECT * FROM verify_heapam(relation := 'heaptest'); +RESET ROLE; + +-- Check specifying invalid block ranges when verifying a non-empty table. +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 10000); +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 10000, endblock := 11000); + +-- Vacuum freeze to change the xids encountered in subsequent tests +VACUUM FREEZE heaptest; + +-- Check that valid options are not rejected nor corruption reported +-- for a non-empty frozen table +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible'); +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0); + +-- Check that partitioned tables (the parent ones) which don't have visibility +-- maps are rejected +CREATE TABLE test_partitioned (a int, b text default repeat('x', 5000)) + PARTITION BY list (a); +SELECT * FROM verify_heapam('test_partitioned', + startblock := NULL, + endblock := NULL); + +-- Check that valid options are not rejected nor corruption reported +-- for an empty partition table (the child one) +CREATE TABLE test_partition partition OF test_partitioned FOR VALUES IN (1); +SELECT * FROM verify_heapam('test_partition', + startblock := NULL, + endblock := NULL); + +-- Check that valid options are not rejected nor corruption reported +-- for a non-empty partition table (the child one) +INSERT INTO test_partitioned (a) (SELECT 1 FROM generate_series(1,1000) gs); +SELECT * FROM verify_heapam('test_partition', + startblock := NULL, + endblock := NULL); + +-- Check that indexes are rejected +CREATE INDEX test_index ON test_partition (a); +SELECT * FROM verify_heapam('test_index', + startblock := NULL, + endblock := NULL); + +-- Check that views are rejected +CREATE VIEW test_view AS SELECT 1; +SELECT * FROM verify_heapam('test_view', + startblock := NULL, + endblock := NULL); + +-- Check that sequences are rejected +CREATE SEQUENCE test_sequence; +SELECT * FROM verify_heapam('test_sequence', + startblock := NULL, + endblock := NULL); + +-- Check that foreign tables are rejected +CREATE FOREIGN DATA WRAPPER dummy; +CREATE SERVER dummy_server FOREIGN DATA WRAPPER dummy; +CREATE FOREIGN TABLE test_foreign_table () SERVER dummy_server; +SELECT * FROM verify_heapam('test_foreign_table', + startblock := NULL, + endblock := NULL); + +-- cleanup +DROP TABLE heaptest; +DROP TABLE test_partition; +DROP TABLE test_partitioned; +DROP OWNED BY regress_heaptest_role; -- permissions +DROP ROLE regress_heaptest_role; diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl new file mode 100644 index 000000000000..8be1069fc20b --- /dev/null +++ b/contrib/amcheck/t/001_verify_heapam.pl @@ -0,0 +1,208 @@ +use strict; +use warnings; + +use PostgresNode; +use TestLib; + +use Test::More tests => 80; + +my ($node, $result); + +# +# Test set-up +# +$node = get_new_node('test'); +$node->init; +$node->append_conf('postgresql.conf', 'autovacuum=off'); +$node->start; +$node->safe_psql('postgres', q(CREATE EXTENSION amcheck)); + +# +# Check a table with data loaded but no corruption, freezing, etc. +# +fresh_test_table('test'); +check_all_options_uncorrupted('test', 'plain'); + +# +# Check a corrupt table +# +fresh_test_table('test'); +corrupt_first_page('test'); +detects_heap_corruption("verify_heapam('test')", "plain corrupted table"); +detects_heap_corruption( + "verify_heapam('test', skip := 'all-visible')", + "plain corrupted table skipping all-visible"); +detects_heap_corruption( + "verify_heapam('test', skip := 'all-frozen')", + "plain corrupted table skipping all-frozen"); +detects_heap_corruption( + "verify_heapam('test', check_toast := false)", + "plain corrupted table skipping toast"); +detects_heap_corruption( + "verify_heapam('test', startblock := 0, endblock := 0)", + "plain corrupted table checking only block zero"); + +# +# Check a corrupt table with all-frozen data +# +fresh_test_table('test'); +$node->safe_psql('postgres', q(VACUUM FREEZE test)); +detects_no_corruption( + "verify_heapam('test')", + "all-frozen not corrupted table"); +corrupt_first_page('test'); +detects_heap_corruption("verify_heapam('test')", + "all-frozen corrupted table"); +detects_no_corruption( + "verify_heapam('test', skip := 'all-frozen')", + "all-frozen corrupted table skipping all-frozen"); + +# Returns the filesystem path for the named relation. +sub relation_filepath +{ + my ($relname) = @_; + + my $pgdata = $node->data_dir; + my $rel = $node->safe_psql('postgres', + qq(SELECT pg_relation_filepath('$relname'))); + die "path not found for relation $relname" unless defined $rel; + return "$pgdata/$rel"; +} + +# Returns the fully qualified name of the toast table for the named relation +sub get_toast_for +{ + my ($relname) = @_; + + return $node->safe_psql( + 'postgres', qq( + SELECT 'pg_toast.' || t.relname + FROM pg_catalog.pg_class c, pg_catalog.pg_class t + WHERE c.relname = '$relname' + AND c.reltoastrelid = t.oid)); +} + +# (Re)create and populate a test table of the given name. +sub fresh_test_table +{ + my ($relname) = @_; + + return $node->safe_psql( + 'postgres', qq( + DROP TABLE IF EXISTS $relname CASCADE; + CREATE TABLE $relname (a integer, b text); + ALTER TABLE $relname SET (autovacuum_enabled=false); + ALTER TABLE $relname ALTER b SET STORAGE external; + INSERT INTO $relname (a, b) + (SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs); + BEGIN; + SAVEPOINT s1; + SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE; + UPDATE $relname SET b = b WHERE a = 42; + RELEASE s1; + SAVEPOINT s1; + SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE; + UPDATE $relname SET b = b WHERE a = 42; + COMMIT; + )); +} + +# Stops the test node, corrupts the first page of the named relation, and +# restarts the node. +sub corrupt_first_page +{ + my ($relname) = @_; + my $relpath = relation_filepath($relname); + + $node->stop; + + my $fh; + open($fh, '+<', $relpath) + or BAIL_OUT("open failed: $!"); + binmode $fh; + + # Corrupt some line pointers. The values are chosen to hit the + # various line-pointer-corruption checks in verify_heapam.c + # on both little-endian and big-endian architectures. + seek($fh, 32, 0) + or BAIL_OUT("seek failed: $!"); + syswrite( + $fh, + pack("L*", + 0xAAA15550, 0xAAA0D550, 0x00010000, + 0x00008000, 0x0000800F, 0x001e8000) + ) or BAIL_OUT("syswrite failed: $!"); + close($fh) + or BAIL_OUT("close failed: $!"); + + $node->start; +} + +sub detects_heap_corruption +{ + my ($function, $testname) = @_; + + detects_corruption( + $function, + $testname, + qr/line pointer redirection to item at offset \d+ precedes minimum offset \d+/, + qr/line pointer redirection to item at offset \d+ exceeds maximum offset \d+/, + qr/line pointer to page offset \d+ is not maximally aligned/, + qr/line pointer length \d+ is less than the minimum tuple header size \d+/, + qr/line pointer to page offset \d+ with length \d+ ends beyond maximum page offset \d+/, + ); +} + +sub detects_corruption +{ + my ($function, $testname, @re) = @_; + + my $result = $node->safe_psql('postgres', qq(SELECT * FROM $function)); + like($result, $_, $testname) for (@re); +} + +sub detects_no_corruption +{ + my ($function, $testname) = @_; + + my $result = $node->safe_psql('postgres', qq(SELECT * FROM $function)); + is($result, '', $testname); +} + +# Check various options are stable (don't abort) and do not report corruption +# when running verify_heapam on an uncorrupted test table. +# +# The relname *must* be an uncorrupted table, or this will fail. +# +# The prefix is used to identify the test, along with the options, +# and should be unique. +sub check_all_options_uncorrupted +{ + my ($relname, $prefix) = @_; + + for my $stop (qw(true false)) + { + for my $check_toast (qw(true false)) + { + for my $skip ("'none'", "'all-frozen'", "'all-visible'") + { + for my $startblock (qw(NULL 0)) + { + for my $endblock (qw(NULL 0)) + { + my $opts = + "on_error_stop := $stop, " + . "check_toast := $check_toast, " + . "skip := $skip, " + . "startblock := $startblock, " + . "endblock := $endblock"; + + detects_no_corruption( + "verify_heapam('$relname', $opts)", + "$prefix: $opts"); + } + } + } + } + } +} diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c new file mode 100644 index 000000000000..00168bb4292d --- /dev/null +++ b/contrib/amcheck/verify_heapam.c @@ -0,0 +1,1463 @@ +/*------------------------------------------------------------------------- + * + * verify_heapam.c + * Functions to check postgresql heap relations for corruption + * + * Copyright (c) 2016-2020, PostgreSQL Global Development Group + * + * contrib/amcheck/verify_heapam.c + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/detoast.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "access/heaptoast.h" +#include "access/multixact.h" +#include "access/toast_internals.h" +#include "access/visibilitymap.h" +#include "catalog/pg_am.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "storage/procarray.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" + +PG_FUNCTION_INFO_V1(verify_heapam); + +/* The number of columns in tuples returned by verify_heapam */ +#define HEAPCHECK_RELATION_COLS 4 + +/* + * Despite the name, we use this for reporting problems with both XIDs and + * MXIDs. + */ +typedef enum XidBoundsViolation +{ + XID_INVALID, + XID_IN_FUTURE, + XID_PRECEDES_CLUSTERMIN, + XID_PRECEDES_RELMIN, + XID_BOUNDS_OK +} XidBoundsViolation; + +typedef enum XidCommitStatus +{ + XID_COMMITTED, + XID_IN_PROGRESS, + XID_ABORTED +} XidCommitStatus; + +typedef enum SkipPages +{ + SKIP_PAGES_ALL_FROZEN, + SKIP_PAGES_ALL_VISIBLE, + SKIP_PAGES_NONE +} SkipPages; + +/* + * Struct holding the running context information during + * a lifetime of a verify_heapam execution. + */ +typedef struct HeapCheckContext +{ + /* + * Cached copies of values from ShmemVariableCache and computed values + * from them. + */ + FullTransactionId next_fxid; /* ShmemVariableCache->nextXid */ + TransactionId next_xid; /* 32-bit version of next_fxid */ + TransactionId oldest_xid; /* ShmemVariableCache->oldestXid */ + FullTransactionId oldest_fxid; /* 64-bit version of oldest_xid, computed + * relative to next_fxid */ + + /* + * Cached copy of value from MultiXactState + */ + MultiXactId next_mxact; /* MultiXactState->nextMXact */ + MultiXactId oldest_mxact; /* MultiXactState->oldestMultiXactId */ + + /* + * Cached copies of the most recently checked xid and its status. + */ + TransactionId cached_xid; + XidCommitStatus cached_status; + + /* Values concerning the heap relation being checked */ + Relation rel; + TransactionId relfrozenxid; + FullTransactionId relfrozenfxid; + TransactionId relminmxid; + Relation toast_rel; + Relation *toast_indexes; + Relation valid_toast_index; + int num_toast_indexes; + + /* Values for iterating over pages in the relation */ + BlockNumber blkno; + BufferAccessStrategy bstrategy; + Buffer buffer; + Page page; + + /* Values for iterating over tuples within a page */ + OffsetNumber offnum; + ItemId itemid; + uint16 lp_len; + uint16 lp_off; + HeapTupleHeader tuphdr; + int natts; + + /* Values for iterating over attributes within the tuple */ + uint32 offset; /* offset in tuple data */ + AttrNumber attnum; + + /* Values for iterating over toast for the attribute */ + int32 chunkno; + int32 attrsize; + int32 endchunk; + int32 totalchunks; + + /* Whether verify_heapam has yet encountered any corrupt tuples */ + bool is_corrupt; + + /* The descriptor and tuplestore for verify_heapam's result tuples */ + TupleDesc tupdesc; + Tuplestorestate *tupstore; +} HeapCheckContext; + +/* Internal implementation */ +static void sanity_check_relation(Relation rel); +static void check_tuple(HeapCheckContext *ctx); +static void check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx); + +static bool check_tuple_attribute(HeapCheckContext *ctx); +static bool check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, + HeapCheckContext *ctx); + +static void report_corruption(HeapCheckContext *ctx, char *msg); +static TupleDesc verify_heapam_tupdesc(void); +static FullTransactionId FullTransactionIdFromXidAndCtx(TransactionId xid, + const HeapCheckContext *ctx); +static void update_cached_xid_range(HeapCheckContext *ctx); +static void update_cached_mxid_range(HeapCheckContext *ctx); +static XidBoundsViolation check_mxid_in_range(MultiXactId mxid, + HeapCheckContext *ctx); +static XidBoundsViolation check_mxid_valid_in_rel(MultiXactId mxid, + HeapCheckContext *ctx); +static XidBoundsViolation get_xid_status(TransactionId xid, + HeapCheckContext *ctx, + XidCommitStatus *status); + +/* + * Scan and report corruption in heap pages, optionally reconciling toasted + * attributes with entries in the associated toast table. Intended to be + * called from SQL with the following parameters: + * + * relation: + * The Oid of the heap relation to be checked. + * + * on_error_stop: + * Whether to stop at the end of the first page for which errors are + * detected. Note that multiple rows may be returned. + * + * check_toast: + * Whether to check each toasted attribute against the toast table to + * verify that it can be found there. + * + * skip: + * What kinds of pages in the heap relation should be skipped. Valid + * options are "all-visible", "all-frozen", and "none". + * + * Returns to the SQL caller a set of tuples, each containing the location + * and a description of a corruption found in the heap. + * + * This code goes to some trouble to avoid crashing the server even if the + * table pages are badly corrupted, but it's probably not perfect. If + * check_toast is true, we'll use regular index lookups to try to fetch TOAST + * tuples, which can certainly cause crashes if the right kind of corruption + * exists in the toast table or index. No matter what parameters you pass, + * we can't protect against crashes that might occur trying to look up the + * commit status of transaction IDs (though we avoid trying to do such lookups + * for transaction IDs that can't legally appear in the table). + */ +Datum +verify_heapam(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + MemoryContext old_context; + bool random_access; + HeapCheckContext ctx; + Buffer vmbuffer = InvalidBuffer; + Oid relid; + bool on_error_stop; + bool check_toast; + SkipPages skip_option = SKIP_PAGES_NONE; + BlockNumber first_block; + BlockNumber last_block; + BlockNumber nblocks; + const char *skip; + + /* Check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("materialize mode required, but it is not allowed in this context"))); + + /* Check supplied arguments */ + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("relation cannot be null"))); + relid = PG_GETARG_OID(0); + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("on_error_stop cannot be null"))); + on_error_stop = PG_GETARG_BOOL(1); + + if (PG_ARGISNULL(2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("check_toast cannot be null"))); + check_toast = PG_GETARG_BOOL(2); + + if (PG_ARGISNULL(3)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("skip cannot be null"))); + skip = text_to_cstring(PG_GETARG_TEXT_PP(3)); + if (pg_strcasecmp(skip, "all-visible") == 0) + skip_option = SKIP_PAGES_ALL_VISIBLE; + else if (pg_strcasecmp(skip, "all-frozen") == 0) + skip_option = SKIP_PAGES_ALL_FROZEN; + else if (pg_strcasecmp(skip, "none") == 0) + skip_option = SKIP_PAGES_NONE; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid skip option"), + errhint("Valid skip options are \"all-visible\", \"all-frozen\", and \"none\"."))); + + memset(&ctx, 0, sizeof(HeapCheckContext)); + ctx.cached_xid = InvalidTransactionId; + + /* + * If we report corruption when not examining some individual attribute, + * we need attnum to be reported as NULL. Set that up before any + * corruption reporting might happen. + */ + ctx.attnum = -1; + + /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */ + old_context = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); + random_access = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0; + ctx.tupdesc = verify_heapam_tupdesc(); + ctx.tupstore = tuplestore_begin_heap(random_access, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = ctx.tupstore; + rsinfo->setDesc = ctx.tupdesc; + MemoryContextSwitchTo(old_context); + + /* Open relation, check relkind and access method, and check privileges */ + ctx.rel = relation_open(relid, AccessShareLock); + sanity_check_relation(ctx.rel); + + /* Early exit if the relation is empty */ + nblocks = RelationGetNumberOfBlocks(ctx.rel); + if (!nblocks) + { + relation_close(ctx.rel, AccessShareLock); + PG_RETURN_NULL(); + } + + ctx.bstrategy = GetAccessStrategy(BAS_BULKREAD); + ctx.buffer = InvalidBuffer; + ctx.page = NULL; + + /* Validate block numbers, or handle nulls. */ + if (PG_ARGISNULL(4)) + first_block = 0; + else + { + int64 fb = PG_GETARG_INT64(4); + + if (fb < 0 || fb >= nblocks) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("starting block number must be between 0 and %u", + nblocks - 1))); + first_block = (BlockNumber) fb; + } + if (PG_ARGISNULL(5)) + last_block = nblocks - 1; + else + { + int64 lb = PG_GETARG_INT64(5); + + if (lb < 0 || lb >= nblocks) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("ending block number must be between 0 and %u", + nblocks - 1))); + last_block = (BlockNumber) lb; + } + + /* Optionally open the toast relation, if any. */ + if (ctx.rel->rd_rel->reltoastrelid && check_toast) + { + int offset; + + /* Main relation has associated toast relation */ + ctx.toast_rel = table_open(ctx.rel->rd_rel->reltoastrelid, + AccessShareLock); + offset = toast_open_indexes(ctx.toast_rel, + AccessShareLock, + &(ctx.toast_indexes), + &(ctx.num_toast_indexes)); + ctx.valid_toast_index = ctx.toast_indexes[offset]; + } + else + { + /* + * Main relation has no associated toast relation, or we're + * intentionally skipping it. + */ + ctx.toast_rel = NULL; + ctx.toast_indexes = NULL; + ctx.num_toast_indexes = 0; + } + + update_cached_xid_range(&ctx); + update_cached_mxid_range(&ctx); + ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid; + ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx); + ctx.relminmxid = ctx.rel->rd_rel->relminmxid; + + if (TransactionIdIsNormal(ctx.relfrozenxid)) + ctx.oldest_xid = ctx.relfrozenxid; + + for (ctx.blkno = first_block; ctx.blkno <= last_block; ctx.blkno++) + { + OffsetNumber maxoff; + + /* Optionally skip over all-frozen or all-visible blocks */ + if (skip_option != SKIP_PAGES_NONE) + { + int32 mapbits; + + mapbits = (int32) visibilitymap_get_status(ctx.rel, ctx.blkno, + &vmbuffer); + if (skip_option == SKIP_PAGES_ALL_FROZEN) + { + if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0) + continue; + } + + if (skip_option == SKIP_PAGES_ALL_VISIBLE) + { + if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0) + continue; + } + } + + /* Read and lock the next page. */ + ctx.buffer = ReadBufferExtended(ctx.rel, MAIN_FORKNUM, ctx.blkno, + RBM_NORMAL, ctx.bstrategy); + LockBuffer(ctx.buffer, BUFFER_LOCK_SHARE); + ctx.page = BufferGetPage(ctx.buffer); + + /* Perform tuple checks */ + maxoff = PageGetMaxOffsetNumber(ctx.page); + for (ctx.offnum = FirstOffsetNumber; ctx.offnum <= maxoff; + ctx.offnum = OffsetNumberNext(ctx.offnum)) + { + ctx.itemid = PageGetItemId(ctx.page, ctx.offnum); + + /* Skip over unused/dead line pointers */ + if (!ItemIdIsUsed(ctx.itemid) || ItemIdIsDead(ctx.itemid)) + continue; + + /* + * If this line pointer has been redirected, check that it + * redirects to a valid offset within the line pointer array + */ + if (ItemIdIsRedirected(ctx.itemid)) + { + OffsetNumber rdoffnum = ItemIdGetRedirect(ctx.itemid); + ItemId rditem; + + if (rdoffnum < FirstOffsetNumber) + { + report_corruption(&ctx, + psprintf("line pointer redirection to item at offset %u precedes minimum offset %u", + (unsigned) rdoffnum, + (unsigned) FirstOffsetNumber)); + continue; + } + if (rdoffnum > maxoff) + { + report_corruption(&ctx, + psprintf("line pointer redirection to item at offset %u exceeds maximum offset %u", + (unsigned) rdoffnum, + (unsigned) maxoff)); + continue; + } + rditem = PageGetItemId(ctx.page, rdoffnum); + if (!ItemIdIsUsed(rditem)) + report_corruption(&ctx, + psprintf("line pointer redirection to unused item at offset %u", + (unsigned) rdoffnum)); + continue; + } + + /* Sanity-check the line pointer's offset and length values */ + ctx.lp_len = ItemIdGetLength(ctx.itemid); + ctx.lp_off = ItemIdGetOffset(ctx.itemid); + + if (ctx.lp_off != MAXALIGN(ctx.lp_off)) + { + report_corruption(&ctx, + psprintf("line pointer to page offset %u is not maximally aligned", + ctx.lp_off)); + continue; + } + if (ctx.lp_len < MAXALIGN(SizeofHeapTupleHeader)) + { + report_corruption(&ctx, + psprintf("line pointer length %u is less than the minimum tuple header size %u", + ctx.lp_len, + (unsigned) MAXALIGN(SizeofHeapTupleHeader))); + continue; + } + if (ctx.lp_off + ctx.lp_len > BLCKSZ) + { + report_corruption(&ctx, + psprintf("line pointer to page offset %u with length %u ends beyond maximum page offset %u", + ctx.lp_off, + ctx.lp_len, + (unsigned) BLCKSZ)); + continue; + } + + /* It should be safe to examine the tuple's header, at least */ + ctx.tuphdr = (HeapTupleHeader) PageGetItem(ctx.page, ctx.itemid); + ctx.natts = HeapTupleHeaderGetNatts(ctx.tuphdr); + + /* Ok, ready to check this next tuple */ + check_tuple(&ctx); + } + + /* clean up */ + UnlockReleaseBuffer(ctx.buffer); + + if (on_error_stop && ctx.is_corrupt) + break; + } + + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + + /* Close the associated toast table and indexes, if any. */ + if (ctx.toast_indexes) + toast_close_indexes(ctx.toast_indexes, ctx.num_toast_indexes, + AccessShareLock); + if (ctx.toast_rel) + table_close(ctx.toast_rel, AccessShareLock); + + /* Close the main relation */ + relation_close(ctx.rel, AccessShareLock); + + PG_RETURN_NULL(); +} + +/* + * Check that a relation's relkind and access method are both supported, + * and that the caller has select privilege on the relation. + */ +static void +sanity_check_relation(Relation rel) +{ + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_MATVIEW && + rel->rd_rel->relkind != RELKIND_TOASTVALUE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table, materialized view, or TOAST table", + RelationGetRelationName(rel)))); + if (rel->rd_rel->relam != HEAP_TABLE_AM_OID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only heap AM is supported"))); +} + +/* + * Record a single corruption found in the table. The values in ctx should + * reflect the location of the corruption, and the msg argument should contain + * a human readable description of the corruption. + * + * The msg argument is pfree'd by this function. + */ +static void +report_corruption(HeapCheckContext *ctx, char *msg) +{ + Datum values[HEAPCHECK_RELATION_COLS]; + bool nulls[HEAPCHECK_RELATION_COLS]; + HeapTuple tuple; + + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls)); + values[0] = Int64GetDatum(ctx->blkno); + values[1] = Int32GetDatum(ctx->offnum); + values[2] = Int32GetDatum(ctx->attnum); + nulls[2] = (ctx->attnum < 0); + values[3] = CStringGetTextDatum(msg); + + /* + * In principle, there is nothing to prevent a scan over a large, highly + * corrupted table from using work_mem worth of memory building up the + * tuplestore. That's ok, but if we also leak the msg argument memory + * until the end of the query, we could exceed work_mem by more than a + * trivial amount. Therefore, free the msg argument each time we are + * called rather than waiting for our current memory context to be freed. + */ + pfree(msg); + + tuple = heap_form_tuple(ctx->tupdesc, values, nulls); + tuplestore_puttuple(ctx->tupstore, tuple); + ctx->is_corrupt = true; +} + +/* + * Construct the TupleDesc used to report messages about corruptions found + * while scanning the heap. + */ +static TupleDesc +verify_heapam_tupdesc(void) +{ + TupleDesc tupdesc; + AttrNumber a = 0; + + tupdesc = CreateTemplateTupleDesc(HEAPCHECK_RELATION_COLS); + TupleDescInitEntry(tupdesc, ++a, "blkno", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, ++a, "offnum", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, ++a, "attnum", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, ++a, "msg", TEXTOID, -1, 0); + Assert(a == HEAPCHECK_RELATION_COLS); + + return BlessTupleDesc(tupdesc); +} + +/* + * Check for tuple header corruption and tuple visibility. + * + * Since we do not hold a snapshot, tuple visibility is not a question of + * whether we should be able to see the tuple relative to any particular + * snapshot, but rather a question of whether it is safe and reasonable to + * check the tuple attributes. + * + * Some kinds of corruption make it unsafe to check the tuple attributes, for + * example when the line pointer refers to a range of bytes outside the page. + * In such cases, we return false (not visible) after recording appropriate + * corruption messages. + * + * Some other kinds of tuple header corruption confuse the question of where + * the tuple attributes begin, or how long the nulls bitmap is, etc., making it + * unreasonable to attempt to check attributes, even if all candidate answers + * to those questions would not result in reading past the end of the line + * pointer or page. In such cases, like above, we record corruption messages + * about the header and then return false. + * + * Other kinds of tuple header corruption do not bear on the question of + * whether the tuple attributes can be checked, so we record corruption + * messages for them but do not base our visibility determination on them. (In + * other words, we do not return false merely because we detected them.) + * + * For visibility determination not specifically related to corruption, what we + * want to know is if a tuple is potentially visible to any running + * transaction. If you are tempted to replace this function's visibility logic + * with a call to another visibility checking function, keep in mind that this + * function does not update hint bits, as it seems imprudent to write hint bits + * (or anything at all) to a table during a corruption check. Nor does this + * function bother classifying tuple visibility beyond a boolean visible vs. + * not visible. + * + * The caller should already have checked that xmin and xmax are not out of + * bounds for the relation. + * + * Returns whether the tuple is both visible and sufficiently sensible to + * undergo attribute checks. + */ +static bool +check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx) +{ + uint16 infomask = tuphdr->t_infomask; + bool header_garbled = false; + unsigned expected_hoff; + + if (ctx->tuphdr->t_hoff > ctx->lp_len) + { + report_corruption(ctx, + psprintf("data begins at offset %u beyond the tuple length %u", + ctx->tuphdr->t_hoff, ctx->lp_len)); + header_garbled = true; + } + + if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) && + (ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)) + { + report_corruption(ctx, + pstrdup("multixact should not be marked committed")); + + /* + * This condition is clearly wrong, but we do not consider the header + * garbled, because we don't rely on this property for determining if + * the tuple is visible or for interpreting other relevant header + * fields. + */ + } + + if (infomask & HEAP_HASNULL) + expected_hoff = MAXALIGN(SizeofHeapTupleHeader + BITMAPLEN(ctx->natts)); + else + expected_hoff = MAXALIGN(SizeofHeapTupleHeader); + if (ctx->tuphdr->t_hoff != expected_hoff) + { + if ((infomask & HEAP_HASNULL) && ctx->natts == 1) + report_corruption(ctx, + psprintf("tuple data should begin at byte %u, but actually begins at byte %u (1 attribute, has nulls)", + expected_hoff, ctx->tuphdr->t_hoff)); + else if ((infomask & HEAP_HASNULL)) + report_corruption(ctx, + psprintf("tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, has nulls)", + expected_hoff, ctx->tuphdr->t_hoff, ctx->natts)); + else if (ctx->natts == 1) + report_corruption(ctx, + psprintf("tuple data should begin at byte %u, but actually begins at byte %u (1 attribute, no nulls)", + expected_hoff, ctx->tuphdr->t_hoff)); + else + report_corruption(ctx, + psprintf("tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, no nulls)", + expected_hoff, ctx->tuphdr->t_hoff, ctx->natts)); + header_garbled = true; + } + + if (header_garbled) + return false; /* checking of this tuple should not continue */ + + /* + * Ok, we can examine the header for tuple visibility purposes, though we + * still need to be careful about a few remaining types of header + * corruption. This logic roughly follows that of + * HeapTupleSatisfiesVacuum. Where possible the comments indicate which + * HTSV_Result we think that function might return for this tuple. + */ + if (!HeapTupleHeaderXminCommitted(tuphdr)) + { + TransactionId raw_xmin = HeapTupleHeaderGetRawXmin(tuphdr); + + if (HeapTupleHeaderXminInvalid(tuphdr)) + return false; /* HEAPTUPLE_DEAD */ + /* Used by pre-9.0 binary upgrades */ + else if (infomask & HEAP_MOVED_OFF || + infomask & HEAP_MOVED_IN) + { + XidCommitStatus status; + TransactionId xvac = HeapTupleHeaderGetXvac(tuphdr); + + switch (get_xid_status(xvac, ctx, &status)) + { + case XID_INVALID: + report_corruption(ctx, + pstrdup("old-style VACUUM FULL transaction ID is invalid")); + return false; /* corrupt */ + case XID_IN_FUTURE: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u equals or exceeds next valid transaction ID %u:%u", + xvac, + EpochFromFullTransactionId(ctx->next_fxid), + XidFromFullTransactionId(ctx->next_fxid))); + return false; /* corrupt */ + case XID_PRECEDES_RELMIN: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u precedes relation freeze threshold %u:%u", + xvac, + EpochFromFullTransactionId(ctx->relfrozenfxid), + XidFromFullTransactionId(ctx->relfrozenfxid))); + return false; /* corrupt */ + break; + case XID_PRECEDES_CLUSTERMIN: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u precedes oldest valid transaction ID %u:%u", + xvac, + EpochFromFullTransactionId(ctx->oldest_fxid), + XidFromFullTransactionId(ctx->oldest_fxid))); + return false; /* corrupt */ + break; + case XID_BOUNDS_OK: + switch (status) + { + case XID_IN_PROGRESS: + return true; /* HEAPTUPLE_DELETE_IN_PROGRESS */ + case XID_COMMITTED: + case XID_ABORTED: + return false; /* HEAPTUPLE_DEAD */ + } + } + } + else + { + XidCommitStatus status; + + switch (get_xid_status(raw_xmin, ctx, &status)) + { + case XID_INVALID: + report_corruption(ctx, + pstrdup("raw xmin is invalid")); + return false; + case XID_IN_FUTURE: + report_corruption(ctx, + psprintf("raw xmin %u equals or exceeds next valid transaction ID %u:%u", + raw_xmin, + EpochFromFullTransactionId(ctx->next_fxid), + XidFromFullTransactionId(ctx->next_fxid))); + return false; /* corrupt */ + case XID_PRECEDES_RELMIN: + report_corruption(ctx, + psprintf("raw xmin %u precedes relation freeze threshold %u:%u", + raw_xmin, + EpochFromFullTransactionId(ctx->relfrozenfxid), + XidFromFullTransactionId(ctx->relfrozenfxid))); + return false; /* corrupt */ + case XID_PRECEDES_CLUSTERMIN: + report_corruption(ctx, + psprintf("raw xmin %u precedes oldest valid transaction ID %u:%u", + raw_xmin, + EpochFromFullTransactionId(ctx->oldest_fxid), + XidFromFullTransactionId(ctx->oldest_fxid))); + return false; /* corrupt */ + case XID_BOUNDS_OK: + switch (status) + { + case XID_COMMITTED: + break; + case XID_IN_PROGRESS: + return true; /* insert or delete in progress */ + case XID_ABORTED: + return false; /* HEAPTUPLE_DEAD */ + } + } + } + } + + if (!(infomask & HEAP_XMAX_INVALID) && !HEAP_XMAX_IS_LOCKED_ONLY(infomask)) + { + if (infomask & HEAP_XMAX_IS_MULTI) + { + XidCommitStatus status; + TransactionId xmax = HeapTupleGetUpdateXid(tuphdr); + + switch (get_xid_status(xmax, ctx, &status)) + { + /* not LOCKED_ONLY, so it has to have an xmax */ + case XID_INVALID: + report_corruption(ctx, + pstrdup("xmax is invalid")); + return false; /* corrupt */ + case XID_IN_FUTURE: + report_corruption(ctx, + psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u", + xmax, + EpochFromFullTransactionId(ctx->next_fxid), + XidFromFullTransactionId(ctx->next_fxid))); + return false; /* corrupt */ + case XID_PRECEDES_RELMIN: + report_corruption(ctx, + psprintf("xmax %u precedes relation freeze threshold %u:%u", + xmax, + EpochFromFullTransactionId(ctx->relfrozenfxid), + XidFromFullTransactionId(ctx->relfrozenfxid))); + return false; /* corrupt */ + case XID_PRECEDES_CLUSTERMIN: + report_corruption(ctx, + psprintf("xmax %u precedes oldest valid transaction ID %u:%u", + xmax, + EpochFromFullTransactionId(ctx->oldest_fxid), + XidFromFullTransactionId(ctx->oldest_fxid))); + return false; /* corrupt */ + case XID_BOUNDS_OK: + switch (status) + { + case XID_IN_PROGRESS: + return true; /* HEAPTUPLE_DELETE_IN_PROGRESS */ + case XID_COMMITTED: + case XID_ABORTED: + return false; /* HEAPTUPLE_RECENTLY_DEAD or + * HEAPTUPLE_DEAD */ + } + } + + /* Ok, the tuple is live */ + } + else if (!(infomask & HEAP_XMAX_COMMITTED)) + return true; /* HEAPTUPLE_DELETE_IN_PROGRESS or + * HEAPTUPLE_LIVE */ + else + return false; /* HEAPTUPLE_RECENTLY_DEAD or HEAPTUPLE_DEAD */ + } + return true; /* not dead */ +} + +/* + * Check the current toast tuple against the state tracked in ctx, recording + * any corruption found in ctx->tupstore. + * + * This is not equivalent to running verify_heapam on the toast table itself, + * and is not hardened against corruption of the toast table. Rather, when + * validating a toasted attribute in the main table, the sequence of toast + * tuples that store the toasted value are retrieved and checked in order, with + * each toast tuple being checked against where we are in the sequence, as well + * as each toast tuple having its varlena structure sanity checked. + */ +static void +check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx) +{ + int32 curchunk; + Pointer chunk; + bool isnull; + int32 chunksize; + int32 expected_size; + + /* + * Have a chunk, extract the sequence number and the data + */ + curchunk = DatumGetInt32(fastgetattr(toasttup, 2, + ctx->toast_rel->rd_att, &isnull)); + if (isnull) + { + report_corruption(ctx, + pstrdup("toast chunk sequence number is null")); + return; + } + chunk = DatumGetPointer(fastgetattr(toasttup, 3, + ctx->toast_rel->rd_att, &isnull)); + if (isnull) + { + report_corruption(ctx, + pstrdup("toast chunk data is null")); + return; + } + if (!VARATT_IS_EXTENDED(chunk)) + chunksize = VARSIZE(chunk) - VARHDRSZ; + else if (VARATT_IS_SHORT(chunk)) + { + /* + * could happen due to heap_form_tuple doing its thing + */ + chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT; + } + else + { + /* should never happen */ + uint32 header = ((varattrib_4b *) chunk)->va_4byte.va_header; + + report_corruption(ctx, + psprintf("corrupt extended toast chunk has invalid varlena header: %0x (sequence number %d)", + header, curchunk)); + return; + } + + /* + * Some checks on the data we've found + */ + if (curchunk != ctx->chunkno) + { + report_corruption(ctx, + psprintf("toast chunk sequence number %u does not match the expected sequence number %u", + curchunk, ctx->chunkno)); + return; + } + if (curchunk > ctx->endchunk) + { + report_corruption(ctx, + psprintf("toast chunk sequence number %u exceeds the end chunk sequence number %u", + curchunk, ctx->endchunk)); + return; + } + + expected_size = curchunk < ctx->totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE + : ctx->attrsize - ((ctx->totalchunks - 1) * TOAST_MAX_CHUNK_SIZE); + if (chunksize != expected_size) + { + report_corruption(ctx, + psprintf("toast chunk size %u differs from the expected size %u", + chunksize, expected_size)); + return; + } +} + +/* + * Check the current attribute as tracked in ctx, recording any corruption + * found in ctx->tupstore. + * + * This function follows the logic performed by heap_deform_tuple(), and in the + * case of a toasted value, optionally continues along the logic of + * detoast_external_attr(), checking for any conditions that would result in + * either of those functions Asserting or crashing the backend. The checks + * performed by Asserts present in those two functions are also performed here. + * In cases where those two functions are a bit cavalier in their assumptions + * about data being correct, we perform additional checks not present in either + * of those two functions. Where some condition is checked in both of those + * functions, we perform it here twice, as we parallel the logical flow of + * those two functions. The presence of duplicate checks seems a reasonable + * price to pay for keeping this code tightly coupled with the code it + * protects. + * + * Returns true if the tuple attribute is sane enough for processing to + * continue on to the next attribute, false otherwise. + */ +static bool +check_tuple_attribute(HeapCheckContext *ctx) +{ + struct varatt_external toast_pointer; + ScanKeyData toastkey; + SysScanDesc toastscan; + SnapshotData SnapshotToast; + HeapTuple toasttup; + bool found_toasttup; + Datum attdatum; + struct varlena *attr; + char *tp; /* pointer to the tuple data */ + uint16 infomask; + Form_pg_attribute thisatt; + + infomask = ctx->tuphdr->t_infomask; + thisatt = TupleDescAttr(RelationGetDescr(ctx->rel), ctx->attnum); + + tp = (char *) ctx->tuphdr + ctx->tuphdr->t_hoff; + + if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len) + { + report_corruption(ctx, + psprintf("attribute %u with length %u starts at offset %u beyond total tuple length %u", + ctx->attnum, + thisatt->attlen, + ctx->tuphdr->t_hoff + ctx->offset, + ctx->lp_len)); + return false; + } + + /* Skip null values */ + if (infomask & HEAP_HASNULL && att_isnull(ctx->attnum, ctx->tuphdr->t_bits)) + return true; + + /* Skip non-varlena values, but update offset first */ + if (thisatt->attlen != -1) + { + ctx->offset = att_align_nominal(ctx->offset, thisatt->attalign); + ctx->offset = att_addlength_pointer(ctx->offset, thisatt->attlen, + tp + ctx->offset); + if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len) + { + report_corruption(ctx, + psprintf("attribute %u with length %u ends at offset %u beyond total tuple length %u", + ctx->attnum, + thisatt->attlen, + ctx->tuphdr->t_hoff + ctx->offset, + ctx->lp_len)); + return false; + } + return true; + } + + /* Ok, we're looking at a varlena attribute. */ + ctx->offset = att_align_pointer(ctx->offset, thisatt->attalign, -1, + tp + ctx->offset); + + /* Get the (possibly corrupt) varlena datum */ + attdatum = fetchatt(thisatt, tp + ctx->offset); + + /* + * We have the datum, but we cannot decode it carelessly, as it may still + * be corrupt. + */ + + /* + * Check that VARTAG_SIZE won't hit a TrapMacro on a corrupt va_tag before + * risking a call into att_addlength_pointer + */ + if (VARATT_IS_EXTERNAL(tp + ctx->offset)) + { + uint8 va_tag = VARTAG_EXTERNAL(tp + ctx->offset); + + if (va_tag != VARTAG_ONDISK) + { + report_corruption(ctx, + psprintf("toasted attribute %u has unexpected TOAST tag %u", + ctx->attnum, + va_tag)); + /* We can't know where the next attribute begins */ + return false; + } + } + + /* Ok, should be safe now */ + ctx->offset = att_addlength_pointer(ctx->offset, thisatt->attlen, + tp + ctx->offset); + + if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len) + { + report_corruption(ctx, + psprintf("attribute %u with length %u ends at offset %u beyond total tuple length %u", + ctx->attnum, + thisatt->attlen, + ctx->tuphdr->t_hoff + ctx->offset, + ctx->lp_len)); + + return false; + } + + /* + * heap_deform_tuple would be done with this attribute at this point, + * having stored it in values[], and would continue to the next attribute. + * We go further, because we need to check if the toast datum is corrupt. + */ + + attr = (struct varlena *) DatumGetPointer(attdatum); + + /* + * Now we follow the logic of detoast_external_attr(), with the same + * caveats about being paranoid about corruption. + */ + + /* Skip values that are not external */ + if (!VARATT_IS_EXTERNAL(attr)) + return true; + + /* It is external, and we're looking at a page on disk */ + + /* The tuple header better claim to contain toasted values */ + if (!(infomask & HEAP_HASEXTERNAL)) + { + report_corruption(ctx, + psprintf("attribute %u is external but tuple header flag HEAP_HASEXTERNAL not set", + ctx->attnum)); + return true; + } + + /* The relation better have a toast table */ + if (!ctx->rel->rd_rel->reltoastrelid) + { + report_corruption(ctx, + psprintf("attribute %u is external but relation has no toast relation", + ctx->attnum)); + return true; + } + + /* If we were told to skip toast checking, then we're done. */ + if (ctx->toast_rel == NULL) + return true; + + /* + * Must copy attr into toast_pointer for alignment considerations + */ + VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); + + ctx->attrsize = toast_pointer.va_extsize; + ctx->endchunk = (ctx->attrsize - 1) / TOAST_MAX_CHUNK_SIZE; + ctx->totalchunks = ctx->endchunk + 1; + + /* + * Setup a scan key to find chunks in toast table with matching va_valueid + */ + ScanKeyInit(&toastkey, + (AttrNumber) 1, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(toast_pointer.va_valueid)); + + /* + * Check if any chunks for this toasted object exist in the toast table, + * accessible via the index. + */ + init_toast_snapshot(&SnapshotToast); + toastscan = systable_beginscan_ordered(ctx->toast_rel, + ctx->valid_toast_index, + &SnapshotToast, 1, + &toastkey); + ctx->chunkno = 0; + found_toasttup = false; + while ((toasttup = + systable_getnext_ordered(toastscan, + ForwardScanDirection)) != NULL) + { + found_toasttup = true; + check_toast_tuple(toasttup, ctx); + ctx->chunkno++; + } + if (ctx->chunkno != (ctx->endchunk + 1)) + report_corruption(ctx, + psprintf("final toast chunk number %u differs from expected value %u", + ctx->chunkno, (ctx->endchunk + 1))); + if (!found_toasttup) + report_corruption(ctx, + psprintf("toasted value for attribute %u missing from toast table", + ctx->attnum)); + systable_endscan_ordered(toastscan); + + return true; +} + +/* + * Check the current tuple as tracked in ctx, recording any corruption found in + * ctx->tupstore. + */ +static void +check_tuple(HeapCheckContext *ctx) +{ + TransactionId xmin; + TransactionId xmax; + bool fatal = false; + uint16 infomask = ctx->tuphdr->t_infomask; + + /* If xmin is normal, it should be within valid range */ + xmin = HeapTupleHeaderGetXmin(ctx->tuphdr); + switch (get_xid_status(xmin, ctx, NULL)) + { + case XID_INVALID: + case XID_BOUNDS_OK: + break; + case XID_IN_FUTURE: + report_corruption(ctx, + psprintf("xmin %u equals or exceeds next valid transaction ID %u:%u", + xmin, + EpochFromFullTransactionId(ctx->next_fxid), + XidFromFullTransactionId(ctx->next_fxid))); + fatal = true; + break; + case XID_PRECEDES_CLUSTERMIN: + report_corruption(ctx, + psprintf("xmin %u precedes oldest valid transaction ID %u:%u", + xmin, + EpochFromFullTransactionId(ctx->oldest_fxid), + XidFromFullTransactionId(ctx->oldest_fxid))); + fatal = true; + break; + case XID_PRECEDES_RELMIN: + report_corruption(ctx, + psprintf("xmin %u precedes relation freeze threshold %u:%u", + xmin, + EpochFromFullTransactionId(ctx->relfrozenfxid), + XidFromFullTransactionId(ctx->relfrozenfxid))); + fatal = true; + break; + } + + xmax = HeapTupleHeaderGetRawXmax(ctx->tuphdr); + + if (infomask & HEAP_XMAX_IS_MULTI) + { + /* xmax is a multixact, so it should be within valid MXID range */ + switch (check_mxid_valid_in_rel(xmax, ctx)) + { + case XID_INVALID: + report_corruption(ctx, + pstrdup("multitransaction ID is invalid")); + fatal = true; + break; + case XID_PRECEDES_RELMIN: + report_corruption(ctx, + psprintf("multitransaction ID %u precedes relation minimum multitransaction ID threshold %u", + xmax, ctx->relminmxid)); + fatal = true; + break; + case XID_PRECEDES_CLUSTERMIN: + report_corruption(ctx, + psprintf("multitransaction ID %u precedes oldest valid multitransaction ID threshold %u", + xmax, ctx->oldest_mxact)); + fatal = true; + break; + case XID_IN_FUTURE: + report_corruption(ctx, + psprintf("multitransaction ID %u equals or exceeds next valid multitransaction ID %u", + xmax, + ctx->next_mxact)); + fatal = true; + break; + case XID_BOUNDS_OK: + break; + } + } + else + { + /* + * xmax is not a multixact and is normal, so it should be within the + * valid XID range. + */ + switch (get_xid_status(xmax, ctx, NULL)) + { + case XID_INVALID: + case XID_BOUNDS_OK: + break; + case XID_IN_FUTURE: + report_corruption(ctx, + psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u", + xmax, + EpochFromFullTransactionId(ctx->next_fxid), + XidFromFullTransactionId(ctx->next_fxid))); + fatal = true; + break; + case XID_PRECEDES_CLUSTERMIN: + report_corruption(ctx, + psprintf("xmax %u precedes oldest valid transaction ID %u:%u", + xmax, + EpochFromFullTransactionId(ctx->oldest_fxid), + XidFromFullTransactionId(ctx->oldest_fxid))); + fatal = true; + break; + case XID_PRECEDES_RELMIN: + report_corruption(ctx, + psprintf("xmax %u precedes relation freeze threshold %u:%u", + xmax, + EpochFromFullTransactionId(ctx->relfrozenfxid), + XidFromFullTransactionId(ctx->relfrozenfxid))); + fatal = true; + } + } + + /* + * Cannot process tuple data if tuple header was corrupt, as the offsets + * within the page cannot be trusted, leaving too much risk of reading + * garbage if we continue. + * + * We also cannot process the tuple if the xmin or xmax were invalid + * relative to relfrozenxid or relminmxid, as clog entries for the xids + * may already be gone. + */ + if (fatal) + return; + + /* + * Check various forms of tuple header corruption. If the header is too + * corrupt to continue checking, or if the tuple is not visible to anyone, + * we cannot continue with other checks. + */ + if (!check_tuple_header_and_visibilty(ctx->tuphdr, ctx)) + return; + + /* + * The tuple is visible, so it must be compatible with the current version + * of the relation descriptor. It might have fewer columns than are + * present in the relation descriptor, but it cannot have more. + */ + if (RelationGetDescr(ctx->rel)->natts < ctx->natts) + { + report_corruption(ctx, + psprintf("number of attributes %u exceeds maximum expected for table %u", + ctx->natts, + RelationGetDescr(ctx->rel)->natts)); + return; + } + + /* + * Check each attribute unless we hit corruption that confuses what to do + * next, at which point we abort further attribute checks for this tuple. + * Note that we don't abort for all types of corruption, only for those + * types where we don't know how to continue. + */ + ctx->offset = 0; + for (ctx->attnum = 0; ctx->attnum < ctx->natts; ctx->attnum++) + if (!check_tuple_attribute(ctx)) + break; /* cannot continue */ + + /* revert attnum to -1 until we again examine individual attributes */ + ctx->attnum = -1; +} + +/* + * Convert a TransactionId into a FullTransactionId using our cached values of + * the valid transaction ID range. It is the caller's responsibility to have + * already updated the cached values, if necessary. + */ +static FullTransactionId +FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx) +{ + uint32 epoch; + + if (!TransactionIdIsNormal(xid)) + return FullTransactionIdFromEpochAndXid(0, xid); + epoch = EpochFromFullTransactionId(ctx->next_fxid); + if (xid > ctx->next_xid) + epoch--; + return FullTransactionIdFromEpochAndXid(epoch, xid); +} + +/* + * Update our cached range of valid transaction IDs. + */ +static void +update_cached_xid_range(HeapCheckContext *ctx) +{ + /* Make cached copies */ + LWLockAcquire(XidGenLock, LW_SHARED); + ctx->next_fxid = ShmemVariableCache->nextXid; + ctx->oldest_xid = ShmemVariableCache->oldestXid; + LWLockRelease(XidGenLock); + + /* And compute alternate versions of the same */ + ctx->oldest_fxid = FullTransactionIdFromXidAndCtx(ctx->oldest_xid, ctx); + ctx->next_xid = XidFromFullTransactionId(ctx->next_fxid); +} + +/* + * Update our cached range of valid multitransaction IDs. + */ +static void +update_cached_mxid_range(HeapCheckContext *ctx) +{ + ReadMultiXactIdRange(&ctx->oldest_mxact, &ctx->next_mxact); +} + +/* + * Return whether the given FullTransactionId is within our cached valid + * transaction ID range. + */ +static inline bool +fxid_in_cached_range(FullTransactionId fxid, const HeapCheckContext *ctx) +{ + return (FullTransactionIdPrecedesOrEquals(ctx->oldest_fxid, fxid) && + FullTransactionIdPrecedes(fxid, ctx->next_fxid)); +} + +/* + * Checks whether a multitransaction ID is in the cached valid range, returning + * the nature of the range violation, if any. + */ +static XidBoundsViolation +check_mxid_in_range(MultiXactId mxid, HeapCheckContext *ctx) +{ + if (!TransactionIdIsValid(mxid)) + return XID_INVALID; + if (MultiXactIdPrecedes(mxid, ctx->relminmxid)) + return XID_PRECEDES_RELMIN; + if (MultiXactIdPrecedes(mxid, ctx->oldest_mxact)) + return XID_PRECEDES_CLUSTERMIN; + if (MultiXactIdPrecedesOrEquals(ctx->next_mxact, mxid)) + return XID_IN_FUTURE; + return XID_BOUNDS_OK; +} + +/* + * Checks whether the given mxid is valid to appear in the heap being checked, + * returning the nature of the range violation, if any. + * + * This function attempts to return quickly by caching the known valid mxid + * range in ctx. Callers should already have performed the initial setup of + * the cache prior to the first call to this function. + */ +static XidBoundsViolation +check_mxid_valid_in_rel(MultiXactId mxid, HeapCheckContext *ctx) +{ + XidBoundsViolation result; + + result = check_mxid_in_range(mxid, ctx); + if (result == XID_BOUNDS_OK) + return XID_BOUNDS_OK; + + /* The range may have advanced. Recheck. */ + update_cached_mxid_range(ctx); + return check_mxid_in_range(mxid, ctx); +} + +/* + * Checks whether the given transaction ID is (or was recently) valid to appear + * in the heap being checked, or whether it is too old or too new to appear in + * the relation, returning information about the nature of the bounds violation. + * + * We cache the range of valid transaction IDs. If xid is in that range, we + * conclude that it is valid, even though concurrent changes to the table might + * invalidate it under certain corrupt conditions. (For example, if the table + * contains corrupt all-frozen bits, a concurrent vacuum might skip the page(s) + * containing the xid and then truncate clog and advance the relfrozenxid + * beyond xid.) Reporting the xid as valid under such conditions seems + * acceptable, since if we had checked it earlier in our scan it would have + * truly been valid at that time. + * + * If the status argument is not NULL, and if and only if the transaction ID + * appears to be valid in this relation, the status argument will be set with + * the commit status of the transaction ID. + */ +static XidBoundsViolation +get_xid_status(TransactionId xid, HeapCheckContext *ctx, + XidCommitStatus *status) +{ + FullTransactionId fxid; + FullTransactionId clog_horizon; + + /* Quick check for special xids */ + if (!TransactionIdIsValid(xid)) + return XID_INVALID; + else if (xid == BootstrapTransactionId || xid == FrozenTransactionId) + { + if (status != NULL) + *status = XID_COMMITTED; + return XID_BOUNDS_OK; + } + + /* Check if the xid is within bounds */ + fxid = FullTransactionIdFromXidAndCtx(xid, ctx); + if (!fxid_in_cached_range(fxid, ctx)) + { + /* + * We may have been checking against stale values. Update the cached + * range to be sure, and since we relied on the cached range when we + * performed the full xid conversion, reconvert. + */ + update_cached_xid_range(ctx); + fxid = FullTransactionIdFromXidAndCtx(xid, ctx); + } + + if (FullTransactionIdPrecedesOrEquals(ctx->next_fxid, fxid)) + return XID_IN_FUTURE; + if (FullTransactionIdPrecedes(fxid, ctx->oldest_fxid)) + return XID_PRECEDES_CLUSTERMIN; + if (FullTransactionIdPrecedes(fxid, ctx->relfrozenfxid)) + return XID_PRECEDES_RELMIN; + + /* Early return if the caller does not request clog checking */ + if (status == NULL) + return XID_BOUNDS_OK; + + /* Early return if we just checked this xid in a prior call */ + if (xid == ctx->cached_xid) + { + *status = ctx->cached_status; + return XID_BOUNDS_OK; + } + + *status = XID_COMMITTED; + LWLockAcquire(XactTruncationLock, LW_SHARED); + clog_horizon = + FullTransactionIdFromXidAndCtx(ShmemVariableCache->oldestClogXid, + ctx); + if (FullTransactionIdPrecedesOrEquals(clog_horizon, fxid)) + { + if (TransactionIdIsCurrentTransactionId(xid)) + *status = XID_IN_PROGRESS; + else if (TransactionIdDidCommit(xid)) + *status = XID_COMMITTED; + else if (TransactionIdDidAbort(xid)) + *status = XID_ABORTED; + else + *status = XID_IN_PROGRESS; + } + LWLockRelease(XactTruncationLock); + ctx->cached_xid = xid; + ctx->cached_status = *status; + return XID_BOUNDS_OK; +} diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c index 5f3de3c0b7f6..6d86e3ccdacf 100644 --- a/contrib/amcheck/verify_nbtree.c +++ b/contrib/amcheck/verify_nbtree.c @@ -1752,14 +1752,36 @@ bt_right_page_check_scankey(BtreeCheckState *state) * this function is capable to compare pivot keys on different levels. */ static bool -bt_pivot_tuple_identical(IndexTuple itup1, IndexTuple itup2) +bt_pivot_tuple_identical(bool heapkeyspace, IndexTuple itup1, IndexTuple itup2) { if (IndexTupleSize(itup1) != IndexTupleSize(itup2)) return false; - if (memcmp(&itup1->t_tid.ip_posid, &itup2->t_tid.ip_posid, - IndexTupleSize(itup1) - offsetof(ItemPointerData, ip_posid)) != 0) - return false; + if (heapkeyspace) + { + /* + * Offset number will contain important information in heapkeyspace + * indexes: the number of attributes left in the pivot tuple following + * suffix truncation. Don't skip over it (compare it too). + */ + if (memcmp(&itup1->t_tid.ip_posid, &itup2->t_tid.ip_posid, + IndexTupleSize(itup1) - + offsetof(ItemPointerData, ip_posid)) != 0) + return false; + } + else + { + /* + * Cannot rely on offset number field having consistent value across + * levels on pg_upgrade'd !heapkeyspace indexes. Compare contents of + * tuple starting from just after item pointer (i.e. after block + * number and offset number). + */ + if (memcmp(&itup1->t_info, &itup2->t_info, + IndexTupleSize(itup1) - + offsetof(IndexTupleData, t_info)) != 0) + return false; + } return true; } @@ -1913,7 +1935,7 @@ bt_child_highkey_check(BtreeCheckState *state, rightsplit = P_INCOMPLETE_SPLIT(opaque); /* - * If we visit page with high key, check that it is be equal to the + * 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)) @@ -2007,7 +2029,7 @@ bt_child_highkey_check(BtreeCheckState *state, itup = state->lowkey; } - if (!bt_pivot_tuple_identical(highkey, itup)) + if (!bt_pivot_tuple_identical(state->heapkeyspace, highkey, itup)) { ereport(ERROR, (errcode(ERRCODE_INDEX_CORRUPTED), diff --git a/contrib/btree_gist/btree_numeric.c b/contrib/btree_gist/btree_numeric.c index d66901680e33..35e466cdd942 100644 --- a/contrib/btree_gist/btree_numeric.c +++ b/contrib/btree_gist/btree_numeric.c @@ -195,7 +195,7 @@ gbt_numeric_penalty(PG_FUNCTION_ARGS) } else { - Numeric nul = DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(0))); + Numeric nul = int64_to_numeric(0); *result = 0.0; diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c index cf5c53cb94bd..cde0aef7f9b9 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -1015,7 +1015,7 @@ estimate_size(PlannerInfo *root, RelOptInfo *baserel, /* * Estimate the number of tuples in the file. */ - if (baserel->pages > 0) + if (baserel->tuples >= 0 && baserel->pages > 0) { /* * We have # of pages and # of tuples from pg_class (that is, from a diff --git a/contrib/jsonb_plperl/jsonb_plperl.c b/contrib/jsonb_plperl/jsonb_plperl.c index b81ba54b809d..22e90afe1b6e 100644 --- a/contrib/jsonb_plperl/jsonb_plperl.c +++ b/contrib/jsonb_plperl/jsonb_plperl.c @@ -216,9 +216,7 @@ SV_to_JsonbValue(SV *in, JsonbParseState **jsonb_state, bool is_elem) IV ival = SvIV(in); out.type = jbvNumeric; - out.val.numeric = - DatumGetNumeric(DirectFunctionCall1(int8_numeric, - Int64GetDatum((int64) ival))); + out.val.numeric = int64_to_numeric(ival); } else if (SvNOK(in)) { diff --git a/contrib/oid2name/oid2name.c b/contrib/oid2name/oid2name.c index 91b7958c48ef..5a884e29049f 100644 --- a/contrib/oid2name/oid2name.c +++ b/contrib/oid2name/oid2name.c @@ -12,6 +12,7 @@ #include "catalog/pg_class_d.h" #include "common/connect.h" #include "common/logging.h" +#include "common/string.h" #include "getopt_long.h" #include "libpq-fe.h" #include "pg_getopt.h" @@ -293,8 +294,7 @@ PGconn * sql_conn(struct options *my_opts) { PGconn *conn; - bool have_password = false; - char password[100]; + char *password = NULL; bool new_pass; PGresult *res; @@ -316,7 +316,7 @@ sql_conn(struct options *my_opts) keywords[2] = "user"; values[2] = my_opts->username; keywords[3] = "password"; - values[3] = have_password ? password : NULL; + values[3] = password; keywords[4] = "dbname"; values[4] = my_opts->dbname; keywords[5] = "fallback_application_name"; @@ -336,11 +336,10 @@ sql_conn(struct options *my_opts) if (PQstatus(conn) == CONNECTION_BAD && PQconnectionNeedsPassword(conn) && - !have_password) + !password) { PQfinish(conn); - simple_prompt("Password: ", password, sizeof(password), false); - have_password = true; + password = simple_prompt("Password: ", false); new_pass = true; } } while (new_pass); diff --git a/contrib/old_snapshot/Makefile b/contrib/old_snapshot/Makefile new file mode 100644 index 000000000000..77c85df3225d --- /dev/null +++ b/contrib/old_snapshot/Makefile @@ -0,0 +1,22 @@ +# contrib/old_snapshot/Makefile + +MODULE_big = old_snapshot +OBJS = \ + $(WIN32RES) \ + time_mapping.o +PG_CPPFLAGS = -I$(libpq_srcdir) + +EXTENSION = old_snapshot +DATA = old_snapshot--1.0.sql +PGFILEDESC = "old_snapshot - utilities in support of old_snapshot_threshold" + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/old_snapshot +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/old_snapshot/old_snapshot--1.0.sql b/contrib/old_snapshot/old_snapshot--1.0.sql new file mode 100644 index 000000000000..9ebb8829e372 --- /dev/null +++ b/contrib/old_snapshot/old_snapshot--1.0.sql @@ -0,0 +1,14 @@ +/* contrib/old_snapshot/old_snapshot--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION old_snapshot" to load this file. \quit + +-- Show visibility map and page-level visibility information for each block. +CREATE FUNCTION pg_old_snapshot_time_mapping(array_offset OUT int4, + end_timestamp OUT timestamptz, + newest_xmin OUT xid) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_old_snapshot_time_mapping' +LANGUAGE C STRICT; + +-- XXX. Do we want REVOKE commands here? diff --git a/contrib/old_snapshot/old_snapshot.control b/contrib/old_snapshot/old_snapshot.control new file mode 100644 index 000000000000..491eec536cd6 --- /dev/null +++ b/contrib/old_snapshot/old_snapshot.control @@ -0,0 +1,5 @@ +# old_snapshot extension +comment = 'utilities in support of old_snapshot_threshold' +default_version = '1.0' +module_pathname = '$libdir/old_snapshot' +relocatable = true diff --git a/contrib/old_snapshot/time_mapping.c b/contrib/old_snapshot/time_mapping.c new file mode 100644 index 000000000000..37e0055a0086 --- /dev/null +++ b/contrib/old_snapshot/time_mapping.c @@ -0,0 +1,159 @@ +/*------------------------------------------------------------------------- + * + * time_mapping.c + * time to XID mapping information + * + * Copyright (c) 2020, PostgreSQL Global Development Group + * + * contrib/old_snapshot/time_mapping.c + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "funcapi.h" +#include "storage/lwlock.h" +#include "utils/old_snapshot.h" +#include "utils/snapmgr.h" +#include "utils/timestamp.h" + +/* + * Backend-private copy of the information from oldSnapshotControl which relates + * to the time to XID mapping, plus an index so that we can iterate. + * + * Note that the length of the xid_by_minute array is given by + * OLD_SNAPSHOT_TIME_MAP_ENTRIES (which is not a compile-time constant). + */ +typedef struct +{ + int current_index; + int head_offset; + TimestampTz head_timestamp; + int count_used; + TransactionId xid_by_minute[FLEXIBLE_ARRAY_MEMBER]; +} OldSnapshotTimeMapping; + +#define NUM_TIME_MAPPING_COLUMNS 3 + +PG_MODULE_MAGIC; +PG_FUNCTION_INFO_V1(pg_old_snapshot_time_mapping); + +static OldSnapshotTimeMapping *GetOldSnapshotTimeMapping(void); +static TupleDesc MakeOldSnapshotTimeMappingTupleDesc(void); +static HeapTuple MakeOldSnapshotTimeMappingTuple(TupleDesc tupdesc, + OldSnapshotTimeMapping *mapping); + +/* + * SQL-callable set-returning function. + */ +Datum +pg_old_snapshot_time_mapping(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + OldSnapshotTimeMapping *mapping; + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + mapping = GetOldSnapshotTimeMapping(); + funcctx->user_fctx = mapping; + funcctx->tuple_desc = MakeOldSnapshotTimeMappingTupleDesc(); + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + mapping = (OldSnapshotTimeMapping *) funcctx->user_fctx; + + while (mapping->current_index < mapping->count_used) + { + HeapTuple tuple; + + tuple = MakeOldSnapshotTimeMappingTuple(funcctx->tuple_desc, mapping); + ++mapping->current_index; + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + + SRF_RETURN_DONE(funcctx); +} + +/* + * Get the old snapshot time mapping data from shared memory. + */ +static OldSnapshotTimeMapping * +GetOldSnapshotTimeMapping(void) +{ + OldSnapshotTimeMapping *mapping; + + mapping = palloc(offsetof(OldSnapshotTimeMapping, xid_by_minute) + + sizeof(TransactionId) * OLD_SNAPSHOT_TIME_MAP_ENTRIES); + mapping->current_index = 0; + + LWLockAcquire(OldSnapshotTimeMapLock, LW_SHARED); + mapping->head_offset = oldSnapshotControl->head_offset; + mapping->head_timestamp = oldSnapshotControl->head_timestamp; + mapping->count_used = oldSnapshotControl->count_used; + for (int i = 0; i < OLD_SNAPSHOT_TIME_MAP_ENTRIES; ++i) + mapping->xid_by_minute[i] = oldSnapshotControl->xid_by_minute[i]; + LWLockRelease(OldSnapshotTimeMapLock); + + return mapping; +} + +/* + * Build a tuple descriptor for the pg_old_snapshot_time_mapping() SRF. + */ +static TupleDesc +MakeOldSnapshotTimeMappingTupleDesc(void) +{ + TupleDesc tupdesc; + + tupdesc = CreateTemplateTupleDesc(NUM_TIME_MAPPING_COLUMNS); + + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "array_offset", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "end_timestamp", + TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "newest_xmin", + XIDOID, -1, 0); + + return BlessTupleDesc(tupdesc); +} + +/* + * Convert one entry from the old snapshot time mapping to a HeapTuple. + */ +static HeapTuple +MakeOldSnapshotTimeMappingTuple(TupleDesc tupdesc, OldSnapshotTimeMapping *mapping) +{ + Datum values[NUM_TIME_MAPPING_COLUMNS]; + bool nulls[NUM_TIME_MAPPING_COLUMNS]; + int array_position; + TimestampTz timestamp; + + /* + * Figure out the array position corresponding to the current index. + * + * Index 0 means the oldest entry in the mapping, which is stored at + * mapping->head_offset. Index 1 means the next-oldest entry, which is a the + * following index, and so on. We wrap around when we reach the end of the array. + */ + array_position = (mapping->head_offset + mapping->current_index) + % OLD_SNAPSHOT_TIME_MAP_ENTRIES; + + /* + * No explicit timestamp is stored for any entry other than the oldest one, + * but each entry corresponds to 1-minute period, so we can just add. + */ + timestamp = TimestampTzPlusMilliseconds(mapping->head_timestamp, + mapping->current_index * 60000); + + /* Initialize nulls and values arrays. */ + memset(nulls, 0, sizeof(nulls)); + values[0] = Int32GetDatum(array_position); + values[1] = TimestampTzGetDatum(timestamp); + values[2] = TransactionIdGetDatum(mapping->xid_by_minute[array_position]); + + return heap_form_tuple(tupdesc, values, nulls); +} diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c index e7a323044bf9..445605db58af 100644 --- a/contrib/pageinspect/btreefuncs.c +++ b/contrib/pageinspect/btreefuncs.c @@ -259,7 +259,7 @@ struct user_args * ------------------------------------------------------ */ static Datum -bt_page_print_tuples(FuncCallContext *fctx, struct user_args *uargs) +bt_page_print_tuples(struct user_args *uargs) { Page page = uargs->page; OffsetNumber offset = uargs->offset; @@ -498,7 +498,7 @@ bt_page_items(PG_FUNCTION_ARGS) if (fctx->call_cntr < fctx->max_calls) { - result = bt_page_print_tuples(fctx, uargs); + result = bt_page_print_tuples(uargs); uargs->offset++; SRF_RETURN_NEXT(fctx, result); } @@ -582,7 +582,7 @@ bt_page_items_bytea(PG_FUNCTION_ARGS) if (fctx->call_cntr < fctx->max_calls) { - result = bt_page_print_tuples(fctx, uargs); + result = bt_page_print_tuples(uargs); uargs->offset++; SRF_RETURN_NEXT(fctx, result); } diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c index d5f9d14b0109..70f056232fe7 100644 --- a/contrib/passwordcheck/passwordcheck.c +++ b/contrib/passwordcheck/passwordcheck.c @@ -91,6 +91,9 @@ check_password(const char *username, int i; bool pwd_has_letter, pwd_has_nonletter; +#ifdef USE_CRACKLIB + const char *reason; +#endif /* enforce minimum length */ if (pwdlen < MIN_PWD_LENGTH) @@ -125,10 +128,11 @@ check_password(const char *username, #ifdef USE_CRACKLIB /* call cracklib to check password */ - if (FascistCheck(password, CRACKLIB_DICTPATH)) + if ((reason = FascistCheck(password, CRACKLIB_DICTPATH))) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("password is easily cracked"))); + errmsg("password is easily cracked"), + errdetail_log("cracklib diagnostic: %s", reason))); #endif } diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 6b91c62c31a8..1eac9edaee72 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -376,7 +376,7 @@ static void JumbleRowMarks(pgssJumbleState *jstate, List *rowMarks); static void JumbleExpr(pgssJumbleState *jstate, Node *node); static void RecordConstLocation(pgssJumbleState *jstate, int location); static char *generate_normalized_query(pgssJumbleState *jstate, const char *query, - int query_loc, int *query_len_p, int encoding); + int query_loc, int *query_len_p); static void fill_in_constant_lengths(pgssJumbleState *jstate, const char *query, int query_loc); static int comp_location(const void *a, const void *b); @@ -1336,8 +1336,7 @@ pgss_store(const char *query, uint64 queryId, LWLockRelease(pgss->lock); norm_query = generate_normalized_query(jstate, query, query_location, - &query_len, - encoding); + &query_len); LWLockAcquire(pgss->lock, LW_SHARED); } @@ -3235,7 +3234,7 @@ RecordConstLocation(pgssJumbleState *jstate, int location) */ static char * generate_normalized_query(pgssJumbleState *jstate, const char *query, - int query_loc, int *query_len_p, int encoding) + int query_loc, int *query_len_p) { char *norm_query; int query_len = *query_len_p; diff --git a/contrib/pg_surgery/.gitignore b/contrib/pg_surgery/.gitignore new file mode 100644 index 000000000000..5dcb3ff97235 --- /dev/null +++ b/contrib/pg_surgery/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/pg_surgery/Makefile b/contrib/pg_surgery/Makefile new file mode 100644 index 000000000000..a66776c4c413 --- /dev/null +++ b/contrib/pg_surgery/Makefile @@ -0,0 +1,23 @@ +# contrib/pg_surgery/Makefile + +MODULE_big = pg_surgery +OBJS = \ + $(WIN32RES) \ + heap_surgery.o + +EXTENSION = pg_surgery +DATA = pg_surgery--1.0.sql +PGFILEDESC = "pg_surgery - perform surgery on a damaged relation" + +REGRESS = heap_surgery + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/pg_surgery +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pg_surgery/expected/heap_surgery.out b/contrib/pg_surgery/expected/heap_surgery.out new file mode 100644 index 000000000000..d4a757ffa014 --- /dev/null +++ b/contrib/pg_surgery/expected/heap_surgery.out @@ -0,0 +1,178 @@ +create extension pg_surgery; +-- create a normal heap table and insert some rows. +-- use a temp table so that vacuum behavior doesn't depend on global xmin +create temp table htab (a int); +insert into htab values (100), (200), (300), (400), (500); +-- test empty TID array +select heap_force_freeze('htab'::regclass, ARRAY[]::tid[]); + heap_force_freeze +------------------- + +(1 row) + +-- nothing should be frozen yet +select * from htab where xmin = 2; + a +--- +(0 rows) + +-- freeze forcibly +select heap_force_freeze('htab'::regclass, ARRAY['(0, 4)']::tid[]); + heap_force_freeze +------------------- + +(1 row) + +-- now we should have one frozen tuple +select ctid, xmax from htab where xmin = 2; + ctid | xmax +-------+------ + (0,4) | 0 +(1 row) + +-- kill forcibly +select heap_force_kill('htab'::regclass, ARRAY['(0, 4)']::tid[]); + heap_force_kill +----------------- + +(1 row) + +-- should be gone now +select * from htab where ctid = '(0, 4)'; + a +--- +(0 rows) + +-- should now be skipped because it's already dead +select heap_force_kill('htab'::regclass, ARRAY['(0, 4)']::tid[]); +NOTICE: skipping tid (0, 4) for relation "htab" because it is marked dead + heap_force_kill +----------------- + +(1 row) + +select heap_force_freeze('htab'::regclass, ARRAY['(0, 4)']::tid[]); +NOTICE: skipping tid (0, 4) for relation "htab" because it is marked dead + heap_force_freeze +------------------- + +(1 row) + +-- freeze two TIDs at once while skipping an out-of-range block number +select heap_force_freeze('htab'::regclass, + ARRAY['(0, 1)', '(0, 3)', '(1, 1)']::tid[]); +NOTICE: skipping block 1 for relation "htab" because the block number is out of range + heap_force_freeze +------------------- + +(1 row) + +-- we should now have two frozen tuples +select ctid, xmax from htab where xmin = 2; + ctid | xmax +-------+------ + (0,1) | 0 + (0,3) | 0 +(2 rows) + +-- out-of-range TIDs should be skipped +select heap_force_freeze('htab'::regclass, ARRAY['(0, 0)', '(0, 6)']::tid[]); +NOTICE: skipping tid (0, 0) for relation "htab" because the item number is out of range +NOTICE: skipping tid (0, 6) for relation "htab" because the item number is out of range + heap_force_freeze +------------------- + +(1 row) + +-- set up a new table with a redirected line pointer +-- use a temp table so that vacuum behavior doesn't depend on global xmin +create temp table htab2(a int); +insert into htab2 values (100); +update htab2 set a = 200; +vacuum htab2; +-- redirected TIDs should be skipped +select heap_force_kill('htab2'::regclass, ARRAY['(0, 1)']::tid[]); +NOTICE: skipping tid (0, 1) for relation "htab2" because it redirects to item 2 + heap_force_kill +----------------- + +(1 row) + +-- now create an unused line pointer +select ctid from htab2; + ctid +------- + (0,2) +(1 row) + +update htab2 set a = 300; +select ctid from htab2; + ctid +------- + (0,3) +(1 row) + +vacuum freeze htab2; +-- unused TIDs should be skipped +select heap_force_kill('htab2'::regclass, ARRAY['(0, 2)']::tid[]); +NOTICE: skipping tid (0, 2) for relation "htab2" because it is marked unused + heap_force_kill +----------------- + +(1 row) + +-- multidimensional TID array should be rejected +select heap_force_kill('htab2'::regclass, ARRAY[['(0, 2)']]::tid[]); +ERROR: argument must be empty or one-dimensional array +-- TID array with nulls should be rejected +select heap_force_kill('htab2'::regclass, ARRAY[NULL]::tid[]); +ERROR: array must not contain nulls +-- but we should be able to kill the one tuple we have +select heap_force_kill('htab2'::regclass, ARRAY['(0, 3)']::tid[]); + heap_force_kill +----------------- + +(1 row) + +-- materialized view. +-- note that we don't commit the transaction, so autovacuum can't interfere. +begin; +create materialized view mvw as select a from generate_series(1, 3) a; +select * from mvw where xmin = 2; + a +--- +(0 rows) + +select heap_force_freeze('mvw'::regclass, ARRAY['(0, 3)']::tid[]); + heap_force_freeze +------------------- + +(1 row) + +select * from mvw where xmin = 2; + a +--- + 3 +(1 row) + +select heap_force_kill('mvw'::regclass, ARRAY['(0, 3)']::tid[]); + heap_force_kill +----------------- + +(1 row) + +select * from mvw where ctid = '(0, 3)'; + a +--- +(0 rows) + +rollback; +-- check that it fails on an unsupported relkind +create view vw as select 1; +select heap_force_kill('vw'::regclass, ARRAY['(0, 1)']::tid[]); +ERROR: "vw" is not a table, materialized view, or TOAST table +select heap_force_freeze('vw'::regclass, ARRAY['(0, 1)']::tid[]); +ERROR: "vw" is not a table, materialized view, or TOAST table +-- cleanup. +drop view vw; +drop extension pg_surgery; diff --git a/contrib/pg_surgery/heap_surgery.c b/contrib/pg_surgery/heap_surgery.c new file mode 100644 index 000000000000..eb96b4bb36d8 --- /dev/null +++ b/contrib/pg_surgery/heap_surgery.c @@ -0,0 +1,428 @@ +/*------------------------------------------------------------------------- + * + * heap_surgery.c + * Functions to perform surgery on the damaged heap table. + * + * Copyright (c) 2020, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pg_surgery/heap_surgery.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/heapam.h" +#include "access/visibilitymap.h" +#include "catalog/pg_am_d.h" +#include "catalog/pg_proc_d.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "utils/acl.h" +#include "utils/rel.h" + +PG_MODULE_MAGIC; + +/* Options to forcefully change the state of a heap tuple. */ +typedef enum HeapTupleForceOption +{ + HEAP_FORCE_KILL, + HEAP_FORCE_FREEZE +} HeapTupleForceOption; + +PG_FUNCTION_INFO_V1(heap_force_kill); +PG_FUNCTION_INFO_V1(heap_force_freeze); + +static int32 tidcmp(const void *a, const void *b); +static Datum heap_force_common(FunctionCallInfo fcinfo, + HeapTupleForceOption heap_force_opt); +static void sanity_check_tid_array(ArrayType *ta, int *ntids); +static void sanity_check_relation(Relation rel); +static BlockNumber find_tids_one_page(ItemPointer tids, int ntids, + OffsetNumber *next_start_ptr); + +/*------------------------------------------------------------------------- + * heap_force_kill() + * + * Force kill the tuple(s) pointed to by the item pointer(s) stored in the + * given TID array. + * + * Usage: SELECT heap_force_kill(regclass, tid[]); + *------------------------------------------------------------------------- + */ +Datum +heap_force_kill(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(heap_force_common(fcinfo, HEAP_FORCE_KILL)); +} + +/*------------------------------------------------------------------------- + * heap_force_freeze() + * + * Force freeze the tuple(s) pointed to by the item pointer(s) stored in the + * given TID array. + * + * Usage: SELECT heap_force_freeze(regclass, tid[]); + *------------------------------------------------------------------------- + */ +Datum +heap_force_freeze(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(heap_force_common(fcinfo, HEAP_FORCE_FREEZE)); +} + +/*------------------------------------------------------------------------- + * heap_force_common() + * + * Common code for heap_force_kill and heap_force_freeze + *------------------------------------------------------------------------- + */ +static Datum +heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt) +{ + Oid relid = PG_GETARG_OID(0); + ArrayType *ta = PG_GETARG_ARRAYTYPE_P_COPY(1); + ItemPointer tids; + int ntids, + nblocks; + Relation rel; + OffsetNumber curr_start_ptr, + next_start_ptr; + bool include_this_tid[MaxHeapTuplesPerPage]; + + if (RecoveryInProgress()) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("recovery is in progress"), + errhint("heap surgery functions cannot be executed during recovery."))); + + /* Check inputs. */ + sanity_check_tid_array(ta, &ntids); + + rel = relation_open(relid, RowExclusiveLock); + + /* Check target relation. */ + sanity_check_relation(rel); + + tids = ((ItemPointer) ARR_DATA_PTR(ta)); + + /* + * If there is more than one TID in the array, sort them so that we can + * easily fetch all the TIDs belonging to one particular page from the + * array. + */ + if (ntids > 1) + qsort((void *) tids, ntids, sizeof(ItemPointerData), tidcmp); + + curr_start_ptr = next_start_ptr = 0; + nblocks = RelationGetNumberOfBlocks(rel); + + /* + * Loop, performing the necessary actions for each block. + */ + while (next_start_ptr != ntids) + { + Buffer buf; + Buffer vmbuf = InvalidBuffer; + Page page; + BlockNumber blkno; + OffsetNumber curoff; + OffsetNumber maxoffset; + int i; + bool did_modify_page = false; + bool did_modify_vm = false; + + CHECK_FOR_INTERRUPTS(); + + /* + * Find all the TIDs belonging to one particular page starting from + * next_start_ptr and process them one by one. + */ + blkno = find_tids_one_page(tids, ntids, &next_start_ptr); + + /* Check whether the block number is valid. */ + if (blkno >= nblocks) + { + /* Update the current_start_ptr before moving to the next page. */ + curr_start_ptr = next_start_ptr; + + ereport(NOTICE, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("skipping block %u for relation \"%s\" because the block number is out of range", + blkno, RelationGetRelationName(rel)))); + continue; + } + + buf = ReadBuffer(rel, blkno); + LockBufferForCleanup(buf); + + page = BufferGetPage(buf); + + maxoffset = PageGetMaxOffsetNumber(page); + + /* + * Figure out which TIDs we are going to process and which ones we are + * going to skip. + */ + memset(include_this_tid, 0, sizeof(include_this_tid)); + for (i = curr_start_ptr; i < next_start_ptr; i++) + { + OffsetNumber offno = ItemPointerGetOffsetNumberNoCheck(&tids[i]); + ItemId itemid; + + /* Check whether the offset number is valid. */ + if (offno == InvalidOffsetNumber || offno > maxoffset) + { + ereport(NOTICE, + errmsg("skipping tid (%u, %u) for relation \"%s\" because the item number is out of range", + blkno, offno, RelationGetRelationName(rel))); + continue; + } + + itemid = PageGetItemId(page, offno); + + /* Only accept an item ID that is used. */ + if (ItemIdIsRedirected(itemid)) + { + ereport(NOTICE, + errmsg("skipping tid (%u, %u) for relation \"%s\" because it redirects to item %u", + blkno, offno, RelationGetRelationName(rel), + ItemIdGetRedirect(itemid))); + continue; + } + else if (ItemIdIsDead(itemid)) + { + ereport(NOTICE, + (errmsg("skipping tid (%u, %u) for relation \"%s\" because it is marked dead", + blkno, offno, RelationGetRelationName(rel)))); + continue; + } + else if (!ItemIdIsUsed(itemid)) + { + ereport(NOTICE, + (errmsg("skipping tid (%u, %u) for relation \"%s\" because it is marked unused", + blkno, offno, RelationGetRelationName(rel)))); + continue; + } + + /* Mark it for processing. */ + Assert(offno < MaxHeapTuplesPerPage); + include_this_tid[offno] = true; + } + + /* + * Before entering the critical section, pin the visibility map page + * if it appears to be necessary. + */ + if (heap_force_opt == HEAP_FORCE_KILL && PageIsAllVisible(page)) + visibilitymap_pin(rel, blkno, &vmbuf); + + /* No ereport(ERROR) from here until all the changes are logged. */ + START_CRIT_SECTION(); + + for (curoff = FirstOffsetNumber; curoff <= maxoffset; + curoff = OffsetNumberNext(curoff)) + { + ItemId itemid; + + if (!include_this_tid[curoff]) + continue; + + itemid = PageGetItemId(page, curoff); + Assert(ItemIdIsNormal(itemid)); + + did_modify_page = true; + + if (heap_force_opt == HEAP_FORCE_KILL) + { + ItemIdSetDead(itemid); + + /* + * If the page is marked all-visible, we must clear + * PD_ALL_VISIBLE flag on the page header and an all-visible + * bit on the visibility map corresponding to the page. + */ + if (PageIsAllVisible(page)) + { + PageClearAllVisible(page); + visibilitymap_clear(rel, blkno, vmbuf, + VISIBILITYMAP_VALID_BITS); + did_modify_vm = true; + } + } + else + { + HeapTupleHeader htup; + + Assert(heap_force_opt == HEAP_FORCE_FREEZE); + + htup = (HeapTupleHeader) PageGetItem(page, itemid); + + /* + * Reset all visibility-related fields of the tuple. This + * logic should mimic heap_execute_freeze_tuple(), but we + * choose to reset xmin and ctid just to be sure that no + * potentially-garbled data is left behind. + */ + ItemPointerSet(&htup->t_ctid, blkno, curoff); + HeapTupleHeaderSetXmin(htup, FrozenTransactionId); + HeapTupleHeaderSetXmax(htup, InvalidTransactionId); + if (htup->t_infomask & HEAP_MOVED) + { + if (htup->t_infomask & HEAP_MOVED_OFF) + HeapTupleHeaderSetXvac(htup, InvalidTransactionId); + else + HeapTupleHeaderSetXvac(htup, FrozenTransactionId); + } + + /* + * Clear all the visibility-related bits of this tuple and + * mark it as frozen. Also, get rid of HOT_UPDATED and + * KEYS_UPDATES bits. + */ + htup->t_infomask &= ~HEAP_XACT_MASK; + htup->t_infomask |= (HEAP_XMIN_FROZEN | HEAP_XMAX_INVALID); + htup->t_infomask2 &= ~HEAP_HOT_UPDATED; + htup->t_infomask2 &= ~HEAP_KEYS_UPDATED; + } + } + + /* + * If the page was modified, only then, we mark the buffer dirty or do + * the WAL logging. + */ + if (did_modify_page) + { + /* Mark buffer dirty before we write WAL. */ + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + log_newpage_buffer(buf, true); + } + + /* WAL log the VM page if it was modified. */ + if (did_modify_vm && RelationNeedsWAL(rel)) + log_newpage_buffer(vmbuf, false); + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + if (vmbuf != InvalidBuffer) + ReleaseBuffer(vmbuf); + + /* Update the current_start_ptr before moving to the next page. */ + curr_start_ptr = next_start_ptr; + } + + relation_close(rel, RowExclusiveLock); + + pfree(ta); + + PG_RETURN_VOID(); +} + +/*------------------------------------------------------------------------- + * tidcmp() + * + * Compare two item pointers, return -1, 0, or +1. + * + * See ItemPointerCompare for details. + * ------------------------------------------------------------------------ + */ +static int32 +tidcmp(const void *a, const void *b) +{ + ItemPointer iptr1 = ((const ItemPointer) a); + ItemPointer iptr2 = ((const ItemPointer) b); + + return ItemPointerCompare(iptr1, iptr2); +} + +/*------------------------------------------------------------------------- + * sanity_check_tid_array() + * + * Perform sanity checks on the given tid array, and set *ntids to the + * number of items in the array. + * ------------------------------------------------------------------------ + */ +static void +sanity_check_tid_array(ArrayType *ta, int *ntids) +{ + if (ARR_HASNULL(ta) && array_contains_nulls(ta)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array must not contain nulls"))); + + if (ARR_NDIM(ta) > 1) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("argument must be empty or one-dimensional array"))); + + *ntids = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta)); +} + +/*------------------------------------------------------------------------- + * sanity_check_relation() + * + * Perform sanity checks on the given relation. + * ------------------------------------------------------------------------ + */ +static void +sanity_check_relation(Relation rel) +{ + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_MATVIEW && + rel->rd_rel->relkind != RELKIND_TOASTVALUE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table, materialized view, or TOAST table", + RelationGetRelationName(rel)))); + + if (rel->rd_rel->relam != HEAP_TABLE_AM_OID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only heap AM is supported"))); + + /* Must be owner of the table or superuser. */ + if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, + get_relkind_objtype(rel->rd_rel->relkind), + RelationGetRelationName(rel)); +} + +/*------------------------------------------------------------------------- + * find_tids_one_page() + * + * Find all the tids residing in the same page as tids[next_start_ptr], and + * update next_start_ptr so that it points to the first tid in the next page. + * + * NOTE: The input tids[] array must be sorted. + * ------------------------------------------------------------------------ + */ +static BlockNumber +find_tids_one_page(ItemPointer tids, int ntids, OffsetNumber *next_start_ptr) +{ + int i; + BlockNumber prev_blkno, + blkno; + + prev_blkno = blkno = InvalidBlockNumber; + + for (i = *next_start_ptr; i < ntids; i++) + { + ItemPointerData tid = tids[i]; + + blkno = ItemPointerGetBlockNumberNoCheck(&tid); + + if (i == *next_start_ptr) + prev_blkno = blkno; + + if (prev_blkno != blkno) + break; + } + + *next_start_ptr = i; + return prev_blkno; +} diff --git a/contrib/pg_surgery/pg_surgery--1.0.sql b/contrib/pg_surgery/pg_surgery--1.0.sql new file mode 100644 index 000000000000..2ae7f228c74b --- /dev/null +++ b/contrib/pg_surgery/pg_surgery--1.0.sql @@ -0,0 +1,18 @@ +/* contrib/pg_surgery/pg_surgery--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_surgery" to load this file. \quit + +CREATE FUNCTION heap_force_kill(reloid regclass, tids tid[]) +RETURNS VOID +AS 'MODULE_PATHNAME', 'heap_force_kill' +LANGUAGE C STRICT; + +REVOKE EXECUTE ON FUNCTION heap_force_kill(regclass, tid[]) FROM PUBLIC; + +CREATE FUNCTION heap_force_freeze(reloid regclass, tids tid[]) +RETURNS VOID +AS 'MODULE_PATHNAME', 'heap_force_freeze' +LANGUAGE C STRICT; + +REVOKE EXECUTE ON FUNCTION heap_force_freeze(regclass, tid[]) FROM PUBLIC; \ No newline at end of file diff --git a/contrib/pg_surgery/pg_surgery.control b/contrib/pg_surgery/pg_surgery.control new file mode 100644 index 000000000000..2bcdad1e3f7f --- /dev/null +++ b/contrib/pg_surgery/pg_surgery.control @@ -0,0 +1,5 @@ +# pg_surgery extension +comment = 'extension to perform surgery on a damaged relation' +default_version = '1.0' +module_pathname = '$libdir/pg_surgery' +relocatable = true diff --git a/contrib/pg_surgery/sql/heap_surgery.sql b/contrib/pg_surgery/sql/heap_surgery.sql new file mode 100644 index 000000000000..6526b27535de --- /dev/null +++ b/contrib/pg_surgery/sql/heap_surgery.sql @@ -0,0 +1,88 @@ +create extension pg_surgery; + +-- create a normal heap table and insert some rows. +-- use a temp table so that vacuum behavior doesn't depend on global xmin +create temp table htab (a int); +insert into htab values (100), (200), (300), (400), (500); + +-- test empty TID array +select heap_force_freeze('htab'::regclass, ARRAY[]::tid[]); + +-- nothing should be frozen yet +select * from htab where xmin = 2; + +-- freeze forcibly +select heap_force_freeze('htab'::regclass, ARRAY['(0, 4)']::tid[]); + +-- now we should have one frozen tuple +select ctid, xmax from htab where xmin = 2; + +-- kill forcibly +select heap_force_kill('htab'::regclass, ARRAY['(0, 4)']::tid[]); + +-- should be gone now +select * from htab where ctid = '(0, 4)'; + +-- should now be skipped because it's already dead +select heap_force_kill('htab'::regclass, ARRAY['(0, 4)']::tid[]); +select heap_force_freeze('htab'::regclass, ARRAY['(0, 4)']::tid[]); + +-- freeze two TIDs at once while skipping an out-of-range block number +select heap_force_freeze('htab'::regclass, + ARRAY['(0, 1)', '(0, 3)', '(1, 1)']::tid[]); + +-- we should now have two frozen tuples +select ctid, xmax from htab where xmin = 2; + +-- out-of-range TIDs should be skipped +select heap_force_freeze('htab'::regclass, ARRAY['(0, 0)', '(0, 6)']::tid[]); + +-- set up a new table with a redirected line pointer +-- use a temp table so that vacuum behavior doesn't depend on global xmin +create temp table htab2(a int); +insert into htab2 values (100); +update htab2 set a = 200; +vacuum htab2; + +-- redirected TIDs should be skipped +select heap_force_kill('htab2'::regclass, ARRAY['(0, 1)']::tid[]); + +-- now create an unused line pointer +select ctid from htab2; +update htab2 set a = 300; +select ctid from htab2; +vacuum freeze htab2; + +-- unused TIDs should be skipped +select heap_force_kill('htab2'::regclass, ARRAY['(0, 2)']::tid[]); + +-- multidimensional TID array should be rejected +select heap_force_kill('htab2'::regclass, ARRAY[['(0, 2)']]::tid[]); + +-- TID array with nulls should be rejected +select heap_force_kill('htab2'::regclass, ARRAY[NULL]::tid[]); + +-- but we should be able to kill the one tuple we have +select heap_force_kill('htab2'::regclass, ARRAY['(0, 3)']::tid[]); + +-- materialized view. +-- note that we don't commit the transaction, so autovacuum can't interfere. +begin; +create materialized view mvw as select a from generate_series(1, 3) a; + +select * from mvw where xmin = 2; +select heap_force_freeze('mvw'::regclass, ARRAY['(0, 3)']::tid[]); +select * from mvw where xmin = 2; + +select heap_force_kill('mvw'::regclass, ARRAY['(0, 3)']::tid[]); +select * from mvw where ctid = '(0, 3)'; +rollback; + +-- check that it fails on an unsupported relkind +create view vw as select 1; +select heap_force_kill('vw'::regclass, ARRAY['(0, 1)']::tid[]); +select heap_force_freeze('vw'::regclass, ARRAY['(0, 1)']::tid[]); + +-- cleanup. +drop view vw; +drop extension pg_surgery; diff --git a/contrib/pgcrypto/crypt-md5.c b/contrib/pgcrypto/crypt-md5.c index b6466d3e3178..d38721a1010a 100644 --- a/contrib/pgcrypto/crypt-md5.c +++ b/contrib/pgcrypto/crypt-md5.c @@ -65,11 +65,17 @@ px_crypt_md5(const char *pw, const char *salt, char *passwd, unsigned dstlen) /* get the length of the true salt */ sl = ep - sp; - /* */ + /* we need two PX_MD objects */ err = px_find_digest("md5", &ctx); if (err) return NULL; err = px_find_digest("md5", &ctx1); + if (err) + { + /* this path is possible under low-memory circumstances */ + px_md_free(ctx); + return NULL; + } /* The password first, since that is what is most unknown */ px_md_update(ctx, (const uint8 *) pw, strlen(pw)); diff --git a/contrib/pgcrypto/imath.c b/contrib/pgcrypto/imath.c index bc1a5659a913..70319540cb1b 100644 --- a/contrib/pgcrypto/imath.c +++ b/contrib/pgcrypto/imath.c @@ -478,7 +478,7 @@ mp_int_init(mp_int z) mp_int mp_int_alloc(void) { - mp_int out = px_alloc(sizeof(mpz_t)); + mp_int out = palloc(sizeof(mpz_t)); if (out != NULL) mp_int_init(out); @@ -604,7 +604,7 @@ mp_int_free(mp_int z) assert(z != NULL); mp_int_clear(z); - px_free(z); /* note: NOT s_free() */ + pfree(z); /* note: NOT s_free() */ } mp_result @@ -2205,7 +2205,7 @@ static const mp_digit fill = (mp_digit) 0xdeadbeefabad1dea; static mp_digit * s_alloc(mp_size num) { - mp_digit *out = px_alloc(num * sizeof(mp_digit)); + mp_digit *out = palloc(num * sizeof(mp_digit)); assert(out != NULL); @@ -2228,7 +2228,7 @@ s_realloc(mp_digit *old, mp_size osize, mp_size nsize) new[ix] = fill; memcpy(new, old, osize * sizeof(mp_digit)); #else - mp_digit *new = px_realloc(old, nsize * sizeof(mp_digit)); + mp_digit *new = repalloc(old, nsize * sizeof(mp_digit)); assert(new != NULL); #endif @@ -2239,7 +2239,7 @@ s_realloc(mp_digit *old, mp_size osize, mp_size nsize) static void s_free(void *ptr) { - px_free(ptr); + pfree(ptr); } static bool diff --git a/contrib/pgcrypto/internal-sha2.c b/contrib/pgcrypto/internal-sha2.c index e06f55445eff..9fa940b5bbbb 100644 --- a/contrib/pgcrypto/internal-sha2.c +++ b/contrib/pgcrypto/internal-sha2.c @@ -85,8 +85,8 @@ int_sha224_free(PX_MD *h) pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr; px_memset(ctx, 0, sizeof(*ctx)); - px_free(ctx); - px_free(h); + pfree(ctx); + pfree(h); } /* SHA256 */ @@ -133,8 +133,8 @@ int_sha256_free(PX_MD *h) pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr; px_memset(ctx, 0, sizeof(*ctx)); - px_free(ctx); - px_free(h); + pfree(ctx); + pfree(h); } /* SHA384 */ @@ -181,8 +181,8 @@ int_sha384_free(PX_MD *h) pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr; px_memset(ctx, 0, sizeof(*ctx)); - px_free(ctx); - px_free(h); + pfree(ctx); + pfree(h); } /* SHA512 */ @@ -229,8 +229,8 @@ int_sha512_free(PX_MD *h) pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr; px_memset(ctx, 0, sizeof(*ctx)); - px_free(ctx); - px_free(h); + pfree(ctx); + pfree(h); } /* init functions */ @@ -240,8 +240,7 @@ init_sha224(PX_MD *md) { pg_sha224_ctx *ctx; - ctx = px_alloc(sizeof(*ctx)); - memset(ctx, 0, sizeof(*ctx)); + ctx = palloc0(sizeof(*ctx)); md->p.ptr = ctx; @@ -260,8 +259,7 @@ init_sha256(PX_MD *md) { pg_sha256_ctx *ctx; - ctx = px_alloc(sizeof(*ctx)); - memset(ctx, 0, sizeof(*ctx)); + ctx = palloc0(sizeof(*ctx)); md->p.ptr = ctx; @@ -280,8 +278,7 @@ init_sha384(PX_MD *md) { pg_sha384_ctx *ctx; - ctx = px_alloc(sizeof(*ctx)); - memset(ctx, 0, sizeof(*ctx)); + ctx = palloc0(sizeof(*ctx)); md->p.ptr = ctx; @@ -300,8 +297,7 @@ init_sha512(PX_MD *md) { pg_sha512_ctx *ctx; - ctx = px_alloc(sizeof(*ctx)); - memset(ctx, 0, sizeof(*ctx)); + ctx = palloc0(sizeof(*ctx)); md->p.ptr = ctx; diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c index f56984d5e226..198d2b570f88 100644 --- a/contrib/pgcrypto/internal.c +++ b/contrib/pgcrypto/internal.c @@ -123,8 +123,8 @@ int_md5_free(PX_MD *h) MD5_CTX *ctx = (MD5_CTX *) h->p.ptr; px_memset(ctx, 0, sizeof(*ctx)); - px_free(ctx); - px_free(h); + pfree(ctx); + pfree(h); } /* SHA1 */ @@ -171,8 +171,8 @@ int_sha1_free(PX_MD *h) SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr; px_memset(ctx, 0, sizeof(*ctx)); - px_free(ctx); - px_free(h); + pfree(ctx); + pfree(h); } /* init functions */ @@ -182,8 +182,7 @@ init_md5(PX_MD *md) { MD5_CTX *ctx; - ctx = px_alloc(sizeof(*ctx)); - memset(ctx, 0, sizeof(*ctx)); + ctx = palloc0(sizeof(*ctx)); md->p.ptr = ctx; @@ -202,8 +201,7 @@ init_sha1(PX_MD *md) { SHA1_CTX *ctx; - ctx = px_alloc(sizeof(*ctx)); - memset(ctx, 0, sizeof(*ctx)); + ctx = palloc0(sizeof(*ctx)); md->p.ptr = ctx; @@ -246,9 +244,9 @@ intctx_free(PX_Cipher *c) if (cx) { px_memset(cx, 0, sizeof *cx); - px_free(cx); + pfree(cx); } - px_free(c); + pfree(c); } /* @@ -373,8 +371,7 @@ rj_load(int mode) PX_Cipher *c; struct int_ctx *cx; - c = px_alloc(sizeof *c); - memset(c, 0, sizeof *c); + c = palloc0(sizeof *c); c->block_size = rj_block_size; c->key_size = rj_key_size; @@ -384,8 +381,7 @@ rj_load(int mode) c->decrypt = rj_decrypt; c->free = intctx_free; - cx = px_alloc(sizeof *cx); - memset(cx, 0, sizeof *cx); + cx = palloc0(sizeof *cx); cx->mode = mode; c->ptr = cx; @@ -482,8 +478,7 @@ bf_load(int mode) PX_Cipher *c; struct int_ctx *cx; - c = px_alloc(sizeof *c); - memset(c, 0, sizeof *c); + c = palloc0(sizeof *c); c->block_size = bf_block_size; c->key_size = bf_key_size; @@ -493,8 +488,7 @@ bf_load(int mode) c->decrypt = bf_decrypt; c->free = intctx_free; - cx = px_alloc(sizeof *cx); - memset(cx, 0, sizeof *cx); + cx = palloc0(sizeof *cx); cx->mode = mode; c->ptr = cx; return c; @@ -564,7 +558,7 @@ px_find_digest(const char *name, PX_MD **res) for (p = int_digest_list; p->name; p++) if (pg_strcasecmp(p->name, name) == 0) { - h = px_alloc(sizeof(*h)); + h = palloc(sizeof(*h)); p->init(h); *res = h; diff --git a/contrib/pgcrypto/mbuf.c b/contrib/pgcrypto/mbuf.c index 548ef6209745..bc668a0e802f 100644 --- a/contrib/pgcrypto/mbuf.c +++ b/contrib/pgcrypto/mbuf.c @@ -70,9 +70,9 @@ mbuf_free(MBuf *mbuf) if (mbuf->own_data) { px_memset(mbuf->data, 0, mbuf->buf_end - mbuf->data); - px_free(mbuf->data); + pfree(mbuf->data); } - px_free(mbuf); + pfree(mbuf); return 0; } @@ -88,7 +88,7 @@ prepare_room(MBuf *mbuf, int block_len) newlen = (mbuf->buf_end - mbuf->data) + ((block_len + STEP + STEP - 1) & -STEP); - newbuf = px_realloc(mbuf->data, newlen); + newbuf = repalloc(mbuf->data, newlen); mbuf->buf_end = newbuf + newlen; mbuf->data_end = newbuf + (mbuf->data_end - mbuf->data); @@ -121,8 +121,8 @@ mbuf_create(int len) if (!len) len = 8192; - mbuf = px_alloc(sizeof *mbuf); - mbuf->data = px_alloc(len); + mbuf = palloc(sizeof *mbuf); + mbuf->data = palloc(len); mbuf->buf_end = mbuf->data + len; mbuf->data_end = mbuf->data; mbuf->read_pos = mbuf->data; @@ -138,7 +138,7 @@ mbuf_create_from_data(uint8 *data, int len) { MBuf *mbuf; - mbuf = px_alloc(sizeof *mbuf); + mbuf = palloc(sizeof *mbuf); mbuf->data = (uint8 *) data; mbuf->buf_end = mbuf->data + len; mbuf->data_end = mbuf->data + len; @@ -219,15 +219,14 @@ pullf_create(PullFilter **pf_p, const PullFilterOps *op, void *init_arg, PullFil res = 0; } - pf = px_alloc(sizeof(*pf)); - memset(pf, 0, sizeof(*pf)); + pf = palloc0(sizeof(*pf)); pf->buflen = res; pf->op = op; pf->priv = priv; pf->src = src; if (pf->buflen > 0) { - pf->buf = px_alloc(pf->buflen); + pf->buf = palloc(pf->buflen); pf->pos = 0; } else @@ -248,11 +247,11 @@ pullf_free(PullFilter *pf) if (pf->buf) { px_memset(pf->buf, 0, pf->buflen); - px_free(pf->buf); + pfree(pf->buf); } px_memset(pf, 0, sizeof(*pf)); - px_free(pf); + pfree(pf); } /* may return less data than asked, 0 means eof */ @@ -386,15 +385,14 @@ pushf_create(PushFilter **mp_p, const PushFilterOps *op, void *init_arg, PushFil res = 0; } - mp = px_alloc(sizeof(*mp)); - memset(mp, 0, sizeof(*mp)); + mp = palloc0(sizeof(*mp)); mp->block_size = res; mp->op = op; mp->priv = priv; mp->next = next; if (mp->block_size > 0) { - mp->buf = px_alloc(mp->block_size); + mp->buf = palloc(mp->block_size); mp->pos = 0; } else @@ -415,11 +413,11 @@ pushf_free(PushFilter *mp) if (mp->buf) { px_memset(mp->buf, 0, mp->block_size); - px_free(mp->buf); + pfree(mp->buf); } px_memset(mp, 0, sizeof(*mp)); - px_free(mp); + pfree(mp); } void diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c index 023d78d5c07e..d66a00cdcc35 100644 --- a/contrib/pgcrypto/openssl.c +++ b/contrib/pgcrypto/openssl.c @@ -180,7 +180,7 @@ digest_free(PX_MD *h) OSSLDigest *digest = (OSSLDigest *) h->p.ptr; free_openssl_digest(digest); - px_free(h); + pfree(h); } static int px_openssl_initialized = 0; @@ -226,6 +226,7 @@ px_find_digest(const char *name, PX_MD **res) } if (EVP_DigestInit_ex(ctx, md, NULL) == 0) { + EVP_MD_CTX_destroy(ctx); pfree(digest); return -1; } @@ -238,7 +239,7 @@ px_find_digest(const char *name, PX_MD **res) open_digests = digest; /* The PX_MD object is allocated in the current memory context. */ - h = px_alloc(sizeof(*h)); + h = palloc(sizeof(*h)); h->result_size = digest_result_size; h->block_size = digest_block_size; h->reset = digest_reset; @@ -377,7 +378,7 @@ gen_ossl_free(PX_Cipher *c) OSSLCipher *od = (OSSLCipher *) c->ptr; free_openssl_cipher(od); - px_free(c); + pfree(c); } static int @@ -427,7 +428,7 @@ gen_ossl_encrypt(PX_Cipher *c, const uint8 *data, unsigned dlen, } if (!EVP_EncryptUpdate(od->evp_ctx, res, &outlen, data, dlen)) - return PXE_ERR_GENERIC; + return PXE_ENCRYPT_FAILED; return 0; } @@ -824,7 +825,7 @@ px_find_cipher(const char *name, PX_Cipher **res) od->evp_ciph = i->ciph->cipher_func(); /* The PX_Cipher is allocated in current memory context */ - c = px_alloc(sizeof(*c)); + c = palloc(sizeof(*c)); c->block_size = gen_ossl_block_size; c->key_size = gen_ossl_key_size; c->iv_size = gen_ossl_iv_size; diff --git a/contrib/pgcrypto/pgp-cfb.c b/contrib/pgcrypto/pgp-cfb.c index 8ae7c8608fb5..dafa562daa12 100644 --- a/contrib/pgcrypto/pgp-cfb.c +++ b/contrib/pgcrypto/pgp-cfb.c @@ -67,8 +67,7 @@ pgp_cfb_create(PGP_CFB **ctx_p, int algo, const uint8 *key, int key_len, return res; } - ctx = px_alloc(sizeof(*ctx)); - memset(ctx, 0, sizeof(*ctx)); + ctx = palloc0(sizeof(*ctx)); ctx->ciph = ciph; ctx->block_size = px_cipher_block_size(ciph); ctx->resync = resync; @@ -85,7 +84,7 @@ pgp_cfb_free(PGP_CFB *ctx) { px_cipher_free(ctx->ciph); px_memset(ctx, 0, sizeof(*ctx)); - px_free(ctx); + pfree(ctx); } /* diff --git a/contrib/pgcrypto/pgp-compress.c b/contrib/pgcrypto/pgp-compress.c index 3636a662b076..086bec31ae2c 100644 --- a/contrib/pgcrypto/pgp-compress.c +++ b/contrib/pgcrypto/pgp-compress.c @@ -57,13 +57,13 @@ struct ZipStat static void * z_alloc(void *priv, unsigned n_items, unsigned item_len) { - return px_alloc(n_items * item_len); + return palloc(n_items * item_len); } static void z_free(void *priv, void *addr) { - px_free(addr); + pfree(addr); } static int @@ -80,8 +80,7 @@ compress_init(PushFilter *next, void *init_arg, void **priv_p) /* * init */ - st = px_alloc(sizeof(*st)); - memset(st, 0, sizeof(*st)); + st = palloc0(sizeof(*st)); st->buf_len = ZIP_OUT_BUF; st->stream.zalloc = z_alloc; st->stream.zfree = z_free; @@ -93,7 +92,7 @@ compress_init(PushFilter *next, void *init_arg, void **priv_p) res = deflateInit(&st->stream, ctx->compress_level); if (res != Z_OK) { - px_free(st); + pfree(st); return PXE_PGP_COMPRESSION_ERROR; } *priv_p = st; @@ -174,7 +173,7 @@ compress_free(void *priv) deflateEnd(&st->stream); px_memset(st, 0, sizeof(*st)); - px_free(st); + pfree(st); } static const PushFilterOps @@ -212,8 +211,7 @@ decompress_init(void **priv_p, void *arg, PullFilter *src) && ctx->compress_algo != PGP_COMPR_ZIP) return PXE_PGP_UNSUPPORTED_COMPR; - dec = px_alloc(sizeof(*dec)); - memset(dec, 0, sizeof(*dec)); + dec = palloc0(sizeof(*dec)); dec->buf_len = ZIP_OUT_BUF; *priv_p = dec; @@ -226,7 +224,7 @@ decompress_init(void **priv_p, void *arg, PullFilter *src) res = inflateInit(&dec->stream); if (res != Z_OK) { - px_free(dec); + pfree(dec); px_debug("decompress_init: inflateInit error"); return PXE_PGP_COMPRESSION_ERROR; } @@ -293,7 +291,7 @@ decompress_read(void *priv, PullFilter *src, int len, * A stream must be terminated by a normal packet. If the last stream * packet in the source stream is a full packet, a normal empty packet * must follow. Since the underlying packet reader doesn't know that - * the compressed stream has been ended, we need to to consume the + * the compressed stream has been ended, we need to consume the * terminating packet here. This read does not harm even if the * stream has already ended. */ @@ -318,7 +316,7 @@ decompress_free(void *priv) inflateEnd(&dec->stream); px_memset(dec, 0, sizeof(*dec)); - px_free(dec); + pfree(dec); } static const PullFilterOps diff --git a/contrib/pgcrypto/pgp-decrypt.c b/contrib/pgcrypto/pgp-decrypt.c index 3ecbf9c0c259..d12dcad19452 100644 --- a/contrib/pgcrypto/pgp-decrypt.c +++ b/contrib/pgcrypto/pgp-decrypt.c @@ -211,7 +211,7 @@ pktreader_free(void *priv) struct PktData *pkt = priv; px_memset(pkt, 0, sizeof(*pkt)); - px_free(pkt); + pfree(pkt); } static struct PullFilterOps pktreader_filter = { @@ -224,13 +224,13 @@ pgp_create_pkt_reader(PullFilter **pf_p, PullFilter *src, int len, int pkttype, PGP_Context *ctx) { int res; - struct PktData *pkt = px_alloc(sizeof(*pkt)); + struct PktData *pkt = palloc(sizeof(*pkt)); pkt->type = pkttype; pkt->len = len; res = pullf_create(pf_p, &pktreader_filter, pkt, src); if (res < 0) - px_free(pkt); + pfree(pkt); return res; } @@ -447,8 +447,7 @@ mdcbuf_init(void **priv_p, void *arg, PullFilter *src) PGP_Context *ctx = arg; struct MDCBufData *st; - st = px_alloc(sizeof(*st)); - memset(st, 0, sizeof(*st)); + st = palloc0(sizeof(*st)); st->buflen = sizeof(st->buf); st->ctx = ctx; *priv_p = st; @@ -576,7 +575,7 @@ mdcbuf_free(void *priv) px_md_free(st->ctx->mdc_ctx); st->ctx->mdc_ctx = NULL; px_memset(st, 0, sizeof(*st)); - px_free(st); + pfree(st); } static struct PullFilterOps mdcbuf_filter = { diff --git a/contrib/pgcrypto/pgp-encrypt.c b/contrib/pgcrypto/pgp-encrypt.c index 46518942ac2a..f7467c9b1cb1 100644 --- a/contrib/pgcrypto/pgp-encrypt.c +++ b/contrib/pgcrypto/pgp-encrypt.c @@ -178,8 +178,7 @@ encrypt_init(PushFilter *next, void *init_arg, void **priv_p) if (res < 0) return res; - st = px_alloc(sizeof(*st)); - memset(st, 0, sizeof(*st)); + st = palloc0(sizeof(*st)); st->ciph = ciph; *priv_p = st; @@ -219,7 +218,7 @@ encrypt_free(void *priv) if (st->ciph) pgp_cfb_free(st->ciph); px_memset(st, 0, sizeof(*st)); - px_free(st); + pfree(st); } static const PushFilterOps encrypt_filter = { @@ -241,7 +240,7 @@ pkt_stream_init(PushFilter *next, void *init_arg, void **priv_p) { struct PktStreamStat *st; - st = px_alloc(sizeof(*st)); + st = palloc(sizeof(*st)); st->final_done = 0; st->pkt_block = 1 << STREAM_BLOCK_SHIFT; *priv_p = st; @@ -301,7 +300,7 @@ pkt_stream_free(void *priv) struct PktStreamStat *st = priv; px_memset(st, 0, sizeof(*st)); - px_free(st); + pfree(st); } static const PushFilterOps pkt_stream_filter = { diff --git a/contrib/pgcrypto/pgp-mpi-internal.c b/contrib/pgcrypto/pgp-mpi-internal.c index 0cea51418058..5b94e654521b 100644 --- a/contrib/pgcrypto/pgp-mpi-internal.c +++ b/contrib/pgcrypto/pgp-mpi-internal.c @@ -60,10 +60,10 @@ mp_px_rand(uint32 bits, mpz_t *res) int last_bits = bits & 7; uint8 *buf; - buf = px_alloc(bytes); + buf = palloc(bytes); if (!pg_strong_random(buf, bytes)) { - px_free(buf); + pfree(buf); return PXE_NO_RANDOM; } @@ -78,7 +78,7 @@ mp_px_rand(uint32 bits, mpz_t *res) mp_int_read_unsigned(res, buf, bytes); - px_free(buf); + pfree(buf); return 0; } diff --git a/contrib/pgcrypto/pgp-mpi.c b/contrib/pgcrypto/pgp-mpi.c index 36a6d361ab31..03be27973bec 100644 --- a/contrib/pgcrypto/pgp-mpi.c +++ b/contrib/pgcrypto/pgp-mpi.c @@ -44,7 +44,7 @@ pgp_mpi_alloc(int bits, PGP_MPI **mpi) px_debug("pgp_mpi_alloc: unreasonable request: bits=%d", bits); return PXE_PGP_CORRUPT_DATA; } - n = px_alloc(sizeof(*n) + len); + n = palloc(sizeof(*n) + len); n->bits = bits; n->bytes = len; n->data = (uint8 *) (n) + sizeof(*n); @@ -72,7 +72,7 @@ pgp_mpi_free(PGP_MPI *mpi) if (mpi == NULL) return 0; px_memset(mpi, 0, sizeof(*mpi) + mpi->bytes); - px_free(mpi); + pfree(mpi); return 0; } diff --git a/contrib/pgcrypto/pgp-pubenc.c b/contrib/pgcrypto/pgp-pubenc.c index 9fdcf7c31c77..c254a3727506 100644 --- a/contrib/pgcrypto/pgp-pubenc.c +++ b/contrib/pgcrypto/pgp-pubenc.c @@ -46,12 +46,12 @@ pad_eme_pkcs1_v15(uint8 *data, int data_len, int res_len, uint8 **res_p) if (pad_len < 8) return PXE_BUG; - buf = px_alloc(res_len); + buf = palloc(res_len); buf[0] = 0x02; if (!pg_strong_random(buf + 1, pad_len)) { - px_free(buf); + pfree(buf); return PXE_NO_RANDOM; } @@ -64,7 +64,7 @@ pad_eme_pkcs1_v15(uint8 *data, int data_len, int res_len, uint8 **res_p) if (!pg_strong_random(p, 1)) { px_memset(buf, 0, res_len); - px_free(buf); + pfree(buf); return PXE_NO_RANDOM; } } @@ -97,7 +97,7 @@ create_secmsg(PGP_Context *ctx, PGP_MPI **msg_p, int full_bytes) /* * create "secret message" */ - secmsg = px_alloc(klen + 3); + secmsg = palloc(klen + 3); secmsg[0] = ctx->cipher_algo; memcpy(secmsg + 1, ctx->sess_key, klen); secmsg[klen + 1] = (cksum >> 8) & 0xFF; @@ -118,10 +118,10 @@ create_secmsg(PGP_Context *ctx, PGP_MPI **msg_p, int full_bytes) if (padded) { px_memset(padded, 0, full_bytes); - px_free(padded); + pfree(padded); } px_memset(secmsg, 0, klen + 3); - px_free(secmsg); + pfree(secmsg); if (res >= 0) *msg_p = m; diff --git a/contrib/pgcrypto/pgp-pubkey.c b/contrib/pgcrypto/pgp-pubkey.c index d447e5fd4fed..9a6561caf9dd 100644 --- a/contrib/pgcrypto/pgp-pubkey.c +++ b/contrib/pgcrypto/pgp-pubkey.c @@ -39,8 +39,7 @@ pgp_key_alloc(PGP_PubKey **pk_p) { PGP_PubKey *pk; - pk = px_alloc(sizeof(*pk)); - memset(pk, 0, sizeof(*pk)); + pk = palloc0(sizeof(*pk)); *pk_p = pk; return 0; } @@ -78,7 +77,7 @@ pgp_key_free(PGP_PubKey *pk) break; } px_memset(pk, 0, sizeof(*pk)); - px_free(pk); + pfree(pk); } static int diff --git a/contrib/pgcrypto/pgp.c b/contrib/pgcrypto/pgp.c index a1f76335ab4d..64292a915b6c 100644 --- a/contrib/pgcrypto/pgp.c +++ b/contrib/pgcrypto/pgp.c @@ -222,8 +222,7 @@ pgp_init(PGP_Context **ctx_p) { PGP_Context *ctx; - ctx = px_alloc(sizeof *ctx); - memset(ctx, 0, sizeof *ctx); + ctx = palloc0(sizeof *ctx); ctx->cipher_algo = def_cipher_algo; ctx->s2k_cipher_algo = def_s2k_cipher_algo; @@ -248,7 +247,7 @@ pgp_free(PGP_Context *ctx) if (ctx->pub_key) pgp_key_free(ctx->pub_key); px_memset(ctx, 0, sizeof *ctx); - px_free(ctx); + pfree(ctx); return 0; } diff --git a/contrib/pgcrypto/px-hmac.c b/contrib/pgcrypto/px-hmac.c index 06e5148f1b42..99174d265517 100644 --- a/contrib/pgcrypto/px-hmac.c +++ b/contrib/pgcrypto/px-hmac.c @@ -57,8 +57,7 @@ hmac_init(PX_HMAC *h, const uint8 *key, unsigned klen) PX_MD *md = h->md; bs = px_md_block_size(md); - keybuf = px_alloc(bs); - memset(keybuf, 0, bs); + keybuf = palloc0(bs); if (klen > bs) { @@ -76,7 +75,7 @@ hmac_init(PX_HMAC *h, const uint8 *key, unsigned klen) } px_memset(keybuf, 0, bs); - px_free(keybuf); + pfree(keybuf); px_md_update(md, h->p.ipad, bs); } @@ -108,7 +107,7 @@ hmac_finish(PX_HMAC *h, uint8 *dst) bs = px_md_block_size(md); hlen = px_md_result_size(md); - buf = px_alloc(hlen); + buf = palloc(hlen); px_md_finish(md, buf); @@ -118,7 +117,7 @@ hmac_finish(PX_HMAC *h, uint8 *dst) px_md_finish(md, dst); px_memset(buf, 0, hlen); - px_free(buf); + pfree(buf); } static void @@ -131,9 +130,9 @@ hmac_free(PX_HMAC *h) px_memset(h->p.ipad, 0, bs); px_memset(h->p.opad, 0, bs); - px_free(h->p.ipad); - px_free(h->p.opad); - px_free(h); + pfree(h->p.ipad); + pfree(h->p.opad); + pfree(h); } @@ -158,9 +157,9 @@ px_find_hmac(const char *name, PX_HMAC **res) return PXE_HASH_UNUSABLE_FOR_HMAC; } - h = px_alloc(sizeof(*h)); - h->p.ipad = px_alloc(bs); - h->p.opad = px_alloc(bs); + h = palloc(sizeof(*h)); + h->p.ipad = palloc(bs); + h->p.opad = palloc(bs); h->md = md; h->result_size = hmac_result_size; diff --git a/contrib/pgcrypto/px.c b/contrib/pgcrypto/px.c index 2c6704e25777..4205e9c3effe 100644 --- a/contrib/pgcrypto/px.c +++ b/contrib/pgcrypto/px.c @@ -58,6 +58,7 @@ static const struct error_desc px_err_list[] = { {PXE_MCRYPT_INTERNAL, "mcrypt internal error"}, {PXE_NO_RANDOM, "Failed to generate strong random bits"}, {PXE_DECRYPT_FAILED, "Decryption failed"}, + {PXE_ENCRYPT_FAILED, "Encryption failed"}, {PXE_PGP_CORRUPT_DATA, "Wrong key or corrupt data"}, {PXE_PGP_CORRUPT_ARMOR, "Corrupt ascii-armor"}, {PXE_PGP_UNSUPPORTED_COMPR, "Unsupported compression algorithm"}, @@ -196,8 +197,7 @@ combo_init(PX_Combo *cx, const uint8 *key, unsigned klen, ivs = px_cipher_iv_size(c); if (ivs > 0) { - ivbuf = px_alloc(ivs); - memset(ivbuf, 0, ivs); + ivbuf = palloc0(ivs); if (ivlen > ivs) memcpy(ivbuf, iv, ivs); else @@ -206,15 +206,15 @@ combo_init(PX_Combo *cx, const uint8 *key, unsigned klen, if (klen > ks) klen = ks; - keybuf = px_alloc(ks); + keybuf = palloc0(ks); memset(keybuf, 0, ks); memcpy(keybuf, key, klen); err = px_cipher_init(c, keybuf, klen, ivbuf); if (ivbuf) - px_free(ivbuf); - px_free(keybuf); + pfree(ivbuf); + pfree(keybuf); return err; } @@ -238,7 +238,7 @@ combo_encrypt(PX_Combo *cx, const uint8 *data, unsigned dlen, /* encrypt */ if (bs > 1) { - bbuf = px_alloc(bs * 4); + bbuf = palloc(bs * 4); bpos = dlen % bs; *rlen = dlen - bpos; memcpy(bbuf, data + *rlen, bpos); @@ -283,7 +283,7 @@ combo_encrypt(PX_Combo *cx, const uint8 *data, unsigned dlen, } out: if (bbuf) - px_free(bbuf); + pfree(bbuf); return err; } @@ -354,7 +354,7 @@ combo_free(PX_Combo *cx) if (cx->cipher) px_cipher_free(cx->cipher); px_memset(cx, 0, sizeof(*cx)); - px_free(cx); + pfree(cx); } /* PARSER */ @@ -411,17 +411,14 @@ px_find_combo(const char *name, PX_Combo **res) PX_Combo *cx; - cx = px_alloc(sizeof(*cx)); - memset(cx, 0, sizeof(*cx)); - - buf = px_alloc(strlen(name) + 1); - strcpy(buf, name); + cx = palloc0(sizeof(*cx)); + buf = pstrdup(name); err = parse_cipher_name(buf, &s_cipher, &s_pad); if (err) { - px_free(buf); - px_free(cx); + pfree(buf); + pfree(cx); return err; } @@ -448,7 +445,7 @@ px_find_combo(const char *name, PX_Combo **res) cx->decrypt_len = combo_decrypt_len; cx->free = combo_free; - px_free(buf); + pfree(buf); *res = cx; @@ -457,7 +454,7 @@ px_find_combo(const char *name, PX_Combo **res) err1: if (cx->cipher) px_cipher_free(cx->cipher); - px_free(cx); - px_free(buf); + pfree(cx); + pfree(buf); return PXE_NO_CIPHER; } diff --git a/contrib/pgcrypto/px.h b/contrib/pgcrypto/px.h index 0212ee8af61c..335d00eb1767 100644 --- a/contrib/pgcrypto/px.h +++ b/contrib/pgcrypto/px.h @@ -37,19 +37,6 @@ /* keep debug messages? */ #define PX_DEBUG -/* a way to disable palloc - * - useful if compiled into standalone - */ -#ifndef PX_OWN_ALLOC -#define px_alloc(s) palloc(s) -#define px_realloc(p, s) repalloc(p, s) -#define px_free(p) pfree(p) -#else -void *px_alloc(size_t s); -void *px_realloc(void *p, size_t s); -void px_free(void *p); -#endif - /* max salt returned */ #define PX_MAX_SALT_LEN 128 @@ -74,6 +61,7 @@ void px_free(void *p); #define PXE_MCRYPT_INTERNAL -16 #define PXE_NO_RANDOM -17 #define PXE_DECRYPT_FAILED -18 +#define PXE_ENCRYPT_FAILED -19 #define PXE_PGP_CORRUPT_DATA -100 #define PXE_PGP_CORRUPT_ARMOR -101 diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c index 868a635ef9bc..e8b39e864439 100644 --- a/contrib/pgstattuple/pgstatapprox.c +++ b/contrib/pgstattuple/pgstatapprox.c @@ -195,6 +195,9 @@ statapprox_heap(Relation rel, output_type *stat) stat->tuple_count = vac_estimate_reltuples(rel, nblocks, scanned, stat->tuple_count); + /* It's not clear if we could get -1 here, but be safe. */ + stat->tuple_count = Max(stat->tuple_count, 0); + /* * Calculate percentages if the relation has one or more pages. */ diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c index ce6e07fb565f..7e435f457e64 100644 --- a/contrib/postgres_fdw/connection.c +++ b/contrib/postgres_fdw/connection.c @@ -74,6 +74,7 @@ static unsigned int prep_stmt_number = 0; static bool xact_got_connection = false; /* prototypes of private functions */ +static void make_new_connection(ConnCacheEntry *entry, UserMapping *user); static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user); static void disconnect_pg_server(ConnCacheEntry *entry); static void check_conn_params(const char **keywords, const char **values, UserMapping *user); @@ -108,8 +109,10 @@ PGconn * GetConnection(UserMapping *user, bool will_prep_stmt) { bool found; + bool retry = false; ConnCacheEntry *entry; ConnCacheKey key; + MemoryContext ccxt = CurrentMemoryContext; /* First time through, initialize connection cache hashtable */ if (ConnectionHash == NULL) @@ -170,45 +173,85 @@ GetConnection(UserMapping *user, bool will_prep_stmt) disconnect_pg_server(entry); } - /* - * We don't check the health of cached connection here, because it would - * require some overhead. Broken connection will be detected when the - * connection is actually used. - */ - /* * If cache entry doesn't have a connection, we have to establish a new * connection. (If connect_pg_server throws an error, the cache entry * will remain in a valid empty state, ie conn == NULL.) */ if (entry->conn == NULL) + make_new_connection(entry, user); + + /* + * We check the health of the cached connection here when starting a new + * remote transaction. If a broken connection is detected, we try to + * reestablish a new connection later. + */ + PG_TRY(); { - ForeignServer *server = GetForeignServer(user->serverid); + /* Start a new transaction or subtransaction if needed. */ + begin_remote_xact(entry); + } + PG_CATCH(); + { + MemoryContext ecxt = MemoryContextSwitchTo(ccxt); + ErrorData *errdata = CopyErrorData(); - /* Reset all transient state fields, to be sure all are clean */ - entry->xact_depth = 0; - entry->have_prep_stmt = false; - entry->have_error = false; - entry->changing_xact_state = false; - entry->invalidated = false; - entry->server_hashvalue = - GetSysCacheHashValue1(FOREIGNSERVEROID, - ObjectIdGetDatum(server->serverid)); - entry->mapping_hashvalue = - GetSysCacheHashValue1(USERMAPPINGOID, - ObjectIdGetDatum(user->umid)); - - /* Now try to make the connection */ - entry->conn = connect_pg_server(server, user); - - elog(DEBUG3, "new postgres_fdw connection %p for server \"%s\" (user mapping oid %u, userid %u)", - entry->conn, server->servername, user->umid, user->userid); + /* + * If connection failure is reported when starting a new remote + * transaction (not subtransaction), new connection will be + * reestablished later. + * + * After a broken connection is detected in libpq, any error other + * than connection failure (e.g., out-of-memory) can be thrown + * somewhere between return from libpq and the expected ereport() call + * in pgfdw_report_error(). In this case, since PQstatus() indicates + * CONNECTION_BAD, checking only PQstatus() causes the false detection + * of connection failure. To avoid this, we also verify that the + * error's sqlstate is ERRCODE_CONNECTION_FAILURE. Note that also + * checking only the sqlstate can cause another false detection + * because pgfdw_report_error() may report ERRCODE_CONNECTION_FAILURE + * for any libpq-originated error condition. + */ + if (errdata->sqlerrcode != ERRCODE_CONNECTION_FAILURE || + PQstatus(entry->conn) != CONNECTION_BAD || + entry->xact_depth > 0) + { + MemoryContextSwitchTo(ecxt); + PG_RE_THROW(); + } + + /* Clean up the error state */ + FlushErrorState(); + FreeErrorData(errdata); + errdata = NULL; + + retry = true; } + PG_END_TRY(); /* - * Start a new transaction or subtransaction if needed. + * If a broken connection is detected, disconnect it, reestablish a new + * connection and retry a new remote transaction. If connection failure is + * reported again, we give up getting a connection. */ - begin_remote_xact(entry); + if (retry) + { + Assert(entry->xact_depth == 0); + + ereport(DEBUG3, + (errmsg_internal("could not start remote transaction on connection %p", + entry->conn)), + errdetail_internal("%s", pchomp(PQerrorMessage(entry->conn)))); + + elog(DEBUG3, "closing connection %p to reestablish a new one", + entry->conn); + disconnect_pg_server(entry); + + if (entry->conn == NULL) + make_new_connection(entry, user); + + begin_remote_xact(entry); + } /* Remember if caller will prepare statements */ entry->have_prep_stmt |= will_prep_stmt; @@ -216,6 +259,37 @@ GetConnection(UserMapping *user, bool will_prep_stmt) return entry->conn; } +/* + * Reset all transient state fields in the cached connection entry and + * establish new connection to the remote server. + */ +static void +make_new_connection(ConnCacheEntry *entry, UserMapping *user) +{ + ForeignServer *server = GetForeignServer(user->serverid); + + Assert(entry->conn == NULL); + + /* Reset all transient state fields, to be sure all are clean */ + entry->xact_depth = 0; + entry->have_prep_stmt = false; + entry->have_error = false; + entry->changing_xact_state = false; + entry->invalidated = false; + entry->server_hashvalue = + GetSysCacheHashValue1(FOREIGNSERVEROID, + ObjectIdGetDatum(server->serverid)); + entry->mapping_hashvalue = + GetSysCacheHashValue1(USERMAPPINGOID, + ObjectIdGetDatum(user->umid)); + + /* Now try to make the connection */ + entry->conn = connect_pg_server(server, user); + + elog(DEBUG3, "new postgres_fdw connection %p for server \"%s\" (user mapping oid %u, userid %u)", + entry->conn, server->servername, user->umid, user->userid); +} + /* * Connect to remote server using specified server and user mapping properties. */ diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index ad37a7422133..2d44df19fee0 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -2706,7 +2706,6 @@ deparseOpExpr(OpExpr *node, deparse_expr_cxt *context) HeapTuple tuple; Form_pg_operator form; char oprkind; - ListCell *arg; /* Retrieve information about the operator from system catalog. */ tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno)); @@ -2716,18 +2715,16 @@ deparseOpExpr(OpExpr *node, deparse_expr_cxt *context) oprkind = form->oprkind; /* Sanity check. */ - Assert((oprkind == 'r' && list_length(node->args) == 1) || - (oprkind == 'l' && list_length(node->args) == 1) || + Assert((oprkind == 'l' && list_length(node->args) == 1) || (oprkind == 'b' && list_length(node->args) == 2)); /* Always parenthesize the expression. */ appendStringInfoChar(buf, '('); - /* Deparse left operand. */ - if (oprkind == 'r' || oprkind == 'b') + /* Deparse left operand, if any. */ + if (oprkind == 'b') { - arg = list_head(node->args); - deparseExpr(lfirst(arg), context); + deparseExpr(linitial(node->args), context); appendStringInfoChar(buf, ' '); } @@ -2735,12 +2732,8 @@ deparseOpExpr(OpExpr *node, deparse_expr_cxt *context) deparseOperatorName(buf, form); /* Deparse right operand. */ - if (oprkind == 'l' || oprkind == 'b') - { - arg = list_tail(node->args); - appendStringInfoChar(buf, ' '); - deparseExpr(lfirst(arg), context); - } + appendStringInfoChar(buf, ' '); + deparseExpr(llast(node->args), context); appendStringInfoChar(buf, ')'); diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 84bc0ee38171..2d88d0635837 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -653,14 +653,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- Op Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1"))) (3 rows) -EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE 1 = c1!; -- OpExpr(r) - QUERY PLAN ----------------------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - Output: c1, c2, c3, c4, c5, c6, c7, c8 - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((1::numeric = ("C 1" !))) -(3 rows) - EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------- @@ -8995,3 +8987,51 @@ PREPARE TRANSACTION 'fdw_tpc'; ERROR: cannot PREPARE a transaction that has operated on postgres_fdw foreign tables ROLLBACK; WARNING: there is no transaction in progress +-- =================================================================== +-- reestablish new connection +-- =================================================================== +-- Terminate the backend having the specified application_name and wait for +-- the termination to complete. +CREATE OR REPLACE PROCEDURE terminate_backend_and_wait(appname text) AS $$ +BEGIN + PERFORM pg_terminate_backend(pid) FROM pg_stat_activity + WHERE application_name = appname; + LOOP + PERFORM * FROM pg_stat_activity WHERE application_name = appname; + EXIT WHEN NOT FOUND; + PERFORM pg_sleep(1), pg_stat_clear_snapshot(); + END LOOP; +END; +$$ LANGUAGE plpgsql; +-- Change application_name of remote connection to special one +-- so that we can easily terminate the connection later. +ALTER SERVER loopback OPTIONS (application_name 'fdw_retry_check'); +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- Terminate the remote connection. +CALL terminate_backend_and_wait('fdw_retry_check'); +-- This query should detect the broken connection when starting new remote +-- transaction, reestablish new connection, and then succeed. +BEGIN; +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- If the query detects the broken connection when starting new remote +-- subtransaction, it doesn't reestablish new connection and should fail. +-- The text of the error might vary across platforms, so don't show it. +CALL terminate_backend_and_wait('fdw_retry_check'); +SAVEPOINT s; +\set VERBOSITY sqlstate +SELECT 1 FROM ft1 LIMIT 1; -- should fail +ERROR: 08006 +\set VERBOSITY default +COMMIT; +-- Clean up +DROP PROCEDURE terminate_backend_and_wait(text); diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 560fa5602224..0009139225a8 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -452,6 +452,7 @@ static void init_returning_filter(PgFdwDirectModifyState *dmstate, List *fdw_scan_tlist, Index rtindex); static TupleTableSlot *apply_returning_filter(PgFdwDirectModifyState *dmstate, + ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate); static void prepare_query_params(PlanState *node, @@ -694,15 +695,14 @@ postgresGetForeignRelSize(PlannerInfo *root, else { /* - * If the foreign table has never been ANALYZEd, it will have relpages - * and reltuples equal to zero, which most likely has nothing to do - * with reality. We can't do a whole lot about that if we're not + * If the foreign table has never been ANALYZEd, it will have + * reltuples < 0, meaning "unknown". We can't do much if we're not * allowed to consult the remote server, but we can use a hack similar * to plancat.c's treatment of empty relations: use a minimum size * estimate of 10 pages, and divide by the column-datatype-based width * estimate to get the corresponding number of tuples. */ - if (baserel->pages == 0 && baserel->tuples == 0) + if (baserel->tuples < 0) { baserel->pages = 10; baserel->tuples = @@ -2301,9 +2301,10 @@ postgresPlanDirectModify(PlannerInfo *root, } /* - * Update the operation info. + * Update the operation and target relation info. */ fscan->operation = operation; + fscan->resultRelation = resultRelation; /* * Update the fdw_exprs list that will be available to the executor. @@ -2369,7 +2370,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags) * Identify which user to do the remote access as. This should match what * ExecCheckRTEPerms() does. */ - rtindex = estate->es_result_relation_info->ri_RangeTableIndex; + rtindex = node->resultRelInfo->ri_RangeTableIndex; rte = exec_rt_fetch(rtindex, estate); userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); @@ -2464,7 +2465,7 @@ postgresIterateDirectModify(ForeignScanState *node) { PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state; EState *estate = node->ss.ps.state; - ResultRelInfo *resultRelInfo = estate->es_result_relation_info; + ResultRelInfo *resultRelInfo = node->resultRelInfo; /* * If this is the first call after Begin, execute the statement. @@ -2603,8 +2604,8 @@ postgresExplainForeignScan(ForeignScanState *node, ExplainState *es) quote_identifier(relname)); } else - appendStringInfo(relations, "%s", - quote_identifier(relname)); + appendStringInfoString(relations, + quote_identifier(relname)); refname = (char *) list_nth(es->rtable_names, rti - 1); if (refname == NULL) refname = rte->eref->aliasname; @@ -4102,7 +4103,7 @@ get_returning_data(ForeignScanState *node) { PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state; EState *estate = node->ss.ps.state; - ResultRelInfo *resultRelInfo = estate->es_result_relation_info; + ResultRelInfo *resultRelInfo = node->resultRelInfo; TupleTableSlot *slot = node->ss.ss_ScanTupleSlot; TupleTableSlot *resultSlot; @@ -4157,7 +4158,7 @@ get_returning_data(ForeignScanState *node) if (dmstate->rel) resultSlot = slot; else - resultSlot = apply_returning_filter(dmstate, slot, estate); + resultSlot = apply_returning_filter(dmstate, resultRelInfo, slot, estate); } dmstate->next_tuple++; @@ -4246,10 +4247,10 @@ init_returning_filter(PgFdwDirectModifyState *dmstate, */ static TupleTableSlot * apply_returning_filter(PgFdwDirectModifyState *dmstate, + ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate) { - ResultRelInfo *relInfo = estate->es_result_relation_info; TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel); TupleTableSlot *resultSlot; Datum *values; @@ -4261,7 +4262,7 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate, /* * Use the return tuple slot as a place to store the result tuple. */ - resultSlot = ExecGetReturningSlot(estate, relInfo); + resultSlot = ExecGetReturningSlot(estate, resultRelInfo); /* * Extract all the values of the scan tuple. diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index d452d063430a..7581c5417b90 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -307,7 +307,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- Nu EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) -EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE 1 = c1!; -- OpExpr(r) EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef @@ -2654,3 +2653,47 @@ SELECT count(*) FROM ft1; -- error here PREPARE TRANSACTION 'fdw_tpc'; ROLLBACK; + +-- =================================================================== +-- reestablish new connection +-- =================================================================== + +-- Terminate the backend having the specified application_name and wait for +-- the termination to complete. +CREATE OR REPLACE PROCEDURE terminate_backend_and_wait(appname text) AS $$ +BEGIN + PERFORM pg_terminate_backend(pid) FROM pg_stat_activity + WHERE application_name = appname; + LOOP + PERFORM * FROM pg_stat_activity WHERE application_name = appname; + EXIT WHEN NOT FOUND; + PERFORM pg_sleep(1), pg_stat_clear_snapshot(); + END LOOP; +END; +$$ LANGUAGE plpgsql; + +-- Change application_name of remote connection to special one +-- so that we can easily terminate the connection later. +ALTER SERVER loopback OPTIONS (application_name 'fdw_retry_check'); +SELECT 1 FROM ft1 LIMIT 1; + +-- Terminate the remote connection. +CALL terminate_backend_and_wait('fdw_retry_check'); + +-- This query should detect the broken connection when starting new remote +-- transaction, reestablish new connection, and then succeed. +BEGIN; +SELECT 1 FROM ft1 LIMIT 1; + +-- If the query detects the broken connection when starting new remote +-- subtransaction, it doesn't reestablish new connection and should fail. +-- The text of the error might vary across platforms, so don't show it. +CALL terminate_backend_and_wait('fdw_retry_check'); +SAVEPOINT s; +\set VERBOSITY sqlstate +SELECT 1 FROM ft1 LIMIT 1; -- should fail +\set VERBOSITY default +COMMIT; + +-- Clean up +DROP PROCEDURE terminate_backend_and_wait(text); diff --git a/contrib/sslinfo/expected/sslinfo.out b/contrib/sslinfo/expected/sslinfo.out index ef0dc43a3c74..1cb0202a0fd9 100644 --- a/contrib/sslinfo/expected/sslinfo.out +++ b/contrib/sslinfo/expected/sslinfo.out @@ -38,15 +38,15 @@ SELECT ssl_client_serial(); (1 row) SELECT ssl_client_dn(); - ssl_client_dn ------------------------------------------------------------------------------------- - /CN=client.example.com/C=CN/ST=Qingdao/L=ClientLocality/O=SSLINFO-Client/OU=Client + ssl_client_dn +----------------------------------------------------------------- + /CN=client.example.com/C=CN/ST=Qingdao/L=ClientLocality/O=SSLIN (1 row) SELECT ssl_issuer_dn(); - ssl_issuer_dn ---------------------------------------------------------------------------- - /CN=root.example.com/C=CN/ST=Beijing/L=RootLocality/O=SSLINFO-dev/OU=Test + ssl_issuer_dn +----------------------------------------------------------------- + /CN=root.example.com/C=CN/ST=Beijing/L=RootLocality/O=SSLINFO-d (1 row) SELECT ssl_client_dn_field('CN') AS client_dn_CN; diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c index 5ba3988e2704..30cae0bb985e 100644 --- a/contrib/sslinfo/sslinfo.c +++ b/contrib/sslinfo/sslinfo.c @@ -22,7 +22,6 @@ PG_MODULE_MAGIC; static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName); -static Datum X509_NAME_to_text(X509_NAME *name); static Datum ASN1_STRING_to_text(ASN1_STRING *str); /* @@ -54,9 +53,16 @@ PG_FUNCTION_INFO_V1(ssl_version); Datum ssl_version(PG_FUNCTION_ARGS) { - if (MyProcPort->ssl == NULL) + const char *version; + + if (!MyProcPort->ssl_in_use) + PG_RETURN_NULL(); + + version = be_tls_get_version(MyProcPort); + if (version == NULL) PG_RETURN_NULL(); - PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl))); + + PG_RETURN_TEXT_P(cstring_to_text(version)); } @@ -67,9 +73,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher); Datum ssl_cipher(PG_FUNCTION_ARGS) { - if (MyProcPort->ssl == NULL) + const char *cipher; + + if (!MyProcPort->ssl_in_use) + PG_RETURN_NULL(); + + cipher = be_tls_get_cipher(MyProcPort); + if (cipher == NULL) PG_RETURN_NULL(); - PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl))); + + PG_RETURN_TEXT_P(cstring_to_text(cipher)); } @@ -83,7 +96,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present); Datum ssl_client_cert_present(PG_FUNCTION_ARGS) { - PG_RETURN_BOOL(MyProcPort->peer != NULL); + PG_RETURN_BOOL(MyProcPort->peer_cert_valid); } @@ -99,25 +112,21 @@ PG_FUNCTION_INFO_V1(ssl_client_serial); Datum ssl_client_serial(PG_FUNCTION_ARGS) { + char decimal[NAMEDATALEN]; Datum result; - Port *port = MyProcPort; - X509 *peer = port->peer; - ASN1_INTEGER *serial = NULL; - BIGNUM *b; - char *decimal; - if (!peer) + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) + PG_RETURN_NULL(); + + be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN); + + if (!*decimal) PG_RETURN_NULL(); - serial = X509_get_serialNumber(peer); - b = ASN1_INTEGER_to_BN(serial, NULL); - decimal = BN_bn2dec(b); - BN_free(b); result = DirectFunctionCall3(numeric_in, CStringGetDatum(decimal), ObjectIdGetDatum(0), Int32GetDatum(-1)); - OPENSSL_free(decimal); return result; } @@ -228,7 +237,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS) text *fieldname = PG_GETARG_TEXT_PP(0); Datum result; - if (!(MyProcPort->peer)) + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) PG_RETURN_NULL(); result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname); @@ -275,76 +284,6 @@ ssl_issuer_field(PG_FUNCTION_ARGS) } -/* - * Equivalent of X509_NAME_oneline that respects encoding - * - * This function converts X509_NAME structure to the text variable - * converting all textual data into current database encoding. - * - * Parameter: X509_NAME *name X509_NAME structure to be converted - * - * Returns: text datum which contains string representation of - * X509_NAME - */ -static Datum -X509_NAME_to_text(X509_NAME *name) -{ - BIO *membuf = BIO_new(BIO_s_mem()); - int i, - nid, - count = X509_NAME_entry_count(name); - X509_NAME_ENTRY *e; - ASN1_STRING *v; - const char *field_name; - size_t size; - char nullterm; - char *sp; - char *dp; - text *result; - - if (membuf == NULL) - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("could not create OpenSSL BIO structure"))); - - (void) BIO_set_close(membuf, BIO_CLOSE); - for (i = 0; i < count; i++) - { - e = X509_NAME_get_entry(name, i); - nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e)); - if (nid == NID_undef) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("could not get NID for ASN1_OBJECT object"))); - v = X509_NAME_ENTRY_get_data(e); - field_name = OBJ_nid2sn(nid); - if (field_name == NULL) - field_name = OBJ_nid2ln(nid); - if (field_name == NULL) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid))); - BIO_printf(membuf, "/%s=", field_name); - ASN1_STRING_print_ex(membuf, v, - ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB) - | ASN1_STRFLGS_UTF8_CONVERT)); - } - - /* ensure null termination of the BIO's content */ - nullterm = '\0'; - BIO_write(membuf, &nullterm, 1); - size = BIO_get_mem_data(membuf, &sp); - dp = pg_any_to_server(sp, size - 1, PG_UTF8); - result = cstring_to_text(dp); - if (dp != sp) - pfree(dp); - if (BIO_free(membuf) != 1) - elog(ERROR, "could not free OpenSSL BIO structure"); - - PG_RETURN_TEXT_P(result); -} - - /* * Returns current client certificate subject as one string * @@ -358,9 +297,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn); Datum ssl_client_dn(PG_FUNCTION_ARGS) { - if (!(MyProcPort->peer)) + char subject[NAMEDATALEN]; + + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) + PG_RETURN_NULL(); + + be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN); + + if (!*subject) PG_RETURN_NULL(); - return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer)); + + PG_RETURN_TEXT_P(cstring_to_text(subject)); } @@ -377,9 +324,17 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn); Datum ssl_issuer_dn(PG_FUNCTION_ARGS) { - if (!(MyProcPort->peer)) + char issuer[NAMEDATALEN]; + + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) PG_RETURN_NULL(); - return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer)); + + be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN); + + if (!*issuer) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(cstring_to_text(issuer)); } diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c index 3802ae905e8f..02f02eab5742 100644 --- a/contrib/tablefunc/tablefunc.c +++ b/contrib/tablefunc/tablefunc.c @@ -49,7 +49,6 @@ static HTAB *load_categories_hash(char *cats_sql, MemoryContext per_query_ctx); static Tuplestorestate *get_crosstab_tuplestore(char *sql, HTAB *crosstab_hash, TupleDesc tupdesc, - MemoryContext per_query_ctx, bool randomAccess); static void validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch, bool show_serial); static bool compatCrosstabTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2); @@ -680,7 +679,6 @@ crosstab_hash(PG_FUNCTION_ARGS) rsinfo->setResult = get_crosstab_tuplestore(sql, crosstab_hash, tupdesc, - per_query_ctx, rsinfo->allowedModes & SFRM_Materialize_Random); /* @@ -793,7 +791,6 @@ static Tuplestorestate * get_crosstab_tuplestore(char *sql, HTAB *crosstab_hash, TupleDesc tupdesc, - MemoryContext per_query_ctx, bool randomAccess) { Tuplestorestate *tupstore; diff --git a/contrib/test_decoding/Makefile b/contrib/test_decoding/Makefile index ed9a3d6c0ede..9a4c76f01364 100644 --- a/contrib/test_decoding/Makefile +++ b/contrib/test_decoding/Makefile @@ -5,9 +5,9 @@ PGFILEDESC = "test_decoding - example of a logical decoding output plugin" REGRESS = ddl xact rewrite toast permissions decoding_in_xact \ decoding_into_rel binary prepared replorigin time messages \ - spill slot truncate stream + spill slot truncate stream stats ISOLATION = mxact delayed_startup ondisk_startup concurrent_ddl_dml \ - oldest_xmin snapshot_transfer subxact_without_top + oldest_xmin snapshot_transfer subxact_without_top concurrent_stream REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/test_decoding/logical.conf ISOLATION_OPTS = --temp-config $(top_srcdir)/contrib/test_decoding/logical.conf diff --git a/contrib/test_decoding/expected/concurrent_stream.out b/contrib/test_decoding/expected/concurrent_stream.out new file mode 100644 index 000000000000..e731d13d8fa9 --- /dev/null +++ b/contrib/test_decoding/expected/concurrent_stream.out @@ -0,0 +1,19 @@ +Parsed test spec with 2 sessions + +starting permutation: s0_begin s0_ddl s1_ddl s1_begin s1_toast_insert s1_commit s1_get_stream_changes +step s0_begin: BEGIN; +step s0_ddl: CREATE TABLE stream_test1(data text); +step s1_ddl: CREATE TABLE stream_test(data text); +step s1_begin: BEGIN; +step s1_toast_insert: INSERT INTO stream_test SELECT large_val(); +step s1_commit: COMMIT; +step s1_get_stream_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); +data + +opening a streamed block for transaction +streaming change for transaction +closing a streamed block for transaction +committing streamed transaction +?column? + +stop diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out index d79cd316b79f..4ff0044c7879 100644 --- a/contrib/test_decoding/expected/ddl.out +++ b/contrib/test_decoding/expected/ddl.out @@ -565,6 +565,35 @@ UPDATE table_with_unique_not_null SET data = 3 WHERE data = 2; UPDATE table_with_unique_not_null SET id = -id; UPDATE table_with_unique_not_null SET id = -id; DELETE FROM table_with_unique_not_null WHERE data = 3; +-- check tables with dropped indexes used in REPLICA IDENTITY +-- table with primary key +CREATE TABLE table_dropped_index_with_pk (a int PRIMARY KEY, b int, c int); +CREATE UNIQUE INDEX table_dropped_index_with_pk_idx + ON table_dropped_index_with_pk(a); +ALTER TABLE table_dropped_index_with_pk REPLICA IDENTITY + USING INDEX table_dropped_index_with_pk_idx; +DROP INDEX table_dropped_index_with_pk_idx; +INSERT INTO table_dropped_index_with_pk VALUES (1,1,1), (2,2,2), (3,3,3); +UPDATE table_dropped_index_with_pk SET a = 4 WHERE a = 1; +UPDATE table_dropped_index_with_pk SET b = 5 WHERE a = 2; +UPDATE table_dropped_index_with_pk SET b = 6, c = 7 WHERE a = 3; +DELETE FROM table_dropped_index_with_pk WHERE b = 1; +DELETE FROM table_dropped_index_with_pk WHERE a = 3; +DROP TABLE table_dropped_index_with_pk; +-- table without primary key +CREATE TABLE table_dropped_index_no_pk (a int NOT NULL, b int, c int); +CREATE UNIQUE INDEX table_dropped_index_no_pk_idx + ON table_dropped_index_no_pk(a); +ALTER TABLE table_dropped_index_no_pk REPLICA IDENTITY + USING INDEX table_dropped_index_no_pk_idx; +DROP INDEX table_dropped_index_no_pk_idx; +INSERT INTO table_dropped_index_no_pk VALUES (1,1,1), (2,2,2), (3,3,3); +UPDATE table_dropped_index_no_pk SET a = 4 WHERE a = 1; +UPDATE table_dropped_index_no_pk SET b = 5 WHERE a = 2; +UPDATE table_dropped_index_no_pk SET b = 6, c = 7 WHERE a = 3; +DELETE FROM table_dropped_index_no_pk WHERE b = 1; +DELETE FROM table_dropped_index_no_pk WHERE a = 3; +DROP TABLE table_dropped_index_no_pk; -- check toast support BEGIN; CREATE SEQUENCE toasttable_rand_seq START 79 INCREMENT 1499; -- portable "random" @@ -682,6 +711,46 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc table public.table_with_unique_not_null: DELETE: id[integer]:4 COMMIT BEGIN + table public.table_dropped_index_with_pk: INSERT: a[integer]:1 b[integer]:1 c[integer]:1 + table public.table_dropped_index_with_pk: INSERT: a[integer]:2 b[integer]:2 c[integer]:2 + table public.table_dropped_index_with_pk: INSERT: a[integer]:3 b[integer]:3 c[integer]:3 + COMMIT + BEGIN + table public.table_dropped_index_with_pk: UPDATE: a[integer]:4 b[integer]:1 c[integer]:1 + COMMIT + BEGIN + table public.table_dropped_index_with_pk: UPDATE: a[integer]:2 b[integer]:5 c[integer]:2 + COMMIT + BEGIN + table public.table_dropped_index_with_pk: UPDATE: a[integer]:3 b[integer]:6 c[integer]:7 + COMMIT + BEGIN + table public.table_dropped_index_with_pk: DELETE: (no-tuple-data) + COMMIT + BEGIN + table public.table_dropped_index_with_pk: DELETE: (no-tuple-data) + COMMIT + BEGIN + table public.table_dropped_index_no_pk: INSERT: a[integer]:1 b[integer]:1 c[integer]:1 + table public.table_dropped_index_no_pk: INSERT: a[integer]:2 b[integer]:2 c[integer]:2 + table public.table_dropped_index_no_pk: INSERT: a[integer]:3 b[integer]:3 c[integer]:3 + COMMIT + BEGIN + table public.table_dropped_index_no_pk: UPDATE: a[integer]:4 b[integer]:1 c[integer]:1 + COMMIT + BEGIN + table public.table_dropped_index_no_pk: UPDATE: a[integer]:2 b[integer]:5 c[integer]:2 + COMMIT + BEGIN + table public.table_dropped_index_no_pk: UPDATE: a[integer]:3 b[integer]:6 c[integer]:7 + COMMIT + BEGIN + table public.table_dropped_index_no_pk: DELETE: (no-tuple-data) + COMMIT + BEGIN + table public.table_dropped_index_no_pk: DELETE: (no-tuple-data) + COMMIT + BEGIN table public.toasttable: INSERT: id[integer]:1 toasted_col1[text]:'12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000' rand1[double precision]:79 toasted_col2[text]:null rand2[double precision]:1578 COMMIT BEGIN @@ -690,7 +759,7 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc BEGIN table public.toasttable: UPDATE: id[integer]:1 toasted_col1[text]:'12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000' rand1[double precision]:79 toasted_col2[text]:null rand2[double precision]:1578 COMMIT -(103 rows) +(143 rows) INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i); -- update of second column, first column unchanged diff --git a/contrib/test_decoding/expected/stats.out b/contrib/test_decoding/expected/stats.out new file mode 100644 index 000000000000..dafca965201d --- /dev/null +++ b/contrib/test_decoding/expected/stats.out @@ -0,0 +1,111 @@ +-- predictability +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +CREATE TABLE stats_test(data text); +-- function to wait for counters to advance +CREATE FUNCTION wait_for_decode_stats(check_reset bool) RETURNS void AS $$ +DECLARE + start_time timestamptz := clock_timestamp(); + updated bool; +BEGIN + -- we don't want to wait forever; loop will exit after 30 seconds + FOR i IN 1 .. 300 LOOP + + -- check to see if all updates have been reset/updated + SELECT CASE WHEN check_reset THEN (spill_txns = 0) + ELSE (spill_txns > 0) + END + INTO updated + FROM pg_stat_replication_slots WHERE slot_name='regression_slot'; + + exit WHEN updated; + + -- wait a little + perform pg_sleep_for('100 milliseconds'); + + -- reset stats snapshot so we can test again + perform pg_stat_clear_snapshot(); + + END LOOP; + + -- report time waited in postmaster log (where it won't change test output) + RAISE LOG 'wait_for_decode_stats delayed % seconds', + extract(epoch from clock_timestamp() - start_time); +END +$$ LANGUAGE plpgsql; +-- spilling the xact +BEGIN; +INSERT INTO stats_test SELECT 'serialize-topbig--1:'||g.i FROM generate_series(1, 5000) g(i); +COMMIT; +SELECT count(*) FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'skip-empty-xacts', '1'); + count +------- + 5002 +(1 row) + +-- Check stats, wait for the stats collector to update. We can't test the +-- exact stats count as that can vary if any background transaction (say by +-- autovacuum) happens in parallel to the main transaction. +SELECT wait_for_decode_stats(false); + wait_for_decode_stats +----------------------- + +(1 row) + +SELECT slot_name, spill_txns > 0 AS spill_txns, spill_count > 0 AS spill_count FROM pg_stat_replication_slots; + slot_name | spill_txns | spill_count +-----------------+------------+------------- + regression_slot | t | t +(1 row) + +-- reset the slot stats, and wait for stats collector to reset +SELECT pg_stat_reset_replication_slot('regression_slot'); + pg_stat_reset_replication_slot +-------------------------------- + +(1 row) + +SELECT wait_for_decode_stats(true); + wait_for_decode_stats +----------------------- + +(1 row) + +SELECT slot_name, spill_txns, spill_count FROM pg_stat_replication_slots; + slot_name | spill_txns | spill_count +-----------------+------------+------------- + regression_slot | 0 | 0 +(1 row) + +-- decode and check stats again. +SELECT count(*) FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'skip-empty-xacts', '1'); + count +------- + 5002 +(1 row) + +SELECT wait_for_decode_stats(false); + wait_for_decode_stats +----------------------- + +(1 row) + +SELECT slot_name, spill_txns > 0 AS spill_txns, spill_count > 0 AS spill_count FROM pg_stat_replication_slots; + slot_name | spill_txns | spill_count +-----------------+------------+------------- + regression_slot | t | t +(1 row) + +DROP FUNCTION wait_for_decode_stats(bool); +DROP TABLE stats_test; +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/test_decoding/expected/stream.out b/contrib/test_decoding/expected/stream.out index d7e32f818546..e1c3bc838d5e 100644 --- a/contrib/test_decoding/expected/stream.out +++ b/contrib/test_decoding/expected/stream.out @@ -29,10 +29,7 @@ COMMIT; SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); data ---------------------------------------------------------- - opening a streamed block for transaction streaming message: transactional: 1 prefix: test, sz: 50 - closing a streamed block for transaction - aborting streamed (sub)transaction opening a streamed block for transaction streaming change for transaction streaming change for transaction @@ -56,7 +53,7 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'incl streaming change for transaction closing a streamed block for transaction committing streamed transaction -(27 rows) +(24 rows) -- streaming test for toast changes ALTER TABLE stream_test ALTER COLUMN data set storage external; diff --git a/contrib/test_decoding/specs/concurrent_stream.spec b/contrib/test_decoding/specs/concurrent_stream.spec new file mode 100644 index 000000000000..ad9fde9c2844 --- /dev/null +++ b/contrib/test_decoding/specs/concurrent_stream.spec @@ -0,0 +1,37 @@ +# Test decoding of in-progress transaction containing dml and a concurrent +# transaction with ddl operation. The transaction containing ddl operation +# should not get streamed as it doesn't have any changes. + +setup +{ + SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); + + -- consume DDL + SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + CREATE OR REPLACE FUNCTION large_val() RETURNS TEXT LANGUAGE SQL AS 'select array_agg(md5(g::text))::text from generate_series(1, 80000) g'; +} + +teardown +{ + DROP TABLE IF EXISTS stream_test; + DROP TABLE IF EXISTS stream_test1; + SELECT 'stop' FROM pg_drop_replication_slot('isolation_slot'); +} + +session "s0" +setup { SET synchronous_commit=on; } +step "s0_begin" { BEGIN; } +step "s0_ddl" {CREATE TABLE stream_test1(data text);} + +# The transaction commit for s1_ddl will add the INTERNAL_SNAPSHOT change to +# the currently running s0_ddl and we want to test that s0_ddl should not get +# streamed when user asked to skip-empty-xacts. +session "s1" +setup { SET synchronous_commit=on; } +step "s1_ddl" { CREATE TABLE stream_test(data text); } +step "s1_begin" { BEGIN; } +step "s1_toast_insert" {INSERT INTO stream_test SELECT large_val();} +step "s1_commit" { COMMIT; } +step "s1_get_stream_changes" { SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1');} + +permutation "s0_begin" "s0_ddl" "s1_ddl" "s1_begin" "s1_toast_insert" "s1_commit" "s1_get_stream_changes" diff --git a/contrib/test_decoding/sql/ddl.sql b/contrib/test_decoding/sql/ddl.sql index 2c4823e57805..1b3866d01530 100644 --- a/contrib/test_decoding/sql/ddl.sql +++ b/contrib/test_decoding/sql/ddl.sql @@ -345,6 +345,37 @@ UPDATE table_with_unique_not_null SET id = -id; UPDATE table_with_unique_not_null SET id = -id; DELETE FROM table_with_unique_not_null WHERE data = 3; +-- check tables with dropped indexes used in REPLICA IDENTITY +-- table with primary key +CREATE TABLE table_dropped_index_with_pk (a int PRIMARY KEY, b int, c int); +CREATE UNIQUE INDEX table_dropped_index_with_pk_idx + ON table_dropped_index_with_pk(a); +ALTER TABLE table_dropped_index_with_pk REPLICA IDENTITY + USING INDEX table_dropped_index_with_pk_idx; +DROP INDEX table_dropped_index_with_pk_idx; +INSERT INTO table_dropped_index_with_pk VALUES (1,1,1), (2,2,2), (3,3,3); +UPDATE table_dropped_index_with_pk SET a = 4 WHERE a = 1; +UPDATE table_dropped_index_with_pk SET b = 5 WHERE a = 2; +UPDATE table_dropped_index_with_pk SET b = 6, c = 7 WHERE a = 3; +DELETE FROM table_dropped_index_with_pk WHERE b = 1; +DELETE FROM table_dropped_index_with_pk WHERE a = 3; +DROP TABLE table_dropped_index_with_pk; + +-- table without primary key +CREATE TABLE table_dropped_index_no_pk (a int NOT NULL, b int, c int); +CREATE UNIQUE INDEX table_dropped_index_no_pk_idx + ON table_dropped_index_no_pk(a); +ALTER TABLE table_dropped_index_no_pk REPLICA IDENTITY + USING INDEX table_dropped_index_no_pk_idx; +DROP INDEX table_dropped_index_no_pk_idx; +INSERT INTO table_dropped_index_no_pk VALUES (1,1,1), (2,2,2), (3,3,3); +UPDATE table_dropped_index_no_pk SET a = 4 WHERE a = 1; +UPDATE table_dropped_index_no_pk SET b = 5 WHERE a = 2; +UPDATE table_dropped_index_no_pk SET b = 6, c = 7 WHERE a = 3; +DELETE FROM table_dropped_index_no_pk WHERE b = 1; +DELETE FROM table_dropped_index_no_pk WHERE a = 3; +DROP TABLE table_dropped_index_no_pk; + -- check toast support BEGIN; CREATE SEQUENCE toasttable_rand_seq START 79 INCREMENT 1499; -- portable "random" diff --git a/contrib/test_decoding/sql/stats.sql b/contrib/test_decoding/sql/stats.sql new file mode 100644 index 000000000000..182df84030d0 --- /dev/null +++ b/contrib/test_decoding/sql/stats.sql @@ -0,0 +1,64 @@ +-- predictability +SET synchronous_commit = on; + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + +CREATE TABLE stats_test(data text); + +-- function to wait for counters to advance +CREATE FUNCTION wait_for_decode_stats(check_reset bool) RETURNS void AS $$ +DECLARE + start_time timestamptz := clock_timestamp(); + updated bool; +BEGIN + -- we don't want to wait forever; loop will exit after 30 seconds + FOR i IN 1 .. 300 LOOP + + -- check to see if all updates have been reset/updated + SELECT CASE WHEN check_reset THEN (spill_txns = 0) + ELSE (spill_txns > 0) + END + INTO updated + FROM pg_stat_replication_slots WHERE slot_name='regression_slot'; + + exit WHEN updated; + + -- wait a little + perform pg_sleep_for('100 milliseconds'); + + -- reset stats snapshot so we can test again + perform pg_stat_clear_snapshot(); + + END LOOP; + + -- report time waited in postmaster log (where it won't change test output) + RAISE LOG 'wait_for_decode_stats delayed % seconds', + extract(epoch from clock_timestamp() - start_time); +END +$$ LANGUAGE plpgsql; + +-- spilling the xact +BEGIN; +INSERT INTO stats_test SELECT 'serialize-topbig--1:'||g.i FROM generate_series(1, 5000) g(i); +COMMIT; +SELECT count(*) FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'skip-empty-xacts', '1'); + +-- Check stats, wait for the stats collector to update. We can't test the +-- exact stats count as that can vary if any background transaction (say by +-- autovacuum) happens in parallel to the main transaction. +SELECT wait_for_decode_stats(false); +SELECT slot_name, spill_txns > 0 AS spill_txns, spill_count > 0 AS spill_count FROM pg_stat_replication_slots; + +-- reset the slot stats, and wait for stats collector to reset +SELECT pg_stat_reset_replication_slot('regression_slot'); +SELECT wait_for_decode_stats(true); +SELECT slot_name, spill_txns, spill_count FROM pg_stat_replication_slots; + +-- decode and check stats again. +SELECT count(*) FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'skip-empty-xacts', '1'); +SELECT wait_for_decode_stats(false); +SELECT slot_name, spill_txns > 0 AS spill_txns, spill_count > 0 AS spill_count FROM pg_stat_replication_slots; + +DROP FUNCTION wait_for_decode_stats(bool); +DROP TABLE stats_test; +SELECT pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c index 34745150e9ba..8e33614f1442 100644 --- a/contrib/test_decoding/test_decoding.c +++ b/contrib/test_decoding/test_decoding.c @@ -64,6 +64,10 @@ static void pg_decode_message(LogicalDecodingContext *ctx, Size sz, const char *message); static void pg_decode_stream_start(LogicalDecodingContext *ctx, ReorderBufferTXN *txn); +static void pg_output_stream_start(LogicalDecodingContext *ctx, + TestDecodingData *data, + ReorderBufferTXN *txn, + bool last_write); static void pg_decode_stream_stop(LogicalDecodingContext *ctx, ReorderBufferTXN *txn); static void pg_decode_stream_abort(LogicalDecodingContext *ctx, @@ -583,46 +587,46 @@ pg_decode_message(LogicalDecodingContext *ctx, OutputPluginWrite(ctx, true); } -/* - * We never try to stream any empty xact so we don't need any special handling - * for skip_empty_xacts in streaming mode APIs. - */ static void pg_decode_stream_start(LogicalDecodingContext *ctx, ReorderBufferTXN *txn) { TestDecodingData *data = ctx->output_plugin_private; - OutputPluginPrepareWrite(ctx, true); + data->xact_wrote_changes = false; + if (data->skip_empty_xacts) + return; + pg_output_stream_start(ctx, data, txn, true); +} + +static void +pg_output_stream_start(LogicalDecodingContext *ctx, TestDecodingData *data, ReorderBufferTXN *txn, bool last_write) +{ + OutputPluginPrepareWrite(ctx, last_write); if (data->include_xids) appendStringInfo(ctx->out, "opening a streamed block for transaction TXN %u", txn->xid); else - appendStringInfo(ctx->out, "opening a streamed block for transaction"); - OutputPluginWrite(ctx, true); + appendStringInfoString(ctx->out, "opening a streamed block for transaction"); + OutputPluginWrite(ctx, last_write); } -/* - * We never try to stream any empty xact so we don't need any special handling - * for skip_empty_xacts in streaming mode APIs. - */ static void pg_decode_stream_stop(LogicalDecodingContext *ctx, ReorderBufferTXN *txn) { TestDecodingData *data = ctx->output_plugin_private; + if (data->skip_empty_xacts && !data->xact_wrote_changes) + return; + OutputPluginPrepareWrite(ctx, true); if (data->include_xids) appendStringInfo(ctx->out, "closing a streamed block for transaction TXN %u", txn->xid); else - appendStringInfo(ctx->out, "closing a streamed block for transaction"); + appendStringInfoString(ctx->out, "closing a streamed block for transaction"); OutputPluginWrite(ctx, true); } -/* - * We never try to stream any empty xact so we don't need any special handling - * for skip_empty_xacts in streaming mode APIs. - */ static void pg_decode_stream_abort(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, @@ -630,18 +634,17 @@ pg_decode_stream_abort(LogicalDecodingContext *ctx, { TestDecodingData *data = ctx->output_plugin_private; + if (data->skip_empty_xacts && !data->xact_wrote_changes) + return; + OutputPluginPrepareWrite(ctx, true); if (data->include_xids) appendStringInfo(ctx->out, "aborting streamed (sub)transaction TXN %u", txn->xid); else - appendStringInfo(ctx->out, "aborting streamed (sub)transaction"); + appendStringInfoString(ctx->out, "aborting streamed (sub)transaction"); OutputPluginWrite(ctx, true); } -/* - * We never try to stream any empty xact so we don't need any special handling - * for skip_empty_xacts in streaming mode APIs. - */ static void pg_decode_stream_commit(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, @@ -649,12 +652,15 @@ pg_decode_stream_commit(LogicalDecodingContext *ctx, { TestDecodingData *data = ctx->output_plugin_private; + if (data->skip_empty_xacts && !data->xact_wrote_changes) + return; + OutputPluginPrepareWrite(ctx, true); if (data->include_xids) appendStringInfo(ctx->out, "committing streamed transaction TXN %u", txn->xid); else - appendStringInfo(ctx->out, "committing streamed transaction"); + appendStringInfoString(ctx->out, "committing streamed transaction"); if (data->include_timestamp) appendStringInfo(ctx->out, " (at %s)", @@ -676,11 +682,18 @@ pg_decode_stream_change(LogicalDecodingContext *ctx, { TestDecodingData *data = ctx->output_plugin_private; + /* output stream start if we haven't yet */ + if (data->skip_empty_xacts && !data->xact_wrote_changes) + { + pg_output_stream_start(ctx, data, txn, false); + } + data->xact_wrote_changes = true; + OutputPluginPrepareWrite(ctx, true); if (data->include_xids) appendStringInfo(ctx->out, "streaming change for TXN %u", txn->xid); else - appendStringInfo(ctx->out, "streaming change for transaction"); + appendStringInfoString(ctx->out, "streaming change for transaction"); OutputPluginWrite(ctx, true); } @@ -722,10 +735,16 @@ pg_decode_stream_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, { TestDecodingData *data = ctx->output_plugin_private; + if (data->skip_empty_xacts && !data->xact_wrote_changes) + { + pg_output_stream_start(ctx, data, txn, false); + } + data->xact_wrote_changes = true; + OutputPluginPrepareWrite(ctx, true); if (data->include_xids) appendStringInfo(ctx->out, "streaming truncate for TXN %u", txn->xid); else - appendStringInfo(ctx->out, "streaming truncate for transaction"); + appendStringInfoString(ctx->out, "streaming truncate for transaction"); OutputPluginWrite(ctx, true); } diff --git a/contrib/vacuumlo/vacuumlo.c b/contrib/vacuumlo/vacuumlo.c index e4019fafaa9e..532cc596c412 100644 --- a/contrib/vacuumlo/vacuumlo.c +++ b/contrib/vacuumlo/vacuumlo.c @@ -24,6 +24,7 @@ #include "catalog/pg_class_d.h" #include "common/connect.h" #include "common/logging.h" +#include "common/string.h" #include "getopt_long.h" #include "libpq-fe.h" #include "pg_getopt.h" @@ -69,15 +70,11 @@ vacuumlo(const char *database, const struct _param *param) int i; bool new_pass; bool success = true; - static bool have_password = false; - static char password[100]; + static char *password = NULL; /* Note: password can be carried over from a previous call */ - if (param->pg_prompt == TRI_YES && !have_password) - { - simple_prompt("Password: ", password, sizeof(password), false); - have_password = true; - } + if (param->pg_prompt == TRI_YES && !password) + password = simple_prompt("Password: ", false); /* * Start the connection. Loop until we have a password if requested by @@ -97,7 +94,7 @@ vacuumlo(const char *database, const struct _param *param) keywords[2] = "user"; values[2] = param->pg_user; keywords[3] = "password"; - values[3] = have_password ? password : NULL; + values[3] = password; keywords[4] = "dbname"; values[4] = database; keywords[5] = "fallback_application_name"; @@ -115,12 +112,11 @@ vacuumlo(const char *database, const struct _param *param) if (PQstatus(conn) == CONNECTION_BAD && PQconnectionNeedsPassword(conn) && - !have_password && + !password && param->pg_prompt != TRI_NO) { PQfinish(conn); - simple_prompt("Password: ", password, sizeof(password), false); - have_password = true; + password = simple_prompt("Password: ", false); new_pass = true; } } while (new_pass); diff --git a/doc/src/sgml/generate-keywords-table.pl b/doc/src/sgml/generate-keywords-table.pl index 824b324ef78a..6332d65aadc7 100644 --- a/doc/src/sgml/generate-keywords-table.pl +++ b/doc/src/sgml/generate-keywords-table.pl @@ -1,6 +1,7 @@ #!/usr/bin/perl # -# Generate the keywords table file +# Generate the keywords table for the documentation's SQL Key Words appendix +# # Copyright (c) 2019-2020, PostgreSQL Global Development Group use strict; @@ -11,8 +12,9 @@ my $srcdir = $ARGV[0]; my %keywords; +my %as_keywords; -# read SQL keywords +# read SQL-spec keywords foreach my $ver (@sql_versions) { @@ -39,9 +41,10 @@ while (<$fh>) { - if (/^PG_KEYWORD\("(\w+)", \w+, (\w+)_KEYWORD\)/) + if (/^PG_KEYWORD\("(\w+)", \w+, (\w+)_KEYWORD\, (\w+)\)/) { $keywords{ uc $1 }{'pg'}{ lc $2 } = 1; + $as_keywords{ uc $1 } = 1 if $3 eq 'AS_LABEL'; } } @@ -107,6 +110,10 @@ END { print "reserved"; } + if ($as_keywords{$word}) + { + print ", requires AS"; + } print "\n"; foreach my $ver (@sql_versions) diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml index 9d2385031ca4..41f3e5ee86ba 100644 --- a/doc/src/sgml/glossary.sgml +++ b/doc/src/sgml/glossary.sgml @@ -708,7 +708,7 @@ Contains the values of row - attributes (i.e. the data) for a + attributes (i.e., the data) for a relation. The heap is realized within one or more file segments diff --git a/doc/src/sgml/install-binaries.sgml b/doc/src/sgml/install-binaries.sgml new file mode 100644 index 000000000000..001c3c7be01f --- /dev/null +++ b/doc/src/sgml/install-binaries.sgml @@ -0,0 +1,24 @@ + + + Installation from Binaries + + + installation + binaries + + + + PostgreSQL is available in the form of binary + packages for most common operating systems today. When available, this is + the recommended way to install PostgreSQL for users of the system. Building + from source (see ) is only recommended for + people developing PostgreSQL or extensions. + + + + For an updated list of platforms providing binary packages, please visit + the download section on the PostgreSQL website at + and follow the + instructions for the specific platform. + + diff --git a/doc/src/sgml/oldsnapshot.sgml b/doc/src/sgml/oldsnapshot.sgml new file mode 100644 index 000000000000..a665ae72e789 --- /dev/null +++ b/doc/src/sgml/oldsnapshot.sgml @@ -0,0 +1,33 @@ + + + + old_snapshot + + + old_snapshot + + + + The old_snapshot module allows inspection + of the server state that is used to implement + . + + + + Functions + + + + pg_old_snapshot_time_mapping(array_offset OUT int4, end_timestamp OUT timestamptz, newest_xmin OUT xid) returns setof record + + + Returns all of the entries in the server's timestamp to XID mapping. + Each entry represents the newest xmin of any snapshot taken in the + corresponding minute. + + + + + + + diff --git a/doc/src/sgml/pgsurgery.sgml b/doc/src/sgml/pgsurgery.sgml new file mode 100644 index 000000000000..134be9bebde0 --- /dev/null +++ b/doc/src/sgml/pgsurgery.sgml @@ -0,0 +1,107 @@ + + + + pg_surgery + + + pg_surgery + + + + The pg_surgery module provides various functions to + perform surgery on a damaged relation. These functions are unsafe by design + and using them may corrupt (or further corrupt) your database. For example, + these functions can easily be used to make a table inconsistent with its + own indexes, to cause UNIQUE or + FOREIGN KEY constraint violations, or even to make + tuples visible which, when read, will cause a database server crash. + They should be used with great caution and only as a last resort. + + + + Functions + + + + + heap_force_kill(regclass, tid[]) returns void + + + + + heap_force_kill marks used line + pointers as dead without examining the tuples. The + intended use of this function is to forcibly remove tuples that are not + otherwise accessible. For example: + +test=> select * from t1 where ctid = '(0, 1)'; +ERROR: could not access status of transaction 4007513275 +DETAIL: Could not open file "pg_xact/0EED": No such file or directory. + +test=# select heap_force_kill('t1'::regclass, ARRAY['(0, 1)']::tid[]); + heap_force_kill +----------------- + +(1 row) + +test=# select * from t1 where ctid = '(0, 1)'; +(0 rows) + + + + + + + + + heap_force_freeze(regclass, tid[]) returns void + + + + + heap_force_freeze marks tuples as frozen without + examining the tuple data. The intended use of this function is to + make accessible tuples which are inaccessible due to corrupted + visibility information, or which prevent the table from being + successfully vacuumed due to corrupted visibility information. + For example: + +test=> vacuum t1; +ERROR: found xmin 507 from before relfrozenxid 515 +CONTEXT: while scanning block 0 of relation "public.t1" + +test=# select ctid from t1 where xmin = 507; + ctid +------- + (0,3) +(1 row) + +test=# select heap_force_freeze('t1'::regclass, ARRAY['(0, 3)']::tid[]); + heap_force_freeze +------------------- + +(1 row) + +test=# select ctid from t1 where xmin = 2; + ctid +------- + (0,3) +(1 row) + + + + + + + + + + + Authors + + + Ashutosh Sharma ashu.coek88@gmail.com + + + + diff --git a/doc/src/sgml/ref/abort.sgml b/doc/src/sgml/ref/abort.sgml index 037291336516..16b5602487d7 100644 --- a/doc/src/sgml/ref/abort.sgml +++ b/doc/src/sgml/ref/abort.sgml @@ -33,7 +33,7 @@ ABORT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ] all the updates made by the transaction to be discarded. This command is identical in behavior to the standard SQL command - , + ROLLBACK, and is present only for historical reasons. @@ -57,8 +57,8 @@ ABORT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ] If AND CHAIN is specified, a new transaction is - immediately started with the same transaction characteristics (see ) as the just finished one. Otherwise, + immediately started with the same transaction characteristics (see SET TRANSACTION) as the just finished one. Otherwise, no new transaction is started. @@ -70,7 +70,7 @@ ABORT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ] Notes - Use to + Use COMMIT to successfully terminate a transaction. diff --git a/doc/src/sgml/ref/alter_aggregate.sgml b/doc/src/sgml/ref/alter_aggregate.sgml index 2ad3e0440bf8..aee10a5ca2e0 100644 --- a/doc/src/sgml/ref/alter_aggregate.sgml +++ b/doc/src/sgml/ref/alter_aggregate.sgml @@ -23,7 +23,7 @@ PostgreSQL documentation ALTER AGGREGATE name ( aggregate_signature ) RENAME TO new_name ALTER AGGREGATE name ( aggregate_signature ) - OWNER TO { new_owner | CURRENT_USER | SESSION_USER } + OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER AGGREGATE name ( aggregate_signature ) SET SCHEMA new_schema where aggregate_signature is: @@ -142,7 +142,7 @@ ALTER AGGREGATE name ( aggregate_signatu The recommended syntax for referencing an ordered-set aggregate is to write ORDER BY between the direct and aggregated argument specifications, in the same style as in - . However, it will also work to + CREATE AGGREGATE. However, it will also work to omit ORDER BY and just run the direct and aggregated argument specifications into a single list. In this abbreviated form, if VARIADIC "any" was used in both the direct and diff --git a/doc/src/sgml/ref/alter_collation.sgml b/doc/src/sgml/ref/alter_collation.sgml index bee6f0dd3ca1..65429aabe28b 100644 --- a/doc/src/sgml/ref/alter_collation.sgml +++ b/doc/src/sgml/ref/alter_collation.sgml @@ -21,10 +21,8 @@ PostgreSQL documentation -ALTER COLLATION name REFRESH VERSION - ALTER COLLATION name RENAME TO new_name -ALTER COLLATION name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER COLLATION name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER COLLATION name SET SCHEMA new_schema @@ -88,70 +86,9 @@ ALTER COLLATION name SET SCHEMA new_sche - - REFRESH VERSION - - - Update the collation's version. - See below. - - - - - Notes - - - When using collations provided by the ICU library, the ICU-specific version - of the collator is recorded in the system catalog when the collation object - is created. When the collation is used, the current version is - checked against the recorded version, and a warning is issued when there is - a mismatch, for example: - -WARNING: collation "xx-x-icu" has version mismatch -DETAIL: The collation in the database was created using version 1.2.3.4, but the operating system provides version 2.3.4.5. -HINT: Rebuild all objects affected by this collation and run ALTER COLLATION pg_catalog."xx-x-icu" REFRESH VERSION, or build PostgreSQL with the right library version. - - A change in collation definitions can lead to corrupt indexes and other - problems because the database system relies on stored objects having a - certain sort order. Generally, this should be avoided, but it can happen - in legitimate circumstances, such as when - using pg_upgrade to upgrade to server binaries linked - with a newer version of ICU. When this happens, all objects depending on - the collation should be rebuilt, for example, - using REINDEX. When that is done, the collation version - can be refreshed using the command ALTER COLLATION ... REFRESH - VERSION. This will update the system catalog to record the - current collator version and will make the warning go away. Note that this - does not actually check whether all affected objects have been rebuilt - correctly. - - - When using collations provided by libc and - PostgreSQL was built with the GNU C library, the - C library's version is used as a collation version. Since collation - definitions typically change only with GNU C library releases, this provides - some defense against corruption, but it is not completely reliable. - - - Currently, there is no version tracking for the database default collation. - - - - The following query can be used to identify all collations in the current - database that need to be refreshed and the objects that depend on them: - pg_collation_actual_version(c.oid) - ORDER BY 1, 2; -]]> - - Examples diff --git a/doc/src/sgml/ref/alter_conversion.sgml b/doc/src/sgml/ref/alter_conversion.sgml index c42bd8b3e404..a128f20f3e8a 100644 --- a/doc/src/sgml/ref/alter_conversion.sgml +++ b/doc/src/sgml/ref/alter_conversion.sgml @@ -22,7 +22,7 @@ PostgreSQL documentation ALTER CONVERSION name RENAME TO new_name -ALTER CONVERSION name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER CONVERSION name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER CONVERSION name SET SCHEMA new_schema diff --git a/doc/src/sgml/ref/alter_database.sgml b/doc/src/sgml/ref/alter_database.sgml index 7db878cf532c..81e37536a3f6 100644 --- a/doc/src/sgml/ref/alter_database.sgml +++ b/doc/src/sgml/ref/alter_database.sgml @@ -31,7 +31,7 @@ ALTER DATABASE name [ [ WITH ] name RENAME TO new_name -ALTER DATABASE name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER DATABASE name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER DATABASE name SET TABLESPACE new_tablespace diff --git a/doc/src/sgml/ref/alter_domain.sgml b/doc/src/sgml/ref/alter_domain.sgml index 8201cbb65fcd..2db53725139c 100644 --- a/doc/src/sgml/ref/alter_domain.sgml +++ b/doc/src/sgml/ref/alter_domain.sgml @@ -36,7 +36,7 @@ ALTER DOMAIN name ALTER DOMAIN name VALIDATE CONSTRAINT constraint_name ALTER DOMAIN name - OWNER TO { new_owner | CURRENT_USER | SESSION_USER } + OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER DOMAIN name RENAME TO new_name ALTER DOMAIN name @@ -80,7 +80,7 @@ ALTER DOMAIN name This form adds a new constraint to a domain using the same syntax as - . + CREATE DOMAIN. When a new constraint is added to a domain, all columns using that domain will be checked against the newly added constraint. These checks can be suppressed by adding the new constraint using the diff --git a/doc/src/sgml/ref/alter_event_trigger.sgml b/doc/src/sgml/ref/alter_event_trigger.sgml index 61919f7845db..ef5253bf37eb 100644 --- a/doc/src/sgml/ref/alter_event_trigger.sgml +++ b/doc/src/sgml/ref/alter_event_trigger.sgml @@ -23,7 +23,7 @@ PostgreSQL documentation ALTER EVENT TRIGGER name DISABLE ALTER EVENT TRIGGER name ENABLE [ REPLICA | ALWAYS ] -ALTER EVENT TRIGGER name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER EVENT TRIGGER name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER EVENT TRIGGER name RENAME TO new_name diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml index a2d405d6cdfb..38fd60128b78 100644 --- a/doc/src/sgml/ref/alter_extension.sgml +++ b/doc/src/sgml/ref/alter_extension.sgml @@ -212,11 +212,12 @@ ALTER EXTENSION name DROP IN, OUT, INOUT, or VARIADIC. If omitted, the default is IN. - Note that ALTER EXTENSION does not actually pay - any attention to OUT arguments, since only the input - arguments are needed to determine the function's identity. - So it is sufficient to list the IN, INOUT, - and VARIADIC arguments. + Note that ALTER EXTENSION does not actually pay any + attention to OUT arguments for functions and + aggregates (but not procedures), since only the input arguments are + needed to determine the function's identity. So it is sufficient to + list the IN, INOUT, and + VARIADIC arguments for functions and aggregates. @@ -251,7 +252,7 @@ ALTER EXTENSION name DROP The data type(s) of the operator's arguments (optionally schema-qualified). Write NONE for the missing argument - of a prefix or postfix operator. + of a prefix operator. diff --git a/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml b/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml index 14f3d616e71c..54f34c2c0151 100644 --- a/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml +++ b/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml @@ -25,7 +25,7 @@ ALTER FOREIGN DATA WRAPPER name [ HANDLER handler_function | NO HANDLER ] [ VALIDATOR validator_function | NO VALIDATOR ] [ OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ]) ] -ALTER FOREIGN DATA WRAPPER name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER FOREIGN DATA WRAPPER name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER FOREIGN DATA WRAPPER name RENAME TO new_name diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml index 0f11897c9977..7ca03f3ac9f1 100644 --- a/doc/src/sgml/ref/alter_foreign_table.sgml +++ b/doc/src/sgml/ref/alter_foreign_table.sgml @@ -53,7 +53,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] nameparent_table NO INHERIT parent_table - OWNER TO { new_owner | CURRENT_USER | SESSION_USER } + OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ]) @@ -71,7 +71,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name This form adds a new column to the foreign table, using the same syntax as - . + CREATE FOREIGN TABLE. 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. @@ -133,8 +133,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name This form sets the per-column statistics-gathering target for subsequent - operations. - See the similar form of + ANALYZE operations. + See the similar form of ALTER TABLE for more details. @@ -146,7 +146,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name This form sets or resets per-attribute options. - See the similar form of + See the similar form of ALTER TABLE for more details. @@ -159,7 +159,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name This form sets the storage mode for a column. - See the similar form of + See the similar form of ALTER TABLE for more details. Note that the storage mode has no effect unless the table's foreign-data wrapper chooses to pay attention to it. @@ -172,7 +172,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name This form adds a new constraint to a foreign table, using the same - syntax as . + syntax as CREATE FOREIGN TABLE. Currently only CHECK constraints are supported. @@ -181,7 +181,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name.) + in CREATE FOREIGN TABLE.) If the constraint is marked NOT VALID, then it isn't assumed to hold, but is only recorded for possible future use. @@ -216,7 +216,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name These forms configure the firing of trigger(s) belonging to the foreign - table. See the similar form of for more + table. See the similar form of ALTER TABLE for more details. @@ -239,7 +239,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name This form adds the target foreign table as a new child of the specified parent table. - See the similar form of + See the similar form of ALTER TABLE for more details. @@ -503,7 +503,7 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name - Refer to for a further description of valid + Refer to CREATE FOREIGN TABLE for a further description of valid parameters. diff --git a/doc/src/sgml/ref/alter_function.sgml b/doc/src/sgml/ref/alter_function.sgml index c8ae245f11a4..b7a1837b6bc1 100644 --- a/doc/src/sgml/ref/alter_function.sgml +++ b/doc/src/sgml/ref/alter_function.sgml @@ -26,7 +26,7 @@ ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] RENAME TO new_name ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] - OWNER TO { new_owner | CURRENT_USER | SESSION_USER } + OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] SET SCHEMA new_schema ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] diff --git a/doc/src/sgml/ref/alter_group.sgml b/doc/src/sgml/ref/alter_group.sgml index 39cc2b88cfa2..fa4a8df91249 100644 --- a/doc/src/sgml/ref/alter_group.sgml +++ b/doc/src/sgml/ref/alter_group.sgml @@ -27,6 +27,7 @@ ALTER GROUP role_specification DROP where role_specification can be: role_name + | CURRENT_ROLE | CURRENT_USER | SESSION_USER @@ -50,14 +51,14 @@ ALTER GROUP group_name RENAME TO group for this purpose.) These variants are effectively equivalent to granting or revoking membership in the role named as the group; so the preferred way to do this is to use - or - . + GRANT or + REVOKE. The third variant changes the name of the group. This is exactly equivalent to renaming the role with - . + ALTER ROLE. diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgml index a5e3b06ee493..214005a86c5f 100644 --- a/doc/src/sgml/ref/alter_index.sgml +++ b/doc/src/sgml/ref/alter_index.sgml @@ -25,6 +25,7 @@ ALTER INDEX [ IF EXISTS ] name RENA ALTER INDEX [ IF EXISTS ] name SET TABLESPACE tablespace_name ALTER INDEX name ATTACH PARTITION index_name ALTER INDEX name DEPENDS ON EXTENSION extension_name +ALTER INDEX name ALTER COLLATION collation_name REFRESH VERSION ALTER INDEX [ IF EXISTS ] name SET ( storage_parameter [= value] [, ... ] ) ALTER INDEX [ IF EXISTS ] name RESET ( storage_parameter [, ... ] ) ALTER INDEX [ IF EXISTS ] name ALTER [ COLUMN ] column_number @@ -81,7 +82,7 @@ ALTER INDEX ALL IN TABLESPACE name this command, use ALTER DATABASE or explicit ALTER INDEX invocations instead if desired. See also - . + CREATE TABLESPACE. @@ -112,17 +113,31 @@ ALTER INDEX ALL IN TABLESPACE name + + ALTER COLLATION collation_name REFRESH VERSION + + + Silences warnings about mismatched collation versions, by declaring + that the index is compatible with the current collation definition. + Be aware that incorrect use of this command can hide index corruption. + If you don't know whether a collation's definition has changed + incompatibly, is a safe alternative. + See for more information. + + + + SET ( storage_parameter [= value] [, ... ] ) This form changes one or more index-method-specific storage parameters for the index. See - + CREATE INDEX for details on the available parameters. Note that the index contents will not be modified immediately by this command; depending on the parameter you might need to rebuild the index with - + REINDEX to get the desired effects. @@ -144,7 +159,7 @@ ALTER INDEX ALL IN TABLESPACE name This form sets the per-column statistics-gathering target for - subsequent operations, though can + subsequent ANALYZE operations, though can be used only on index columns that are defined as an expression. Since expressions lack a unique name, we refer to them using the ordinal number of the index column. @@ -252,7 +267,7 @@ ALTER INDEX ALL IN TABLESPACE name These operations are also possible using - . + ALTER TABLE. ALTER INDEX is in fact just an alias for the forms of ALTER TABLE that apply to indexes. diff --git a/doc/src/sgml/ref/alter_language.sgml b/doc/src/sgml/ref/alter_language.sgml index eac63dec1322..0b61c18aee36 100644 --- a/doc/src/sgml/ref/alter_language.sgml +++ b/doc/src/sgml/ref/alter_language.sgml @@ -22,7 +22,7 @@ PostgreSQL documentation ALTER [ PROCEDURAL ] LANGUAGE name RENAME TO new_name -ALTER [ PROCEDURAL ] LANGUAGE name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER [ PROCEDURAL ] LANGUAGE name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } diff --git a/doc/src/sgml/ref/alter_large_object.sgml b/doc/src/sgml/ref/alter_large_object.sgml index 356f8a8eabf4..17ea1491ba37 100644 --- a/doc/src/sgml/ref/alter_large_object.sgml +++ b/doc/src/sgml/ref/alter_large_object.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -ALTER LARGE OBJECT large_object_oid OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER LARGE OBJECT large_object_oid OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } diff --git a/doc/src/sgml/ref/alter_materialized_view.sgml b/doc/src/sgml/ref/alter_materialized_view.sgml index 7321183dd0db..bf379db77e38 100644 --- a/doc/src/sgml/ref/alter_materialized_view.sgml +++ b/doc/src/sgml/ref/alter_materialized_view.sgml @@ -44,7 +44,7 @@ ALTER MATERIALIZED VIEW ALL IN TABLESPACE namestorage_parameter [= value] [, ... ] ) RESET ( storage_parameter [, ... ] ) - OWNER TO { new_owner | CURRENT_USER | SESSION_USER } + OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } @@ -72,7 +72,8 @@ ALTER MATERIALIZED VIEW ALL IN TABLESPACE nameALTER MATERIALIZED VIEW are a subset of those available for ALTER TABLE, and have the same meaning when used for - materialized views. See the descriptions for + materialized views. See the descriptions for + ALTER TABLE for details. diff --git a/doc/src/sgml/ref/alter_opclass.sgml b/doc/src/sgml/ref/alter_opclass.sgml index 59a64caa4fad..b1db459b113c 100644 --- a/doc/src/sgml/ref/alter_opclass.sgml +++ b/doc/src/sgml/ref/alter_opclass.sgml @@ -25,7 +25,7 @@ ALTER OPERATOR CLASS name USING index_method - OWNER TO { new_owner | CURRENT_USER | SESSION_USER } + OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER OPERATOR CLASS name USING index_method SET SCHEMA new_schema diff --git a/doc/src/sgml/ref/alter_operator.sgml b/doc/src/sgml/ref/alter_operator.sgml index b3bfa9ccbe97..ad90c137f149 100644 --- a/doc/src/sgml/ref/alter_operator.sgml +++ b/doc/src/sgml/ref/alter_operator.sgml @@ -21,13 +21,13 @@ PostgreSQL documentation -ALTER OPERATOR name ( { left_type | NONE } , { right_type | NONE } ) - OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER OPERATOR name ( { left_type | NONE } , right_type ) + OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } -ALTER OPERATOR name ( { left_type | NONE } , { right_type | NONE } ) +ALTER OPERATOR name ( { left_type | NONE } , right_type ) SET SCHEMA new_schema -ALTER OPERATOR name ( { left_type | NONE } , { right_type | NONE } ) +ALTER OPERATOR name ( { left_type | NONE } , right_type ) SET ( { RESTRICT = { res_proc | NONE } | JOIN = { join_proc | NONE } } [, ... ] ) @@ -79,8 +79,7 @@ ALTER OPERATOR name ( { left_typeright_type - The data type of the operator's right operand; write - NONE if the operator has no right operand. + The data type of the operator's right operand. diff --git a/doc/src/sgml/ref/alter_opfamily.sgml b/doc/src/sgml/ref/alter_opfamily.sgml index 4ac1cca95a3f..b3b5d61a852e 100644 --- a/doc/src/sgml/ref/alter_opfamily.sgml +++ b/doc/src/sgml/ref/alter_opfamily.sgml @@ -37,7 +37,7 @@ ALTER OPERATOR FAMILY name USING index_method - OWNER TO { new_owner | CURRENT_USER | SESSION_USER } + OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER OPERATOR FAMILY name USING index_method SET SCHEMA new_schema @@ -141,7 +141,7 @@ ALTER OPERATOR FAMILY name USING name ON table_name RENAME TO new_name ALTER POLICY name ON table_name - [ TO { role_name | PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ] + [ TO { role_name | PUBLIC | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...] ] [ USING ( using_expression ) ] [ WITH CHECK ( check_expression ) ] diff --git a/doc/src/sgml/ref/alter_procedure.sgml b/doc/src/sgml/ref/alter_procedure.sgml index dae80076d953..5c176fb5d876 100644 --- a/doc/src/sgml/ref/alter_procedure.sgml +++ b/doc/src/sgml/ref/alter_procedure.sgml @@ -26,7 +26,7 @@ ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] RENAME TO new_name ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] - OWNER TO { new_owner | CURRENT_USER | SESSION_USER } + OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] SET SCHEMA new_schema ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] @@ -81,8 +81,9 @@ ALTER PROCEDURE name [ ( [ [ name ADD TABLE [ ALTER PUBLICATION name SET TABLE [ ONLY ] table_name [ * ] [, ...] ALTER PUBLICATION name DROP TABLE [ ONLY ] table_name [ * ] [, ...] ALTER PUBLICATION name SET ( publication_parameter [= value] [, ... ] ) -ALTER PUBLICATION name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER PUBLICATION name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER PUBLICATION name RENAME TO new_name diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml index 0ada4fe1da78..c9047374effd 100644 --- a/doc/src/sgml/ref/alter_role.sgml +++ b/doc/src/sgml/ref/alter_role.sgml @@ -53,6 +53,7 @@ ALTER ROLE name RESOURCE GROUP {where role_specification can be: role_name + | CURRENT_ROLE | CURRENT_USER | SESSION_USER @@ -69,15 +70,17 @@ ALTER ROLE name RESOURCE GROUP { The first variant of this command listed in the synopsis can change many of the role attributes that can be specified in - . + CREATE ROLE. (All the possible attributes are covered, except that there are no options for adding or removing memberships; use - and - for that.) + GRANT and + REVOKE for that.) Attributes not mentioned in the command retain their previous settings. Database superusers can change any of these settings for any role. Roles having CREATEROLE privilege can change any of these - settings, but only for non-superuser and non-replication roles. + settings except SUPERUSER, REPLICATION, + and BYPASSRLS; but only for non-superuser and + non-replication roles. Ordinary roles can only change their own password. @@ -109,8 +112,8 @@ ALTER ROLE name RESOURCE GROUP {postgresql.conf or has been received from the postgres command line. This only happens at login time; executing - or - does not cause new + SET ROLE or + SET SESSION AUTHORIZATION does not cause new configuration values to be set. Settings set for all databases are overridden by database-specific settings attached to a role. Settings for specific databases or specific roles override @@ -141,6 +144,7 @@ ALTER ROLE name RESOURCE GROUP { + CURRENT_ROLE CURRENT_USER @@ -181,7 +185,7 @@ ALTER ROLE name RESOURCE GROUP { These clauses alter attributes originally set by - . For more information, see the + CREATE ROLE. For more information, see the CREATE ROLE reference page. @@ -252,8 +256,8 @@ ALTER ROLE name RESOURCE GROUP { Role-specific variable settings take effect only at login; - and - + SET ROLE and + SET SESSION AUTHORIZATION do not process role-specific variable settings. @@ -271,14 +275,14 @@ ALTER ROLE name RESOURCE GROUP {Notes - Use - to add new roles, and to remove a role. + Use CREATE ROLE + to add new roles, and DROP ROLE to remove a role. ALTER ROLE cannot change a role's memberships. - Use and - + Use GRANT and + REVOKE to do that. diff --git a/doc/src/sgml/ref/alter_routine.sgml b/doc/src/sgml/ref/alter_routine.sgml index d1699691e10f..36acaff3198d 100644 --- a/doc/src/sgml/ref/alter_routine.sgml +++ b/doc/src/sgml/ref/alter_routine.sgml @@ -26,7 +26,7 @@ ALTER ROUTINE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] RENAME TO new_name ALTER ROUTINE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] - OWNER TO { new_owner | CURRENT_USER | SESSION_USER } + OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER ROUTINE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] SET SCHEMA new_schema ALTER ROUTINE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] diff --git a/doc/src/sgml/ref/alter_schema.sgml b/doc/src/sgml/ref/alter_schema.sgml index 2937214026ec..04624c5a5eb0 100644 --- a/doc/src/sgml/ref/alter_schema.sgml +++ b/doc/src/sgml/ref/alter_schema.sgml @@ -22,7 +22,7 @@ PostgreSQL documentation ALTER SCHEMA name RENAME TO new_name -ALTER SCHEMA name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER SCHEMA name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } diff --git a/doc/src/sgml/ref/alter_sequence.sgml b/doc/src/sgml/ref/alter_sequence.sgml index bfd20af6d3d5..3cd9ece49f22 100644 --- a/doc/src/sgml/ref/alter_sequence.sgml +++ b/doc/src/sgml/ref/alter_sequence.sgml @@ -31,7 +31,7 @@ ALTER SEQUENCE [ IF EXISTS ] name [ RESTART [ [ WITH ] restart ] ] [ CACHE cache ] [ [ NO ] CYCLE ] [ OWNED BY { table_name.column_name | NONE } ] -ALTER SEQUENCE [ IF EXISTS ] name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER SEQUENCE [ IF EXISTS ] name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER SEQUENCE [ IF EXISTS ] name RENAME TO new_name ALTER SEQUENCE [ IF EXISTS ] name SET SCHEMA new_schema diff --git a/doc/src/sgml/ref/alter_server.sgml b/doc/src/sgml/ref/alter_server.sgml index 17e55b093e93..186f38b5f82e 100644 --- a/doc/src/sgml/ref/alter_server.sgml +++ b/doc/src/sgml/ref/alter_server.sgml @@ -23,7 +23,7 @@ PostgreSQL documentation ALTER SERVER name [ VERSION 'new_version' ] [ OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ] ) ] -ALTER SERVER name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER SERVER name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER SERVER name RENAME TO new_name diff --git a/doc/src/sgml/ref/alter_statistics.sgml b/doc/src/sgml/ref/alter_statistics.sgml index be4c3f1f0576..ce6cdf2bb1ec 100644 --- a/doc/src/sgml/ref/alter_statistics.sgml +++ b/doc/src/sgml/ref/alter_statistics.sgml @@ -23,7 +23,7 @@ PostgreSQL documentation -ALTER STATISTICS name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER STATISTICS name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER STATISTICS name RENAME TO new_name ALTER STATISTICS name SET SCHEMA new_schema ALTER STATISTICS name SET STATISTICS new_target @@ -99,9 +99,10 @@ ALTER STATISTICS name SET STATISTIC The statistic-gathering target for this statistics object for subsequent - operations. + ANALYZE operations. The target can be set in the range 0 to 10000; alternatively, set it - to -1 to revert to using the system default statistics + to -1 to revert to using the maximum of the statistics target of the + referenced columns, if set, or the system default statistics target (). For more information on the use of statistics by the PostgreSQL query planner, refer to diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml index 81c4e70cdf45..db5e59f707c6 100644 --- a/doc/src/sgml/ref/alter_subscription.sgml +++ b/doc/src/sgml/ref/alter_subscription.sgml @@ -27,7 +27,7 @@ ALTER SUBSCRIPTION name REFRESH PUB ALTER SUBSCRIPTION name ENABLE ALTER SUBSCRIPTION name DISABLE ALTER SUBSCRIPTION name SET ( subscription_parameter [= value] [, ... ] ) -ALTER SUBSCRIPTION name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER SUBSCRIPTION name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER SUBSCRIPTION name RENAME TO new_name @@ -165,8 +165,9 @@ ALTER SUBSCRIPTION name RENAME TO < . See there for more information. The parameters that can be altered are slot_name, - synchronous_commit, and - binary. + synchronous_commit, + binary, and + streaming. diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 65fa52d21822..6fec52efa4a4 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -94,7 +94,7 @@ ALTER TABLE name NO INHERIT parent_table OF type_name NOT OF - OWNER TO { new_owner | CURRENT_USER | SESSION_USER } + OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } REPLICA IDENTITY { DEFAULT | USING INDEX index_name | FULL | NOTHING } and partition_bound_spec is: @@ -223,7 +223,7 @@ Where column_reference_storage_directive is: This form adds a new column to the table, using the same syntax as - . If IF NOT EXISTS + CREATE TABLE. If IF NOT EXISTS is specified and a column already exists with this name, no error is thrown. @@ -339,7 +339,7 @@ Where column_reference_storage_directive is: These forms change whether a column is an identity column or change the generation attribute of an existing identity column. - See for details. + See CREATE TABLE for details. Like SET DEFAULT, these forms only affect the behavior of subsequent INSERT and UPDATE commands; they do not cause rows @@ -361,7 +361,7 @@ Where column_reference_storage_directive is: These forms alter the sequence that underlies an existing identity column. sequence_option is an option - supported by such + supported by ALTER SEQUENCE such as INCREMENT BY. @@ -373,7 +373,7 @@ Where column_reference_storage_directive is: This form sets the per-column statistics-gathering target for subsequent - operations. + ANALYZE operations. The target can be set in the range 0 to 10000; alternatively, set it to -1 to revert to using the system default statistics target (). @@ -397,7 +397,7 @@ Where column_reference_storage_directive is: defined per-attribute options are n_distinct and 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 @@ -459,7 +459,7 @@ Where column_reference_storage_directive is: This form adds a new constraint to a table using the same constraint - syntax as , plus the option NOT + syntax as CREATE TABLE, plus the option NOT VALID, which is currently only allowed for foreign key and CHECK constraints. @@ -493,7 +493,7 @@ Where column_reference_storage_directive is: Additional restrictions apply when unique or primary key constraints - are added to partitioned tables; see . + are added to partitioned tables; see CREATE TABLE. Also, foreign key constraints on partitioned tables may not be declared NOT VALID at present. @@ -669,7 +669,7 @@ Where column_reference_storage_directive is: even if row level security is disabled. In this case, the policies will not be applied and the policies will be ignored. See also - . + CREATE POLICY. @@ -684,7 +684,7 @@ Where column_reference_storage_directive is: disabled (the default) then row level security will not be applied when the user is the table owner. See also - . + CREATE POLICY. @@ -694,7 +694,7 @@ Where column_reference_storage_directive is: This form selects the default index for future - + CLUSTER operations. It does not actually re-cluster the table. @@ -708,7 +708,7 @@ Where column_reference_storage_directive is: This form removes the most recently used - + CLUSTER index specification from the table. This affects future cluster operations that don't specify an index. @@ -750,7 +750,7 @@ Where column_reference_storage_directive is: When applied to a partitioned table, nothing is moved, but any partitions created afterwards with CREATE TABLE PARTITION OF will use that tablespace, - unless the TABLESPACE clause is used to override it. + unless overridden by a TABLESPACE clause. @@ -766,7 +766,7 @@ Where column_reference_storage_directive is: information_schema relations are not considered part of the system catalogs and will be moved. See also - . + CREATE TABLESPACE. @@ -788,12 +788,12 @@ Where column_reference_storage_directive is: This form changes one or more storage parameters for the table. See in the - documentation + CREATE TABLE documentation for details on the available parameters. Note that the table contents will not be modified immediately by this command; depending on the parameter you might need to rewrite the table to get the desired effects. - That can be done with VACUUM - FULL, or one of the forms + That can be done with VACUUM + FULL, CLUSTER or one of the forms of ALTER TABLE that forces a table rewrite. For planner related parameters, changes will take effect from the next time the table is locked so currently executing queries will not be @@ -892,7 +892,7 @@ Where column_reference_storage_directive is: - + REPLICA IDENTITY @@ -959,7 +959,7 @@ Where column_reference_storage_directive is: A partition using FOR VALUES uses same syntax for partition_bound_spec as - . The partition bound specification + CREATE TABLE. The partition bound specification must correspond to the partitioning strategy and partition key of the target table. The table to be attached must have all the same columns as the target table and no more; moreover, the column types must also @@ -970,7 +970,7 @@ Where column_reference_storage_directive is: from the parent table will be created in the partition, if they don't already exist. If any of the CHECK constraints of the table being - attached is marked NO INHERIT, the command will fail; + attached are marked NO INHERIT, the command will fail; such constraints must be recreated without the NO INHERIT clause. diff --git a/doc/src/sgml/ref/alter_tablespace.sgml b/doc/src/sgml/ref/alter_tablespace.sgml index 356fb9f93f32..6de80746d564 100644 --- a/doc/src/sgml/ref/alter_tablespace.sgml +++ b/doc/src/sgml/ref/alter_tablespace.sgml @@ -22,7 +22,7 @@ PostgreSQL documentation ALTER TABLESPACE name RENAME TO new_name -ALTER TABLESPACE name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER TABLESPACE name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER TABLESPACE name SET ( tablespace_option = value [, ... ] ) ALTER TABLESPACE name RESET ( tablespace_option [, ... ] ) diff --git a/doc/src/sgml/ref/alter_trigger.sgml b/doc/src/sgml/ref/alter_trigger.sgml index 6d4784c82f19..43a7da4f0bcf 100644 --- a/doc/src/sgml/ref/alter_trigger.sgml +++ b/doc/src/sgml/ref/alter_trigger.sgml @@ -93,7 +93,7 @@ ALTER TRIGGER name ON The ability to temporarily enable or disable a trigger is provided by - , not by + ALTER TABLE, not by ALTER TRIGGER, because ALTER TRIGGER has no convenient way to express the option of enabling or disabling all of a table's triggers at once. diff --git a/doc/src/sgml/ref/alter_tsconfig.sgml b/doc/src/sgml/ref/alter_tsconfig.sgml index ebe0b94b27e5..8fafcd3bbd82 100644 --- a/doc/src/sgml/ref/alter_tsconfig.sgml +++ b/doc/src/sgml/ref/alter_tsconfig.sgml @@ -32,7 +32,7 @@ ALTER TEXT SEARCH CONFIGURATION name ALTER TEXT SEARCH CONFIGURATION name DROP MAPPING [ IF EXISTS ] FOR token_type [, ... ] ALTER TEXT SEARCH CONFIGURATION name RENAME TO new_name -ALTER TEXT SEARCH CONFIGURATION name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER TEXT SEARCH CONFIGURATION name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER TEXT SEARCH CONFIGURATION name SET SCHEMA new_schema diff --git a/doc/src/sgml/ref/alter_tsdictionary.sgml b/doc/src/sgml/ref/alter_tsdictionary.sgml index b29865e11e92..d1923ef1609f 100644 --- a/doc/src/sgml/ref/alter_tsdictionary.sgml +++ b/doc/src/sgml/ref/alter_tsdictionary.sgml @@ -25,7 +25,7 @@ ALTER TEXT SEARCH DICTIONARY name ( option [ = value ] [, ... ] ) ALTER TEXT SEARCH DICTIONARY name RENAME TO new_name -ALTER TEXT SEARCH DICTIONARY name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER TEXT SEARCH DICTIONARY name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER TEXT SEARCH DICTIONARY name SET SCHEMA new_schema diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml index f015fcd2689b..64bf266373d4 100644 --- a/doc/src/sgml/ref/alter_type.sgml +++ b/doc/src/sgml/ref/alter_type.sgml @@ -23,7 +23,7 @@ PostgreSQL documentation -ALTER TYPE name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER TYPE name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER TYPE name RENAME TO new_name ALTER TYPE name SET SCHEMA new_schema ALTER TYPE name RENAME ATTRIBUTE attribute_name TO new_attribute_name [ CASCADE | RESTRICT ] @@ -90,7 +90,7 @@ ALTER TYPE name SET ( This form adds a new attribute to a composite type, using the same syntax as - . + CREATE TYPE. diff --git a/doc/src/sgml/ref/alter_user.sgml b/doc/src/sgml/ref/alter_user.sgml index 9ee61a41e47a..d6a77dccbaf5 100644 --- a/doc/src/sgml/ref/alter_user.sgml +++ b/doc/src/sgml/ref/alter_user.sgml @@ -47,6 +47,7 @@ ALTER USER { role_specification | A where role_specification can be: role_name + | CURRENT_ROLE | CURRENT_USER | SESSION_USER @@ -57,7 +58,7 @@ ALTER USER { role_specification | A ALTER USER is now an alias for - . + ALTER ROLE. diff --git a/doc/src/sgml/ref/alter_user_mapping.sgml b/doc/src/sgml/ref/alter_user_mapping.sgml index 7a9b5a188af4..ee5aee9bc9e5 100644 --- a/doc/src/sgml/ref/alter_user_mapping.sgml +++ b/doc/src/sgml/ref/alter_user_mapping.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -ALTER USER MAPPING FOR { user_name | USER | CURRENT_USER | SESSION_USER | PUBLIC } +ALTER USER MAPPING FOR { user_name | USER | CURRENT_ROLE | CURRENT_USER | SESSION_USER | PUBLIC } SERVER server_name OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ] ) @@ -51,7 +51,7 @@ ALTER USER MAPPING FOR { user_name user_name - User name of the mapping. CURRENT_USER + User name of the mapping. CURRENT_ROLE, CURRENT_USER, and USER match the name of the current user. PUBLIC is used to match all present and future user names in the system. diff --git a/doc/src/sgml/ref/alter_view.sgml b/doc/src/sgml/ref/alter_view.sgml index e8d9e11e0f6f..98c312c5bf6b 100644 --- a/doc/src/sgml/ref/alter_view.sgml +++ b/doc/src/sgml/ref/alter_view.sgml @@ -23,7 +23,7 @@ PostgreSQL documentation ALTER VIEW [ IF EXISTS ] name ALTER [ COLUMN ] column_name SET DEFAULT expression ALTER VIEW [ IF EXISTS ] name ALTER [ COLUMN ] column_name DROP DEFAULT -ALTER VIEW [ IF EXISTS ] name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER VIEW [ IF EXISTS ] name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ALTER VIEW [ IF EXISTS ] name RENAME [ COLUMN ] column_name TO new_column_name ALTER VIEW [ IF EXISTS ] name RENAME TO new_name ALTER VIEW [ IF EXISTS ] name SET SCHEMA new_schema diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml index 5ac3ba832193..7d816c87c603 100644 --- a/doc/src/sgml/ref/analyze.sgml +++ b/doc/src/sgml/ref/analyze.sgml @@ -174,7 +174,7 @@ ANALYZE [ VERBOSE ] [ table_and_columns + strategy for read-mostly databases is to run VACUUM and ANALYZE once a day during a low-usage time of day. (This will not be sufficient if there is heavy update activity.) @@ -205,7 +205,7 @@ ANALYZE [ VERBOSE ] [ table_and_columnsANALYZE is run, even if the actual table contents did not change. This might result in small changes in the planner's estimated costs shown by - . + EXPLAIN. In rare situations, this non-determinism will cause the planner's choices of query plans to change after ANALYZE is run. To avoid this, raise the amount of statistics collected by @@ -216,8 +216,8 @@ ANALYZE [ VERBOSE ] [ table_and_columns configuration variable, or on a column-by-column basis by setting the per-column statistics - target with ALTER TABLE ... ALTER COLUMN ... SET - STATISTICS (see ). + target with ALTER TABLE ... ALTER COLUMN ... SET + STATISTICS. The target value sets the maximum number of entries in the most-common-value list and the maximum number of bins in the histogram. The default target value @@ -246,8 +246,7 @@ ANALYZE [ VERBOSE ] [ table_and_columnsALTER TABLE ... ALTER COLUMN ... SET (n_distinct = ...) - (see ). + ALTER TABLE ... ALTER COLUMN ... SET (n_distinct = ...). diff --git a/doc/src/sgml/ref/begin.sgml b/doc/src/sgml/ref/begin.sgml index c23bbfb4e711..016b02148741 100644 --- a/doc/src/sgml/ref/begin.sgml +++ b/doc/src/sgml/ref/begin.sgml @@ -37,9 +37,9 @@ BEGIN [ WORK | TRANSACTION ] [ transaction_mode BEGIN initiates a transaction block, that is, all statements after a BEGIN command will be - executed in a single transaction until an explicit or is given. + executed in a single transaction until an explicit COMMIT or ROLLBACK is given. By default (without BEGIN), PostgreSQL executes transactions in autocommit mode, that is, each @@ -60,7 +60,7 @@ BEGIN [ WORK | TRANSACTION ] [ transaction_mode If the isolation level, read/write mode, or deferrable mode is specified, the new transaction has those characteristics, as if - + SET TRANSACTION was executed. @@ -90,13 +90,13 @@ BEGIN [ WORK | TRANSACTION ] [ transaction_modeNotes - has the same functionality + START TRANSACTION has the same functionality as BEGIN. - Use or - + Use COMMIT or + ROLLBACK to terminate a transaction block. @@ -131,7 +131,7 @@ BEGIN; BEGIN is a PostgreSQL language extension. It is equivalent to the SQL-standard command - , whose reference page + START TRANSACTION, whose reference page contains additional compatibility information. diff --git a/doc/src/sgml/ref/close.sgml b/doc/src/sgml/ref/close.sgml index e464df1965d9..32d20edd6aa4 100644 --- a/doc/src/sgml/ref/close.sgml +++ b/doc/src/sgml/ref/close.sgml @@ -84,7 +84,7 @@ CLOSE { name | ALL } PostgreSQL does not have an explicit OPEN cursor statement; a cursor is considered open when it is declared. Use the - + DECLARE statement to declare a cursor. diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml index 978a6a5acef2..e6d3ce159562 100644 --- a/doc/src/sgml/ref/cluster.sgml +++ b/doc/src/sgml/ref/cluster.sgml @@ -57,7 +57,7 @@ CLUSTER [VERBOSE] CLUSTER table_name reclusters the table using the same index as before. You can also use the CLUSTER or SET WITHOUT CLUSTER - forms of to set the index to be used for + forms of ALTER TABLE to set the index to be used for future cluster operations, or to clear any previous setting. @@ -170,7 +170,7 @@ CLUSTER [VERBOSE] Because the planner records statistics about the ordering of - tables, it is advisable to run + tables, it is advisable to run ANALYZE on the newly clustered table. Otherwise, the planner might make poor choices of query plans. diff --git a/doc/src/sgml/ref/clusterdb.sgml b/doc/src/sgml/ref/clusterdb.sgml index 177856ca74d2..c838b22c4405 100644 --- a/doc/src/sgml/ref/clusterdb.sgml +++ b/doc/src/sgml/ref/clusterdb.sgml @@ -90,12 +90,15 @@ PostgreSQL documentation - Specifies the name of the database to be clustered. - If this is not specified and (or - ) is not used, the database name is read + Specifies the name of the database to be clustered, + when / is not used. + If this is not specified, the database name is read from the environment variable PGDATABASE. If that is not set, the user name specified for the connection is - used. + used. The dbname can be a connection string. If so, + connection string parameters will override any conflicting command + line options. @@ -246,10 +249,16 @@ PostgreSQL documentation - Specifies the name of the database to connect to discover what other - databases should be clustered. If not specified, the - postgres database will be used, - and if that does not exist, template1 will be used. + Specifies the name of the database to connect to to discover which + databases should be clustered, + when / is used. + If not specified, the postgres database will be used, + or if that does not exist, template1 will be used. + This can be a connection + string. If so, connection string parameters will override any + conflicting command line options. Also, connection string parameters + other than the database name itself will be re-used when connecting + to other databases. diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index ade77e4a89d8..1e04757fd7e9 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -179,11 +179,12 @@ COMMENT ON argument: IN, OUT, INOUT, or VARIADIC. If omitted, the default is IN. - Note that COMMENT does not actually pay - any attention to OUT arguments, since only the input - arguments are needed to determine the function's identity. - So it is sufficient to list the IN, INOUT, - and VARIADIC arguments. + Note that COMMENT does not actually pay any attention + to OUT arguments for functions and aggregates (but + not procedures), since only the input arguments are needed to determine + the function's identity. So it is sufficient to list the + IN, INOUT, and + VARIADIC arguments for functions and aggregates. @@ -225,7 +226,7 @@ COMMENT ON The data type(s) of the operator's arguments (optionally schema-qualified). Write NONE for the missing argument - of a prefix or postfix operator. + of a prefix operator. @@ -307,7 +308,7 @@ COMMENT ON TABLE mytable IS NULL; Some more examples: -COMMENT ON ACCESS METHOD rtree IS 'R-Tree access method'; +COMMENT ON ACCESS METHOD gin IS 'GIN index access method'; COMMENT ON AGGREGATE my_aggregate (double precision) IS 'Computes sample variance'; COMMENT ON CAST (text AS int4) IS 'Allow casts from text to int4'; COMMENT ON COLLATION "fr_CA" IS 'Canadian French'; @@ -317,6 +318,7 @@ COMMENT ON CONSTRAINT bar_col_cons ON bar IS 'Constrains column col'; COMMENT ON CONSTRAINT dom_col_constr ON DOMAIN dom IS 'Constrains col of domain'; COMMENT ON DATABASE my_database IS 'Development Database'; COMMENT ON DOMAIN my_domain IS 'Email Address Domain'; +COMMENT ON EVENT TRIGGER abort_ddl IS 'Aborts all DDL commands'; COMMENT ON EXTENSION hstore IS 'implements the hstore data type'; COMMENT ON FOREIGN DATA WRAPPER mywrapper IS 'my foreign data wrapper'; COMMENT ON FOREIGN TABLE my_foreign_table IS 'Employee Information in other database'; @@ -331,12 +333,15 @@ COMMENT ON OPERATOR CLASS int4ops USING btree IS '4 byte integer operators for b COMMENT ON OPERATOR FAMILY integer_ops USING btree IS 'all integer operators for btrees'; COMMENT ON POLICY my_policy ON mytable IS 'Filter rows by users'; COMMENT ON PROCEDURE my_proc (integer, integer) IS 'Runs a report'; +COMMENT ON PUBLICATION alltables IS 'Publishes all operations on all tables'; COMMENT ON ROLE my_role IS 'Administration group for finance tables'; +COMMENT ON ROUTINE my_routine (integer, integer) IS 'Runs a routine (which is a function or procedure)'; COMMENT ON RULE my_rule ON my_table IS 'Logs updates of employee records'; COMMENT ON SCHEMA my_schema IS 'Departmental data'; COMMENT ON SEQUENCE my_sequence IS 'Used to generate primary keys'; COMMENT ON SERVER myserver IS 'my foreign server'; COMMENT ON STATISTICS my_statistics IS 'Improves planner row estimations'; +COMMENT ON SUBSCRIPTION alltables IS 'Subscription for all operations on all tables'; COMMENT ON TABLE my_schema.my_table IS 'Employee Information'; COMMENT ON TABLESPACE my_tablespace IS 'Tablespace for indexes'; COMMENT ON TEXT SEARCH CONFIGURATION my_config IS 'Special word filtering'; diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml index 94b59ce145f1..86ce1ff60282 100644 --- a/doc/src/sgml/ref/copy.sgml +++ b/doc/src/sgml/ref/copy.sgml @@ -118,9 +118,11 @@ COPY { table_name [ ( query - A , , - , or - command whose results are to be + A SELECT, + VALUES, + INSERT, + UPDATE, or + DELETE command whose results are to be copied. Note that parentheses are required around the query. diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml index c3f9b1383c75..4e6204a1f8a9 100644 --- a/doc/src/sgml/ref/create_aggregate.sgml +++ b/doc/src/sgml/ref/create_aggregate.sgml @@ -642,7 +642,7 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; The meanings of PARALLEL SAFE, PARALLEL RESTRICTED, and PARALLEL UNSAFE are the same as - in . An aggregate will not be + in CREATE FUNCTION. An aggregate will not be considered for parallelization if it is marked PARALLEL UNSAFE (which is the default!) or PARALLEL RESTRICTED. Note that the parallel-safety markings of the aggregate's support diff --git a/doc/src/sgml/ref/create_cast.sgml b/doc/src/sgml/ref/create_cast.sgml index 2b4d4d557328..bad75bc1dce5 100644 --- a/doc/src/sgml/ref/create_cast.sgml +++ b/doc/src/sgml/ref/create_cast.sgml @@ -304,7 +304,7 @@ SELECT CAST ( 2 AS numeric ) + 4.0; Notes - Use to remove user-defined casts. + Use DROP CAST to remove user-defined casts. diff --git a/doc/src/sgml/ref/create_collation.sgml b/doc/src/sgml/ref/create_collation.sgml index 58f5f0cd63a2..b97842071f92 100644 --- a/doc/src/sgml/ref/create_collation.sgml +++ b/doc/src/sgml/ref/create_collation.sgml @@ -27,7 +27,6 @@ CREATE COLLATION [ IF NOT EXISTS ] name ( [ LC_CTYPE = lc_ctype, ] [ PROVIDER = provider, ] [ DETERMINISTIC = boolean, ] - [ VERSION = version ] ) CREATE COLLATION [ IF NOT EXISTS ] name FROM existing_collation @@ -149,26 +148,6 @@ CREATE COLLATION [ IF NOT EXISTS ] name FROM - - version - - - - Specifies the version string to store with the collation. Normally, - this should be omitted, which will cause the version to be computed - from the actual version of the collation as provided by the operating - system. This option is intended to be used - by pg_upgrade for copying the version from an - existing installation. - - - - See also for how to handle - collation version mismatches. - - - - existing_collation diff --git a/doc/src/sgml/ref/create_database.sgml b/doc/src/sgml/ref/create_database.sgml index d116b321bce9..41cb4068ec2f 100644 --- a/doc/src/sgml/ref/create_database.sgml +++ b/doc/src/sgml/ref/create_database.sgml @@ -139,7 +139,7 @@ CREATE DATABASE name Collation order (LC_COLLATE) to use in the new database. - This affects the sort order applied to strings, e.g. in queries with + This affects the sort order applied to strings, e.g., in queries with ORDER BY, as well as the order used in indexes on text columns. The default is to use the collation order of the template database. See below for additional restrictions. @@ -151,7 +151,7 @@ CREATE DATABASE name Character classification (LC_CTYPE) to use in the new - database. This affects the categorization of characters, e.g. lower, + database. This affects the categorization of characters, e.g., lower, upper and digit. The default is to use the character classification of the template database. See below for additional restrictions. @@ -226,7 +226,7 @@ CREATE DATABASE name - Use to remove a database. + Use DROP DATABASE to remove a database. @@ -235,9 +235,9 @@ CREATE DATABASE name - Database-level configuration parameters (set via ) and database-level permissions (set via - ) are not copied from the template database. + Database-level configuration parameters (set via ALTER DATABASE) and database-level permissions (set via + GRANT) are not copied from the template database. diff --git a/doc/src/sgml/ref/create_event_trigger.sgml b/doc/src/sgml/ref/create_event_trigger.sgml index 52ba746166be..becd31bcadf7 100644 --- a/doc/src/sgml/ref/create_event_trigger.sgml +++ b/doc/src/sgml/ref/create_event_trigger.sgml @@ -86,7 +86,7 @@ CREATE EVENT TRIGGER name A list of values for the associated filter_variable for which the trigger should fire. For TAG, this means a - list of command tags (e.g. 'DROP FUNCTION'). + list of command tags (e.g., 'DROP FUNCTION'). diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml index 2e2243601f48..7d9f25244926 100644 --- a/doc/src/sgml/ref/create_foreign_table.sgml +++ b/doc/src/sgml/ref/create_foreign_table.sgml @@ -159,7 +159,7 @@ CHECK ( expression ) [ NO INHERIT ] tables from which the new foreign table automatically inherits all columns. Parent tables can be plain tables or foreign tables. See the similar form of - for more details. + CREATE TABLE for more details. @@ -171,7 +171,7 @@ CHECK ( expression ) [ NO INHERIT ] This form can be used to create the foreign table as partition of the given parent table with specified partition bound values. See the similar form of - for more details. + CREATE TABLE for more details. Note that it is currently not allowed to create the foreign table as a partition of the parent table if there are UNIQUE indexes on the parent table. (See also diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml index 84d4e024db4d..8b2041c51159 100644 --- a/doc/src/sgml/ref/create_function.sgml +++ b/doc/src/sgml/ref/create_function.sgml @@ -258,7 +258,7 @@ CREATE [ OR REPLACE ] FUNCTION The name of the language that the function is implemented in. It can be sql, c, internal, or the name of a user-defined - procedural language, e.g. plpgsql. Enclosing the + procedural language, e.g., plpgsql. Enclosing the name in single quotes is deprecated and requires matching case. @@ -432,11 +432,11 @@ CREATE [ OR REPLACE ] FUNCTION Functions should be labeled parallel unsafe if they modify any database state, or if they make changes to the transaction such as using sub-transactions, or if they access sequences or attempt to make - persistent changes to settings (e.g. setval). They should + persistent changes to settings (e.g., setval). They should be labeled as parallel restricted if they access temporary tables, client connection state, cursors, prepared statements, or miscellaneous backend-local state which the system cannot synchronize in parallel mode - (e.g. setseed cannot be executed other than by the group + (e.g., setseed cannot be executed other than by the group leader because a change made by another process would not be reflected in the leader). In general, if a function is labeled as being safe when it is restricted or unsafe, or if it is labeled as being restricted when @@ -558,7 +558,7 @@ CREATE [ OR REPLACE ] FUNCTION the SQL function. The string obj_file is the name of the shared library file containing the compiled C function, and is interpreted - as for the command. The string + as for the LOAD command. The string link_symbol is the function's link symbol, that is, the name of the function in the C language source code. If the link symbol is omitted, it is assumed to diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml index c6dab3dae4f4..39d680e6d5fe 100644 --- a/doc/src/sgml/ref/create_index.sgml +++ b/doc/src/sgml/ref/create_index.sgml @@ -463,11 +463,15 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] - Determines whether the buffering build technique described in + Determines whether the buffered build technique described in is used to build the index. With - OFF it is disabled, with ON it is enabled, and - with AUTO it is initially disabled, but turned on - on-the-fly once the index size reaches . The default is AUTO. + OFF buffering is disabled, with ON + it is enabled, and with AUTO it is initially disabled, + but is turned on on-the-fly once the index size reaches + . The default + is AUTO. + Note that if sorted build is possible, it will be used instead of + buffered build unless buffering=ON is specified. @@ -771,7 +775,7 @@ Indexes: least a 32MB share of the total maintenance_work_mem budget. There must also be a remaining 32MB share for the leader process. - Increasing + Increasing may allow more workers to be used, which will reduce the time needed for index creation, so long as the index build is not already I/O bound. Of course, there should also be sufficient @@ -779,8 +783,8 @@ Indexes: - Setting a value for parallel_workers via directly controls how many parallel + Setting a value for parallel_workers via ALTER TABLE directly controls how many parallel worker processes will be requested by a CREATE INDEX against the table. This bypasses the cost model completely, and prevents maintenance_work_mem @@ -808,7 +812,7 @@ Indexes: - Use + Use DROP INDEX to remove an index. diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml index 10d1533d6d8c..102efe5a6c7f 100644 --- a/doc/src/sgml/ref/create_language.sgml +++ b/doc/src/sgml/ref/create_language.sgml @@ -137,7 +137,7 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE inline_handler is the name of a previously registered function that will be called to execute an anonymous code block - ( command) + (DO command) in this language. If no inline_handler function is specified, the language does not support anonymous code @@ -183,7 +183,7 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE to drop procedural languages. + Use DROP LANGUAGE to drop procedural languages. diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml index de9f17655c63..5ba851b687a4 100644 --- a/doc/src/sgml/ref/create_materialized_view.sgml +++ b/doc/src/sgml/ref/create_materialized_view.sgml @@ -132,8 +132,8 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] table_name query - A , TABLE, - or command. This query will run within a + A SELECT, TABLE, + or VALUES command. This query will run within a security-restricted operation; in particular, calls to functions that themselves create temporary tables will fail. diff --git a/doc/src/sgml/ref/create_opclass.sgml b/doc/src/sgml/ref/create_opclass.sgml index f42fb6494c6b..2d75a1c0b0d6 100644 --- a/doc/src/sgml/ref/create_opclass.sgml +++ b/doc/src/sgml/ref/create_opclass.sgml @@ -161,7 +161,7 @@ CREATE OPERATOR CLASS name [ DEFAUL In an OPERATOR clause, the operand data type(s) of the operator, or NONE to - signify a left-unary or right-unary operator. The operand data + signify a prefix operator. The operand data types can be omitted in the normal case where they are the same as the operator class's data type. diff --git a/doc/src/sgml/ref/create_operator.sgml b/doc/src/sgml/ref/create_operator.sgml index d5c385c087f5..e27512ff3919 100644 --- a/doc/src/sgml/ref/create_operator.sgml +++ b/doc/src/sgml/ref/create_operator.sgml @@ -86,13 +86,9 @@ CREATE OPERATOR name ( - At least one of LEFTARG and RIGHTARG must be defined. For - binary operators, both must be defined. For right unary - operators, only LEFTARG should be defined, while for left - unary operators only RIGHTARG should be defined. - - - + For binary operators, both LEFTARG and + RIGHTARG must be defined. For prefix operators only + RIGHTARG should be defined. The function_name function must have been previously defined using CREATE FUNCTION and must be defined to accept the correct number @@ -153,7 +149,7 @@ CREATE OPERATOR name ( The data type of the operator's left operand, if any. - This option would be omitted for a left-unary operator. + This option would be omitted for a prefix operator. @@ -162,8 +158,7 @@ CREATE OPERATOR name ( right_type - The data type of the operator's right operand, if any. - This option would be omitted for a right-unary operator. + The data type of the operator's right operand. @@ -256,8 +251,8 @@ COMMUTATOR = OPERATOR(myschema.===) , - Use to delete user-defined operators - from a database. Use to modify operators in a + Use DROP OPERATOR to delete user-defined operators + from a database. Use ALTER OPERATOR to modify operators in a database. diff --git a/doc/src/sgml/ref/create_policy.sgml b/doc/src/sgml/ref/create_policy.sgml index 2e1229c4f94c..b4f90561018c 100644 --- a/doc/src/sgml/ref/create_policy.sgml +++ b/doc/src/sgml/ref/create_policy.sgml @@ -24,7 +24,7 @@ PostgreSQL documentation CREATE POLICY name ON table_name [ AS { PERMISSIVE | RESTRICTIVE } ] [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ] - [ TO { role_name | PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ] + [ TO { role_name | PUBLIC | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...] ] [ USING ( using_expression ) ] [ WITH CHECK ( check_expression ) ] diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml index 0ea6513cb588..e258eca5ceea 100644 --- a/doc/src/sgml/ref/create_procedure.sgml +++ b/doc/src/sgml/ref/create_procedure.sgml @@ -97,11 +97,9 @@ CREATE [ OR REPLACE ] PROCEDURE - The mode of an argument: IN, + The mode of an argument: IN, OUT, INOUT, or VARIADIC. If omitted, - the default is IN. (OUT - arguments are currently not supported for procedures. Use - INOUT instead.) + the default is IN. @@ -164,7 +162,7 @@ CREATE [ OR REPLACE ] PROCEDURE The name of the language that the procedure is implemented in. It can be sql, c, internal, or the name of a user-defined - procedural language, e.g. plpgsql. Enclosing the + procedural language, e.g., plpgsql. Enclosing the name in single quotes is deprecated and requires matching case. @@ -285,7 +283,7 @@ CREATE [ OR REPLACE ] PROCEDURE the SQL procedure. The string obj_file is the name of the shared library file containing the compiled C procedure, and is interpreted - as for the command. The string + as for the LOAD command. The string link_symbol is the procedure's link symbol, that is, the name of the procedure in the C language source code. If the link symbol is omitted, it is assumed diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml index ae2fbaa2348d..b8305b6556a6 100644 --- a/doc/src/sgml/ref/create_role.sgml +++ b/doc/src/sgml/ref/create_role.sgml @@ -169,7 +169,7 @@ in sync when changing the above synopsis! If not specified, NOLOGIN is the default, except when CREATE ROLE is invoked through its alternative spelling - . + CREATE USER. @@ -188,6 +188,8 @@ in sync when changing the above synopsis! highly privileged role, and should only be used on roles actually used for replication. If not specified, NOREPLICATION is the default. + You must be a superuser to create a new role having the + REPLICATION attribute. @@ -199,11 +201,16 @@ in sync when changing the above synopsis! These clauses determine whether a role bypasses every row-level security (RLS) policy. NOBYPASSRLS is the default. + You must be a superuser to create a new role having + the BYPASSRLS attribute. + + + Note that pg_dump will set row_security to OFF by default, to ensure all contents of a table are dumped out. If the user running pg_dump does not have appropriate - permissions, an error will be returned. The superuser and owner of the - table being dumped always bypass RLS. + permissions, an error will be returned. However, superusers and the + owner of the table being dumped always bypass RLS. @@ -382,8 +389,8 @@ in sync when changing the above synopsis! Notes - Use to - change the attributes of a role, and + Use ALTER ROLE to + change the attributes of a role, and DROP ROLE to remove a role. All the attributes specified by CREATE ROLE can be modified by later ALTER ROLE commands. @@ -392,8 +399,8 @@ in sync when changing the above synopsis! The preferred way to add and remove members of roles that are being used as groups is to use - and - . + GRANT and + REVOKE. @@ -411,7 +418,7 @@ in sync when changing the above synopsis! a member of a role with CREATEDB privilege does not immediately grant the ability to create databases, even if INHERIT is set; it would be necessary to become that role via - before + SET ROLE before creating a database. diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml index ffbe1ba3bcc2..3c2dddb1631e 100644 --- a/doc/src/sgml/ref/create_schema.sgml +++ b/doc/src/sgml/ref/create_schema.sgml @@ -29,6 +29,7 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION role_sp where role_specification can be: user_name + | CURRENT_ROLE | CURRENT_USER | SESSION_USER diff --git a/doc/src/sgml/ref/create_statistics.sgml b/doc/src/sgml/ref/create_statistics.sgml index 5b583aacb433..4363be50c3c4 100644 --- a/doc/src/sgml/ref/create_statistics.sgml +++ b/doc/src/sgml/ref/create_statistics.sgml @@ -131,7 +131,7 @@ CREATE STATISTICS [ IF NOT EXISTS ] statistics_na Examples - Create table t1 with two functionally dependent columns, i.e. + Create table t1 with two functionally dependent columns, i.e., knowledge of a value in the first column is sufficient for determining the value in the other column. Then functional dependency statistics are built on those columns: diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml index cdb22c54feab..e812beee3738 100644 --- a/doc/src/sgml/ref/create_subscription.sgml +++ b/doc/src/sgml/ref/create_subscription.sgml @@ -160,7 +160,7 @@ CREATE SUBSCRIPTION subscription_name It is safe to use off for logical replication: If the subscriber loses transactions because of missing - synchronization, the data will be resent from the publisher. + synchronization, the data will be sent again from the publisher. @@ -228,6 +228,17 @@ CREATE SUBSCRIPTION subscription_name + + streaming (boolean) + + + Specifies whether streaming of in-progress transactions should + be enabled for this subscription. By default, all transactions + are fully decoded on the publisher, and only then sent to the + subscriber as a whole. + + + diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index e9a9f7ac9f51..a351e5c20394 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -256,8 +256,9 @@ Where column_reference_storage_directive is: If specified, the table is created as a temporary table. Temporary tables are automatically dropped at the end of a session, or optionally at the end of the current transaction - (see ON COMMIT below). Existing permanent - tables with the same name are not visible to the current session + (see ON COMMIT below). The default + search_path includes the temporary schema first and so identically + named existing permanent tables are not chosen for new plans while the temporary table exists, unless they are referenced with schema-qualified names. Any indexes created on a temporary table are automatically temporary as well. @@ -1008,7 +1009,7 @@ Where column_reference_storage_directive is: one or more columns on which the uniqueness is not enforced. Note that although the constraint is not enforced on the included columns, it still depends on them. Consequently, some operations on these columns - (e.g. DROP COLUMN) can cause cascaded constraint and + (e.g., DROP COLUMN) can cause cascaded constraint and index deletion. @@ -1054,7 +1055,7 @@ Where column_reference_storage_directive is: of columns to be specified which will be included in the non-key portion of the index. Although uniqueness is not enforced on the included columns, the constraint still depends on them. Consequently, some operations on the - included columns (e.g. DROP COLUMN) can cause cascaded + included columns (e.g., DROP COLUMN) can cause cascaded constraint and index deletion. @@ -1246,7 +1247,7 @@ Where column_reference_storage_directive is: constraint that is not deferrable will be checked immediately after every command. Checking of constraints that are deferrable can be postponed until the end of the transaction - (using the command). + (using the SET CONSTRAINTS command). NOT DEFERRABLE is the default. Currently, only UNIQUE, PRIMARY KEY, EXCLUDE, and @@ -1270,7 +1271,7 @@ Where column_reference_storage_directive is: statement. This is the default. If the constraint is INITIALLY DEFERRED, it is checked only at the end of the transaction. The constraint check time can be - altered with the command. + altered with the SET CONSTRAINTS command. @@ -1338,8 +1339,8 @@ Where column_reference_storage_directive is: All rows in the temporary table will be deleted at the end - of each transaction block. Essentially, an automatic is done + of each transaction block. Essentially, an automatic TRUNCATE is done at each commit. When used on a partitioned table, this is not cascaded to its partitions. @@ -1453,10 +1454,11 @@ Where column_reference_storage_directive is: The toast_tuple_target specifies the minimum tuple length required before - we try to move long column values into TOAST tables, and is also the - target length we try to reduce the length below once toasting begins. - This only affects columns marked as either External or Extended - and applies only to new tuples; there is no effect on existing rows. + we try to compress and/or move long column values into TOAST tables, and + is also the target length we try to reduce the length below once toasting + begins. This affects columns marked as External (for move), + Main (for compression), or Extended (for both) and applies only to new + tuples. There is no effect on existing rows. By default this parameter is set to allow at least 4 tuples per block, which with the default block size will be 2040 bytes. Valid values are between 128 bytes and the (block size - header), by default 8160 bytes. @@ -1524,7 +1526,7 @@ Where column_reference_storage_directive is: Disabling index cleanup can speed up VACUUM very significantly, but may also lead to severely bloated indexes if table modifications are frequent. The INDEX_CLEANUP - parameter of , if specified, overrides + parameter of VACUUM, if specified, overrides the value of this option. @@ -1545,7 +1547,7 @@ Where column_reference_storage_directive is: the truncated pages is returned to the operating system. Note that the truncation requires ACCESS EXCLUSIVE lock on the table. The TRUNCATE parameter - of , if specified, overrides the value + of VACUUM, if specified, overrides the value of this option. diff --git a/doc/src/sgml/ref/create_table_as.sgml b/doc/src/sgml/ref/create_table_as.sgml index e2ad538cdb69..5ffa3834646a 100644 --- a/doc/src/sgml/ref/create_table_as.sgml +++ b/doc/src/sgml/ref/create_table_as.sgml @@ -196,8 +196,8 @@ where storage_parameter is: All rows in the temporary table will be deleted at the end - of each transaction block. Essentially, an automatic is done + of each transaction block. Essentially, an automatic TRUNCATE is done at each commit. @@ -233,9 +233,9 @@ where storage_parameter is: query - A , TABLE, or - command, or an command that runs a + A SELECT, TABLE, or VALUES + command, or an EXECUTE command that runs a prepared SELECT, TABLE, or VALUES query. diff --git a/doc/src/sgml/ref/create_tablespace.sgml b/doc/src/sgml/ref/create_tablespace.sgml index 462b8831c274..84fa7ee5e29e 100644 --- a/doc/src/sgml/ref/create_tablespace.sgml +++ b/doc/src/sgml/ref/create_tablespace.sgml @@ -22,7 +22,7 @@ PostgreSQL documentation CREATE TABLESPACE tablespace_name - [ OWNER { new_owner | CURRENT_USER | SESSION_USER } ] + [ OWNER { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ] LOCATION 'directory' [ WITH ( tablespace_option = value [, ... ] ) ] diff --git a/doc/src/sgml/ref/create_transform.sgml b/doc/src/sgml/ref/create_transform.sgml index 5b46c23196db..3f81dc6bba2c 100644 --- a/doc/src/sgml/ref/create_transform.sgml +++ b/doc/src/sgml/ref/create_transform.sgml @@ -147,7 +147,7 @@ CREATE [ OR REPLACE ] TRANSFORM FOR type_name LANGUAG Notes - Use to remove transforms. + Use DROP TRANSFORM to remove transforms. diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml index 6a6c513f1561..999de14421e6 100644 --- a/doc/src/sgml/ref/create_trigger.sgml +++ b/doc/src/sgml/ref/create_trigger.sgml @@ -170,7 +170,7 @@ CREATE [ CONSTRAINT ] TRIGGER name When the CONSTRAINT option is specified, this command creates a constraint trigger. This is the same as a regular trigger except that the timing of the trigger firing can be adjusted using - . + SET CONSTRAINTS. Constraint triggers must be AFTER ROW triggers on plain tables (not foreign tables). They can be fired either at the end of the statement causing the triggering @@ -442,7 +442,7 @@ UPDATE OF column_name1 [, column_name2 - Use to remove a trigger. + Use DROP TRIGGER to remove a trigger. diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 33fa3164cb6f..ca20698dd51d 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -124,8 +124,8 @@ CREATE TYPE name must be less than NAMEDATALEN bytes long (64 bytes in a standard PostgreSQL build). (It is possible to create an enumerated type with zero labels, but such a type cannot be used - to hold values before at least one label is added using .) + to hold values before at least one label is added using ALTER TYPE.) diff --git a/doc/src/sgml/ref/create_user.sgml b/doc/src/sgml/ref/create_user.sgml index 2f8881d77c9e..7d1e42c607d2 100644 --- a/doc/src/sgml/ref/create_user.sgml +++ b/doc/src/sgml/ref/create_user.sgml @@ -50,7 +50,7 @@ CREATE USER name [ [ WITH ] CREATE USER is now an alias for - . + CREATE ROLE. The only difference is that when the command is spelled CREATE USER, LOGIN is assumed by default, whereas NOLOGIN is assumed when diff --git a/doc/src/sgml/ref/create_user_mapping.sgml b/doc/src/sgml/ref/create_user_mapping.sgml index 9719a4ff2c0d..55debd54012d 100644 --- a/doc/src/sgml/ref/create_user_mapping.sgml +++ b/doc/src/sgml/ref/create_user_mapping.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -CREATE USER MAPPING [ IF NOT EXISTS ] FOR { user_name | USER | CURRENT_USER | PUBLIC } +CREATE USER MAPPING [ IF NOT EXISTS ] FOR { user_name | USER | CURRENT_ROLE | CURRENT_USER | PUBLIC } SERVER server_name [ OPTIONS ( option 'value' [ , ... ] ) ] @@ -67,7 +67,7 @@ CREATE USER MAPPING [ IF NOT EXISTS ] FOR { user_ The name of an existing user that is mapped to foreign server. - CURRENT_USER and USER match the name of + CURRENT_ROLE, CURRENT_USER, and USER match the name of the current user. When PUBLIC is specified, a so-called public mapping is created that is used when no user-specific mapping is applicable. diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml index eb5591b63c73..4b5b1cf79531 100644 --- a/doc/src/sgml/ref/create_view.sgml +++ b/doc/src/sgml/ref/create_view.sgml @@ -137,8 +137,8 @@ CREATE VIEW [ schema . ] view_namelocal or cascaded, and is equivalent to specifying WITH [ CASCADED | LOCAL ] CHECK OPTION (see below). - This option can be changed on existing views using . + This option can be changed on existing views using ALTER VIEW. @@ -160,8 +160,8 @@ CREATE VIEW [ schema . ] view_namequery - A or - command + A SELECT or + VALUES command which will provide the columns and rows of the view. @@ -245,7 +245,7 @@ CREATE VIEW [ schema . ] view_nameNotes - Use the + Use the DROP VIEW statement to drop views. diff --git a/doc/src/sgml/ref/createdb.sgml b/doc/src/sgml/ref/createdb.sgml index d3c92943f071..86473455c9d0 100644 --- a/doc/src/sgml/ref/createdb.sgml +++ b/doc/src/sgml/ref/createdb.sgml @@ -46,7 +46,7 @@ PostgreSQL documentation createdb is a wrapper around the - SQL command . + SQL command CREATE DATABASE. There is no effective difference between creating databases via this utility and via other methods for accessing the server. @@ -197,7 +197,7 @@ PostgreSQL documentation The options , , , , and correspond to options of the underlying - SQL command ; see there for more information + SQL command CREATE DATABASE; see there for more information about them. @@ -284,6 +284,9 @@ PostgreSQL documentation database will be used; if that does not exist (or if it is the name of the new database being created), template1 will be used. + This can be a connection + string. If so, connection string parameters will override any + conflicting command line options. diff --git a/doc/src/sgml/ref/createuser.sgml b/doc/src/sgml/ref/createuser.sgml index 9d24df8b7a88..4d60dc2cda12 100644 --- a/doc/src/sgml/ref/createuser.sgml +++ b/doc/src/sgml/ref/createuser.sgml @@ -49,7 +49,7 @@ PostgreSQL documentation createuser is a wrapper around the - SQL command . + SQL command CREATE ROLE. There is no effective difference between creating users via this utility and via other methods for accessing the server. diff --git a/doc/src/sgml/ref/declare.sgml b/doc/src/sgml/ref/declare.sgml index 7151cb8765c7..1b5265615525 100644 --- a/doc/src/sgml/ref/declare.sgml +++ b/doc/src/sgml/ref/declare.sgml @@ -39,7 +39,7 @@ DECLARE name [ BINARY ] [ INSENSITI can be used to retrieve a small number of rows at a time out of a larger query. After the cursor is created, rows are fetched from it using - . + FETCH. @@ -148,8 +148,8 @@ DECLARE name [ BINARY ] [ INSENSITI query - A or - command + A SELECT or + VALUES command which will provide the rows to be returned by the cursor. @@ -207,9 +207,9 @@ DECLARE name [ BINARY ] [ INSENSITI PostgreSQL reports an error if such a command is used outside a transaction block. Use - and - - (or ) + BEGIN and + COMMIT + (or ROLLBACK) to define a transaction block. @@ -268,7 +268,7 @@ DECLARE name [ BINARY ] [ INSENSITI If the cursor's query includes FOR UPDATE or FOR SHARE, then returned rows are locked at the time they are first fetched, in the same way as for a regular - command with + SELECT command with these options. In addition, the returned rows will be the most up-to-date versions; therefore these options provide the equivalent of what the SQL standard diff --git a/doc/src/sgml/ref/delete.sgml b/doc/src/sgml/ref/delete.sgml index ec3c40df2ea9..1b81b4e7d743 100644 --- a/doc/src/sgml/ref/delete.sgml +++ b/doc/src/sgml/ref/delete.sgml @@ -41,7 +41,7 @@ DELETE FROM [ ONLY ] table_name [ * - provides a + TRUNCATE provides a faster mechanism to remove all rows from a table. diff --git a/doc/src/sgml/ref/drop_group.sgml b/doc/src/sgml/ref/drop_group.sgml index 47d4a72121b6..eb7dc182c82b 100644 --- a/doc/src/sgml/ref/drop_group.sgml +++ b/doc/src/sgml/ref/drop_group.sgml @@ -30,7 +30,7 @@ DROP GROUP [ IF EXISTS ] name [, .. DROP GROUP is now an alias for - . + DROP ROLE. diff --git a/doc/src/sgml/ref/drop_index.sgml b/doc/src/sgml/ref/drop_index.sgml index 0aedd71bd68d..85cf23bca20a 100644 --- a/doc/src/sgml/ref/drop_index.sgml +++ b/doc/src/sgml/ref/drop_index.sgml @@ -57,6 +57,8 @@ DROP INDEX [ CONCURRENTLY ] [ IF EXISTS ] nameDROP INDEX commands can be performed within a transaction block, but DROP INDEX CONCURRENTLY cannot. + Lastly, indexes on partitioned tables cannot be dropped using this + option. For temporary tables, DROP INDEX is always diff --git a/doc/src/sgml/ref/drop_language.sgml b/doc/src/sgml/ref/drop_language.sgml index 4705836ac79e..8ba6621bc4af 100644 --- a/doc/src/sgml/ref/drop_language.sgml +++ b/doc/src/sgml/ref/drop_language.sgml @@ -38,7 +38,7 @@ DROP [ PROCEDURAL ] LANGUAGE [ IF EXISTS ] name As of PostgreSQL 9.1, most procedural languages have been made into extensions, and should - therefore be removed with + therefore be removed with DROP EXTENSION not DROP LANGUAGE. diff --git a/doc/src/sgml/ref/drop_operator.sgml b/doc/src/sgml/ref/drop_operator.sgml index 2dff050ecf22..7bcdd082ae70 100644 --- a/doc/src/sgml/ref/drop_operator.sgml +++ b/doc/src/sgml/ref/drop_operator.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -DROP OPERATOR [ IF EXISTS ] name ( { left_type | NONE } , { right_type | NONE } ) [, ...] [ CASCADE | RESTRICT ] +DROP OPERATOR [ IF EXISTS ] name ( { left_type | NONE } , right_type ) [, ...] [ CASCADE | RESTRICT ] @@ -73,8 +73,7 @@ DROP OPERATOR [ IF EXISTS ] name ( right_type - The data type of the operator's right operand; write - NONE if the operator has no right operand. + The data type of the operator's right operand. @@ -113,24 +112,17 @@ DROP OPERATOR ^ (integer, integer); - Remove the left unary bitwise complement operator + Remove the bitwise-complement prefix operator ~b for type bit: DROP OPERATOR ~ (none, bit); - - Remove the right unary factorial operator x! - for type bigint: - -DROP OPERATOR ! (bigint, none); - - Remove multiple operators in one command: -DROP OPERATOR ~ (none, bit), ! (bigint, none); +DROP OPERATOR ~ (none, bit), ^ (integer, integer); diff --git a/doc/src/sgml/ref/drop_owned.sgml b/doc/src/sgml/ref/drop_owned.sgml index 09107bef6474..8fa8c414a10e 100644 --- a/doc/src/sgml/ref/drop_owned.sgml +++ b/doc/src/sgml/ref/drop_owned.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -DROP OWNED BY { name | CURRENT_USER | SESSION_USER } [, ...] [ CASCADE | RESTRICT ] +DROP OWNED BY { name | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...] [ CASCADE | RESTRICT ] @@ -90,7 +90,7 @@ DROP OWNED BY { name | CURRENT_USER - The command is an alternative that + The REASSIGN OWNED command is an alternative that reassigns the ownership of all the database objects owned by one or more roles. However, REASSIGN OWNED does not deal with privileges for other objects. diff --git a/doc/src/sgml/ref/drop_procedure.sgml b/doc/src/sgml/ref/drop_procedure.sgml index 6da266ae2dae..bf2c6ce1aaa1 100644 --- a/doc/src/sgml/ref/drop_procedure.sgml +++ b/doc/src/sgml/ref/drop_procedure.sgml @@ -67,8 +67,9 @@ DROP PROCEDURE [ IF EXISTS ] name [ - The mode of an argument: IN or VARIADIC. - If omitted, the default is IN. + The mode of an argument: IN, OUT, + INOUT, or VARIADIC. If omitted, + the default is IN. diff --git a/doc/src/sgml/ref/drop_role.sgml b/doc/src/sgml/ref/drop_role.sgml index 13079f3e1f4a..13dc1cc64998 100644 --- a/doc/src/sgml/ref/drop_role.sgml +++ b/doc/src/sgml/ref/drop_role.sgml @@ -40,7 +40,9 @@ DROP ROLE [ IF EXISTS ] name [, ... of the cluster; an error will be raised if so. Before dropping the role, you must drop all the objects it owns (or reassign their ownership) and revoke any privileges the role has been granted on other objects. - The and + The REASSIGN + OWNED and DROP + OWNED commands can be useful for this purpose; see for more discussion. diff --git a/doc/src/sgml/ref/drop_table.sgml b/doc/src/sgml/ref/drop_table.sgml index bf8996d19858..450458fd2a42 100644 --- a/doc/src/sgml/ref/drop_table.sgml +++ b/doc/src/sgml/ref/drop_table.sgml @@ -32,8 +32,8 @@ DROP TABLE [ IF EXISTS ] name [, .. DROP TABLE removes tables from the database. Only the table owner, the schema owner, and superuser can drop a table. To empty a table of rows - without destroying the table, use - or . + without destroying the table, use DELETE + or TRUNCATE. diff --git a/doc/src/sgml/ref/drop_user.sgml b/doc/src/sgml/ref/drop_user.sgml index 37ab856125d1..74e736b0ebd8 100644 --- a/doc/src/sgml/ref/drop_user.sgml +++ b/doc/src/sgml/ref/drop_user.sgml @@ -30,7 +30,7 @@ DROP USER [ IF EXISTS ] name [, ... DROP USER is simply an alternate spelling of - . + DROP ROLE. diff --git a/doc/src/sgml/ref/drop_user_mapping.sgml b/doc/src/sgml/ref/drop_user_mapping.sgml index 7cb09f1166dd..9e8896a307f7 100644 --- a/doc/src/sgml/ref/drop_user_mapping.sgml +++ b/doc/src/sgml/ref/drop_user_mapping.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -DROP USER MAPPING [ IF EXISTS ] FOR { user_name | USER | CURRENT_USER | PUBLIC } SERVER server_name +DROP USER MAPPING [ IF EXISTS ] FOR { user_name | USER | CURRENT_ROLE | CURRENT_USER | PUBLIC } SERVER server_name @@ -59,7 +59,7 @@ DROP USER MAPPING [ IF EXISTS ] FOR { user_nameuser_name - User name of the mapping. CURRENT_USER + User name of the mapping. CURRENT_ROLE, CURRENT_USER, and USER match the name of the current user. PUBLIC is used to match all present and future user names in the system. diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml index ded85b0e232d..d36aed38c527 100644 --- a/doc/src/sgml/ref/dropdb.sgml +++ b/doc/src/sgml/ref/dropdb.sgml @@ -41,7 +41,7 @@ PostgreSQL documentation dropdb is a wrapper around the - SQL command . + SQL command DROP DATABASE. There is no effective difference between dropping databases via this utility and via other methods for accessing the server. @@ -217,6 +217,9 @@ PostgreSQL documentation target database. If not specified, the postgres database will be used; if that does not exist (or is the database being dropped), template1 will be used. + This can be a connection + string. If so, connection string parameters will override any + conflicting command line options. diff --git a/doc/src/sgml/ref/dropuser.sgml b/doc/src/sgml/ref/dropuser.sgml index f9aab340d3ba..81580507e826 100644 --- a/doc/src/sgml/ref/dropuser.sgml +++ b/doc/src/sgml/ref/dropuser.sgml @@ -42,7 +42,7 @@ PostgreSQL documentation dropuser is a wrapper around the - SQL command . + SQL command DROP ROLE. There is no effective difference between dropping users via this utility and via other methods for accessing the server. diff --git a/doc/src/sgml/ref/end.sgml b/doc/src/sgml/ref/end.sgml index 8b8f4f0dbb9f..498652919ad8 100644 --- a/doc/src/sgml/ref/end.sgml +++ b/doc/src/sgml/ref/end.sgml @@ -33,7 +33,7 @@ END [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ] made by the transaction become visible to others and are guaranteed to be durable if a crash occurs. This command is a PostgreSQL extension - that is equivalent to . + that is equivalent to COMMIT. @@ -69,7 +69,7 @@ END [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ] Notes - Use to + Use ROLLBACK to abort a transaction. @@ -94,8 +94,8 @@ END; END is a PostgreSQL - extension that provides functionality equivalent to , which is + extension that provides functionality equivalent to COMMIT, which is specified in the SQL standard. diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml index 1c19e254dc24..b0ccdd26e730 100644 --- a/doc/src/sgml/ref/explain.sgml +++ b/doc/src/sgml/ref/explain.sgml @@ -187,8 +187,7 @@ ROLLBACK; query processing. The number of blocks shown for an upper-level node includes those used by all its child nodes. In text - format, only non-zero values are printed. This parameter may only be - used when ANALYZE is also enabled. It defaults to + format, only non-zero values are printed. It defaults to FALSE. @@ -303,7 +302,7 @@ ROLLBACK; the autovacuum daemon will take care of that automatically. But if a table has recently had substantial changes in its contents, you might need to do a manual - rather than wait for autovacuum to catch up + ANALYZE rather than wait for autovacuum to catch up with the changes. diff --git a/doc/src/sgml/ref/fetch.sgml b/doc/src/sgml/ref/fetch.sgml index e802be61c8c6..ec843f568442 100644 --- a/doc/src/sgml/ref/fetch.sgml +++ b/doc/src/sgml/ref/fetch.sgml @@ -335,9 +335,9 @@ FETCH count - + DECLARE is used to define a cursor. Use - + MOVE to change cursor position without retrieving data. diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index 0cd9b20940ff..1594da56f4a2 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -87,6 +87,7 @@ GRANT role_name [, ...] TO role_name | PUBLIC + | CURRENT_ROLE | CURRENT_USER | SESSION_USER @@ -295,7 +296,7 @@ GRANT role_name [, ...] TO Notes - The command is used + The REVOKE command is used to revoke access privileges. diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml index 7f52209743d9..370eaf91d215 100644 --- a/doc/src/sgml/ref/initdb.sgml +++ b/doc/src/sgml/ref/initdb.sgml @@ -86,7 +86,7 @@ PostgreSQL documentation initdb initializes the database cluster's default locale and character set encoding. The character set encoding, collation order (LC_COLLATE) and character set classes - (LC_CTYPE, e.g. upper, lower, digit) can be set separately + (LC_CTYPE, e.g., upper, lower, digit) can be set separately for a database when it is created. initdb determines those settings for the template1 database, which will serve as the default for all other databases. diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml index 0c4688603d9f..37881f25ac57 100644 --- a/doc/src/sgml/ref/lock.sgml +++ b/doc/src/sgml/ref/lock.sgml @@ -16,7 +16,7 @@ PostgreSQL documentation LOCK - lock a table + lock a named relation (table, etc) @@ -34,7 +34,9 @@ LOCK [ TABLE ] [ ONLY ] name [ * ] Description - LOCK TABLE obtains a table-level lock, waiting + LOCK TABLE obtains a table-level lock on a + relation (table, partitioned table, foreign table, view, + materialized view, index, composite type, sequence), waiting if necessary for any conflicting locks to be released. If NOWAIT is specified, LOCK TABLE does not wait to acquire the desired lock: if it @@ -115,17 +117,18 @@ LOCK [ TABLE ] [ ONLY ] name [ * ] name - The name (optionally schema-qualified) of an existing table to - lock. If ONLY is specified before the table name, only that + The name (optionally schema-qualified) of an existing relation to + lock. If ONLY is specified before a table name, only that table is locked. If ONLY is not specified, the table and all its descendant tables (if any) are locked. Optionally, * can be specified after the table name to explicitly indicate that - descendant tables are included. + descendant tables are included. When locking a view, all relations appearing + in the view definition are locked, regardless of ONLY. The command LOCK TABLE a, b; is equivalent to - LOCK TABLE a; LOCK TABLE b;. The tables are locked + LOCK TABLE a; LOCK TABLE b;. The relations are locked one-by-one in the order specified in the LOCK TABLE command. @@ -186,9 +189,9 @@ LOCK [ TABLE ] [ ONLY ] name [ * ] PostgreSQL reports an error if LOCK is used outside a transaction block. Use - and - - (or ) + BEGIN and + COMMIT + (or ROLLBACK) to define a transaction block. diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml index aa0b27c9f300..e993e8761c13 100644 --- a/doc/src/sgml/ref/pg_basebackup.sgml +++ b/doc/src/sgml/ref/pg_basebackup.sgml @@ -368,7 +368,7 @@ PostgreSQL documentation The following command-line options control the generation of the - backup and the running of the program: + backup and the invocation of the program: @@ -540,7 +540,7 @@ PostgreSQL documentation of each file for users who wish to verify that the backup has not been tampered with, while the CRC32C algorithm provides a checksum that is much faster to calculate; it is good at catching errors due to accidental - changes but is not resistant to targeted modifications. Note that, to + changes but is not resistant to malicious modifications. Note that, to be useful against an adversary who has access to the backup, the backup manifest would need to be stored securely elsewhere or otherwise verified not to have been modified since the backup was taken. @@ -653,8 +653,9 @@ PostgreSQL documentation - Specifies parameters used to connect to the server, as a connection - string. See for more information. + Specifies parameters used to connect to the server, as a connction string; these + will override any conflicting command line options. The option is called --dbname for consistency with other diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml index 8e7807f86bd9..1dd4e54ff11b 100644 --- a/doc/src/sgml/ref/pg_checksums.sgml +++ b/doc/src/sgml/ref/pg_checksums.sgml @@ -47,8 +47,8 @@ PostgreSQL documentation When verifying checksums, every file in the cluster is scanned. When - enabling checksums, every file in the cluster is rewritten. Disabling - checksums only updates the file pg_control. + enabling checksums, every file in the cluster is rewritten in-place. + Disabling checksums only updates the file pg_control. diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index fa1c2cd56f46..d470c5eab31e 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -322,7 +322,7 @@ PostgreSQL documentation Run the dump in parallel by dumping njobs - tables simultaneously. This option reduces the time of the dump but it also + tables simultaneously. This option may reduce the time needed to perform the dump but it also increases the load on the database server. You can only use this option with the directory output format because this is the only output format where multiple processes can write their data at the same time. @@ -517,9 +517,7 @@ PostgreSQL documentation Dump only tables with names matching - pattern. - For this purpose, table includes views, materialized views, - sequences, and foreign tables. Multiple tables + pattern. Multiple tables can be selected by writing multiple switches. The pattern parameter is interpreted as a pattern according to the same rules used by @@ -531,6 +529,14 @@ PostgreSQL documentation below. + + As well as tables, this option can be used to dump the definition of matching + views, materialized views, foreign tables, and sequences. It will not dump the + contents of views or materialized views, and the contents of foreign tables will + only be dumped if the corresponding foreign server is specified with + . + + The and switches have no effect when is used, because tables selected by will @@ -548,18 +554,6 @@ PostgreSQL documentation - - - The behavior of the switch is not entirely upward - compatible with pre-8.2 PostgreSQL - versions. Formerly, writing -t tab would dump all - tables named tab, but now it just dumps whichever one - is visible in your default search path. To get the old behavior - you can write -t '*.tab'. Also, you must write something - like -t sch.tab to select a table in a particular schema, - rather than the old locution of -n sch -t tab. - - @@ -594,6 +588,8 @@ PostgreSQL documentation pg_dump to output detailed object comments and start/stop times to the dump file, and progress messages to standard error. + Repeating the option causes additional debug-level messages + to appear on standard error. @@ -759,7 +755,7 @@ PostgreSQL documentation - Use conditional commands (i.e. add an IF EXISTS + Use conditional commands (i.e., add an IF EXISTS clause) when cleaning database objects. This option is not valid unless is also specified. @@ -1130,14 +1126,10 @@ PostgreSQL documentation Specifies the name of the database to connect to. This is equivalent to specifying dbname as the first non-option - argument on the command line. - - - If this parameter contains an = sign or starts - with a valid URI prefix - (postgresql:// - or postgres://), it is treated as a - conninfo string. See for more information. + argument on the command line. The dbname + can be a connection string. + If so, connection string parameters will override any conflicting + command line options. diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml index 79b3175b8076..9259263e29db 100644 --- a/doc/src/sgml/ref/pg_dumpall.sgml +++ b/doc/src/sgml/ref/pg_dumpall.sgml @@ -237,7 +237,9 @@ PostgreSQL documentation Specifies verbose mode. This will cause pg_dumpall to output start/stop times to the dump file, and progress messages to standard error. - It will also enable verbose output in pg_dump. + Repeating the option causes additional debug-level messages + to appear on standard error. + The option is also passed down to pg_dump. @@ -356,7 +358,7 @@ PostgreSQL documentation - Use conditional commands (i.e. add an IF EXISTS + Use conditional commands (i.e., add an IF EXISTS clause) to drop databases and other objects. This option is not valid unless is also specified. @@ -408,10 +410,7 @@ PostgreSQL documentation the dump. Instead, fail if unable to lock a table within the specified timeout. The timeout may be specified in any of the formats accepted by SET - statement_timeout. Allowed values vary depending on the server - version you are dumping from, but an integer number of milliseconds - is accepted by all versions since 7.3. This option is ignored when - dumping from a pre-7.3 server. + statement_timeout. @@ -585,8 +584,9 @@ PostgreSQL documentation - Specifies parameters used to connect to the server, as a connection - string. See for more information. + Specifies parameters used to connect to the server, as a connction string; these + will override any conflicting command line options. The option is called --dbname for consistency with other diff --git a/doc/src/sgml/ref/pg_isready.sgml b/doc/src/sgml/ref/pg_isready.sgml index 3d5b551b87f2..ba25ca65a40e 100644 --- a/doc/src/sgml/ref/pg_isready.sgml +++ b/doc/src/sgml/ref/pg_isready.sgml @@ -47,15 +47,11 @@ PostgreSQL documentation - Specifies the name of the database to connect to. - - - If this parameter contains an = sign or starts - with a valid URI prefix - (postgresql:// - or postgres://), it is treated as a - conninfo string. See for more information. + Specifies the name of the database to connect to. The + dbname can be a connection string. If so, + connection string parameters will override any conflicting command + line options. diff --git a/doc/src/sgml/ref/pg_receivewal.sgml b/doc/src/sgml/ref/pg_receivewal.sgml index 865ec8426219..26a66c0e19b8 100644 --- a/doc/src/sgml/ref/pg_receivewal.sgml +++ b/doc/src/sgml/ref/pg_receivewal.sgml @@ -252,8 +252,9 @@ PostgreSQL documentation - Specifies parameters used to connect to the server, as a connection - string. See for more information. + Specifies parameters used to connect to the server, as a connction string; these + will override any conflicting command line options. The option is called --dbname for consistency with other diff --git a/doc/src/sgml/ref/pg_recvlogical.sgml b/doc/src/sgml/ref/pg_recvlogical.sgml index 41508fdc1e56..6b1d98d06ef1 100644 --- a/doc/src/sgml/ref/pg_recvlogical.sgml +++ b/doc/src/sgml/ref/pg_recvlogical.sgml @@ -273,14 +273,16 @@ PostgreSQL documentation - - + + - The database to connect to. See the description of the actions for - what this means in detail. This can be a libpq connection string; - see for more information. Defaults - to user name. + The database to connect to. See the description + of the actions for what this means in detail. + The dbname can be a connection string. If so, + connection string parameters will override any conflicting + command line options. Defaults to the user name. diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index b942cb238b1b..93ea937ac8ea 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -156,7 +156,10 @@ PostgreSQL documentation Connect to database dbname and restore directly - into the database. + into the database. The dbname can + be a connection string. + If so, connection string parameters will override any conflicting + command line options. @@ -483,7 +486,12 @@ PostgreSQL documentation - Specifies verbose mode. + Specifies verbose mode. This will cause + pg_restore to output detailed object + comments and start/stop times to the output file, and progress + messages to standard error. + Repeating the option causes additional debug-level messages + to appear on standard error. @@ -572,7 +580,7 @@ PostgreSQL documentation - Use conditional commands (i.e. add an IF EXISTS + Use conditional commands (i.e., add an IF EXISTS clause) to drop database objects. This option is not valid unless is also specified. diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml index 440eed7d4b71..43282e6016fb 100644 --- a/doc/src/sgml/ref/pg_rewind.sgml +++ b/doc/src/sgml/ref/pg_rewind.sgml @@ -73,8 +73,8 @@ PostgreSQL documentation from the WAL archive to the pg_wal directory, or run pg_rewind with the -c option to automatically retrieve them from the WAL archive. The use of - pg_rewind is not limited to failover, e.g. a standby - server can be promoted, run some write transactions, and then rewinded + pg_rewind is not limited to failover, e.g., a standby + server can be promoted, run some write transactions, and then rewound to become a standby again. @@ -211,7 +211,7 @@ PostgreSQL documentation pg_rewind to return without waiting, which is faster, but means that a subsequent operating system crash can leave the synchronized data directory corrupt. Generally, this option is - useful for testing but should not be used when creating a production + useful for testing but should not be used on a production installation. @@ -322,7 +322,7 @@ GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, b When executing pg_rewind using an online cluster as source which has been recently promoted, it is necessary - to execute a CHECKPOINT after promotion so as its + to execute a CHECKPOINT after promotion such that its control file reflects up-to-date timeline information, which is used by pg_rewind to check if the target cluster can be rewound using the designated source cluster. diff --git a/doc/src/sgml/ref/pg_verifybackup.sgml b/doc/src/sgml/ref/pg_verifybackup.sgml index c160992e6d7d..a0989d3cd165 100644 --- a/doc/src/sgml/ref/pg_verifybackup.sgml +++ b/doc/src/sgml/ref/pg_verifybackup.sgml @@ -82,8 +82,8 @@ PostgreSQL documentation for any files for which the computed checksum does not match the checksum stored in the manifest. This step is not performed for any files which produced errors in the previous step, since they are already known - to have problems. Also, files which were ignored in the previous step are - also ignored in this step. + to have problems. Files which were ignored in the previous step are also + ignored in this step. @@ -121,7 +121,8 @@ PostgreSQL documentation Options - The following command-line options control the behavior. + pg_verifybackup accepts the following + command-line arguments: diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml index 9f3bb5fce65c..7180fedd6585 100644 --- a/doc/src/sgml/ref/pgbench.sgml +++ b/doc/src/sgml/ref/pgbench.sgml @@ -617,7 +617,7 @@ pgbench options d transaction to finish. The wait time is called the schedule lag time, and its average and maximum are also reported separately. The transaction latency with respect to the actual transaction start time, - i.e. the time spent executing the transaction in the database, can be + i.e., the time spent executing the transaction in the database, can be computed by subtracting the schedule lag time from the reported latency. @@ -767,7 +767,7 @@ pgbench options d client per thread and there are no external or data dependencies. From a statistical viewpoint reproducing runs exactly is a bad idea because it can hide the performance variability or improve performance unduly, - e.g. by hitting the same pages as a previous run. + e.g., by hitting the same pages as a previous run. However, it may also be of great help for debugging, for instance re-running a tricky case which leads to an error. Use wisely. @@ -787,7 +787,7 @@ pgbench options d Remember to take the sampling rate into account when processing the log file. For example, when computing TPS values, you need to multiply - the numbers accordingly (e.g. with 0.01 sample rate, you'll only get + the numbers accordingly (e.g., with 0.01 sample rate, you'll only get 1/100 of the actual TPS). @@ -812,8 +812,8 @@ pgbench options d Common Options - pgbench accepts the following command-line - common arguments: + pgbench also accepts the following common command-line + arguments for connection parameters: @@ -1991,7 +1991,7 @@ f(x) = PHI(2.0 * parameter * (x - mu) / (max - min + 1)) / 2.0 / parameter, that is a relative 1.0 / parameter around the mean; for instance, if parameter is 4.0, 67% of values are drawn from the - middle quarter (1.0 / 4.0) of the interval (i.e. from + middle quarter (1.0 / 4.0) of the interval (i.e., from 3.0 / 8.0 to 5.0 / 8.0) and 95% from the middle half (2.0 / 4.0) of the interval (second and third quartiles). The minimum allowed parameter @@ -2186,7 +2186,7 @@ END; and max_lag, are only present if the option is used. They provide statistics about the time each transaction had to wait for the - previous one to finish, i.e. the difference between each transaction's + previous one to finish, i.e., the difference between each transaction's scheduled start time and the time it actually started. The very last field, skipped, is only present if the option is used, too. diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml index 6779a5bddcf3..92e1d09a55ca 100644 --- a/doc/src/sgml/ref/pgupgrade.sgml +++ b/doc/src/sgml/ref/pgupgrade.sgml @@ -41,8 +41,8 @@ PostgreSQL documentation pg_upgrade (formerly called pg_migrator) allows data stored in PostgreSQL data files to be upgraded to a later PostgreSQL major version without the data dump/reload typically required for - major version upgrades, e.g. from 9.5.8 to 9.6.4 or from 10.7 to 11.2. - It is not required for minor version upgrades, e.g. from 9.6.2 to 9.6.3 + major version upgrades, e.g., from 9.5.8 to 9.6.4 or from 10.7 to 11.2. + It is not required for minor version upgrades, e.g., from 9.6.2 to 9.6.3 or from 10.1 to 10.2. @@ -60,7 +60,7 @@ PostgreSQL documentation pg_upgrade does its best to - make sure the old and new clusters are binary-compatible, e.g. by + make sure the old and new clusters are binary-compatible, e.g., by checking for compatible compile-time settings, including 32/64-bit binaries. It is important that any external modules are also binary compatible, though this cannot @@ -215,6 +215,21 @@ PostgreSQL documentation + + + + + When upgrading indexes from releases before 14 that didn't track + collation versions, pg_upgrade + assumes by default that the upgraded indexes are compatible with the + currently installed versions of relevant collations (see + ). Specify + to mark + them as needing to be rebuilt instead. + + + + @@ -239,13 +254,13 @@ PostgreSQL documentation Optionally move the old cluster - If you are using a version-specific installation directory, e.g. + If you are using a version-specific installation directory, e.g., /opt/PostgreSQL/&majorversion;, you do not need to move the old cluster. The graphical installers all use version-specific installation directories. - If your installation directory is not version-specific, e.g. + If your installation directory is not version-specific, e.g., /usr/local/pgsql, it is necessary to move the current PostgreSQL install directory so it does not interfere with the new PostgreSQL installation. Once the current PostgreSQL server is shut down, it is safe to rename the @@ -303,9 +318,9 @@ make prefix=/usr/local/pgsql.new install Install any custom shared object files (or DLLs) used by the old cluster - into the new cluster, e.g. pgcrypto.so, + into the new cluster, e.g., pgcrypto.so, whether they are from contrib - or some other source. Do not install the schema definitions, e.g. + or some other source. Do not install the schema definitions, e.g., CREATE EXTENSION pgcrypto, because these will be upgraded from the old cluster. Also, any custom full text search files (dictionary, synonym, @@ -516,9 +531,10 @@ pg_upgrade.exe Save any configuration files from the old standbys' configuration - directories you need to keep, e.g. postgresql.conf, - pg_hba.conf, because these will be overwritten or - removed in the next step. + directories you need to keep, e.g., postgresql.conf + (and any files included by it), postgresql.auto.conf, + pg_hba.conf, because these will be overwritten + or removed in the next step. @@ -542,7 +558,7 @@ rsync --archive --delete --hard-links --size-only --no-inc-recursive old_cluster on the standby. The directory structure under the specified directories on the primary and standbys must match. Consult the rsync manual page for details on specifying the - remote directory, e.g. + remote directory, e.g., rsync --archive --delete --hard-links --size-only --no-inc-recursive /opt/PostgreSQL/9.5 \ @@ -606,7 +622,8 @@ rsync --archive --delete --hard-links --size-only --no-inc-recursive /vol1/pg_tb If you modified pg_hba.conf, restore its original settings. It might also be necessary to adjust other configuration files in the new - cluster to match the old cluster, e.g. postgresql.conf. + cluster to match the old cluster, e.g., postgresql.conf + (and any files included by it), postgresql.auto.conf. @@ -667,7 +684,7 @@ psql --username=postgres --file=script.sql postgres pg_upgrade completes. (Automatic deletion is not possible if you have user-defined tablespaces inside the old data directory.) You can also delete the old installation directories - (e.g. bin, share). + (e.g., bin, share). @@ -791,7 +808,7 @@ psql --username=postgres --file=script.sql postgres If you are upgrading a pre-PostgreSQL 9.2 cluster that uses a configuration-file-only directory, you must pass the real data directory location to pg_upgrade, and - pass the configuration directory location to the server, e.g. + pass the configuration directory location to the server, e.g., -d /real-data-directory -o '-D /configuration-directory'. @@ -813,7 +830,7 @@ psql --username=postgres --file=script.sql postgres copy with any changes to make it consistent. ( is necessary because rsync only has file modification-time granularity of one second.) You might want to exclude some - files, e.g. postmaster.pid, as documented in postmaster.pid, as documented in . If your file system supports file system snapshots or copy-on-write file copies, you can use that to make a backup of the old cluster and tablespaces, though the snapshot diff --git a/doc/src/sgml/ref/postgres-ref.sgml b/doc/src/sgml/ref/postgres-ref.sgml index 6e62f54c597c..fda678e345c8 100644 --- a/doc/src/sgml/ref/postgres-ref.sgml +++ b/doc/src/sgml/ref/postgres-ref.sgml @@ -143,8 +143,8 @@ PostgreSQL documentation This option is meant for other programs that interact with a server instance, such as , to query configuration - parameter values. User-facing applications should instead use or the pg_settings view. + parameter values. User-facing applications should instead use SHOW or the pg_settings view. @@ -821,7 +821,7 @@ PostgreSQL documentation To start postgres with a specific - port, e.g. 1234: + port, e.g., 1234: $ postgres -p 1234 diff --git a/doc/src/sgml/ref/prepare.sgml b/doc/src/sgml/ref/prepare.sgml index 5ec86aee10d3..57a34ff83c79 100644 --- a/doc/src/sgml/ref/prepare.sgml +++ b/doc/src/sgml/ref/prepare.sgml @@ -66,14 +66,14 @@ PREPARE name [ ( command. + manually cleaned up using the DEALLOCATE command. Prepared statements potentially have the largest performance advantage when a single session is being used to execute a large number of similar statements. The performance difference will be particularly - significant if the statements are complex to plan or rewrite, e.g. + significant if the statements are complex to plan or rewrite, e.g., if the query involves a join of many tables or requires the application of several rules. If the statement is relatively simple to plan and rewrite but relatively expensive to execute, the @@ -163,7 +163,7 @@ PREPARE name [ ( To examine the query plan PostgreSQL is using - for a prepared statement, use , for example + for a prepared statement, use EXPLAIN, for example EXPLAIN EXECUTE name(parameter_values); diff --git a/doc/src/sgml/ref/prepare_transaction.sgml b/doc/src/sgml/ref/prepare_transaction.sgml index 18051983e160..f4f6118ac316 100644 --- a/doc/src/sgml/ref/prepare_transaction.sgml +++ b/doc/src/sgml/ref/prepare_transaction.sgml @@ -39,8 +39,8 @@ PREPARE TRANSACTION transaction_id Once prepared, a transaction can later be committed or rolled back - with - or , + with COMMIT PREPARED + or ROLLBACK PREPARED, respectively. Those commands can be issued from any session, not only the one that executed the original transaction. @@ -92,8 +92,8 @@ PREPARE TRANSACTION transaction_id - This command must be used inside a transaction block. Use to start one. + This command must be used inside a transaction block. Use BEGIN to start one. diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 99ba3e0d34bf..b2e68b781901 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -168,15 +168,10 @@ EOF Specifies the name of the database to connect to. This is equivalent to specifying dbname as the first non-option - argument on the command line. - - - If this parameter contains an = sign or starts - with a valid URI prefix - (postgresql:// - or postgres://), it is treated as a - conninfo string. See for more information. + argument on the command line. The dbname + can be a connection string. + If so, connection string parameters will override any conflicting + command line options. @@ -498,7 +493,7 @@ EOF Never issue a password prompt. If the server requires password - authentication and a password is not available by other means + authentication and a password is not available from other sources such as a .pgpass file, the connection attempt will fail. This option can be useful in batch jobs and scripts where no user is present to enter a password. @@ -518,13 +513,15 @@ EOF Force psql to prompt for a - password before connecting to a database. + password before connecting to a database, even if the password will + not be used. - This option is never essential, since psql - will automatically prompt for a password if the server demands - password authentication. However, psql + If the server requires password authentication and a password is not + available from other sources such as a .pgpass + file, psql will prompt for a + password in any case. However, psql will waste a connection attempt finding out that the server wants a password. In some cases it is worth typing to avoid the extra connection attempt. @@ -635,7 +632,7 @@ EOF psql returns 0 to the shell if it - finished normally, 1 if a fatal error of its own occurs (e.g. out of memory, + finished normally, 1 if a fatal error of its own occurs (e.g., out of memory, file not found), 2 if the connection to the server went bad and the session was not interactive, and 3 if an error occurred in a script and the variable ON_ERROR_STOP was set. @@ -773,8 +770,8 @@ testdb=> Whenever a command is executed, psql also polls for asynchronous notification events generated by - and - . + LISTEN and + NOTIFY. @@ -906,40 +903,65 @@ testdb=> Establishes a new connection to a PostgreSQL server. The connection parameters to use can be specified either - using a positional syntax, or using conninfo connection - strings as detailed in . + using a positional syntax (one or more of database name, user, + host, and port), or using a conninfo + connection string as detailed in + . If no arguments are given, a + new connection is made using the same parameters as before. - Where the command omits database name, user, host, or port, the new - connection can reuse values from the previous connection. By default, - values from the previous connection are reused except when processing - a conninfo string. Passing a first argument - of -reuse-previous=on - or -reuse-previous=off overrides that default. - When the command neither specifies nor reuses a particular parameter, - the libpq default is used. Specifying any + Specifying any of dbname, username, host or port as - is equivalent to omitting that parameter. - If hostaddr was specified in the original - connection's conninfo, that address is reused - for the new connection (disregarding any other host specification). + + + + The new connection can re-use connection parameters from the previous + connection; not only database name, user, host, and port, but other + settings such as sslmode. By default, + parameters are re-used in the positional syntax, but not when + a conninfo string is given. Passing a + first argument of -reuse-previous=on + or -reuse-previous=off overrides that default. If + parameters are re-used, then any parameter not explicitly specified as + a positional parameter or in the conninfo + string is taken from the existing connection's parameters. An + exception is that if the host setting + is changed from its previous value using the positional syntax, + any hostaddr setting present in the + existing connection's parameters is dropped. + Also, any password used for the existing connection will be re-used + only if the user, host, and port settings are not changed. + When the command neither specifies nor reuses a particular parameter, + the libpq default is used. If the new connection is successfully made, the previous connection is closed. - If the connection attempt failed (wrong user name, access - denied, etc.), the previous connection will only be kept if - psql is in interactive mode. When - executing a non-interactive script, processing will - immediately stop with an error. This distinction was chosen as + If the connection attempt fails (wrong user name, access + denied, etc.), the previous connection will be kept if + psql is in interactive mode. But when + executing a non-interactive script, the old connection is closed + and an error is reported. That may or may not terminate the + script; if it does not, all database-accessing commands will fail + until another \connect command is successfully + executed. This distinction was chosen as a user convenience against typos on the one hand, and a safety mechanism that scripts are not accidentally acting on the wrong database on the other hand. + Note that whenever a \connect command attempts + to re-use parameters, the values re-used are those of the last + successful connection, not of any failed attempts made subsequently. + However, in the case of a + non-interactive \connect failure, no parameters + are allowed to be re-used later, since the script would likely be + expecting the values from the failed \connect + to be re-used. @@ -949,6 +971,7 @@ testdb=> => \c mydb myuser host.dom 6432 => \c service=foo => \c "host=localhost port=5432 dbname=mydb connect_timeout=10 sslmode=disable" +=> \c -reuse-previous=on sslmode=require -- changes only sslmode => \c postgresql://tom@localhost/mydb?application_name=myapp @@ -1010,7 +1033,7 @@ testdb=> Performs a frontend (client) copy. This is an operation that - runs an SQL + runs an SQL COPY command, but instead of the server reading or writing the specified file, psql reads or writes the file and @@ -1047,9 +1070,9 @@ testdb=> The syntax of this command is similar to that of the - SQL + SQL COPY command. All options other than the data source/destination are - as specified for . + as specified for COPY. Because of this, special parsing rules apply to the \copy meta-command. Unlike most other meta-commands, the entire remainder of the line is always taken to be the arguments of \copy, @@ -1196,8 +1219,10 @@ testdb=> more information is displayed: any comments associated with the columns of the table are shown, as is the presence of OIDs in the table, the view definition if the relation is a view, a non-default - replica - identity setting. + replica + identity setting and the + access method name + if the relation has an access method. @@ -1412,8 +1437,8 @@ testdb=> - Descriptions for objects can be created with the + Descriptions for objects can be created with the COMMENT SQL command. @@ -1450,9 +1475,9 @@ testdb=> - The command is used to set - default access privileges. The meaning of the - privilege display is explained in + The ALTER DEFAULT + PRIVILEGES command is used to set default access + privileges. The meaning of the privilege display is explained in . @@ -1767,8 +1792,8 @@ testdb=> - The and - + The GRANT and + REVOKE commands are used to set access privileges. The meaning of the privilege display is explained in . @@ -1823,8 +1848,8 @@ testdb=> - The and - + The ALTER ROLE and + ALTER DATABASE commands are used to define per-role and per-database configuration settings. @@ -3026,7 +3051,7 @@ lo_import 152801 In latex-longtable format, this controls the proportional width of each column containing a left-aligned data type. It is specified as a whitespace-separated list of values, - e.g. '0.2 0.2 0.6'. Unspecified output columns + e.g., '0.2 0.2 0.6'. Unspecified output columns use the last specified value. @@ -3195,7 +3220,7 @@ lo_import 152801 This command is unrelated to the SQL - command . + command SET. @@ -4484,7 +4509,7 @@ testdb=> \set PROMPT1 '%[%033[1;33;40m%]%n@%/%R%[%033[0m%]%# ' psql starts up. Tab-completion is also supported, although the completion logic makes no claim to be an SQL parser. The queries generated by tab-completion - can also interfere with other SQL commands, e.g. SET + can also interfere with other SQL commands, e.g., SET TRANSACTION ISOLATION LEVEL. If for some reason you do not like the tab completion, you can turn it off by putting this in a file named diff --git a/doc/src/sgml/ref/reassign_owned.sgml b/doc/src/sgml/ref/reassign_owned.sgml index 42f72a726fd1..ab692bd06908 100644 --- a/doc/src/sgml/ref/reassign_owned.sgml +++ b/doc/src/sgml/ref/reassign_owned.sgml @@ -21,8 +21,8 @@ PostgreSQL documentation -REASSIGN OWNED BY { old_role | CURRENT_USER | SESSION_USER } [, ...] - TO { new_role | CURRENT_USER | SESSION_USER } +REASSIGN OWNED BY { old_role | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...] + TO { new_role | CURRENT_ROLE | CURRENT_USER | SESSION_USER } @@ -82,7 +82,7 @@ REASSIGN OWNED BY { old_role | CURR - The command is an alternative that + The DROP OWNED command is an alternative that simply drops all the database objects owned by one or more roles. diff --git a/doc/src/sgml/ref/refresh_materialized_view.sgml b/doc/src/sgml/ref/refresh_materialized_view.sgml index 8ae62671adab..3bf888444782 100644 --- a/doc/src/sgml/ref/refresh_materialized_view.sgml +++ b/doc/src/sgml/ref/refresh_materialized_view.sgml @@ -94,7 +94,7 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] name While the default index for future - + CLUSTER operations is retained, REFRESH MATERIALIZED VIEW does not order the generated rows based on this property. If you want the data to be ordered upon generation, you must use an ORDER BY diff --git a/doc/src/sgml/ref/reindex.sgml b/doc/src/sgml/ref/reindex.sgml index 33af4ae02a13..f6d425a6910e 100644 --- a/doc/src/sgml/ref/reindex.sgml +++ b/doc/src/sgml/ref/reindex.sgml @@ -38,6 +38,15 @@ REINDEX [ ( option [, ...] ) ] { IN several scenarios in which to use REINDEX: + + + The index depends on the sort order of a collation, and the definition + of the collation has changed. This can cause index scans to fail to + find keys that are present. See for + more information. + + + An index has become corrupted, and no longer contains valid @@ -252,7 +261,7 @@ REINDEX [ ( option [, ...] ) ] { IN Reindexing a single index or table requires being the owner of that index or table. Reindexing a schema or database requires being the - owner of that schema or database. Note that is therefore sometimes + owner of that schema or database. Note specifically that it's thus possible for non-superusers to rebuild indexes of tables owned by other users. However, as a special exception, when REINDEX DATABASE, REINDEX SCHEMA diff --git a/doc/src/sgml/ref/reindexdb.sgml b/doc/src/sgml/ref/reindexdb.sgml index 026fd018d93e..574144533378 100644 --- a/doc/src/sgml/ref/reindexdb.sgml +++ b/doc/src/sgml/ref/reindexdb.sgml @@ -93,7 +93,7 @@ PostgreSQL documentation reindexdb is a wrapper around the SQL - command . + command REINDEX. There is no effective difference between reindexing databases via this utility and via other methods for accessing the server. @@ -134,12 +134,15 @@ PostgreSQL documentation - Specifies the name of the database to be reindexed. - If this is not specified and (or - ) is not used, the database name is read + Specifies the name of the database to be reindexed, + when / is not used. + If this is not specified, the database name is read from the environment variable PGDATABASE. If that is not set, the user name specified for the connection is - used. + used. The dbname can be a connection string. If so, + connection string parameters will override any conflicting command + line options. @@ -174,8 +177,8 @@ PostgreSQL documentation Execute the reindex commands in parallel by running njobs - commands simultaneously. This option reduces the time of the - processing but it also increases the load on the database server. + commands simultaneously. This option may reduce the processing time + but it also increases the load on the database server. reindexdb will open @@ -348,10 +351,16 @@ PostgreSQL documentation - Specifies the name of the database to connect to discover what other - databases should be reindexed. If not specified, the - postgres database will be used, - and if that does not exist, template1 will be used. + Specifies the name of the database to connect to to discover which + databases should be reindexed, + when / is used. + If not specified, the postgres database will be used, + or if that does not exist, template1 will be used. + This can be a connection + string. If so, connection string parameters will override any + conflicting command line options. Also, connection string parameters + other than the database name itself will be re-used when connecting + to other databases. diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml index b6bac21c57a3..35ff87a4f5e2 100644 --- a/doc/src/sgml/ref/revoke.sgml +++ b/doc/src/sgml/ref/revoke.sgml @@ -114,6 +114,7 @@ REVOKE [ ADMIN OPTION FOR ] [ GROUP ] role_name | PUBLIC + | CURRENT_ROLE | CURRENT_USER | SESSION_USER @@ -130,7 +131,7 @@ REVOKE [ ADMIN OPTION FOR ] - See the description of the command for + See the description of the GRANT command for the meaning of the privilege types. @@ -291,7 +292,7 @@ REVOKE admins FROM joe; Compatibility - The compatibility notes of the command + The compatibility notes of the GRANT command apply analogously to REVOKE. The keyword RESTRICT or CASCADE is required according to the standard, but PostgreSQL diff --git a/doc/src/sgml/ref/rollback.sgml b/doc/src/sgml/ref/rollback.sgml index 1357eaa8323a..142f71e77425 100644 --- a/doc/src/sgml/ref/rollback.sgml +++ b/doc/src/sgml/ref/rollback.sgml @@ -70,7 +70,7 @@ ROLLBACK [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ] Notes - Use to + Use COMMIT to successfully terminate a transaction. diff --git a/doc/src/sgml/ref/rollback_to.sgml b/doc/src/sgml/ref/rollback_to.sgml index 4d5647a302e2..3d5a241e1aa9 100644 --- a/doc/src/sgml/ref/rollback_to.sgml +++ b/doc/src/sgml/ref/rollback_to.sgml @@ -64,7 +64,7 @@ ROLLBACK [ WORK | TRANSACTION ] TO [ SAVEPOINT ] savepoint_nameNotes - Use to destroy a savepoint + Use RELEASE SAVEPOINT to destroy a savepoint without discarding the effects of commands executed after it was established. diff --git a/doc/src/sgml/ref/savepoint.sgml b/doc/src/sgml/ref/savepoint.sgml index 87243b1d2046..b17342a1ee6a 100644 --- a/doc/src/sgml/ref/savepoint.sgml +++ b/doc/src/sgml/ref/savepoint.sgml @@ -64,8 +64,8 @@ SAVEPOINT savepoint_name Notes - Use to - rollback to a savepoint. Use + Use ROLLBACK TO to + rollback to a savepoint. Use RELEASE SAVEPOINT to destroy a savepoint, keeping the effects of commands executed after it was established. diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml index e9688cce214b..9b87bcd51961 100644 --- a/doc/src/sgml/ref/security_label.sgml +++ b/doc/src/sgml/ref/security_label.sgml @@ -127,11 +127,12 @@ SECURITY LABEL [ FOR provider ] ON argument: IN, OUT, INOUT, or VARIADIC. If omitted, the default is IN. - Note that SECURITY LABEL does not actually - pay any attention to OUT arguments, since only the input - arguments are needed to determine the function's identity. - So it is sufficient to list the IN, INOUT, - and VARIADIC arguments. + Note that SECURITY LABEL does not actually pay any + attention to OUT arguments for functions and + aggregates (but not procedures), since only the input arguments are + needed to determine the function's identity. So it is sufficient to + list the IN, INOUT, and + VARIADIC arguments for functions and aggregates. diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml index ab0bd7e0cae4..672d9698e480 100644 --- a/doc/src/sgml/ref/select.sgml +++ b/doc/src/sgml/ref/select.sgml @@ -38,8 +38,8 @@ SELECT [ ALL | DISTINCT [ ON ( expressionfrom_item [, ...] ] [ WHERE condition ] [ GROUP BY grouping_element [, ...] ] - [ HAVING condition [, ...] ] - [ WINDOW window_name AS (window_specification) ] + [ HAVING condition ] + [ WINDOW window_name AS ( window_definition ) [, ...] ] [ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] select ] [ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...] ] [ LIMIT { count | ALL } ] @@ -462,7 +462,7 @@ TABLE [ ONLY ] table_name [ * ] sub-SELECT must be surrounded by parentheses, and an alias must be provided for it. A - command + VALUES command can also be used here. @@ -1550,7 +1550,7 @@ KEY SHARE to the row-level lock(s) — the required ROW SHARE table-level lock is still taken in the ordinary way (see ). You can use - + LOCK with the NOWAIT option first, if you need to acquire the table-level lock without waiting. diff --git a/doc/src/sgml/ref/select_into.sgml b/doc/src/sgml/ref/select_into.sgml index e4133adf4709..7b327d9eeef3 100644 --- a/doc/src/sgml/ref/select_into.sgml +++ b/doc/src/sgml/ref/select_into.sgml @@ -28,7 +28,8 @@ SELECT [ ALL | DISTINCT [ ON ( expressionfrom_item [, ...] ] [ WHERE condition ] [ GROUP BY expression [, ...] ] - [ HAVING condition [, ...] ] + [ HAVING condition ] + [ WINDOW window_name AS ( window_definition ) [, ...] ] [ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] select ] [ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...] ] [ LIMIT { count | ALL } ] @@ -94,7 +95,7 @@ SELECT [ ALL | DISTINCT [ ON ( expressionNotes - is functionally similar to + CREATE TABLE AS is functionally similar to SELECT INTO. CREATE TABLE AS is the recommended syntax, since this form of SELECT INTO is not available in ECPG @@ -108,8 +109,8 @@ SELECT [ ALL | DISTINCT [ ON ( expressionCREATE TABLE AS, SELECT INTO does not allow to specify properties like a table's access method with or the table's - tablespace with . Use if necessary. Therefore, the default table + tablespace with . Use + CREATE TABLE AS if necessary. Therefore, the default table access method is chosen for the new table. See for more information. diff --git a/doc/src/sgml/ref/set_role.sgml b/doc/src/sgml/ref/set_role.sgml index a4842f363c8b..739f2c5cdfa5 100644 --- a/doc/src/sgml/ref/set_role.sgml +++ b/doc/src/sgml/ref/set_role.sgml @@ -48,7 +48,7 @@ RESET ROLE The SESSION and LOCAL modifiers act the same - as for the regular + as for the regular SET command. @@ -82,7 +82,7 @@ RESET ROLE SET ROLE has effects comparable to - , but the privilege + SET SESSION AUTHORIZATION, but the privilege checks involved are quite different. Also, SET SESSION AUTHORIZATION determines which roles are allowable for later SET ROLE commands, whereas changing @@ -92,7 +92,7 @@ RESET ROLE SET ROLE does not process session variables as specified by - the role's settings; this only happens during + the role's ALTER ROLE settings; this only happens during login. diff --git a/doc/src/sgml/ref/set_session_auth.sgml b/doc/src/sgml/ref/set_session_auth.sgml index 6a838e58b764..e44e78ed8d67 100644 --- a/doc/src/sgml/ref/set_session_auth.sgml +++ b/doc/src/sgml/ref/set_session_auth.sgml @@ -45,7 +45,7 @@ RESET SESSION AUTHORIZATION identifier is normally equal to the session user identifier, but might change temporarily in the context of SECURITY DEFINER functions and similar mechanisms; it can also be changed by - . + SET ROLE. The current user identifier is relevant for permission checking. @@ -58,7 +58,7 @@ RESET SESSION AUTHORIZATION The SESSION and LOCAL modifiers act the same - as for the regular + as for the regular SET command. diff --git a/doc/src/sgml/ref/start_transaction.sgml b/doc/src/sgml/ref/start_transaction.sgml index d6cd1d417792..74ccd7e3456c 100644 --- a/doc/src/sgml/ref/start_transaction.sgml +++ b/doc/src/sgml/ref/start_transaction.sgml @@ -37,8 +37,8 @@ START TRANSACTION [ transaction_mode This command begins a new transaction block. If the isolation level, read/write mode, or deferrable mode is specified, the new transaction has those - characteristics, as if was executed. This is the same - as the command. + characteristics, as if SET TRANSACTION was executed. This is the same + as the BEGIN command. diff --git a/doc/src/sgml/ref/truncate.sgml b/doc/src/sgml/ref/truncate.sgml index 5922ee579e11..91cdac556233 100644 --- a/doc/src/sgml/ref/truncate.sgml +++ b/doc/src/sgml/ref/truncate.sgml @@ -160,8 +160,7 @@ TRUNCATE [ TABLE ] [ ONLY ] name [ When RESTART IDENTITY is specified, the implied ALTER SEQUENCE RESTART operations are also done transactionally; that is, they will be rolled back if the surrounding - transaction does not commit. This is unlike the normal behavior of - ALTER SEQUENCE RESTART. Be aware that if any additional + transaction does not commit. Be aware that if any additional sequence operations are done on the restarted sequences before the transaction rolls back, the effects of these operations on the sequences will be rolled back, but not their effects on currval(); diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml index a48f75ad7baf..21ab57d88048 100644 --- a/doc/src/sgml/ref/vacuum.sgml +++ b/doc/src/sgml/ref/vacuum.sgml @@ -235,22 +235,22 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ integer background workers (for the details of each vacuum phase, please - refer to ). In plain VACUUM - (without FULL), if the PARALLEL option - is omitted, then the number of workers is determined based on the number of - indexes on the relation that support parallel vacuum operation and is further - limited by . An index - can participate in parallel vacuum if and only if the size of the index is - more than . Please note - that it is not guaranteed that the number of parallel workers specified in - integer will be used during - execution. It is possible for a vacuum to run with fewer workers than - specified, or even with no workers at all. Only one worker can be used per - index. So parallel workers are launched only when there are at least - 2 indexes in the table. Workers for vacuum are launched - before the start of each phase and exit at the end of the phase. These - behaviors might change in a future release. This option can't be used with - the FULL option. + refer to ). The number of workers used + to perform the operation is equal to the number of indexes on the + relation that support parallel vacuum which is limited by the number of + workers specified with PARALLEL option if any which is + further limited by . + An index can participate in parallel vacuum if and only if the size of the + index is more than . + Please note that it is not guaranteed that the number of parallel workers + specified in integer will be + used during execution. It is possible for a vacuum to run with fewer + workers than specified, or even with no workers at all. Only one worker + can be used per index. So parallel workers are launched only when there + are at least 2 indexes in the table. Workers for + vacuum are launched before the start of each phase and exit at the end of + the phase. These behaviors might change in a future release. This + option can't be used with the FULL option. diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml index 95d6894cb03a..a90fc9322f99 100644 --- a/doc/src/sgml/ref/vacuumdb.sgml +++ b/doc/src/sgml/ref/vacuumdb.sgml @@ -62,7 +62,7 @@ PostgreSQL documentation vacuumdb is a wrapper around the SQL - command . + command VACUUM. There is no effective difference between vacuuming and analyzing databases via this utility and via other methods for accessing the server. @@ -92,12 +92,15 @@ PostgreSQL documentation - Specifies the name of the database to be cleaned or analyzed. - If this is not specified and (or - ) is not used, the database name is read + Specifies the name of the database to be cleaned or analyzed, + when / is not used. + If this is not specified, the database name is read from the environment variable PGDATABASE. If that is not set, the user name specified for the connection is - used. + used. The dbname can be a connection string. If so, + connection string parameters will override any conflicting command + line options. @@ -155,8 +158,8 @@ PostgreSQL documentation Execute the vacuum or analyze commands in parallel by running njobs - commands simultaneously. This option reduces the time of the - processing but it also increases the load on the database server. + commands simultaneously. This option may reduce the processing time + but it also increases the load on the database server. vacuumdb will open @@ -471,10 +474,16 @@ PostgreSQL documentation - Specifies the name of the database to connect to discover what other - databases should be vacuumed. If not specified, the - postgres database will be used, - and if that does not exist, template1 will be used. + Specifies the name of the database to connect to to discover which + databases should be vacuumed, + when / is used. + If not specified, the postgres database will be used, + or if that does not exist, template1 will be used. + This can be a connection + string. If so, connection string parameters will override any + conflicting command line options. Also, connection string parameters + other than the database name itself will be re-used when connecting + to other databases. diff --git a/gpMgmt/test/behave/mgmt_utils/steps/data/gpcheckcat/add_operator.sql b/gpMgmt/test/behave/mgmt_utils/steps/data/gpcheckcat/add_operator.sql index d63ff0295dac..08aa13707eb5 100644 --- a/gpMgmt/test/behave/mgmt_utils/steps/data/gpcheckcat/add_operator.sql +++ b/gpMgmt/test/behave/mgmt_utils/steps/data/gpcheckcat/add_operator.sql @@ -2,4 +2,4 @@ CREATE FUNCTION my_pk_schema.is_zero(int) RETURNS TABLE(f1 boolean) AS $$ select $1 = 0 $$ LANGUAGE SQL; -CREATE OPERATOR my_pk_schema.!# (PROCEDURE = my_pk_schema.is_zero,LEFTARG = integer); +CREATE OPERATOR my_pk_schema.!# (PROCEDURE = my_pk_schema.is_zero,RIGHTARG = integer); diff --git a/src/Makefile b/src/Makefile index d088aaec6ceb..0fe4c39a4f29 100644 --- a/src/Makefile +++ b/src/Makefile @@ -69,7 +69,6 @@ clean: $(MAKE) -C test $@ $(MAKE) -C tutorial NO_PGXS=1 $@ $(MAKE) -C test/isolation $@ - $(MAKE) -C test/thread $@ $(MAKE) -C $(MOCK_DIR) $@ $(MAKE) -C $(CMOCKERY_DIR) $@ @@ -77,7 +76,6 @@ distclean maintainer-clean: $(MAKE) -C test $@ $(MAKE) -C tutorial NO_PGXS=1 $@ $(MAKE) -C test/isolation $@ - $(MAKE) -C test/thread $@ $(MAKE) -C $(MOCK_DIR) $@ $(MAKE) -C $(CMOCKERY_DIR) $@ rm -f Makefile.port Makefile.global diff --git a/src/Makefile.global.in b/src/Makefile.global.in index 8275b3298c63..9249c5ff231b 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -295,7 +295,8 @@ SUN_STUDIO_CC = @SUN_STUDIO_CC@ CXX = @CXX@ CFLAGS = @CFLAGS@ CFLAGS_SL = @CFLAGS_SL@ -CFLAGS_VECTOR = @CFLAGS_VECTOR@ +CFLAGS_UNROLL_LOOPS = @CFLAGS_UNROLL_LOOPS@ +CFLAGS_VECTORIZE = @CFLAGS_VECTORIZE@ CFLAGS_SSE42 = @CFLAGS_SSE42@ CFLAGS_ARMV8_CRC32C = @CFLAGS_ARMV8_CRC32C@ PERMIT_DECLARATION_AFTER_STATEMENT = @PERMIT_DECLARATION_AFTER_STATEMENT@ diff --git a/src/backend/access/aocs/aocs_compaction.c b/src/backend/access/aocs/aocs_compaction.c index 2cb52b79eb4d..82a5e3629c0a 100644 --- a/src/backend/access/aocs/aocs_compaction.c +++ b/src/backend/access/aocs/aocs_compaction.c @@ -191,7 +191,7 @@ AOCSMoveTuple(TupleTableSlot *slot, /* insert index' tuples if needed */ if (resultRelInfo->ri_NumIndices > 0) { - ExecInsertIndexTuples(slot, estate, false, false, NIL); + ExecInsertIndexTuples(resultRelInfo, slot, estate, false, false, NIL); ResetPerTupleExprContext(estate); } @@ -263,9 +263,6 @@ AOCSSegmentFileFullCompaction(Relation aorel, resultRelInfo->ri_RelationDesc = aorel; resultRelInfo->ri_TrigDesc = NULL; /* we don't fire triggers */ ExecOpenIndices(resultRelInfo, false); - estate->es_result_relations = resultRelInfo; - estate->es_num_result_relations = 1; - estate->es_result_relation_info = resultRelInfo; /* * We don't want uniqueness checks to be performed while "insert"ing tuples diff --git a/src/backend/access/appendonly/appendonly_compaction.c b/src/backend/access/appendonly/appendonly_compaction.c index a9ca318d06d1..e50f2d59716a 100644 --- a/src/backend/access/appendonly/appendonly_compaction.c +++ b/src/backend/access/appendonly/appendonly_compaction.c @@ -301,7 +301,8 @@ AppendOnlyMoveTuple(TupleTableSlot *slot, /* insert index' tuples if needed */ if (resultRelInfo->ri_NumIndices > 0) { - ExecInsertIndexTuples(slot, + ExecInsertIndexTuples(resultRelInfo, + slot, estate, false, /* noDupError */ NULL, /* specConflict */ @@ -444,9 +445,6 @@ AppendOnlySegmentFileFullCompaction(Relation aorel, resultRelInfo->ri_RelationDesc = aorel; resultRelInfo->ri_TrigDesc = NULL; /* we don't fire triggers */ ExecOpenIndices(resultRelInfo, false); - estate->es_result_relations = resultRelInfo; - estate->es_num_result_relations = 1; - estate->es_result_relation_info = resultRelInfo; /* * We don't want uniqueness checks to be performed while "insert"ing tuples diff --git a/src/backend/access/brin/brin_tuple.c b/src/backend/access/brin/brin_tuple.c index 6cb7c26b39f2..46e6b23c8742 100644 --- a/src/backend/access/brin/brin_tuple.c +++ b/src/backend/access/brin/brin_tuple.c @@ -243,7 +243,6 @@ brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple, *bitP |= bitmask; } - bitP = ((bits8 *) (rettuple + SizeOfBrinTuple)) - 1; } if (tuple->bt_placeholder) diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index eb9a8a43c264..4efbb2f48cc9 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -764,7 +764,7 @@ expand_tuple(HeapTuple *targetHeapTuple, { AttrMissing *attrmiss = NULL; int attnum; - int firstmissingnum = 0; + int firstmissingnum; bool hasNulls = HeapTupleHasNulls(sourceTuple); HeapTupleHeader targetTHeader; HeapTupleHeader sourceTHeader = sourceTuple->t_data; diff --git a/src/backend/access/gin/README b/src/backend/access/gin/README index 125a82219b9e..41d4e1e8a093 100644 --- a/src/backend/access/gin/README +++ b/src/backend/access/gin/README @@ -413,7 +413,7 @@ leftmost leaf of the tree. Deletion algorithm keeps exclusive locks on left siblings of pages comprising currently investigated path. Thus, if current page is to be removed, all required pages to remove both downlink and rightlink are already locked. That -evades potential right to left page locking order, which could deadlock with +avoids potential right to left page locking order, which could deadlock with concurrent stepping right. A search concurrent to page deletion might already have read a pointer to the diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c index 8d08b05f5156..82788a5c367a 100644 --- a/src/backend/access/gin/ginbtree.c +++ b/src/backend/access/gin/ginbtree.c @@ -241,7 +241,6 @@ ginFindParents(GinBtree btree, GinBtreeStack *stack) blkno = root->blkno; buffer = root->buffer; - offset = InvalidOffsetNumber; ptr = (GinBtreeStack *) palloc(sizeof(GinBtreeStack)); diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c index 4b36f927c03c..51f2c3770972 100644 --- a/src/backend/access/gin/ginget.c +++ b/src/backend/access/gin/ginget.c @@ -264,24 +264,28 @@ collectMatchBitmap(GinBtreeData *btree, GinBtreeStack *stack, /* Search forward to re-find idatum */ for (;;) { - Datum newDatum; - GinNullCategory newCategory; - if (moveRightIfItNeeded(btree, stack, snapshot) == false) - elog(ERROR, "lost saved point in index"); /* must not happen !!! */ + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to re-find tuple within index \"%s\"", + RelationGetRelationName(btree->index)))); page = BufferGetPage(stack->buffer); itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off)); - if (gintuple_get_attrnum(btree->ginstate, itup) != attnum) - elog(ERROR, "lost saved point in index"); /* must not happen !!! */ - newDatum = gintuple_get_key(btree->ginstate, itup, - &newCategory); + if (gintuple_get_attrnum(btree->ginstate, itup) == attnum) + { + Datum newDatum; + GinNullCategory newCategory; + + newDatum = gintuple_get_key(btree->ginstate, itup, + &newCategory); - if (ginCompareEntries(btree->ginstate, attnum, - newDatum, newCategory, - idatum, icategory) == 0) - break; /* Found! */ + if (ginCompareEntries(btree->ginstate, attnum, + newDatum, newCategory, + idatum, icategory) == 0) + break; /* Found! */ + } stack->off++; } diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c index 9cd6638df621..0935a6d9e53d 100644 --- a/src/backend/access/gin/ginvacuum.c +++ b/src/backend/access/gin/ginvacuum.c @@ -727,7 +727,7 @@ ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) * entries. This is bogus if the index is partial, but it's real hard to * tell how many distinct heap entries are referenced by a GIN index. */ - stats->num_index_tuples = info->num_heap_tuples; + stats->num_index_tuples = Max(info->num_heap_tuples, 0); stats->estimated_count = info->estimated_count; /* diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c index 671b5e9186ff..9d3fa9c3b75b 100644 --- a/src/backend/access/gist/gistbuild.c +++ b/src/backend/access/gist/gistbuild.c @@ -3,6 +3,24 @@ * gistbuild.c * build algorithm for GiST indexes implementation. * + * There are two different strategies: + * + * 1. Sort all input tuples, pack them into GiST leaf pages in the sorted + * order, and create downlinks and internal pages as we go. This builds + * the index from the bottom up, similar to how B-tree index build + * works. + * + * 2. Start with an empty index, and insert all tuples one by one. + * + * The sorted method is used if the operator classes for all columns have + * a 'sortsupport' defined. Otherwise, we resort to the second strategy. + * + * The second strategy can optionally use buffers at different levels of + * the tree to reduce I/O, see "Buffering build algorithm" in the README + * for a more detailed explanation. It initially calls insert over and + * over, but switches to the buffered algorithm after a certain number of + * tuples (unless buffering mode is disabled). + * * * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -28,6 +46,7 @@ #include "storage/smgr.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "utils/tuplesort.h" /* Step of index tuples for check whether to switch to buffering build mode */ #define BUFFERING_MODE_SWITCH_CHECK_STEP 256 @@ -40,8 +59,14 @@ */ #define BUFFERING_MODE_TUPLE_SIZE_STATS_TARGET 4096 +/* + * Strategy used to build the index. It can change between the + * GIST_BUFFERING_* modes on the fly, but if the Sorted method is used, + * that needs to be decided up-front and cannot be changed afterwards. + */ typedef enum { + GIST_SORTED_BUILD, /* bottom-up build by sorting */ GIST_BUFFERING_DISABLED, /* in regular build mode and aren't going to * switch */ GIST_BUFFERING_AUTO, /* in regular build mode, but will switch to @@ -51,7 +76,7 @@ typedef enum * before switching to the buffering build * mode */ GIST_BUFFERING_ACTIVE /* in buffering build mode */ -} GistBufferingMode; +} GistBuildMode; /* Working state for gistbuild and its callback */ typedef struct @@ -60,23 +85,58 @@ typedef struct Relation heaprel; GISTSTATE *giststate; - int64 indtuples; /* number of tuples indexed */ - int64 indtuplesSize; /* total size of all indexed tuples */ - Size freespace; /* amount of free space to leave on pages */ + GistBuildMode buildMode; + + int64 indtuples; /* number of tuples indexed */ + /* * Extra data structures used during a buffering build. 'gfbb' contains * information related to managing the build buffers. 'parentMap' is a * lookup table of the parent of each internal page. */ + int64 indtuplesSize; /* total size of all indexed tuples */ GISTBuildBuffers *gfbb; HTAB *parentMap; - GistBufferingMode bufferingMode; + /* + * Extra data structures used during a sorting build. + */ + Tuplesortstate *sortstate; /* state data for tuplesort.c */ + + BlockNumber pages_allocated; + BlockNumber pages_written; + + int ready_num_pages; + BlockNumber ready_blknos[XLR_MAX_BLOCK_ID]; + Page ready_pages[XLR_MAX_BLOCK_ID]; } GISTBuildState; +/* + * In sorted build, we use a stack of these structs, one for each level, + * to hold an in-memory buffer of the righmost page at the level. When the + * page fills up, it is written out and a new page is allocated. + */ +typedef struct GistSortedBuildPageState +{ + Page page; + struct GistSortedBuildPageState *parent; /* Upper level, if any */ +} GistSortedBuildPageState; + /* prototypes for private functions */ + +static void gistSortedBuildCallback(Relation index, ItemPointer tid, + Datum *values, bool *isnull, + bool tupleIsAlive, void *state); +static void gist_indexsortbuild(GISTBuildState *state); +static void gist_indexsortbuild_pagestate_add(GISTBuildState *state, + GistSortedBuildPageState *pagestate, + IndexTuple itup); +static void gist_indexsortbuild_pagestate_flush(GISTBuildState *state, + GistSortedBuildPageState *pagestate); +static void gist_indexsortbuild_flush_ready_pages(GISTBuildState *state); + static void gistInitBuffering(GISTBuildState *buildstate); static int calculatePagesPerBuffer(GISTBuildState *buildstate, int levelStep); static void gistBuildCallback(Relation index, @@ -107,10 +167,9 @@ static void gistMemorizeParent(GISTBuildState *buildstate, BlockNumber child, static void gistMemorizeAllDownlinks(GISTBuildState *buildstate, Buffer parent); static BlockNumber gistGetParent(GISTBuildState *buildstate, BlockNumber child); + /* - * Main entry point to GiST index build. Initially calls insert over and over, - * but switches to more efficient buffering build algorithm after a certain - * number of tuples (unless buffering mode is disabled). + * Main entry point to GiST index build. */ IndexBuildResult * gistbuild(Relation heap, Relation index, IndexInfo *indexInfo) @@ -118,124 +177,425 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo) IndexBuildResult *result; double reltuples; GISTBuildState buildstate; - Buffer buffer; - Page page; MemoryContext oldcxt = CurrentMemoryContext; int fillfactor; + Oid SortSupportFnOids[INDEX_MAX_KEYS]; + GiSTOptions *options = (GiSTOptions *) index->rd_options; + + /* + * We expect to be called exactly once for any index relation. If that's + * not the case, big trouble's what we have. + */ + if (RelationGetNumberOfBlocks(index) != 0) + elog(ERROR, "index \"%s\" already contains data", + RelationGetRelationName(index)); buildstate.indexrel = index; buildstate.heaprel = heap; + buildstate.sortstate = NULL; + buildstate.giststate = initGISTstate(index); - if (index->rd_options) - { - /* Get buffering mode from the options string */ - GiSTOptions *options = (GiSTOptions *) index->rd_options; + /* + * Create a temporary memory context that is reset once for each tuple + * processed. (Note: we don't bother to make this a child of the + * giststate's scanCxt, so we have to delete it separately at the end.) + */ + buildstate.giststate->tempCxt = createTempGistContext(); + /* + * Choose build strategy. First check whether the user specified to use + * buffering mode. (The use-case for that in the field is somewhat + * questionable perhaps, but it's important for testing purposes.) + */ + if (options) + { if (options->buffering_mode == GIST_OPTION_BUFFERING_ON) - buildstate.bufferingMode = GIST_BUFFERING_STATS; + buildstate.buildMode = GIST_BUFFERING_STATS; else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF) - buildstate.bufferingMode = GIST_BUFFERING_DISABLED; - else - buildstate.bufferingMode = GIST_BUFFERING_AUTO; - - fillfactor = options->fillfactor; + buildstate.buildMode = GIST_BUFFERING_DISABLED; + else /* must be "auto" */ + buildstate.buildMode = GIST_BUFFERING_AUTO; } else { - /* - * By default, switch to buffering mode when the index grows too large - * to fit in cache. - */ - buildstate.bufferingMode = GIST_BUFFERING_AUTO; - fillfactor = GIST_DEFAULT_FILLFACTOR; + buildstate.buildMode = GIST_BUFFERING_AUTO; + } + + /* + * Unless buffering mode was forced, see if we can use sorting instead. + */ + if (buildstate.buildMode != GIST_BUFFERING_STATS) + { + bool hasallsortsupports = true; + int keyscount = IndexRelationGetNumberOfKeyAttributes(index); + + for (int i = 0; i < keyscount; i++) + { + SortSupportFnOids[i] = index_getprocid(index, i + 1, + GIST_SORTSUPPORT_PROC); + if (!OidIsValid(SortSupportFnOids[i])) + { + hasallsortsupports = false; + break; + } + } + if (hasallsortsupports) + buildstate.buildMode = GIST_SORTED_BUILD; } - /* Calculate target amount of free space to leave on pages */ + + /* + * Calculate target amount of free space to leave on pages. + */ + fillfactor = options ? options->fillfactor : GIST_DEFAULT_FILLFACTOR; buildstate.freespace = BLCKSZ * (100 - fillfactor) / 100; /* - * We expect to be called exactly once for any index relation. If that's - * not the case, big trouble's what we have. + * Build the index using the chosen strategy. */ - if (RelationGetNumberOfBlocks(index) != 0) - elog(ERROR, "index \"%s\" already contains data", - RelationGetRelationName(index)); + buildstate.indtuples = 0; + buildstate.indtuplesSize = 0; - /* no locking is needed */ - buildstate.giststate = initGISTstate(index); + if (buildstate.buildMode == GIST_SORTED_BUILD) + { + /* + * Sort all data, build the index from bottom up. + */ + buildstate.sortstate = tuplesort_begin_index_gist(heap, + index, + maintenance_work_mem, + NULL, + false); + + /* Scan the table, adding all tuples to the tuplesort */ + reltuples = table_index_build_scan(heap, index, indexInfo, true, true, + gistSortedBuildCallback, + (void *) &buildstate, NULL); + + /* + * Perform the sort and build index pages. + */ + tuplesort_performsort(buildstate.sortstate); + + gist_indexsortbuild(&buildstate); + + tuplesort_end(buildstate.sortstate); + } + else + { + /* + * Initialize an empty index and insert all tuples, possibly using + * buffers on intermediate levels. + */ + Buffer buffer; + Page page; + + /* initialize the root page */ + buffer = gistNewBuffer(index); + Assert(BufferGetBlockNumber(buffer) == GIST_ROOT_BLKNO); + page = BufferGetPage(buffer); + + START_CRIT_SECTION(); + + GISTInitBuffer(buffer, F_LEAF); + + MarkBufferDirty(buffer); + PageSetLSN(page, GistBuildLSN); + + UnlockReleaseBuffer(buffer); + + END_CRIT_SECTION(); + + /* Scan the table, inserting all the tuples to the index. */ + reltuples = table_index_build_scan(heap, index, indexInfo, true, true, + gistBuildCallback, + (void *) &buildstate, NULL); + + /* + * If buffering was used, flush out all the tuples that are still in + * the buffers. + */ + if (buildstate.buildMode == GIST_BUFFERING_ACTIVE) + { + elog(DEBUG1, "all tuples processed, emptying buffers"); + gistEmptyAllBuffers(&buildstate); + gistFreeBuildBuffers(buildstate.gfbb); + } + + /* + * We didn't write WAL records as we built the index, so if + * WAL-logging is required, write all pages to the WAL now. + */ + if (RelationNeedsWAL(index)) + { + log_newpage_range(index, MAIN_FORKNUM, + 0, RelationGetNumberOfBlocks(index), + true); + } + } + + /* okay, all heap tuples are indexed */ + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(buildstate.giststate->tempCxt); + + freeGISTstate(buildstate.giststate); /* - * Create a temporary memory context that is reset once for each tuple - * processed. (Note: we don't bother to make this a child of the - * giststate's scanCxt, so we have to delete it separately at the end.) + * Return statistics */ - buildstate.giststate->tempCxt = createTempGistContext(); + result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult)); - /* initialize the root page */ - buffer = gistNewBuffer(index); - Assert(BufferGetBlockNumber(buffer) == GIST_ROOT_BLKNO); - page = BufferGetPage(buffer); + result->heap_tuples = reltuples; + result->index_tuples = (double) buildstate.indtuples; + + return result; +} + +/*------------------------------------------------------------------------- + * Routines for sorted build + *------------------------------------------------------------------------- + */ + +/* + * Per-tuple callback for table_index_build_scan. + */ +static void +gistSortedBuildCallback(Relation index, + ItemPointer tid, + Datum *values, + bool *isnull, + bool tupleIsAlive, + void *state) +{ + GISTBuildState *buildstate = (GISTBuildState *) state; + MemoryContext oldCtx; + Datum compressed_values[INDEX_MAX_KEYS]; - START_CRIT_SECTION(); + oldCtx = MemoryContextSwitchTo(buildstate->giststate->tempCxt); - GISTInitBuffer(buffer, F_LEAF); + /* Form an index tuple and point it at the heap tuple */ + gistCompressValues(buildstate->giststate, index, + values, isnull, + true, compressed_values); - MarkBufferDirty(buffer); - PageSetLSN(page, GistBuildLSN); + tuplesort_putindextuplevalues(buildstate->sortstate, + buildstate->indexrel, + tid, + compressed_values, isnull); - UnlockReleaseBuffer(buffer); + MemoryContextSwitchTo(oldCtx); + MemoryContextReset(buildstate->giststate->tempCxt); - END_CRIT_SECTION(); + /* Update tuple count. */ + buildstate->indtuples += 1; +} - /* build the index */ - buildstate.indtuples = 0; - buildstate.indtuplesSize = 0; +/* + * Build GiST index from bottom up from pre-sorted tuples. + */ +static void +gist_indexsortbuild(GISTBuildState *state) +{ + IndexTuple itup; + GistSortedBuildPageState *leafstate; + GistSortedBuildPageState *pagestate; + Page page; + + state->pages_allocated = 0; + state->pages_written = 0; + state->ready_num_pages = 0; /* - * Do the heap scan. + * Write an empty page as a placeholder for the root page. It will be + * replaced with the real root page at the end. */ - reltuples = table_index_build_scan(heap, index, indexInfo, true, true, - gistBuildCallback, - (void *) &buildstate, NULL); + page = palloc0(BLCKSZ); + RelationOpenSmgr(state->indexrel); + smgrextend(state->indexrel->rd_smgr, MAIN_FORKNUM, GIST_ROOT_BLKNO, + page, true); + state->pages_allocated++; + state->pages_written++; + + /* Allocate a temporary buffer for the first leaf page. */ + leafstate = palloc(sizeof(GistSortedBuildPageState)); + leafstate->page = page; + leafstate->parent = NULL; + gistinitpage(page, F_LEAF); /* - * If buffering was used, flush out all the tuples that are still in the - * buffers. + * Fill index pages with tuples in the sorted order. */ - if (buildstate.bufferingMode == GIST_BUFFERING_ACTIVE) + while ((itup = tuplesort_getindextuple(state->sortstate, true)) != NULL) { - elog(DEBUG1, "all tuples processed, emptying buffers"); - gistEmptyAllBuffers(&buildstate); - gistFreeBuildBuffers(buildstate.gfbb); + gist_indexsortbuild_pagestate_add(state, leafstate, itup); + MemoryContextReset(state->giststate->tempCxt); } - /* okay, all heap tuples are indexed */ - MemoryContextSwitchTo(oldcxt); - MemoryContextDelete(buildstate.giststate->tempCxt); + /* + * Write out the partially full non-root pages. + * + * Keep in mind that flush can build a new root. + */ + pagestate = leafstate; + while (pagestate->parent != NULL) + { + GistSortedBuildPageState *parent; - freeGISTstate(buildstate.giststate); + gist_indexsortbuild_pagestate_flush(state, pagestate); + parent = pagestate->parent; + pfree(pagestate->page); + pfree(pagestate); + pagestate = parent; + } + + gist_indexsortbuild_flush_ready_pages(state); + + /* Write out the root */ + RelationOpenSmgr(state->indexrel); + PageSetLSN(pagestate->page, GistBuildLSN); + PageSetChecksumInplace(pagestate->page, GIST_ROOT_BLKNO); + smgrwrite(state->indexrel->rd_smgr, MAIN_FORKNUM, GIST_ROOT_BLKNO, + pagestate->page, true); + if (RelationNeedsWAL(state->indexrel)) + log_newpage(&state->indexrel->rd_node, MAIN_FORKNUM, GIST_ROOT_BLKNO, + pagestate->page, true); + + pfree(pagestate->page); + pfree(pagestate); +} + +/* + * Add tuple to a page. If the pages is full, write it out and re-initialize + * a new page first. + */ +static void +gist_indexsortbuild_pagestate_add(GISTBuildState *state, + GistSortedBuildPageState *pagestate, + IndexTuple itup) +{ + Size sizeNeeded; + + /* Does the tuple fit? If not, flush */ + sizeNeeded = IndexTupleSize(itup) + sizeof(ItemIdData) + state->freespace; + if (PageGetFreeSpace(pagestate->page) < sizeNeeded) + gist_indexsortbuild_pagestate_flush(state, pagestate); + + gistfillbuffer(pagestate->page, &itup, 1, InvalidOffsetNumber); +} + +static void +gist_indexsortbuild_pagestate_flush(GISTBuildState *state, + GistSortedBuildPageState *pagestate) +{ + GistSortedBuildPageState *parent; + IndexTuple *itvec; + IndexTuple union_tuple; + int vect_len; + bool isleaf; + BlockNumber blkno; + MemoryContext oldCtx; + + /* check once per page */ + CHECK_FOR_INTERRUPTS(); + + if (state->ready_num_pages == XLR_MAX_BLOCK_ID) + gist_indexsortbuild_flush_ready_pages(state); + + /* + * The page is now complete. Assign a block number to it, and add it to + * the list of finished pages. (We don't write it out immediately, because + * we want to WAL-log the pages in batches.) + */ + blkno = state->pages_allocated++; + state->ready_blknos[state->ready_num_pages] = blkno; + state->ready_pages[state->ready_num_pages] = pagestate->page; + state->ready_num_pages++; + + isleaf = GistPageIsLeaf(pagestate->page); + + /* + * Form a downlink tuple to represent all the tuples on the page. + */ + oldCtx = MemoryContextSwitchTo(state->giststate->tempCxt); + itvec = gistextractpage(pagestate->page, &vect_len); + union_tuple = gistunion(state->indexrel, itvec, vect_len, + state->giststate); + ItemPointerSetBlockNumber(&(union_tuple->t_tid), blkno); + MemoryContextSwitchTo(oldCtx); /* - * We didn't write WAL records as we built the index, so if WAL-logging is - * required, write all pages to the WAL now. + * Insert the downlink to the parent page. If this was the root, create a + * new page as the parent, which becomes the new root. */ - if (RelationNeedsWAL(index)) + parent = pagestate->parent; + if (parent == NULL) { - log_newpage_range(index, MAIN_FORKNUM, - 0, RelationGetNumberOfBlocks(index), - true); + parent = palloc(sizeof(GistSortedBuildPageState)); + parent->page = (Page) palloc(BLCKSZ); + parent->parent = NULL; + gistinitpage(parent->page, 0); + + pagestate->parent = parent; } + gist_indexsortbuild_pagestate_add(state, parent, union_tuple); + + /* Re-initialize the page buffer for next page on this level. */ + pagestate->page = palloc(BLCKSZ); + gistinitpage(pagestate->page, isleaf ? F_LEAF : 0); /* - * Return statistics + * Set the right link to point to the previous page. This is just for + * debugging purposes: GiST only follows the right link if a page is split + * concurrently to a scan, and that cannot happen during index build. + * + * It's a bit counterintuitive that we set the right link on the new page + * to point to the previous page, and not the other way round. But GiST + * pages are not ordered like B-tree pages are, so as long as the + * right-links form a chain through all the pages in the same level, the + * order doesn't matter. */ - result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult)); + GistPageGetOpaque(pagestate->page)->rightlink = blkno; +} - result->heap_tuples = reltuples; - result->index_tuples = (double) buildstate.indtuples; +static void +gist_indexsortbuild_flush_ready_pages(GISTBuildState *state) +{ + if (state->ready_num_pages == 0) + return; - return result; + RelationOpenSmgr(state->indexrel); + + for (int i = 0; i < state->ready_num_pages; i++) + { + Page page = state->ready_pages[i]; + BlockNumber blkno = state->ready_blknos[i]; + + /* Currently, the blocks must be buffered in order. */ + if (blkno != state->pages_written) + elog(ERROR, "unexpected block number to flush GiST sorting build"); + + PageSetLSN(page, GistBuildLSN); + PageSetChecksumInplace(page, blkno); + smgrextend(state->indexrel->rd_smgr, MAIN_FORKNUM, blkno, page, true); + + state->pages_written++; + } + + if (RelationNeedsWAL(state->indexrel)) + log_newpages(&state->indexrel->rd_node, MAIN_FORKNUM, state->ready_num_pages, + state->ready_blknos, state->ready_pages, true); + + for (int i = 0; i < state->ready_num_pages; i++) + pfree(state->ready_pages[i]); + + state->ready_num_pages = 0; } + +/*------------------------------------------------------------------------- + * Routines for non-sorted build + *------------------------------------------------------------------------- + */ + /* * Attempt to switch to buffering mode. * @@ -375,7 +735,7 @@ gistInitBuffering(GISTBuildState *buildstate) if (levelStep <= 0) { elog(DEBUG1, "failed to switch to buffered GiST build"); - buildstate->bufferingMode = GIST_BUFFERING_DISABLED; + buildstate->buildMode = GIST_BUFFERING_DISABLED; return; } @@ -392,7 +752,7 @@ gistInitBuffering(GISTBuildState *buildstate) gistInitParentMap(buildstate); - buildstate->bufferingMode = GIST_BUFFERING_ACTIVE; + buildstate->buildMode = GIST_BUFFERING_ACTIVE; elog(DEBUG1, "switched to buffered GiST build; level step = %d, pagesPerBuffer = %d", levelStep, pagesPerBuffer); @@ -453,10 +813,12 @@ gistBuildCallback(Relation index, oldCtx = MemoryContextSwitchTo(buildstate->giststate->tempCxt); /* form an index tuple and point it at the heap tuple */ - itup = gistFormTuple(buildstate->giststate, index, values, isnull, true); + itup = gistFormTuple(buildstate->giststate, index, + values, isnull, + true); itup->t_tid = *tid; - if (buildstate->bufferingMode == GIST_BUFFERING_ACTIVE) + if (buildstate->buildMode == GIST_BUFFERING_ACTIVE) { /* We have buffers, so use them. */ gistBufferingBuildInsert(buildstate, itup); @@ -478,7 +840,7 @@ gistBuildCallback(Relation index, MemoryContextSwitchTo(oldCtx); MemoryContextReset(buildstate->giststate->tempCxt); - if (buildstate->bufferingMode == GIST_BUFFERING_ACTIVE && + if (buildstate->buildMode == GIST_BUFFERING_ACTIVE && buildstate->indtuples % BUFFERING_MODE_TUPLE_SIZE_STATS_TARGET == 0) { /* Adjust the target buffer size now */ @@ -491,12 +853,15 @@ gistBuildCallback(Relation index, * and switch to buffering mode if it has. * * To avoid excessive calls to smgrnblocks(), only check this every - * BUFFERING_MODE_SWITCH_CHECK_STEP index tuples + * BUFFERING_MODE_SWITCH_CHECK_STEP index tuples. + * + * In 'stats' state, switch as soon as we have seen enough tuples to have + * some idea of the average tuple size. */ - if ((buildstate->bufferingMode == GIST_BUFFERING_AUTO && + if ((buildstate->buildMode == GIST_BUFFERING_AUTO && buildstate->indtuples % BUFFERING_MODE_SWITCH_CHECK_STEP == 0 && effective_cache_size < smgrnblocks(index->rd_smgr, MAIN_FORKNUM)) || - (buildstate->bufferingMode == GIST_BUFFERING_STATS && + (buildstate->buildMode == GIST_BUFFERING_STATS && buildstate->indtuples >= BUFFERING_MODE_TUPLE_SIZE_STATS_TARGET)) { /* diff --git a/src/backend/access/gist/gistbuildbuffers.c b/src/backend/access/gist/gistbuildbuffers.c index fc84a12eb94f..a8738aa34887 100644 --- a/src/backend/access/gist/gistbuildbuffers.c +++ b/src/backend/access/gist/gistbuildbuffers.c @@ -666,7 +666,7 @@ gistRelocateBuildBuffersOnSplit(GISTBuildBuffers *gfbb, GISTSTATE *giststate, zero_penalty = true; /* Loop over index attributes. */ - for (j = 0; j < r->rd_att->natts; j++) + for (j = 0; j < IndexRelationGetNumberOfKeyAttributes(r); j++) { float usize; @@ -692,7 +692,7 @@ gistRelocateBuildBuffersOnSplit(GISTBuildBuffers *gfbb, GISTSTATE *giststate, which = i; best_penalty[j] = usize; - if (j < r->rd_att->natts - 1) + if (j < IndexRelationGetNumberOfKeyAttributes(r) - 1) best_penalty[j + 1] = -1; } else if (best_penalty[j] == usize) diff --git a/src/backend/access/gist/gistproc.c b/src/backend/access/gist/gistproc.c index 9ace64c3c4a9..27d9c0f77c30 100644 --- a/src/backend/access/gist/gistproc.c +++ b/src/backend/access/gist/gistproc.c @@ -24,6 +24,7 @@ #include "utils/builtins.h" #include "utils/float.h" #include "utils/geo_decls.h" +#include "utils/sortsupport.h" static bool gist_box_leaf_consistent(BOX *key, BOX *query, @@ -31,6 +32,15 @@ static bool gist_box_leaf_consistent(BOX *key, BOX *query, static bool rtree_internal_consistent(BOX *key, BOX *query, StrategyNumber strategy); +static uint64 point_zorder_internal(float4 x, float4 y); +static uint64 part_bits32_by2(uint32 x); +static uint32 ieee_float32_to_uint32(float f); +static int gist_bbox_zorder_cmp(Datum a, Datum b, SortSupport ssup); +static Datum gist_bbox_zorder_abbrev_convert(Datum original, SortSupport ssup); +static int gist_bbox_zorder_cmp_abbrev(Datum z1, Datum z2, SortSupport ssup); +static bool gist_bbox_zorder_abbrev_abort(int memtupcount, SortSupport ssup); + + /* Minimum accepted ratio of split */ #define LIMIT_RATIO 0.3 @@ -1540,3 +1550,222 @@ gist_poly_distance(PG_FUNCTION_ARGS) PG_RETURN_FLOAT8(distance); } + +/* + * Z-order routines for fast index build + */ + +/* + * Compute Z-value of a point + * + * Z-order (also known as Morton Code) maps a two-dimensional point to a + * single integer, in a way that preserves locality. Points that are close in + * the two-dimensional space are mapped to integer that are not far from each + * other. We do that by interleaving the bits in the X and Y components. + * + * Morton Code is normally defined only for integers, but the X and Y values + * of a point are floating point. We expect floats to be in IEEE format. + */ +static uint64 +point_zorder_internal(float4 x, float4 y) +{ + uint32 ix = ieee_float32_to_uint32(x); + uint32 iy = ieee_float32_to_uint32(y); + + /* Interleave the bits */ + return part_bits32_by2(ix) | (part_bits32_by2(iy) << 1); +} + +/* Interleave 32 bits with zeroes */ +static uint64 +part_bits32_by2(uint32 x) +{ + uint64 n = x; + + n = (n | (n << 16)) & UINT64CONST(0x0000FFFF0000FFFF); + n = (n | (n << 8)) & UINT64CONST(0x00FF00FF00FF00FF); + n = (n | (n << 4)) & UINT64CONST(0x0F0F0F0F0F0F0F0F); + n = (n | (n << 2)) & UINT64CONST(0x3333333333333333); + n = (n | (n << 1)) & UINT64CONST(0x5555555555555555); + + return n; +} + +/* + * Convert a 32-bit IEEE float to uint32 in a way that preserves the ordering + */ +static uint32 +ieee_float32_to_uint32(float f) +{ + /*---- + * + * IEEE 754 floating point format + * ------------------------------ + * + * IEEE 754 floating point numbers have this format: + * + * exponent (8 bits) + * | + * s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm + * | | + * sign mantissa (23 bits) + * + * Infinity has all bits in the exponent set and the mantissa is all + * zeros. Negative infinity is the same but with the sign bit set. + * + * NaNs are represented with all bits in the exponent set, and the least + * significant bit in the mantissa also set. The rest of the mantissa bits + * can be used to distinguish different kinds of NaNs. + * + * The IEEE format has the nice property that when you take the bit + * representation and interpret it as an integer, the order is preserved, + * except for the sign. That holds for the +-Infinity values too. + * + * Mapping to uint32 + * ----------------- + * + * In order to have a smooth transition from negative to positive numbers, + * we map floats to unsigned integers like this: + * + * x < 0 to range 0-7FFFFFFF + * x = 0 to value 8000000 (both positive and negative zero) + * x > 0 to range 8000001-FFFFFFFF + * + * We don't care to distinguish different kind of NaNs, so they are all + * mapped to the same arbitrary value, FFFFFFFF. Because of the IEEE bit + * representation of NaNs, there aren't any non-NaN values that would be + * mapped to FFFFFFFF. In fact, there is a range of unused values on both + * ends of the uint32 space. + */ + if (isnan(f)) + return 0xFFFFFFFF; + else + { + union + { + float f; + uint32 i; + } u; + + u.f = f; + + /* Check the sign bit */ + if ((u.i & 0x80000000) != 0) + { + /* + * Map the negative value to range 0-7FFFFFFF. This flips the sign + * bit to 0 in the same instruction. + */ + Assert(f <= 0); /* can be -0 */ + u.i ^= 0xFFFFFFFF; + } + else + { + /* Map the positive value (or 0) to range 80000000-FFFFFFFF */ + u.i |= 0x80000000; + } + + return u.i; + } +} + +/* + * Compare the Z-order of points + */ +static int +gist_bbox_zorder_cmp(Datum a, Datum b, SortSupport ssup) +{ + Point *p1 = &(DatumGetBoxP(a)->low); + Point *p2 = &(DatumGetBoxP(b)->low); + uint64 z1; + uint64 z2; + + /* + * Do a quick check for equality first. It's not clear if this is worth it + * in general, but certainly is when used as tie-breaker with abbreviated + * keys, + */ + if (p1->x == p2->x && p1->y == p2->y) + return 0; + + z1 = point_zorder_internal(p1->x, p1->y); + z2 = point_zorder_internal(p2->x, p2->y); + if (z1 > z2) + return 1; + else if (z1 < z2) + return -1; + else + return 0; +} + +/* + * Abbreviated version of Z-order comparison + * + * The abbreviated format is a Z-order value computed from the two 32-bit + * floats. If SIZEOF_DATUM == 8, the 64-bit Z-order value fits fully in the + * abbreviated Datum, otherwise use its most significant bits. + */ +static Datum +gist_bbox_zorder_abbrev_convert(Datum original, SortSupport ssup) +{ + Point *p = &(DatumGetBoxP(original)->low); + uint64 z; + + z = point_zorder_internal(p->x, p->y); + +#if SIZEOF_DATUM == 8 + return (Datum) z; +#else + return (Datum) (z >> 32); +#endif +} + +static int +gist_bbox_zorder_cmp_abbrev(Datum z1, Datum z2, SortSupport ssup) +{ + /* + * Compare the pre-computed Z-orders as unsigned integers. Datum is a + * typedef for 'uintptr_t', so no casting is required. + */ + if (z1 > z2) + return 1; + else if (z1 < z2) + return -1; + else + return 0; +} + +/* + * We never consider aborting the abbreviation. + * + * On 64-bit systems, the abbreviation is not lossy so it is always + * worthwhile. (Perhaps it's not on 32-bit systems, but we don't bother + * with logic to decide.) + */ +static bool +gist_bbox_zorder_abbrev_abort(int memtupcount, SortSupport ssup) +{ + return false; +} + +/* + * Sort support routine for fast GiST index build by sorting. + */ +Datum +gist_point_sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + + if (ssup->abbreviate) + { + ssup->comparator = gist_bbox_zorder_cmp_abbrev; + ssup->abbrev_converter = gist_bbox_zorder_abbrev_convert; + ssup->abbrev_abort = gist_bbox_zorder_abbrev_abort; + ssup->abbrev_full_comparator = gist_bbox_zorder_cmp; + } + else + { + ssup->comparator = gist_bbox_zorder_cmp; + } + PG_RETURN_VOID(); +} diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index bfda7fbe3d58..615b5ade2331 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -32,7 +32,6 @@ void gistfillbuffer(Page page, IndexTuple *itup, int len, OffsetNumber off) { - OffsetNumber l = InvalidOffsetNumber; int i; if (off == InvalidOffsetNumber) @@ -42,6 +41,7 @@ gistfillbuffer(Page page, IndexTuple *itup, int len, OffsetNumber off) for (i = 0; i < len; i++) { Size sz = IndexTupleSize(itup[i]); + OffsetNumber l; l = PageAddItem(page, (Item) itup[i], sz, off, false, false); if (l == InvalidOffsetNumber) @@ -572,12 +572,31 @@ gistdentryinit(GISTSTATE *giststate, int nkey, GISTENTRY *e, IndexTuple gistFormTuple(GISTSTATE *giststate, Relation r, - Datum attdata[], bool isnull[], bool isleaf) + Datum *attdata, bool *isnull, bool isleaf) { Datum compatt[INDEX_MAX_KEYS]; - int i; IndexTuple res; + gistCompressValues(giststate, r, attdata, isnull, isleaf, compatt); + + res = index_form_tuple(isleaf ? giststate->leafTupdesc : + giststate->nonLeafTupdesc, + compatt, isnull); + + /* + * The offset number on tuples on internal pages is unused. For historical + * reasons, it is set to 0xffff. + */ + ItemPointerSetOffsetNumber(&(res->t_tid), 0xffff); + return res; +} + +void +gistCompressValues(GISTSTATE *giststate, Relation r, + Datum *attdata, bool *isnull, bool isleaf, Datum *compatt) +{ + int i; + /* * Call the compress method on each attribute. */ @@ -617,17 +636,6 @@ gistFormTuple(GISTSTATE *giststate, Relation r, compatt[i] = attdata[i]; } } - - res = index_form_tuple(isleaf ? giststate->leafTupdesc : - giststate->nonLeafTupdesc, - compatt, isnull); - - /* - * The offset number on tuples on internal pages is unused. For historical - * reasons, it is set to 0xffff. - */ - ItemPointerSetOffsetNumber(&(res->t_tid), 0xffff); - return res; } /* @@ -745,14 +753,11 @@ gistpenalty(GISTSTATE *giststate, int attno, * Initialize a new index page */ void -GISTInitBuffer(Buffer b, uint32 f) +gistinitpage(Page page, uint32 f) { GISTPageOpaque opaque; - Page page; - Size pageSize; + Size pageSize = BLCKSZ; - pageSize = BufferGetPageSize(b); - page = BufferGetPage(b); PageInit(page, pageSize, sizeof(GISTPageOpaqueData)); opaque = GistPageGetOpaque(page); @@ -763,6 +768,18 @@ GISTInitBuffer(Buffer b, uint32 f) opaque->gist_page_id = GIST_PAGE_ID; } +/* + * Initialize a new index buffer + */ +void +GISTInitBuffer(Buffer b, uint32 f) +{ + Page page; + + page = BufferGetPage(b); + gistinitpage(page, f); +} + /* * Verify that a freshly-read page looks sane. */ diff --git a/src/backend/access/gist/gistvalidate.c b/src/backend/access/gist/gistvalidate.c index 2b9ab693be18..e600015b12d6 100644 --- a/src/backend/access/gist/gistvalidate.c +++ b/src/backend/access/gist/gistvalidate.c @@ -143,6 +143,10 @@ gistvalidate(Oid opclassoid) case GIST_OPTIONS_PROC: ok = check_amoptsproc_signature(procform->amproc); break; + case GIST_SORTSUPPORT_PROC: + ok = check_amproc_signature(procform->amproc, VOIDOID, true, + 1, 1, INTERNALOID); + break; default: ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), @@ -263,7 +267,7 @@ gistvalidate(Oid opclassoid) continue; /* got it */ if (i == GIST_DISTANCE_PROC || i == GIST_FETCH_PROC || i == GIST_COMPRESS_PROC || i == GIST_DECOMPRESS_PROC || - i == GIST_OPTIONS_PROC) + i == GIST_OPTIONS_PROC || i == GIST_SORTSUPPORT_PROC) continue; /* optional methods */ ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), @@ -334,6 +338,7 @@ gistadjustmembers(Oid opfamilyoid, case GIST_DISTANCE_PROC: case GIST_FETCH_PROC: case GIST_OPTIONS_PROC: + case GIST_SORTSUPPORT_PROC: /* Optional, so force it to be a soft family dependency */ op->ref_is_hard = false; op->ref_is_family = true; diff --git a/src/backend/access/gist/gistxlog.c b/src/backend/access/gist/gistxlog.c index dcd28f678b3d..91b3e111820d 100644 --- a/src/backend/access/gist/gistxlog.c +++ b/src/backend/access/gist/gistxlog.c @@ -405,7 +405,6 @@ gistRedoPageReuse(XLogReaderState *record) * logged value is very old, so that XID wrap-around already happened * on it, there can't be any snapshots that still see it. */ - nextXid = ReadNextFullTransactionId(); diff = U64FromFullTransactionId(nextXid) - U64FromFullTransactionId(latestRemovedFullXid); if (diff < MaxTransactionId / 2) diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock index d03ddf6cdcc8..6441e8baf0e4 100644 --- a/src/backend/access/heap/README.tuplock +++ b/src/backend/access/heap/README.tuplock @@ -146,9 +146,10 @@ The following infomask bits are applicable: FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit. - HEAP_KEYS_UPDATED - This bit lives in t_infomask2. If set, indicates that the XMAX updated - this tuple and changed the key values, or it deleted the tuple. - It's set regardless of whether the XMAX is a TransactionId or a MultiXactId. + This bit lives in t_infomask2. If set, indicates that the operation(s) done + by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE + that modifies the columns of the key, or a DELETE. It's set regardless of + whether the XMAX is a TransactionId or a MultiXactId. We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit is set. diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 144d5797f6fd..f7acaf11cfbe 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -2116,12 +2116,10 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid, CommandId cid, int options, bool isFrozen) { /* - * Parallel operations are required to be strictly read-only in a parallel - * worker. Parallel inserts are not safe even in the leader in the - * general case, because group locking means that heavyweight locks for - * relation extension or GIN page locks will not conflict between members - * of a lock group, but we don't prohibit that case here because there are - * useful special cases that we can safely allow, such as CREATE TABLE AS. + * To allow parallel inserts, we need to ensure that they are safe to be + * performed in workers. We have the infrastructure to allow parallel + * inserts in general except for the cases where inserts generate a new + * CommandId (eg. inserts into a table having a foreign key column). */ if (IsParallelWorker()) ereport(ERROR, @@ -5841,10 +5839,10 @@ heap_inplace_update(Relation relation, HeapTuple tuple) uint32 newlen; /* - * For now, parallel operations are required to be strictly read-only. - * Unlike a regular update, this should never create a combo CID, so it - * might be possible to relax this restriction, but not without more - * thought and testing. It's not clear that it would be useful, anyway. + * For now, we don't allow parallel updates. Unlike a regular update, + * this should never create a combo CID, so it might be possible to relax + * this restriction, but not without more thought and testing. It's not + * clear that it would be useful, anyway. */ if (IsInParallelMode()) ereport(ERROR, diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c index 6e8bc1e9dc3d..ddf1c5777425 100644 --- a/src/backend/access/heap/hio.c +++ b/src/backend/access/heap/hio.c @@ -47,6 +47,15 @@ RelationPutHeapTuple(Relation relation pg_attribute_unused(), */ Assert(!token || HeapTupleHeaderIsSpeculative(tuple->t_data)); + /* + * Do not allow tuples with invalid combinations of hint bits to be placed + * on a page. This combination is detected as corruption by the + * contrib/amcheck logic, so if you disable this assertion, make + * corresponding changes there. + */ + Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) && + (tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI))); + /* Add the tuple to the page */ pageHeader = BufferGetPage(buffer); diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index 5b446218cfeb..238cdd23e6d4 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -188,7 +188,7 @@ heap_page_prune_opt(Relation relation, Buffer buffer) /* OK to prune */ (void) heap_page_prune(relation, buffer, vistest, limited_xmin, limited_ts, - true, &ignore); + true, &ignore, NULL); } /* And release buffer lock */ @@ -213,6 +213,9 @@ heap_page_prune_opt(Relation relation, Buffer buffer) * send its own new total to pgstats, and we don't want this delta applied * on top of that.) * + * off_loc is the offset location required by the caller to use in error + * callback. + * * Returns the number of tuples deleted from the page and sets * latestRemovedXid. */ @@ -221,7 +224,8 @@ heap_page_prune(Relation relation, Buffer buffer, GlobalVisState *vistest, TransactionId old_snap_xmin, TimestampTz old_snap_ts, - bool report_stats, TransactionId *latestRemovedXid) + bool report_stats, TransactionId *latestRemovedXid, + OffsetNumber *off_loc) { int ndeleted = 0; Page page = BufferGetPage(buffer); @@ -262,6 +266,13 @@ heap_page_prune(Relation relation, Buffer buffer, if (prstate.marked[offnum]) continue; + /* + * Set the offset number so that we can display it along with any + * error that occurred while processing this tuple. + */ + if (off_loc) + *off_loc = offnum; + /* Nothing to do if slot is empty or already dead */ itemid = PageGetItemId(page, offnum); if (!ItemIdIsUsed(itemid) || ItemIdIsDead(itemid)) @@ -271,6 +282,10 @@ heap_page_prune(Relation relation, Buffer buffer, ndeleted += heap_prune_chain(buffer, offnum, &prstate); } + /* Clear the offset information once we have processed the given page. */ + if (off_loc) + *off_loc = InvalidOffsetNumber; + /* Any error while applying the changes is critical */ START_CRIT_SECTION(); @@ -370,7 +385,7 @@ heap_page_prune(Relation relation, Buffer buffer, /* - * Perform visiblity checks for heap pruning. + * Perform visibility checks for heap pruning. * * This is more complicated than just using GlobalVisTestIsRemovableXid() * because of old_snapshot_threshold. We only want to increase the threshold diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 8391274302d7..0071aa91eb84 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -222,7 +222,8 @@ typedef struct LVShared * live tuples in the index vacuum case or the new live tuples in the * index cleanup case. * - * estimated_count is true if reltuples is an estimated value. + * estimated_count is true if reltuples is an estimated value. (Note that + * reltuples could be -1 in this case, indicating we have no idea.) */ double reltuples; bool estimated_count; @@ -330,6 +331,7 @@ typedef struct LVRelStats /* Used for error callback */ char *indname; BlockNumber blkno; /* used only for heap operations */ + OffsetNumber offnum; /* used only for heap operations */ VacErrPhase phase; } LVRelStats; @@ -337,6 +339,7 @@ typedef struct LVRelStats typedef struct LVSavedErrInfo { BlockNumber blkno; + OffsetNumber offnum; VacErrPhase phase; } LVSavedErrInfo; @@ -355,7 +358,8 @@ static void lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, Relation *Irel, int nindexes, bool aggressive); static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats); -static bool lazy_check_needs_freeze(Buffer buf, bool *hastup); +static bool lazy_check_needs_freeze(Buffer buf, bool *hastup, + LVRelStats *vacrelstats); static void lazy_vacuum_all_indexes(Relation onerel, Relation *Irel, IndexBulkDeleteResult **stats, LVRelStats *vacrelstats, LVParallelState *lps, @@ -378,6 +382,7 @@ static void lazy_record_dead_tuple(LVDeadTuples *dead_tuples, static bool lazy_tid_reaped(ItemPointer itemptr, void *state); static int vac_cmp_itemptr(const void *left, const void *right); static bool heap_page_is_all_visible(Relation rel, Buffer buf, + LVRelStats *vacrelstats, TransactionId *visibility_cutoff_xid, bool *all_frozen); static void lazy_parallel_vacuum_indexes(Relation *Irel, IndexBulkDeleteResult **stats, LVRelStats *vacrelstats, LVParallelState *lps, @@ -410,7 +415,8 @@ static LVSharedIndStats *get_indstats(LVShared *lvshared, int n); static bool skip_parallel_vacuum_index(Relation indrel, LVShared *lvshared); static void vacuum_error_callback(void *arg); static void update_vacuum_error_info(LVRelStats *errinfo, LVSavedErrInfo *saved_err_info, - int phase, BlockNumber blkno); + int phase, BlockNumber blkno, + OffsetNumber offnum); static void restore_vacuum_error_info(LVRelStats *errinfo, const LVSavedErrInfo *saved_err_info); /* @@ -569,7 +575,8 @@ lazy_vacuum_rel_heap(Relation onerel, VacuumParams *params, * revert to the previous phase. */ update_vacuum_error_info(vacrelstats, NULL, VACUUM_ERRCB_PHASE_TRUNCATE, - vacrelstats->nonempty_pages); + vacrelstats->nonempty_pages, + InvalidOffsetNumber); lazy_truncate_heap(onerel, vacrelstats); } @@ -583,31 +590,19 @@ lazy_vacuum_rel_heap(Relation onerel, VacuumParams *params, /* * Update statistics in pg_class. * - * A corner case here is that if we scanned no pages at all because every - * page is all-visible, we should not update relpages/reltuples, because - * we have no new information to contribute. In particular this keeps us - * from replacing relpages=reltuples=0 (which means "unknown tuple - * density") with nonzero relpages and reltuples=0 (which means "zero - * tuple density") unless there's some actual evidence for the latter. + * In principle new_live_tuples could be -1 indicating that we (still) + * don't know the tuple count. In practice that probably can't happen, + * since we'd surely have scanned some pages if the table is new and + * nonempty. * - * It's important that we use tupcount_pages and not scanned_pages for the - * check described above; scanned_pages counts pages where we could not - * get cleanup lock, and which were processed only for frozenxid purposes. - * - * We do update relallvisible even in the corner case, since if the table - * is all-visible we'd definitely like to know that. But clamp the value - * to be not more than what we're setting relpages to. + * For safety, clamp relallvisible to be not more than what we're setting + * relpages to. * * Also, don't change relfrozenxid/relminmxid if we skipped any pages, * since then we don't know for certain that all tuples have a newer xmin. */ new_rel_pages = vacrelstats->rel_pages; new_live_tuples = vacrelstats->new_live_tuples; - if (vacrelstats->tupcount_pages == 0 && new_rel_pages > 0) - { - new_rel_pages = vacrelstats->old_rel_pages; - new_live_tuples = vacrelstats->old_live_tuples; - } visibilitymap_count(onerel, &new_rel_allvisible, NULL); if (new_rel_allvisible > new_rel_pages) @@ -629,7 +624,7 @@ lazy_vacuum_rel_heap(Relation onerel, VacuumParams *params, /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, - new_live_tuples, + Max(new_live_tuples, 0), vacrelstats->new_dead_tuples); pgstat_progress_end_command(); @@ -704,11 +699,10 @@ lazy_vacuum_rel_heap(Relation onerel, VacuumParams *params, read_rate, write_rate); appendStringInfo(&buf, _("system usage: %s\n"), pg_rusage_show(&ru0)); appendStringInfo(&buf, - _("WAL usage: %ld records, %ld full page images, " - UINT64_FORMAT " bytes"), + _("WAL usage: %ld records, %ld full page images, %llu bytes"), walusage.wal_records, walusage.wal_fpi, - walusage.wal_bytes); + (unsigned long long) walusage.wal_bytes); ereport(LOG, (errmsg_internal("%s", buf.data))); @@ -983,7 +977,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno); update_vacuum_error_info(vacrelstats, NULL, VACUUM_ERRCB_PHASE_SCAN_HEAP, - blkno); + blkno, InvalidOffsetNumber); if (blkno == next_unskippable_block) { @@ -1152,7 +1146,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, * to use lazy_check_needs_freeze() for both situations, though. */ LockBuffer(buf, BUFFER_LOCK_SHARE); - if (!lazy_check_needs_freeze(buf, &hastup)) + if (!lazy_check_needs_freeze(buf, &hastup, vacrelstats)) { UnlockReleaseBuffer(buf); vacrelstats->scanned_pages++; @@ -1267,7 +1261,8 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, */ tups_vacuumed += heap_page_prune(onerel, buf, vistest, false, InvalidTransactionId, 0, - &vacrelstats->latestRemovedXid); + &vacrelstats->latestRemovedXid, + &vacrelstats->offnum); /* * Now scan the page to collect vacuumable items and check for tuples @@ -1290,6 +1285,11 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, { ItemId itemid; + /* + * Set the offset number so that we can display it along with any + * error that occurred while processing this tuple. + */ + vacrelstats->offnum = offnum; itemid = PageGetItemId(page, offnum); /* Unused items require no processing, but we count 'em */ @@ -1491,6 +1491,12 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, } } /* scan along page */ + /* + * Clear the offset information once we have processed all the tuples + * on the page. + */ + vacrelstats->offnum = InvalidOffsetNumber; + /* * If we froze any tuples, mark the buffer dirty, and write a WAL * record recording the changes. We must log the changes to be @@ -1688,6 +1694,9 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, /* report that everything is scanned and vacuumed */ pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno); + /* Clear the block number information */ + vacrelstats->blkno = InvalidBlockNumber; + pfree(frozen); /* save stats for use later */ @@ -1700,9 +1709,12 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, vacrelstats->tupcount_pages, live_tuples); - /* also compute total number of surviving heap entries */ + /* + * Also compute the total number of surviving heap entries. In the + * (unlikely) scenario that new_live_tuples is -1, take it as zero. + */ vacrelstats->new_rel_tuples = - vacrelstats->new_live_tuples + vacrelstats->new_dead_tuples; + Max(vacrelstats->new_live_tuples, 0) + vacrelstats->new_dead_tuples; /* * Release any remaining pin on visibility map page. @@ -1868,7 +1880,7 @@ lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats) /* Update error traceback information */ update_vacuum_error_info(vacrelstats, &saved_err_info, VACUUM_ERRCB_PHASE_VACUUM_HEAP, - InvalidBlockNumber); + InvalidBlockNumber, InvalidOffsetNumber); pg_rusage_init(&ru0); npages = 0; @@ -1905,6 +1917,9 @@ lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats) npages++; } + /* Clear the block number information */ + vacrelstats->blkno = InvalidBlockNumber; + if (BufferIsValid(vmbuffer)) { ReleaseBuffer(vmbuffer); @@ -1947,7 +1962,7 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, /* Update error traceback information */ update_vacuum_error_info(vacrelstats, &saved_err_info, VACUUM_ERRCB_PHASE_VACUUM_HEAP, - blkno); + blkno, InvalidOffsetNumber); START_CRIT_SECTION(); @@ -1999,7 +2014,8 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, * dirty, exclusively locked, and, if needed, a full page image has been * emitted in the log_heap_clean() above. */ - if (heap_page_is_all_visible(onerel, buffer, &visibility_cutoff_xid, + if (heap_page_is_all_visible(onerel, buffer, vacrelstats, + &visibility_cutoff_xid, &all_frozen)) PageSetAllVisible(page); @@ -2038,7 +2054,7 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, * Also returns a flag indicating whether page contains any tuples at all. */ static bool -lazy_check_needs_freeze(Buffer buf, bool *hastup) +lazy_check_needs_freeze(Buffer buf, bool *hastup, LVRelStats *vacrelstats) { Page page = BufferGetPage(buf); OffsetNumber offnum, @@ -2063,6 +2079,11 @@ lazy_check_needs_freeze(Buffer buf, bool *hastup) { ItemId itemid; + /* + * Set the offset number so that we can display it along with any + * error that occurred while processing this tuple. + */ + vacrelstats->offnum = offnum; itemid = PageGetItemId(page, offnum); /* this should match hastup test in count_nondeletable_pages() */ @@ -2077,10 +2098,13 @@ lazy_check_needs_freeze(Buffer buf, bool *hastup) if (heap_tuple_needs_freeze(tupleheader, FreezeLimit, MultiXactCutoff, buf)) - return true; + break; } /* scan along page */ - return false; + /* Clear the offset information once we have processed the given page. */ + vacrelstats->offnum = InvalidOffsetNumber; + + return (offnum <= maxoff); } /* @@ -2427,7 +2451,7 @@ lazy_cleanup_all_indexes(Relation *Irel, IndexBulkDeleteResult **stats, * dead_tuples, and update running statistics. * * reltuples is the number of heap tuples to be passed to the - * bulkdelete callback. + * bulkdelete callback. It's always assumed to be estimated. */ static void lazy_vacuum_index(Relation indrel, IndexBulkDeleteResult **stats, @@ -2458,7 +2482,7 @@ lazy_vacuum_index(Relation indrel, IndexBulkDeleteResult **stats, vacrelstats->indname = pstrdup(RelationGetRelationName(indrel)); update_vacuum_error_info(vacrelstats, &saved_err_info, VACUUM_ERRCB_PHASE_VACUUM_INDEX, - InvalidBlockNumber); + InvalidBlockNumber, InvalidOffsetNumber); /* Do bulk deletion */ *stats = index_bulk_delete(&ivinfo, *stats, @@ -2518,34 +2542,34 @@ lazy_cleanup_index(Relation indrel, vacrelstats->indname = pstrdup(RelationGetRelationName(indrel)); update_vacuum_error_info(vacrelstats, &saved_err_info, VACUUM_ERRCB_PHASE_INDEX_CLEANUP, - InvalidBlockNumber); + InvalidBlockNumber, InvalidOffsetNumber); *stats = index_vacuum_cleanup(&ivinfo, *stats); - /* Revert back to the old phase information for error traceback */ + if (*stats) + { + if (IsParallelWorker()) + msg = gettext_noop("index \"%s\" now contains %.0f row versions in %u pages as reported by parallel vacuum worker"); + else + msg = gettext_noop("index \"%s\" now contains %.0f row versions in %u pages"); + + ereport(elevel, + (errmsg(msg, + RelationGetRelationName(indrel), + (*stats)->num_index_tuples, + (*stats)->num_pages), + errdetail("%.0f index row versions were removed.\n" + "%u index pages have been deleted, %u are currently reusable.\n" + "%s.", + (*stats)->tuples_removed, + (*stats)->pages_deleted, (*stats)->pages_free, + pg_rusage_show(&ru0)))); + } + + /* Revert to the previous phase information for error traceback */ restore_vacuum_error_info(vacrelstats, &saved_err_info); pfree(vacrelstats->indname); vacrelstats->indname = NULL; - - if (!(*stats)) - return; - - if (IsParallelWorker()) - msg = gettext_noop("index \"%s\" now contains %.0f row versions in %u pages as reported by parallel vacuum worker"); - else - msg = gettext_noop("index \"%s\" now contains %.0f row versions in %u pages"); - - ereport(elevel, - (errmsg(msg, - RelationGetRelationName(indrel), - (*stats)->num_index_tuples, - (*stats)->num_pages), - errdetail("%.0f index row versions were removed.\n" - "%u index pages have been deleted, %u are currently reusable.\n" - "%s.", - (*stats)->tuples_removed, - (*stats)->pages_deleted, (*stats)->pages_free, - pg_rusage_show(&ru0)))); } /* @@ -2985,6 +3009,7 @@ vac_cmp_itemptr(const void *left, const void *right) */ static bool heap_page_is_all_visible(Relation rel, Buffer buf, + LVRelStats *vacrelstats, TransactionId *visibility_cutoff_xid, bool *all_frozen) { @@ -3009,6 +3034,11 @@ heap_page_is_all_visible(Relation rel, Buffer buf, ItemId itemid; HeapTupleData tuple; + /* + * Set the offset number so that we can display it along with any + * error that occurred while processing this tuple. + */ + vacrelstats->offnum = offnum; itemid = PageGetItemId(page, offnum); /* Unused or redirect line pointers are of no interest */ @@ -3086,6 +3116,9 @@ heap_page_is_all_visible(Relation rel, Buffer buf, } } /* scan along page */ + /* Clear the offset information once we have processed the given page. */ + vacrelstats->offnum = InvalidOffsetNumber; + return all_visible; } @@ -3232,7 +3265,6 @@ begin_parallel_vacuum(Oid relid, Relation *Irel, LVRelStats *vacrelstats, WalUsage *wal_usage; bool *can_parallel_vacuum; long maxtuples; - char *sharedquery; Size est_shared; Size est_deadtuples; int nindexes_mwm = 0; @@ -3329,9 +3361,14 @@ begin_parallel_vacuum(Oid relid, Relation *Irel, LVRelStats *vacrelstats, shm_toc_estimate_keys(&pcxt->estimator, 1); /* Finally, estimate PARALLEL_VACUUM_KEY_QUERY_TEXT space */ - querylen = strlen(debug_query_string); - shm_toc_estimate_chunk(&pcxt->estimator, querylen + 1); - shm_toc_estimate_keys(&pcxt->estimator, 1); + if (debug_query_string) + { + querylen = strlen(debug_query_string); + shm_toc_estimate_chunk(&pcxt->estimator, querylen + 1); + shm_toc_estimate_keys(&pcxt->estimator, 1); + } + else + querylen = 0; /* keep compiler quiet */ InitializeParallelDSM(pcxt); @@ -3376,10 +3413,16 @@ begin_parallel_vacuum(Oid relid, Relation *Irel, LVRelStats *vacrelstats, lps->wal_usage = wal_usage; /* Store query string for workers */ - sharedquery = (char *) shm_toc_allocate(pcxt->toc, querylen + 1); - memcpy(sharedquery, debug_query_string, querylen + 1); - sharedquery[querylen] = '\0'; - shm_toc_insert(pcxt->toc, PARALLEL_VACUUM_KEY_QUERY_TEXT, sharedquery); + if (debug_query_string) + { + char *sharedquery; + + sharedquery = (char *) shm_toc_allocate(pcxt->toc, querylen + 1); + memcpy(sharedquery, debug_query_string, querylen + 1); + sharedquery[querylen] = '\0'; + shm_toc_insert(pcxt->toc, + PARALLEL_VACUUM_KEY_QUERY_TEXT, sharedquery); + } pfree(can_parallel_vacuum); return lps; @@ -3516,12 +3559,13 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc) false); elevel = lvshared->elevel; - ereport(DEBUG1, - (errmsg("starting parallel vacuum worker for %s", - lvshared->for_cleanup ? "cleanup" : "bulk delete"))); + if (lvshared->for_cleanup) + elog(DEBUG1, "starting parallel vacuum worker for cleanup"); + else + elog(DEBUG1, "starting parallel vacuum worker for bulk delete"); /* Set debug_query_string for individual workers */ - sharedquery = shm_toc_lookup(toc, PARALLEL_VACUUM_KEY_QUERY_TEXT, false); + sharedquery = shm_toc_lookup(toc, PARALLEL_VACUUM_KEY_QUERY_TEXT, true); debug_query_string = sharedquery; pgstat_report_activity(STATE_RUNNING, debug_query_string); @@ -3608,14 +3652,32 @@ vacuum_error_callback(void *arg) { case VACUUM_ERRCB_PHASE_SCAN_HEAP: if (BlockNumberIsValid(errinfo->blkno)) - errcontext("while scanning block %u of relation \"%s.%s\"", - errinfo->blkno, errinfo->relnamespace, errinfo->relname); + { + if (OffsetNumberIsValid(errinfo->offnum)) + errcontext("while scanning block %u and offset %u of relation \"%s.%s\"", + errinfo->blkno, errinfo->offnum, errinfo->relnamespace, errinfo->relname); + else + errcontext("while scanning block %u of relation \"%s.%s\"", + errinfo->blkno, errinfo->relnamespace, errinfo->relname); + } + else + errcontext("while scanning relation \"%s.%s\"", + errinfo->relnamespace, errinfo->relname); break; case VACUUM_ERRCB_PHASE_VACUUM_HEAP: if (BlockNumberIsValid(errinfo->blkno)) - errcontext("while vacuuming block %u of relation \"%s.%s\"", - errinfo->blkno, errinfo->relnamespace, errinfo->relname); + { + if (OffsetNumberIsValid(errinfo->offnum)) + errcontext("while vacuuming block %u and offset %u of relation \"%s.%s\"", + errinfo->blkno, errinfo->offnum, errinfo->relnamespace, errinfo->relname); + else + errcontext("while vacuuming block %u of relation \"%s.%s\"", + errinfo->blkno, errinfo->relnamespace, errinfo->relname); + } + else + errcontext("while vacuuming relation \"%s.%s\"", + errinfo->relnamespace, errinfo->relname); break; case VACUUM_ERRCB_PHASE_VACUUM_INDEX: @@ -3647,15 +3709,17 @@ vacuum_error_callback(void *arg) */ static void update_vacuum_error_info(LVRelStats *errinfo, LVSavedErrInfo *saved_err_info, int phase, - BlockNumber blkno) + BlockNumber blkno, OffsetNumber offnum) { if (saved_err_info) { + saved_err_info->offnum = errinfo->offnum; saved_err_info->blkno = errinfo->blkno; saved_err_info->phase = errinfo->phase; } errinfo->blkno = blkno; + errinfo->offnum = offnum; errinfo->phase = phase; } @@ -3666,5 +3730,6 @@ static void restore_vacuum_error_info(LVRelStats *errinfo, const LVSavedErrInfo *saved_err_info) { errinfo->blkno = saved_err_info->blkno; + errinfo->offnum = saved_err_info->offnum; errinfo->phase = saved_err_info->phase; } diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 1d6cd53f0d53..a9d2e032ca49 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -955,6 +955,7 @@ _bt_vacuum_needs_cleanup(IndexVacuumInfo *info) prev_num_heap_tuples = metad->btm_last_cleanup_num_heap_tuples; if (cleanup_scale_factor <= 0 || + info->num_heap_tuples < 0 || prev_num_heap_tuples <= 0 || (info->num_heap_tuples - prev_num_heap_tuples) / prev_num_heap_tuples >= cleanup_scale_factor) diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c index 28dc196b55e3..8f6575fdf15c 100644 --- a/src/backend/access/nbtree/nbtsearch.c +++ b/src/backend/access/nbtree/nbtsearch.c @@ -860,7 +860,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) ScanKeyData notnullkeys[INDEX_MAX_KEYS]; int keysCount = 0; int i; - bool status = true; + bool status; StrategyNumber strat_total; BTScanPosItem *currItem; BlockNumber blkno; @@ -880,7 +880,11 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) * never be satisfied (eg, x == 1 AND x > 2). */ if (!so->qual_ok) + { + /* Notify any other workers that we're done with this scan key. */ + _bt_parallel_done(scan); return false; + } /* * For parallel scans, get the starting page from shared state. If the @@ -1858,7 +1862,7 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir) { BTScanOpaque so = (BTScanOpaque) scan->opaque; BlockNumber blkno = InvalidBlockNumber; - bool status = true; + bool status; Assert(BTScanPosIsValid(so->currPos)); @@ -1967,7 +1971,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) Relation rel; Page page; BTPageOpaque opaque; - bool status = true; + bool status; rel = scan->indexRelation; diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c index ec856c45b543..dd30a64375e2 100644 --- a/src/backend/access/nbtree/nbtsort.c +++ b/src/backend/access/nbtree/nbtsort.c @@ -1466,7 +1466,6 @@ _bt_begin_parallel(BTBuildState *buildstate, bool isconcurrent, int request) WalUsage *walusage; BufferUsage *bufferusage; bool leaderparticipates = true; - char *sharedquery; int querylen; #ifdef DISABLE_LEADER_PARTICIPATION @@ -1533,9 +1532,14 @@ _bt_begin_parallel(BTBuildState *buildstate, bool isconcurrent, int request) shm_toc_estimate_keys(&pcxt->estimator, 1); /* Finally, estimate PARALLEL_KEY_QUERY_TEXT space */ - querylen = strlen(debug_query_string); - shm_toc_estimate_chunk(&pcxt->estimator, querylen + 1); - shm_toc_estimate_keys(&pcxt->estimator, 1); + if (debug_query_string) + { + querylen = strlen(debug_query_string); + shm_toc_estimate_chunk(&pcxt->estimator, querylen + 1); + shm_toc_estimate_keys(&pcxt->estimator, 1); + } + else + querylen = 0; /* keep compiler quiet */ /* Everyone's had a chance to ask for space, so now create the DSM */ InitializeParallelDSM(pcxt); @@ -1599,9 +1603,14 @@ _bt_begin_parallel(BTBuildState *buildstate, bool isconcurrent, int request) } /* Store query string for workers */ - sharedquery = (char *) shm_toc_allocate(pcxt->toc, querylen + 1); - memcpy(sharedquery, debug_query_string, querylen + 1); - shm_toc_insert(pcxt->toc, PARALLEL_KEY_QUERY_TEXT, sharedquery); + if (debug_query_string) + { + char *sharedquery; + + sharedquery = (char *) shm_toc_allocate(pcxt->toc, querylen + 1); + memcpy(sharedquery, debug_query_string, querylen + 1); + shm_toc_insert(pcxt->toc, PARALLEL_KEY_QUERY_TEXT, sharedquery); + } /* * Allocate space for each worker's WalUsage and BufferUsage; no need to @@ -1806,7 +1815,7 @@ _bt_parallel_build_main(dsm_segment *seg, shm_toc *toc) #endif /* BTREE_BUILD_STATS */ /* Set debug_query_string for individual workers first */ - sharedquery = shm_toc_lookup(toc, PARALLEL_KEY_QUERY_TEXT, false); + sharedquery = shm_toc_lookup(toc, PARALLEL_KEY_QUERY_TEXT, true); debug_query_string = sharedquery; /* Report the query string from leader */ diff --git a/src/backend/access/rmgrdesc/dbasedesc.c b/src/backend/access/rmgrdesc/dbasedesc.c index d82484b9db40..47580feaeae4 100644 --- a/src/backend/access/rmgrdesc/dbasedesc.c +++ b/src/backend/access/rmgrdesc/dbasedesc.c @@ -37,7 +37,7 @@ dbase_desc(StringInfo buf, XLogReaderState *record) xl_dbase_drop_rec *xlrec = (xl_dbase_drop_rec *) rec; int i; - appendStringInfo(buf, "dir"); + appendStringInfoString(buf, "dir"); for (i = 0; i < xlrec->ntablespaces; i++) appendStringInfo(buf, " %u/%u", xlrec->tablespace_ids[i], xlrec->db_id); diff --git a/src/backend/access/rmgrdesc/logicalmsgdesc.c b/src/backend/access/rmgrdesc/logicalmsgdesc.c index bff298c9287f..83ab93a24be9 100644 --- a/src/backend/access/rmgrdesc/logicalmsgdesc.c +++ b/src/backend/access/rmgrdesc/logicalmsgdesc.c @@ -24,10 +24,21 @@ logicalmsg_desc(StringInfo buf, XLogReaderState *record) if (info == XLOG_LOGICAL_MESSAGE) { xl_logical_message *xlrec = (xl_logical_message *) rec; + char *prefix = xlrec->message; + char *message = xlrec->message + xlrec->prefix_size; + char *sep = ""; - appendStringInfo(buf, "%s message size %zu bytes", - xlrec->transactional ? "transactional" : "nontransactional", - xlrec->message_size); + Assert(prefix[xlrec->prefix_size] != '\0'); + + appendStringInfo(buf, "%s, prefix \"%s\"; payload (%zu bytes): ", + xlrec->transactional ? "transactional" : "non-transactional", + prefix, xlrec->message_size); + /* Write message payload as a series of hex bytes */ + for (int cnt = 0; cnt < xlrec->message_size; cnt++) + { + appendStringInfo(buf, "%s%02X", sep, (unsigned char) message[cnt]); + sep = " "; + } } } diff --git a/src/backend/access/table/table.c b/src/backend/access/table/table.c index 19d0c2c76b7f..4cb640d1f880 100644 --- a/src/backend/access/table/table.c +++ b/src/backend/access/table/table.c @@ -62,6 +62,40 @@ table_open(Oid relationId, LOCKMODE lockmode) return r; } + +/* ---------------- + * try_table_open - open a table relation by relation OID + * + * Same as table_open, except return NULL instead of failing + * if the relation does not exist. + * ---------------- + */ +Relation +try_table_open(Oid relationId, LOCKMODE lockmode, bool noWait) +{ + Relation r; + + r = try_relation_open(relationId, lockmode, noWait); + + /* leave if table does not exist */ + if (!r) + return NULL; + + if (r->rd_rel->relkind == RELKIND_INDEX || + r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is an index", + RelationGetRelationName(r)))); + else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is a composite type", + RelationGetRelationName(r)))); + + return r; +} + /* ---------------- * table_openrv - open a table relation specified * by a RangeVar node @@ -125,36 +159,6 @@ table_openrv_extended(const RangeVar *relation, LOCKMODE lockmode, return r; } -/* ---------------- - * try_table_open - open a heap relation by relation OID - * - * As above, but relation return NULL for relation-not-found - * ---------------- - */ -Relation -try_table_open(Oid relationId, LOCKMODE lockmode, bool noWait) -{ - Relation r; - - r = try_relation_open(relationId, lockmode, noWait); - - if (!RelationIsValid(r)) - return NULL; - - if (r->rd_rel->relkind == RELKIND_INDEX) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is an index", - RelationGetRelationName(r)))); - else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is a composite type", - RelationGetRelationName(r)))); - - return r; -} - /* ---------------- * table_close - close a table * diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c index f10f8941e3f2..bd2305887614 100644 --- a/src/backend/access/table/tableam.c +++ b/src/backend/access/table/tableam.c @@ -709,18 +709,14 @@ table_block_relation_estimate_size(Relation rel, int32 *attr_widths, * doesn't happen instantaneously, and it won't happen at all for cases * such as temporary tables.) * - * We approximate "never vacuumed" by "has relpages = 0", which means this - * will also fire on genuinely empty relations. Not great, but - * fortunately that's a seldom-seen case in the real world, and it - * shouldn't degrade the quality of the plan too much anyway to err in - * this direction. + * We test "never vacuumed" by seeing whether reltuples < 0. * * If the table has inheritance children, we don't apply this heuristic. * Totally empty parent tables are quite common, so we should be willing * to believe that they are empty. */ if (curpages < 10 && - relpages == 0 && + reltuples < 0 && !rel->rd_rel->relhassubclass) curpages = 10; @@ -735,17 +731,17 @@ table_block_relation_estimate_size(Relation rel, int32 *attr_widths, } /* estimate number of tuples from previous tuple density */ - if (relpages > 0) + if (reltuples >= 0 && relpages > 0) density = reltuples / (double) relpages; else { /* - * When we have no data because the relation was truncated, estimate - * tuple width from attribute datatypes. We assume here that the - * pages are completely full, which is OK for tables (since they've - * presumably not been VACUUMed yet) but is probably an overestimate - * for indexes. Fortunately get_relation_info() can clamp the - * overestimate to the parent table's size. + * When we have no data because the relation was never yet vacuumed, + * estimate tuple width from attribute datatypes. We assume here that + * the pages are completely full, which is OK for tables but is + * probably an overestimate for indexes. Fortunately + * get_relation_info() can clamp the overestimate to the parent + * table's size. * * Note: this code intentionally disregards alignment considerations, * because (a) that would be gilding the lily considering how crude diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c index 8f41e4f34e71..576a4168c8b6 100644 --- a/src/backend/access/transam/clog.c +++ b/src/backend/access/transam/clog.c @@ -42,6 +42,7 @@ #include "pg_trace.h" #include "pgstat.h" #include "storage/proc.h" +#include "storage/sync.h" /* * Defines for CLOG page sizes. A page is the same BLCKSZ as is used @@ -801,7 +802,8 @@ CLOGShmemInit(void) { XactCtl->PagePrecedes = CLOGPagePrecedes; SimpleLruInit(XactCtl, "Xact", CLOGShmemBuffers(), CLOG_LSNS_PER_PAGE, - XactSLRULock, "pg_xact", LWTRANCHE_XACT_BUFFER); + XactSLRULock, "pg_xact", LWTRANCHE_XACT_BUFFER, + SYNC_HANDLER_CLOG); } /* @@ -918,41 +920,19 @@ TrimCLOG(void) LWLockRelease(XactSLRULock); } -/* - * This must be called ONCE during postmaster or standalone-backend shutdown - */ -void -ShutdownCLOG(void) -{ - /* Flush dirty CLOG pages to disk */ - TRACE_POSTGRESQL_CLOG_CHECKPOINT_START(false); - SimpleLruFlush(XactCtl, false); - - /* - * fsync pg_xact to ensure that any files flushed previously are durably - * on disk. - */ - fsync_fname("pg_xact", true); - - TRACE_POSTGRESQL_CLOG_CHECKPOINT_DONE(false); -} - /* * Perform a checkpoint --- either during shutdown, or on-the-fly */ void CheckPointCLOG(void) { - /* Flush dirty CLOG pages to disk */ - TRACE_POSTGRESQL_CLOG_CHECKPOINT_START(true); - SimpleLruFlush(XactCtl, true); - /* - * fsync pg_xact to ensure that any files flushed previously are durably - * on disk. + * Write dirty CLOG pages to disk. This may result in sync requests + * queued for later handling by ProcessSyncRequests(), as part of the + * checkpoint. */ - fsync_fname("pg_xact", true); - + TRACE_POSTGRESQL_CLOG_CHECKPOINT_START(true); + SimpleLruWriteAll(XactCtl, true); TRACE_POSTGRESQL_CLOG_CHECKPOINT_DONE(true); } @@ -1143,3 +1123,12 @@ clog_redo(XLogReaderState *record) else elog(PANIC, "clog_redo: unknown op code %u", info); } + +/* + * Entrypoint for sync.c to sync clog files. + */ +int +clogsyncfiletag(const FileTag *ftag, char *path) +{ + return SlruSyncFileTag(XactCtl, ftag, path); +} diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c index 5244b06a2b65..2fe551f17e77 100644 --- a/src/backend/access/transam/commit_ts.c +++ b/src/backend/access/transam/commit_ts.c @@ -404,7 +404,7 @@ error_commit_ts_disabled(void) Datum pg_xact_commit_timestamp(PG_FUNCTION_ARGS) { - TransactionId xid = PG_GETARG_UINT32(0); + TransactionId xid = PG_GETARG_TRANSACTIONID(0); TimestampTz ts; bool found; @@ -481,7 +481,7 @@ pg_last_committed_xact(PG_FUNCTION_ARGS) Datum pg_xact_commit_timestamp_origin(PG_FUNCTION_ARGS) { - TransactionId xid = PG_GETARG_UINT32(0); + TransactionId xid = PG_GETARG_TRANSACTIONID(0); RepOriginId nodeid; TimestampTz ts; Datum values[2]; @@ -555,7 +555,8 @@ CommitTsShmemInit(void) CommitTsCtl->PagePrecedes = CommitTsPagePrecedes; SimpleLruInit(CommitTsCtl, "CommitTs", CommitTsShmemBuffers(), 0, CommitTsSLRULock, "pg_commit_ts", - LWTRANCHE_COMMITTS_BUFFER); + LWTRANCHE_COMMITTS_BUFFER, + SYNC_HANDLER_COMMIT_TS); commitTsShared = ShmemInitStruct("CommitTs shared", sizeof(CommitTimestampShared), @@ -798,36 +799,18 @@ DeactivateCommitTs(void) LWLockRelease(CommitTsSLRULock); } -/* - * This must be called ONCE during postmaster or standalone-backend shutdown - */ -void -ShutdownCommitTs(void) -{ - /* Flush dirty CommitTs pages to disk */ - SimpleLruFlush(CommitTsCtl, false); - - /* - * fsync pg_commit_ts to ensure that any files flushed previously are - * durably on disk. - */ - fsync_fname("pg_commit_ts", true); -} - /* * Perform a checkpoint --- either during shutdown, or on-the-fly */ void CheckPointCommitTs(void) { - /* Flush dirty CommitTs pages to disk */ - SimpleLruFlush(CommitTsCtl, true); - /* - * fsync pg_commit_ts to ensure that any files flushed previously are - * durably on disk. + * Write dirty CommitTs pages to disk. This may result in sync requests + * queued for later handling by ProcessSyncRequests(), as part of the + * checkpoint. */ - fsync_fname("pg_commit_ts", true); + SimpleLruWriteAll(CommitTsCtl, true); } /* @@ -1083,3 +1066,12 @@ commit_ts_redo(XLogReaderState *record) else elog(PANIC, "commit_ts_redo: unknown op code %u", info); } + +/* + * Entrypoint for sync.c to sync commit_ts files. + */ +int +committssyncfiletag(const FileTag *ftag, char *path) +{ + return SlruSyncFileTag(CommitTsCtl, ftag, path); +} diff --git a/src/backend/access/transam/distributedlog.c b/src/backend/access/transam/distributedlog.c index 6d083c30a75b..5ab6ebd83441 100644 --- a/src/backend/access/transam/distributedlog.c +++ b/src/backend/access/transam/distributedlog.c @@ -661,7 +661,7 @@ DistributedLog_ShmemInit(void) DistributedLogCtl->PagePrecedes = DistributedLog_PagePrecedes; SimpleLruInit(DistributedLogCtl, "DistributedLogCtl", DistributedLog_ShmemBuffers(), 0, DistributedLogControlLock, "pg_distributedlog", - LWTRANCHE_DISTRIBUTEDLOG_BUFFERS); + LWTRANCHE_DISTRIBUTEDLOG_BUFFERS, SYNC_HANDLER_DISTRIBUTED_CLOG); /* Create or attach to the shared structure */ DistributedLogShared = @@ -836,22 +836,6 @@ DistributedLog_Startup(TransactionId oldestActiveXid, LWLockRelease(DistributedLogControlLock); } -/* - * This must be called ONCE during postmaster or standalone-backend shutdown - */ -void -DistributedLog_Shutdown(void) -{ - if (IS_QUERY_DISPATCHER()) - return; - - elog((Debug_print_full_dtm ? LOG : DEBUG5), - "DistributedLog_Shutdown"); - - /* Flush dirty DistributedLog pages to disk */ - SimpleLruFlush(DistributedLogCtl, false); -} - /* * Perform a checkpoint --- either during shutdown, or on-the-fly */ @@ -864,8 +848,8 @@ DistributedLog_CheckPoint(void) elog((Debug_print_full_dtm ? LOG : DEBUG5), "DistributedLog_CheckPoint"); - /* Flush dirty DistributedLog pages to disk */ - SimpleLruFlush(DistributedLogCtl, true); + /* Write dirty DistributedLog pages to disk */ + SimpleLruWriteAll(DistributedLogCtl, true); } @@ -1091,3 +1075,12 @@ DistributedLog_redo(XLogReaderState *record) else elog(PANIC, "DistributedLog_redo: unknown op code %u", info); } + +/* + * Entrypoint for sync.c to sync distributed clog files. + */ +int +DistributedLog_syncfiletag(const FileTag *ftag, char *path) +{ + return SlruSyncFileTag(DistributedLogCtl, ftag, path); +} diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index 125af0163d37..d8f7c7db2fdd 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -734,6 +734,25 @@ ReadNextMultiXactId(void) return mxid; } +/* + * ReadMultiXactIdRange + * Get the range of IDs that may still be referenced by a relation. + */ +void +ReadMultiXactIdRange(MultiXactId *oldest, MultiXactId *next) +{ + LWLockAcquire(MultiXactGenLock, LW_SHARED); + *oldest = MultiXactState->oldestMultiXactId; + *next = MultiXactState->nextMXact; + LWLockRelease(MultiXactGenLock); + + if (*oldest < FirstMultiXactId) + *oldest = FirstMultiXactId; + if (*next < FirstMultiXactId) + *next = FirstMultiXactId; +} + + /* * MultiXactIdCreateFromMembers * Make a new MultiXactId from the specified set of members @@ -1741,7 +1760,7 @@ PostPrepare_MultiXact(TransactionId xid) OldestVisibleMXactId[MyBackendId] = InvalidMultiXactId; /* - * Discard the local MultiXactId cache like in AtEOX_MultiXact + * Discard the local MultiXactId cache like in AtEOXact_MultiXact. */ MXactContext = NULL; dlist_init(&MXactCache); @@ -1771,7 +1790,7 @@ multixact_twophase_recover(TransactionId xid, uint16 info, /* * multixact_twophase_postcommit - * Similar to AtEOX_MultiXact but for COMMIT PREPARED + * Similar to AtEOXact_MultiXact but for COMMIT PREPARED */ void multixact_twophase_postcommit(TransactionId xid, uint16 info, @@ -1830,11 +1849,13 @@ MultiXactShmemInit(void) SimpleLruInit(MultiXactOffsetCtl, "MultiXactOffset", NUM_MULTIXACTOFFSET_BUFFERS, 0, MultiXactOffsetSLRULock, "pg_multixact/offsets", - LWTRANCHE_MULTIXACTOFFSET_BUFFER); + LWTRANCHE_MULTIXACTOFFSET_BUFFER, + SYNC_HANDLER_MULTIXACT_OFFSET); SimpleLruInit(MultiXactMemberCtl, "MultiXactMember", NUM_MULTIXACTMEMBER_BUFFERS, 0, MultiXactMemberSLRULock, "pg_multixact/members", - LWTRANCHE_MULTIXACTMEMBER_BUFFER); + LWTRANCHE_MULTIXACTMEMBER_BUFFER, + SYNC_HANDLER_MULTIXACT_MEMBER); /* Initialize our shared state struct */ MultiXactState = ShmemInitStruct("Shared MultiXact State", @@ -2099,20 +2120,6 @@ TrimMultiXact(void) SetMultiXactIdLimit(oldestMXact, oldestMXactDB, true); } -/* - * This must be called ONCE during postmaster or standalone-backend shutdown - */ -void -ShutdownMultiXact(void) -{ - /* Flush dirty MultiXact pages to disk */ - TRACE_POSTGRESQL_MULTIXACT_CHECKPOINT_START(false); - SimpleLruFlush(MultiXactOffsetCtl, false); - SimpleLruFlush(MultiXactMemberCtl, false); - - TRACE_POSTGRESQL_MULTIXACT_CHECKPOINT_DONE(false); -} - /* * Get the MultiXact data to save in a checkpoint record */ @@ -2143,9 +2150,13 @@ CheckPointMultiXact(void) { TRACE_POSTGRESQL_MULTIXACT_CHECKPOINT_START(true); - /* Flush dirty MultiXact pages to disk */ - SimpleLruFlush(MultiXactOffsetCtl, true); - SimpleLruFlush(MultiXactMemberCtl, true); + /* + * Write dirty MultiXact pages to disk. This may result in sync requests + * queued for later handling by ProcessSyncRequests(), as part of the + * checkpoint. + */ + SimpleLruWriteAll(MultiXactOffsetCtl, true); + SimpleLruWriteAll(MultiXactMemberCtl, true); TRACE_POSTGRESQL_MULTIXACT_CHECKPOINT_DONE(true); } @@ -2728,14 +2739,10 @@ find_multixact_start(MultiXactId multi, MultiXactOffset *result) entryno = MultiXactIdToOffsetEntry(multi); /* - * Flush out dirty data, so PhysicalPageExists can work correctly. - * SimpleLruFlush() is a pretty big hammer for that. Alternatively we - * could add an in-memory version of page exists, but find_multixact_start - * is called infrequently, and it doesn't seem bad to flush buffers to - * disk before truncation. + * Write out dirty data, so PhysicalPageExists can work correctly. */ - SimpleLruFlush(MultiXactOffsetCtl, true); - SimpleLruFlush(MultiXactMemberCtl, true); + SimpleLruWriteAll(MultiXactOffsetCtl, true); + SimpleLruWriteAll(MultiXactMemberCtl, true); if (!SimpleLruDoesPhysicalPageExist(MultiXactOffsetCtl, pageno)) return false; @@ -3331,7 +3338,7 @@ pg_get_multixact_members(PG_FUNCTION_ARGS) int nmembers; int iter; } mxact; - MultiXactId mxid = PG_GETARG_UINT32(0); + MultiXactId mxid = PG_GETARG_TRANSACTIONID(0); mxact *multi; FuncCallContext *funccxt; @@ -3386,3 +3393,21 @@ pg_get_multixact_members(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funccxt); } + +/* + * Entrypoint for sync.c to sync offsets files. + */ +int +multixactoffsetssyncfiletag(const FileTag *ftag, char *path) +{ + return SlruSyncFileTag(MultiXactOffsetCtl, ftag, path); +} + +/* + * Entrypoint for sync.c to sync members files. + */ +int +multixactmemberssyncfiletag(const FileTag *ftag, char *path) +{ + return SlruSyncFileTag(MultiXactMemberCtl, ftag, path); +} diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c index c96ce6245aab..d00860e97ca5 100644 --- a/src/backend/access/transam/slru.c +++ b/src/backend/access/transam/slru.c @@ -63,22 +63,33 @@ snprintf(path, MAXPGPATH, "%s/%04X", (ctl)->Dir, seg) /* - * During SimpleLruFlush(), we will usually not need to write/fsync more - * than one or two physical files, but we may need to write several pages - * per file. We can consolidate the I/O requests by leaving files open - * until control returns to SimpleLruFlush(). This data structure remembers - * which files are open. + * During SimpleLruWriteAll(), we will usually not need to write more than one + * or two physical files, but we may need to write several pages per file. We + * can consolidate the I/O requests by leaving files open until control returns + * to SimpleLruWriteAll(). This data structure remembers which files are open. */ -#define MAX_FLUSH_BUFFERS 16 +#define MAX_WRITEALL_BUFFERS 16 -typedef struct SlruFlushData +typedef struct SlruWriteAllData { int num_files; /* # files actually open */ - int fd[MAX_FLUSH_BUFFERS]; /* their FD's */ - int segno[MAX_FLUSH_BUFFERS]; /* their log seg#s */ -} SlruFlushData; + int fd[MAX_WRITEALL_BUFFERS]; /* their FD's */ + int segno[MAX_WRITEALL_BUFFERS]; /* their log seg#s */ +} SlruWriteAllData; -typedef struct SlruFlushData *SlruFlush; +typedef struct SlruWriteAllData *SlruWriteAll; + +/* + * Populate a file tag describing a segment file. We only use the segment + * number, since we can derive everything else we need by having separate + * sync handler functions for clog, multixact etc. + */ +#define INIT_SLRUFILETAG(a,xx_handler,xx_segno) \ +( \ + memset(&(a), 0, sizeof(FileTag)), \ + (a).handler = (xx_handler), \ + (a).segno = (xx_segno) \ +) /* * Macro to mark a buffer slot "most recently used". Note multiple evaluation @@ -125,10 +136,10 @@ static int slru_errno; static void SimpleLruZeroLSNs(SlruCtl ctl, int slotno); static void SimpleLruWaitIO(SlruCtl ctl, int slotno); -static void SlruInternalWritePage(SlruCtl ctl, int slotno, SlruFlush fdata); +static void SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata); static bool SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno); static bool SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, - SlruFlush fdata); + SlruWriteAll fdata); static void SlruReportIOError(SlruCtl ctl, int pageno, TransactionId xid); static int SlruSelectLRUPage(SlruCtl ctl, int pageno); @@ -173,7 +184,8 @@ SimpleLruShmemSize(int nslots, int nlsns) */ void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns, - LWLock *ctllock, const char *subdir, int tranche_id) + LWLock *ctllock, const char *subdir, int tranche_id, + SyncRequestHandler sync_handler) { SlruShared shared; bool found; @@ -251,7 +263,7 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns, * assume caller set PagePrecedes. */ ctl->shared = shared; - ctl->do_fsync = true; /* default behavior */ + ctl->sync_handler = sync_handler; strlcpy(ctl->Dir, subdir, sizeof(ctl->Dir)); } @@ -523,7 +535,7 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno, TransactionId xid) * Control lock must be held at entry, and will be held at exit. */ static void -SlruInternalWritePage(SlruCtl ctl, int slotno, SlruFlush fdata) +SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata) { SlruShared shared = ctl->shared; int pageno = shared->page_number[slotno]; @@ -587,6 +599,10 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruFlush fdata) /* Now it's okay to ereport if we failed */ if (!ok) SlruReportIOError(ctl, pageno, InvalidTransactionId); + + /* If part of a checkpoint, count this as a buffer written. */ + if (fdata) + CheckpointStats.ckpt_bufs_written++; } /* @@ -730,13 +746,13 @@ SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno) * * For now, assume it's not worth keeping a file pointer open across * independent read/write operations. We do batch operations during - * SimpleLruFlush, though. + * SimpleLruWriteAll, though. * * fdata is NULL for a standalone write, pointer to open-file info during - * SimpleLruFlush. + * SimpleLruWriteAll. */ static bool -SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruFlush fdata) +SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruWriteAll fdata) { SlruShared shared = ctl->shared; int segno = pageno / SLRU_PAGES_PER_SEGMENT; @@ -791,7 +807,7 @@ SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruFlush fdata) } /* - * During a Flush, we may already have the desired file open. + * During a WriteAll, we may already have the desired file open. */ if (fdata) { @@ -837,7 +853,7 @@ SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruFlush fdata) if (fdata) { - if (fdata->num_files < MAX_FLUSH_BUFFERS) + if (fdata->num_files < MAX_WRITEALL_BUFFERS) { fdata->fd[fdata->num_files] = fd; fdata->segno[fdata->num_files] = segno; @@ -870,23 +886,31 @@ SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruFlush fdata) } pgstat_report_wait_end(); - /* - * If not part of Flush, need to fsync now. We assume this happens - * infrequently enough that it's not a performance issue. - */ - if (!fdata) + /* Queue up a sync request for the checkpointer. */ + if (ctl->sync_handler != SYNC_HANDLER_NONE) { - pgstat_report_wait_start(WAIT_EVENT_SLRU_SYNC); - if (ctl->do_fsync && pg_fsync(fd) != 0) + FileTag tag; + + INIT_SLRUFILETAG(tag, ctl->sync_handler, segno); + if (!RegisterSyncRequest(&tag, SYNC_REQUEST, false)) { + /* No space to enqueue sync request. Do it synchronously. */ + pgstat_report_wait_start(WAIT_EVENT_SLRU_SYNC); + if (pg_fsync(fd) != 0) + { + pgstat_report_wait_end(); + slru_errcause = SLRU_FSYNC_FAILED; + slru_errno = errno; + CloseTransientFile(fd); + return false; + } pgstat_report_wait_end(); - slru_errcause = SLRU_FSYNC_FAILED; - slru_errno = errno; - CloseTransientFile(fd); - return false; } - pgstat_report_wait_end(); + } + /* Close file, unless part of flush request. */ + if (!fdata) + { if (CloseTransientFile(fd) != 0) { slru_errcause = SLRU_CLOSE_FAILED; @@ -1122,13 +1146,16 @@ SlruSelectLRUPage(SlruCtl ctl, int pageno) } /* - * Flush dirty pages to disk during checkpoint or database shutdown + * Write dirty pages to disk during checkpoint or database shutdown. Flushing + * is deferred until the next call to ProcessSyncRequests(), though we do fsync + * the containing directory here to make sure that newly created directory + * entries are on disk. */ void -SimpleLruFlush(SlruCtl ctl, bool allow_redirtied) +SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied) { SlruShared shared = ctl->shared; - SlruFlushData fdata; + SlruWriteAllData fdata; int slotno; int pageno = 0; int i; @@ -1162,21 +1189,11 @@ SimpleLruFlush(SlruCtl ctl, bool allow_redirtied) LWLockRelease(shared->ControlLock); /* - * Now fsync and close any files that were open + * Now close any files that were open */ ok = true; for (i = 0; i < fdata.num_files; i++) { - pgstat_report_wait_start(WAIT_EVENT_SLRU_FLUSH_SYNC); - if (ctl->do_fsync && pg_fsync(fdata.fd[i]) != 0) - { - slru_errcause = SLRU_FSYNC_FAILED; - slru_errno = errno; - pageno = fdata.segno[i] * SLRU_PAGES_PER_SEGMENT; - ok = false; - } - pgstat_report_wait_end(); - if (CloseTransientFile(fdata.fd[i]) != 0) { slru_errcause = SLRU_CLOSE_FAILED; @@ -1187,6 +1204,10 @@ SimpleLruFlush(SlruCtl ctl, bool allow_redirtied) } if (!ok) SlruReportIOError(ctl, pageno, InvalidTransactionId); + + /* Ensure that directory entries for new files are on disk. */ + if (ctl->sync_handler != SYNC_HANDLER_NONE) + fsync_fname(ctl->Dir, true); } /* @@ -1361,6 +1382,19 @@ SlruDeleteSegment(SlruCtl ctl, int segno) snprintf(path, MAXPGPATH, "%s/%04X", ctl->Dir, segno); ereport(DEBUG2, (errmsg("removing file \"%s\"", path))); + + /* + * Tell the checkpointer to forget any sync requests, before we unlink the + * file. + */ + if (ctl->sync_handler != SYNC_HANDLER_NONE) + { + FileTag tag; + + INIT_SLRUFILETAG(tag, ctl->sync_handler, segno); + RegisterSyncRequest(&tag, SYNC_FORGET_REQUEST, true); + } + unlink(path); LWLockRelease(shared->ControlLock); @@ -1459,3 +1493,31 @@ SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data) return retval; } + +/* + * Individual SLRUs (clog, ...) have to provide a sync.c handler function so + * that they can provide the correct "SlruCtl" (otherwise we don't know how to + * build the path), but they just forward to this common implementation that + * performs the fsync. + */ +int +SlruSyncFileTag(SlruCtl ctl, const FileTag *ftag, char *path) +{ + int fd; + int save_errno; + int result; + + SlruFileName(ctl, path, ftag->segno); + + fd = OpenTransientFile(path, O_RDWR | PG_BINARY); + if (fd < 0) + return -1; + + result = pg_fsync(fd); + save_errno = errno; + + CloseTransientFile(fd); + + errno = save_errno; + return result; +} diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c index 6e7f6f2c9aeb..4ed6941160f5 100644 --- a/src/backend/access/transam/subtrans.c +++ b/src/backend/access/transam/subtrans.c @@ -197,9 +197,7 @@ SUBTRANSShmemInit(void) SubTransCtl->PagePrecedes = SubTransPagePrecedes; SimpleLruInit(SubTransCtl, "Subtrans", NUM_SUBTRANS_BUFFERS, 0, SubtransSLRULock, "pg_subtrans", - LWTRANCHE_SUBTRANS_BUFFER); - /* Override default assumption that writes should be fsync'd */ - SubTransCtl->do_fsync = false; + LWTRANCHE_SUBTRANS_BUFFER, SYNC_HANDLER_NONE); } /* @@ -286,23 +284,6 @@ StartupSUBTRANS(TransactionId oldestActiveXID) LWLockRelease(SubtransSLRULock); } -/* - * This must be called ONCE during postmaster or standalone-backend shutdown - */ -void -ShutdownSUBTRANS(void) -{ - /* - * Flush dirty SUBTRANS pages to disk - * - * This is not actually necessary from a correctness point of view. We do - * it merely as a debugging aid. - */ - TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_START(false); - SimpleLruFlush(SubTransCtl, false); - TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(false); -} - /* * Perform a checkpoint --- either during shutdown, or on-the-fly */ @@ -310,14 +291,14 @@ void CheckPointSUBTRANS(void) { /* - * Flush dirty SUBTRANS pages to disk + * Write dirty SUBTRANS pages to disk * * This is not actually necessary from a correctness point of view. We do * it merely to improve the odds that writing of dirty pages is done by * the checkpoint process and not by backends. */ TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_START(true); - SimpleLruFlush(SubTransCtl, true); + SimpleLruWriteAll(SubTransCtl, true); TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(true); } diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index c9411a4277c7..6001d54698aa 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -1300,10 +1300,10 @@ ReadTwoPhaseFile(TransactionId xid, bool missing_ok) stat.st_size > MaxAllocSize) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), - errmsg_plural("incorrect size of file \"%s\": %zu byte", - "incorrect size of file \"%s\": %zu bytes", - (Size) stat.st_size, path, - (Size) stat.st_size))); + errmsg_plural("incorrect size of file \"%s\": %lld byte", + "incorrect size of file \"%s\": %lld bytes", + (long long int) stat.st_size, path, + (long long int) stat.st_size))); crc_offset = stat.st_size - sizeof(pg_crc32c); if (crc_offset != MAXALIGN(crc_offset)) @@ -1327,8 +1327,8 @@ ReadTwoPhaseFile(TransactionId xid, bool missing_ok) errmsg("could not read file \"%s\": %m", path))); else ereport(ERROR, - (errmsg("could not read file \"%s\": read %d of %zu", - path, r, (Size) stat.st_size))); + (errmsg("could not read file \"%s\": read %d of %lld", + path, r, (long long int) stat.st_size))); } pgstat_report_wait_end(); diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index ed7556f537b1..888ca8874a84 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -76,6 +76,7 @@ #include "utils/memutils.h" #include "utils/ps_status.h" #include "utils/relmapper.h" +#include "utils/pg_rusage.h" #include "utils/snapmgr.h" #include "utils/timestamp.h" @@ -715,7 +716,7 @@ typedef struct XLogCtlData * WAL replay, if it is waiting for WAL to arrive or failover trigger file * to appear. */ - Latch recoveryWakeupLatch; + Latch *recoveryWakeupLatch; /* * During recovery, we keep a copy of the latest checkpoint record here. @@ -976,6 +977,7 @@ static bool CheckForStandbyTrigger(void); #ifdef WAL_DEBUG static void xlog_outrec(StringInfo buf, XLogReaderState *record); #endif +static void xlog_block_info(StringInfo buf, XLogReaderState *record); static void xlog_outdesc(StringInfo buf, XLogReaderState *record); static void pg_start_backup_callback(int code, Datum arg); static void pg_stop_backup_callback(int code, Datum arg); @@ -2236,6 +2238,7 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, bool opportunistic) WriteRqst.Flush = 0; XLogWrite(WriteRqst, false); LWLockRelease(WALWriteLock); + WalStats.m_wal_buffers_full++; TRACE_POSTGRESQL_WAL_BUFFER_WRITE_DIRTY_DONE(); } /* Re-acquire WALBufMappingLock and retry */ @@ -5249,7 +5252,6 @@ XLOGShmemInit(void) SpinLockInit(&XLogCtl->Insert.insertpos_lck); SpinLockInit(&XLogCtl->info_lck); SpinLockInit(&XLogCtl->ulsn_lck); - InitSharedLatch(&XLogCtl->recoveryWakeupLatch); } /* @@ -6206,7 +6208,7 @@ recoveryApplyDelay(XLogReaderState *record) while (true) { - ResetLatch(&XLogCtl->recoveryWakeupLatch); + ResetLatch(MyLatch); /* might change the trigger file's location */ HandleStartupProcInterrupts(); @@ -6230,7 +6232,7 @@ recoveryApplyDelay(XLogReaderState *record) elog(DEBUG2, "recovery apply delay %ld seconds, %d milliseconds", secs, microsecs / 1000); - (void) WaitLatch(&XLogCtl->recoveryWakeupLatch, + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, secs * 1000L + microsecs / 1000, WAIT_EVENT_RECOVERY_APPLY_DELAY); @@ -6730,11 +6732,11 @@ StartupXLOG(void) } /* - * Take ownership of the wakeup latch if we're going to sleep during - * recovery. + * Advertise our latch that other processes can use to wake us up + * if we're going to sleep during recovery. */ if (ArchiveRecoveryRequested) - OwnLatch(&XLogCtl->recoveryWakeupLatch); + XLogCtl->recoveryWakeupLatch = &MyProc->procLatch; /* Set up XLOG reader facility */ MemSet(&private, 0, sizeof(XLogPageReadPrivate)); @@ -7465,6 +7467,9 @@ StartupXLOG(void) { ErrorContextCallback errcallback; TimestampTz xtime; + PGRUsage ru0; + + pg_rusage_init(&ru0); InRedo = true; @@ -7756,8 +7761,9 @@ StartupXLOG(void) } ereport(LOG, - (errmsg("redo done at %X/%X", - (uint32) (ReadRecPtr >> 32), (uint32) ReadRecPtr))); + (errmsg("redo done at %X/%X system usage: %s", + (uint32) (ReadRecPtr >> 32), (uint32) ReadRecPtr, + pg_rusage_show(&ru0)))); xtime = GetLatestXTime(); if (xtime) ereport(LOG, @@ -7804,11 +7810,11 @@ StartupXLOG(void) ResetUnloggedRelations(UNLOGGED_RELATION_INIT); /* - * We don't need the latch anymore. It's not strictly necessary to disown - * it, but let's do it for the sake of tidiness. + * We don't need the latch anymore. It's not strictly necessary to reset + * it to NULL, but let's do it for the sake of tidiness. */ if (ArchiveRecoveryRequested) - DisownLatch(&XLogCtl->recoveryWakeupLatch); + XLogCtl->recoveryWakeupLatch = NULL; /* * We are now done reading the xlog from stream. Turn off streaming @@ -8959,11 +8965,6 @@ ShutdownXLOG(int code pg_attribute_unused() , Datum arg pg_attribute_unused() ) CreateCheckPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE); } - ShutdownCLOG(); - ShutdownCommitTs(); - ShutdownSUBTRANS(); - ShutdownMultiXact(); - DistributedLog_Shutdown(); } /* @@ -9739,18 +9740,30 @@ CreateOverwriteContrecordRecord(XLogRecPtr aborted_lsn) static void CheckPointGuts(XLogRecPtr checkPointRedo, int flags) { + CheckPointRelationMap(); + CheckPointReplicationSlots(); + CheckPointSnapBuild(); + CheckPointLogicalRewriteHeap(); + CheckPointReplicationOrigin(); + + /* Write out all dirty data in SLRUs and the main buffer pool */ + TRACE_POSTGRESQL_BUFFER_CHECKPOINT_START(flags); + CheckpointStats.ckpt_write_t = GetCurrentTimestamp(); CheckPointCLOG(); CheckPointCommitTs(); CheckPointSUBTRANS(); CheckPointMultiXact(); DistributedLog_CheckPoint(); CheckPointPredicate(); - CheckPointRelationMap(); - CheckPointReplicationSlots(); - CheckPointSnapBuild(); - CheckPointLogicalRewriteHeap(); - CheckPointBuffers(flags); /* performs all required fsyncs */ - CheckPointReplicationOrigin(); + CheckPointBuffers(flags); + + /* Perform all queued up fsyncs */ + TRACE_POSTGRESQL_BUFFER_CHECKPOINT_SYNC_START(); + CheckpointStats.ckpt_sync_t = GetCurrentTimestamp(); + ProcessSyncRequests(); + CheckpointStats.ckpt_sync_end_t = GetCurrentTimestamp(); + TRACE_POSTGRESQL_BUFFER_CHECKPOINT_DONE(); + /* We deliberately delay 2PC checkpointing as long as possible */ CheckPointTwoPhase(checkPointRedo); } @@ -10947,8 +10960,6 @@ VerifyOverwriteContrecord(xl_overwrite_contrecord *xlrec, XLogReaderState *state static void xlog_outrec(StringInfo buf, XLogReaderState *record) { - int block_id; - appendStringInfo(buf, "prev %X/%X; xid %u", (uint32) (XLogRecGetPrev(record) >> 32), (uint32) XLogRecGetPrev(record), @@ -10957,6 +10968,19 @@ xlog_outrec(StringInfo buf, XLogReaderState *record) appendStringInfo(buf, "; len %u", XLogRecGetDataLen(record)); + xlog_block_info(buf, record); +} +#endif /* WAL_DEBUG */ + +/* + * Returns a string giving information about all the blocks in an + * XLogRecord. + */ +static void +xlog_block_info(StringInfo buf, XLogReaderState *record) +{ + int block_id; + /* decode block references */ for (block_id = 0; block_id <= record->max_block_id; block_id++) { @@ -10983,7 +11007,6 @@ xlog_outrec(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, " FPW"); } } -#endif /* WAL_DEBUG */ /* * Returns a string describing an XLogRecord, consisting of its identity @@ -12469,6 +12492,7 @@ rm_redo_error_callback(void *arg) initStringInfo(&buf); xlog_outdesc(&buf, record); + xlog_block_info(&buf, record); /* translator: %s is a WAL record description */ errcontext("WAL redo at %X/%X for %s", @@ -12945,12 +12969,12 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, wait_time = wal_retrieve_retry_interval - (secs * 1000 + usecs / 1000); - (void) WaitLatch(&XLogCtl->recoveryWakeupLatch, + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, wait_time, WAIT_EVENT_RECOVERY_RETRIEVE_RETRY_INTERVAL); - ResetLatch(&XLogCtl->recoveryWakeupLatch); + ResetLatch(MyLatch); now = GetCurrentTimestamp(); } last_fail_time = now; @@ -13216,11 +13240,11 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, * to react to a trigger file promptly and to check if the * WAL receiver is still active. */ - (void) WaitLatch(&XLogCtl->recoveryWakeupLatch, + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 5000L, WAIT_EVENT_RECOVERY_WAL_STREAM); - ResetLatch(&XLogCtl->recoveryWakeupLatch); + ResetLatch(MyLatch); break; } @@ -13248,7 +13272,7 @@ StartupRequestWalReceiverRestart(void) if (currentSource == XLOG_FROM_STREAM && WalRcvRunning()) { ereport(LOG, - (errmsg("wal receiver process shutdown requested"))); + (errmsg("WAL receiver process shutdown requested"))); pendingWalRcvRestart = true; } @@ -13392,7 +13416,7 @@ CheckPromoteSignal(void) void WakeupRecovery(void) { - SetLatch(&XLogCtl->recoveryWakeupLatch); + SetLatch(XLogCtl->recoveryWakeupLatch); } /* diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c index 03911190335c..65eb5ab992cd 100644 --- a/src/backend/access/transam/xlogarchive.c +++ b/src/backend/access/transam/xlogarchive.c @@ -208,10 +208,10 @@ RestoreArchivedFile(char *path, const char *xlogfname, else elevel = FATAL; ereport(elevel, - (errmsg("archive file \"%s\" has wrong size: %lu instead of %lu", + (errmsg("archive file \"%s\" has wrong size: %lld instead of %lld", xlogfname, - (unsigned long) stat_buf.st_size, - (unsigned long) expectedSize))); + (long long int) stat_buf.st_size, + (long long int) expectedSize))); return false; } else diff --git a/src/backend/access/transam/xlogfuncs_gp.c b/src/backend/access/transam/xlogfuncs_gp.c index 781056324eb2..d95c3fdd39c6 100644 --- a/src/backend/access/transam/xlogfuncs_gp.c +++ b/src/backend/access/transam/xlogfuncs_gp.c @@ -64,7 +64,7 @@ gp_create_restore_point(PG_FUNCTION_ARGS) TupleDescInitEntry(tupdesc, (AttrNumber) 1, "gp_segment_id", INT2OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "restore_lsn", - LSNOID, -1, 0); + PG_LSNOID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); @@ -198,7 +198,7 @@ gp_switch_wal(PG_FUNCTION_ARGS) TupleDescInitEntry(tupdesc, (AttrNumber) 1, "segment_id", INT2OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "switch_lsn", - LSNOID, -1, 0); + PG_LSNOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 3, "switch_walfilename", TEXTOID, -1, 0); diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index a01b6d4c564e..ce73c1b1f811 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -1059,6 +1059,63 @@ log_newpage(RelFileNode *rnode, ForkNumber forkNum, BlockNumber blkno, return recptr; } +/* + * Like log_newpage(), but allows logging multiple pages in one operation. + * It is more efficient than calling log_newpage() for each page separately, + * because we can write multiple pages in a single WAL record. + */ +void +log_newpages(RelFileNode *rnode, ForkNumber forkNum, int num_pages, + BlockNumber *blknos, Page *pages, bool page_std) +{ + int flags; + XLogRecPtr recptr; + int i; + int j; + + flags = REGBUF_FORCE_IMAGE; + if (page_std) + flags |= REGBUF_STANDARD; + + /* + * Iterate over all the pages. They are collected into batches of + * XLR_MAX_BLOCK_ID pages, and a single WAL-record is written for each + * batch. + */ + XLogEnsureRecordSpace(XLR_MAX_BLOCK_ID - 1, 0); + + i = 0; + while (i < num_pages) + { + int batch_start = i; + int nbatch; + + XLogBeginInsert(); + + nbatch = 0; + while (nbatch < XLR_MAX_BLOCK_ID && i < num_pages) + { + XLogRegisterBlock(nbatch, rnode, forkNum, blknos[i], pages[i], flags); + i++; + nbatch++; + } + + recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI); + + for (j = batch_start; j < i; j++) + { + /* + * The page may be uninitialized. If so, we can't set the LSN because that + * would corrupt the page. + */ + if (!PageIsNew(pages[j])) + { + PageSetLSN(pages[j], recptr); + } + } + } +} + /* * Write a WAL record containing a full image of a page. * diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index 97fe3595df1c..ecd8bb8e039d 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -496,8 +496,9 @@ XLogReadRecord(XLogReaderState *state, char **errormsg) total_len != (pageHeader->xlp_rem_len + gotlen)) { report_invalid_record(state, - "invalid contrecord length %u at %X/%X", + "invalid contrecord length %u (expected %lld) at %X/%X", pageHeader->xlp_rem_len, + ((long long) total_len) - gotlen, (uint32) (RecPtr >> 32), (uint32) RecPtr); goto err; } diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index 2802c1629c28..9d043da22ba0 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -20,16 +20,10 @@ #include -#include "access/attnum.h" -#include "access/htup.h" -#include "access/itup.h" -#include "access/tupdesc.h" #include "bootstrap/bootstrap.h" -#include "catalog/catalog.h" #include "catalog/heap.h" #include "catalog/namespace.h" #include "catalog/pg_am.h" -#include "catalog/pg_attribute.h" #include "catalog/pg_authid.h" #include "catalog/pg_auth_members.h" #include "catalog/pg_class.h" @@ -40,20 +34,7 @@ #include "commands/defrem.h" #include "miscadmin.h" #include "nodes/makefuncs.h" -#include "nodes/nodes.h" -#include "nodes/parsenodes.h" -#include "nodes/pg_list.h" -#include "nodes/primnodes.h" -#include "rewrite/prs2lock.h" -#include "storage/block.h" -#include "storage/fd.h" -#include "storage/ipc.h" -#include "storage/itemptr.h" -#include "storage/off.h" -#include "storage/smgr.h" -#include "tcop/dest.h" #include "utils/memutils.h" -#include "utils/rel.h" /* diff --git a/src/backend/bootstrap/bootscanner.l b/src/backend/bootstrap/bootscanner.l index 94fd2c902614..7192a442beb5 100644 --- a/src/backend/bootstrap/bootscanner.l +++ b/src/backend/bootstrap/bootscanner.l @@ -15,25 +15,8 @@ */ #include "postgres.h" -#include "access/attnum.h" -#include "access/htup.h" -#include "access/itup.h" -#include "access/tupdesc.h" #include "bootstrap/bootstrap.h" -#include "catalog/pg_am.h" -#include "catalog/pg_attribute.h" -#include "catalog/pg_class.h" -#include "nodes/nodes.h" -#include "nodes/parsenodes.h" -#include "nodes/pg_list.h" -#include "nodes/primnodes.h" -#include "parser/scansup.h" -#include "rewrite/prs2lock.h" -#include "storage/block.h" -#include "storage/fd.h" -#include "storage/itemptr.h" -#include "storage/off.h" -#include "utils/rel.h" +#include "utils/guc.h" #define unify_version(a,b,c) ((a<<16)+(b<<8)+c) @@ -87,7 +70,7 @@ static int yyline = 1; /* line number for error reporting */ id [-A-Za-z0-9_]+ -sid \"([^\"])*\" +sid \'([^']|\'\')*\' /* * Keyword tokens return the keyword text (as a constant string) in yylval.kw, @@ -141,14 +124,12 @@ NOT { yylval.kw = "NOT"; return XNOT; } NULL { yylval.kw = "NULL"; return XNULL; } {id} { - yylval.str = scanstr(yytext); + yylval.str = pstrdup(yytext); return ID; } {sid} { - /* leading and trailing quotes are not passed to scanstr */ - yytext[strlen(yytext) - 1] = '\0'; - yylval.str = scanstr(yytext+1); - yytext[strlen(yytext)] = '"'; /* restore yytext */ + /* strip quotes and escapes */ + yylval.str = DeescapeQuotedString(yytext); return ID; } diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 3f85f5ed6508..df6cdddc5d66 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -55,14 +55,12 @@ uint32 bootstrap_data_checksum_version = 0; /* No checksum */ -#define ALLOC(t, c) \ - ((t *) MemoryContextAllocZero(TopMemoryContext, (unsigned)(c) * sizeof(t))) - static void CheckerModeMain(void); static void BootstrapModeMain(void); static void bootstrap_signals(void); static void ShutdownAuxiliaryProcess(int code, Datum arg); static Form_pg_attribute AllocateAttribute(void); +static void populate_typ_array(void); static Oid gettype(char *type); static void cleanup(void); @@ -137,7 +135,7 @@ static const struct typinfo TypInfo[] = { F_XIDIN, F_XIDOUT}, {"cid", CIDOID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid, F_CIDIN, F_CIDOUT}, - {"pg_node_tree", PGNODETREEOID, 0, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, DEFAULT_COLLATION_OID, + {"pg_node_tree", PG_NODE_TREEOID, 0, -1, false, TYPALIGN_INT, TYPSTORAGE_EXTENDED, DEFAULT_COLLATION_OID, F_PG_NODE_TREE_IN, F_PG_NODE_TREE_OUT}, {"int2vector", INT2VECTOROID, INT2OID, -1, false, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid, F_INT2VECTORIN, F_INT2VECTOROUT}, @@ -591,46 +589,24 @@ ShutdownAuxiliaryProcess(int code, Datum arg) /* ---------------- * boot_openrel + * + * Execute BKI OPEN command. * ---------------- */ void boot_openrel(char *relname) { int i; - struct typmap **app; - Relation rel; - TableScanDesc scan; - HeapTuple tup; if (strlen(relname) >= NAMEDATALEN) relname[NAMEDATALEN - 1] = '\0'; + /* + * pg_type must be filled before any OPEN command is executed, hence we + * can now populate the Typ array if we haven't yet. + */ if (Typ == NULL) - { - /* We can now load the pg_type data */ - rel = table_open(TypeRelationId, NoLock); - scan = table_beginscan_catalog(rel, 0, NULL); - i = 0; - while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL) - ++i; - table_endscan(scan); - app = Typ = ALLOC(struct typmap *, i + 1); - while (i-- > 0) - *app++ = ALLOC(struct typmap, 1); - *app = NULL; - scan = table_beginscan_catalog(rel, 0, NULL); - app = Typ; - while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - (*app)->am_oid = ((Form_pg_type) GETSTRUCT(tup))->oid; - memcpy((char *) &(*app)->am_typ, - (char *) GETSTRUCT(tup), - sizeof((*app)->am_typ)); - app++; - } - table_endscan(scan); - table_close(rel, NoLock); - } + populate_typ_array(); if (boot_reldesc != NULL) closerel(NULL); @@ -897,6 +873,52 @@ cleanup(void) closerel(NULL); } +/* ---------------- + * populate_typ_array + * + * Load the Typ array by reading pg_type. + * ---------------- + */ +static void +populate_typ_array(void) +{ + Relation rel; + TableScanDesc scan; + HeapTuple tup; + int nalloc; + int i; + + Assert(Typ == NULL); + + nalloc = 512; + Typ = (struct typmap **) + MemoryContextAlloc(TopMemoryContext, nalloc * sizeof(struct typmap *)); + + rel = table_open(TypeRelationId, NoLock); + scan = table_beginscan_catalog(rel, 0, NULL); + i = 0; + while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Form_pg_type typForm = (Form_pg_type) GETSTRUCT(tup); + + /* make sure there will be room for a trailing NULL pointer */ + if (i >= nalloc - 1) + { + nalloc *= 2; + Typ = (struct typmap **) + repalloc(Typ, nalloc * sizeof(struct typmap *)); + } + Typ[i] = (struct typmap *) + MemoryContextAlloc(TopMemoryContext, sizeof(struct typmap)); + Typ[i]->am_oid = typForm->oid; + memcpy(&(Typ[i]->am_typ), typForm, sizeof(Typ[i]->am_typ)); + i++; + } + Typ[i] = NULL; /* Fill trailing NULL pointer */ + table_endscan(scan); + table_close(rel, NoLock); +} + /* ---------------- * gettype * @@ -911,14 +933,10 @@ cleanup(void) static Oid gettype(char *type) { - int i; - Relation rel; - TableScanDesc scan; - HeapTuple tup; - struct typmap **app; - if (Typ != NULL) { + struct typmap **app; + for (app = Typ; *app != NULL; app++) { if (strncmp(NameStr((*app)->am_typ.typname), type, NAMEDATALEN) == 0) @@ -930,33 +948,16 @@ gettype(char *type) } else { + int i; + for (i = 0; i < n_types; i++) { if (strncmp(type, TypInfo[i].name, NAMEDATALEN) == 0) return i; } + /* Not in TypInfo, so we'd better be able to read pg_type now */ elog(DEBUG4, "external type: %s", type); - rel = table_open(TypeRelationId, NoLock); - scan = table_beginscan_catalog(rel, 0, NULL); - i = 0; - while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL) - ++i; - table_endscan(scan); - app = Typ = ALLOC(struct typmap *, i + 1); - while (i-- > 0) - *app++ = ALLOC(struct typmap, 1); - *app = NULL; - scan = table_beginscan_catalog(rel, 0, NULL); - app = Typ; - while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - (*app)->am_oid = ((Form_pg_type) GETSTRUCT(tup))->oid; - memmove((char *) &(*app++)->am_typ, - (char *) GETSTRUCT(tup), - sizeof((*app)->am_typ)); - } - table_endscan(scan); - table_close(rel, NoLock); + populate_typ_array(); return gettype(type); } elog(ERROR, "unrecognized type \"%s\"", type); diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 6455e634ffe0..9d003f37d89c 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -450,6 +450,84 @@ performMultipleDeletions(const ObjectAddresses *objects, table_close(depRel, RowExclusiveLock); } +/* + * Call a function for all objects that 'object' depend on. If the function + * returns true, refobjversion will be updated in the catalog. + */ +void +visitDependenciesOf(const ObjectAddress *object, + VisitDependenciesOfCB callback, + void *userdata) +{ + Relation depRel; + ScanKeyData key[3]; + SysScanDesc scan; + HeapTuple tup; + ObjectAddress otherObject; + + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->classId)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + ScanKeyInit(&key[2], + Anum_pg_depend_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(object->objectSubId)); + + depRel = table_open(DependRelationId, RowExclusiveLock); + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 3, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup); + char *new_version; + Datum depversion; + bool isnull; + + otherObject.classId = foundDep->refclassid; + otherObject.objectId = foundDep->refobjid; + otherObject.objectSubId = foundDep->refobjsubid; + + depversion = heap_getattr(tup, Anum_pg_depend_refobjversion, + RelationGetDescr(depRel), &isnull); + + /* Does the callback want to update the version? */ + if (callback(&otherObject, + isnull ? NULL : TextDatumGetCString(depversion), + &new_version, + userdata)) + { + Datum values[Natts_pg_depend]; + bool nulls[Natts_pg_depend]; + bool replaces[Natts_pg_depend]; + + memset(values, 0, sizeof(values)); + memset(nulls, false, sizeof(nulls)); + memset(replaces, false, sizeof(replaces)); + + if (new_version) + values[Anum_pg_depend_refobjversion - 1] = + CStringGetTextDatum(new_version); + else + nulls[Anum_pg_depend_refobjversion - 1] = true; + replaces[Anum_pg_depend_refobjversion - 1] = true; + + tup = heap_modify_tuple(tup, RelationGetDescr(depRel), values, + nulls, replaces); + CatalogTupleUpdate(depRel, &tup->t_self, tup); + + heap_freetuple(tup); + } + } + systable_endscan(scan); + table_close(depRel, RowExclusiveLock); +} + /* * findDependentObjects - find all objects that depend on 'object' * @@ -1636,6 +1714,38 @@ ReleaseDeletionLock(const ObjectAddress *object) AccessExclusiveLock); } +/* + * Record dependencies on a list of collations, optionally with their current + * version. + */ +void +recordDependencyOnCollations(ObjectAddress *myself, + List *collations, + bool record_version) +{ + ObjectAddresses *addrs; + ListCell *lc; + + if (list_length(collations) == 0) + return; + + addrs = new_object_addresses(); + foreach(lc, collations) + { + ObjectAddress referenced; + + ObjectAddressSet(referenced, CollationRelationId, lfirst_oid(lc)); + + add_exact_object_address(&referenced, addrs); + } + + eliminate_duplicate_dependencies(addrs); + recordMultipleDependencies(myself, addrs->refs, addrs->numrefs, + DEPENDENCY_NORMAL, record_version); + + free_object_addresses(addrs); +} + /* * recordDependencyOnExpr - find expression dependencies * @@ -1670,8 +1780,10 @@ recordDependencyOnExpr(const ObjectAddress *depender, /* And record 'em */ recordMultipleDependencies(depender, - context.addrs->refs, context.addrs->numrefs, - behavior); + context.addrs->refs, + context.addrs->numrefs, + behavior, + false); free_object_addresses(context.addrs); } @@ -1698,7 +1810,8 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender, Node *expr, Oid relId, DependencyType behavior, DependencyType self_behavior, - bool reverse_self) + bool reverse_self, + bool record_version) { find_expr_references_context context; RangeTblEntry rte; @@ -1757,8 +1870,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender, /* Record the self-dependencies with the appropriate direction */ if (!reverse_self) recordMultipleDependencies(depender, - self_addrs->refs, self_addrs->numrefs, - self_behavior); + self_addrs->refs, + self_addrs->numrefs, + self_behavior, + record_version); else { /* Can't use recordMultipleDependencies, so do it the hard way */ @@ -1777,8 +1892,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender, /* Record the external dependencies */ recordMultipleDependencies(depender, - context.addrs->refs, context.addrs->numrefs, - behavior); + context.addrs->refs, + context.addrs->numrefs, + behavior, + record_version); free_object_addresses(context.addrs); } @@ -1834,6 +1951,29 @@ find_expr_references_walker(Node *node, /* If it's a plain relation, reference this column */ add_object_address(OCLASS_CLASS, rte->relid, var->varattno, context->addrs); + + /* Top-level collation if valid */ + if (OidIsValid(var->varcollid)) + add_object_address(OCLASS_COLLATION, var->varcollid, 0, + context->addrs); + /* Otherwise, it may be a type with internal collations */ + else if (var->vartype >= FirstNormalObjectId) + { + List *collations; + ListCell *lc; + + collations = GetTypeCollations(var->vartype); + + foreach(lc, collations) + { + Oid coll = lfirst_oid(lc); + + if (OidIsValid(coll)) + add_object_address(OCLASS_COLLATION, + lfirst_oid(lc), 0, + context->addrs); + } + } } /* @@ -1858,11 +1998,9 @@ find_expr_references_walker(Node *node, /* * We must also depend on the constant's collation: it could be * different from the datatype's, if a CollateExpr was const-folded to - * a simple constant. However we can save work in the most common - * case where the collation is "default", since we know that's pinned. + * a simple constant. */ - if (OidIsValid(con->constcollid) && - con->constcollid != DEFAULT_COLLATION_OID) + if (OidIsValid(con->constcollid)) add_object_address(OCLASS_COLLATION, con->constcollid, 0, context->addrs); @@ -1951,8 +2089,7 @@ find_expr_references_walker(Node *node, add_object_address(OCLASS_TYPE, param->paramtype, 0, context->addrs); /* and its collation, just as for Consts */ - if (OidIsValid(param->paramcollid) && - param->paramcollid != DEFAULT_COLLATION_OID) + if (OidIsValid(param->paramcollid)) add_object_address(OCLASS_COLLATION, param->paramcollid, 0, context->addrs); } @@ -2039,8 +2176,7 @@ find_expr_references_walker(Node *node, add_object_address(OCLASS_TYPE, fselect->resulttype, 0, context->addrs); /* the collation might not be referenced anywhere else, either */ - if (OidIsValid(fselect->resultcollid) && - fselect->resultcollid != DEFAULT_COLLATION_OID) + if (OidIsValid(fselect->resultcollid)) add_object_address(OCLASS_COLLATION, fselect->resultcollid, 0, context->addrs); } @@ -2070,8 +2206,7 @@ find_expr_references_walker(Node *node, add_object_address(OCLASS_TYPE, relab->resulttype, 0, context->addrs); /* the collation might not be referenced anywhere else, either */ - if (OidIsValid(relab->resultcollid) && - relab->resultcollid != DEFAULT_COLLATION_OID) + if (OidIsValid(relab->resultcollid)) add_object_address(OCLASS_COLLATION, relab->resultcollid, 0, context->addrs); } @@ -2083,8 +2218,7 @@ find_expr_references_walker(Node *node, add_object_address(OCLASS_TYPE, iocoerce->resulttype, 0, context->addrs); /* the collation might not be referenced anywhere else, either */ - if (OidIsValid(iocoerce->resultcollid) && - iocoerce->resultcollid != DEFAULT_COLLATION_OID) + if (OidIsValid(iocoerce->resultcollid)) add_object_address(OCLASS_COLLATION, iocoerce->resultcollid, 0, context->addrs); } @@ -2096,8 +2230,7 @@ find_expr_references_walker(Node *node, add_object_address(OCLASS_TYPE, acoerce->resulttype, 0, context->addrs); /* the collation might not be referenced anywhere else, either */ - if (OidIsValid(acoerce->resultcollid) && - acoerce->resultcollid != DEFAULT_COLLATION_OID) + if (OidIsValid(acoerce->resultcollid)) add_object_address(OCLASS_COLLATION, acoerce->resultcollid, 0, context->addrs); /* fall through to examine arguments */ @@ -2185,8 +2318,7 @@ find_expr_references_walker(Node *node, if (OidIsValid(wc->endInRangeFunc)) add_object_address(OCLASS_PROC, wc->endInRangeFunc, 0, context->addrs); - if (OidIsValid(wc->inRangeColl) && - wc->inRangeColl != DEFAULT_COLLATION_OID) + if (OidIsValid(wc->inRangeColl)) add_object_address(OCLASS_COLLATION, wc->inRangeColl, 0, context->addrs); /* fall through to examine substructure */ @@ -2331,7 +2463,7 @@ find_expr_references_walker(Node *node, { Oid collid = lfirst_oid(ct); - if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID) + if (OidIsValid(collid)) add_object_address(OCLASS_COLLATION, collid, 0, context->addrs); } @@ -2353,7 +2485,7 @@ find_expr_references_walker(Node *node, { Oid collid = lfirst_oid(ct); - if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID) + if (OidIsValid(collid)) add_object_address(OCLASS_COLLATION, collid, 0, context->addrs); } @@ -2750,7 +2882,8 @@ record_object_address_dependencies(const ObjectAddress *depender, eliminate_duplicate_dependencies(referenced); recordMultipleDependencies(depender, referenced->refs, referenced->numrefs, - behavior); + behavior, + false); } /* diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl index eb7a640728a3..d05a78cd67c6 100644 --- a/src/backend/catalog/genbki.pl +++ b/src/backend/catalog/genbki.pl @@ -599,9 +599,13 @@ } # Special hack to generate OID symbols for pg_type entries - # that lack one. - if ($catname eq 'pg_type' and !exists $bki_values{oid_symbol}) + if ($catname eq 'pg_type') { + die sprintf + "custom OID symbols are not allowed for pg_type entries: '%s'", + $bki_values{oid_symbol} + if defined $bki_values{oid_symbol}; + my $symbol = form_pg_type_symbol($bki_values{typname}); $bki_values{oid_symbol} = $symbol if defined $symbol; @@ -613,6 +617,13 @@ # Emit OID symbol if (defined $bki_values{oid_symbol}) { + # OID symbols for builtin functions are handled automatically + # by utils/Gen_fmgrtab.pl + die sprintf + "custom OID symbols are not allowed for pg_proc entries: '%s'", + $bki_values{oid_symbol} + if $catname eq 'pg_proc'; + printf $def "#define %s %s\n", $bki_values{oid_symbol}, $bki_values{oid}; } @@ -857,17 +868,15 @@ sub print_bki_insert # since that represents a NUL char in C code. $bki_value = '' if $bki_value eq '\0'; - # Handle single quotes by doubling them, and double quotes by - # converting them to octal escapes, because that's what the + # Handle single quotes by doubling them, because that's what the # bootstrap scanner requires. We do not process backslashes # specially; this allows escape-string-style backslash escapes # to be used in catalog data. $bki_value =~ s/'/''/g; - $bki_value =~ s/"/\\042/g; # Quote value if needed. We need not quote values that satisfy # the "id" pattern in bootscanner.l, currently "[-A-Za-z0-9_]+". - $bki_value = sprintf(qq'"%s"', $bki_value) + $bki_value = sprintf("'%s'", $bki_value) if length($bki_value) == 0 or $bki_value =~ /[^-A-Za-z0-9_]/; diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index b6632fd4466d..c07e99dc7800 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -1046,12 +1046,6 @@ void MetaTrackDropObject(Oid classid, } /* end MetaTrackDropObject */ -/* - * Cap the maximum amount of bytes allocated for InsertPgAttributeTuples() - * slots. - */ -#define MAX_PGATTRIBUTE_INSERT_BYTES 65535 - /* * InsertPgAttributeTuples * Construct and insert a set of tuples in pg_attribute. @@ -1087,7 +1081,7 @@ InsertPgAttributeTuples(Relation pg_attribute_rel, /* Initialize the number of slots to use */ nslots = Min(tupdesc->natts, - (MAX_PGATTRIBUTE_INSERT_BYTES / sizeof(FormData_pg_attribute))); + (MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_attribute))); slot = palloc(sizeof(TupleTableSlot *) * nslots); for (int i = 0; i < nslots; i++) slot[i] = MakeSingleTupleTableSlot(td, &TTSOpsHeapTuple); @@ -1355,7 +1349,7 @@ AddNewRelationTuple(Relation pg_class_desc, case RELKIND_AOVISIMAP: /* The relation is real, but as yet empty */ new_rel_reltup->relpages = 0; - new_rel_reltup->reltuples = 0; + new_rel_reltup->reltuples = -1; new_rel_reltup->relallvisible = 0; break; case RELKIND_SEQUENCE: @@ -1367,7 +1361,7 @@ AddNewRelationTuple(Relation pg_class_desc, default: /* Views, etc, have no disk storage */ new_rel_reltup->relpages = 0; - new_rel_reltup->reltuples = 0; + new_rel_reltup->reltuples = -1; new_rel_reltup->relallvisible = 0; break; } @@ -1804,15 +1798,9 @@ heap_create_with_catalog(const char *relname, { ObjectAddress myself, referenced; + ObjectAddresses *addrs; - myself.classId = RelationRelationId; - myself.objectId = relid; - myself.objectSubId = 0; - - referenced.classId = NamespaceRelationId; - referenced.objectId = relnamespace; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(myself, RelationRelationId, relid); recordDependencyOnOwner(RelationRelationId, relid, ownerid); @@ -1820,12 +1808,15 @@ heap_create_with_catalog(const char *relname, recordDependencyOnCurrentExtension(&myself, false); + addrs = new_object_addresses(); + + ObjectAddressSet(referenced, NamespaceRelationId, relnamespace); + add_exact_object_address(&referenced, addrs); + if (reloftypeid) { - referenced.classId = TypeRelationId; - referenced.objectId = reloftypeid; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, TypeRelationId, reloftypeid); + add_exact_object_address(&referenced, addrs); } /* @@ -1840,11 +1831,12 @@ heap_create_with_catalog(const char *relname, relkind == RELKIND_MATVIEW || relkind == RELKIND_PARTITIONED_TABLE) { - referenced.classId = AccessMethodRelationId; - referenced.objectId = accessmtd; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, AccessMethodRelationId, accessmtd); + add_exact_object_address(&referenced, addrs); } + + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); } /* Post creation hook for new relation */ @@ -2866,7 +2858,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, */ recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel), DEPENDENCY_AUTO, - DEPENDENCY_AUTO, false); + DEPENDENCY_AUTO, false, false); } else { @@ -2876,7 +2868,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, */ recordDependencyOnSingleRelExpr(&defobject, expr, RelationGetRelid(rel), DEPENDENCY_NORMAL, - DEPENDENCY_NORMAL, false); + DEPENDENCY_NORMAL, false, false); } /* @@ -3680,6 +3672,47 @@ cookConstraint(ParseState *pstate, return expr; } +/* + * CopyStatistics --- copy entries in pg_statistic from one rel to another + */ +void +CopyStatistics(Oid fromrelid, Oid torelid) +{ + HeapTuple tup; + SysScanDesc scan; + ScanKeyData key[1]; + Relation statrel; + + statrel = table_open(StatisticRelationId, RowExclusiveLock); + + /* Now search for stat records */ + ScanKeyInit(&key[0], + Anum_pg_statistic_starelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(fromrelid)); + + scan = systable_beginscan(statrel, StatisticRelidAttnumInhIndexId, + true, NULL, 1, key); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + Form_pg_statistic statform; + + /* make a modifiable copy */ + tup = heap_copytuple(tup); + statform = (Form_pg_statistic) GETSTRUCT(tup); + + /* update the copy of the tuple and insert it */ + statform->starelid = torelid; + CatalogTupleInsert(statrel, tup); + + heap_freetuple(tup); + } + + systable_endscan(scan); + + table_close(statrel, RowExclusiveLock); +} /* * RemoveStatistics --- remove entries in pg_statistic for a rel or column @@ -3971,7 +4004,7 @@ List * heap_truncate_find_FKs(List *relationIds) { List *result = NIL; - List *oids = list_copy(relationIds); + List *oids; List *parent_cons; ListCell *cell; ScanKeyData key; @@ -4115,6 +4148,7 @@ StorePartitionKey(Relation rel, bool nulls[Natts_pg_partitioned_table]; ObjectAddress myself; ObjectAddress referenced; + ObjectAddresses *addrs; Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); @@ -4158,31 +4192,27 @@ StorePartitionKey(Relation rel, table_close(pg_partitioned_table, RowExclusiveLock); /* Mark this relation as dependent on a few things as follows */ - myself.classId = RelationRelationId; - myself.objectId = RelationGetRelid(rel); - myself.objectSubId = 0; + addrs = new_object_addresses(); + ObjectAddressSet(myself, RelationRelationId, RelationGetRelid(rel)); /* Operator class and collation per key column */ for (i = 0; i < partnatts; i++) { - referenced.classId = OperatorClassRelationId; - referenced.objectId = partopclass[i]; - referenced.objectSubId = 0; - - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, OperatorClassRelationId, partopclass[i]); + add_exact_object_address(&referenced, addrs); /* The default collation is pinned, so don't bother recording it */ if (OidIsValid(partcollation[i]) && partcollation[i] != DEFAULT_COLLATION_OID) { - referenced.classId = CollationRelationId; - referenced.objectId = partcollation[i]; - referenced.objectSubId = 0; - - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, CollationRelationId, partcollation[i]); + add_exact_object_address(&referenced, addrs); } } + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); + /* * The partitioning columns are made internally dependent on the table, * because we cannot drop any of them without dropping the whole table. @@ -4194,10 +4224,8 @@ StorePartitionKey(Relation rel, if (partattrs[i] == 0) continue; /* ignore expressions here */ - referenced.classId = RelationRelationId; - referenced.objectId = RelationGetRelid(rel); - referenced.objectSubId = partattrs[i]; - + ObjectAddressSubSet(referenced, RelationRelationId, + RelationGetRelid(rel), partattrs[i]); recordDependencyOn(&referenced, &myself, DEPENDENCY_INTERNAL); } @@ -4213,7 +4241,8 @@ StorePartitionKey(Relation rel, RelationGetRelid(rel), DEPENDENCY_NORMAL, DEPENDENCY_INTERNAL, - true /* reverse the self-deps */ ); + true /* reverse the self-deps */ , + false /* don't track versions */ ); /* * We must invalidate the relcache so that the next diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index b1963461c8f3..77d13e750a16 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -59,6 +59,7 @@ #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "catalog/storage.h" +#include "commands/defrem.h" #include "commands/event_trigger.h" #include "commands/progress.h" #include "commands/tablecmds.h" @@ -81,6 +82,7 @@ #include "utils/guc.h" #include "utils/inval.h" #include "utils/lsyscache.h" +#include "utils/pg_locale.h" #include "utils/memutils.h" #include "utils/pg_rusage.h" #include "utils/rel.h" @@ -1095,6 +1097,9 @@ index_create(Relation heapRelation, { ObjectAddress myself, referenced; + ObjectAddresses *addrs; + List *colls = NIL, + *colls_no_version = NIL; ObjectAddressSet(myself, RelationRelationId, indexRelationId); @@ -1131,6 +1136,8 @@ index_create(Relation heapRelation, { bool have_simple_col = false; + addrs = new_object_addresses(); + /* Create auto dependencies on simply-referenced columns */ for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) { @@ -1139,7 +1146,7 @@ index_create(Relation heapRelation, ObjectAddressSubSet(referenced, RelationRelationId, heapRelationId, indexInfo->ii_IndexAttrNumbers[i]); - recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + add_exact_object_address(&referenced, addrs); have_simple_col = true; } } @@ -1154,8 +1161,11 @@ index_create(Relation heapRelation, { ObjectAddressSet(referenced, RelationRelationId, heapRelationId); - recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + add_exact_object_address(&referenced, addrs); } + + record_object_address_dependencies(&myself, addrs, DEPENDENCY_AUTO); + free_object_addresses(addrs); } /* @@ -1173,25 +1183,67 @@ index_create(Relation heapRelation, recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC); } - /* Store dependency on collations */ - /* The default collation is pinned, so don't bother recording it */ + /* Get dependencies on collations for all index keys. */ for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++) { - if (OidIsValid(collationObjectId[i]) && - collationObjectId[i] != DEFAULT_COLLATION_OID) + Oid colloid = collationObjectId[i]; + + if (OidIsValid(colloid)) { - ObjectAddressSet(referenced, CollationRelationId, - collationObjectId[i]); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + Oid opclass = classObjectId[i]; + + /* + * The *_pattern_ops opclasses are special: they explicitly do + * not depend on collation order so we can save some effort. + * + * XXX With more analysis, we could also skip version tracking + * for some cases like hash indexes with deterministic + * collations, because they will never need to order strings. + */ + if (opclass == TEXT_BTREE_PATTERN_OPS_OID || + opclass == VARCHAR_BTREE_PATTERN_OPS_OID || + opclass == BPCHAR_BTREE_PATTERN_OPS_OID) + colls_no_version = lappend_oid(colls_no_version, colloid); + else + colls = lappend_oid(colls, colloid); + } + else + { + Form_pg_attribute att = TupleDescAttr(indexTupDesc, i); + + Assert(i < indexTupDesc->natts); + + /* + * Even though there is no top-level collation, there may be + * collations affecting ordering inside types, so look there + * too. + */ + colls = list_concat(colls, GetTypeCollations(att->atttypid)); } } + /* + * If we have anything in both lists, keep just the versioned one to + * avoid some duplication. + */ + if (colls_no_version != NIL && colls != NIL) + colls_no_version = list_difference_oid(colls_no_version, colls); + + /* Store the versioned and unversioned collation dependencies. */ + if (colls_no_version != NIL) + recordDependencyOnCollations(&myself, colls_no_version, false); + if (colls != NIL) + recordDependencyOnCollations(&myself, colls, true); + /* Store dependency on operator classes */ + addrs = new_object_addresses(); for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++) { ObjectAddressSet(referenced, OperatorClassRelationId, classObjectId[i]); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); /* Store dependencies on anything mentioned in index expressions */ if (indexInfo->ii_Expressions) @@ -1200,7 +1252,7 @@ index_create(Relation heapRelation, (Node *) indexInfo->ii_Expressions, heapRelationId, DEPENDENCY_NORMAL, - DEPENDENCY_AUTO, false); + DEPENDENCY_AUTO, false, true); } /* Store dependencies on anything mentioned in predicate */ @@ -1210,7 +1262,7 @@ index_create(Relation heapRelation, (Node *) indexInfo->ii_Predicate, heapRelationId, DEPENDENCY_NORMAL, - DEPENDENCY_AUTO, false); + DEPENDENCY_AUTO, false, true); } } else @@ -1288,6 +1340,129 @@ index_create(Relation heapRelation, return indexRelationId; } +typedef struct do_collation_version_check_context +{ + Oid relid; + List *warned_colls; +} do_collation_version_check_context; + +/* + * Raise a warning if the recorded and current collation version don't match. + * This is a callback for visitDependenciesOf(). + */ +static bool +do_collation_version_check(const ObjectAddress *otherObject, + const char *version, + char **new_version, + void *data) +{ + do_collation_version_check_context *context = data; + char *current_version; + + /* We only care about dependencies on collations with a version. */ + if (!version || otherObject->classId != CollationRelationId) + return false; + + /* Ask the provider for the current version. Give up if unsupported. */ + current_version = get_collation_version_for_oid(otherObject->objectId); + if (!current_version) + return false; + + /* + * We don't expect too many duplicates, but it's possible, and we don't + * want to generate duplicate warnings. + */ + if (list_member_oid(context->warned_colls, otherObject->objectId)) + return false; + + /* Do they match? */ + if (strcmp(current_version, version) != 0) + { + /* + * The version has changed, probably due to an OS/library upgrade or + * streaming replication between different OS/library versions. + */ + ereport(WARNING, + (errmsg("index \"%s\" depends on collation \"%s\" version \"%s\", but the current version is \"%s\"", + get_rel_name(context->relid), + get_collation_name(otherObject->objectId), + version, + current_version), + errdetail("The index may be corrupted due to changes in sort order."), + errhint("REINDEX to avoid the risk of corruption."))); + + /* Remember not to complain about this collation again. */ + context->warned_colls = lappend_oid(context->warned_colls, + otherObject->objectId); + } + + return false; +} + +/* index_check_collation_versions + * Check the collation version for all dependencies on the given index. + */ +void +index_check_collation_versions(Oid relid) +{ + do_collation_version_check_context context; + ObjectAddress object; + + /* + * The callback needs the relid for error messages, and some scratch space + * to avoid duplicate warnings. + */ + context.relid = relid; + context.warned_colls = NIL; + + object.classId = RelationRelationId; + object.objectId = relid; + object.objectSubId = 0; + + visitDependenciesOf(&object, &do_collation_version_check, &context); + + list_free(context.warned_colls); +} + +/* + * Update the version for collations. A callback for visitDependenciesOf(). + */ +static bool +do_collation_version_update(const ObjectAddress *otherObject, + const char *version, + char **new_version, + void *data) +{ + Oid *coll = data; + + /* We only care about dependencies on collations with versions. */ + if (!version || otherObject->classId != CollationRelationId) + return false; + + /* If we're only trying to update one collation, skip others. */ + if (OidIsValid(*coll) && otherObject->objectId != *coll) + return false; + + *new_version = get_collation_version_for_oid(otherObject->objectId); + + return true; +} + +/* + * Record the current versions of one or all collations that an index depends + * on. InvalidOid means all. + */ +void +index_update_collation_versions(Oid relid, Oid coll) +{ + ObjectAddress object; + + object.classId = RelationRelationId; + object.objectId = relid; + object.objectSubId = 0; + visitDependenciesOf(&object, &do_collation_version_update, &coll); +} + /* * index_concurrently_create_copy * @@ -1588,7 +1763,6 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName) /* Preserve indisreplident in the new index */ newIndexForm->indisreplident = oldIndexForm->indisreplident; - oldIndexForm->indisreplident = false; /* Preserve indisclustered in the new index */ newIndexForm->indisclustered = oldIndexForm->indisclustered; @@ -1600,6 +1774,7 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName) newIndexForm->indisvalid = true; oldIndexForm->indisvalid = false; oldIndexForm->indisclustered = false; + oldIndexForm->indisreplident = false; CatalogTupleUpdate(pg_index, &oldIndexTuple->t_self, oldIndexTuple); CatalogTupleUpdate(pg_index, &newIndexTuple->t_self, newIndexTuple); @@ -1748,6 +1923,10 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName) changeDependenciesOf(RelationRelationId, oldIndexId, newIndexId); changeDependenciesOn(RelationRelationId, oldIndexId, newIndexId); + /* Now we have the old index's collation versions, so fix that. */ + CommandCounterIncrement(); + index_update_collation_versions(newIndexId, InvalidOid); + /* * Copy over statistics from old to new index */ @@ -1773,6 +1952,9 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName) } } + /* Copy data of pg_statistic from the old index to the new one */ + CopyStatistics(oldIndexId, newIndexId); + /* Close relations */ table_close(pg_class, RowExclusiveLock); table_close(pg_index, RowExclusiveLock); @@ -2803,6 +2985,15 @@ index_update_stats(Relation rel, /* Should this be a more comprehensive test? */ Assert(rd_rel->relkind != RELKIND_PARTITIONED_INDEX); + /* + * As a special hack, if we are dealing with an empty table and the + * existing reltuples is -1, we leave that alone. This ensures that + * creating an index as part of CREATE TABLE doesn't cause the table to + * prematurely look like it's been vacuumed. + */ + if (reltuples == 0 && rd_rel->reltuples < 0) + reltuples = -1; + /* Apply required updates, if any, to copied tuple */ dirty = false; @@ -3384,18 +3575,10 @@ validate_index_callback(ItemPointer itemptr, void *opaque) * index_set_state_flags - adjust pg_index state flags * * This is used during CREATE/DROP INDEX CONCURRENTLY to adjust the pg_index - * flags that denote the index's state. Because the update is not - * transactional and will not roll back on error, this must only be used as - * the last step in a transaction that has not made any transactional catalog - * updates! + * flags that denote the index's state. * - * Note that heap_inplace_update does send a cache inval message for the + * Note that CatalogTupleUpdate() sends a cache invalidation message for the * tuple, so other sessions will hear about the update as soon as we commit. - * - * NB: In releases prior to PostgreSQL 9.4, the use of a non-transactional - * update here would have been unsafe; now that MVCC rules apply even for - * system catalog scans, we could potentially use a transactional update here - * instead. */ void index_set_state_flags(Oid indexId, IndexStateFlagsAction action) @@ -3404,9 +3587,6 @@ index_set_state_flags(Oid indexId, IndexStateFlagsAction action) HeapTuple indexTuple; Form_pg_index indexForm; - /* Assert that current xact hasn't done any transactional updates */ - Assert(GetTopTransactionIdIfAny() == InvalidTransactionId); - /* Open pg_index and fetch a writable copy of the index's tuple */ pg_index = table_open(IndexRelationId, RowExclusiveLock); @@ -3445,10 +3625,13 @@ index_set_state_flags(Oid indexId, IndexStateFlagsAction action) * CONCURRENTLY that failed partway through.) * * Note: the CLUSTER logic assumes that indisclustered cannot be - * set on any invalid index, so clear that flag too. + * set on any invalid index, so clear that flag too. Similarly, + * ALTER TABLE assumes that indisreplident cannot be set for + * invalid indexes. */ indexForm->indisvalid = false; indexForm->indisclustered = false; + indexForm->indisreplident = false; break; case INDEX_DROP_SET_DEAD: @@ -3460,13 +3643,15 @@ index_set_state_flags(Oid indexId, IndexStateFlagsAction action) * the index at all. */ Assert(!indexForm->indisvalid); + Assert(!indexForm->indisclustered); + Assert(!indexForm->indisreplident); indexForm->indisready = false; indexForm->indislive = false; break; } - /* ... and write it back in-place */ - heap_inplace_update(pg_index, indexTuple); + /* ... and update it */ + CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple); table_close(pg_index, RowExclusiveLock); } @@ -3522,8 +3707,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, * Open and lock the parent heap relation. ShareLock is sufficient since * we only need to be sure no schema or data changes are going on. */ - heapId = IndexGetRelation(indexId, false); - heapRelation = table_open(heapId, ShareLock); + heapId = IndexGetRelation(indexId, + (options & REINDEXOPT_MISSING_OK) != 0); + /* if relation is missing, leave */ + if (!OidIsValid(heapId)) + return; + + if ((options & REINDEXOPT_MISSING_OK) != 0) + heapRelation = try_table_open(heapId, ShareLock, false); + else + heapRelation = table_open(heapId, ShareLock); + + /* if relation is gone, leave */ + if (!heapRelation) + return; if (progress) { @@ -3755,6 +3952,9 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, /* Close rels, but keep locks */ index_close(iRel, NoLock); table_close(heapRelation, NoLock); + + /* Record the current versions of all depended-on collations. */ + index_update_collation_versions(indexId, InvalidOid); } /* @@ -3814,7 +4014,14 @@ reindex_relation(Oid relid, int flags, int options) * to prevent schema and data changes in it. The lock level used here * should match ReindexTable(). */ - rel = table_open(relid, ShareLock); + if ((options & REINDEXOPT_MISSING_OK) != 0) + rel = try_table_open(relid, ShareLock, false); + else + rel = table_open(relid, ShareLock); + + /* if relation is gone, leave */ + if (!rel) + return false; /* * Partitioned tables should never get processed here, as they have no @@ -3910,7 +4117,14 @@ reindex_relation(Oid relid, int flags, int options) * still hold the lock on the main table. */ if ((flags & REINDEX_REL_PROCESS_TOAST) && OidIsValid(toast_relid)) - result |= reindex_relation(toast_relid, flags, options); + { + /* + * Note that this should fail if the toast relation is missing, so + * reset REINDEXOPT_MISSING_OK. + */ + result |= reindex_relation(toast_relid, flags, + options & ~(REINDEXOPT_MISSING_OK)); + } /* Obtain the aoseg_relid and aoblkdir_relid if the relation is an AO table. */ if ((flags & REINDEX_REL_PROCESS_TOAST) && relIsAO) diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 62b9892ffbb7..c91e11c76116 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -172,6 +172,8 @@ static Oid namespaceUser = InvalidOid; /* The above four values are valid only if baseSearchPathValid */ static bool baseSearchPathValid = true; +static bool before_shmem_exit_callback_registered = false; + /* Override requests are remembered in a stack of OverrideStackEntry structs */ typedef struct @@ -1497,8 +1499,7 @@ FunctionIsVisible(Oid funcid) * Given a possibly-qualified operator name and exact input datatypes, * look up the operator. Returns InvalidOid if not found. * - * Pass oprleft = InvalidOid for a prefix op, oprright = InvalidOid for - * a postfix op. + * Pass oprleft = InvalidOid for a prefix op. * * If the operator name is not schema-qualified, it is sought in the current * namespace search path. If the name is schema-qualified and the given @@ -1604,8 +1605,8 @@ OpernameGetOprid(List *names, Oid oprleft, Oid oprright) * namespace case, we arrange for entries in earlier namespaces to mask * identical entries in later namespaces. * - * The returned items always have two args[] entries --- one or the other - * will be InvalidOid for a prefix or postfix oprkind. nargs is 2, too. + * The returned items always have two args[] entries --- the first will be + * InvalidOid for a prefix oprkind. nargs is always 2, too. */ FuncCandidateList OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok) @@ -3942,7 +3943,7 @@ recomputeNamespacePath(void) /* * We want to detect the case where the effective value of the base search * path variables didn't change. As long as we're doing so, we can avoid - * copying the OID list unncessarily. + * copying the OID list unnecessarily. */ if (baseCreationNamespace == firstNS && baseTempCreationPending == temp_missing && @@ -4306,10 +4307,14 @@ ResetTempNamespace(void) /* * MPP-19973: The shmem exit callback to remove a temp - * namespace is registered. We need to remove it here as the + * namespace may be registered. We need to remove it here as the * namespace has already been reseted. */ - cancel_before_shmem_exit(RemoveTempRelationsCallback, 0); + if (before_shmem_exit_callback_registered) + { + before_shmem_exit_callback_registered = false; + cancel_before_shmem_exit(RemoveTempRelationsCallback, 0); + } myTempNamespace = InvalidOid; myTempToastNamespace = InvalidOid; @@ -4336,7 +4341,10 @@ AtEOXact_Namespace(bool isCommit, bool parallel) if (myTempNamespaceSubID != InvalidSubTransactionId && !parallel) { if (isCommit) + { before_shmem_exit(RemoveTempRelationsCallback, 0); + before_shmem_exit_callback_registered = true; + } else { myTempNamespace = InvalidOid; @@ -4519,6 +4527,8 @@ RemoveSchemaById(Oid schemaOid) static void RemoveTempRelationsCallback(int code, Datum arg) { + before_shmem_exit_callback_registered = false; + if (DistributedTransactionContext == DTX_CONTEXT_QE_PREPARED) { /* diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index c02b05a76258..456910f07a43 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -1545,7 +1545,7 @@ get_object_address_attribute(ObjectType objtype, List *object, ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("column name must be qualified"))); - attname = strVal(lfirst(list_tail(object))); + attname = strVal(llast(object)); relname = list_truncate(list_copy(object), list_length(object) - 1); /* XXX no missing_ok support here */ relation = relation_openrv(makeRangeVarFromNameList(relname), lockmode); diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index 2d3c7ea14c3e..2a421ddb89c9 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -106,6 +106,7 @@ AggregateCreate(const char *aggName, int i; ObjectAddress myself, referenced; + ObjectAddresses *addrs; AclResult aclresult; /* sanity checks (caller should have caught these) */ @@ -565,8 +566,8 @@ AggregateCreate(const char *aggName, ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("moving-aggregate implementation returns type %s, but plain implementation returns type %s", - format_type_be(aggmTransType), - format_type_be(aggTransType)))); + format_type_be(rettype), + format_type_be(finaltype)))); } /* handle sortop, if supplied */ @@ -745,66 +746,70 @@ AggregateCreate(const char *aggName, * way. */ + addrs = new_object_addresses(); + /* Depends on transition function */ ObjectAddressSet(referenced, ProcedureRelationId, transfn); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); /* Depends on final function, if any */ if (OidIsValid(finalfn)) { ObjectAddressSet(referenced, ProcedureRelationId, finalfn); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* Depends on combine function, if any */ if (OidIsValid(combinefn)) { ObjectAddressSet(referenced, ProcedureRelationId, combinefn); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* Depends on serialization function, if any */ if (OidIsValid(serialfn)) { ObjectAddressSet(referenced, ProcedureRelationId, serialfn); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* Depends on deserialization function, if any */ if (OidIsValid(deserialfn)) { ObjectAddressSet(referenced, ProcedureRelationId, deserialfn); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* Depends on forward transition function, if any */ if (OidIsValid(mtransfn)) { ObjectAddressSet(referenced, ProcedureRelationId, mtransfn); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* Depends on inverse transition function, if any */ if (OidIsValid(minvtransfn)) { ObjectAddressSet(referenced, ProcedureRelationId, minvtransfn); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* Depends on final function, if any */ if (OidIsValid(mfinalfn)) { ObjectAddressSet(referenced, ProcedureRelationId, mfinalfn); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* Depends on sort operator, if any */ if (OidIsValid(sortop)) { ObjectAddressSet(referenced, OperatorRelationId, sortop); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); return myself; } diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c index 6f020975a40c..29597ebd4430 100644 --- a/src/backend/catalog/pg_cast.c +++ b/src/backend/catalog/pg_cast.c @@ -3,7 +3,7 @@ * pg_cast.c * routines to support manipulation of the pg_cast relation * - * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * @@ -54,6 +54,7 @@ CastCreate(Oid sourcetypeid, Oid targettypeid, Oid funcid, char castcontext, bool nulls[Natts_pg_cast]; ObjectAddress myself, referenced; + ObjectAddresses *addrs; relation = table_open(CastRelationId, RowExclusiveLock); @@ -88,32 +89,29 @@ CastCreate(Oid sourcetypeid, Oid targettypeid, Oid funcid, char castcontext, CatalogTupleInsert(relation, tuple); + addrs = new_object_addresses(); + /* make dependency entries */ - myself.classId = CastRelationId; - myself.objectId = castid; - myself.objectSubId = 0; + ObjectAddressSet(myself, CastRelationId, castid); /* dependency on source type */ - referenced.classId = TypeRelationId; - referenced.objectId = sourcetypeid; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, behavior); + ObjectAddressSet(referenced, TypeRelationId, sourcetypeid); + add_exact_object_address(&referenced, addrs); /* dependency on target type */ - referenced.classId = TypeRelationId; - referenced.objectId = targettypeid; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, behavior); + ObjectAddressSet(referenced, TypeRelationId, targettypeid); + add_exact_object_address(&referenced, addrs); /* dependency on function */ if (OidIsValid(funcid)) { - referenced.classId = ProcedureRelationId; - referenced.objectId = funcid; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, behavior); + ObjectAddressSet(referenced, ProcedureRelationId, funcid); + add_exact_object_address(&referenced, addrs); } + record_object_address_dependencies(&myself, addrs, behavior); + free_object_addresses(addrs); + /* dependency on extension */ recordDependencyOnCurrentExtension(&myself, false); diff --git a/src/backend/catalog/pg_collation.c b/src/backend/catalog/pg_collation.c index c67cd43b1104..901afd07cd25 100644 --- a/src/backend/catalog/pg_collation.c +++ b/src/backend/catalog/pg_collation.c @@ -51,7 +51,6 @@ CollationCreate(const char *collname, Oid collnamespace, bool collisdeterministic, int32 collencoding, const char *collcollate, const char *collctype, - const char *collversion, bool if_not_exists, bool quiet) { @@ -187,10 +186,6 @@ CollationCreate(const char *collname, Oid collnamespace, values[Anum_pg_collation_collcollate - 1] = NameGetDatum(&name_collate); namestrcpy(&name_ctype, collctype); values[Anum_pg_collation_collctype - 1] = NameGetDatum(&name_ctype); - if (collversion) - values[Anum_pg_collation_collversion - 1] = CStringGetTextDatum(collversion); - else - nulls[Anum_pg_collation_collversion - 1] = true; tup = heap_form_tuple(tupDesc, values, nulls); diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 9df0435cb626..8d1ca35a3d50 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -93,6 +93,8 @@ CreateConstraintEntry(const char *constraintName, NameData cname; int i; ObjectAddress conobject; + ObjectAddresses *addrs_auto; + ObjectAddresses *addrs_normal; conDesc = table_open(ConstraintRelationId, RowExclusiveLock); @@ -230,6 +232,9 @@ CreateConstraintEntry(const char *constraintName, table_close(conDesc, RowExclusiveLock); + /* Handle set of auto dependencies */ + addrs_auto = new_object_addresses(); + if (OidIsValid(relId)) { /* @@ -244,13 +249,13 @@ CreateConstraintEntry(const char *constraintName, { ObjectAddressSubSet(relobject, RelationRelationId, relId, constraintKey[i]); - recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO); + add_exact_object_address(&relobject, addrs_auto); } } else { ObjectAddressSet(relobject, RelationRelationId, relId); - recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO); + add_exact_object_address(&relobject, addrs_auto); } } @@ -262,9 +267,16 @@ CreateConstraintEntry(const char *constraintName, ObjectAddress domobject; ObjectAddressSet(domobject, TypeRelationId, domainId); - recordDependencyOn(&conobject, &domobject, DEPENDENCY_AUTO); + add_exact_object_address(&domobject, addrs_auto); } + record_object_address_dependencies(&conobject, addrs_auto, + DEPENDENCY_AUTO); + free_object_addresses(addrs_auto); + + /* Handle set of normal dependencies */ + addrs_normal = new_object_addresses(); + if (OidIsValid(foreignRelId)) { /* @@ -279,13 +291,13 @@ CreateConstraintEntry(const char *constraintName, { ObjectAddressSubSet(relobject, RelationRelationId, foreignRelId, foreignKey[i]); - recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL); + add_exact_object_address(&relobject, addrs_normal); } } else { ObjectAddressSet(relobject, RelationRelationId, foreignRelId); - recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL); + add_exact_object_address(&relobject, addrs_normal); } } @@ -300,7 +312,7 @@ CreateConstraintEntry(const char *constraintName, ObjectAddress relobject; ObjectAddressSet(relobject, RelationRelationId, indexRelId); - recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL); + add_exact_object_address(&relobject, addrs_normal); } if (foreignNKeys > 0) @@ -319,20 +331,24 @@ CreateConstraintEntry(const char *constraintName, for (i = 0; i < foreignNKeys; i++) { oprobject.objectId = pfEqOp[i]; - recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL); + add_exact_object_address(&oprobject, addrs_normal); if (ppEqOp[i] != pfEqOp[i]) { oprobject.objectId = ppEqOp[i]; - recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL); + add_exact_object_address(&oprobject, addrs_normal); } if (ffEqOp[i] != pfEqOp[i]) { oprobject.objectId = ffEqOp[i]; - recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL); + add_exact_object_address(&oprobject, addrs_normal); } } } + record_object_address_dependencies(&conobject, addrs_normal, + DEPENDENCY_NORMAL); + free_object_addresses(addrs_normal); + /* * We don't bother to register dependencies on the exclusion operators of * an exclusion constraint. We assume they are members of the opclass @@ -349,7 +365,7 @@ CreateConstraintEntry(const char *constraintName, */ recordDependencyOnSingleRelExpr(&conobject, conExpr, relId, DEPENDENCY_NORMAL, - DEPENDENCY_NORMAL, false); + DEPENDENCY_NORMAL, false, true); } /* Post creation hook for new constraint */ diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c index 2655f73eaed4..57848d1986f9 100644 --- a/src/backend/catalog/pg_depend.c +++ b/src/backend/catalog/pg_depend.c @@ -19,13 +19,16 @@ #include "access/table.h" #include "catalog/dependency.h" #include "catalog/indexing.h" +#include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" #include "catalog/pg_extension.h" #include "commands/extension.h" #include "miscadmin.h" +#include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/pg_locale.h" #include "utils/rel.h" @@ -44,25 +47,33 @@ recordDependencyOn(const ObjectAddress *depender, const ObjectAddress *referenced, DependencyType behavior) { - recordMultipleDependencies(depender, referenced, 1, behavior); + recordMultipleDependencies(depender, referenced, 1, behavior, false); } /* * Record multiple dependencies (of the same kind) for a single dependent * object. This has a little less overhead than recording each separately. + * + * If record_version is true, then a record is added even if the referenced + * object is pinned, and the dependency version will be retrieved according to + * the referenced object kind. For now, only collation version is + * supported. */ void recordMultipleDependencies(const ObjectAddress *depender, const ObjectAddress *referenced, int nreferenced, - DependencyType behavior) + DependencyType behavior, + bool record_version) { Relation dependDesc; CatalogIndexState indstate; - HeapTuple tup; - int i; - bool nulls[Natts_pg_depend]; - Datum values[Natts_pg_depend]; + TupleTableSlot **slot; + int i, + max_slots, + slot_init_count, + slot_stored_count; + char *version = NULL; if (nreferenced <= 0) return; /* nothing to do */ @@ -76,50 +87,121 @@ recordMultipleDependencies(const ObjectAddress *depender, dependDesc = table_open(DependRelationId, RowExclusiveLock); + /* + * Allocate the slots to use, but delay costly initialization until we + * know that they will be used. + */ + max_slots = Min(nreferenced, + MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_depend)); + slot = palloc(sizeof(TupleTableSlot *) * max_slots); + /* Don't open indexes unless we need to make an update */ indstate = NULL; - memset(nulls, false, sizeof(nulls)); - + /* number of slots currently storing tuples */ + slot_stored_count = 0; + /* number of slots currently initialized */ + slot_init_count = 0; for (i = 0; i < nreferenced; i++, referenced++) { + bool ignore_systempin = false; + + if (record_version) + { + /* For now we only know how to deal with collations. */ + if (referenced->classId == CollationRelationId) + { + /* C and POSIX don't need version tracking. */ + if (referenced->objectId == C_COLLATION_OID || + referenced->objectId == POSIX_COLLATION_OID) + continue; + + version = get_collation_version_for_oid(referenced->objectId); + + /* + * Default collation is pinned, so we need to force recording + * the dependency to store the version. + */ + if (referenced->objectId == DEFAULT_COLLATION_OID) + ignore_systempin = true; + } + } + else + Assert(!version); + /* * If the referenced object is pinned by the system, there's no real - * need to record dependencies on it. This saves lots of space in - * pg_depend, so it's worth the time taken to check. + * need to record dependencies on it, unless we need to record a + * version. This saves lots of space in pg_depend, so it's worth the + * time taken to check. */ - if (!isObjectPinned(referenced, dependDesc)) + if (!ignore_systempin && isObjectPinned(referenced, dependDesc)) + continue; + + if (slot_init_count < max_slots) { - /* - * Record the Dependency. Note we don't bother to check for - * duplicate dependencies; there's no harm in them. - */ - values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId); - values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId); - values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId); + slot[slot_stored_count] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc), + &TTSOpsHeapTuple); + slot_init_count++; + } - values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId); - values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId); - values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId); + ExecClearTuple(slot[slot_stored_count]); - values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior); + /* + * Record the dependency. Note we don't bother to check for duplicate + * dependencies; there's no harm in them. + */ + memset(slot[slot_stored_count]->tts_isnull, false, + slot[slot_stored_count]->tts_tupleDescriptor->natts * sizeof(bool)); + + slot[slot_stored_count]->tts_values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId); + slot[slot_stored_count]->tts_values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId); + slot[slot_stored_count]->tts_values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId); + slot[slot_stored_count]->tts_values[Anum_pg_depend_deptype - 1] = CharGetDatum((char) behavior); + slot[slot_stored_count]->tts_values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId); + slot[slot_stored_count]->tts_values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId); + slot[slot_stored_count]->tts_values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId); + if (version) + slot[slot_stored_count]->tts_values[Anum_pg_depend_refobjversion - 1] = CStringGetTextDatum(version); + else + slot[slot_stored_count]->tts_isnull[Anum_pg_depend_refobjversion - 1] = true; - tup = heap_form_tuple(dependDesc->rd_att, values, nulls); + ExecStoreVirtualTuple(slot[slot_stored_count]); + slot_stored_count++; + /* If slots are full, insert a batch of tuples */ + if (slot_stored_count == max_slots) + { /* fetch index info only when we know we need it */ if (indstate == NULL) indstate = CatalogOpenIndexes(dependDesc); - CatalogTupleInsertWithInfo(dependDesc, tup, indstate); - - heap_freetuple(tup); + CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slot_stored_count, + indstate); + slot_stored_count = 0; } } + /* Insert any tuples left in the buffer */ + if (slot_stored_count > 0) + { + /* fetch index info only when we know we need it */ + if (indstate == NULL) + indstate = CatalogOpenIndexes(dependDesc); + + CatalogTuplesMultiInsertWithInfo(dependDesc, slot, slot_stored_count, + indstate); + } + if (indstate != NULL) CatalogCloseIndexes(indstate); table_close(dependDesc, RowExclusiveLock); + + /* Drop only the number of slots used */ + for (i = 0; i < slot_init_count; i++) + ExecDropSingleTupleTableSlot(slot[i]); + pfree(slot); } /* @@ -542,7 +624,7 @@ changeDependenciesOf(Oid classId, Oid oldObjectId, while (HeapTupleIsValid((tup = systable_getnext(scan)))) { - Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + Form_pg_depend depform; /* make a modifiable copy */ tup = heap_copytuple(tup); @@ -625,12 +707,12 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId, while (HeapTupleIsValid((tup = systable_getnext(scan)))) { - Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); - if (newIsPinned) CatalogTupleDelete(depRel, &tup->t_self); else { + Form_pg_depend depform; + /* make a modifiable copy */ tup = heap_copytuple(tup); depform = (Form_pg_depend) GETSTRUCT(tup); diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c index 34403eda2426..5afc6210b141 100644 --- a/src/backend/catalog/pg_enum.c +++ b/src/backend/catalog/pg_enum.c @@ -146,7 +146,7 @@ EnumValuesCreate(Oid enumTypeOid, List *vals) ereport(ERROR, (errcode(ERRCODE_INVALID_NAME), errmsg("invalid enum label \"%s\"", lab), - errdetail("Labels must be %d characters or less.", + errdetail("Labels must be %d bytes or less.", NAMEDATALEN - 1))); values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(oids[elemno]); @@ -249,7 +249,7 @@ AddEnumLabel(Oid enumTypeOid, ereport(ERROR, (errcode(ERRCODE_INVALID_NAME), errmsg("invalid enum label \"%s\"", newVal), - errdetail("Labels must be %d characters or less.", + errdetail("Labels must be %d bytes or less.", NAMEDATALEN - 1))); /* @@ -533,7 +533,7 @@ RenameEnumLabel(Oid enumTypeOid, ereport(ERROR, (errcode(ERRCODE_INVALID_NAME), errmsg("invalid enum label \"%s\"", newVal), - errdetail("Labels must be %d characters or less.", + errdetail("Labels must be %d bytes or less.", NAMEDATALEN - 1))); /* diff --git a/src/backend/catalog/pg_namespace.c b/src/backend/catalog/pg_namespace.c index d41196c70e48..0f2ae0aa94db 100644 --- a/src/backend/catalog/pg_namespace.c +++ b/src/backend/catalog/pg_namespace.c @@ -109,7 +109,7 @@ NamespaceCreate(const char *nspName, Oid ownerId, bool isTemp) /* dependency on owner */ recordDependencyOnOwner(NamespaceRelationId, nspoid, ownerId); - /* dependences on roles mentioned in default ACL */ + /* dependencies on roles mentioned in default ACL */ recordDependencyOnNewAcl(NamespaceRelationId, nspoid, 0, ownerId, nspacl); /* dependency on extension ... but not for magic temp schemas */ diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c index a65e9a49226f..df76da1068d5 100644 --- a/src/backend/catalog/pg_operator.c +++ b/src/backend/catalog/pg_operator.c @@ -250,7 +250,7 @@ OperatorShellMake(const char *operatorName, values[Anum_pg_operator_oprname - 1] = NameGetDatum(&oname); values[Anum_pg_operator_oprnamespace - 1] = ObjectIdGetDatum(operatorNamespace); values[Anum_pg_operator_oprowner - 1] = ObjectIdGetDatum(GetUserId()); - values[Anum_pg_operator_oprkind - 1] = CharGetDatum(leftTypeId ? (rightTypeId ? 'b' : 'r') : 'l'); + values[Anum_pg_operator_oprkind - 1] = CharGetDatum(leftTypeId ? 'b' : 'l'); values[Anum_pg_operator_oprcanmerge - 1] = BoolGetDatum(false); values[Anum_pg_operator_oprcanhash - 1] = BoolGetDatum(false); values[Anum_pg_operator_oprleft - 1] = ObjectIdGetDatum(leftTypeId); @@ -499,7 +499,7 @@ OperatorCreate(const char *operatorName, values[Anum_pg_operator_oprname - 1] = NameGetDatum(&oname); values[Anum_pg_operator_oprnamespace - 1] = ObjectIdGetDatum(operatorNamespace); values[Anum_pg_operator_oprowner - 1] = ObjectIdGetDatum(GetUserId()); - values[Anum_pg_operator_oprkind - 1] = CharGetDatum(leftTypeId ? (rightTypeId ? 'b' : 'r') : 'l'); + values[Anum_pg_operator_oprkind - 1] = CharGetDatum(leftTypeId ? 'b' : 'l'); values[Anum_pg_operator_oprcanmerge - 1] = BoolGetDatum(canMerge); values[Anum_pg_operator_oprcanhash - 1] = BoolGetDatum(canHash); values[Anum_pg_operator_oprleft - 1] = ObjectIdGetDatum(leftTypeId); @@ -782,6 +782,7 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate) Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple); ObjectAddress myself, referenced; + ObjectAddresses *addrs; ObjectAddressSet(myself, OperatorRelationId, oper->oid); @@ -795,32 +796,34 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate) deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0); } + addrs = new_object_addresses(); + /* Dependency on namespace */ if (OidIsValid(oper->oprnamespace)) { ObjectAddressSet(referenced, NamespaceRelationId, oper->oprnamespace); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* Dependency on left type */ if (OidIsValid(oper->oprleft)) { ObjectAddressSet(referenced, TypeRelationId, oper->oprleft); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* Dependency on right type */ if (OidIsValid(oper->oprright)) { ObjectAddressSet(referenced, TypeRelationId, oper->oprright); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* Dependency on result type */ if (OidIsValid(oper->oprresult)) { ObjectAddressSet(referenced, TypeRelationId, oper->oprresult); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* @@ -836,23 +839,26 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate) if (OidIsValid(oper->oprcode)) { ObjectAddressSet(referenced, ProcedureRelationId, oper->oprcode); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* Dependency on restriction selectivity function */ if (OidIsValid(oper->oprrest)) { ObjectAddressSet(referenced, ProcedureRelationId, oper->oprrest); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* Dependency on join selectivity function */ if (OidIsValid(oper->oprjoin)) { ObjectAddressSet(referenced, ProcedureRelationId, oper->oprjoin); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); + /* Dependency on owner */ recordDependencyOnOwner(OperatorRelationId, oper->oid, oper->oprowner); diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index ed80f447a179..bfaa5ce18caa 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -125,6 +125,7 @@ ProcedureCreate(const char *procedureName, char *detailmsg; int i; Oid trfid; + ObjectAddresses *addrs; /* * sanity checks @@ -259,6 +260,9 @@ ProcedureCreate(const char *procedureName, elog(ERROR, "variadic parameter must be last"); break; case PROARGMODE_OUT: + if (OidIsValid(variadicType) && prokind == PROKIND_PROCEDURE) + elog(ERROR, "variadic parameter must be last"); + break; case PROARGMODE_TABLE: /* okay */ break; @@ -475,10 +479,12 @@ ProcedureCreate(const char *procedureName, if (isnull) proargmodes = PointerGetDatum(NULL); /* just to be sure */ - n_old_arg_names = get_func_input_arg_names(proargnames, + n_old_arg_names = get_func_input_arg_names(prokind, + proargnames, proargmodes, &old_arg_names); - n_new_arg_names = get_func_input_arg_names(parameterNames, + n_new_arg_names = get_func_input_arg_names(prokind, + parameterNames, parameterModes, &new_arg_names); for (j = 0; j < n_old_arg_names; j++) @@ -650,19 +656,21 @@ ProcedureCreate(const char *procedureName, deleteProcCallbacks(retval); } + addrs = new_object_addresses(); + ObjectAddressSet(myself, ProcedureRelationId, retval); /* dependency on namespace */ ObjectAddressSet(referenced, NamespaceRelationId, procNamespace); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); /* dependency on implementation language */ ObjectAddressSet(referenced, LanguageRelationId, languageObjectId); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); /* dependency on return type */ ObjectAddressSet(referenced, TypeRelationId, returnType); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); /* dependency on describe function */ if (OidIsValid(describeFuncOid)) @@ -678,35 +686,38 @@ ProcedureCreate(const char *procedureName, if ((trfid = get_transform_oid(returnType, languageObjectId, true))) { ObjectAddressSet(referenced, TransformRelationId, trfid); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } /* dependency on parameter types */ for (i = 0; i < allParamCount; i++) { ObjectAddressSet(referenced, TypeRelationId, allParams[i]); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); /* dependency on transform used by parameter type, if any */ if ((trfid = get_transform_oid(allParams[i], languageObjectId, true))) { ObjectAddressSet(referenced, TransformRelationId, trfid); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } } - /* dependency on parameter default expressions */ - if (parameterDefaults) - recordDependencyOnExpr(&myself, (Node *) parameterDefaults, - NIL, DEPENDENCY_NORMAL); - /* dependency on support function, if any */ if (OidIsValid(prosupport)) { ObjectAddressSet(referenced, ProcedureRelationId, prosupport); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); + + /* dependency on parameter default expressions */ + if (parameterDefaults) + recordDependencyOnExpr(&myself, (Node *) parameterDefaults, + NIL, DEPENDENCY_NORMAL); + /* dependency on owner */ if (!is_update) recordDependencyOnOwner(ProcedureRelationId, retval, proowner); @@ -977,8 +988,8 @@ fmgr_sql_validator(PG_FUNCTION_ARGS) (ParserSetupHook) sql_fn_parser_setup, pinfo, NULL); - querytree_list = list_concat(querytree_list, - querytree_sublist); + querytree_list = lappend(querytree_list, + querytree_sublist); } check_sql_fn_statements(querytree_list); diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c index b5bc36c2bd6b..a606d8c3adb4 100644 --- a/src/backend/catalog/pg_range.c +++ b/src/backend/catalog/pg_range.c @@ -43,6 +43,7 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation, HeapTuple tup; ObjectAddress myself; ObjectAddress referenced; + ObjectAddresses *addrs; pg_range = table_open(RangeRelationId, RowExclusiveLock); @@ -61,45 +62,37 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation, heap_freetuple(tup); /* record type's dependencies on range-related items */ + addrs = new_object_addresses(); - myself.classId = TypeRelationId; - myself.objectId = rangeTypeOid; - myself.objectSubId = 0; + ObjectAddressSet(myself, TypeRelationId, rangeTypeOid); - referenced.classId = TypeRelationId; - referenced.objectId = rangeSubType; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, TypeRelationId, rangeSubType); + add_exact_object_address(&referenced, addrs); - referenced.classId = OperatorClassRelationId; - referenced.objectId = rangeSubOpclass; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, OperatorClassRelationId, rangeSubOpclass); + add_exact_object_address(&referenced, addrs); if (OidIsValid(rangeCollation)) { - referenced.classId = CollationRelationId; - referenced.objectId = rangeCollation; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, CollationRelationId, rangeCollation); + add_exact_object_address(&referenced, addrs); } if (OidIsValid(rangeCanonical)) { - referenced.classId = ProcedureRelationId; - referenced.objectId = rangeCanonical; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, ProcedureRelationId, rangeCanonical); + add_exact_object_address(&referenced, addrs); } if (OidIsValid(rangeSubDiff)) { - referenced.classId = ProcedureRelationId; - referenced.objectId = rangeSubDiff; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, ProcedureRelationId, rangeSubDiff); + add_exact_object_address(&referenced, addrs); } + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); + table_close(pg_range, RowExclusiveLock); } diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c index 30b234e90e12..3dd7afd34339 100644 --- a/src/backend/catalog/pg_shdepend.c +++ b/src/backend/catalog/pg_shdepend.c @@ -786,12 +786,6 @@ checkSharedDependencies(Oid classId, Oid objectId, } -/* - * Cap the maximum amount of bytes allocated for copyTemplateDependencies() - * slots. - */ -#define MAX_PGSHDEPEND_INSERT_BYTES 65535 - /* * copyTemplateDependencies * @@ -806,21 +800,20 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId) ScanKeyData key[1]; SysScanDesc scan; HeapTuple tup; - int slotCount; CatalogIndexState indstate; TupleTableSlot **slot; - int nslots, - max_slots; - bool slot_init = true; + int max_slots, + slot_init_count, + slot_stored_count; sdepRel = table_open(SharedDependRelationId, RowExclusiveLock); sdepDesc = RelationGetDescr(sdepRel); /* - * Allocate the slots to use, but delay initialization until we know that - * they will be used. + * Allocate the slots to use, but delay costly initialization until we + * know that they will be used. */ - max_slots = MAX_PGSHDEPEND_INSERT_BYTES / sizeof(FormData_pg_shdepend); + max_slots = MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_shdepend); slot = palloc(sizeof(TupleTableSlot *) * max_slots); indstate = CatalogOpenIndexes(sdepRel); @@ -834,6 +827,11 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId) scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true, NULL, 1, key); + /* number of slots currently storing tuples */ + slot_stored_count = 0; + /* number of slots currently initialized */ + slot_init_count = 0; + /* * Copy the entries of the original database, changing the database Id to * that of the new database. Note that because we are not copying rows @@ -841,41 +839,42 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId) * copy the ownership dependency of the template database itself; this is * what we want. */ - slotCount = 0; while (HeapTupleIsValid(tup = systable_getnext(scan))) { Form_pg_shdepend shdep; - if (slot_init) - slot[slotCount] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple); + if (slot_init_count < max_slots) + { + slot[slot_stored_count] = MakeSingleTupleTableSlot(sdepDesc, &TTSOpsHeapTuple); + slot_init_count++; + } - ExecClearTuple(slot[slotCount]); + ExecClearTuple(slot[slot_stored_count]); shdep = (Form_pg_shdepend) GETSTRUCT(tup); - slot[slotCount]->tts_values[Anum_pg_shdepend_dbid] = ObjectIdGetDatum(newDbId); - slot[slotCount]->tts_values[Anum_pg_shdepend_classid] = shdep->classid; - slot[slotCount]->tts_values[Anum_pg_shdepend_objid] = shdep->objid; - slot[slotCount]->tts_values[Anum_pg_shdepend_objsubid] = shdep->objsubid; - slot[slotCount]->tts_values[Anum_pg_shdepend_refclassid] = shdep->refclassid; - slot[slotCount]->tts_values[Anum_pg_shdepend_refobjid] = shdep->refobjid; - slot[slotCount]->tts_values[Anum_pg_shdepend_deptype] = shdep->deptype; + slot[slot_stored_count]->tts_values[Anum_pg_shdepend_dbid] = ObjectIdGetDatum(newDbId); + slot[slot_stored_count]->tts_values[Anum_pg_shdepend_classid] = shdep->classid; + slot[slot_stored_count]->tts_values[Anum_pg_shdepend_objid] = shdep->objid; + slot[slot_stored_count]->tts_values[Anum_pg_shdepend_objsubid] = shdep->objsubid; + slot[slot_stored_count]->tts_values[Anum_pg_shdepend_refclassid] = shdep->refclassid; + slot[slot_stored_count]->tts_values[Anum_pg_shdepend_refobjid] = shdep->refobjid; + slot[slot_stored_count]->tts_values[Anum_pg_shdepend_deptype] = shdep->deptype; - ExecStoreVirtualTuple(slot[slotCount]); - slotCount++; + ExecStoreVirtualTuple(slot[slot_stored_count]); + slot_stored_count++; /* If slots are full, insert a batch of tuples */ - if (slotCount == max_slots) + if (slot_stored_count == max_slots) { - CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate); - slotCount = 0; - slot_init = false; + CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slot_stored_count, indstate); + slot_stored_count = 0; } } /* Insert any tuples left in the buffer */ - if (slotCount > 0) - CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slotCount, indstate); + if (slot_stored_count > 0) + CatalogTuplesMultiInsertWithInfo(sdepRel, slot, slot_stored_count, indstate); systable_endscan(scan); @@ -883,8 +882,7 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId) table_close(sdepRel, RowExclusiveLock); /* Drop only the number of slots used */ - nslots = slot_init ? slotCount : max_slots; - for (int i = 0; i < nslots; i++) + for (int i = 0; i < slot_init_count; i++) ExecDropSingleTupleTableSlot(slot[i]); pfree(slot); } diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c index 90bf5cf0c6de..ca78d395181a 100644 --- a/src/backend/catalog/pg_subscription.c +++ b/src/backend/catalog/pg_subscription.c @@ -66,6 +66,7 @@ GetSubscription(Oid subid, bool missing_ok) sub->owner = subform->subowner; sub->enabled = subform->subenabled; sub->binary = subform->subbinary; + sub->stream = subform->substream; /* Get conninfo */ datum = SysCacheGetAttr(SUBSCRIPTIONOID, @@ -327,20 +328,16 @@ UpdateSubscriptionRelState(Oid subid, Oid relid, char state, /* * Get state of subscription table. * - * Returns SUBREL_STATE_UNKNOWN when not found and missing_ok is true. + * Returns SUBREL_STATE_UNKNOWN when the table is not in the subscription. */ char -GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn, - bool missing_ok) +GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn) { - Relation rel; HeapTuple tup; char substate; bool isnull; Datum d; - rel = table_open(SubscriptionRelRelationId, AccessShareLock); - /* Try finding the mapping. */ tup = SearchSysCache2(SUBSCRIPTIONRELMAP, ObjectIdGetDatum(relid), @@ -348,22 +345,14 @@ GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn, if (!HeapTupleIsValid(tup)) { - if (missing_ok) - { - table_close(rel, AccessShareLock); - *sublsn = InvalidXLogRecPtr; - return SUBREL_STATE_UNKNOWN; - } - - elog(ERROR, "subscription table %u in subscription %u does not exist", - relid, subid); + *sublsn = InvalidXLogRecPtr; + return SUBREL_STATE_UNKNOWN; } /* Get the state. */ - d = SysCacheGetAttr(SUBSCRIPTIONRELMAP, tup, - Anum_pg_subscription_rel_srsubstate, &isnull); - Assert(!isnull); - substate = DatumGetChar(d); + substate = ((Form_pg_subscription_rel) GETSTRUCT(tup))->srsubstate; + + /* Get the LSN */ d = SysCacheGetAttr(SUBSCRIPTIONRELMAP, tup, Anum_pg_subscription_rel_srsublsn, &isnull); if (isnull) @@ -373,7 +362,6 @@ GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn, /* Cleanup */ ReleaseSysCache(tup); - table_close(rel, AccessShareLock); return substate; } diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index f58898ebc408..e6e4d092fc59 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -14,10 +14,8 @@ */ #include "postgres.h" -#include "access/genam.h" -#include "access/heapam.h" #include "access/htup_details.h" -#include "access/reloptions.h" +#include "access/relation.h" #include "access/table.h" #include "access/xact.h" #include "catalog/binary_upgrade.h" @@ -33,7 +31,6 @@ #include "commands/typecmds.h" #include "miscadmin.h" #include "parser/scansup.h" -#include "parser/parse_type.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -41,8 +38,12 @@ #include "utils/rel.h" #include "utils/syscache.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "access/reloptions.h" #include "catalog/pg_type_encoding.h" #include "cdb/cdbvars.h" +#include "parser/parse_type.h" /* * Record a type's default encoding clause in the catalog. @@ -653,6 +654,65 @@ TypeCreate(Oid newTypeOid, return address; } +/* + * Get a list of all distinct collations that the given type depends on. + */ +List * +GetTypeCollations(Oid typeoid) +{ + List *result = NIL; + HeapTuple tuple; + Form_pg_type typeTup; + + tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for type %u", typeoid); + typeTup = (Form_pg_type) GETSTRUCT(tuple); + + if (OidIsValid(typeTup->typcollation)) + result = list_append_unique_oid(result, typeTup->typcollation); + else if (typeTup->typtype == TYPTYPE_COMPOSITE) + { + Relation rel = relation_open(typeTup->typrelid, AccessShareLock); + TupleDesc desc = RelationGetDescr(rel); + + for (int i = 0; i < RelationGetNumberOfAttributes(rel); i++) + { + Form_pg_attribute att = TupleDescAttr(desc, i); + + if (OidIsValid(att->attcollation)) + result = list_append_unique_oid(result, att->attcollation); + else + result = list_concat_unique_oid(result, + GetTypeCollations(att->atttypid)); + } + + relation_close(rel, NoLock); + } + else if (typeTup->typtype == TYPTYPE_DOMAIN) + { + Assert(OidIsValid(typeTup->typbasetype)); + + result = list_concat_unique_oid(result, + GetTypeCollations(typeTup->typbasetype)); + } + else if (typeTup->typtype == TYPTYPE_RANGE) + { + Oid rangeid = get_range_subtype(typeTup->oid); + + Assert(OidIsValid(rangeid)); + + result = list_concat_unique_oid(result, GetTypeCollations(rangeid)); + } + else if (OidIsValid(typeTup->typelem)) + result = list_concat_unique_oid(result, + GetTypeCollations(typeTup->typelem)); + + ReleaseSysCache(tuple); + + return result; +} + /* * GenerateTypeDependencies: build the dependencies needed for a type * @@ -694,6 +754,7 @@ GenerateTypeDependencies(HeapTuple typeTuple, bool isNull; ObjectAddress myself, referenced; + ObjectAddresses *addrs_normal; /* Extract defaultExpr if caller didn't pass it */ if (defaultExpr == NULL) @@ -727,6 +788,10 @@ GenerateTypeDependencies(HeapTuple typeTuple, * Skip these for a dependent type, since it will have such dependencies * indirectly through its depended-on type or relation. */ + + /* placeholder for all normal dependencies */ + addrs_normal = new_object_addresses(); + if (!isDependentType) { ObjectAddressSet(referenced, NamespaceRelationId, @@ -746,45 +811,70 @@ GenerateTypeDependencies(HeapTuple typeTuple, if (OidIsValid(typeForm->typinput)) { ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typinput); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs_normal); } if (OidIsValid(typeForm->typoutput)) { ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typoutput); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs_normal); } if (OidIsValid(typeForm->typreceive)) { ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typreceive); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs_normal); } if (OidIsValid(typeForm->typsend)) { ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typsend); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs_normal); } if (OidIsValid(typeForm->typmodin)) { ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typmodin); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs_normal); } if (OidIsValid(typeForm->typmodout)) { ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typmodout); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs_normal); } if (OidIsValid(typeForm->typanalyze)) { ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typanalyze); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs_normal); + } + + /* Normal dependency from a domain to its base type. */ + if (OidIsValid(typeForm->typbasetype)) + { + ObjectAddressSet(referenced, TypeRelationId, typeForm->typbasetype); + add_exact_object_address(&referenced, addrs_normal); + } + + /* + * Normal dependency from a domain to its collation. We know the default + * collation is pinned, so don't bother recording it. + */ + if (OidIsValid(typeForm->typcollation) && + typeForm->typcollation != DEFAULT_COLLATION_OID) + { + ObjectAddressSet(referenced, CollationRelationId, typeForm->typcollation); + add_exact_object_address(&referenced, addrs_normal); } + record_object_address_dependencies(&myself, addrs_normal, DEPENDENCY_NORMAL); + free_object_addresses(addrs_normal); + + /* Normal dependency on the default expression. */ + if (defaultExpr) + recordDependencyOnExpr(&myself, defaultExpr, NIL, DEPENDENCY_NORMAL); + /* * If the type is a rowtype for a relation, mark it as internally * dependent on the relation, *unless* it is a stand-alone composite type @@ -815,26 +905,6 @@ GenerateTypeDependencies(HeapTuple typeTuple, recordDependencyOn(&myself, &referenced, isImplicitArray ? DEPENDENCY_INTERNAL : DEPENDENCY_NORMAL); } - - /* Normal dependency from a domain to its base type. */ - if (OidIsValid(typeForm->typbasetype)) - { - ObjectAddressSet(referenced, TypeRelationId, typeForm->typbasetype); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); - } - - /* Normal dependency from a domain to its collation. */ - /* We know the default collation is pinned, so don't bother recording it */ - if (OidIsValid(typeForm->typcollation) && - typeForm->typcollation != DEFAULT_COLLATION_OID) - { - ObjectAddressSet(referenced, CollationRelationId, typeForm->typcollation); - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); - } - - /* Normal dependency on the default expression. */ - if (defaultExpr) - recordDependencyOnExpr(&myself, defaultExpr, NIL, DEPENDENCY_NORMAL); } /* diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index 12a89829d7e4..10cd392ac55b 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -449,7 +449,8 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst, smgrread(src, forkNum, blkno, buf.data); - if (!PageIsVerified(page, blkno)) + if (!PageIsVerifiedExtended(page, blkno, + PIV_LOG_WARNING | PIV_REPORT_STAT)) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid page in block %u of relation %s", @@ -608,7 +609,6 @@ smgrDoPendingDeletes(bool isCommit) PendingRelDelete *prev; PendingRelDelete *next; int nrels = 0, - i = 0, maxrels = 0; SMgrRelation *srels = NULL; @@ -664,7 +664,7 @@ smgrDoPendingDeletes(bool isCommit) { smgrdounlinkall(srels, nrels, false); - for (i = 0; i < nrels; i++) + for (int i = 0; i < nrels; i++) smgrclose(srels[i]); pfree(srels); diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 134e3edd6805..c29bcfc4eadc 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -564,6 +564,9 @@ REVOKE EXECUTE ON FUNCTION pg_get_shmem_allocations() FROM PUBLIC; CREATE VIEW pg_backend_memory_contexts AS SELECT * FROM pg_get_backend_memory_contexts(); +REVOKE ALL ON pg_backend_memory_contexts FROM PUBLIC; +REVOKE EXECUTE ON FUNCTION pg_get_backend_memory_contexts() FROM PUBLIC; + -- Statistics views CREATE VIEW pg_stat_all_tables_internal AS @@ -967,6 +970,18 @@ CREATE VIEW gp_stat_replication AS ON G.gp_segment_id = R.gp_segment_id ); +CREATE VIEW pg_stat_replication_slots AS + SELECT + s.slot_name, + s.spill_txns, + s.spill_count, + s.spill_bytes, + s.stream_txns, + s.stream_count, + s.stream_bytes, + s.stats_reset + FROM pg_stat_get_replication_slots() AS s; + CREATE VIEW pg_stat_slru AS SELECT s.name, @@ -1363,6 +1378,12 @@ CREATE VIEW pg_stat_bgwriter AS pg_stat_get_buf_alloc() AS buffers_alloc, pg_stat_get_bgwriter_stat_reset_time() AS stats_reset; +CREATE VIEW pg_stat_wal AS + SELECT + w.wal_buffers_full, + w.stats_reset + FROM pg_stat_get_wal() w; + CREATE VIEW pg_stat_progress_analyze AS SELECT S.pid AS pid, S.datid AS datid, D.datname AS datname, @@ -1512,7 +1533,7 @@ REVOKE ALL ON pg_replication_origin_status FROM public; -- All columns of pg_subscription except subconninfo are readable. REVOKE ALL ON pg_subscription FROM public; -GRANT SELECT (subdbid, subname, subowner, subenabled, subbinary, subslotname, subpublications) +GRANT SELECT (subdbid, subname, subowner, subenabled, subbinary, substream, subslotname, subpublications) ON pg_subscription TO public; @@ -1832,6 +1853,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_reset_shared(text) FROM public; REVOKE EXECUTE ON FUNCTION pg_stat_reset_slru(text) FROM public; REVOKE EXECUTE ON FUNCTION pg_stat_reset_single_table_counters(oid) FROM public; REVOKE EXECUTE ON FUNCTION pg_stat_reset_single_function_counters(oid) FROM public; +REVOKE EXECUTE ON FUNCTION pg_stat_reset_replication_slot(text) FROM public; REVOKE EXECUTE ON FUNCTION lo_import(text) FROM public; REVOKE EXECUTE ON FUNCTION lo_import(text, oid) FROM public; diff --git a/src/backend/cdb/cdblegacyhash.c b/src/backend/cdb/cdblegacyhash.c index 5a4b89f831e6..dd7f0d515160 100644 --- a/src/backend/cdb/cdblegacyhash.c +++ b/src/backend/cdb/cdblegacyhash.c @@ -234,7 +234,7 @@ get_legacy_cdbhash_opclass_for_base_type(Oid orig_typid) case OIDVECTOROID: opclass_name = "cdbhash_oidvector_ops"; break; - case CASHOID: + case MONEYOID: opclass_name = "cdbhash_cash_ops"; break; case UUIDOID: diff --git a/src/backend/cdb/cdbllize.c b/src/backend/cdb/cdbllize.c index e1787a07f604..579fee0f52aa 100644 --- a/src/backend/cdb/cdbllize.c +++ b/src/backend/cdb/cdbllize.c @@ -1177,7 +1177,8 @@ cdbllize_build_slice_table(PlannerInfo *root, Plan *top_plan, * list, because that would screw up the plan_id numbering of the * subplans). */ - pfree(lfirst(lc)); + if (lfirst(lc) != NULL) + pfree(lfirst(lc)); dummy_plan = (Plan *) make_result(NIL, (Node *) list_make1(makeBoolConst(false, false)), NULL); diff --git a/src/backend/cdb/cdbmutate.c b/src/backend/cdb/cdbmutate.c index 0562007cdbc4..3badfc7819b9 100644 --- a/src/backend/cdb/cdbmutate.c +++ b/src/backend/cdb/cdbmutate.c @@ -1133,7 +1133,7 @@ makeSegmentFilterExpr(int segid) make_opclause(Int4EqualOperator, BOOLOID, false, /* opretset */ - (Expr *) makeFuncExpr(F_MPP_EXECUTION_SEGMENT, + (Expr *) makeFuncExpr(F_GP_EXECUTION_SEGMENT, INT4OID, NIL, /* args */ InvalidOid, @@ -1603,8 +1603,8 @@ pre_dispatch_function_evaluation_mutator(Node *node, * xlog which will also flush any xlog writes that the sequence * server might do. */ - if (funcid == F_NEXTVAL_OID || funcid == F_CURRVAL_OID || - funcid == F_SETVAL_OID) + if (funcid == F_NEXTVAL || funcid == F_CURRVAL || + funcid == F_SETVAL_REGCLASS_INT8) { ExecutorMarkTransactionUsesSequences(); is_seq_func = true; diff --git a/src/backend/commands/analyzefuncs.c b/src/backend/commands/analyzefuncs.c index 6a4cdb545fef..6ea5f48d47c2 100644 --- a/src/backend/commands/analyzefuncs.c +++ b/src/backend/commands/analyzefuncs.c @@ -342,7 +342,7 @@ gp_acquire_sample_rows_col_type(Oid typid) */ return OIDOID; - case PGNODETREEOID: + case PG_NODE_TREEOID: /* * Input function of pg_node_tree doesn't allow loading * back values. Treat it as text. diff --git a/src/backend/commands/analyzeutils.c b/src/backend/commands/analyzeutils.c index 0246037277b8..737734de82c0 100644 --- a/src/backend/commands/analyzeutils.c +++ b/src/backend/commands/analyzeutils.c @@ -1216,7 +1216,7 @@ leaf_parts_analyzed(Oid attrelid, Oid relid_exclude, List *va_cols, int elevel) int32 relpages = get_rel_relpages(partRelid); /* Partition is not analyzed */ - if (relTuples == 0.0 && relpages == 0) + if (relTuples < 0.0) { if (relid_exclude == InvalidOid) ereport(elevel, @@ -1240,7 +1240,7 @@ leaf_parts_analyzed(Oid attrelid, Oid relid_exclude, List *va_cols, int elevel) float4 relTuples = get_rel_reltuples(partRelid); /* Partition is analyzed and we detect it is empty */ - if (relTuples == 0.0) + if (relTuples <= 0.0) continue; all_parts_empty = false; diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c index b63f56d5c76a..8e55730f9433 100644 --- a/src/backend/commands/async.c +++ b/src/backend/commands/async.c @@ -554,9 +554,8 @@ AsyncShmemInit(void) */ NotifyCtl->PagePrecedes = asyncQueuePagePrecedes; SimpleLruInit(NotifyCtl, "Notify", NUM_NOTIFY_BUFFERS, 0, - NotifySLRULock, "pg_notify", LWTRANCHE_NOTIFY_BUFFER); - /* Override default assumption that writes should be fsync'd */ - NotifyCtl->do_fsync = false; + NotifySLRULock, "pg_notify", LWTRANCHE_NOTIFY_BUFFER, + SYNC_HANDLER_NONE); if (!found) { @@ -1918,7 +1917,6 @@ static void asyncQueueReadAllNotifications(void) { volatile QueuePosition pos; - QueuePosition oldpos; QueuePosition head; Snapshot snapshot; @@ -1933,7 +1931,7 @@ asyncQueueReadAllNotifications(void) LWLockAcquire(NotifyQueueLock, LW_SHARED); /* Assert checks that we have a valid state entry */ Assert(MyProcPid == QUEUE_BACKEND_PID(MyBackendId)); - pos = oldpos = QUEUE_BACKEND_POS(MyBackendId); + pos = QUEUE_BACKEND_POS(MyBackendId); head = QUEUE_HEAD; LWLockRelease(NotifyQueueLock); diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c index 6d4765aa76c2..fedb5d74d33b 100644 --- a/src/backend/commands/collationcmds.c +++ b/src/backend/commands/collationcmds.c @@ -66,14 +66,12 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e DefElem *lcctypeEl = NULL; DefElem *providerEl = NULL; DefElem *deterministicEl = NULL; - DefElem *versionEl = NULL; char *collcollate = NULL; char *collctype = NULL; char *collproviderstr = NULL; bool collisdeterministic = true; int collencoding = 0; char collprovider = 0; - char *collversion = NULL; Oid newoid; ObjectAddress address; @@ -101,8 +99,6 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e defelp = &providerEl; else if (strcmp(defel->defname, "deterministic") == 0) defelp = &deterministicEl; - else if (strcmp(defel->defname, "version") == 0) - defelp = &versionEl; else { ereport(ERROR, @@ -171,9 +167,6 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e if (deterministicEl) collisdeterministic = defGetBoolean(deterministicEl); - if (versionEl) - collversion = defGetString(versionEl); - if (collproviderstr) { if (pg_strcasecmp(collproviderstr, "icu") == 0) @@ -220,9 +213,6 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e } } - if (!collversion) - collversion = get_collation_actual_version(collprovider, collcollate); - newoid = CollationCreate(collName, collNamespace, GetUserId(), @@ -231,7 +221,6 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e collencoding, collcollate, collctype, - collversion, if_not_exists, false); /* not quiet */ @@ -299,101 +288,13 @@ IsThereCollationInNamespace(const char *collname, Oid nspOid) collname, get_namespace_name(nspOid)))); } -/* - * ALTER COLLATION - */ -ObjectAddress -AlterCollation(AlterCollationStmt *stmt) -{ - Relation rel; - Oid collOid; - HeapTuple tup; - Form_pg_collation collForm; - Datum collversion; - bool isnull; - char *oldversion; - char *newversion; - ObjectAddress address; - - rel = table_open(CollationRelationId, RowExclusiveLock); - collOid = get_collation_oid(stmt->collname, false); - - if (!pg_collation_ownercheck(collOid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_COLLATION, - NameListToString(stmt->collname)); - - tup = SearchSysCacheCopy1(COLLOID, ObjectIdGetDatum(collOid)); - if (!HeapTupleIsValid(tup)) - elog(ERROR, "cache lookup failed for collation %u", collOid); - - collForm = (Form_pg_collation) GETSTRUCT(tup); - collversion = SysCacheGetAttr(COLLOID, tup, Anum_pg_collation_collversion, - &isnull); - oldversion = isnull ? NULL : TextDatumGetCString(collversion); - - newversion = get_collation_actual_version(collForm->collprovider, NameStr(collForm->collcollate)); - - /* cannot change from NULL to non-NULL or vice versa */ - if ((!oldversion && newversion) || (oldversion && !newversion)) - elog(ERROR, "invalid collation version change"); - else if (oldversion && newversion && strcmp(newversion, oldversion) != 0) - { - bool nulls[Natts_pg_collation]; - bool replaces[Natts_pg_collation]; - Datum values[Natts_pg_collation]; - - ereport(NOTICE, - (errmsg("changing version from %s to %s", - oldversion, newversion))); - - memset(values, 0, sizeof(values)); - memset(nulls, false, sizeof(nulls)); - memset(replaces, false, sizeof(replaces)); - - values[Anum_pg_collation_collversion - 1] = CStringGetTextDatum(newversion); - replaces[Anum_pg_collation_collversion - 1] = true; - - tup = heap_modify_tuple(tup, RelationGetDescr(rel), - values, nulls, replaces); - } - else - ereport(NOTICE, - (errmsg("version has not changed"))); - - CatalogTupleUpdate(rel, &tup->t_self, tup); - - InvokeObjectPostAlterHook(CollationRelationId, collOid, 0); - - ObjectAddressSet(address, CollationRelationId, collOid); - - heap_freetuple(tup); - table_close(rel, NoLock); - - return address; -} - - Datum pg_collation_actual_version(PG_FUNCTION_ARGS) { Oid collid = PG_GETARG_OID(0); - HeapTuple tp; - char *collcollate; - char collprovider; char *version; - tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid)); - if (!HeapTupleIsValid(tp)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("collation with OID %u does not exist", collid))); - - collcollate = pstrdup(NameStr(((Form_pg_collation) GETSTRUCT(tp))->collcollate)); - collprovider = ((Form_pg_collation) GETSTRUCT(tp))->collprovider; - - ReleaseSysCache(tp); - - version = get_collation_actual_version(collprovider, collcollate); + version = get_collation_version_for_oid(collid); if (version) PG_RETURN_TEXT_P(cstring_to_text(version)); @@ -678,7 +579,6 @@ pg_import_system_collations(PG_FUNCTION_ARGS) collid = CollationCreate(localebuf, nspid, GetUserId(), COLLPROVIDER_LIBC, true, enc, localebuf, localebuf, - get_collation_actual_version(COLLPROVIDER_LIBC, localebuf), true, true); if (OidIsValid(collid)) { @@ -740,7 +640,6 @@ pg_import_system_collations(PG_FUNCTION_ARGS) collid = CollationCreate(alias, nspid, GetUserId(), COLLPROVIDER_LIBC, true, enc, locale, locale, - get_collation_actual_version(COLLPROVIDER_LIBC, locale), true, true); if (OidIsValid(collid)) { @@ -803,7 +702,6 @@ pg_import_system_collations(PG_FUNCTION_ARGS) nspid, GetUserId(), COLLPROVIDER_ICU, true, -1, collcollate, collcollate, - get_collation_actual_version(COLLPROVIDER_ICU, collcollate), true, true); if (OidIsValid(collid)) { diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 93853224bb05..f556b77ca8a0 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -1437,6 +1437,8 @@ ProcessCopyOptions(ParseState *pstate, List *options) { bool format_specified = false; + bool freeze_specified = false; + bool header_specified = false; ListCell *option; /* Support external use for option sanity checking */ @@ -1480,11 +1482,12 @@ ProcessCopyOptions(ParseState *pstate, } else if (strcmp(defel->defname, "freeze") == 0) { - if (cstate->freeze) + if (freeze_specified) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"), parser_errposition(pstate, defel->location))); + freeze_specified = true; cstate->freeze = defGetBoolean(defel); } else if (strcmp(defel->defname, "delimiter") == 0) @@ -1519,11 +1522,12 @@ ProcessCopyOptions(ParseState *pstate, } else if (strcmp(defel->defname, "header") == 0) { - if (cstate->header_line) + if (header_specified) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"), parser_errposition(pstate, defel->location))); + header_specified = true; cstate->header_line = defGetBoolean(defel); } else if (strcmp(defel->defname, "quote") == 0) @@ -3564,9 +3568,6 @@ CopyMultiInsertBufferFlush(CopyMultiInsertInfo *miinfo, ResultRelInfo *resultRelInfo = buffer->resultRelInfo; TupleTableSlot **slots = buffer->slots; - /* Set es_result_relation_info to the ResultRelInfo we're flushing. */ - estate->es_result_relation_info = resultRelInfo; - /* * Print error context information correctly, if one of the operations * below fail. @@ -3599,7 +3600,8 @@ CopyMultiInsertBufferFlush(CopyMultiInsertInfo *miinfo, cstate->cur_lineno = buffer->linenos[i]; recheckIndexes = - ExecInsertIndexTuples(buffer->slots[i], estate, false, NULL, + ExecInsertIndexTuples(resultRelInfo, + buffer->slots[i], estate, false, NULL, NIL); ExecARInsertTriggers(estate, resultRelInfo, slots[i], recheckIndexes, @@ -3687,9 +3689,9 @@ CopyMultiInsertInfoFlush(CopyMultiInsertInfo *miinfo, ResultRelInfo *curr_rri) /* * Trim the list of tracked buffers down if it exceeds the limit. Here we - * remove buffers starting with the ones we created first. It seems more - * likely that these older ones are less likely to be needed than ones - * that were just created. + * remove buffers starting with the ones we created first. It seems less + * likely that these older ones will be needed than the ones that were + * just created. */ while (list_length(miinfo->multiInsertBuffers) > MAX_PARTITION_BUFFERS) { @@ -3806,6 +3808,7 @@ CopyFrom(CopyState cstate) GpDistributionData *distData = NULL; /* distribution data used to compute target seg */ Assert(cstate->rel); + Assert(list_length(cstate->range_table) == 1); /* * The target must be a plain, foreign, or partitioned relation, or have @@ -3908,26 +3911,15 @@ CopyFrom(CopyState cstate) * index-entry-making machinery. (There used to be a huge amount of code * here that basically duplicated execUtils.c ...) */ - resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(resultRelInfo, - cstate->rel, - 1, /* must match rel's position in range_table */ - NULL, - 0); - - target_resultRelInfo = resultRelInfo; + ExecInitRangeTable(estate, cstate->range_table); + resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo); + ExecInitResultRelation(estate, resultRelInfo, 1); /* Verify the named relation is a valid target for INSERT */ CheckValidResultRel(resultRelInfo, CMD_INSERT); ExecOpenIndices(resultRelInfo, false); - estate->es_result_relations = resultRelInfo; - estate->es_num_result_relations = 1; - estate->es_result_relation_info = resultRelInfo; - - ExecInitRangeTable(estate, cstate->range_table); - /* * Set up a ModifyTableState so we can let FDW(s) init themselves for * foreign-table result relation(s). @@ -3936,7 +3928,7 @@ CopyFrom(CopyState cstate) mtstate->ps.plan = NULL; mtstate->ps.state = estate; mtstate->operation = CMD_INSERT; - mtstate->resultRelInfo = estate->es_result_relations; + mtstate->resultRelInfo = resultRelInfo; if (resultRelInfo->ri_FdwRoutine != NULL && resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL) @@ -4240,12 +4232,6 @@ CopyFrom(CopyState cstate) { if (!NextCopyFromExecute(cstate, econtext, myslot->tts_values, myslot->tts_isnull)) break; - - /* - * NextCopyFromExecute set up estate->es_result_relation_info, - * and stored the tuple in the correct slot. - */ - resultRelInfo = estate->es_result_relation_info; } else { @@ -4381,44 +4367,22 @@ CopyFrom(CopyState cstate) prevResultRelInfo = resultRelInfo; } - /* - * For ExecInsertIndexTuples() to work on the partition's indexes - */ - estate->es_result_relation_info = resultRelInfo; - /* * If we're capturing transition tuples, we might need to convert - * from the partition rowtype to root rowtype. + * from the partition rowtype to root rowtype. But if there are no + * BEFORE triggers on the partition that could change the tuple, + * we can just remember the original unconverted tuple to avoid a + * needless round trip conversion. */ if (cstate->transition_capture != NULL) - { - if (has_before_insert_row_trig) - { - /* - * If there are any BEFORE triggers on the partition, - * we'll have to be ready to convert their result back to - * tuplestore format. - */ - cstate->transition_capture->tcs_original_insert_tuple = NULL; - cstate->transition_capture->tcs_map = - resultRelInfo->ri_PartitionInfo->pi_PartitionToRootMap; - } - else - { - /* - * Otherwise, just remember the original unconverted - * tuple, to avoid a needless round trip conversion. - */ - cstate->transition_capture->tcs_original_insert_tuple = myslot; - cstate->transition_capture->tcs_map = NULL; - } - } + cstate->transition_capture->tcs_original_insert_tuple = + !has_before_insert_row_trig ? myslot : NULL; /* * We might need to convert from the root rowtype to the partition * rowtype. */ - map = resultRelInfo->ri_PartitionInfo->pi_RootToPartitionMap; + map = resultRelInfo->ri_RootToPartitionMap; if (insertMethod == CIM_SINGLE || !leafpart_use_multi_insert) { /* non batch insert */ @@ -4426,7 +4390,7 @@ CopyFrom(CopyState cstate) { TupleTableSlot *new_slot; - new_slot = resultRelInfo->ri_PartitionInfo->pi_PartitionTupleSlot; + new_slot = resultRelInfo->ri_PartitionTupleSlot; myslot = execute_attr_map_slot(map->attrMap, myslot, new_slot); } } @@ -4518,7 +4482,8 @@ CopyFrom(CopyState cstate) /* Compute stored generated columns */ if (resultRelInfo->ri_RelationDesc->rd_att->constr && resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored) - ExecComputeStoredGenerated(estate, myslot, CMD_INSERT); + ExecComputeStoredGenerated(resultRelInfo, estate, myslot, + CMD_INSERT); /* * If the target is a plain table, check the constraints of @@ -4534,7 +4499,7 @@ CopyFrom(CopyState cstate) * we don't need to if there's no BR trigger defined on the * partition. */ - if (resultRelInfo->ri_PartitionCheck && + if (resultRelInfo->ri_RelationDesc->rd_rel->relispartition && (proute == NULL || has_before_insert_row_trig)) ExecPartitionCheck(resultRelInfo, myslot, estate, true); @@ -4589,7 +4554,8 @@ CopyFrom(CopyState cstate) myslot, mycid, ti_options, bistate); if (resultRelInfo->ri_NumIndices > 0) - recheckIndexes = ExecInsertIndexTuples(myslot, + recheckIndexes = ExecInsertIndexTuples(resultRelInfo, + myslot, estate, false, NULL, @@ -4720,8 +4686,6 @@ CopyFrom(CopyState cstate) if (insertMethod != CIM_SINGLE) CopyMultiInsertInfoCleanup(&multiInsertInfo); - ExecCloseIndices(target_resultRelInfo); - if (target_resultRelInfo->ri_RelationDesc->rd_tableam) table_dml_finish(target_resultRelInfo->ri_RelationDesc); @@ -4729,8 +4693,9 @@ CopyFrom(CopyState cstate) if (proute) ExecCleanupTupleRouting(mtstate, proute); - /* Close any trigger target relations */ - ExecCleanUpTriggerState(estate); + /* Close the result relations, including any trigger target relations */ + ExecCloseResultRelations(estate); + ExecCloseRangeTableRelations(estate); FreeDistributionData(distData); diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 28bfaa3ad359..3d677f104847 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -177,7 +177,7 @@ CreateEventTrigger(CreateEventTrigStmt *stmt) /* Find and validate the trigger function. */ funcoid = LookupFuncName(stmt->funcname, 0, NULL, false); funcrettype = get_func_rettype(funcoid); - if (funcrettype != EVTTRIGGEROID) + if (funcrettype != EVENT_TRIGGEROID) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("function %s must return type %s", @@ -1654,9 +1654,15 @@ EventTriggerAlterTableEnd(void) /* If no subcommands, don't collect */ if (list_length(currentEventTriggerState->currentCommand->d.alterTable.subcmds) != 0) { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + currentEventTriggerState->commandList = lappend(currentEventTriggerState->commandList, currentEventTriggerState->currentCommand); + + MemoryContextSwitchTo(oldcxt); } else pfree(currentEventTriggerState->currentCommand); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 1c54abe7903f..3fd724430c78 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -141,7 +141,8 @@ static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es); static void show_eval_params(Bitmapset *bms_params, ExplainState *es); static void show_join_pruning_info(List *join_prune_ids, ExplainState *es); static const char *explain_get_index_name(Oid indexId); -static void show_buffer_usage(ExplainState *es, const BufferUsage *usage); +static void show_buffer_usage(ExplainState *es, const BufferUsage *usage, + bool planning); static void show_wal_usage(ExplainState *es, const WalUsage *usage); static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir, ExplainState *es); @@ -256,11 +257,6 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, parser_errposition(pstate, opt->location))); } - if (es->buffers && !es->analyze) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("EXPLAIN option BUFFERS requires ANALYZE"))); - if (es->wal && !es->analyze) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -724,8 +720,13 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, if (cursorOptions & CURSOR_OPT_PARALLEL_RETRIEVE) ExplainParallelRetrieveCursor(es, queryDesc); - if (es->summary && (planduration || bufusage)) + /* Show buffer usage in planning */ + if (bufusage) + { ExplainOpenGroup("Planning", "Planning", true, es); + show_buffer_usage(es, bufusage, true); + ExplainCloseGroup("Planning", "Planning", true, es); + } if (es->summary && planduration) { @@ -738,19 +739,6 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, if (es->slicetable) ExplainPrintSliceTable(es, queryDesc); - /* Show buffer usage */ - if (es->summary && bufusage) - { - if (es->format == EXPLAIN_FORMAT_TEXT) - es->indent++; - show_buffer_usage(es, bufusage); - if (es->format == EXPLAIN_FORMAT_TEXT) - es->indent--; - } - - if (es->summary && (planduration || bufusage)) - ExplainCloseGroup("Planning", "Planning", true, es); - /* Print info about runtime of triggers */ if (es->analyze) ExplainPrintTriggers(es, queryDesc); @@ -1077,27 +1065,24 @@ ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc) { ResultRelInfo *rInfo; bool show_relname; - int numrels = queryDesc->estate->es_num_result_relations; - int numrootrels = queryDesc->estate->es_num_root_result_relations; + List *resultrels; List *routerels; List *targrels; - int nr; ListCell *l; + resultrels = queryDesc->estate->es_opened_result_relations; routerels = queryDesc->estate->es_tuple_routing_result_relations; targrels = queryDesc->estate->es_trig_target_relations; ExplainOpenGroup("Triggers", "Triggers", false, es); - show_relname = (numrels > 1 || numrootrels > 0 || + show_relname = (list_length(resultrels) > 1 || routerels != NIL || targrels != NIL); - rInfo = queryDesc->estate->es_result_relations; - for (nr = 0; nr < numrels; rInfo++, nr++) - report_triggers(rInfo, show_relname, es); - - rInfo = queryDesc->estate->es_root_result_relations; - for (nr = 0; nr < numrootrels; rInfo++, nr++) + foreach(l, resultrels) + { + rInfo = (ResultRelInfo *) lfirst(l); report_triggers(rInfo, show_relname, es); + } foreach(l, routerels) { @@ -2689,7 +2674,7 @@ ExplainNode(PlanState *planstate, List *ancestors, /* Show buffer/WAL usage */ if (es->buffers && planstate->instrument) - show_buffer_usage(es, &planstate->instrument->bufusage); + show_buffer_usage(es, &planstate->instrument->bufusage, false); if (es->wal && planstate->instrument) show_wal_usage(es, &planstate->instrument->walusage); @@ -2708,7 +2693,7 @@ ExplainNode(PlanState *planstate, List *ancestors, ExplainOpenWorker(n, es); if (es->buffers) - show_buffer_usage(es, &instrument->bufusage); + show_buffer_usage(es, &instrument->bufusage, false); if (es->wal) show_wal_usage(es, &instrument->walusage); ExplainCloseWorker(n, es); @@ -3605,14 +3590,14 @@ show_incremental_sort_group_info(IncrementalSortGroupInfo *groupInfo, groupInfo->groupCount); /* plural/singular based on methodNames size */ if (list_length(methodNames) > 1) - appendStringInfo(es->str, "s: "); + appendStringInfoString(es->str, "s: "); else - appendStringInfo(es->str, ": "); + appendStringInfoString(es->str, ": "); foreach(methodCell, methodNames) { - appendStringInfo(es->str, "%s", (char *) methodCell->ptr_value); + appendStringInfoString(es->str, (char *) methodCell->ptr_value); if (foreach_current_index(methodCell) < list_length(methodNames) - 1) - appendStringInfo(es->str, ", "); + appendStringInfoString(es->str, ", "); } if (groupInfo->maxMemorySpaceUsed > 0) @@ -3664,7 +3649,7 @@ show_incremental_sort_group_info(IncrementalSortGroupInfo *groupInfo, ExplainPropertyInteger("Peak Sort Space Used", "kB", groupInfo->maxMemorySpaceUsed, es); - ExplainCloseGroup("Sort Spaces", memoryName.data, true, es); + ExplainCloseGroup("Sort Space", memoryName.data, true, es); } if (groupInfo->maxDiskSpaceUsed > 0) { @@ -3681,7 +3666,7 @@ show_incremental_sort_group_info(IncrementalSortGroupInfo *groupInfo, ExplainPropertyInteger("Peak Sort Space Used", "kB", groupInfo->maxDiskSpaceUsed, es); - ExplainCloseGroup("Sort Spaces", diskName.data, true, es); + ExplainCloseGroup("Sort Space", diskName.data, true, es); } ExplainCloseGroup("Incremental Sort Groups", groupName.data, true, es); @@ -3719,11 +3704,11 @@ show_incremental_sort_info(IncrementalSortState *incrsortstate, if (prefixsortGroupInfo->groupCount > 0) { if (es->format == EXPLAIN_FORMAT_TEXT) - appendStringInfo(es->str, "\n"); + appendStringInfoChar(es->str, '\n'); show_incremental_sort_group_info(prefixsortGroupInfo, "Pre-sorted", true, es); } if (es->format == EXPLAIN_FORMAT_TEXT) - appendStringInfo(es->str, "\n"); + appendStringInfoChar(es->str, '\n'); } if (incrsortstate->shared_info != NULL) @@ -3762,11 +3747,11 @@ show_incremental_sort_info(IncrementalSortState *incrsortstate, if (prefixsortGroupInfo->groupCount > 0) { if (es->format == EXPLAIN_FORMAT_TEXT) - appendStringInfo(es->str, "\n"); + appendStringInfoChar(es->str, '\n'); show_incremental_sort_group_info(prefixsortGroupInfo, "Pre-sorted", true, es); } if (es->format == EXPLAIN_FORMAT_TEXT) - appendStringInfo(es->str, "\n"); + appendStringInfoChar(es->str, '\n'); if (es->workers_state) ExplainCloseWorker(n, es); @@ -4144,7 +4129,7 @@ explain_get_index_name(Oid indexId) * Show buffer usage details. */ static void -show_buffer_usage(ExplainState *es, const BufferUsage *usage) +show_buffer_usage(ExplainState *es, const BufferUsage *usage, bool planning) { if (es->format == EXPLAIN_FORMAT_TEXT) { @@ -4160,6 +4145,15 @@ show_buffer_usage(ExplainState *es, const BufferUsage *usage) usage->temp_blks_written > 0); bool has_timing = (!INSTR_TIME_IS_ZERO(usage->blk_read_time) || !INSTR_TIME_IS_ZERO(usage->blk_write_time)); + bool show_planning = (planning && (has_shared || + has_local || has_temp || has_timing)); + + if (show_planning) + { + ExplainIndentText(es); + appendStringInfoString(es->str, "Planning:\n"); + es->indent++; + } /* Show only positive counter values. */ if (has_shared || has_local || has_temp) @@ -4229,6 +4223,9 @@ show_buffer_usage(ExplainState *es, const BufferUsage *usage) INSTR_TIME_GET_MILLISEC(usage->blk_write_time)); appendStringInfoChar(es->str, '\n'); } + + if (show_planning) + es->indent--; } else { diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index a0a168c64041..1b5217ce9b29 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -215,8 +215,8 @@ interpret_function_parameter_list(ParseState *pstate, Oid *requiredResultType) { int parameterCount = list_length(parameters); - Oid *inTypes; - int inCount = 0; + Oid *sigArgTypes; + int sigArgCount = 0; Datum *allTypes; Datum *paramModes; Datum *paramNames; @@ -234,8 +234,7 @@ interpret_function_parameter_list(ParseState *pstate, *allParameterTypes = NULL; *parameterModes = NULL; - /* Allocate local memory */ - inTypes = (Oid *) palloc(parameterCount * sizeof(Oid)); + sigArgTypes = (Oid *) palloc(parameterCount * sizeof(Oid)); allTypes = (Datum *) palloc(parameterCount * sizeof(Datum)); paramModes = (Datum *) palloc(parameterCount * sizeof(Datum)); paramNames = (Datum *) palloc0(parameterCount * sizeof(Datum)); @@ -307,25 +306,21 @@ interpret_function_parameter_list(ParseState *pstate, errmsg("functions cannot accept set arguments"))); } - if (objtype == OBJECT_PROCEDURE) - { - if (fp->mode == FUNC_PARAM_OUT) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("procedures cannot have OUT arguments"), - errhint("INOUT arguments are permitted."))); - } - /* handle input parameters */ if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE) + isinput = true; + + /* handle signature parameters */ + if (fp->mode == FUNC_PARAM_IN || fp->mode == FUNC_PARAM_INOUT || + (objtype == OBJECT_PROCEDURE && fp->mode == FUNC_PARAM_OUT) || + fp->mode == FUNC_PARAM_VARIADIC) { - /* other input parameters can't follow a VARIADIC parameter */ + /* other signature parameters can't follow a VARIADIC parameter */ if (varCount > 0) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("VARIADIC parameter must be the last input parameter"))); - inTypes[inCount++] = toid; - isinput = true; + errmsg("VARIADIC parameter must be the last signature parameter"))); + sigArgTypes[sigArgCount++] = toid; /* Keep track of the number of anytable arguments */ if (toid == ANYTABLEOID) @@ -479,7 +474,7 @@ interpret_function_parameter_list(ParseState *pstate, } /* Now construct the proper outputs as needed */ - *parameterTypes = buildoidvector(inTypes, inCount); + *parameterTypes = buildoidvector(sigArgTypes, sigArgCount); if (outCount > 0 || varCount > 0) { @@ -2169,6 +2164,7 @@ CreateTransform(CreateTransformStmt *stmt) Relation relation; ObjectAddress myself, referenced; + ObjectAddresses *addrs; bool is_replace; /* @@ -2310,39 +2306,34 @@ CreateTransform(CreateTransformStmt *stmt) if (is_replace) deleteDependencyRecordsFor(TransformRelationId, transformid, true); + addrs = new_object_addresses(); + /* make dependency entries */ - myself.classId = TransformRelationId; - myself.objectId = transformid; - myself.objectSubId = 0; + ObjectAddressSet(myself, TransformRelationId, transformid); /* dependency on language */ - referenced.classId = LanguageRelationId; - referenced.objectId = langid; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, LanguageRelationId, langid); + add_exact_object_address(&referenced, addrs); /* dependency on type */ - referenced.classId = TypeRelationId; - referenced.objectId = typeid; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, TypeRelationId, typeid); + add_exact_object_address(&referenced, addrs); /* dependencies on functions */ if (OidIsValid(fromsqlfuncid)) { - referenced.classId = ProcedureRelationId; - referenced.objectId = fromsqlfuncid; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, ProcedureRelationId, fromsqlfuncid); + add_exact_object_address(&referenced, addrs); } if (OidIsValid(tosqlfuncid)) { - referenced.classId = ProcedureRelationId; - referenced.objectId = tosqlfuncid; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, ProcedureRelationId, tosqlfuncid); + add_exact_object_address(&referenced, addrs); } + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); + /* dependency on extension */ recordDependencyOnCurrentExtension(&myself, is_replace); @@ -2582,6 +2573,9 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver int nargs; int i; AclResult aclresult; + Oid *argtypes; + char **argnames; + char *argmodes; FmgrInfo flinfo; CallContext *callcontext; EState *estate; @@ -2642,6 +2636,8 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver tp); nargs = list_length(fexpr->args); + get_func_arg_info(tp, &argtypes, &argnames, &argmodes); + ReleaseSysCache(tp); /* safety check; see ExecInitFunc() */ @@ -2671,16 +2667,24 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver i = 0; foreach(lc, fexpr->args) { - ExprState *exprstate; - Datum val; - bool isnull; + if (argmodes && argmodes[i] == PROARGMODE_OUT) + { + fcinfo->args[i].value = 0; + fcinfo->args[i].isnull = true; + } + else + { + ExprState *exprstate; + Datum val; + bool isnull; - exprstate = ExecPrepareExpr(lfirst(lc), estate); + exprstate = ExecPrepareExpr(lfirst(lc), estate); - val = ExecEvalExprSwitchContext(exprstate, econtext, &isnull); + val = ExecEvalExprSwitchContext(exprstate, econtext, &isnull); - fcinfo->args[i].value = val; - fcinfo->args[i].isnull = isnull; + fcinfo->args[i].value = val; + fcinfo->args[i].isnull = isnull; + } i++; } diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index d5f064fb1e7f..1b68706cc7c9 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -100,8 +100,8 @@ static void RangeVarCallbackForReindexIndex(const RangeVar *relation, Oid relId, Oid oldRelId, void *arg); static bool ReindexRelationConcurrently(Oid relationOid, int options); -static void ReindexPartitions(Oid relid, int options, bool concurrent, bool isTopLevel); -static void ReindexMultipleInternal(List *relids, int options, bool concurrent); +static void ReindexPartitions(Oid relid, int options, bool isTopLevel); +static void ReindexMultipleInternal(List *relids, int options); static void reindex_error_callback(void *args); static void update_relispartition(Oid relationId, bool newval); static bool CompareOpclassOptions(Datum *opts1, Datum *opts2, int natts); @@ -111,7 +111,7 @@ static bool CompareOpclassOptions(Datum *opts1, Datum *opts2, int natts); */ struct ReindexIndexCallbackState { - bool concurrent; /* flag from statement */ + int options; /* options from statement */ Oid locked_table_oid; /* tracks previously locked table */ }; @@ -1234,8 +1234,7 @@ DefineIndex(Oid relationId, key->partattrs[i] - 1); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("insufficient columns in %s constraint definition", - constraint_type), + errmsg("unique constraint on partitioned table must include all partitioning columns"), errdetail("%s constraint on table \"%s\" lacks column \"%s\" which is part of the partition key.", constraint_type, RelationGetRelationName(rel), NameStr(att->attname)))); @@ -2763,7 +2762,6 @@ ReindexIndex(ReindexStmt *stmt, bool isTopLevel) { RangeVar *indexRelation = stmt->relation; int options = stmt->options; - bool concurrent = stmt->concurrent; struct ReindexIndexCallbackState state; Oid indOid; char persistence; @@ -2776,7 +2774,7 @@ ReindexIndex(ReindexStmt *stmt, bool isTopLevel) */ if (Gp_role == GP_ROLE_EXECUTE) { - Assert(OidIsValid(stmt->relid) && !concurrent); + Assert(OidIsValid(stmt->relid) && (options & REINDEXOPT_CONCURRENTLY) == 0); LockRelationOid(stmt->relid, AccessExclusiveLock); persistence = get_rel_persistence(stmt->relid); @@ -2797,10 +2795,11 @@ ReindexIndex(ReindexStmt *stmt, bool isTopLevel) * upgrade the lock, but that's OK, because other sessions can't hold * locks on our temporary table. */ - state.concurrent = concurrent; + state.options = options; state.locked_table_oid = InvalidOid; indOid = RangeVarGetRelidExtended(indexRelation, - concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock, + (options & REINDEXOPT_CONCURRENTLY) != 0 ? + ShareUpdateExclusiveLock : AccessExclusiveLock, 0, RangeVarCallbackForReindexIndex, &state); @@ -2812,10 +2811,9 @@ ReindexIndex(ReindexStmt *stmt, bool isTopLevel) persistence = get_rel_persistence(indOid); relkind = get_rel_relkind(indOid); - if (relkind == RELKIND_PARTITIONED_INDEX) - ReindexPartitions(indOid, options, concurrent, isTopLevel); - else if (concurrent && + ReindexPartitions(indOid, options, isTopLevel); + else if ((options & REINDEXOPT_CONCURRENTLY) != 0 && persistence != RELPERSISTENCE_TEMP) ReindexRelationConcurrently(indOid, options); else @@ -2834,7 +2832,6 @@ ReindexIndex(ReindexStmt *stmt, bool isTopLevel) qestmt->kind = REINDEX_OBJECT_INDEX; qestmt->relation = NULL; qestmt->options = options; - qestmt->concurrent = concurrent; qestmt->relid = indOid; CdbDispatchUtilityStatement((Node *) qestmt, @@ -2863,7 +2860,8 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation, * non-concurrent case and table locks used by index_concurrently_*() for * concurrent case. */ - table_lockmode = state->concurrent ? ShareUpdateExclusiveLock : ShareLock; + table_lockmode = ((state->options & REINDEXOPT_CONCURRENTLY) != 0) ? + ShareUpdateExclusiveLock : ShareLock; /* * If we previously locked some other index's heap, and the name we're @@ -2924,7 +2922,6 @@ ReindexTable(ReindexStmt *stmt, bool isTopLevel) { RangeVar *relation = stmt->relation; int options = stmt->options; - bool concurrent = stmt->concurrent; Oid heapOid; bool result; @@ -2951,13 +2948,14 @@ ReindexTable(ReindexStmt *stmt, bool isTopLevel) * locks on our temporary table. */ heapOid = RangeVarGetRelidExtended(relation, - concurrent ? ShareUpdateExclusiveLock : ShareLock, + (options & REINDEXOPT_CONCURRENTLY) != 0 ? + ShareUpdateExclusiveLock : ShareLock, 0, RangeVarCallbackOwnsTable, NULL); if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE) - ReindexPartitions(heapOid, options, concurrent, isTopLevel); - else if (concurrent && + ReindexPartitions(heapOid, options, isTopLevel); + else if ((options & REINDEXOPT_CONCURRENTLY) != 0 && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP) { result = ReindexRelationConcurrently(heapOid, options); @@ -2991,7 +2989,6 @@ ReindexTable(ReindexStmt *stmt, bool isTopLevel) qestmt->kind = REINDEX_OBJECT_TABLE; qestmt->relation = NULL; qestmt->options = options; - qestmt->concurrent = concurrent; qestmt->relid = heapOid; CdbDispatchUtilityStatement((Node *) qestmt, @@ -3014,7 +3011,7 @@ ReindexTable(ReindexStmt *stmt, bool isTopLevel) */ void ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind, - int options, bool concurrent) + int options) { Oid objectOid; Relation relationRelation; @@ -3033,7 +3030,8 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind, objectKind == REINDEX_OBJECT_SYSTEM || objectKind == REINDEX_OBJECT_DATABASE); - if (objectKind == REINDEX_OBJECT_SYSTEM && concurrent) + if (objectKind == REINDEX_OBJECT_SYSTEM && + (options & REINDEXOPT_CONCURRENTLY) != 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot reindex system catalogs concurrently"))); @@ -3143,7 +3141,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind, * Skip system tables, since index_create() would reject indexing them * concurrently (and it would likely fail if we tried). */ - if (concurrent && + if ((options & REINDEXOPT_CONCURRENTLY) != 0 && IsCatalogRelationOid(relid)) { if (!concurrent_warning) @@ -3178,7 +3176,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind, * Process each relation listed in a separate transaction. Note that this * commits and then starts a new transaction immediately. */ - ReindexMultipleInternal(relids, options, concurrent); + ReindexMultipleInternal(relids, options); MemoryContextDelete(private_context); } @@ -3209,7 +3207,7 @@ reindex_error_callback(void *arg) * by the caller. */ static void -ReindexPartitions(Oid relid, int options, bool concurrent, bool isTopLevel) +ReindexPartitions(Oid relid, int options, bool isTopLevel) { List *partitions = NIL; char relkind = get_rel_relkind(relid); @@ -3286,7 +3284,7 @@ ReindexPartitions(Oid relid, int options, bool concurrent, bool isTopLevel) * Process each partition listed in a separate transaction. Note that * this commits and then starts a new transaction immediately. */ - ReindexMultipleInternal(partitions, options, concurrent); + ReindexMultipleInternal(partitions, options); /* * Clean up working storage --- note we must do this after @@ -3304,7 +3302,7 @@ ReindexPartitions(Oid relid, int options, bool concurrent, bool isTopLevel) * and starts a new transaction when finished. */ static void -ReindexMultipleInternal(List *relids, int options, bool concurrent) +ReindexMultipleInternal(List *relids, int options) { ListCell *l; @@ -3335,7 +3333,7 @@ ReindexMultipleInternal(List *relids, int options, bool concurrent) relkind = get_rel_relkind(relid); relpersistence = get_rel_persistence(relid); - lockmode = concurrent ? ShareUpdateExclusiveLock : + lockmode = (options & REINDEXOPT_CONCURRENTLY) != 0 ? ShareUpdateExclusiveLock : (relkind == RELKIND_INDEX ? AccessExclusiveLock : ShareLock); /* * If the relation is index, lock the table first to prevent dead lock. @@ -3370,16 +3368,20 @@ ReindexMultipleInternal(List *relids, int options, bool concurrent) Assert(relkind != RELKIND_PARTITIONED_INDEX && relkind != RELKIND_PARTITIONED_TABLE); - if (concurrent && + if ((options & REINDEXOPT_CONCURRENTLY) != 0 && relpersistence != RELPERSISTENCE_TEMP) { - result = ReindexRelationConcurrently(relid, options); + result = ReindexRelationConcurrently(relid, + options | + REINDEXOPT_MISSING_OK); /* ReindexRelationConcurrently() does the verbose output */ } else if (relkind == RELKIND_INDEX) { reindex_index(relid, false, relpersistence, - options); + options | + REINDEXOPT_REPORT_PROGRESS | + REINDEXOPT_MISSING_OK); PopActiveSnapshot(); /* reindex_index() does the verbose output */ result = true; @@ -3389,7 +3391,9 @@ ReindexMultipleInternal(List *relids, int options, bool concurrent) result = reindex_relation(relid, REINDEX_REL_PROCESS_TOAST | REINDEX_REL_CHECK_CONSTRAINTS, - options | REINDEXOPT_REPORT_PROGRESS); + options | + REINDEXOPT_REPORT_PROGRESS | + REINDEXOPT_MISSING_OK); if (result && (options & REINDEXOPT_VERBOSE)) ereport(INFO, @@ -3411,7 +3415,6 @@ ReindexMultipleInternal(List *relids, int options, bool concurrent) REINDEX_OBJECT_INDEX : REINDEX_OBJECT_TABLE; stmt->relation = NULL; stmt->options = options; - stmt->concurrent = concurrent; stmt->relid = relid; PushActiveSnapshot(GetTransactionSnapshot()); @@ -3469,6 +3472,13 @@ ReindexRelationConcurrently(Oid relationOid, int options) char *relationName = NULL; char *relationNamespace = NULL; PGRUsage ru0; + const int progress_index[] = { + PROGRESS_CREATEIDX_COMMAND, + PROGRESS_CREATEIDX_PHASE, + PROGRESS_CREATEIDX_INDEX_OID, + PROGRESS_CREATEIDX_ACCESS_METHOD_OID + }; + int64 progress_vals[4]; /* * Create a memory context that will survive forced transaction commits we @@ -3525,7 +3535,18 @@ ReindexRelationConcurrently(Oid relationOid, int options) errmsg("cannot reindex system catalogs concurrently"))); /* Open relation to get its indexes */ - heapRelation = table_open(relationOid, ShareUpdateExclusiveLock); + if ((options & REINDEXOPT_MISSING_OK) != 0) + { + heapRelation = try_table_open(relationOid, + ShareUpdateExclusiveLock, + false); + /* leave if relation does not exist */ + if (!heapRelation) + break; + } + else + heapRelation = table_open(relationOid, + ShareUpdateExclusiveLock); /* Add all the valid indexes of relation to list */ foreach(lc, RelationGetIndexList(heapRelation)) @@ -3610,7 +3631,13 @@ ReindexRelationConcurrently(Oid relationOid, int options) } case RELKIND_INDEX: { - Oid heapId = IndexGetRelation(relationOid, false); + Oid heapId = IndexGetRelation(relationOid, + (options & REINDEXOPT_MISSING_OK) != 0); + Relation heapRelation; + + /* if relation is missing, leave */ + if (!OidIsValid(heapId)) + break; if (IsCatalogRelationOid(heapId)) ereport(ERROR, @@ -3627,6 +3654,26 @@ ReindexRelationConcurrently(Oid relationOid, int options) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot reindex invalid index on TOAST table concurrently"))); + /* + * Check if parent relation can be locked and if it exists, + * this needs to be done at this stage as the list of indexes + * to rebuild is not complete yet, and REINDEXOPT_MISSING_OK + * should not be used once all the session locks are taken. + */ + if ((options & REINDEXOPT_MISSING_OK) != 0) + { + heapRelation = try_table_open(heapId, + ShareUpdateExclusiveLock, + false); + /* leave if relation does not exist */ + if (!heapRelation) + break; + } + else + heapRelation = table_open(heapId, + ShareUpdateExclusiveLock); + table_close(heapRelation, NoLock); + /* Save the list of relation OIDs in private context */ oldcontext = MemoryContextSwitchTo(private_context); @@ -3653,7 +3700,13 @@ ReindexRelationConcurrently(Oid relationOid, int options) break; } - /* Definitely no indexes, so leave */ + /* + * Definitely no indexes, so leave. Any checks based on + * REINDEXOPT_MISSING_OK should be done only while the list of indexes to + * work on is built as the session locks taken before this transaction + * commits will make sure that they cannot be dropped by a concurrent + * session until this operation completes. + */ if (indexIds == NIL) { PopActiveSnapshot(); @@ -3707,12 +3760,11 @@ ReindexRelationConcurrently(Oid relationOid, int options) pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, RelationGetRelid(heapRel)); - pgstat_progress_update_param(PROGRESS_CREATEIDX_COMMAND, - PROGRESS_CREATEIDX_COMMAND_REINDEX_CONCURRENTLY); - pgstat_progress_update_param(PROGRESS_CREATEIDX_INDEX_OID, - indexId); - pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID, - indexRel->rd_rel->relam); + progress_vals[0] = PROGRESS_CREATEIDX_COMMAND_REINDEX_CONCURRENTLY; + progress_vals[1] = 0; /* initializing */ + progress_vals[2] = indexId; + progress_vals[3] = indexRel->rd_rel->relam; + pgstat_progress_update_multi_param(4, progress_index, progress_vals); /* Choose a temporary relation name for the new index */ concurrentName = ChooseRelationName(get_rel_name(indexId), @@ -3816,12 +3868,12 @@ ReindexRelationConcurrently(Oid relationOid, int options) WaitForLockersMultiple(lockTags, ShareLock, true); CommitTransactionCommand(); - forboth(lc, indexIds, lc2, newIndexIds) + foreach(lc, newIndexIds) { - Relation indexRel; - Oid oldIndexId = lfirst_oid(lc); - Oid newIndexId = lfirst_oid(lc2); + Relation newIndexRel; + Oid newIndexId = lfirst_oid(lc); Oid heapId; + Oid indexam; /* Start new transaction for this index's concurrent build */ StartTransactionCommand(); @@ -3840,9 +3892,21 @@ ReindexRelationConcurrently(Oid relationOid, int options) * Index relation has been closed by previous commit, so reopen it to * get its information. */ - indexRel = index_open(oldIndexId, ShareUpdateExclusiveLock); - heapId = indexRel->rd_index->indrelid; - index_close(indexRel, NoLock); + newIndexRel = index_open(newIndexId, ShareUpdateExclusiveLock); + heapId = newIndexRel->rd_index->indrelid; + indexam = newIndexRel->rd_rel->relam; + index_close(newIndexRel, NoLock); + + /* + * Update progress for the index to build, with the correct parent + * table involved. + */ + pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, heapId); + progress_vals[0] = PROGRESS_CREATEIDX_COMMAND_REINDEX_CONCURRENTLY; + progress_vals[1] = PROGRESS_CREATEIDX_PHASE_BUILD; + progress_vals[2] = newIndexId; + progress_vals[3] = indexam; + pgstat_progress_update_multi_param(4, progress_index, progress_vals); /* Perform concurrent build of new index */ index_concurrently_build(heapId, newIndexId); @@ -3871,6 +3935,8 @@ ReindexRelationConcurrently(Oid relationOid, int options) Oid heapId; TransactionId limitXmin; Snapshot snapshot; + Relation newIndexRel; + Oid indexam; StartTransactionCommand(); @@ -3881,8 +3947,6 @@ ReindexRelationConcurrently(Oid relationOid, int options) */ CHECK_FOR_INTERRUPTS(); - heapId = IndexGetRelation(newIndexId, false); - /* * Take the "reference snapshot" that will be used by validate_index() * to filter candidate tuples. @@ -3890,6 +3954,26 @@ ReindexRelationConcurrently(Oid relationOid, int options) snapshot = RegisterSnapshot(GetTransactionSnapshot()); PushActiveSnapshot(snapshot); + /* + * Index relation has been closed by previous commit, so reopen it to + * get its information. + */ + newIndexRel = index_open(newIndexId, ShareUpdateExclusiveLock); + heapId = newIndexRel->rd_index->indrelid; + indexam = newIndexRel->rd_rel->relam; + index_close(newIndexRel, NoLock); + + /* + * Update progress for the index to build, with the correct parent + * table involved. + */ + pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, heapId); + progress_vals[0] = PROGRESS_CREATEIDX_COMMAND_REINDEX_CONCURRENTLY; + progress_vals[1] = PROGRESS_CREATEIDX_PHASE_VALIDATE_IDXSCAN; + progress_vals[2] = newIndexId; + progress_vals[3] = indexam; + pgstat_progress_update_multi_param(4, progress_index, progress_vals); + validate_index(heapId, newIndexId, snapshot); /* @@ -4024,7 +4108,7 @@ ReindexRelationConcurrently(Oid relationOid, int options) */ pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE, - PROGRESS_CREATEIDX_PHASE_WAIT_4); + PROGRESS_CREATEIDX_PHASE_WAIT_5); WaitForLockersMultiple(lockTags, AccessExclusiveLock, true); PushActiveSnapshot(GetTransactionSnapshot()); diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c index 2accc484daf1..57d89f9c724b 100644 --- a/src/backend/commands/lockcmds.c +++ b/src/backend/commands/lockcmds.c @@ -105,14 +105,6 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid, return; /* woops, concurrently dropped; no permissions * check */ - /* Currently, we only allow plain tables or views to be locked */ - if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE && - relkind != RELKIND_VIEW) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table or view", - rv->relname))); - #if 0 /* Upstream code not applicable to GPDB */ /* * Make note if a temporary relation has been accessed in this @@ -225,11 +217,13 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context) foreach(rtable, query->rtable) { RangeTblEntry *rte = lfirst(rtable); + Oid relid; AclResult aclresult; - Oid relid = rte->relid; - char relkind = rte->relkind; - char *relname = get_rel_name(relid); + /* ignore all non-relation RTEs */ + if (rte->rtekind != RTE_RELATION) + continue; + relid = rte->relid; /* * The OLD and NEW placeholder entries in the view's rtable are @@ -240,11 +234,6 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context) strcmp(rte->eref->aliasname, "new") == 0)) continue; - /* Currently, we only allow plain tables or views to be locked. */ - if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE && - relkind != RELKIND_VIEW) - continue; - /* Check infinite recursion in the view definition. */ if (list_member_oid(context->ancestor_views, relid)) ereport(ERROR, @@ -255,7 +244,8 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context) /* Check permissions with the view owner's privilege. */ aclresult = LockTableAclCheck(relid, context->lockmode, context->viewowner); if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, get_relkind_objtype(relkind), relname); + aclcheck_error(aclresult, get_relkind_objtype(rte->relkind), + get_rel_name(relid)); /* We have enough rights to lock the relation; do so. */ if (!context->nowait) @@ -264,9 +254,9 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context) ereport(ERROR, (errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("could not obtain lock on relation \"%s\"", - relname))); + get_rel_name(relid)))); - if (relkind == RELKIND_VIEW) + if (rte->relkind == RELKIND_VIEW) LockViewRecurse(relid, context->lockmode, context->nowait, context->ancestor_views); else if (rte->inh) LockTableRecurse(relid, context->lockmode, context->nowait); diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c index 97e4a0fbe7e7..6b4a8bbaa19f 100644 --- a/src/backend/commands/opclasscmds.c +++ b/src/backend/commands/opclasscmds.c @@ -1248,14 +1248,14 @@ assignProcTypes(OpFamilyMember *member, Oid amoid, Oid typeoid, (OidIsValid(member->righttype) && member->righttype != typeoid)) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("associated data types for opclass options parsing functions must match opclass input type"))); + errmsg("associated data types for operator class options parsing functions must match opclass input type"))); } else { if (member->lefttype != member->righttype) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("left and right associated data types for opclass options parsing functions must match"))); + errmsg("left and right associated data types for operator class options parsing functions must match"))); } if (procform->prorettype != VOIDOID || @@ -1263,8 +1263,8 @@ assignProcTypes(OpFamilyMember *member, Oid amoid, Oid typeoid, procform->proargtypes.values[0] != INTERNALOID) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("invalid opclass options parsing function"), - errhint("Valid signature of opclass options parsing function is '%s'.", + errmsg("invalid operator class options parsing function"), + errhint("Valid signature of operator class options parsing function is %s.", "(internal) RETURNS void"))); } diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c index 220f073e2cff..dee977ba9af7 100644 --- a/src/backend/commands/operatorcmds.c +++ b/src/backend/commands/operatorcmds.c @@ -184,10 +184,22 @@ DefineOperator(List *names, List *parameters) if (typeName2) typeId2 = typenameTypeId(NULL, typeName2); + /* + * If only the right argument is missing, the user is likely trying to + * create a postfix operator, so give them a hint about why that does not + * work. But if both arguments are missing, do not mention postfix + * operators, as the user most likely simply neglected to mention the + * arguments. + */ if (!OidIsValid(typeId1) && !OidIsValid(typeId2)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("at least one of leftarg or rightarg must be specified"))); + errmsg("operator argument types must be specified"))); + if (!OidIsValid(typeId2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("operator right argument type must be specified"), + errdetail("Postfix operators are not supported."))); if (typeName1) { diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c index a6ebb56b1353..8114c20be57b 100644 --- a/src/backend/commands/policy.c +++ b/src/backend/commands/policy.c @@ -192,159 +192,139 @@ policy_role_list_to_array(List *roles, int *num_roles) /* * Load row security policy from the catalog, and store it in * the relation's relcache entry. + * + * Note that caller should have verified that pg_class.relrowsecurity + * is true for this relation. */ void RelationBuildRowSecurity(Relation relation) { MemoryContext rscxt; MemoryContext oldcxt = CurrentMemoryContext; - RowSecurityDesc *volatile rsdesc = NULL; + RowSecurityDesc *rsdesc; + Relation catalog; + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple tuple; /* * Create a memory context to hold everything associated with this * relation's row security policy. This makes it easy to clean up during - * a relcache flush. + * a relcache flush. However, to cover the possibility of an error + * partway through, we don't make the context long-lived till we're done. */ - rscxt = AllocSetContextCreate(CacheMemoryContext, + rscxt = AllocSetContextCreate(CurrentMemoryContext, "row security descriptor", ALLOCSET_SMALL_SIZES); + MemoryContextCopyAndSetIdentifier(rscxt, + RelationGetRelationName(relation)); + + rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc)); + rsdesc->rscxt = rscxt; /* - * Since rscxt lives under CacheMemoryContext, it is long-lived. Use a - * PG_TRY block to ensure it'll get freed if we fail partway through. + * Now scan pg_policy for RLS policies associated with this relation. + * Because we use the index on (polrelid, polname), we should consistently + * visit the rel's policies in name order, at least when system indexes + * aren't disabled. This simplifies equalRSDesc(). */ - PG_TRY(); - { - Relation catalog; - ScanKeyData skey; - SysScanDesc sscan; - HeapTuple tuple; - - MemoryContextCopyAndSetIdentifier(rscxt, - RelationGetRelationName(relation)); + catalog = table_open(PolicyRelationId, AccessShareLock); - rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc)); - rsdesc->rscxt = rscxt; + ScanKeyInit(&skey, + Anum_pg_policy_polrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(relation))); - catalog = table_open(PolicyRelationId, AccessShareLock); + sscan = systable_beginscan(catalog, PolicyPolrelidPolnameIndexId, true, + NULL, 1, &skey); - ScanKeyInit(&skey, - Anum_pg_policy_polrelid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationGetRelid(relation))); + while (HeapTupleIsValid(tuple = systable_getnext(sscan))) + { + Form_pg_policy policy_form = (Form_pg_policy) GETSTRUCT(tuple); + RowSecurityPolicy *policy; + Datum datum; + bool isnull; + char *str_value; - sscan = systable_beginscan(catalog, PolicyPolrelidPolnameIndexId, true, - NULL, 1, &skey); + policy = MemoryContextAllocZero(rscxt, sizeof(RowSecurityPolicy)); /* - * Loop through the row level security policies for this relation, if - * any. + * Note: we must be sure that pass-by-reference data gets copied into + * rscxt. We avoid making that context current over wider spans than + * we have to, though. */ - while (HeapTupleIsValid(tuple = systable_getnext(sscan))) - { - Datum value_datum; - char cmd_value; - bool permissive_value; - Datum roles_datum; - char *qual_value; - Expr *qual_expr; - char *with_check_value; - Expr *with_check_qual; - char *policy_name_value; - bool isnull; - RowSecurityPolicy *policy; - - /* - * Note: all the pass-by-reference data we collect here is either - * still stored in the tuple, or constructed in the caller's - * short-lived memory context. We must copy it into rscxt - * explicitly below. - */ - - /* Get policy command */ - value_datum = heap_getattr(tuple, Anum_pg_policy_polcmd, - RelationGetDescr(catalog), &isnull); - Assert(!isnull); - cmd_value = DatumGetChar(value_datum); - - /* Get policy permissive or restrictive */ - value_datum = heap_getattr(tuple, Anum_pg_policy_polpermissive, - RelationGetDescr(catalog), &isnull); - Assert(!isnull); - permissive_value = DatumGetBool(value_datum); - - /* Get policy name */ - value_datum = heap_getattr(tuple, Anum_pg_policy_polname, - RelationGetDescr(catalog), &isnull); - Assert(!isnull); - policy_name_value = NameStr(*(DatumGetName(value_datum))); - - /* Get policy roles */ - roles_datum = heap_getattr(tuple, Anum_pg_policy_polroles, - RelationGetDescr(catalog), &isnull); - /* shouldn't be null, but initdb doesn't mark it so, so check */ - if (isnull) - elog(ERROR, "unexpected null value in pg_policy.polroles"); - - /* Get policy qual */ - value_datum = heap_getattr(tuple, Anum_pg_policy_polqual, - RelationGetDescr(catalog), &isnull); - if (!isnull) - { - qual_value = TextDatumGetCString(value_datum); - qual_expr = (Expr *) stringToNode(qual_value); - } - else - qual_expr = NULL; - /* Get WITH CHECK qual */ - value_datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck, - RelationGetDescr(catalog), &isnull); - if (!isnull) - { - with_check_value = TextDatumGetCString(value_datum); - with_check_qual = (Expr *) stringToNode(with_check_value); - } - else - with_check_qual = NULL; + /* Get policy command */ + policy->polcmd = policy_form->polcmd; - /* Now copy everything into the cache context */ - MemoryContextSwitchTo(rscxt); + /* Get policy, permissive or restrictive */ + policy->permissive = policy_form->polpermissive; - policy = palloc0(sizeof(RowSecurityPolicy)); - policy->policy_name = pstrdup(policy_name_value); - policy->polcmd = cmd_value; - policy->permissive = permissive_value; - policy->roles = DatumGetArrayTypePCopy(roles_datum); - policy->qual = copyObject(qual_expr); - policy->with_check_qual = copyObject(with_check_qual); - policy->hassublinks = checkExprHasSubLink((Node *) qual_expr) || - checkExprHasSubLink((Node *) with_check_qual); + /* Get policy name */ + policy->policy_name = + MemoryContextStrdup(rscxt, NameStr(policy_form->polname)); - rsdesc->policies = lcons(policy, rsdesc->policies); + /* Get policy roles */ + datum = heap_getattr(tuple, Anum_pg_policy_polroles, + RelationGetDescr(catalog), &isnull); + /* shouldn't be null, but let's check for luck */ + if (isnull) + elog(ERROR, "unexpected null value in pg_policy.polroles"); + MemoryContextSwitchTo(rscxt); + policy->roles = DatumGetArrayTypePCopy(datum); + MemoryContextSwitchTo(oldcxt); + /* Get policy qual */ + datum = heap_getattr(tuple, Anum_pg_policy_polqual, + RelationGetDescr(catalog), &isnull); + if (!isnull) + { + str_value = TextDatumGetCString(datum); + MemoryContextSwitchTo(rscxt); + policy->qual = (Expr *) stringToNode(str_value); MemoryContextSwitchTo(oldcxt); + pfree(str_value); + } + else + policy->qual = NULL; - /* clean up some (not all) of the junk ... */ - if (qual_expr != NULL) - pfree(qual_expr); - if (with_check_qual != NULL) - pfree(with_check_qual); + /* Get WITH CHECK qual */ + datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck, + RelationGetDescr(catalog), &isnull); + if (!isnull) + { + str_value = TextDatumGetCString(datum); + MemoryContextSwitchTo(rscxt); + policy->with_check_qual = (Expr *) stringToNode(str_value); + MemoryContextSwitchTo(oldcxt); + pfree(str_value); } + else + policy->with_check_qual = NULL; - systable_endscan(sscan); - table_close(catalog, AccessShareLock); - } - PG_CATCH(); - { - /* Delete rscxt, first making sure it isn't active */ + /* We want to cache whether there are SubLinks in these expressions */ + policy->hassublinks = checkExprHasSubLink((Node *) policy->qual) || + checkExprHasSubLink((Node *) policy->with_check_qual); + + /* + * Add this object to list. For historical reasons, the list is built + * in reverse order. + */ + MemoryContextSwitchTo(rscxt); + rsdesc->policies = lcons(policy, rsdesc->policies); MemoryContextSwitchTo(oldcxt); - MemoryContextDelete(rscxt); - PG_RE_THROW(); } - PG_END_TRY(); - /* Success --- attach the policy descriptor to the relcache entry */ + systable_endscan(sscan); + table_close(catalog, AccessShareLock); + + /* + * Success. Reparent the descriptor's memory context under + * CacheMemoryContext so that it will live indefinitely, then attach the + * policy descriptor to the relcache entry. + */ + MemoryContextSetParent(rscxt, CacheMemoryContext); + relation->rd_rsdesc = rsdesc; } diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c index 2730e8203fc3..39fed59b7495 100644 --- a/src/backend/commands/proclang.c +++ b/src/backend/commands/proclang.c @@ -61,6 +61,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt) bool is_update; ObjectAddress myself, referenced; + ObjectAddresses *addrs; /* * Check permission @@ -191,30 +192,29 @@ CreateProceduralLanguage(CreatePLangStmt *stmt) /* dependency on extension */ recordDependencyOnCurrentExtension(&myself, is_update); + addrs = new_object_addresses(); + /* dependency on the PL handler function */ - referenced.classId = ProcedureRelationId; - referenced.objectId = handlerOid; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, ProcedureRelationId, handlerOid); + add_exact_object_address(&referenced, addrs); /* dependency on the inline handler function, if any */ if (OidIsValid(inlineOid)) { - referenced.classId = ProcedureRelationId; - referenced.objectId = inlineOid; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, ProcedureRelationId, inlineOid); + add_exact_object_address(&referenced, addrs); } /* dependency on the validator function, if any */ if (OidIsValid(valOid)) { - referenced.classId = ProcedureRelationId; - referenced.objectId = valOid; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, ProcedureRelationId, valOid); + add_exact_object_address(&referenced, addrs); } + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); + /* Post creation hook for new procedural language */ InvokeObjectPostCreateHook(LanguageRelationId, myself.objectId, 0); diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 552450122c8f..4b97ffc2256a 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -1842,7 +1842,7 @@ process_owned_by(Relation seqrel, List *owned_by, bool for_identity) /* Separate relname and attr name */ relname = list_truncate(list_copy(owned_by), nnames - 1); - attrname = strVal(lfirst(list_tail(owned_by))); + attrname = strVal(llast(owned_by)); /* Open and lock rel to ensure it won't go away meanwhile */ rel = makeRangeVarFromNameList(relname); diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 8ac4ab4ee66d..6b9f07bf8515 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -68,7 +68,8 @@ parse_subscription_options(List *options, bool *copy_data, char **synchronous_commit, bool *refresh, - bool *binary_given, bool *binary) + bool *binary_given, bool *binary, + bool *streaming_given, bool *streaming) { ListCell *lc; bool connect_given = false; @@ -104,6 +105,11 @@ parse_subscription_options(List *options, *binary_given = false; *binary = false; } + if (streaming) + { + *streaming_given = false; + *streaming = false; + } /* Parse options */ foreach(lc, options) @@ -199,6 +205,16 @@ parse_subscription_options(List *options, *binary_given = true; *binary = defGetBoolean(defel); } + else if (strcmp(defel->defname, "streaming") == 0 && streaming) + { + if (*streaming_given) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + + *streaming_given = true; + *streaming = defGetBoolean(defel); + } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -342,6 +358,8 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel) bool enabled_given; bool enabled; bool copy_data; + bool streaming; + bool streaming_given; char *synchronous_commit; char *conninfo; char *slotname; @@ -365,7 +383,8 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel) ©_data, &synchronous_commit, NULL, /* no "refresh" */ - &binary_given, &binary); + &binary_given, &binary, + &streaming_given, &streaming); /* * Since creating a replication slot is not transactional, rolling back @@ -439,6 +458,7 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel) values[Anum_pg_subscription_subowner - 1] = ObjectIdGetDatum(owner); values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(enabled); values[Anum_pg_subscription_subbinary - 1] = BoolGetDatum(binary); + values[Anum_pg_subscription_substream - 1] = BoolGetDatum(streaming); values[Anum_pg_subscription_subconninfo - 1] = CStringGetTextDatum(conninfo); if (slotname) @@ -732,6 +752,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt) char *synchronous_commit; bool binary_given; bool binary; + bool streaming_given; + bool streaming; parse_subscription_options(stmt->options, NULL, /* no "connect" */ @@ -741,7 +763,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt) NULL, /* no "copy_data" */ &synchronous_commit, NULL, /* no "refresh" */ - &binary_given, &binary); + &binary_given, &binary, + &streaming_given, &streaming); if (slotname_given) { @@ -773,6 +796,13 @@ AlterSubscription(AlterSubscriptionStmt *stmt) replaces[Anum_pg_subscription_subbinary - 1] = true; } + if (streaming_given) + { + values[Anum_pg_subscription_substream - 1] = + BoolGetDatum(streaming); + replaces[Anum_pg_subscription_substream - 1] = true; + } + update_tuple = true; break; } @@ -790,7 +820,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt) NULL, /* no "copy_data" */ NULL, /* no "synchronous_commit" */ NULL, /* no "refresh" */ - NULL, NULL); /* no "binary" */ + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no streaming */ Assert(enabled_given); if (!sub->slotname && enabled) @@ -840,8 +871,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt) ©_data, NULL, /* no "synchronous_commit" */ &refresh, - NULL, NULL); /* no "binary" */ - + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ values[Anum_pg_subscription_subpublications - 1] = publicationListToArray(stmt->publication); replaces[Anum_pg_subscription_subpublications - 1] = true; @@ -883,7 +914,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt) ©_data, NULL, /* no "synchronous_commit" */ NULL, /* no "refresh" */ - NULL, NULL); /* no "binary" */ + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ AlterSubscription_refresh(sub, copy_data); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index de6f9993d85d..c42da109c666 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -113,6 +113,7 @@ #include "utils/memutils.h" #include "utils/metrics_utils.h" #include "utils/partcache.h" +#include "utils/pg_locale.h" #include "utils/relcache.h" #include "utils/ruleutils.h" #include "utils/snapmgr.h" @@ -372,12 +373,14 @@ static bool ConstraintImpliedByRelConstraint(Relation scanrel, List *testConstraint, List *provenConstraint); static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName, Node *newDefault, LOCKMODE lockmode); +static ObjectAddress ATExecCookedColumnDefault(Relation rel, AttrNumber attnum, + Node *newDefault); static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode); static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode); static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode); -static void ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recursing); +static void ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode); static ObjectAddress ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode); static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode); @@ -519,7 +522,8 @@ static void ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partPa static void CreateInheritance(Relation child_rel, Relation parent_rel); static void RemoveInheritance(Relation child_rel, Relation parent_rel); static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel, - PartitionCmd *cmd); + PartitionCmd *cmd, + AlterTableUtilityContext *context); static void AttachPartitionEnsureIndexes(Relation rel, Relation attachrel); static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel, List *partConstraint, @@ -534,6 +538,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl); static List *GetParentedForeignKeyRefs(Relation partition); static void ATDetachCheckNoForeignKeyRefs(Relation partition); +static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll); static RangeVar *make_temp_table_name(Relation rel, BackendId id); static bool prebuild_temp_table(Relation rel, RangeVar *tmpname, DistributedBy *distro, @@ -1271,7 +1276,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, * Check first that the new partition's bound is valid and does not * overlap with any of existing partitions of the parent. */ - check_new_partition_bound(relname, parent, bound); + check_new_partition_bound(relname, parent, bound, pstate); /* * If the default partition exists, its partition constraints will @@ -1773,6 +1778,17 @@ RemoveRelations(DropStmt *drop) flags |= PERFORM_DELETION_CONCURRENTLY; } + /* + * Concurrent index drop cannot be used with partitioned indexes, + * either. + */ + if ((flags & PERFORM_DELETION_CONCURRENTLY) != 0 && + get_rel_relkind(relOid) == RELKIND_PARTITIONED_INDEX) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot drop partitioned index \"%s\" concurrently", + rel->relname))); + /* * If we're told to drop a partitioned index, we must acquire lock on * all the children of its parent partitioned table before proceeding. @@ -2241,6 +2257,11 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, /* * To fire triggers, we'll need an EState as well as a ResultRelInfo for * each relation. We don't need to call ExecOpenIndices, though. + * + * We put the ResultRelInfos in the es_opened_result_relations list, even + * though we don't have a range table and don't populate the + * es_result_relations array. That's a bit bogus, but it's enough to make + * ExecGetTriggerResultRel() find them. */ estate = CreateExecutorState(); resultRelInfos = (ResultRelInfo *) @@ -2255,10 +2276,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, 0, /* dummy rangetable index */ NULL, 0); + estate->es_opened_result_relations = + lappend(estate->es_opened_result_relations, resultRelInfo); resultRelInfo++; } - estate->es_result_relations = resultRelInfos; - estate->es_num_result_relations = list_length(rels); /* * Process all BEFORE STATEMENT TRUNCATE triggers before we begin @@ -2269,7 +2290,6 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, resultRelInfo = resultRelInfos; foreach(cell, rels) { - estate->es_result_relation_info = resultRelInfo; ExecBSTruncateTriggers(estate, resultRelInfo); resultRelInfo++; } @@ -2433,7 +2453,6 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, resultRelInfo = resultRelInfos; foreach(cell, rels) { - estate->es_result_relation_info = resultRelInfo; ExecASTruncateTriggers(estate, resultRelInfo); resultRelInfo++; } @@ -2560,8 +2579,8 @@ storage_name(char c) * 'schema' is the column/attribute definition for the table. (It's a list * of ColumnDef's.) It is destructively changed. * 'supers' is a list of OIDs of parent relations, already locked by caller. - * 'relpersistence' is a persistence type of the table. - * 'is_partition' tells if the table is a partition + * 'relpersistence' is the persistence type of the table. + * 'is_partition' tells if the table is a partition. * * Output arguments: * 'supconstr' receives a list of constraints belonging to the parents, @@ -2724,7 +2743,11 @@ MergeAttributes(List *schema, List *supers, char relpersistence, TupleDesc tupleDesc; TupleConstr *constr; AttrMap *newattmap; + List *inherited_defaults; + List *cols_with_defaults; AttrNumber parent_attno; + ListCell *lc1; + ListCell *lc2; /* caller already got lock */ relation = table_open(parent, NoLock); @@ -2815,6 +2838,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence, */ newattmap = make_attrmap(tupleDesc->natts); + /* We can't process inherited defaults until newattmap is complete. */ + inherited_defaults = cols_with_defaults = NIL; + for (parent_attno = 1; parent_attno <= tupleDesc->natts; parent_attno++) { @@ -2870,7 +2896,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, get_collation_name(defCollId), get_collation_name(attribute->attcollation)))); - /* Copy storage parameter */ + /* Copy/check storage parameter */ if (def->storage == 0) def->storage = attribute->attstorage; else if (def->storage != attribute->attstorage) @@ -2921,7 +2947,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, } /* - * Copy default if any + * Locate default if any */ if (attribute->atthasdef) { @@ -2943,23 +2969,59 @@ MergeAttributes(List *schema, List *supers, char relpersistence, Assert(this_default != NULL); /* - * If default expr could contain any vars, we'd need to fix - * 'em, but it can't; so default is ready to apply to child. - * - * If we already had a default from some prior parent, check - * to see if they are the same. If so, no problem; if not, - * mark the column as having a bogus default. Below, we will - * complain if the bogus default isn't overridden by the child - * schema. + * If it's a GENERATED default, it might contain Vars that + * need to be mapped to the inherited column(s)' new numbers. + * We can't do that till newattmap is ready, so just remember + * all the inherited default expressions for the moment. */ - Assert(def->raw_default == NULL); - if (def->cooked_default == NULL) - def->cooked_default = this_default; - else if (!equal(def->cooked_default, this_default)) - { - def->cooked_default = &bogus_marker; - have_bogus_defaults = true; - } + inherited_defaults = lappend(inherited_defaults, this_default); + cols_with_defaults = lappend(cols_with_defaults, def); + } + } + + /* + * Now process any inherited default expressions, adjusting attnos + * using the completed newattmap map. + */ + forboth(lc1, inherited_defaults, lc2, cols_with_defaults) + { + Node *this_default = (Node *) lfirst(lc1); + ColumnDef *def = (ColumnDef *) lfirst(lc2); + bool found_whole_row; + + /* Adjust Vars to match new table's column numbering */ + this_default = map_variable_attnos(this_default, + 1, 0, + newattmap, + InvalidOid, &found_whole_row); + + /* + * For the moment we have to reject whole-row variables. We could + * convert them, if we knew the new table's rowtype OID, but that + * hasn't been assigned yet. (A variable could only appear in a + * generation expression, so the error message is correct.) + */ + if (found_whole_row) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert whole-row table reference"), + errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".", + def->colname, + RelationGetRelationName(relation)))); + + /* + * If we already had a default from some prior parent, check to + * see if they are the same. If so, no problem; if not, mark the + * column as having a bogus default. Below, we will complain if + * the bogus default isn't overridden by the child schema. + */ + Assert(def->raw_default == NULL); + if (def->cooked_default == NULL) + def->cooked_default = this_default; + else if (!equal(def->cooked_default, this_default)) + { + def->cooked_default = &bogus_marker; + have_bogus_defaults = true; } } @@ -3178,7 +3240,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence, def->raw_default = newdef->raw_default; def->cooked_default = newdef->cooked_default; } - } else { @@ -4479,6 +4540,7 @@ AlterTableGetLockLevel(List *cmds) * Theoretically, these could be ShareRowExclusiveLock. */ case AT_ColumnDefault: + case AT_CookedColumnDefault: case AT_AlterConstraint: case AT_AddIndex: /* from ADD CONSTRAINT */ case AT_AddIndexConstraint: @@ -4624,6 +4686,10 @@ AlterTableGetLockLevel(List *cmds) cmd_lockmode = AccessShareLock; break; + case AT_AlterCollationRefreshVersion: + cmd_lockmode = AccessExclusiveLock; + break; + /* GPDB additions */ case AT_ExpandTable: case AT_ExpandPartitionTablePrepare: @@ -4825,6 +4891,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* No command-specific prep needed */ pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP; break; + case AT_CookedColumnDefault: /* add a pre-cooked default */ + /* This is currently used only in CREATE TABLE */ + /* (so the permission check really isn't necessary) */ + ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); + /* This command never recurses */ + pass = AT_PASS_ADD_OTHERCONSTR; + break; case AT_AddIdentity: ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); /* This command never recurses */ @@ -4863,7 +4936,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); - ATPrepDropExpression(rel, cmd, recursing); + ATPrepDropExpression(rel, cmd, recurse, recursing, lockmode); pass = AT_PASS_DROP; break; case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ @@ -4878,6 +4951,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* This command never recurses */ pass = AT_PASS_MISC; break; + case AT_AlterCollationRefreshVersion: /* ALTER COLLATION ... REFRESH + * VERSION */ + ATSimplePermissions(rel, ATT_INDEX); + /* This command never recurses */ + pass = AT_PASS_MISC; + break; case AT_SetStorage: /* ALTER COLUMN SET STORAGE */ ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); @@ -5388,6 +5467,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */ address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode); break; + case AT_CookedColumnDefault: /* add a pre-cooked default */ + address = ATExecCookedColumnDefault(rel, cmd->num, cmd->def); + break; case AT_AddIdentity: cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, cur_pass, context, &cmd->execStmts); @@ -5448,9 +5530,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, lockmode); break; case AT_AddConstraint: /* ADD CONSTRAINT */ - cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, - cur_pass, context, &cmd->execStmts); - /* Might not have gotten AddConstraint back from parse transform */ + /* Transform the command only during initial examination */ + if (cur_pass == AT_PASS_ADD_CONSTR) + cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, + false, lockmode, + cur_pass, context, &cmd->execStmts); + /* Depending on constraint type, might be no more work to do now */ if (cmd != NULL) address = ATExecAddConstraint(wqueue, tab, rel, @@ -5458,9 +5543,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, false, false, lockmode); break; case AT_AddConstraintRecurse: /* ADD CONSTRAINT with recursion */ - cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, true, lockmode, - cur_pass, context, &cmd->execStmts); - /* Might not have gotten AddConstraint back from parse transform */ + /* Transform the command only during initial examination */ + if (cur_pass == AT_PASS_ADD_CONSTR) + cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, + true, lockmode, + cur_pass, context, &cmd->execStmts); + /* Depending on constraint type, might be no more work to do now */ if (cmd != NULL) address = ATExecAddConstraint(wqueue, tab, rel, @@ -5686,7 +5774,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, cur_pass, context, &cmd->execStmts); Assert(cmd != NULL); if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def); + ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def, + context); else ATExecAttachPartitionIdx(wqueue, rel, ((PartitionCmd *) cmd->def)->name); @@ -5699,6 +5788,11 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name); break; + case AT_AlterCollationRefreshVersion: + /* ATPrepCmd ensured it must be an index */ + Assert(rel->rd_rel->relkind == RELKIND_INDEX); + ATExecAlterCollationRefreshVersion(rel, cmd->object); + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -5794,82 +5888,95 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, foreach(lc, atstmt->cmds) { AlterTableCmd *cmd2 = lfirst_node(AlterTableCmd, lc); + int pass; - if (newcmd == NULL && - (cmd->subtype == cmd2->subtype || - (cmd->subtype == AT_AddConstraintRecurse && - cmd2->subtype == AT_AddConstraint))) + /* + * This switch need only cover the subcommand types that can be added + * by parse_utilcmd.c; otherwise, we'll use the default strategy of + * executing the subcommand immediately, as a substitute for the + * original subcommand. (Note, however, that this does cause + * AT_AddConstraint subcommands to be rescheduled into later passes, + * which is important for index and foreign key constraints.) + * + * We assume we needn't do any phase-1 checks for added subcommands. + */ + switch (cmd2->subtype) { - /* Found the transformed version of our subcommand */ - cmd2->subtype = cmd->subtype; /* copy recursion flag */ - newcmd = cmd2; + case AT_SetNotNull: + /* Need command-specific recursion decision */ + ATPrepSetNotNull(wqueue, rel, cmd2, + recurse, false, + lockmode, context); + pass = AT_PASS_COL_ATTRS; + break; + case AT_AddIndex: + /* This command never recurses */ + /* No command-specific prep needed */ + pass = AT_PASS_ADD_INDEX; + break; + case AT_AddIndexConstraint: + /* This command never recurses */ + /* No command-specific prep needed */ + pass = AT_PASS_ADD_INDEXCONSTR; + break; + case AT_AddConstraint: + /* Recursion occurs during execution phase */ + if (recurse) + cmd2->subtype = AT_AddConstraintRecurse; + switch (castNode(Constraint, cmd2->def)->contype) + { + case CONSTR_PRIMARY: + case CONSTR_UNIQUE: + case CONSTR_EXCLUSION: + pass = AT_PASS_ADD_INDEXCONSTR; + break; + default: + pass = AT_PASS_ADD_OTHERCONSTR; + break; + } + break; + case AT_AlterColumnGenericOptions: + /* This command never recurses */ + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; + default: + pass = cur_pass; + break; + } - /* - * In the QD save transformed version of definition for executing - * in the QE - */ - if (Gp_role == GP_ROLE_DISPATCH) - cmd->def = newcmd->def; + if (pass < cur_pass) + { + /* Cannot schedule into a pass we already finished */ + elog(ERROR, "ALTER TABLE scheduling failure: too late for pass %d", + pass); + } + else if (pass > cur_pass) + { + /* OK, queue it up for later */ + tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd2); } else { - int pass; - /* - * Schedule added subcommand appropriately. We assume we needn't - * do any phase-1 checks for it. This switch only has to cover - * the subcommand types that can be added by parse_utilcmd.c. + * We should see at most one subcommand for the current pass, + * which is the transformed version of the original subcommand. */ - switch (cmd2->subtype) + if (newcmd == NULL && cmd->subtype == cmd2->subtype) { - case AT_SetNotNull: - /* Need command-specific recursion decision */ - ATPrepSetNotNull(wqueue, rel, cmd2, - recurse, false, - lockmode, context); - pass = AT_PASS_COL_ATTRS; - break; - case AT_AddIndex: - /* This command never recurses */ - /* No command-specific prep needed */ - pass = AT_PASS_ADD_INDEX; - break; - case AT_AddIndexConstraint: - /* This command never recurses */ - /* No command-specific prep needed */ - pass = AT_PASS_ADD_INDEXCONSTR; - break; - case AT_AddConstraint: - /* Recursion occurs during execution phase */ - if (recurse) - cmd2->subtype = AT_AddConstraintRecurse; - switch (castNode(Constraint, cmd2->def)->contype) - { - case CONSTR_PRIMARY: - case CONSTR_UNIQUE: - case CONSTR_EXCLUSION: - pass = AT_PASS_ADD_INDEXCONSTR; - break; - default: - pass = AT_PASS_ADD_OTHERCONSTR; - break; - } - break; - case AT_AlterColumnGenericOptions: - /* This command never recurses */ - /* No command-specific prep needed */ - pass = AT_PASS_MISC; - break; - default: - elog(ERROR, "unexpected AlterTableType: %d", - (int) cmd2->subtype); - pass = AT_PASS_UNSET; - break; + /* Found the transformed version of our subcommand */ + newcmd = cmd2; + + /* + * In the QD save transformed version of definition for + * executing in the QE + */ + if (Gp_role == GP_ROLE_DISPATCH) + cmd->def = newcmd->def; } - /* Must be for a later pass than we're currently doing */ - if (pass <= cur_pass) - elog(ERROR, "ALTER TABLE scheduling failure"); - tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd2); + else + elog(ERROR, "ALTER TABLE scheduling failure: bogus item for pass %d", + pass); } } @@ -7184,14 +7291,10 @@ ATSimpleRecursion(List **wqueue, Relation rel, AlterTableUtilityContext *context) { /* - * Propagate to children if desired. Only plain tables, foreign tables - * and partitioned tables have children, so no need to search for other - * relkinds. + * Propagate to children, if desired and if there are (or might be) any + * children. */ - if (recurse && - (rel->rd_rel->relkind == RELKIND_RELATION || - rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE || - rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)) + if (recurse && rel->rd_rel->relhassubclass) { Oid relid = RelationGetRelid(rel); ListCell *child; @@ -7253,7 +7356,7 @@ ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode) inh = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL); /* first element is the parent rel; must ignore it */ - for_each_cell(cell, inh, list_second_cell(inh)) + for_each_from(cell, inh, 1) { Relation childrel; @@ -8428,6 +8531,41 @@ ATPrepSetNotNull(List **wqueue, Relation rel, if (recursing) return; + /* + * If the target column is already marked NOT NULL, we can skip recursing + * to children, because their columns should already be marked NOT NULL as + * well. But there's no point in checking here unless the relation has + * some children; else we can just wait till execution to check. (If it + * does have children, however, this can save taking per-child locks + * unnecessarily. This greatly improves concurrency in some parallel + * restore scenarios.) + * + * Unfortunately, we can only apply this optimization to partitioned + * tables, because traditional inheritance doesn't enforce that child + * columns be NOT NULL when their parent is. (That's a bug that should + * get fixed someday.) + */ + if (rel->rd_rel->relhassubclass && + rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + HeapTuple tuple; + bool attnotnull; + + tuple = SearchSysCacheAttName(RelationGetRelid(rel), cmd->name); + + /* Might as well throw the error now, if name is bad */ + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + cmd->name, RelationGetRelationName(rel)))); + + attnotnull = ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull; + ReleaseSysCache(tuple); + if (attnotnull) + return; + } + /* * If we have ALTER TABLE ONLY ... SET NOT NULL on a partitioned table, * apply ALTER TABLE ... CHECK NOT NULL to every child. Otherwise, use @@ -8594,7 +8732,7 @@ NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr) if (ConstraintImpliedByRelConstraint(rel, list_make1(nnulltest), NIL)) { ereport(DEBUG1, - (errmsg("existing constraints on column \"%s\".\"%s\" are sufficient to prove that it does not contain nulls", + (errmsg("existing constraints on column \"%s.%s\" are sufficient to prove that it does not contain nulls", RelationGetRelationName(rel), NameStr(attr->attname)))); return true; } @@ -8692,6 +8830,37 @@ ATExecColumnDefault(Relation rel, const char *colName, return address; } +/* + * Add a pre-cooked default expression. + * + * Return the address of the affected column. + */ +static ObjectAddress +ATExecCookedColumnDefault(Relation rel, AttrNumber attnum, + Node *newDefault) +{ + ObjectAddress address; + + /* We assume no checking is required */ + + /* + * Remove any old default for the column. We use RESTRICT here for + * safety, but at present we do not expect anything to depend on the + * default. (In ordinary cases, there could not be a default in place + * anyway, but it's possible when combining LIKE with inheritance.) + */ + RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false, + true); + + (void) StoreAttrDefault(rel, attnum, newDefault, + NULL, NULL, NULL, /* missing val stuff */ + true, false); + + ObjectAddressSubSet(address, RelationRelationId, + RelationGetRelid(rel), attnum); + return address; +} + /* * ALTER TABLE ALTER COLUMN ADD IDENTITY * @@ -8926,8 +9095,24 @@ ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE * ALTER TABLE ALTER COLUMN DROP EXPRESSION */ static void -ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recursing) +ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode) { + /* + * Reject ONLY if there are child tables. We could implement this, but it + * is a bit complicated. GENERATED clauses must be attached to the column + * definition and cannot be added later like DEFAULT, so if a child table + * has a generation expression that the parent does not have, the child + * column will necessarily be an attlocal column. So to implement ONLY + * here, we'd need extra code to update attislocal of the direct child + * tables, somewhat similar to how DROP COLUMN does it, so that the + * resulting state can be properly dumped and restored. + */ + if (!recurse && + find_inheritance_children(RelationGetRelid(rel), lockmode)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ALTER TABLE / DROP EXPRESSION must be applied to child tables too"))); + /* * Cannot drop generation expression from inherited columns. */ @@ -20191,7 +20376,8 @@ QueuePartitionConstraintValidation(List **wqueue, Relation scanrel, * Return the address of the newly attached partition. */ static ObjectAddress -ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) +ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd, + AlterTableUtilityContext *context) { Relation attachrel, catalog; @@ -20206,6 +20392,9 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) const char *trigger_name; Oid defaultPartOid; List *partBoundConstraint; + ParseState *pstate = make_parsestate(NULL); + + pstate->p_sourcetext = context->queryString; /* * We must lock the default partition if one exists, because attaching a @@ -20389,7 +20578,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) * error. */ check_new_partition_bound(RelationGetRelationName(attachrel), rel, - cmd->bound); + cmd->bound, pstate); /* OK to create inheritance. Rest of the checks performed there */ CreateInheritance(attachrel, rel); @@ -21513,3 +21702,17 @@ ATDetachCheckNoForeignKeyRefs(Relation partition) table_close(rel, NoLock); } } + +/* + * ALTER INDEX ... ALTER COLLATION ... REFRESH VERSION + * + * Update refobjversion to the current collation version by force. This clears + * warnings about version mismatches without the need to run REINDEX, + * potentially hiding corruption due to ordering changes. + */ +static void +ATExecAlterCollationRefreshVersion(Relation rel, List *coll) +{ + index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false)); + CacheInvalidateRelcache(rel); +} diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 176a434e8d5d..1bff608c4a28 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -35,6 +35,7 @@ #include "commands/defrem.h" #include "commands/trigger.h" #include "executor/executor.h" +#include "executor/execPartition.h" #include "miscadmin.h" #include "nodes/execnodes.h" #include "nodes/bitmapset.h" @@ -3019,6 +3020,9 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo) } +/* + * Fetch tuple into "oldslot", dealing with locking and EPQ if necessary + */ static bool GetTupleForTrigger(EState *estate, EPQState *epqstate, @@ -4321,7 +4325,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, if (local_estate) { - ExecCleanUpTriggerState(estate); + ExecCloseResultRelations(estate); ExecResetTupleTable(estate->es_tupleTable, false); FreeExecutorState(estate); } @@ -4386,9 +4390,10 @@ GetAfterTriggersTableData(Oid relid, CmdType cmdType) * If there are no triggers in 'trigdesc' that request relevant transition * tables, then return NULL. * - * The resulting object can be passed to the ExecAR* functions. The caller - * should set tcs_map or tcs_original_insert_tuple as appropriate when dealing - * with child tables. + * The resulting object can be passed to the ExecAR* functions. When + * dealing with child tables, the caller can set tcs_original_insert_tuple + * to avoid having to reconstruct the original tuple in the root table's + * format. * * Note that we copy the flags from a parent table into this struct (rather * than subsequently using the relation's TriggerDesc directly) so that we can @@ -5499,7 +5504,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, if (row_trigger && transition_capture != NULL) { TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple; - TupleConversionMap *map = transition_capture->tcs_map; + TupleConversionMap *map = relinfo->ri_ChildToRootMap; bool delete_old_table = transition_capture->tcs_delete_old_table; bool update_old_table = transition_capture->tcs_update_old_table; bool update_new_table = transition_capture->tcs_update_new_table; diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c index 421fb1bbe6f4..5ae59d996a65 100644 --- a/src/backend/commands/tsearchcmds.c +++ b/src/backend/commands/tsearchcmds.c @@ -136,42 +136,41 @@ makeParserDependencies(HeapTuple tuple) Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple); ObjectAddress myself, referenced; + ObjectAddresses *addrs; - myself.classId = TSParserRelationId; - myself.objectId = prs->oid; - myself.objectSubId = 0; - - /* dependency on namespace */ - referenced.classId = NamespaceRelationId; - referenced.objectId = prs->prsnamespace; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(myself, TSParserRelationId, prs->oid); /* dependency on extension */ recordDependencyOnCurrentExtension(&myself, false); - /* dependencies on functions */ - referenced.classId = ProcedureRelationId; - referenced.objectSubId = 0; + addrs = new_object_addresses(); - referenced.objectId = prs->prsstart; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + /* dependency on namespace */ + ObjectAddressSet(referenced, NamespaceRelationId, prs->prsnamespace); + add_exact_object_address(&referenced, addrs); + + /* dependencies on functions */ + ObjectAddressSet(referenced, ProcedureRelationId, prs->prsstart); + add_exact_object_address(&referenced, addrs); referenced.objectId = prs->prstoken; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); referenced.objectId = prs->prsend; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); referenced.objectId = prs->prslextype; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); if (OidIsValid(prs->prsheadline)) { referenced.objectId = prs->prsheadline; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); + return myself; } @@ -326,16 +325,9 @@ makeDictionaryDependencies(HeapTuple tuple) Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple); ObjectAddress myself, referenced; + ObjectAddresses *addrs; - myself.classId = TSDictionaryRelationId; - myself.objectId = dict->oid; - myself.objectSubId = 0; - - /* dependency on namespace */ - referenced.classId = NamespaceRelationId; - referenced.objectId = dict->dictnamespace; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(myself, TSDictionaryRelationId, dict->oid); /* dependency on owner */ recordDependencyOnOwner(myself.classId, myself.objectId, dict->dictowner); @@ -343,11 +335,18 @@ makeDictionaryDependencies(HeapTuple tuple) /* dependency on extension */ recordDependencyOnCurrentExtension(&myself, false); + addrs = new_object_addresses(); + + /* dependency on namespace */ + ObjectAddressSet(referenced, NamespaceRelationId, dict->dictnamespace); + add_exact_object_address(&referenced, addrs); + /* dependency on template */ - referenced.classId = TSTemplateRelationId; - referenced.objectId = dict->dicttemplate; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(referenced, TSTemplateRelationId, dict->dicttemplate); + add_exact_object_address(&referenced, addrs); + + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); return myself; } @@ -696,33 +695,32 @@ makeTSTemplateDependencies(HeapTuple tuple) Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple); ObjectAddress myself, referenced; + ObjectAddresses *addrs; - myself.classId = TSTemplateRelationId; - myself.objectId = tmpl->oid; - myself.objectSubId = 0; - - /* dependency on namespace */ - referenced.classId = NamespaceRelationId; - referenced.objectId = tmpl->tmplnamespace; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSet(myself, TSTemplateRelationId, tmpl->oid); /* dependency on extension */ recordDependencyOnCurrentExtension(&myself, false); - /* dependencies on functions */ - referenced.classId = ProcedureRelationId; - referenced.objectSubId = 0; + addrs = new_object_addresses(); - referenced.objectId = tmpl->tmpllexize; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + /* dependency on namespace */ + ObjectAddressSet(referenced, NamespaceRelationId, tmpl->tmplnamespace); + add_exact_object_address(&referenced, addrs); + + /* dependencies on functions */ + ObjectAddressSet(referenced, ProcedureRelationId, tmpl->tmpllexize); + add_exact_object_address(&referenced, addrs); if (OidIsValid(tmpl->tmplinit)) { referenced.objectId = tmpl->tmplinit; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + add_exact_object_address(&referenced, addrs); } + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); + return myself; } diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 1b70ecd536de..0f22c0aa01e1 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -395,7 +395,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to change bypassrls attribute"))); + errmsg("must be superuser to create bypassrls users"))); } else { @@ -1018,8 +1018,10 @@ AlterRole(AlterRoleStmt *stmt) roleid = authform->oid; /* - * To mess with a superuser you gotta be superuser; else you need - * createrole, or just want to change your own password + * To mess with a superuser or replication role in any way you gotta be + * superuser. We also insist on superuser to change the BYPASSRLS + * property. Otherwise, if you don't have createrole, you're only allowed + * to change your own password. */ bWas_super = ((Form_pg_authid) GETSTRUCT(tuple))->rolsuper; @@ -1029,16 +1031,16 @@ AlterRole(AlterRoleStmt *stmt) if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to alter superusers"))); + errmsg("must be superuser to alter superuser roles or change superuser attribute"))); } else if (authform->rolreplication || isreplication >= 0) { if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to alter replication users"))); + errmsg("must be superuser to alter replication roles or change replication attribute"))); } - else if (authform->rolbypassrls || bypassrls >= 0) + else if (bypassrls >= 0) { if (!superuser()) ereport(ERROR, @@ -1047,11 +1049,11 @@ AlterRole(AlterRoleStmt *stmt) } else if (!have_createrole_privilege()) { + /* We already checked issuper, isreplication, and bypassrls */ if (!(inherit < 0 && createrole < 0 && createdb < 0 && canlogin < 0 && - isreplication < 0 && !dconnlimit && !rolemembers && !validUntil && diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 2b03ac8ea0e9..4ddef09bfe63 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -1147,6 +1147,8 @@ get_all_vacuum_rels(int options) /* * vacuum_set_xid_limits() -- compute oldestXmin and freeze cutoff points * + * Input parameters are the target relation, applicable freeze age settings. + * * The output parameters are: * - oldestXmin is the cutoff value used to distinguish whether tuples are * DEAD or RECENTLY_DEAD (see HeapTupleSatisfiesVacuum). @@ -1202,12 +1204,13 @@ vacuum_set_xid_limits(Relation rel, TransactionId limit_xmin; TimestampTz limit_ts; - if (TransactionIdLimitedForOldSnapshots(*oldestXmin, rel, &limit_xmin, &limit_ts)) + if (TransactionIdLimitedForOldSnapshots(*oldestXmin, rel, + &limit_xmin, &limit_ts)) { /* * TODO: We should only set the threshold if we are pruning on the - * basis of the increased limits. Not as crucial here as it is for - * opportunistic pruning (which often happens at a much higher + * basis of the increased limits. Not as crucial here as it is + * for opportunistic pruning (which often happens at a much higher * frequency), but would still be a significant improvement. */ SetOldSnapshotThresholdTimestamp(limit_ts, limit_xmin); @@ -1368,8 +1371,8 @@ vacuum_set_xid_limits(Relation rel, * live tuples seen; but if we did not, we should not blindly extrapolate * from that number, since VACUUM may have scanned a quite nonrandom * subset of the table. When we have only partial information, we take - * the old value of pg_class.reltuples as a measurement of the - * tuple density in the unscanned pages. + * the old value of pg_class.reltuples/pg_class.relpages as a measurement + * of the tuple density in the unscanned pages. * * Note: scanned_tuples should count only *live* tuples, since * pg_class.reltuples is defined that way. @@ -1392,18 +1395,16 @@ vac_estimate_reltuples(Relation relation, /* * If scanned_pages is zero but total_pages isn't, keep the existing value - * of reltuples. (Note: callers should avoid updating the pg_class - * statistics in this situation, since no new information has been - * provided.) + * of reltuples. (Note: we might be returning -1 in this case.) */ if (scanned_pages == 0) return old_rel_tuples; /* - * If old value of relpages is zero, old density is indeterminate; we - * can't do much except scale up scanned_tuples to match total_pages. + * If old density is unknown, we can't do much except scale up + * scanned_tuples to match total_pages. */ - if (old_rel_pages == 0) + if (old_rel_tuples < 0 || old_rel_pages == 0) return floor((scanned_tuples / scanned_pages) * total_pages + 0.5); /* diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 2b6e1c856e6d..14920978f8a1 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -1177,23 +1177,6 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } - case T_AlternativeSubPlan: - { - AlternativeSubPlan *asplan = (AlternativeSubPlan *) node; - AlternativeSubPlanState *asstate; - - if (!state->parent) - elog(ERROR, "AlternativeSubPlan found with no parent plan"); - - asstate = ExecInitAlternativeSubPlan(asplan, state->parent); - - scratch.opcode = EEOP_ALTERNATIVE_SUBPLAN; - scratch.d.alternative_subplan.asstate = asstate; - - ExprEvalPushStep(state, &scratch); - break; - } - case T_FieldSelect: { FieldSelect *fselect = (FieldSelect *) node; @@ -3596,7 +3579,7 @@ ExecBuildAggTransCall(ExprState *state, AggState *aggstate, * * For ordered aggregates: * - * Only need to choose between the faster path for a single orderred + * Only need to choose between the faster path for a single ordered * column, and the one between multiple columns. Checking strictness etc * is done when finalizing the aggregate. See * process_ordered_aggregate_{single, multi} and diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 096e3001a2c9..bfae333ab20f 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -440,7 +440,6 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_ROWIDEXPR, &&CASE_EEOP_WINDOW_FUNC, &&CASE_EEOP_SUBPLAN, - &&CASE_EEOP_ALTERNATIVE_SUBPLAN, &&CASE_EEOP_AGG_STRICT_DESERIALIZE, &&CASE_EEOP_AGG_DESERIALIZE, &&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS, @@ -1601,14 +1600,6 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } - EEO_CASE(EEOP_ALTERNATIVE_SUBPLAN) - { - /* too complex for an inline implementation */ - ExecEvalAlternativeSubPlan(state, op, econtext); - - EEO_NEXT(); - } - /* evaluate a strict aggregate deserialization function */ EEO_CASE(EEOP_AGG_STRICT_DESERIALIZE) { @@ -4091,20 +4082,6 @@ ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext) *op->resvalue = ExecSubPlan(sstate, econtext, op->resnull); } -/* - * Hand off evaluation of an alternative subplan to nodeSubplan.c - */ -void -ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext) -{ - AlternativeSubPlanState *asstate = op->d.alternative_subplan.asstate; - - /* could potentially be nested, so make sure there's enough stack */ - check_stack_depth(); - - *op->resvalue = ExecAlternativeSubPlan(asstate, econtext, op->resnull); -} - /* * Evaluate a wholerow Var expression. * diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c index 658c658a0439..0c75bf61d76f 100644 --- a/src/backend/executor/execIndexing.c +++ b/src/backend/executor/execIndexing.c @@ -274,7 +274,8 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo) * ---------------------------------------------------------------- */ List * -ExecInsertIndexTuples(TupleTableSlot *slot, +ExecInsertIndexTuples(ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, EState *estate, bool noDupErr, bool *specConflict, @@ -282,7 +283,6 @@ ExecInsertIndexTuples(TupleTableSlot *slot, { ItemPointer tupleid = &slot->tts_tid; List *result = NIL; - ResultRelInfo *resultRelInfo; int i; int numIndices; RelationPtr relationDescs; @@ -297,7 +297,6 @@ ExecInsertIndexTuples(TupleTableSlot *slot, /* * Get information from the result relation info structure. */ - resultRelInfo = estate->es_result_relation_info; numIndices = resultRelInfo->ri_NumIndices; relationDescs = resultRelInfo->ri_IndexRelationDescs; indexInfoArray = resultRelInfo->ri_IndexRelationInfo; @@ -483,11 +482,10 @@ ExecInsertIndexTuples(TupleTableSlot *slot, * ---------------------------------------------------------------- */ bool -ExecCheckIndexConstraints(TupleTableSlot *slot, +ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate, ItemPointer conflictTid, List *arbiterIndexes) { - ResultRelInfo *resultRelInfo; int i; int numIndices; RelationPtr relationDescs; @@ -505,7 +503,6 @@ ExecCheckIndexConstraints(TupleTableSlot *slot, /* * Get information from the result relation info structure. */ - resultRelInfo = estate->es_result_relation_info; numIndices = resultRelInfo->ri_NumIndices; relationDescs = resultRelInfo->ri_IndexRelationDescs; indexInfoArray = resultRelInfo->ri_IndexRelationInfo; diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c index 40d700dd9e23..1a822ff24b38 100644 --- a/src/backend/executor/execJunk.c +++ b/src/backend/executor/execJunk.c @@ -54,23 +54,48 @@ * * The source targetlist is passed in. The output tuple descriptor is * built from the non-junk tlist entries. - * An optional resultSlot can be passed as well. + * An optional resultSlot can be passed as well; otherwise, we create one. */ JunkFilter * ExecInitJunkFilter(List *targetList, TupleTableSlot *slot) { - JunkFilter *junkfilter; TupleDesc cleanTupType; - int cleanLength; - AttrNumber *cleanMap; - ListCell *t; - AttrNumber cleanResno; /* * Compute the tuple descriptor for the cleaned tuple. */ cleanTupType = ExecCleanTypeFromTL(targetList); + /* + * The rest is the same as ExecInitJunkFilterInsertion, ie, we want to map + * every non-junk targetlist column into the output tuple. + */ + return ExecInitJunkFilterInsertion(targetList, cleanTupType, slot); +} + +/* + * ExecInitJunkFilterInsertion + * + * Initialize a JunkFilter for insertions into a table. + * + * Here, we are given the target "clean" tuple descriptor rather than + * inferring it from the targetlist. Although the target descriptor can + * contain deleted columns, that is not of concern here, since the targetlist + * should contain corresponding NULL constants (cf. ExecCheckPlanOutput). + * It is assumed that the caller has checked that the table's columns match up + * with the non-junk columns of the targetlist. + */ +JunkFilter * +ExecInitJunkFilterInsertion(List *targetList, + TupleDesc cleanTupType, + TupleTableSlot *slot) +{ + JunkFilter *junkfilter; + int cleanLength; + AttrNumber *cleanMap; + ListCell *t; + AttrNumber cleanResno; + /* * Use the given slot, or make a new slot if we weren't given one. */ @@ -93,17 +118,18 @@ ExecInitJunkFilter(List *targetList, TupleTableSlot *slot) if (cleanLength > 0) { cleanMap = (AttrNumber *) palloc(cleanLength * sizeof(AttrNumber)); - cleanResno = 1; + cleanResno = 0; foreach(t, targetList) { TargetEntry *tle = lfirst(t); if (!tle->resjunk) { - cleanMap[cleanResno - 1] = tle->resno; + cleanMap[cleanResno] = tle->resno; cleanResno++; } } + Assert(cleanResno == cleanLength); } else cleanMap = NULL; diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index a764c06e3b64..e91a1410ee5d 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1709,91 +1709,6 @@ InitPlan(QueryDesc *queryDesc, int eflags) estate->es_plannedstmt = plannedstmt; - /* - * Initialize ResultRelInfo data structures, and open the result rels. - * - * CDB: Note that we need this info even if we aren't the slice that will be doing - * the actual updating, since it's where we learn things, such as if the row needs to - * contain OIDs or not. - */ - if (plannedstmt->resultRelations) - { - List *resultRelations = plannedstmt->resultRelations; - int numResultRelations = list_length(resultRelations); - ResultRelInfo *resultRelInfos; - ResultRelInfo *resultRelInfo; - - resultRelInfos = (ResultRelInfo *) - palloc(numResultRelations * sizeof(ResultRelInfo)); - resultRelInfo = resultRelInfos; - foreach(l, plannedstmt->resultRelations) - { - Index resultRelationIndex = lfirst_int(l); - Relation resultRelation; - - resultRelation = ExecGetRangeTableRelation(estate, - resultRelationIndex); - InitResultRelInfo(resultRelInfo, - resultRelation, - resultRelationIndex, - NULL, - estate->es_instrument); - resultRelInfo++; - } - estate->es_result_relations = resultRelInfos; - estate->es_num_result_relations = numResultRelations; - - /* es_result_relation_info is NULL except when within ModifyTable */ - estate->es_result_relation_info = NULL; - - /* - * In the partitioned result relation case, also build ResultRelInfos - * for all the partitioned table roots, because we will need them to - * fire statement-level triggers, if any. - */ - if (plannedstmt->rootResultRelations) - { - int num_roots = list_length(plannedstmt->rootResultRelations); - - resultRelInfos = (ResultRelInfo *) - palloc(num_roots * sizeof(ResultRelInfo)); - resultRelInfo = resultRelInfos; - foreach(l, plannedstmt->rootResultRelations) - { - Index resultRelIndex = lfirst_int(l); - Relation resultRelDesc; - - resultRelDesc = ExecGetRangeTableRelation(estate, - resultRelIndex); - InitResultRelInfo(resultRelInfo, - resultRelDesc, - resultRelIndex, - NULL, - estate->es_instrument); - resultRelInfo++; - } - - estate->es_root_result_relations = resultRelInfos; - estate->es_num_root_result_relations = num_roots; - } - else - { - estate->es_root_result_relations = NULL; - estate->es_num_root_result_relations = 0; - } - } - else - { - /* - * if no result relation, then set state appropriately - */ - estate->es_result_relations = NULL; - estate->es_num_result_relations = 0; - estate->es_result_relation_info = NULL; - estate->es_root_result_relations = NULL; - estate->es_num_root_result_relations = 0; - } - /* * Next, build the ExecRowMark array from the PlanRowMark(s), if any. */ @@ -2312,8 +2227,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation partition_root, int instrument_options) { - List *partition_check = NIL; - MemSet(resultRelInfo, 0, sizeof(ResultRelInfo)); resultRelInfo->type = T_ResultRelInfo; resultRelInfo->ri_RangeTableIndex = resultRelationIndex; @@ -2359,25 +2272,11 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, resultRelInfo->ri_ReturningSlot = NULL; resultRelInfo->ri_TrigOldSlot = NULL; resultRelInfo->ri_TrigNewSlot = NULL; - - /* - * Partition constraint, which also includes the partition constraint of - * all the ancestors that are partitions. Note that it will be checked - * even in the case of tuple-routing where this table is the target leaf - * partition, if there any BR triggers defined on the table. Although - * tuple-routing implicitly preserves the partition constraint of the - * target partition for a given row, the BR triggers may change the row - * such that the constraint is no longer satisfied, which we must fail for - * by checking it explicitly. - * - * If this is a partitioned table, the partition constraint (if any) of a - * given row will be checked just before performing tuple-routing. - */ - partition_check = RelationGetPartitionQual(resultRelationDesc); - - resultRelInfo->ri_PartitionCheck = partition_check; resultRelInfo->ri_PartitionRoot = partition_root; - resultRelInfo->ri_PartitionInfo = NULL; /* may be set later */ + resultRelInfo->ri_RootToPartitionMap = NULL; /* set by + * ExecInitRoutingInfo */ + resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */ + resultRelInfo->ri_ChildToRootMap = NULL; resultRelInfo->ri_CopyMultiInsertBuffer = NULL; } @@ -2387,8 +2286,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, * * Most of the time, triggers are fired on one of the result relations of the * query, and so we can just return a member of the es_result_relations array, - * or the es_root_result_relations array (if any), or the - * es_tuple_routing_result_relations list (if any). (Note: in self-join + * or the es_tuple_routing_result_relations list (if any). (Note: in self-join * situations there might be multiple members with the same OID; if so it * doesn't matter which one we pick.) * @@ -2405,30 +2303,16 @@ ResultRelInfo * ExecGetTriggerResultRel(EState *estate, Oid relid) { ResultRelInfo *rInfo; - int nr; ListCell *l; Relation rel; MemoryContext oldcontext; /* First, search through the query result relations */ - rInfo = estate->es_result_relations; - nr = estate->es_num_result_relations; - while (nr > 0) - { - if (RelationGetRelid(rInfo->ri_RelationDesc) == relid) - return rInfo; - rInfo++; - nr--; - } - /* Second, search through the root result relations, if any */ - rInfo = estate->es_root_result_relations; - nr = estate->es_num_root_result_relations; - while (nr > 0) + foreach(l, estate->es_opened_result_relations) { + rInfo = lfirst(l); if (RelationGetRelid(rInfo->ri_RelationDesc) == relid) return rInfo; - rInfo++; - nr--; } /* @@ -2481,35 +2365,6 @@ ExecGetTriggerResultRel(EState *estate, Oid relid) return rInfo; } -/* - * Close any relations that have been opened by ExecGetTriggerResultRel(). - */ -void -ExecCleanUpTriggerState(EState *estate) -{ - ListCell *l; - - foreach(l, estate->es_trig_target_relations) - { - ResultRelInfo *resultRelInfo = (ResultRelInfo *) lfirst(l); - - /* - * Assert this is a "dummy" ResultRelInfo, see above. Otherwise we - * might be issuing a duplicate close against a Relation opened by - * ExecGetRangeTableRelation. - */ - Assert(resultRelInfo->ri_RangeTableIndex == 0); - - /* - * Since ExecGetTriggerResultRel doesn't call ExecOpenIndices for - * these rels, we needn't call ExecCloseIndices either. - */ - Assert(resultRelInfo->ri_NumIndices == 0); - - table_close(resultRelInfo->ri_RelationDesc, NoLock); - } -} - /* ---------------------------------------------------------------- * ExecPostprocessPlan * @@ -2565,9 +2420,6 @@ ExecPostprocessPlan(EState *estate) void ExecEndPlan(PlanState *planstate, EState *estate) { - ResultRelInfo *resultRelInfo; - Index num_relations; - Index i; ListCell *l; /* @@ -2600,29 +2452,69 @@ ExecEndPlan(PlanState *planstate, EState *estate) AdjustReplicatedTableCounts(estate); /* - * close indexes of result relation(s) if any. (Rels themselves get - * closed next.) + * Close any Relations that have been opened for range table entries or + * result relations. + */ + ExecCloseResultRelations(estate); + ExecCloseRangeTableRelations(estate); +} + +/* + * Close any relations that have been opened for ResultRelInfos. + */ +void +ExecCloseResultRelations(EState *estate) +{ + ListCell *l; + + /* + * close indexes of result relation(s) if any. (Rels themselves are + * closed in ExecCloseRangeTableRelations()) */ - resultRelInfo = estate->es_result_relations; - for (i = 0; i < estate->es_num_result_relations; i++) + foreach(l, estate->es_opened_result_relations) { + ResultRelInfo *resultRelInfo = lfirst(l); + ExecCloseIndices(resultRelInfo); - resultRelInfo++; } - /* - * close whatever rangetable Relations have been opened. We do not - * release any locks we might hold on those rels. - */ - num_relations = estate->es_range_table_size; - for (i = 0; i < num_relations; i++) + /* Close any relations that have been opened by ExecGetTriggerResultRel(). */ + foreach(l, estate->es_trig_target_relations) + { + ResultRelInfo *resultRelInfo = (ResultRelInfo *) lfirst(l); + + /* + * Assert this is a "dummy" ResultRelInfo, see above. Otherwise we + * might be issuing a duplicate close against a Relation opened by + * ExecGetRangeTableRelation. + */ + Assert(resultRelInfo->ri_RangeTableIndex == 0); + + /* + * Since ExecGetTriggerResultRel doesn't call ExecOpenIndices for + * these rels, we needn't call ExecCloseIndices either. + */ + Assert(resultRelInfo->ri_NumIndices == 0); + + table_close(resultRelInfo->ri_RelationDesc, NoLock); + } +} + +/* + * Close all relations opened by ExecGetRangeTableRelation(). + * + * We do not release any locks we might hold on those rels. + */ +void +ExecCloseRangeTableRelations(EState *estate) +{ + int i; + + for (i = 0; i < estate->es_range_table_size; i++) { if (estate->es_relations[i]) table_close(estate->es_relations[i], NoLock); } - - /* likewise close any trigger target relations */ - ExecCleanUpTriggerState(estate); } /* ---------------------------------------------------------------- @@ -2865,7 +2757,7 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, * ExecPartitionCheck --- check that tuple meets the partition constraint. * * Returns true if it meets the partition constraint. If the constraint - * fails and we're asked to emit to error, do so and don't return; otherwise + * fails and we're asked to emit an error, do so and don't return; otherwise * return false. */ bool @@ -2877,14 +2769,22 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, /* * If first time through, build expression state tree for the partition - * check expression. Keep it in the per-query memory context so they'll - * survive throughout the query. + * check expression. (In the corner case where the partition check + * expression is empty, ie there's a default partition and nothing else, + * we'll be fooled into executing this code each time through. But it's + * pretty darn cheap in that case, so we don't worry about it.) */ if (resultRelInfo->ri_PartitionCheckExpr == NULL) { - List *qual = resultRelInfo->ri_PartitionCheck; + /* + * Ensure that the qual tree and prepared expression are in the + * query-lifespan context. + */ + MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); + List *qual = RelationGetPartitionQual(resultRelInfo->ri_RelationDesc); resultRelInfo->ri_PartitionCheckExpr = ExecPrepareCheck(qual, estate); + MemoryContextSwitchTo(oldcxt); } /* @@ -2993,9 +2893,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo, Bitmapset *insertedCols; Bitmapset *updatedCols; - Assert(constr || resultRelInfo->ri_PartitionCheck); + Assert(constr); /* we should not be called otherwise */ - if (constr && constr->has_not_null) + if (constr->has_not_null) { int natts = tupdesc->natts; int attrChk; @@ -3056,7 +2956,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo, } } - if (constr && constr->num_check > 0) + if (constr->num_check > 0) { const char *failed; @@ -3858,17 +3758,9 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree) /* * Child EPQ EStates share the parent's copy of unchanging state such as - * the snapshot, rangetable, result-rel info, and external Param info. - * They need their own copies of local state, including a tuple table, - * es_param_exec_vals, etc. - * - * The ResultRelInfo array management is trickier than it looks. We - * create fresh arrays for the child but copy all the content from the - * parent. This is because it's okay for the child to share any - * per-relation state the parent has already created --- but if the child - * sets up any ResultRelInfo fields, such as its own junkfilter, that - * state must *not* propagate back to the parent. (For one thing, the - * pointed-to data is in a memory context that won't last long enough.) + * the snapshot, rangetable, and external Param info. They need their own + * copies of local state, including a tuple table, es_param_exec_vals, + * result-rel info, etc. */ rcestate->es_direction = ForwardScanDirection; rcestate->es_snapshot = parentestate->es_snapshot; @@ -3881,31 +3773,12 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree) rcestate->es_plannedstmt = parentestate->es_plannedstmt; rcestate->es_junkFilter = parentestate->es_junkFilter; rcestate->es_output_cid = parentestate->es_output_cid; - if (parentestate->es_num_result_relations > 0) - { - int numResultRelations = parentestate->es_num_result_relations; - int numRootResultRels = parentestate->es_num_root_result_relations; - ResultRelInfo *resultRelInfos; - - resultRelInfos = (ResultRelInfo *) - palloc(numResultRelations * sizeof(ResultRelInfo)); - memcpy(resultRelInfos, parentestate->es_result_relations, - numResultRelations * sizeof(ResultRelInfo)); - rcestate->es_result_relations = resultRelInfos; - rcestate->es_num_result_relations = numResultRelations; - - /* Also transfer partitioned root result relations. */ - if (numRootResultRels > 0) - { - resultRelInfos = (ResultRelInfo *) - palloc(numRootResultRels * sizeof(ResultRelInfo)); - memcpy(resultRelInfos, parentestate->es_root_result_relations, - numRootResultRels * sizeof(ResultRelInfo)); - rcestate->es_root_result_relations = resultRelInfos; - rcestate->es_num_root_result_relations = numRootResultRels; - } - } - /* es_result_relation_info must NOT be copied */ + + /* + * ResultRelInfos needed by subplans are initialized from scratch when the + * subplans themselves are initialized. + */ + rcestate->es_result_relations = NULL; /* es_trig_target_relations must NOT be copied */ rcestate->es_top_eflags = parentestate->es_top_eflags; rcestate->es_instrument = parentestate->es_instrument; @@ -4014,8 +3887,9 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree) * This is a cut-down version of ExecutorEnd(); basically we want to do most * of the normal cleanup, but *not* close result relations (which we are * just sharing from the outer query). We do, however, have to close any - * trigger target relations that got opened, since those are not shared. - * (There probably shouldn't be any of the latter, but just in case...) + * result and trigger target relations that got opened, since those are not + * shared. (There probably shouldn't be any of the latter, but just in + * case...) */ void EvalPlanQualEnd(EPQState *epqstate) @@ -4057,8 +3931,8 @@ EvalPlanQualEnd(EPQState *epqstate) /* throw away the per-estate tuple table, some node may have used it */ ExecResetTupleTable(estate->es_tupleTable, false); - /* close any trigger target relations attached to this EState */ - ExecCleanUpTriggerState(estate); + /* Close any result and trigger target relations attached to this EState */ + ExecCloseResultRelations(estate); MemoryContextSwitchTo(oldcontext); @@ -4145,14 +4019,15 @@ AdjustReplicatedTableCounts(EState *estate) ResultRelInfo *resultRelInfo; bool containReplicatedTable = false; int numsegments = 1; + ListCell *l; if (Gp_role != GP_ROLE_DISPATCH) return; /* check if result_relations contain replicated table*/ - for (i = 0; i < estate->es_num_result_relations; i++) + foreach(l, estate->es_opened_result_relations) { - resultRelInfo = estate->es_result_relations + i; + resultRelInfo = lfirst(l); if (!resultRelInfo->ri_RelationDesc->rd_cdbpolicy) continue; diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index 6f14136eef25..408eee1424a1 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -184,7 +184,6 @@ ExecSerializePlan(Plan *plan, EState *estate) pstmt->planTree = plan; pstmt->rtable = estate->es_range_table; pstmt->resultRelations = NIL; - pstmt->rootResultRelations = NIL; pstmt->appendRelations = NIL; /* diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 767f54368125..e3652da4334c 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -53,6 +53,11 @@ * PartitionDispatchData->indexes for details on how this array is * indexed. * + * nonleaf_partitions + * Array of 'max_dispatch' elements containing pointers to fake + * ResultRelInfo objects for nonleaf partitions, useful for checking + * the partition constraint. + * * num_dispatch * The current number of items stored in the 'partition_dispatch_info' * array. Also serves as the index of the next free array element for @@ -91,6 +96,7 @@ struct PartitionTupleRouting { Relation partition_root; PartitionDispatch *partition_dispatch_info; + ResultRelInfo **nonleaf_partitions; int num_dispatch; int max_dispatch; ResultRelInfo **partitions; @@ -256,7 +262,7 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate, * If the partition's ResultRelInfo does not yet exist in 'proute' then we set * one up or reuse one from mtstate's resultRelInfo array. When reusing a * ResultRelInfo from the mtstate we verify that the relation is a valid - * target for INSERTs and then set up a PartitionRoutingInfo for it. + * target for INSERTs and initialize tuple routing information. * * rootResultRelInfo is the relation named in the query. * @@ -281,9 +287,11 @@ ExecFindPartition(ModifyTableState *mtstate, PartitionDispatch dispatch; PartitionDesc partdesc; ExprContext *ecxt = GetPerTupleExprContext(estate); - TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple; + TupleTableSlot *ecxt_scantuple_saved = ecxt->ecxt_scantuple; + TupleTableSlot *rootslot = slot; TupleTableSlot *myslot = NULL; MemoryContext oldcxt; + ResultRelInfo *rri = NULL; /* use per-tuple context here to avoid leaking memory */ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); @@ -292,32 +300,21 @@ ExecFindPartition(ModifyTableState *mtstate, * First check the root table's partition constraint, if any. No point in * routing the tuple if it doesn't belong in the root table itself. */ - if (rootResultRelInfo->ri_PartitionCheck) + if (rootResultRelInfo->ri_RelationDesc->rd_rel->relispartition) ExecPartitionCheck(rootResultRelInfo, slot, estate, true); /* start with the root partitioned table */ dispatch = pd[0]; - while (true) + while (dispatch != NULL) { - AttrMap *map = dispatch->tupmap; int partidx = -1; + bool is_leaf; CHECK_FOR_INTERRUPTS(); rel = dispatch->reldesc; partdesc = dispatch->partdesc; - /* - * Convert the tuple to this parent's layout, if different from the - * current relation. - */ - myslot = dispatch->tupslot; - if (myslot != NULL) - { - Assert(map != NULL); - slot = execute_attr_map_slot(map, slot, myslot); - } - /* * Extract partition key from tuple. Expression evaluation machinery * that FormPartitionKeyDatum() invokes expects ecxt_scantuple to @@ -357,13 +354,13 @@ ExecFindPartition(ModifyTableState *mtstate, errtable(rel))); } - if (partdesc->is_leaf[partidx]) + is_leaf = partdesc->is_leaf[partidx]; + if (is_leaf) { - ResultRelInfo *rri; /* - * Look to see if we've already got a ResultRelInfo for this - * partition. + * We've reached the leaf -- hurray, we're done. Look to see if + * we've already got a ResultRelInfo for this partition. */ if (likely(dispatch->indexes[partidx] >= 0)) { @@ -395,7 +392,10 @@ ExecFindPartition(ModifyTableState *mtstate, /* Verify this ResultRelInfo allows INSERTs */ CheckValidResultRel(rri, CMD_INSERT); - /* Set up the PartitionRoutingInfo for it */ + /* + * Initialize information needed to insert this and + * subsequent tuples routed to this partition. + */ ExecInitRoutingInfo(mtstate, estate, proute, dispatch, rri, partidx); } @@ -407,14 +407,10 @@ ExecFindPartition(ModifyTableState *mtstate, dispatch, rootResultRelInfo, partidx); } + Assert(rri != NULL); - /* Release the tuple in the lowest parent's dedicated slot. */ - if (slot == myslot) - ExecClearTuple(myslot); - - MemoryContextSwitchTo(oldcxt); - ecxt->ecxt_scantuple = ecxt_scantuple_old; - return rri; + /* Signal to terminate the loop */ + dispatch = NULL; } else { @@ -426,6 +422,8 @@ ExecFindPartition(ModifyTableState *mtstate, /* Already built. */ Assert(dispatch->indexes[partidx] < proute->num_dispatch); + rri = proute->nonleaf_partitions[dispatch->indexes[partidx]]; + /* * Move down to the next partition level and search again * until we find a leaf partition that matches this tuple @@ -447,10 +445,73 @@ ExecFindPartition(ModifyTableState *mtstate, dispatch, partidx); Assert(dispatch->indexes[partidx] >= 0 && dispatch->indexes[partidx] < proute->num_dispatch); + + rri = proute->nonleaf_partitions[dispatch->indexes[partidx]]; dispatch = subdispatch; } + + /* + * Convert the tuple to the new parent's layout, if different from + * the previous parent. + */ + if (dispatch->tupslot) + { + AttrMap *map = dispatch->tupmap; + TupleTableSlot *tempslot = myslot; + + myslot = dispatch->tupslot; + slot = execute_attr_map_slot(map, slot, myslot); + + if (tempslot != NULL) + ExecClearTuple(tempslot); + } + } + + /* + * If this partition is the default one, we must check its partition + * constraint now, which may have changed concurrently due to + * partitions being added to the parent. + * + * (We do this here, and do not rely on ExecInsert doing it, because + * we don't want to miss doing it for non-leaf partitions.) + */ + if (partidx == partdesc->boundinfo->default_index) + { + /* + * The tuple must match the partition's layout for the constraint + * expression to be evaluated successfully. If the partition is + * sub-partitioned, that would already be the case due to the code + * above, but for a leaf partition the tuple still matches the + * parent's layout. + * + * Note that we have a map to convert from root to current + * partition, but not from immediate parent to current partition. + * So if we have to convert, do it from the root slot; if not, use + * the root slot as-is. + */ + if (is_leaf) + { + TupleConversionMap *map = rri->ri_RootToPartitionMap; + + if (map) + slot = execute_attr_map_slot(map->attrMap, rootslot, + rri->ri_PartitionTupleSlot); + else + slot = rootslot; + } + + ExecPartitionCheck(rri, slot, estate, true); } } + + /* Release the tuple in the lowest parent's dedicated slot. */ + if (myslot != NULL) + ExecClearTuple(myslot); + /* and restore ecxt's scantuple */ + ecxt->ecxt_scantuple = ecxt_scantuple_saved; + MemoryContextSwitchTo(oldcxt); + + return rri; } /* @@ -738,7 +799,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, { TupleConversionMap *map; - map = leaf_part_rri->ri_PartitionInfo->pi_RootToPartitionMap; + map = leaf_part_rri->ri_RootToPartitionMap; Assert(node->onConflictSet != NIL); Assert(rootResultRelInfo->ri_onConflict != NULL); @@ -857,6 +918,15 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, } } + /* + * Also, if transition capture is required, store a map to convert tuples + * from partition's rowtype to the root partition table's. + */ + if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture) + leaf_part_rri->ri_ChildToRootMap = + convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc), + RelationGetDescr(leaf_part_rri->ri_PartitionRoot)); + /* * Since we've just initialized this ResultRelInfo, it's not in any list * attached to the estate as yet. Add it, so that it can be found later. @@ -893,18 +963,15 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, int partidx) { MemoryContext oldcxt; - PartitionRoutingInfo *partrouteinfo; int rri_index; oldcxt = MemoryContextSwitchTo(proute->memcxt); - partrouteinfo = palloc(sizeof(PartitionRoutingInfo)); - /* * Set up a tuple conversion map to convert a tuple routed to the * partition from the parent's type to the partition's. */ - partrouteinfo->pi_RootToPartitionMap = + partRelInfo->ri_RootToPartitionMap = convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_PartitionRoot), RelationGetDescr(partRelInfo->ri_RelationDesc)); @@ -914,7 +981,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, * for various operations that are applied to tuples after routing, such * as checking constraints. */ - if (partrouteinfo->pi_RootToPartitionMap != NULL) + if (partRelInfo->ri_RootToPartitionMap != NULL) { Relation partrel = partRelInfo->ri_RelationDesc; @@ -923,25 +990,11 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, * partition's TupleDesc; TupleDesc reference will be released at the * end of the command. */ - partrouteinfo->pi_PartitionTupleSlot = + partRelInfo->ri_PartitionTupleSlot = table_slot_create(partrel, &estate->es_tupleTable); } else - partrouteinfo->pi_PartitionTupleSlot = NULL; - - /* - * Also, if transition capture is required, store a map to convert tuples - * from partition's rowtype to the root partition table's. - */ - if (mtstate && - (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)) - { - partrouteinfo->pi_PartitionToRootMap = - convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc), - RelationGetDescr(partRelInfo->ri_PartitionRoot)); - } - else - partrouteinfo->pi_PartitionToRootMap = NULL; + partRelInfo->ri_PartitionTupleSlot = NULL; /* * If the partition is a foreign table, let the FDW init itself for @@ -951,7 +1004,6 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, partRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL) partRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate, partRelInfo); - partRelInfo->ri_PartitionInfo = partrouteinfo; partRelInfo->ri_CopyMultiInsertBuffer = NULL; /* @@ -1070,6 +1122,8 @@ ExecInitPartitionDispatchInfo(EState *estate, proute->max_dispatch = 4; proute->partition_dispatch_info = (PartitionDispatch *) palloc(sizeof(PartitionDispatch) * proute->max_dispatch); + proute->nonleaf_partitions = (ResultRelInfo **) + palloc(sizeof(ResultRelInfo *) * proute->max_dispatch); } else { @@ -1077,10 +1131,28 @@ ExecInitPartitionDispatchInfo(EState *estate, proute->partition_dispatch_info = (PartitionDispatch *) repalloc(proute->partition_dispatch_info, sizeof(PartitionDispatch) * proute->max_dispatch); + proute->nonleaf_partitions = (ResultRelInfo **) + repalloc(proute->nonleaf_partitions, + sizeof(ResultRelInfo *) * proute->max_dispatch); } } proute->partition_dispatch_info[dispatchidx] = pd; + /* + * If setting up a PartitionDispatch for a sub-partitioned table, we may + * also need a minimally valid ResultRelInfo for checking the partition + * constraint later; set that up now. + */ + if (parent_pd) + { + ResultRelInfo *rri = makeNode(ResultRelInfo); + + InitResultRelInfo(rri, rel, 1, proute->partition_root, 0); + proute->nonleaf_partitions[dispatchidx] = rri; + } + else + proute->nonleaf_partitions[dispatchidx] = NULL; + /* * Finally, if setting up a PartitionDispatch for a sub-partitioned table, * install a downlink in the parent to allow quick descent. diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index 69d100ddc9d9..69ab3f290c40 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -404,10 +404,10 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, * Caller is responsible for opening the indexes. */ void -ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) +ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo, + EState *estate, TupleTableSlot *slot) { bool skip_tuple = false; - ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; /* For now we support only tables. */ @@ -430,19 +430,21 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) /* Compute stored generated columns */ if (rel->rd_att->constr && rel->rd_att->constr->has_generated_stored) - ExecComputeStoredGenerated(estate, slot, CMD_INSERT); + ExecComputeStoredGenerated(resultRelInfo, estate, slot, + CMD_INSERT); /* Check the constraints of the tuple */ if (rel->rd_att->constr) ExecConstraints(resultRelInfo, slot, estate); - if (resultRelInfo->ri_PartitionCheck) + if (rel->rd_rel->relispartition) ExecPartitionCheck(resultRelInfo, slot, estate, true); /* OK, store the tuple and create index entries for it */ simple_table_tuple_insert(resultRelInfo->ri_RelationDesc, slot); if (resultRelInfo->ri_NumIndices > 0) - recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, + recheckIndexes = ExecInsertIndexTuples(resultRelInfo, + slot, estate, false, NULL, NIL); /* AFTER ROW INSERT Triggers */ @@ -466,11 +468,11 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) * Caller is responsible for opening the indexes. */ void -ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, +ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo, + EState *estate, EPQState *epqstate, TupleTableSlot *searchslot, TupleTableSlot *slot) { bool skip_tuple = false; - ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; ItemPointer tid = &(searchslot->tts_tid); @@ -496,19 +498,21 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, /* Compute stored generated columns */ if (rel->rd_att->constr && rel->rd_att->constr->has_generated_stored) - ExecComputeStoredGenerated(estate, slot, CMD_UPDATE); + ExecComputeStoredGenerated(resultRelInfo, estate, slot, + CMD_UPDATE); /* Check the constraints of the tuple */ if (rel->rd_att->constr) ExecConstraints(resultRelInfo, slot, estate); - if (resultRelInfo->ri_PartitionCheck) + if (rel->rd_rel->relispartition) ExecPartitionCheck(resultRelInfo, slot, estate, true); simple_table_tuple_update(rel, tid, slot, estate->es_snapshot, &update_indexes); if (resultRelInfo->ri_NumIndices > 0 && update_indexes) - recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, + recheckIndexes = ExecInsertIndexTuples(resultRelInfo, + slot, estate, false, NULL, NIL); /* AFTER ROW UPDATE Triggers */ @@ -527,11 +531,11 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, * Caller is responsible for opening the indexes. */ void -ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, +ExecSimpleRelationDelete(ResultRelInfo *resultRelInfo, + EState *estate, EPQState *epqstate, TupleTableSlot *searchslot) { bool skip_tuple = false; - ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; ItemPointer tid = &searchslot->tts_tid; diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index d708f70db9b7..30a25ca0ee65 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -157,14 +157,8 @@ CreateExecutorState(void) estate->es_output_cid = (CommandId) 0; estate->es_result_relations = NULL; - estate->es_num_result_relations = 0; - estate->es_result_relation_info = NULL; - - estate->es_root_result_relations = NULL; - estate->es_num_root_result_relations = 0; - + estate->es_opened_result_relations = NIL; estate->es_tuple_routing_result_relations = NIL; - estate->es_trig_target_relations = NIL; estate->es_param_list_info = NULL; @@ -765,16 +759,7 @@ ExecCreateScanSlotFromOuterPlan(EState *estate, bool ExecRelationIsTargetRelation(EState *estate, Index scanrelid) { - ResultRelInfo *resultRelInfos; - int i; - - resultRelInfos = estate->es_result_relations; - for (i = 0; i < estate->es_num_result_relations; i++) - { - if (resultRelInfos[i].ri_RangeTableIndex == scanrelid) - return true; - } - return false; + return list_member_int(estate->es_plannedstmt->resultRelations, scanrelid); } /* ---------------------------------------------------------------- @@ -833,9 +818,10 @@ ExecInitRangeTable(EState *estate, List *rangeTable) palloc0(estate->es_range_table_size * sizeof(Relation)); /* - * es_rowmarks is also parallel to the es_range_table, but it's allocated - * only if needed. + * es_result_relations and es_rowmarks are also parallel to + * es_range_table, but are allocated only if needed. */ + estate->es_result_relations = NULL; estate->es_rowmarks = NULL; } @@ -891,6 +877,40 @@ ExecGetRangeTableRelation(EState *estate, Index rti) return rel; } +/* + * ExecInitResultRelation + * Open relation given by the passed-in RT index and fill its + * ResultRelInfo node + * + * Here, we also save the ResultRelInfo in estate->es_result_relations array + * such that it can be accessed later using the RT index. + */ +void +ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo, + Index rti) +{ + Relation resultRelationDesc; + + resultRelationDesc = ExecGetRangeTableRelation(estate, rti); + InitResultRelInfo(resultRelInfo, + resultRelationDesc, + rti, + NULL, + estate->es_instrument); + + if (estate->es_result_relations == NULL) + estate->es_result_relations = (ResultRelInfo **) + palloc0(estate->es_range_table_size * sizeof(ResultRelInfo *)); + estate->es_result_relations[rti - 1] = resultRelInfo; + + /* + * Saving in the list allows to avoid needlessly traversing the whole + * array when only a few of its entries are possibly non-NULL. + */ + estate->es_opened_result_relations = + lappend(estate->es_opened_result_relations, resultRelInfo); +} + /* * UpdateChangedParamSet * Add changed parameters to a plan node's chgParam set diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 163d06e9bc20..765129824e89 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -352,7 +352,8 @@ prepare_sql_fn_parse_info(HeapTuple procedureTuple, if (isNull) proargmodes = PointerGetDatum(NULL); /* just to be sure */ - n_arg_names = get_func_input_arg_names(proargnames, proargmodes, + n_arg_names = get_func_input_arg_names(procedureStruct->prokind, + proargnames, proargmodes, &pinfo->argnames); /* Paranoia: ignore the result if too few array entries */ @@ -704,7 +705,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK) SQLFunctionCachePtr fcache; List *raw_parsetree_list; List *queryTree_list; - List *flat_query_list; List *resulttlist; ListCell *lc; Datum tmp; @@ -784,13 +784,7 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK) /* * Parse and rewrite the queries in the function text. Use sublists to - * keep track of the original query boundaries. But we also build a - * "flat" list of the rewritten queries to pass to check_sql_fn_retval. - * This is because the last canSetTag query determines the result type - * independently of query boundaries --- and it might not be in the last - * sublist, for example if the last query rewrites to DO INSTEAD NOTHING. - * (It might not be unreasonable to throw an error in such a case, but - * this is the historical behavior and it doesn't seem worth changing.) + * keep track of the original query boundaries. * * Note: since parsing and planning is done in fcontext, we will generate * a lot of cruft that lives as long as the fcache does. This is annoying @@ -800,7 +794,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK) raw_parsetree_list = pg_parse_query(fcache->src); queryTree_list = NIL; - flat_query_list = NIL; foreach(lc, raw_parsetree_list) { RawStmt *parsetree = lfirst_node(RawStmt, lc); @@ -812,10 +805,12 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK) fcache->pinfo, NULL); queryTree_list = lappend(queryTree_list, queryTree_sublist); - flat_query_list = list_concat(flat_query_list, queryTree_sublist); } - check_sql_fn_statements(flat_query_list); + /* + * Check that there are no statements we don't want to allow. + */ + check_sql_fn_statements(queryTree_list); /* * If we have only SELECT statements with no FROM clauses, we should @@ -855,7 +850,7 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK) * the rowtype column into multiple columns, since we have no way to * notify the caller that it should do that.) */ - fcache->returnsTuple = check_sql_fn_retval(flat_query_list, + fcache->returnsTuple = check_sql_fn_retval(queryTree_list, rettype, rettupdesc, false, @@ -1653,51 +1648,63 @@ ShutdownSQLFunction(Datum arg) * is not acceptable. */ void -check_sql_fn_statements(List *queryTreeList) +check_sql_fn_statements(List *queryTreeLists) { ListCell *lc; - foreach(lc, queryTreeList) + /* We are given a list of sublists of Queries */ + foreach(lc, queryTreeLists) { - Query *query = lfirst_node(Query, lc); + List *sublist = lfirst_node(List, lc); + ListCell *lc2; - /* - * Disallow procedures with output arguments. The current - * implementation would just throw the output values away, unless the - * statement is the last one. Per SQL standard, we should assign the - * output values by name. By disallowing this here, we preserve an - * opportunity for future improvement. - */ - if (query->commandType == CMD_UTILITY && - IsA(query->utilityStmt, CallStmt)) + foreach(lc2, sublist) { - CallStmt *stmt = castNode(CallStmt, query->utilityStmt); - HeapTuple tuple; - int numargs; - Oid *argtypes; - char **argnames; - char *argmodes; - int i; - - tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(stmt->funcexpr->funcid)); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "cache lookup failed for function %u", stmt->funcexpr->funcid); - numargs = get_func_arg_info(tuple, &argtypes, &argnames, &argmodes); - ReleaseSysCache(tuple); - - for (i = 0; i < numargs; i++) + Query *query = lfirst_node(Query, lc2); + + /* + * Disallow procedures with output arguments. The current + * implementation would just throw the output values away, unless + * the statement is the last one. Per SQL standard, we should + * assign the output values by name. By disallowing this here, we + * preserve an opportunity for future improvement. + */ + if (query->commandType == CMD_UTILITY && + IsA(query->utilityStmt, CallStmt)) { - if (argmodes && (argmodes[i] == PROARGMODE_INOUT || argmodes[i] == PROARGMODE_OUT)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("calling procedures with output arguments is not supported in SQL functions"))); + CallStmt *stmt = castNode(CallStmt, query->utilityStmt); + HeapTuple tuple; + int numargs; + Oid *argtypes; + char **argnames; + char *argmodes; + int i; + + tuple = SearchSysCache1(PROCOID, + ObjectIdGetDatum(stmt->funcexpr->funcid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for function %u", + stmt->funcexpr->funcid); + numargs = get_func_arg_info(tuple, + &argtypes, &argnames, &argmodes); + ReleaseSysCache(tuple); + + for (i = 0; i < numargs; i++) + { + if (argmodes && (argmodes[i] == PROARGMODE_INOUT || + argmodes[i] == PROARGMODE_OUT)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("calling procedures with output arguments is not supported in SQL functions"))); + } } } } } /* - * check_sql_fn_retval() -- check return value of a list of sql parse trees. + * check_sql_fn_retval() + * Check return value of a list of lists of sql parse trees. * * The return value of a sql function is the value returned by the last * canSetTag query in the function. We do some ad-hoc type checking and @@ -1735,7 +1742,7 @@ check_sql_fn_statements(List *queryTreeList) * function is defined to return VOID then *resultTargetList is set to NIL. */ bool -check_sql_fn_retval(List *queryTreeList, +check_sql_fn_retval(List *queryTreeLists, Oid rettype, TupleDesc rettupdesc, bool insertDroppedCols, List **resultTargetList) @@ -1762,20 +1769,30 @@ check_sql_fn_retval(List *queryTreeList, return false; /* - * Find the last canSetTag query in the list. This isn't necessarily the - * last parsetree, because rule rewriting can insert queries after what - * the user wrote. + * Find the last canSetTag query in the function body (which is presented + * to us as a list of sublists of Query nodes). This isn't necessarily + * the last parsetree, because rule rewriting can insert queries after + * what the user wrote. Note that it might not even be in the last + * sublist, for example if the last query rewrites to DO INSTEAD NOTHING. + * (It might not be unreasonable to throw an error in such a case, but + * this is the historical behavior and it doesn't seem worth changing.) */ parse = NULL; parse_cell = NULL; - foreach(lc, queryTreeList) + foreach(lc, queryTreeLists) { - Query *q = lfirst_node(Query, lc); + List *sublist = lfirst_node(List, lc); + ListCell *lc2; - if (q->canSetTag) + foreach(lc2, sublist) { - parse = q; - parse_cell = lc; + Query *q = lfirst_node(Query, lc2); + + if (q->canSetTag) + { + parse = q; + parse_cell = lc2; + } } } diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 24a388950ada..e046265468bd 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -2726,8 +2726,6 @@ agg_refill_hash_table(AggState *aggstate) */ hashagg_recompile_expressions(aggstate, true, true); - LogicalTapeRewindForRead(tapeinfo->tapeset, batch->input_tapenum, - HASHAGG_READ_BUFFER_SIZE); for (;;) { TupleTableSlot *spillslot = aggstate->hash_spill_rslot; @@ -2793,8 +2791,8 @@ agg_refill_hash_table(AggState *aggstate) if (spill_initialized) { - hash_agg_update_metrics(aggstate, true, spill.npartitions); hashagg_spill_finish(aggstate, &spill, batch->setno); + hash_agg_update_metrics(aggstate, true, spill.npartitions); } else hash_agg_update_metrics(aggstate, true, 0); @@ -2969,7 +2967,7 @@ hashagg_tapeinfo_init(AggState *aggstate) HashTapeInfo *tapeinfo = palloc(sizeof(HashTapeInfo)); int init_tapes = 16; /* expanded dynamically */ - tapeinfo->tapeset = LogicalTapeSetCreate(init_tapes, NULL, NULL, -1); + tapeinfo->tapeset = LogicalTapeSetCreate(init_tapes, true, NULL, NULL, -1); tapeinfo->ntapes = init_tapes; tapeinfo->nfreetapes = init_tapes; tapeinfo->freetapes_alloc = init_tapes; @@ -3025,6 +3023,7 @@ hashagg_tapeinfo_assign(HashTapeInfo *tapeinfo, int *partitions, static void hashagg_tapeinfo_release(HashTapeInfo *tapeinfo, int tapenum) { + /* rewinding frees the buffer while not in use */ LogicalTapeRewindForWrite(tapeinfo->tapeset, tapenum); if (tapeinfo->freetapes_alloc == tapeinfo->nfreetapes) { @@ -3254,6 +3253,7 @@ hashagg_spill_finish(AggState *aggstate, HashAggSpill *spill, int setno) for (i = 0; i < spill->npartitions; i++) { + LogicalTapeSet *tapeset = aggstate->hash_tapeinfo->tapeset; int tapenum = spill->partitions[i]; HashAggBatch *new_batch; double cardinality; @@ -3265,9 +3265,13 @@ hashagg_spill_finish(AggState *aggstate, HashAggSpill *spill, int setno) cardinality = estimateHyperLogLog(&spill->hll_card[i]); freeHyperLogLog(&spill->hll_card[i]); - new_batch = hashagg_batch_new(aggstate->hash_tapeinfo->tapeset, - tapenum, setno, spill->ntuples[i], - cardinality, used_bits); + /* rewinding frees the buffer while not in use */ + LogicalTapeRewindForRead(tapeset, tapenum, + HASHAGG_READ_BUFFER_SIZE); + + new_batch = hashagg_batch_new(tapeset, tapenum, setno, + spill->ntuples[i], cardinality, + used_bits); aggstate->hash_batches = lcons(new_batch, aggstate->hash_batches); aggstate->hash_batches_used++; } diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c index 513471ab9b90..0b20f94035ed 100644 --- a/src/backend/executor/nodeForeignscan.c +++ b/src/backend/executor/nodeForeignscan.c @@ -215,6 +215,13 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags) scanstate->fdwroutine = fdwroutine; scanstate->fdw_state = NULL; + /* + * For the FDW's convenience, look up the modification target relation's. + * ResultRelInfo. + */ + if (node->resultRelation > 0) + scanstate->resultRelInfo = estate->es_result_relations[node->resultRelation - 1]; + /* Initialize any outer plan. */ if (outerPlan(node)) outerPlanState(scanstate) = diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index a962f3574235..4d1402744044 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -1675,6 +1675,16 @@ ExecReScanHashJoin(HashJoinState *node) ExecReScan(node->js.ps.righttree); } } + else + { + /* + * GPDB: HashTable not built with righttree may be squelched, + * HashJoin need rescan righttree to reset its squelch flag. + */ + if (node->js.ps.righttree->chgParam == NULL && + node->js.ps.righttree->squelched) + ExecReScan(node->js.ps.righttree); + } /* Always reset intra-tuple state */ node->hj_CurHashValue = 0; diff --git a/src/backend/executor/nodeIncrementalSort.c b/src/backend/executor/nodeIncrementalSort.c index 6c0d24ee25a5..eb1c1326dea2 100644 --- a/src/backend/executor/nodeIncrementalSort.c +++ b/src/backend/executor/nodeIncrementalSort.c @@ -333,7 +333,7 @@ switchToPresortedPrefixMode(PlanState *pstate) */ if (node->bounded) { - SO1_printf("Setting bound on presorted prefix tuplesort to: %ld\n", + SO1_printf("Setting bound on presorted prefix tuplesort to: " INT64_FORMAT "\n", node->bound - node->bound_Done); tuplesort_set_bound(node->prefixsort_state, node->bound - node->bound_Done); @@ -417,9 +417,9 @@ switchToPresortedPrefixMode(PlanState *pstate) * remaining in the large single prefix key group we think we've * encountered. */ - SO1_printf("Moving %ld tuples to presorted prefix tuplesort\n", nTuples); + SO1_printf("Moving " INT64_FORMAT " tuples to presorted prefix tuplesort\n", nTuples); node->n_fullsort_remaining -= nTuples; - SO1_printf("Setting n_fullsort_remaining to %ld\n", node->n_fullsort_remaining); + SO1_printf("Setting n_fullsort_remaining to " INT64_FORMAT "\n", node->n_fullsort_remaining); if (lastTuple) { @@ -449,7 +449,7 @@ switchToPresortedPrefixMode(PlanState *pstate) * out all of those tuples, and then come back around to find another * batch. */ - SO1_printf("Sorting presorted prefix tuplesort with %ld tuples\n", nTuples); + SO1_printf("Sorting presorted prefix tuplesort with " INT64_FORMAT " tuples\n", nTuples); tuplesort_performsort(node->prefixsort_state); INSTRUMENT_SORT_GROUP(node, prefixsort); @@ -462,7 +462,7 @@ switchToPresortedPrefixMode(PlanState *pstate) * - n), so store the current number of processed tuples for use * in configuring sorting bound. */ - SO2_printf("Changing bound_Done from %ld to %ld\n", + SO2_printf("Changing bound_Done from " INT64_FORMAT " to " INT64_FORMAT "\n", Min(node->bound, node->bound_Done + nTuples), node->bound_Done); node->bound_Done = Min(node->bound, node->bound_Done + nTuples); } @@ -574,7 +574,7 @@ ExecIncrementalSort(PlanState *pstate) * need to re-execute the prefix mode transition function to pull * out the next prefix key group. */ - SO1_printf("Re-calling switchToPresortedPrefixMode() because n_fullsort_remaining is > 0 (%ld)\n", + SO1_printf("Re-calling switchToPresortedPrefixMode() because n_fullsort_remaining is > 0 (" INT64_FORMAT ")\n", node->n_fullsort_remaining); switchToPresortedPrefixMode(pstate); } @@ -707,7 +707,7 @@ ExecIncrementalSort(PlanState *pstate) */ node->outerNodeDone = true; - SO1_printf("Sorting fullsort with %ld tuples\n", nTuples); + SO1_printf("Sorting fullsort with " INT64_FORMAT " tuples\n", nTuples); tuplesort_performsort(fullsort_state); INSTRUMENT_SORT_GROUP(node, fullsort); @@ -776,7 +776,7 @@ ExecIncrementalSort(PlanState *pstate) * current number of processed tuples for later use * configuring the sort state's bound. */ - SO2_printf("Changing bound_Done from %ld to %ld\n", + SO2_printf("Changing bound_Done from " INT64_FORMAT " to " INT64_FORMAT "\n", node->bound_Done, Min(node->bound, node->bound_Done + nTuples)); node->bound_Done = Min(node->bound, node->bound_Done + nTuples); @@ -787,7 +787,7 @@ ExecIncrementalSort(PlanState *pstate) * sort and transition modes to reading out the sorted * tuples. */ - SO1_printf("Sorting fullsort tuplesort with %ld tuples\n", + SO1_printf("Sorting fullsort tuplesort with " INT64_FORMAT " tuples\n", nTuples); tuplesort_performsort(fullsort_state); @@ -828,7 +828,7 @@ ExecIncrementalSort(PlanState *pstate) * on FIFO retrieval semantics when transferring them to the * presorted prefix tuplesort. */ - SO1_printf("Sorting fullsort tuplesort with %ld tuples\n", nTuples); + SO1_printf("Sorting fullsort tuplesort with " INT64_FORMAT " tuples\n", nTuples); tuplesort_performsort(fullsort_state); INSTRUMENT_SORT_GROUP(node, fullsort); @@ -847,12 +847,12 @@ ExecIncrementalSort(PlanState *pstate) { int64 currentBound = node->bound - node->bound_Done; - SO2_printf("Read %ld tuples, but setting to %ld because we used bounded sort\n", + SO2_printf("Read " INT64_FORMAT " tuples, but setting to " INT64_FORMAT " because we used bounded sort\n", nTuples, Min(currentBound, nTuples)); nTuples = Min(currentBound, nTuples); } - SO1_printf("Setting n_fullsort_remaining to %ld and calling switchToPresortedPrefixMode()\n", + SO1_printf("Setting n_fullsort_remaining to " INT64_FORMAT " and calling switchToPresortedPrefixMode()\n", nTuples); /* @@ -942,7 +942,7 @@ ExecIncrementalSort(PlanState *pstate) * Perform the sort and begin returning the tuples to the parent plan * node. */ - SO1_printf("Sorting presorted prefix tuplesort with >= %ld tuples\n", nTuples); + SO1_printf("Sorting presorted prefix tuplesort with " INT64_FORMAT " tuples\n", nTuples); tuplesort_performsort(node->prefixsort_state); INSTRUMENT_SORT_GROUP(node, prefixsort); @@ -958,7 +958,7 @@ ExecIncrementalSort(PlanState *pstate) * - n), so store the current number of processed tuples for use * in configuring sorting bound. */ - SO2_printf("Changing bound_Done from %ld to %ld\n", + SO2_printf("Changing bound_Done from " INT64_FORMAT " to " INT64_FORMAT "\n", node->bound_Done, Min(node->bound, node->bound_Done + nTuples)); node->bound_Done = Min(node->bound, node->bound_Done + nTuples); @@ -1097,7 +1097,7 @@ ExecEndIncrementalSort(IncrementalSortState *node) ExecClearTuple(node->ss.ss_ScanTupleSlot); /* must drop pointer to sort result tuple */ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); - /* must drop stanalone tuple slots from outer node */ + /* must drop standalone tuple slots from outer node */ ExecDropSingleTupleTableSlot(node->group_pivot); ExecDropSingleTupleTableSlot(node->transfer_tuple); diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c index 95c5185aef64..ef104533f349 100644 --- a/src/backend/executor/nodeLimit.c +++ b/src/backend/executor/nodeLimit.c @@ -108,7 +108,7 @@ ExecLimit_guts(PlanState *pstate) } /* - * Tuple at limit is needed for comparation in subsequent + * Tuple at limit is needed for comparison in subsequent * execution to detect ties. */ if (node->limitOption == LIMIT_OPTION_WITH_TIES && diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 84c66d8bcc90..600450533c1a 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -80,11 +80,8 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate, EState *estate, PartitionTupleRouting *proute, ResultRelInfo *targetRelInfo, - TupleTableSlot *slot); -static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node); -static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate); -static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node, - int whichplan); + TupleTableSlot *slot, + ResultRelInfo **partRelInfo); /* * Verify that the tuples to be produced by INSERT or UPDATE match the @@ -261,9 +258,10 @@ ExecCheckTIDVisible(EState *estate, * Compute stored generated columns for a tuple */ void -ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype) +ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo, + EState *estate, TupleTableSlot *slot, + CmdType cmdtype) { - ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; TupleDesc tupdesc = RelationGetDescr(rel); int natts = tupdesc->natts; @@ -381,10 +379,15 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype * ExecInsert * * For INSERT, we have to insert the tuple into the target relation - * and insert appropriate tuples into the index relations. + * (or partition thereof) and insert appropriate tuples into the index + * relations. * * Returns RETURNING result if any, otherwise NULL. * + * This may change the currently active tuple conversion map in + * mtstate->mt_transition_capture, so the callers must take care to + * save the previous value to avoid losing track of it. + * * If the target table is partitioned, the input tuple in 'parentslot' * is in the shape required for the parent table. This function will * look up the ResultRelInfo of the target partition, and form a @@ -400,26 +403,37 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype */ static TupleTableSlot * ExecInsert(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot, EState *estate, bool canSetTag, bool splitUpdate) { - ResultRelInfo *resultRelInfo; Relation resultRelationDesc; List *recheckIndexes = NIL; TupleTableSlot *result = NULL; TransitionCaptureState *ar_insert_trig_tcs; ModifyTable *node = (ModifyTable *) mtstate->ps.plan; OnConflictAction onconflict = node->onConflictAction; - - ExecMaterializeSlot(slot); + PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing; /* - * get information on the (current) result relation + * If the input result relation is a partitioned table, find the leaf + * partition to insert the tuple into. */ - resultRelInfo = estate->es_result_relation_info; + if (proute) + { + ResultRelInfo *partRelInfo; + + slot = ExecPrepareTupleRouting(mtstate, estate, proute, + resultRelInfo, slot, + &partRelInfo); + resultRelInfo = partRelInfo; + } + + ExecMaterializeSlot(slot); + resultRelationDesc = resultRelInfo->ri_RelationDesc; /* @@ -458,7 +472,8 @@ ExecInsert(ModifyTableState *mtstate, */ if (resultRelationDesc->rd_att->constr && resultRelationDesc->rd_att->constr->has_generated_stored) - ExecComputeStoredGenerated(estate, slot, CMD_INSERT); + ExecComputeStoredGenerated(resultRelInfo, estate, slot, + CMD_INSERT); /* * insert into foreign table: let the FDW do it @@ -493,7 +508,8 @@ ExecInsert(ModifyTableState *mtstate, */ if (resultRelationDesc->rd_att->constr && resultRelationDesc->rd_att->constr->has_generated_stored) - ExecComputeStoredGenerated(estate, slot, CMD_INSERT); + ExecComputeStoredGenerated(resultRelInfo, estate, slot, + CMD_INSERT); /* * Check any RLS WITH CHECK policies. @@ -525,7 +541,7 @@ ExecInsert(ModifyTableState *mtstate, * one; except that if we got here via tuple-routing, we don't need to * if there's no BR trigger defined on the partition. */ - if (resultRelInfo->ri_PartitionCheck && + if (resultRelationDesc->rd_rel->relispartition && (resultRelInfo->ri_PartitionRoot == NULL || (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_before_row))) @@ -555,8 +571,8 @@ ExecInsert(ModifyTableState *mtstate, */ vlock: specConflict = false; - if (!ExecCheckIndexConstraints(slot, estate, &conflictTid, - arbiterIndexes)) + if (!ExecCheckIndexConstraints(resultRelInfo, slot, estate, + &conflictTid, arbiterIndexes)) { /* committed conflict tuple found */ if (onconflict == ONCONFLICT_UPDATE) @@ -616,7 +632,8 @@ ExecInsert(ModifyTableState *mtstate, specToken); /* insert index entries for tuple */ - recheckIndexes = ExecInsertIndexTuples(slot, estate, true, + recheckIndexes = ExecInsertIndexTuples(resultRelInfo, + slot, estate, true, &specConflict, arbiterIndexes); @@ -655,8 +672,9 @@ ExecInsert(ModifyTableState *mtstate, /* insert index entries for tuple */ if (resultRelInfo->ri_NumIndices > 0) - recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, - NIL); + recheckIndexes = ExecInsertIndexTuples(resultRelInfo, + slot, estate, false, + NULL, NIL); } } if (canSetTag) @@ -750,6 +768,7 @@ ExecInsert(ModifyTableState *mtstate, */ static TupleTableSlot * ExecDelete(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, ItemPointer tupleid, int32 segid, HeapTuple oldtuple, @@ -763,8 +782,7 @@ ExecDelete(ModifyTableState *mtstate, bool *tupleDeleted, TupleTableSlot **epqreturnslot) { - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; + Relation resultRelationDesc = resultRelInfo->ri_RelationDesc; TM_Result result; TM_FailureData tmfd; TupleTableSlot *slot = NULL; @@ -787,12 +805,6 @@ ExecDelete(ModifyTableState *mtstate, tupleid->ip_posid, segid); - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - /* BEFORE ROW DELETE Triggers */ /* * Disallow DELETE triggers on a split UPDATE. See comments in ExecInsert(). @@ -1145,6 +1157,135 @@ ldelete:; return NULL; } +/* + * ExecCrossPartitionUpdate --- Move an updated tuple to another partition. + * + * This works by first deleting the old tuple from the current partition, + * followed by inserting the new tuple into the root parent table, that is, + * mtstate->rootResultRelInfo. It will be re-routed from there to the + * correct partition. + * + * Returns true if the tuple has been successfully moved, or if it's found + * that the tuple was concurrently deleted so there's nothing more to do + * for the caller. + * + * False is returned if the tuple we're trying to move is found to have been + * concurrently updated. In that case, the caller must to check if the + * updated tuple that's returned in *retry_slot still needs to be re-routed, + * and call this function again or perform a regular update accordingly. + */ +static bool +ExecCrossPartitionUpdate(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, + ItemPointer tupleid, int32 segid, HeapTuple oldtuple, + TupleTableSlot *slot, TupleTableSlot *planSlot, + EPQState *epqstate, bool canSetTag, + TupleTableSlot **retry_slot, + TupleTableSlot **inserted_tuple) +{ + EState *estate = mtstate->ps.state; + PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing; + TupleConversionMap *tupconv_map; + bool tuple_deleted; + TupleTableSlot *epqslot = NULL; + + *inserted_tuple = NULL; + *retry_slot = NULL; + + /* + * Disallow an INSERT ON CONFLICT DO UPDATE that causes the original row + * to migrate to a different partition. Maybe this can be implemented + * some day, but it seems a fringe feature with little redeeming value. + */ + if (((ModifyTable *) mtstate->ps.plan)->onConflictAction == ONCONFLICT_UPDATE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("invalid ON UPDATE specification"), + errdetail("The result tuple would appear in a different partition than the original tuple."))); + + /* + * When an UPDATE is run on a leaf partition, we will not have partition + * tuple routing set up. In that case, fail with partition constraint + * violation error. + */ + if (proute == NULL) + ExecPartitionCheckEmitError(resultRelInfo, slot, estate); + + /* + * Row movement, part 1. Delete the tuple, but skip RETURNING processing. + * We want to return rows from INSERT. + */ + ExecDelete(mtstate, resultRelInfo, tupleid, segid, oldtuple, planSlot, + epqstate, estate, + false, /* processReturning */ + false, /* canSetTag */ + true, /* changingPart */ + false, /* splitUpdate */ + &tuple_deleted, &epqslot); + + /* + * For some reason if DELETE didn't happen (e.g. trigger prevented it, or + * it was already deleted by self, or it was concurrently deleted by + * another transaction), then we should skip the insert as well; + * otherwise, an UPDATE could cause an increase in the total number of + * rows across all partitions, which is clearly wrong. + * + * For a normal UPDATE, the case where the tuple has been the subject of a + * concurrent UPDATE or DELETE would be handled by the EvalPlanQual + * machinery, but for an UPDATE that we've translated into a DELETE from + * this partition and an INSERT into some other partition, that's not + * available, because CTID chains can't span relation boundaries. We + * mimic the semantics to a limited extent by skipping the INSERT if the + * DELETE fails to find a tuple. This ensures that two concurrent + * attempts to UPDATE the same tuple at the same time can't turn one tuple + * into two, and that an UPDATE of a just-deleted tuple can't resurrect + * it. + */ + if (!tuple_deleted) + { + /* + * epqslot will be typically NULL. But when ExecDelete() finds that + * another transaction has concurrently updated the same row, it + * re-fetches the row, skips the delete, and epqslot is set to the + * re-fetched tuple slot. In that case, we need to do all the checks + * again. + */ + if (TupIsNull(epqslot)) + return true; + else + { + *retry_slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); + return false; + } + } + + /* + * resultRelInfo is one of the per-subplan resultRelInfos. So we should + * convert the tuple into root's tuple descriptor if needed, since + * ExecInsert() starts the search from root. + */ + tupconv_map = resultRelInfo->ri_ChildToRootMap; + if (tupconv_map != NULL) + slot = execute_attr_map_slot(tupconv_map->attrMap, + slot, + mtstate->mt_root_tuple_slot); + + /* Tuple routing starts from the root table. */ + *inserted_tuple = ExecInsert(mtstate, mtstate->rootResultRelInfo, slot, + planSlot, estate, canSetTag, + false /* splitUpdate */); + + /* + * Reset the transition state that may possibly have been written by + * INSERT. + */ + if (mtstate->mt_transition_capture) + mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL; + + /* We're done moving. */ + return true; +} + /* ---------------------------------------------------------------- * ExecUpdate * @@ -1169,6 +1310,7 @@ ldelete:; */ static TupleTableSlot * ExecUpdate(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, @@ -1178,12 +1320,10 @@ ExecUpdate(ModifyTableState *mtstate, EState *estate, bool canSetTag) { - ResultRelInfo *resultRelInfo; - Relation resultRelationDesc; + Relation resultRelationDesc = resultRelInfo->ri_RelationDesc; TM_Result result; TM_FailureData tmfd; List *recheckIndexes = NIL; - TupleConversionMap *saved_tcs_map = NULL; /* * abort the operation if not running transactions @@ -1207,12 +1347,6 @@ ExecUpdate(ModifyTableState *mtstate, ExecMaterializeSlot(slot); - /* - * get information on the (current) result relation - */ - resultRelInfo = estate->es_result_relation_info; - resultRelationDesc = resultRelInfo->ri_RelationDesc; - /* BEFORE ROW UPDATE Triggers */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_update_before_row) @@ -1237,7 +1371,8 @@ ExecUpdate(ModifyTableState *mtstate, */ if (resultRelationDesc->rd_att->constr && resultRelationDesc->rd_att->constr->has_generated_stored) - ExecComputeStoredGenerated(estate, slot, CMD_UPDATE); + ExecComputeStoredGenerated(resultRelInfo, estate, slot, + CMD_UPDATE); /* * update in foreign table: let the FDW do it @@ -1274,7 +1409,8 @@ ExecUpdate(ModifyTableState *mtstate, */ if (resultRelationDesc->rd_att->constr && resultRelationDesc->rd_att->constr->has_generated_stored) - ExecComputeStoredGenerated(estate, slot, CMD_UPDATE); + ExecComputeStoredGenerated(resultRelInfo, estate, slot, + CMD_UPDATE); /* * Check any RLS UPDATE WITH CHECK policies @@ -1298,7 +1434,7 @@ lreplace:; * row. So skip the WCO checks if the partition constraint fails. */ partition_constraint_failed = - resultRelInfo->ri_PartitionCheck && + resultRelationDesc->rd_rel->relispartition && !ExecPartitionCheck(resultRelInfo, slot, estate, false); if (!partition_constraint_failed && @@ -1318,127 +1454,29 @@ lreplace:; */ if (partition_constraint_failed) { - bool tuple_deleted; - TupleTableSlot *ret_slot; - TupleTableSlot *epqslot = NULL; - PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing; - int map_index; - TupleConversionMap *tupconv_map; - - /* - * Disallow an INSERT ON CONFLICT DO UPDATE that causes the - * original row to migrate to a different partition. Maybe this - * can be implemented some day, but it seems a fringe feature with - * little redeeming value. - */ - if (((ModifyTable *) mtstate->ps.plan)->onConflictAction == ONCONFLICT_UPDATE) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("invalid ON UPDATE specification"), - errdetail("The result tuple would appear in a different partition than the original tuple."))); - - /* - * When an UPDATE is run on a leaf partition, we will not have - * partition tuple routing set up. In that case, fail with - * partition constraint violation error. - */ - if (proute == NULL) - ExecPartitionCheckEmitError(resultRelInfo, slot, estate); - - /* - * Row movement, part 1. Delete the tuple, but skip RETURNING - * processing. We want to return rows from INSERT. - */ - ExecDelete(mtstate, tupleid, segid, oldtuple, planSlot, epqstate, - estate, false, false /* canSetTag */ , - true /* changingPart */ , - false /* splitUpdate */ , - &tuple_deleted, &epqslot); - - /* - * For some reason if DELETE didn't happen (e.g. trigger prevented - * it, or it was already deleted by self, or it was concurrently - * deleted by another transaction), then we should skip the insert - * as well; otherwise, an UPDATE could cause an increase in the - * total number of rows across all partitions, which is clearly - * wrong. - * - * For a normal UPDATE, the case where the tuple has been the - * subject of a concurrent UPDATE or DELETE would be handled by - * the EvalPlanQual machinery, but for an UPDATE that we've - * translated into a DELETE from this partition and an INSERT into - * some other partition, that's not available, because CTID chains - * can't span relation boundaries. We mimic the semantics to a - * limited extent by skipping the INSERT if the DELETE fails to - * find a tuple. This ensures that two concurrent attempts to - * UPDATE the same tuple at the same time can't turn one tuple - * into two, and that an UPDATE of a just-deleted tuple can't - * resurrect it. - */ - if (!tuple_deleted) - { - /* - * epqslot will be typically NULL. But when ExecDelete() - * finds that another transaction has concurrently updated the - * same row, it re-fetches the row, skips the delete, and - * epqslot is set to the re-fetched tuple slot. In that case, - * we need to do all the checks again. - */ - if (TupIsNull(epqslot)) - return NULL; - else - { - slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); - goto lreplace; - } - } - - /* - * Updates set the transition capture map only when a new subplan - * is chosen. But for inserts, it is set for each row. So after - * INSERT, we need to revert back to the map created for UPDATE; - * otherwise the next UPDATE will incorrectly use the one created - * for INSERT. So first save the one created for UPDATE. - */ - if (mtstate->mt_transition_capture) - saved_tcs_map = mtstate->mt_transition_capture->tcs_map; - - /* - * resultRelInfo is one of the per-subplan resultRelInfos. So we - * should convert the tuple into root's tuple descriptor, since - * ExecInsert() starts the search from root. The tuple conversion - * map list is in the order of mtstate->resultRelInfo[], so to - * retrieve the one for this resultRel, we need to know the - * position of the resultRel in mtstate->resultRelInfo[]. - */ - map_index = resultRelInfo - mtstate->resultRelInfo; - Assert(map_index >= 0 && map_index < mtstate->mt_nplans); - tupconv_map = tupconv_map_for_subplan(mtstate, map_index); - if (tupconv_map != NULL) - slot = execute_attr_map_slot(tupconv_map->attrMap, - slot, - mtstate->mt_root_tuple_slot); + TupleTableSlot *inserted_tuple, + *retry_slot; + bool retry; /* - * Prepare for tuple routing, making it look like we're inserting - * into the root. + * ExecCrossPartitionUpdate will first DELETE the row from the + * partition it's currently in and then insert it back into the + * root table, which will re-route it to the correct partition. + * The first part may have to be repeated if it is detected that + * the tuple we're trying to move has been concurrently updated. */ - Assert(mtstate->rootResultRelInfo != NULL); - slot = ExecPrepareTupleRouting(mtstate, estate, proute, - mtstate->rootResultRelInfo, slot); - - ret_slot = ExecInsert(mtstate, slot, planSlot, - estate, canSetTag, false /* splitUpdate */); - - /* Revert ExecPrepareTupleRouting's node change. */ - estate->es_result_relation_info = resultRelInfo; - if (mtstate->mt_transition_capture) + retry = !ExecCrossPartitionUpdate(mtstate, resultRelInfo, tupleid, + segid, + oldtuple, slot, planSlot, + epqstate, canSetTag, + &retry_slot, &inserted_tuple); + if (retry) { - mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL; - mtstate->mt_transition_capture->tcs_map = saved_tcs_map; + slot = retry_slot; + goto lreplace; } - return ret_slot; + return inserted_tuple; } /* @@ -1602,7 +1640,9 @@ lreplace:; /* insert index entries for tuple if necessary */ if (resultRelInfo->ri_NumIndices > 0 && update_indexes) - recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, NIL); + recheckIndexes = ExecInsertIndexTuples(resultRelInfo, + slot, estate, false, + NULL, NIL); } if (canSetTag) (estate->es_processed)++; @@ -1645,23 +1685,20 @@ lreplace:; */ static TupleTableSlot * ExecSplitUpdate_Insert(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot, EState *estate, bool canSetTag) { - ResultRelInfo *resultRelInfo; Relation resultRelationDesc; bool partition_constraint_failed; - TupleConversionMap *saved_tcs_map = NULL; PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing; - int map_index; TupleConversionMap *tupconv_map; /* * get information on the (current) result relation */ - resultRelInfo = estate->es_result_relation_info; resultRelationDesc = resultRelInfo->ri_RelationDesc; /* ensure slot is independent, consider e.g. EPQ */ @@ -1675,7 +1712,7 @@ ExecSplitUpdate_Insert(ModifyTableState *mtstate, * row. So skip the WCO checks if the partition constraint fails. */ partition_constraint_failed = - resultRelInfo->ri_PartitionCheck && + resultRelInfo->ri_RelationDesc->rd_rel->relispartition && !ExecPartitionCheck(resultRelInfo, slot, estate, false); if (!partition_constraint_failed && @@ -1689,16 +1726,6 @@ ExecSplitUpdate_Insert(ModifyTableState *mtstate, resultRelInfo, slot, estate); } - /* - * Updates set the transition capture map only when a new subplan - * is chosen. But for inserts, it is set for each row. So after - * INSERT, we need to revert back to the map created for UPDATE; - * otherwise the next UPDATE will incorrectly use the one created - * for INSERT. So first save the one created for UPDATE. - */ - if (mtstate->mt_transition_capture) - saved_tcs_map = mtstate->mt_transition_capture->tcs_map; - if (partition_constraint_failed) { /* @@ -1717,9 +1744,7 @@ ExecSplitUpdate_Insert(ModifyTableState *mtstate, * retrieve the one for this resultRel, we need to know the * position of the resultRel in mtstate->resultRelInfo[]. */ - map_index = resultRelInfo - mtstate->resultRelInfo; - Assert(map_index >= 0 && map_index < mtstate->mt_nplans); - tupconv_map = tupconv_map_for_subplan(mtstate, map_index); + tupconv_map = resultRelInfo->ri_ChildToRootMap; if (tupconv_map != NULL) slot = execute_attr_map_slot(tupconv_map->attrMap, slot, @@ -1730,24 +1755,18 @@ ExecSplitUpdate_Insert(ModifyTableState *mtstate, * into the root. */ Assert(mtstate->rootResultRelInfo != NULL); - slot = ExecPrepareTupleRouting(mtstate, estate, proute, - mtstate->rootResultRelInfo, slot); - slot = ExecInsert(mtstate, slot, planSlot, + slot = ExecInsert(mtstate, mtstate->rootResultRelInfo, slot, planSlot, estate, mtstate->canSetTag, true /* splitUpdate */); /* Revert ExecPrepareTupleRouting's node change. */ - estate->es_result_relation_info = resultRelInfo; if (mtstate->mt_transition_capture) - { mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL; - mtstate->mt_transition_capture->tcs_map = saved_tcs_map; - } } else { - slot = ExecInsert(mtstate, slot, planSlot, + slot = ExecInsert(mtstate, resultRelInfo, slot, planSlot, estate, mtstate->canSetTag, true /* splitUpdate */); } @@ -1960,7 +1979,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, */ /* Execute UPDATE with projection */ - *returning = ExecUpdate(mtstate, conflictTid, NULL, + *returning = ExecUpdate(mtstate, resultRelInfo, conflictTid, NULL, resultRelInfo->ri_onConflict->oc_ProjSlot, planSlot, GpIdentity.segindex, @@ -1984,15 +2003,7 @@ static void fireBSTriggers(ModifyTableState *node) { ModifyTable *plan = (ModifyTable *) node->ps.plan; - ResultRelInfo *resultRelInfo = node->resultRelInfo; - - /* - * If the node modifies a partitioned table, we must fire its triggers. - * Note that in that case, node->resultRelInfo points to the first leaf - * partition, not the root table. - */ - if (node->rootResultRelInfo != NULL) - resultRelInfo = node->rootResultRelInfo; + ResultRelInfo *resultRelInfo = node->rootResultRelInfo; switch (node->operation) { @@ -2014,28 +2025,6 @@ fireBSTriggers(ModifyTableState *node) } } -/* - * Return the target rel ResultRelInfo. - * - * This relation is the same as : - * - the relation for which we will fire AFTER STATEMENT triggers. - * - the relation into whose tuple format all captured transition tuples must - * be converted. - * - the root partitioned table. - */ -static ResultRelInfo * -getTargetResultRelInfo(ModifyTableState *node) -{ - /* - * Note that if the node modifies a partitioned table, node->resultRelInfo - * points to the first leaf partition, not the root table. - */ - if (node->rootResultRelInfo != NULL) - return node->rootResultRelInfo; - else - return node->resultRelInfo; -} - /* * Process AFTER EACH STATEMENT triggers */ @@ -2043,7 +2032,7 @@ static void fireASTriggers(ModifyTableState *node) { ModifyTable *plan = (ModifyTable *) node->ps.plan; - ResultRelInfo *resultRelInfo = getTargetResultRelInfo(node); + ResultRelInfo *resultRelInfo = node->rootResultRelInfo; switch (node->operation) { @@ -2077,7 +2066,7 @@ static void ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate) { ModifyTable *plan = (ModifyTable *) mtstate->ps.plan; - ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate); + ResultRelInfo *targetRelInfo = mtstate->rootResultRelInfo; /* Check for transition tables on the directly targeted relation. */ mtstate->mt_transition_capture = @@ -2090,50 +2079,27 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate) MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc, RelationGetRelid(targetRelInfo->ri_RelationDesc), CMD_UPDATE); - - /* - * If we found that we need to collect transition tuples then we may also - * need tuple conversion maps for any children that have TupleDescs that - * aren't compatible with the tuplestores. (We can share these maps - * between the regular and ON CONFLICT cases.) - */ - if (mtstate->mt_transition_capture != NULL || - mtstate->mt_oc_transition_capture != NULL) - { - ExecSetupChildParentMapForSubplan(mtstate); - - /* - * Install the conversion map for the first plan for UPDATE and DELETE - * operations. It will be advanced each time we switch to the next - * plan. (INSERT operations set it every time, so we need not update - * mtstate->mt_oc_transition_capture here.) - */ - if (mtstate->mt_transition_capture && mtstate->operation != CMD_INSERT) - mtstate->mt_transition_capture->tcs_map = - tupconv_map_for_subplan(mtstate, 0); - } } /* * ExecPrepareTupleRouting --- prepare for routing one tuple * * Determine the partition in which the tuple in slot is to be inserted, - * and modify mtstate and estate to prepare for it. + * and return its ResultRelInfo in *partRelInfo. The return value is + * a slot holding the tuple of the partition rowtype. * - * Caller must revert the estate changes after executing the insertion! - * In mtstate, transition capture changes may also need to be reverted. - * - * Returns a slot holding the tuple of the partition rowtype. + * This also sets the transition table information in mtstate based on the + * selected partition. */ static TupleTableSlot * ExecPrepareTupleRouting(ModifyTableState *mtstate, EState *estate, PartitionTupleRouting *proute, ResultRelInfo *targetRelInfo, - TupleTableSlot *slot) + TupleTableSlot *slot, + ResultRelInfo **partRelInfo) { ResultRelInfo *partrel; - PartitionRoutingInfo *partrouteinfo; TupleConversionMap *map; /* @@ -2144,113 +2110,40 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate, * UPDATE to another partition becomes a DELETE+INSERT. */ partrel = ExecFindPartition(mtstate, targetRelInfo, proute, slot, estate); - partrouteinfo = partrel->ri_PartitionInfo; - Assert(partrouteinfo != NULL); - - /* - * Make it look like we are inserting into the partition. - */ - estate->es_result_relation_info = partrel; /* * If we're capturing transition tuples, we might need to convert from the - * partition rowtype to root partitioned table's rowtype. + * partition rowtype to root partitioned table's rowtype. But if there + * are no BEFORE triggers on the partition that could change the tuple, we + * can just remember the original unconverted tuple to avoid a needless + * round trip conversion. */ if (mtstate->mt_transition_capture != NULL) { - if (partrel->ri_TrigDesc && - partrel->ri_TrigDesc->trig_insert_before_row) - { - /* - * If there are any BEFORE triggers on the partition, we'll have - * to be ready to convert their result back to tuplestore format. - */ - mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL; - mtstate->mt_transition_capture->tcs_map = - partrouteinfo->pi_PartitionToRootMap; - } - else - { - /* - * Otherwise, just remember the original unconverted tuple, to - * avoid a needless round trip conversion. - */ - mtstate->mt_transition_capture->tcs_original_insert_tuple = slot; - mtstate->mt_transition_capture->tcs_map = NULL; - } - } - if (mtstate->mt_oc_transition_capture != NULL) - { - mtstate->mt_oc_transition_capture->tcs_map = - partrouteinfo->pi_PartitionToRootMap; + bool has_before_insert_row_trig; + + has_before_insert_row_trig = (partrel->ri_TrigDesc && + partrel->ri_TrigDesc->trig_insert_before_row); + + mtstate->mt_transition_capture->tcs_original_insert_tuple = + !has_before_insert_row_trig ? slot : NULL; } /* * Convert the tuple, if necessary. */ - map = partrouteinfo->pi_RootToPartitionMap; + map = partrel->ri_RootToPartitionMap; if (map != NULL) { - TupleTableSlot *new_slot = partrouteinfo->pi_PartitionTupleSlot; + TupleTableSlot *new_slot = partrel->ri_PartitionTupleSlot; slot = execute_attr_map_slot(map->attrMap, slot, new_slot); } + *partRelInfo = partrel; return slot; } -/* - * Initialize the child-to-root tuple conversion map array for UPDATE subplans. - * - * This map array is required to convert the tuple from the subplan result rel - * to the target table descriptor. This requirement arises for two independent - * scenarios: - * 1. For update-tuple-routing. - * 2. For capturing tuples in transition tables. - */ -static void -ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate) -{ - ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate); - ResultRelInfo *resultRelInfos = mtstate->resultRelInfo; - TupleDesc outdesc; - int numResultRelInfos = mtstate->mt_nplans; - int i; - - /* - * Build array of conversion maps from each child's TupleDesc to the one - * used in the target relation. The map pointers may be NULL when no - * conversion is necessary, which is hopefully a common case. - */ - - /* Get tuple descriptor of the target rel. */ - outdesc = RelationGetDescr(targetRelInfo->ri_RelationDesc); - - mtstate->mt_per_subplan_tupconv_maps = (TupleConversionMap **) - palloc(sizeof(TupleConversionMap *) * numResultRelInfos); - - for (i = 0; i < numResultRelInfos; ++i) - { - mtstate->mt_per_subplan_tupconv_maps[i] = - convert_tuples_by_name(RelationGetDescr(resultRelInfos[i].ri_RelationDesc), - outdesc); - } -} - -/* - * For a given subplan index, get the tuple conversion map. - */ -static TupleConversionMap * -tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan) -{ - /* If nobody else set the per-subplan array of maps, do so ourselves. */ - if (mtstate->mt_per_subplan_tupconv_maps == NULL) - ExecSetupChildParentMapForSubplan(mtstate); - - Assert(whichplan >= 0 && whichplan < mtstate->mt_nplans); - return mtstate->mt_per_subplan_tupconv_maps[whichplan]; -} - /* ---------------------------------------------------------------- * ExecModifyTable * @@ -2262,11 +2155,10 @@ static TupleTableSlot * ExecModifyTable(PlanState *pstate) { ModifyTableState *node = castNode(ModifyTableState, pstate); - PartitionTupleRouting *proute = node->mt_partition_tuple_routing; EState *estate = node->ps.state; CmdType operation = node->operation; - ResultRelInfo *saved_resultRelInfo; ResultRelInfo *resultRelInfo; + ResultRelInfo *routedResultRelInfo; PlanState *subplanstate; JunkFilter *junkfilter; AttrNumber action_attno; @@ -2334,17 +2226,6 @@ ExecModifyTable(PlanState *pstate) action_attno = resultRelInfo->ri_action_attno; segid_attno = resultRelInfo->ri_segid_attno; - /* - * es_result_relation_info must point to the currently active result - * relation while we are within this ModifyTable node. Even though - * ModifyTable nodes can't be nested statically, they can be nested - * dynamically (since our subplan could include a reference to a modifying - * CTE). So we have to save and restore the caller's value. - */ - saved_resultRelInfo = estate->es_result_relation_info; - - estate->es_result_relation_info = resultRelInfo; - /* * Fetch rows from subplan(s), and execute the required table modification * for each row. @@ -2375,25 +2256,13 @@ ExecModifyTable(PlanState *pstate) node->mt_whichplan++; if (node->mt_whichplan < node->mt_nplans) { - estate->es_result_relation_info = estate->es_result_relations + node->mt_whichplan; - resultRelInfo = estate->es_result_relation_info; + resultRelInfo = &node->resultRelInfo[node->mt_whichplan]; subplanstate = node->mt_plans[node->mt_whichplan]; - junkfilter = estate->es_result_relation_info->ri_junkFilter; - action_attno = estate->es_result_relation_info->ri_action_attno; - segid_attno = estate->es_result_relation_info->ri_segid_attno; + junkfilter = resultRelInfo->ri_junkFilter; + action_attno = resultRelInfo->ri_action_attno; + segid_attno = resultRelInfo->ri_segid_attno; EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan, node->mt_arowmarks[node->mt_whichplan]); - /* Prepare to convert transition tuples from this child. */ - if (node->mt_transition_capture != NULL) - { - node->mt_transition_capture->tcs_map = - tupconv_map_for_subplan(node, node->mt_whichplan); - } - if (node->mt_oc_transition_capture != NULL) - { - node->mt_oc_transition_capture->tcs_map = - tupconv_map_for_subplan(node, node->mt_whichplan); - } continue; } else @@ -2425,7 +2294,6 @@ ExecModifyTable(PlanState *pstate) */ slot = ExecProcessReturning(resultRelInfo, NULL, planSlot); - estate->es_result_relation_info = saved_resultRelInfo; return slot; } @@ -2538,88 +2406,84 @@ ExecModifyTable(PlanState *pstate) switch (operation) { case CMD_INSERT: - /* Prepare for tuple routing if needed. */ - if (proute) - slot = ExecPrepareTupleRouting(node, estate, proute, - resultRelInfo, slot); - slot = ExecInsert(node, slot, planSlot, + slot = ExecInsert(node, resultRelInfo, slot, planSlot, estate, node->canSetTag, false /* splitUpdate */); - /* Revert ExecPrepareTupleRouting's state change. */ - if (proute) - estate->es_result_relation_info = resultRelInfo; break; case CMD_UPDATE: - /* Prepare for tuple routing if needed. */ - if (castNode(ModifyTable, node->ps.plan)->forceTupleRouting) + + /* + * INSERT part of split update handles the routing by itself, + * no need to force it + */ + if (castNode(ModifyTable, node->ps.plan)->forceTupleRouting && DML_INSERT != action) + { + PartitionTupleRouting *proute = node->mt_partition_tuple_routing; + slot = ExecPrepareTupleRouting(node, estate, proute, - resultRelInfo, slot); + resultRelInfo, slot, + &routedResultRelInfo); + } + else + routedResultRelInfo = resultRelInfo; + if (!AttributeNumberIsValid(action_attno)) { /* normal non-split UPDATE */ - slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot, - segid, - &node->mt_epqstate, estate, node->canSetTag); + slot = ExecUpdate(node, routedResultRelInfo, tupleid, oldtuple, + slot, planSlot, segid, + &node->mt_epqstate, estate, + node->canSetTag); } else if (DML_INSERT == action) { - slot = ExecSplitUpdate_Insert(node, slot, planSlot, + slot = ExecSplitUpdate_Insert(node, routedResultRelInfo, slot, planSlot, estate, node->canSetTag); } else /* DML_DELETE */ { - slot = ExecDelete(node, tupleid, segid, oldtuple, planSlot, + slot = ExecDelete(node, routedResultRelInfo, tupleid, segid, + oldtuple, planSlot, &node->mt_epqstate, estate, - false, - false /* canSetTag */, - true /* changingPart */ , - true /* splitUpdate */ , + false, /* processReturning */ + false, /* canSetTag */ + true, /* changingPart */ + true, /* splitUpdate */ NULL, NULL); } - /* Revert ExecPrepareTupleRouting's state change. */ - if (castNode(ModifyTable, node->ps.plan)->forceTupleRouting) - estate->es_result_relation_info = resultRelInfo; break; case CMD_DELETE: if (castNode(ModifyTable, node->ps.plan)->forceTupleRouting) + { + PartitionTupleRouting *proute = node->mt_partition_tuple_routing; + planSlot = ExecPrepareTupleRouting(node, estate, proute, - resultRelInfo, slot); - slot = ExecDelete(node, tupleid, segid, oldtuple, planSlot, - &node->mt_epqstate, estate, - true, node->canSetTag, - false /* changingPart */ , - false /* splitUpdate */ , + resultRelInfo, slot, + &routedResultRelInfo); + } + else + routedResultRelInfo = resultRelInfo; + + slot = ExecDelete(node, routedResultRelInfo, tupleid, segid, oldtuple, + planSlot, &node->mt_epqstate, estate, + true, /* processReturning */ + node->canSetTag, + false, /* changingPart */ + false, /* splitUpdate */ NULL, NULL); - if (castNode(ModifyTable, node->ps.plan)->forceTupleRouting) - estate->es_result_relation_info = resultRelInfo; break; default: elog(ERROR, "unknown operation"); break; } - /* - * If the target is a partitioned table, ExecInsert / ExecUpdate / - * ExecDelete might have changed es_result_relation_info to point to - * a partition, instead of the top-level table. Reset it. (It would - * be more tidy if those functions cleaned up after themselves, but - * it's more robust to do it here just once.) - */ - estate->es_result_relation_info = resultRelInfo; - /* * If we got a RETURNING result, return it to caller. We'll continue * the work on next call. */ if (slot) - { - estate->es_result_relation_info = saved_resultRelInfo; return slot; - } } - /* Restore es_result_relation_info before exiting */ - estate->es_result_relation_info = saved_resultRelInfo; - /* * We're done, but fire AFTER STATEMENT triggers before exiting. */ @@ -2642,10 +2506,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) ModifyTableState *mtstate; CmdType operation = node->operation; int nplans = list_length(node->plans); - ResultRelInfo *saved_resultRelInfo; ResultRelInfo *resultRelInfo; Plan *subplan; - ListCell *l; + ListCell *l, + *l1; int i; Relation rel; bool update_tuple_routing_needed = node->partColsUpdated; @@ -2666,13 +2530,36 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->mt_done = false; mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans); - mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex; + mtstate->resultRelInfo = (ResultRelInfo *) + palloc(nplans * sizeof(ResultRelInfo)); mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans); - /* If modifying a partitioned table, initialize the root table info */ - if (node->rootResultRelIndex >= 0) - mtstate->rootResultRelInfo = estate->es_root_result_relations + - node->rootResultRelIndex; + /*---------- + * Resolve the target relation. This is the same as: + * + * - the relation for which we will fire FOR STATEMENT triggers, + * - the relation into whose tuple format all captured transition tuples + * must be converted, and + * - the root partitioned table used for tuple routing. + * + * If it's a partitioned table, the root partition doesn't appear + * elsewhere in the plan and its RT index is given explicitly in + * node->rootRelation. Otherwise (i.e. table inheritance) the target + * relation is the first relation in the node->resultRelations list. + *---------- + */ + if (node->rootRelation > 0) + { + mtstate->rootResultRelInfo = makeNode(ResultRelInfo); + ExecInitResultRelation(estate, mtstate->rootResultRelInfo, + node->rootRelation); + } + else + { + mtstate->rootResultRelInfo = mtstate->resultRelInfo; + ExecInitResultRelation(estate, mtstate->resultRelInfo, + linitial_int(node->resultRelations)); + } mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans); mtstate->mt_nplans = nplans; @@ -2698,23 +2585,33 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) if (Gp_role != GP_ROLE_EXECUTE || Gp_is_writer) mtstate->fireBSTriggers = true; + /* + * Build state for collecting transition tuples. This requires having a + * valid trigger query context, so skip it in explain-only mode. + */ + if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY)) + ExecSetupTransitionCaptureState(mtstate, estate); + /* * call ExecInitNode on each of the plans to be executed and save the * results into the array "mt_plans". This is also a convenient place to * verify that the proposed target relations are valid and open their - * indexes for insertion of new index entries. Note we *must* set - * estate->es_result_relation_info correctly while we initialize each - * sub-plan; external modules such as FDWs may depend on that (see - * contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify() as one - * example). + * indexes for insertion of new index entries. */ - saved_resultRelInfo = estate->es_result_relation_info; - resultRelInfo = mtstate->resultRelInfo; i = 0; - foreach(l, node->plans) + forboth(l, node->resultRelations, l1, node->plans) { - subplan = (Plan *) lfirst(l); + Index resultRelation = lfirst_int(l); + + subplan = (Plan *) lfirst(l1); + + /* + * This opens result relation and fills ResultRelInfo. (root relation + * was initialized already.) + */ + if (resultRelInfo != mtstate->rootResultRelInfo) + ExecInitResultRelation(estate, resultRelInfo, resultRelation); /* Initialize the usesFdwDirectModify flag */ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i, @@ -2770,7 +2667,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) update_tuple_routing_needed = true; /* Now init the plan for this result rel */ - estate->es_result_relation_info = resultRelInfo; mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags); mtstate->mt_scans[i] = ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]), @@ -2793,14 +2689,29 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) eflags); } + /* + * If needed, initialize a map to convert tuples in the child format + * to the format of the table mentioned in the query (root relation). + * It's needed for update tuple routing, because the routing starts + * from the root relation. It's also needed for capturing transition + * tuples, because the transition tuple store can only store tuples in + * the root table format. + * + * For INSERT, the map is only initialized for a given partition when + * the partition itself is first initialized by ExecFindPartition(). + */ + if (update_tuple_routing_needed || + (mtstate->mt_transition_capture && + mtstate->operation != CMD_INSERT)) + resultRelInfo->ri_ChildToRootMap = + convert_tuples_by_name(RelationGetDescr(resultRelInfo->ri_RelationDesc), + RelationGetDescr(mtstate->rootResultRelInfo->ri_RelationDesc)); resultRelInfo++; i++; } - estate->es_result_relation_info = saved_resultRelInfo; - /* Get the target relation */ - rel = (getTargetResultRelInfo(mtstate))->ri_RelationDesc; + rel = mtstate->rootResultRelInfo->ri_RelationDesc; /* * GPDB dynamic scan nodes optimize memory usage by avoiding the need to @@ -2838,26 +2749,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) ExecSetupPartitionTupleRouting(estate, mtstate, rel); /* - * Build state for collecting transition tuples. This requires having a - * valid trigger query context, so skip it in explain-only mode. - */ - if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY)) - ExecSetupTransitionCaptureState(mtstate, estate); - - /* - * Construct mapping from each of the per-subplan partition attnos to the - * root attno. This is required when during update row movement the tuple - * descriptor of a source partition does not match the root partitioned - * table descriptor. In such a case we need to convert tuples to the root - * tuple descriptor, because the search for destination partition starts - * from the root. We'll also need a slot to store these converted tuples. - * We can skip this setup if it's not a partition key update. + * For update row movement we'll need a dedicated slot to store the tuples + * that have been converted from partition format to the root table + * format. */ if (update_tuple_routing_needed) - { - ExecSetupChildParentMapForSubplan(mtstate); mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL); - } /* * Initialize any WITH CHECK OPTION constraints if needed. @@ -3106,15 +3003,27 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) TupleTableSlot *junkresslot; subplan = mtstate->mt_plans[i]->plan; - if (operation == CMD_INSERT || operation == CMD_UPDATE) - ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, - subplan->targetlist); junkresslot = ExecInitExtraTupleSlot(estate, NULL, table_slot_callbacks(resultRelInfo->ri_RelationDesc)); - j = ExecInitJunkFilter(subplan->targetlist, - junkresslot); + + /* + * For an INSERT or UPDATE, the result tuple must always match + * the target table's descriptor. For a DELETE, it won't + * (indeed, there's probably no non-junk output columns). + */ + if (operation == CMD_INSERT || operation == CMD_UPDATE) + { + ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, + subplan->targetlist); + j = ExecInitJunkFilterInsertion(subplan->targetlist, + RelationGetDescr(resultRelInfo->ri_RelationDesc), + junkresslot); + } + else + j = ExecInitJunkFilter(subplan->targetlist, + junkresslot); if (operation == CMD_UPDATE || operation == CMD_DELETE) { diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index c92e26c5ac45..d64ee4fb683f 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -816,7 +816,15 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) sstate->planstate = (PlanState *) list_nth(estate->es_subplanstates, subplan->plan_id - 1); - /* ... and to its parent's state */ + /* + * This check can fail if the planner mistakenly puts a parallel-unsafe + * subplan into a parallelized subquery; see ExecSerializePlan. + */ + if (Gp_role != GP_ROLE_EXECUTE && sstate->planstate == NULL) + elog(ERROR, "subplan \"%s\" was not initialized", + subplan->plan_name); + + /* Link to parent's state, too */ sstate->parent = parent; /* Initialize subexpressions */ @@ -1533,83 +1541,3 @@ ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent) parent->chgParam = bms_add_member(parent->chgParam, paramid); } } - - -/* - * ExecInitAlternativeSubPlan - * - * Initialize for execution of one of a set of alternative subplans. - */ -AlternativeSubPlanState * -ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent) -{ - AlternativeSubPlanState *asstate = makeNode(AlternativeSubPlanState); - double num_calls; - SubPlan *subplan1; - SubPlan *subplan2; - Cost cost1; - Cost cost2; - ListCell *lc; - - asstate->subplan = asplan; - - /* - * Initialize subplans. (Can we get away with only initializing the one - * we're going to use?) - */ - foreach(lc, asplan->subplans) - { - SubPlan *sp = lfirst_node(SubPlan, lc); - SubPlanState *sps = ExecInitSubPlan(sp, parent); - - asstate->subplans = lappend(asstate->subplans, sps); - parent->subPlan = lappend(parent->subPlan, sps); - } - - /* - * Select the one to be used. For this, we need an estimate of the number - * of executions of the subplan. We use the number of output rows - * expected from the parent plan node. This is a good estimate if we are - * in the parent's targetlist, and an underestimate (but probably not by - * more than a factor of 2) if we are in the qual. - */ - num_calls = parent->plan->plan_rows; - - /* - * The planner saved enough info so that we don't have to work very hard - * to estimate the total cost, given the number-of-calls estimate. - */ - Assert(list_length(asplan->subplans) == 2); - subplan1 = (SubPlan *) linitial(asplan->subplans); - subplan2 = (SubPlan *) lsecond(asplan->subplans); - - cost1 = subplan1->startup_cost + num_calls * subplan1->per_call_cost; - cost2 = subplan2->startup_cost + num_calls * subplan2->per_call_cost; - - if (cost1 < cost2) - asstate->active = 0; - else - asstate->active = 1; - - return asstate; -} - -/* - * ExecAlternativeSubPlan - * - * Execute one of a set of alternative subplans. - * - * Note: in future we might consider changing to different subplans on the - * fly, in case the original rowcount estimate turns out to be way off. - */ -Datum -ExecAlternativeSubPlan(AlternativeSubPlanState *node, - ExprContext *econtext, - bool *isNull) -{ - /* Just pass control to the active subplan */ - SubPlanState *activesp = list_nth_node(SubPlanState, - node->subplans, node->active); - - return ExecSubPlan(activesp, econtext, isNull); -} diff --git a/src/backend/gpopt/gpdbwrappers.cpp b/src/backend/gpopt/gpdbwrappers.cpp index a6ee4403cebb..ca9fd915e34e 100644 --- a/src/backend/gpopt/gpdbwrappers.cpp +++ b/src/backend/gpopt/gpdbwrappers.cpp @@ -526,17 +526,16 @@ gpdb::IsFuncAllowedForPartitionSelection(Oid funcid) // For range partition selection, the logic in ORCA checks on bounds of the partition ranges. // Hence these must be increasing functions. case F_TIMESTAMP_DATE: // date(timestamp) -> date - case F_DTOI4: // int4(float8) -> int4 - case F_FTOI4: // int4(float4) -> int4 - case F_INT82: // int2(int8) -> int2 - case F_INT84: // int4(int8) -> int4 - case F_I4TOI2: // int2(int4) -> int2 - case F_FTOI8: // int8(float4) -> int8 - case F_FTOI2: // int2(float4) -> int2 + case F_FLOAT8_INT4: // int4(float8) -> int4 + case F_FLOAT4_INT4: // int4(float4) -> int4 + case F_INT8_INT2: // int2(int8) -> int2 + case F_INT8_INT4: // int4(int8) -> int4 + case F_INT4_INT2: // int2(int4) -> int2 + case F_FLOAT4_INT8: // int8(float4) -> int8 + case F_FLOAT4_INT2: // int2(float4) -> int2 case F_FLOAT4_NUMERIC: // numeric(float4) -> numeric - case F_DTOI8: // int8(float8) -> int8 - case F_DTOI2: // int2(float4) -> int2 - case F_DTOF: // float4(float8) -> float4 + case F_FLOAT8_INT8: // int8(float8) -> int8 + case F_FLOAT8_FLOAT4: // float4(float8) -> float4 case F_FLOAT8_NUMERIC: // numeric(float8) -> numeric case F_NUMERIC_INT8: // int8(numeric) -> int8 case F_NUMERIC_INT2: // int2(numeric) -> int2 @@ -568,11 +567,11 @@ gpdb::IsFuncNDVPreserving(Oid funcid) switch (funcid) { // for now, these are the functions we consider for this optimization - case F_LOWER: - case F_LTRIM1: - case F_BTRIM1: - case F_RTRIM1: - case F_UPPER: + case F_LOWER_TEXT: + case F_LTRIM_TEXT: + case F_BTRIM_TEXT: + case F_RTRIM_TEXT: + case F_UPPER_TEXT: return true; default: return false; diff --git a/src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp b/src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp index 679562ee4735..678b9193b35f 100644 --- a/src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp +++ b/src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp @@ -4388,7 +4388,6 @@ CTranslatorDXLToPlStmt::TranslateDXLDml( dml->canSetTag = true; // FIXME dml->nominalRelation = index; dml->resultRelations = ListMake1Int(index); - dml->resultRelIndex = list_length(m_result_rel_list) - 1; dml->rootRelation = md_rel->IsPartitioned() ? index : 0; dml->plans = ListMake1(child_plan); diff --git a/src/backend/gpopt/translate/CTranslatorRelcacheToDXL.cpp b/src/backend/gpopt/translate/CTranslatorRelcacheToDXL.cpp index 26e7e9055e47..51f882a9e994 100644 --- a/src/backend/gpopt/translate/CTranslatorRelcacheToDXL.cpp +++ b/src/backend/gpopt/translate/CTranslatorRelcacheToDXL.cpp @@ -1098,7 +1098,7 @@ CTranslatorRelcacheToDXL::RetrieveType(CMemoryPool *mp, IMDId *mdid) // count aggregate is the same for all types CMDIdGPDB *mdid_count = - GPOS_NEW(mp) CMDIdGPDB(IMDId::EmdidGeneral, COUNT_ANY_OID); + GPOS_NEW(mp) CMDIdGPDB(IMDId::EmdidGeneral, GPDB_COUNT_ANY); // check if type is composite CMDIdGPDB *mdid_type_relid = nullptr; diff --git a/src/backend/gpopt/utils/COptTasks.cpp b/src/backend/gpopt/utils/COptTasks.cpp index a8fcd23a98d4..c3af62f9af24 100644 --- a/src/backend/gpopt/utils/COptTasks.cpp +++ b/src/backend/gpopt/utils/COptTasks.cpp @@ -384,7 +384,7 @@ COptTasks::CreateOptimizerConfig(CMemoryPool *mp, ICostModel *cost_model) * enforce them ourselves in the executor */ push_group_by_below_setop_threshold, xform_bind_threshold, skew_factor), - GPOS_NEW(mp) CWindowOids(OID(F_WINDOW_ROW_NUMBER), OID(F_WINDOW_RANK))); + GPOS_NEW(mp) CWindowOids(OID(F_ROW_NUMBER), OID(F_RANK_))); } //--------------------------------------------------------------------------- diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index d92cfc619c96..4eeb8c414995 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2009,12 +2009,6 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; - case EEOP_ALTERNATIVE_SUBPLAN: - build_EvalXFunc(b, mod, "ExecEvalAlternativeSubPlan", - v_state, op, v_econtext); - LLVMBuildBr(b, opblocks[opno + 1]); - break; - case EEOP_AGG_STRICT_DESERIALIZE: case EEOP_AGG_DESERIALIZE: { diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index 61825881dfe1..4f74fd6d07fd 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -102,7 +102,6 @@ void *referenced_functions[] = ExecAggTransReparent, ExecEvalAggOrderedTransDatum, ExecEvalAggOrderedTransTuple, - ExecEvalAlternativeSubPlan, ExecEvalArrayCoerce, ExecEvalArrayExpr, ExecEvalConstraintCheck, diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c index 5214d328656f..0f79b28bb5ab 100644 --- a/src/backend/libpq/auth-scram.c +++ b/src/backend/libpq/auth-scram.c @@ -1400,8 +1400,8 @@ scram_mock_salt(const char *username) /* * Generate salt using a SHA256 hash of the username and the cluster's * mock authentication nonce. (This works as long as the salt length is - * not larger the SHA256 digest length. If the salt is smaller, the caller - * will just ignore the extra data.) + * not larger than the SHA256 digest length. If the salt is smaller, the + * caller will just ignore the extra data.) */ StaticAssertStmt(PG_SHA256_DIGEST_LENGTH >= SCRAM_DEFAULT_SALT_LEN, "salt length greater than SHA256 digest length"); diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index fbe0b255089e..452f69024525 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -1029,7 +1029,7 @@ recv_password_packet(Port *port) } initStringInfo(&buf); - if (pq_getmessage(&buf, 1000)) /* receive password */ + if (pq_getmessage(&buf, 0)) /* receive password */ { /* EOF - pq_getmessage already logged a suitable message */ pfree(buf.data); @@ -1893,7 +1893,7 @@ pg_SSPI_recvauth(Port *port) (errmsg("could not load library \"%s\": error code %lu", "SECUR32.DLL", GetLastError()))); - _QuerySecurityContextToken = (QUERY_SECURITY_CONTEXT_TOKEN_FN) + _QuerySecurityContextToken = (QUERY_SECURITY_CONTEXT_TOKEN_FN) (pg_funcptr_t) GetProcAddress(secur32, "QuerySecurityContextToken"); if (_QuerySecurityContextToken == NULL) { @@ -2900,7 +2900,7 @@ InitializeLDAPConnection(Port *port, LDAP **ldap) ldap_unbind(*ldap); return STATUS_ERROR; } - _ldap_start_tls_sA = (__ldap_start_tls_sA) GetProcAddress(ldaphandle, "ldap_start_tls_sA"); + _ldap_start_tls_sA = (__ldap_start_tls_sA) (pg_funcptr_t) GetProcAddress(ldaphandle, "ldap_start_tls_sA"); if (_ldap_start_tls_sA == NULL) { ereport(LOG, diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c index a42783926928..9a7dd77dd71f 100644 --- a/src/backend/libpq/be-secure-gssapi.c +++ b/src/backend/libpq/be-secure-gssapi.c @@ -209,7 +209,7 @@ be_gssapi_write(Port *port, void *ptr, size_t len) PqGSSSendConsumed += input.length; /* 4 network-order bytes of length, then payload */ - netlen = htonl(output.length); + netlen = pg_hton32(output.length); memcpy(PqGSSSendBuffer + PqGSSSendLength, &netlen, sizeof(uint32)); PqGSSSendLength += sizeof(uint32); @@ -323,7 +323,7 @@ be_gssapi_read(Port *port, void *ptr, size_t len) } /* Decode the packet length and check for overlength packet */ - input.length = ntohl(*(uint32 *) PqGSSRecvBuffer); + input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer); if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) ereport(FATAL, @@ -509,7 +509,7 @@ secure_open_gssapi(Port *port) /* * Get the length for this packet from the length header. */ - input.length = ntohl(*(uint32 *) PqGSSRecvBuffer); + input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer); /* Done with the length, reset our buffer */ PqGSSRecvLength = 0; @@ -567,7 +567,7 @@ secure_open_gssapi(Port *port) */ if (output.length > 0) { - uint32 netlen = htonl(output.length); + uint32 netlen = pg_hton32(output.length); if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)) ereport(FATAL, diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index 8b21ff4065c5..9231a1470cf4 100644 --- a/src/backend/libpq/be-secure-openssl.c +++ b/src/backend/libpq/be-secure-openssl.c @@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name) char *dp; char *result; + if (membuf == NULL) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("failed to create BIO"))); + (void) BIO_set_close(membuf, BIO_CLOSE); for (i = 0; i < count; i++) { e = X509_NAME_get_entry(name, i); nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e)); + if (nid == NID_undef) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not get NID for ASN1_OBJECT object"))); v = X509_NAME_ENTRY_get_data(e); field_name = OBJ_nid2sn(nid); - if (!field_name) + if (field_name == NULL) field_name = OBJ_nid2ln(nid); + if (field_name == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid))); BIO_printf(membuf, "/%s=", field_name); ASN1_STRING_print_ex(membuf, v, ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB) @@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name) result = pstrdup(dp); if (dp != sp) pfree(dp); - BIO_free(membuf); + if (BIO_free(membuf) != 1) + elog(ERROR, "could not free OpenSSL BIO structure"); return result; } diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 0325c057672f..99339791a21f 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -29,6 +29,7 @@ #include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "common/ip.h" +#include "common/string.h" #include "funcapi.h" #include "libpq/ifaddr.h" #include "libpq/libpq.h" @@ -54,7 +55,6 @@ #define MAX_TOKEN 256 -#define MAX_LINE 8192 /* callback data for check_network_callback */ typedef struct check_network_data @@ -166,11 +166,19 @@ pg_isblank(const char c) /* * Grab one token out of the string pointed to by *lineptr. * - * Tokens are strings of non-blank - * characters bounded by blank characters, commas, beginning of line, and - * end of line. Blank means space or tab. Tokens can be delimited by - * double quotes (this allows the inclusion of blanks, but not newlines). - * Comments (started by an unquoted '#') are skipped. + * Tokens are strings of non-blank characters bounded by blank characters, + * commas, beginning of line, and end of line. Blank means space or tab. + * + * Tokens can be delimited by double quotes (this allows the inclusion of + * blanks or '#', but not newlines). As in SQL, write two double-quotes + * to represent a double quote. + * + * Comments (started by an unquoted '#') are skipped, i.e. the remainder + * of the line is ignored. + * + * (Note that line continuation processing happens before tokenization. + * Thus, if a continuation occurs within quoted text or a comment, the + * quoted text or comment is considered to continue to the next line.) * * The token, if any, is returned at *buf (a buffer of size bufsz), and * *lineptr is advanced past the token. @@ -470,6 +478,7 @@ static MemoryContext tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel) { int line_number = 1; + StringInfoData buf; MemoryContext linecxt; MemoryContext oldcxt; @@ -478,47 +487,60 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel) ALLOCSET_SMALL_SIZES); oldcxt = MemoryContextSwitchTo(linecxt); + initStringInfo(&buf); + *tok_lines = NIL; while (!feof(file) && !ferror(file)) { - char rawline[MAX_LINE]; char *lineptr; List *current_line = NIL; char *err_msg = NULL; + int last_backslash_buflen = 0; + int continuations = 0; + + /* Collect the next input line, handling backslash continuations */ + resetStringInfo(&buf); - if (!fgets(rawline, sizeof(rawline), file)) + while (pg_get_line_append(file, &buf)) { - int save_errno = errno; + /* Strip trailing newline, including \r in case we're on Windows */ + buf.len = pg_strip_crlf(buf.data); - if (!ferror(file)) - break; /* normal EOF */ + /* + * Check for backslash continuation. The backslash must be after + * the last place we found a continuation, else two backslashes + * followed by two \n's would behave surprisingly. + */ + if (buf.len > last_backslash_buflen && + buf.data[buf.len - 1] == '\\') + { + /* Continuation, so strip it and keep reading */ + buf.data[--buf.len] = '\0'; + last_backslash_buflen = buf.len; + continuations++; + continue; + } + + /* Nope, so we have the whole line */ + break; + } + + if (ferror(file)) + { /* I/O error! */ + int save_errno = errno; + ereport(elevel, (errcode_for_file_access(), errmsg("could not read file \"%s\": %m", filename))); err_msg = psprintf("could not read file \"%s\": %s", filename, strerror(save_errno)); - rawline[0] = '\0'; - } - if (strlen(rawline) == MAX_LINE - 1) - { - /* Line too long! */ - ereport(elevel, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("authentication file line too long"), - errcontext("line %d of configuration file \"%s\"", - line_number, filename))); - err_msg = "authentication file line too long"; + break; } - /* Strip trailing linebreak from rawline */ - lineptr = rawline + strlen(rawline) - 1; - while (lineptr >= rawline && (*lineptr == '\n' || *lineptr == '\r')) - *lineptr-- = '\0'; - /* Parse fields */ - lineptr = rawline; + lineptr = buf.data; while (*lineptr && err_msg == NULL) { List *current_field; @@ -538,12 +560,12 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel) tok_line = (TokenizedLine *) palloc(sizeof(TokenizedLine)); tok_line->fields = current_line; tok_line->line_num = line_number; - tok_line->raw_line = pstrdup(rawline); + tok_line->raw_line = pstrdup(buf.data); tok_line->err_msg = err_msg; *tok_lines = lappend(*tok_lines, tok_line); } - line_number++; + line_number += continuations + 1; } MemoryContextSwitchTo(oldcxt); @@ -1166,8 +1188,11 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) ret = pg_getaddrinfo_all(str, NULL, &hints, &gai_result); if (ret == 0 && gai_result) + { memcpy(&parsedline->addr, gai_result->ai_addr, gai_result->ai_addrlen); + parsedline->addrlen = gai_result->ai_addrlen; + } else if (ret == EAI_NONAME) parsedline->hostname = str; else @@ -1216,6 +1241,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) token->string); return NULL; } + parsedline->masklen = parsedline->addrlen; pfree(str); } else if (!parsedline->hostname) @@ -1266,6 +1292,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen); + parsedline->masklen = gai_result->ai_addrlen; pg_freeaddrinfo_all(hints.ai_family, gai_result); if (parsedline->addr.ss_family != parsedline->mask.ss_family) @@ -1723,29 +1750,25 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, *err_msg = "clientcert can only be configured for \"hostssl\" rows"; return false; } - if (strcmp(val, "1") == 0 - || strcmp(val, "verify-ca") == 0) - { - hbaline->clientcert = clientCertCA; - } - else if (strcmp(val, "verify-full") == 0) + + if (strcmp(val, "verify-full") == 0) { hbaline->clientcert = clientCertFull; } - else if (strcmp(val, "0") == 0 - || strcmp(val, "no-verify") == 0) + else if (strcmp(val, "verify-ca") == 0) { if (hbaline->auth_method == uaCert) { ereport(elevel, (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("clientcert can not be set to \"no-verify\" when using \"cert\" authentication"), + errmsg("clientcert only accepts \"verify-full\" when using \"cert\" authentication"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); - *err_msg = "clientcert can not be set to \"no-verify\" when using \"cert\" authentication"; + *err_msg = "clientcert can only be set to \"verify-full\" when using \"cert\" authentication"; return false; } - hbaline->clientcert = clientCertOff; + + hbaline->clientcert = clientCertCA; } else { @@ -2535,20 +2558,26 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, } else { - if (pg_getnameinfo_all(&hba->addr, sizeof(hba->addr), - buffer, sizeof(buffer), - NULL, 0, - NI_NUMERICHOST) == 0) + /* + * Note: if pg_getnameinfo_all fails, it'll set buffer to + * "???", which we want to return. + */ + if (hba->addrlen > 0) { - clean_ipv6_addr(hba->addr.ss_family, buffer); + if (pg_getnameinfo_all(&hba->addr, hba->addrlen, + buffer, sizeof(buffer), + NULL, 0, + NI_NUMERICHOST) == 0) + clean_ipv6_addr(hba->addr.ss_family, buffer); addrstr = pstrdup(buffer); } - if (pg_getnameinfo_all(&hba->mask, sizeof(hba->mask), - buffer, sizeof(buffer), - NULL, 0, - NI_NUMERICHOST) == 0) + if (hba->masklen > 0) { - clean_ipv6_addr(hba->mask.ss_family, buffer); + if (pg_getnameinfo_all(&hba->mask, hba->masklen, + buffer, sizeof(buffer), + NULL, 0, + NI_NUMERICHOST) == 0) + clean_ipv6_addr(hba->mask.ss_family, buffer); maskstr = pstrdup(buffer); } } diff --git a/src/backend/mock.mk b/src/backend/mock.mk index 442b474e4f5c..8e67c9fe4494 100644 --- a/src/backend/mock.mk +++ b/src/backend/mock.mk @@ -20,6 +20,21 @@ override CPPFLAGS+= -I$(top_srcdir)/src/backend/libpq \ # postgres in src/backend/Makefile doesn't need this and -pthread. MOCK_LIBS := -ldl $(filter-out -ledit, $(LIBS)) $(LDAP_LIBS_BE) $(ICU_LIBS) $(ZSTD_LIBS) +# The server variants of libpgcommon/libpgport are already part of $(OBJFILES) +# (they sit at the end of objfiles.txt), but they are scanned *before* the mock +# objects in the link line. A mocked backend file can reference a symbol that +# lives only in src/common -- e.g. PG14's fd.c references get_dirent_type(), +# which is defined in common/file_utils.c. That reference only becomes +# unresolved once the linker reaches the mock object, i.e. after the server +# archives have already been scanned, so the symbol would otherwise be resolved +# by the FRONTEND libpgcommon.a in $(LIBS) -- pulling in fe_memutils.o / +# file_utils.o and producing "multiple definition" errors against mcxt.o and +# the mock (palloc, fsync_fname, durable_rename, ...). Re-list the *server* +# archives after the mock objects so such late references resolve against the +# server variant (which omits the FRONTEND-only definitions). +MOCK_SRV_LIBS := $(top_builddir)/src/common/libpgcommon_srv.a \ + $(top_builddir)/src/port/libpgport_srv.a + # These files are not linked into test programs. EXCL_OBJS=\ src/backend/main/main.o \ @@ -121,7 +136,7 @@ WRAP_FUNCS=$(addprefix $(WRAP_FLAGS), \ # The test target depends on $(OBJFILES) which would update files including mocks. %.t: $(OBJFILES) $(CMOCKERY_OBJS) $(MOCK_OBJS) %_test.o - $(CXX) $(CFLAGS) $(LDFLAGS) $(call WRAP_FUNCS, $(top_srcdir)/$(subdir)/test/$*_test.c) $(call BACKEND_OBJS, $(top_srcdir)/$(subdir)/$*.o $(patsubst $(MOCK_DIR)/%_mock.o,$(top_builddir)/src/%.o, $^)) $(filter-out %/objfiles.txt, $^) $(MOCK_LIBS) -o $@ + $(CXX) $(CFLAGS) $(LDFLAGS) $(call WRAP_FUNCS, $(top_srcdir)/$(subdir)/test/$*_test.c) $(call BACKEND_OBJS, $(top_srcdir)/$(subdir)/$*.o $(patsubst $(MOCK_DIR)/%_mock.o,$(top_builddir)/src/%.o, $^)) $(filter-out %/objfiles.txt, $^) $(MOCK_SRV_LIBS) $(MOCK_LIBS) -o $@ # We'd like to call only src/backend, but it seems we should build src/port and # src/timezone before src/backend. This is not the case when main build has finished, diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 944a78e6c8a4..95f4361c4c3d 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -114,7 +114,6 @@ _copyPlannedStmt(const PlannedStmt *from) COPY_NODE_FIELD(planTree); COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(resultRelations); - COPY_NODE_FIELD(rootResultRelations); COPY_NODE_FIELD(appendRelations); COPY_NODE_FIELD(subplans); COPY_POINTER_FIELD(subplan_sliceIds, list_length(from->subplans) * sizeof(int)); @@ -338,8 +337,6 @@ _copyModifyTable(const ModifyTable *from) COPY_SCALAR_FIELD(rootRelation); COPY_SCALAR_FIELD(partColsUpdated); COPY_NODE_FIELD(resultRelations); - COPY_SCALAR_FIELD(resultRelIndex); - COPY_SCALAR_FIELD(rootResultRelIndex); COPY_NODE_FIELD(plans); COPY_NODE_FIELD(withCheckOptionLists); COPY_NODE_FIELD(returningLists); @@ -1013,6 +1010,7 @@ _copyForeignScan(const ForeignScan *from) COPY_NODE_FIELD(fdw_recheck_quals); COPY_BITMAPSET_FIELD(fs_relids); COPY_SCALAR_FIELD(fsSystemCol); + COPY_SCALAR_FIELD(resultRelation); return newnode; } @@ -3801,6 +3799,7 @@ _copyAlterTableCmd(const AlterTableCmd *from) COPY_SCALAR_FIELD(subtype); COPY_STRING_FIELD(name); + COPY_NODE_FIELD(object); COPY_SCALAR_FIELD(num); COPY_NODE_FIELD(newowner); COPY_NODE_FIELD(def); @@ -3813,16 +3812,6 @@ _copyAlterTableCmd(const AlterTableCmd *from) return newnode; } -static AlterCollationStmt * -_copyAlterCollationStmt(const AlterCollationStmt *from) -{ - AlterCollationStmt *newnode = makeNode(AlterCollationStmt); - - COPY_NODE_FIELD(collname); - - return newnode; -} - static AlterDomainStmt * _copyAlterDomainStmt(const AlterDomainStmt *from) { @@ -5136,7 +5125,6 @@ _copyReindexStmt(const ReindexStmt *from) COPY_STRING_FIELD(name); COPY_SCALAR_FIELD(relid); COPY_SCALAR_FIELD(options); - COPY_SCALAR_FIELD(concurrent); return newnode; } @@ -6316,9 +6304,6 @@ copyObjectImpl(const void *from) case T_AlterTableCmd: retval = _copyAlterTableCmd(from); break; - case T_AlterCollationStmt: - retval = _copyAlterCollationStmt(from); - break; case T_AlterDomainStmt: retval = _copyAlterDomainStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 6b8bad4acffe..bf1f71cf6350 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1187,14 +1187,6 @@ _equalAlterTableCmd(const AlterTableCmd *a, const AlterTableCmd *b) return true; } -static bool -_equalAlterCollationStmt(const AlterCollationStmt *a, const AlterCollationStmt *b) -{ - COMPARE_NODE_FIELD(collname); - - return true; -} - static bool _equalAlterDomainStmt(const AlterDomainStmt *a, const AlterDomainStmt *b) { @@ -2318,7 +2310,6 @@ _equalReindexStmt(const ReindexStmt *a, const ReindexStmt *b) COMPARE_NODE_FIELD(relation); COMPARE_STRING_FIELD(name); COMPARE_SCALAR_FIELD(options); - COMPARE_SCALAR_FIELD(concurrent); COMPARE_SCALAR_FIELD(relid); return true; @@ -3568,9 +3559,6 @@ equal(const void *a, const void *b) case T_AlterTableCmd: retval = _equalAlterTableCmd(a, b); break; - case T_AlterCollationStmt: - retval = _equalAlterCollationStmt(a, b); - break; case T_AlterDomainStmt: retval = _equalAlterDomainStmt(a, b); break; diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c index 2482cae220af..a814b7db12b8 100644 --- a/src/backend/nodes/list.c +++ b/src/backend/nodes/list.c @@ -327,7 +327,7 @@ lappend(List *list, void *datum) else new_tail_cell(list); - lfirst(list_tail(list)) = datum; + llast(list) = datum; check_list_invariants(list); return list; } @@ -345,7 +345,7 @@ lappend_int(List *list, int datum) else new_tail_cell(list); - lfirst_int(list_tail(list)) = datum; + llast_int(list) = datum; check_list_invariants(list); return list; } @@ -363,7 +363,7 @@ lappend_oid(List *list, Oid datum) else new_tail_cell(list); - lfirst_oid(list_tail(list)) = datum; + llast_oid(list) = datum; check_list_invariants(list); return list; } @@ -459,7 +459,7 @@ lcons(void *datum, List *list) else new_head_cell(list); - lfirst(list_head(list)) = datum; + linitial(list) = datum; check_list_invariants(list); return list; } @@ -477,7 +477,7 @@ lcons_int(int datum, List *list) else new_head_cell(list); - lfirst_int(list_head(list)) = datum; + linitial_int(list) = datum; check_list_invariants(list); return list; } @@ -495,7 +495,7 @@ lcons_oid(Oid datum, List *list) else new_head_cell(list); - lfirst_oid(list_head(list)) = datum; + linitial_oid(list) = datum; check_list_invariants(list); return list; } diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 7d647438bf16..b51159255729 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -460,7 +460,7 @@ exprTypmod(const Node *expr) typmod = exprTypmod((Node *) linitial(cexpr->args)); if (typmod < 0) return -1; /* no point in trying harder */ - for_each_cell(arg, cexpr->args, list_second_cell(cexpr->args)) + for_each_from(arg, cexpr->args, 1) { Node *e = (Node *) lfirst(arg); @@ -488,7 +488,7 @@ exprTypmod(const Node *expr) typmod = exprTypmod((Node *) linitial(mexpr->args)); if (typmod < 0) return -1; /* no point in trying harder */ - for_each_cell(arg, mexpr->args, list_second_cell(mexpr->args)) + for_each_from(arg, mexpr->args, 1) { Node *e = (Node *) lfirst(arg); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 0dcc83160bc0..54c744088e78 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -351,7 +351,6 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node) WRITE_NODE_FIELD(planTree); WRITE_NODE_FIELD(rtable); WRITE_NODE_FIELD(resultRelations); - WRITE_NODE_FIELD(rootResultRelations); WRITE_NODE_FIELD(appendRelations); WRITE_NODE_FIELD(subplans); WRITE_BITMAPSET_FIELD(rewindPlanIDs); @@ -574,8 +573,6 @@ _outModifyTable(StringInfo str, const ModifyTable *node) WRITE_UINT_FIELD(rootRelation); WRITE_BOOL_FIELD(partColsUpdated); WRITE_NODE_FIELD(resultRelations); - WRITE_INT_FIELD(resultRelIndex); - WRITE_INT_FIELD(rootResultRelIndex); WRITE_NODE_FIELD(plans); WRITE_NODE_FIELD(withCheckOptionLists); WRITE_NODE_FIELD(returningLists); @@ -954,6 +951,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node) WRITE_NODE_FIELD(fdw_recheck_quals); WRITE_BITMAPSET_FIELD(fs_relids); WRITE_BOOL_FIELD(fsSystemCol); + WRITE_INT_FIELD(resultRelation); } #ifndef COMPILING_BINARY_FUNCS @@ -2715,7 +2713,6 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node) WRITE_NODE_FIELD(finalrtable); WRITE_NODE_FIELD(finalrowmarks); WRITE_NODE_FIELD(resultRelations); - WRITE_NODE_FIELD(rootResultRelations); WRITE_NODE_FIELD(appendRelations); WRITE_NODE_FIELD(relationOids); WRITE_NODE_FIELD(invalItems); @@ -2779,6 +2776,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasHavingQual); WRITE_BOOL_FIELD(hasPseudoConstantQuals); + WRITE_BOOL_FIELD(hasAlternativeSubPlans); WRITE_BOOL_FIELD(hasRecursion); WRITE_INT_FIELD(wt_param_id); WRITE_BITMAPSET_FIELD(curOuterRels); @@ -2839,7 +2837,6 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_BOOL_FIELD(partbounds_merged); WRITE_BITMAPSET_FIELD(all_partrels); - WRITE_NODE_FIELD(partitioned_child_rels); } #endif /* COMPILING_BINARY_FUNCS */ @@ -2884,6 +2881,7 @@ _outForeignKeyOptInfo(StringInfo str, const ForeignKeyOptInfo *node) WRITE_ATTRNUMBER_ARRAY(confkey, node->nkeys); WRITE_OID_ARRAY(conpfeqop, node->nkeys); WRITE_INT_FIELD(nmatched_ec); + WRITE_INT_FIELD(nconst_ec); WRITE_INT_FIELD(nmatched_rcols); WRITE_INT_FIELD(nmatched_ri); /* for compactness, just print the number of matches per column: */ diff --git a/src/backend/nodes/params.c b/src/backend/nodes/params.c index bce0c7e72b2c..c05f04a259c1 100644 --- a/src/backend/nodes/params.c +++ b/src/backend/nodes/params.c @@ -414,9 +414,9 @@ ParamsErrorCallback(void *arg) return; if (data->portalName && data->portalName[0] != '\0') - errcontext("extended query \"%s\" with parameters: %s", + errcontext("portal \"%s\" with parameters: %s", data->portalName, data->params->paramValuesStr); else - errcontext("extended query with parameters: %s", + errcontext("unnamed portal with parameters: %s", data->params->paramValuesStr); } diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c index 6b4c01064f47..2ca764c636f7 100644 --- a/src/backend/nodes/print.c +++ b/src/backend/nodes/print.c @@ -404,7 +404,6 @@ print_expr(const Node *expr, const List *rtable) } else { - /* we print prefix and postfix ops the same... */ printf("%s ", ((opname != NULL) ? opname : "(invalid operator)")); print_expr(get_leftop((const Expr *) e), rtable); } diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 2a4565786ee9..94d207ec8a33 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -2476,7 +2476,6 @@ _readPlannedStmt(void) READ_NODE_FIELD(planTree); READ_NODE_FIELD(rtable); READ_NODE_FIELD(resultRelations); - READ_NODE_FIELD(rootResultRelations); READ_NODE_FIELD(appendRelations); READ_NODE_FIELD(subplans); READ_BITMAPSET_FIELD(rewindPlanIDs); @@ -2616,8 +2615,6 @@ _readModifyTable(void) READ_UINT_FIELD(rootRelation); READ_BOOL_FIELD(partColsUpdated); READ_NODE_FIELD(resultRelations); - READ_INT_FIELD(resultRelIndex); - READ_INT_FIELD(rootResultRelIndex); READ_NODE_FIELD(plans); READ_NODE_FIELD(withCheckOptionLists); READ_NODE_FIELD(returningLists); @@ -3081,6 +3078,7 @@ _readForeignScan(void) READ_NODE_FIELD(fdw_recheck_quals); READ_BITMAPSET_FIELD(fs_relids); READ_BOOL_FIELD(fsSystemCol); + READ_INT_FIELD(resultRelation); READ_DONE(); } diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 4dc997c6cb19..fe40c5506511 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -116,8 +116,13 @@ static void generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel, static Path *get_cheapest_parameterized_child_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer); +static List *accumulate_partitioned_rels(List *partitioned_rels, + List *sub_partitioned_rels, + bool flatten_partitioned_rels); static void accumulate_append_subpath(Path *path, - List **subpaths, List **special_subpaths); + List **subpaths, List **special_subpaths, + List **partitioned_rels, + bool flatten_partitioned_rels); static Path *get_singleton_append_subpath(Path *path); static void set_dummy_rel_pathlist(PlannerInfo *root, RelOptInfo *rel); static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, @@ -1166,7 +1171,11 @@ set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* ... but do not let it set the rows estimate to zero */ rel->rows = clamp_row_est(rel->rows); - /* also, make sure rel->tuples is not insane relative to rel->rows */ + /* + * Also, make sure rel->tuples is not insane relative to rel->rows. + * Notably, this ensures sanity if pg_class.reltuples contains -1 and the + * FDW doesn't do anything to replace that. + */ rel->tuples = Max(rel->tuples, rel->rows); } @@ -1209,17 +1218,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); - /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel @@ -1519,12 +1517,6 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (IS_DUMMY_REL(childrel)) continue; - /* Bubble up childrel's partitioned children. */ - if (rel->part_scheme) - rel->partitioned_child_rels = - list_concat(rel->partitioned_child_rels, - childrel->partitioned_child_rels); - /* * Child is live, so add it to the live_childrels list for use below. */ @@ -1562,56 +1554,35 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, List *all_child_outers = NIL; ListCell *l; List *partitioned_rels = NIL; + List *partial_partitioned_rels = NIL; + List *pa_partitioned_rels = NIL; double partial_rows = -1; + bool flatten_partitioned_rels; /* If appropriate, consider parallel append */ pa_subpaths_valid = enable_parallel_append && rel->consider_parallel; + /* What we do with the partitioned_rels list is different for UNION ALL */ + flatten_partitioned_rels = (rel->rtekind != RTE_SUBQUERY); + /* - * AppendPath generated for partitioned tables must record the RT indexes - * of partitioned tables that are direct or indirect children of this - * Append rel. - * - * AppendPath may be for a sub-query RTE (UNION ALL), in which case, 'rel' - * itself does not represent a partitioned relation, but the child sub- - * queries may contain references to partitioned relations. The loop - * below will look for such children and collect them in a list to be - * passed to the path creation function. (This assumes that we don't need - * to look through multiple levels of subquery RTEs; if we ever do, we - * could consider stuffing the list we generate here into sub-query RTE's - * RelOptInfo, just like we do for partitioned rels, which would be used - * when populating our parent rel with paths. For the present, that - * appears to be unnecessary.) + * For partitioned tables, we accumulate a list of Relids of each + * partitioned table which has at least one of its subpartitions directly + * present as a subpath in this Append. This is used later for run-time + * partition pruning. We must maintain separate lists for each Append + * Path that we create as some paths that we create here can't flatten + * sub-Appends and sub-MergeAppends into the top-level Append. We needn't + * bother doing this for join rels as no run-time pruning is done on + * those. */ - if (rel->part_scheme != NULL) + if (rel->reloptkind != RELOPT_JOINREL && rel->part_scheme != NULL) { - if (IS_SIMPLE_REL(rel)) - partitioned_rels = list_make1(rel->partitioned_child_rels); - else if (IS_JOIN_REL(rel)) - { - int relid = -1; - List *partrels = NIL; - - /* - * For a partitioned joinrel, concatenate the component rels' - * partitioned_child_rels lists. - */ - while ((relid = bms_next_member(rel->relids, relid)) >= 0) - { - RelOptInfo *component; - - Assert(relid >= 1 && relid < root->simple_rel_array_size); - component = root->simple_rel_array[relid]; - Assert(component->part_scheme != NULL); - Assert(list_length(component->partitioned_child_rels) >= 1); - partrels = list_concat(partrels, - component->partitioned_child_rels); - } - - partitioned_rels = list_make1(partrels); - } + partitioned_rels = list_make1(bms_make_singleton(rel->relid)); + partial_partitioned_rels = list_make1(bms_make_singleton(rel->relid)); - Assert(list_length(partitioned_rels) >= 1); + /* skip this one if we're not going to make a Parallel Append path */ + if (pa_subpaths_valid) + pa_partitioned_rels = list_make1(bms_make_singleton(rel->relid)); } /* @@ -1625,14 +1596,6 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, ListCell *lcp; Path *cheapest_partial_path = NULL; - /* - * For UNION ALLs with non-empty partitioned_child_rels, accumulate - * the Lists of child relations. - */ - if (rel->rtekind == RTE_SUBQUERY && childrel->partitioned_child_rels != NIL) - partitioned_rels = lappend(partitioned_rels, - childrel->partitioned_child_rels); - /* * If child has an unparameterized cheapest-total path, add that to * the unparameterized Append path we are constructing for the parent. @@ -1644,7 +1607,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, if (childrel->pathlist != NIL && childrel->cheapest_total_path->param_info == NULL) accumulate_append_subpath(childrel->cheapest_total_path, - &subpaths, NULL); + &subpaths, NULL, &partitioned_rels, + flatten_partitioned_rels); else subpaths_valid = false; @@ -1653,7 +1617,9 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, { cheapest_partial_path = linitial(childrel->partial_pathlist); accumulate_append_subpath(cheapest_partial_path, - &partial_subpaths, NULL); + &partial_subpaths, NULL, + &partial_partitioned_rels, + flatten_partitioned_rels); } else partial_subpaths_valid = false; @@ -1682,7 +1648,9 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, Assert(cheapest_partial_path != NULL); accumulate_append_subpath(cheapest_partial_path, &pa_partial_subpaths, - &pa_nonpartial_subpaths); + &pa_nonpartial_subpaths, + &pa_partitioned_rels, + flatten_partitioned_rels); } else @@ -1702,7 +1670,9 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, */ accumulate_append_subpath(nppath, &pa_nonpartial_subpaths, - NULL); + NULL, + &pa_partitioned_rels, + flatten_partitioned_rels); } } @@ -1822,7 +1792,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, appendpath = create_append_path(root, rel, NIL, partial_subpaths, NIL, NULL, parallel_workers, enable_parallel_append, - partitioned_rels, -1); + partial_partitioned_rels, -1); /* * Make sure any subsequent partial paths use the same row count @@ -1871,7 +1841,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, appendpath = create_append_path(root, rel, pa_nonpartial_subpaths, pa_partial_subpaths, NIL, NULL, parallel_workers, true, - partitioned_rels, partial_rows); + pa_partitioned_rels, partial_rows); add_partial_path(rel, (Path *) appendpath); } @@ -1901,6 +1871,10 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, { Relids required_outer = (Relids) lfirst(l); ListCell *lcr; + List *part_rels = NIL; + + if (rel->reloptkind != RELOPT_JOINREL && rel->part_scheme != NULL) + part_rels = list_make1(bms_make_singleton(rel->relid)); /* Select the child paths for an Append with this parameterization */ subpaths = NIL; @@ -1926,14 +1900,15 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, subpaths_valid = false; break; } - accumulate_append_subpath(subpath, &subpaths, NULL); + accumulate_append_subpath(subpath, &subpaths, NULL, &part_rels, + flatten_partitioned_rels); } if (subpaths_valid) add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL, NIL, required_outer, 0, false, - partitioned_rels, -1)); + part_rels, -1)); } /* @@ -1947,17 +1922,14 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, { RelOptInfo *childrel = (RelOptInfo *) linitial(live_childrels); - foreach(l, childrel->partial_pathlist) + /* skip the cheapest partial path, since we already used that above */ + for_each_from(l, childrel->partial_pathlist, 1) { Path *path = (Path *) lfirst(l); AppendPath *appendpath; - /* - * Skip paths with no pathkeys. Also skip the cheapest partial - * path, since we already used that above. - */ - if (path->pathkeys == NIL || - path == linitial(childrel->partial_pathlist)) + /* skip paths with no pathkeys. */ + if (path->pathkeys == NIL) continue; appendpath = create_append_path(root, rel, NIL, list_make1(path), @@ -2007,6 +1979,18 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel, List *partition_pathkeys_desc = NIL; bool partition_pathkeys_partial = true; bool partition_pathkeys_desc_partial = true; + List *startup_partitioned_rels = NIL; + List *total_partitioned_rels = NIL; + bool flatten_partitioned_rels; + + /* Set up the method for building the partitioned rels lists */ + flatten_partitioned_rels = (rel->rtekind != RTE_SUBQUERY); + + if (rel->reloptkind != RELOPT_JOINREL && rel->part_scheme != NULL) + { + startup_partitioned_rels = list_make1(bms_make_singleton(rel->relid)); + total_partitioned_rels = list_make1(bms_make_singleton(rel->relid)); + } /* * Some partitioned table setups may allow us to use an Append node @@ -2148,9 +2132,13 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel, * child paths for the MergeAppend. */ accumulate_append_subpath(cheapest_startup, - &startup_subpaths, NULL); + &startup_subpaths, NULL, + &startup_partitioned_rels, + flatten_partitioned_rels); accumulate_append_subpath(cheapest_total, - &total_subpaths, NULL); + &total_subpaths, NULL, + &total_partitioned_rels, + flatten_partitioned_rels); } } @@ -2166,7 +2154,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel, NULL, 0, false, - partitioned_rels, + startup_partitioned_rels, -1)); if (startup_neq_total) add_path(rel, (Path *) create_append_path(root, @@ -2177,7 +2165,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel, NULL, 0, false, - partitioned_rels, + total_partitioned_rels, -1)); } else @@ -2188,14 +2176,14 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel, startup_subpaths, pathkeys, NULL, - partitioned_rels)); + startup_partitioned_rels)); if (startup_neq_total) add_path(rel, (Path *) create_merge_append_path(root, rel, total_subpaths, pathkeys, NULL, - partitioned_rels)); + total_partitioned_rels)); } } } @@ -2274,6 +2262,54 @@ get_cheapest_parameterized_child_path(PlannerInfo *root, RelOptInfo *rel, return cheapest; } +/* + * accumulate_partitioned_rels + * Record 'sub_partitioned_rels' in the 'partitioned_rels' list, + * flattening as appropriate. + */ +static List * +accumulate_partitioned_rels(List *partitioned_rels, + List *sub_partitioned_rels, + bool flatten) +{ + if (flatten) + { + /* + * We're only called with flatten == true when the partitioned_rels + * list has at most 1 element. So we can just add the members from + * sub list's first element onto the first element of + * partitioned_rels. Only later in planning when doing UNION ALL + * Append processing will we see flatten == false. partitioned_rels + * may end up with more than 1 element then, but we never expect to be + * called with flatten == true again after that, so we needn't bother + * doing anything here for anything but the initial element. + */ + if (partitioned_rels != NIL && sub_partitioned_rels != NIL) + { + Relids partrels = (Relids) linitial(partitioned_rels); + Relids subpartrels = (Relids) linitial(sub_partitioned_rels); + + /* Ensure the above comment holds true */ + Assert(list_length(partitioned_rels) == 1); + Assert(list_length(sub_partitioned_rels) == 1); + + linitial(partitioned_rels) = bms_add_members(partrels, subpartrels); + } + } + else + { + /* + * Handle UNION ALL to partitioned tables. This always occurs after + * we've done the accumulation for sub-partitioned tables, so there's + * no need to consider how adding multiple elements to the top level + * list affects the flatten == true case above. + */ + partitioned_rels = list_concat(partitioned_rels, sub_partitioned_rels); + } + + return partitioned_rels; +} + /* * accumulate_append_subpath * Add a subpath to the list being built for an Append or MergeAppend. @@ -2294,9 +2330,24 @@ get_cheapest_parameterized_child_path(PlannerInfo *root, RelOptInfo *rel, * children to subpaths and the rest to special_subpaths. If the latter is * NULL, we don't flatten the path at all (unless it contains only partial * paths). + * + * When pulling up sub-Appends and sub-Merge Appends, we also gather the + * path's list of partitioned tables and store in 'partitioned_rels'. The + * exact behavior here depends on the value of 'flatten_partitioned_rels'. + * + * When 'flatten_partitioned_rels' is true, 'partitioned_rels' will contain at + * most one element which is a Relids of the partitioned relations which there + * are subpaths for. In this case, we just add the RT indexes for the + * partitioned tables for the subpath we're pulling up to the single entry in + * 'partitioned_rels'. When 'flatten_partitioned_rels' is false we + * concatenate the path's partitioned rel list onto the top-level list. This + * done for UNION ALLs which could have a partitioned table in each union + * branch. */ static void -accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths) +accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths, + List **partitioned_rels, + bool flatten_partitioned_rels) { if (IsA(path, AppendPath)) { @@ -2305,6 +2356,9 @@ accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths) if (!apath->path.parallel_aware || apath->first_partial_path == 0) { *subpaths = list_concat(*subpaths, apath->subpaths); + *partitioned_rels = accumulate_partitioned_rels(*partitioned_rels, + apath->partitioned_rels, + flatten_partitioned_rels); return; } else if (special_subpaths != NULL) @@ -2320,6 +2374,9 @@ accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths) apath->first_partial_path); *special_subpaths = list_concat(*special_subpaths, new_special_subpaths); + *partitioned_rels = accumulate_partitioned_rels(*partitioned_rels, + apath->partitioned_rels, + flatten_partitioned_rels); return; } } @@ -2328,6 +2385,9 @@ accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths) MergeAppendPath *mpath = (MergeAppendPath *) path; *subpaths = list_concat(*subpaths, mpath->subpaths); + *partitioned_rels = accumulate_partitioned_rels(*partitioned_rels, + mpath->partitioned_rels, + flatten_partitioned_rels); return; } @@ -3305,7 +3365,8 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel) /* * Considering query_pathkeys is always worth it, because it might allow * us to avoid a total sort when we have a partially presorted path - * available. + * available or to push the total sort into the parallel portion of the + * query. */ if (root->query_pathkeys) { @@ -3318,17 +3379,17 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel) EquivalenceClass *pathkey_ec = pathkey->pk_eclass; /* - * We can only build an Incremental Sort for pathkeys which - * contain an EC member in the current relation, so ignore any - * suffix of the list as soon as we find a pathkey without an EC - * member the relation. + * We can only build a sort for pathkeys which contain an EC + * member in the current relation's target, so ignore any suffix + * of the list as soon as we find a pathkey without an EC member + * in the relation. * * By still returning the prefix of the pathkeys list that does * meet criteria of EC membership in the current relation, we * enable not just an incremental sort on the entirety of * query_pathkeys but also incremental sort below a JOIN. */ - if (!find_em_expr_for_rel(pathkey_ec, rel)) + if (!find_em_expr_usable_for_sorting_rel(pathkey_ec, rel)) break; npathkeys++; @@ -3404,7 +3465,7 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r /* * If the path has no ordering at all, then we can't use either - * incremental sort or rely on implict sorting with a gather + * incremental sort or rely on implicit sorting with a gather * merge. */ if (subpath->pathkeys == NIL) @@ -3830,6 +3891,17 @@ push_down_restrict(PlannerInfo *root, RelOptInfo *rel, * volatile qual could succeed for some SRF output rows and fail for others, * a behavior that cannot occur if it's evaluated before SRF expansion. * + * 6. If the subquery has nonempty grouping sets, we cannot push down any + * quals. The concern here is that a qual referencing a "constant" grouping + * column could get constant-folded, which would be improper because the value + * is potentially nullable by grouping-set expansion. This restriction could + * be removed if we had a parsetree representation that shows that such + * grouping columns are not really constant. (There are other ideas that + * could be used to relax this restriction, but that's the approach most + * likely to get taken in the future. Note that there's not much to be gained + * so long as subquery_planner can't move HAVING clauses to WHERE within such + * a subquery.) + * * In addition, we make several checks on the subquery's output columns to see * if it is safe to reference them in pushed-down quals. If output column k * is found to be unsafe to reference, we set safetyInfo->unsafeColumns[k] @@ -3874,6 +3946,10 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery, if (subquery->limitOffset != NULL || subquery->limitCount != NULL) return false; + /* Check point 6 */ + if (subquery->groupClause && subquery->groupingSets) + return false; + /* Check points 3, 4, and 5 */ if (subquery->distinctClause || subquery->hasWindowFuncs || diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c index 1ce4fdf180b5..81146bba7c69 100644 --- a/src/backend/optimizer/path/clausesel.c +++ b/src/backend/optimizer/path/clausesel.c @@ -202,8 +202,7 @@ clauselist_selectivity_simple(PlannerInfo *root, * directly to clause_selectivity(). None of what we might do below is * relevant. */ - if ((list_length(clauses) == 1) && - bms_num_members(estimatedclauses) == 0) + if (list_length(clauses) == 1 && bms_is_empty(estimatedclauses)) return clause_selectivity(root, (Node *) linitial(clauses), varRelid, jointype, sjinfo, use_damping); diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 1b2e69eb2e08..368ba7b9f1ae 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -112,7 +112,6 @@ */ #define APPEND_CPU_COST_MULTIPLIER 0.5 - double seq_page_cost = DEFAULT_SEQ_PAGE_COST; double random_page_cost = DEFAULT_RANDOM_PAGE_COST; double cpu_tuple_cost = DEFAULT_CPU_TUPLE_COST; @@ -2623,6 +2622,7 @@ cost_agg(Path *path, PlannerInfo *root, double pages; double pages_written = 0.0; double pages_read = 0.0; + double spill_cost; double hashentrysize; double nbatches; Size mem_limit; @@ -2660,9 +2660,21 @@ cost_agg(Path *path, PlannerInfo *root, pages = relation_byte_size(input_tuples, input_width) / BLCKSZ; pages_written = pages_read = pages * depth; + /* + * HashAgg has somewhat worse IO behavior than Sort on typical + * hardware/OS combinations. Account for this with a generic penalty. + */ + pages_read *= 2.0; + pages_written *= 2.0; + startup_cost += pages_written * random_page_cost; total_cost += pages_written * random_page_cost; total_cost += pages_read * seq_page_cost; + + /* account for CPU cost of spilling a tuple and reading it back */ + spill_cost = depth * input_tuples * 2.0 * cpu_tuple_cost; + startup_cost += spill_cost; + total_cost += spill_cost; } /* @@ -2995,10 +3007,10 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path, double ntuples; double numsegments; - /* Protect some assumptions below that rowcounts aren't zero or NaN */ - if (outer_path_rows <= 0 || isnan(outer_path_rows)) + /* Protect some assumptions below that rowcounts aren't zero */ + if (outer_path_rows <= 0) outer_path_rows = 1; - if (inner_path_rows <= 0 || isnan(inner_path_rows)) + if (inner_path_rows <= 0) inner_path_rows = 1; if (CdbPathLocus_IsPartitioned(path->path.locus)) @@ -3216,10 +3228,10 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, innerendsel; Path sort_path; /* dummy for result of cost_sort */ - /* Protect some assumptions below that rowcounts aren't zero or NaN */ - if (outer_path_rows <= 0 || isnan(outer_path_rows)) + /* Protect some assumptions below that rowcounts aren't zero */ + if (outer_path_rows <= 0) outer_path_rows = 1; - if (inner_path_rows <= 0 || isnan(inner_path_rows)) + if (inner_path_rows <= 0) inner_path_rows = 1; /* @@ -3455,8 +3467,8 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path, double rescanratio; double numsegments; - /* Protect some assumptions below that rowcounts aren't zero or NaN */ - if (inner_path_rows <= 0 || isnan(inner_path_rows)) + /* Protect some assumptions below that rowcounts aren't zero */ + if (inner_path_rows <= 0) inner_path_rows = 1; if (CdbPathLocus_IsPartitioned(path->jpath.path.locus)) @@ -5557,9 +5569,16 @@ get_foreign_key_join_selectivity(PlannerInfo *root, * remove back into the worklist. * * Since the matching clauses are known not outerjoin-delayed, they - * should certainly have appeared in the initial joinclause list. If - * we didn't find them, they must have been matched to, and removed - * by, some other FK in a previous iteration of this loop. (A likely + * would normally have appeared in the initial joinclause list. If we + * didn't find them, there are two possibilities: + * + * 1. If the FK match is based on an EC that is ec_has_const, it won't + * have generated any join clauses at all. We discount such ECs while + * checking to see if we have "all" the clauses. (Below, we'll adjust + * the selectivity estimate for this case.) + * + * 2. The clauses were matched to some other FK in a previous + * iteration of this loop, and thus removed from worklist. (A likely * case is that two FKs are matched to the same EC; there will be only * one EC-derived clause in the initial list, so the first FK will * consume it.) Applying both FKs' selectivity independently risks @@ -5569,8 +5588,9 @@ get_foreign_key_join_selectivity(PlannerInfo *root, * Later we might think of a reasonable way to combine the estimates, * but for now, just punt, since this is a fairly uncommon situation. */ - if (list_length(removedlist) != - (fkinfo->nmatched_ec + fkinfo->nmatched_ri)) + if (removedlist == NIL || + list_length(removedlist) != + (fkinfo->nmatched_ec - fkinfo->nconst_ec + fkinfo->nmatched_ri)) { worklist = list_concat(worklist, removedlist); continue; @@ -5629,9 +5649,49 @@ get_foreign_key_join_selectivity(PlannerInfo *root, fkselec *= 1.0 / ref_tuples; } + + /* + * If any of the FK columns participated in ec_has_const ECs, then + * equivclass.c will have generated "var = const" restrictions for + * each side of the join, thus reducing the sizes of both input + * relations. Taking the fkselec at face value would amount to + * double-counting the selectivity of the constant restriction for the + * referencing Var. Hence, look for the restriction clause(s) that + * were applied to the referencing Var(s), and divide out their + * selectivity to correct for this. + */ + if (fkinfo->nconst_ec > 0) + { + for (int i = 0; i < fkinfo->nkeys; i++) + { + EquivalenceClass *ec = fkinfo->eclass[i]; + + if (ec && ec->ec_has_const) + { + EquivalenceMember *em = fkinfo->fk_eclass_member[i]; + RestrictInfo *rinfo = find_derived_clause_for_ec_member(ec, + em); + + if (rinfo) + { + Selectivity s0; + + s0 = clause_selectivity(root, + (Node *) rinfo, + 0, + jointype, + sjinfo, + false /* use_damping */); + if (s0 > 0) + fkselec /= s0; + } + } + } + } } *restrictlist = worklist; + CLAMP_PROBABILITY(fkselec); return fkselec; } diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 3fc0f870a3dd..05ebdaa3b539 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -137,6 +137,7 @@ process_equivalence(PlannerInfo *root, EquivalenceMember *em1, *em2; ListCell *lc1; + int ec2_idx; /* Should not already be marked as having generated an eclass */ Assert(restrictinfo->left_ec == NULL); @@ -258,6 +259,7 @@ process_equivalence(PlannerInfo *root, */ ec1 = ec2 = NULL; em1 = em2 = NULL; + ec2_idx = -1; foreach(lc1, root->eq_classes) { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); @@ -311,6 +313,7 @@ process_equivalence(PlannerInfo *root, equal(item2, cur_em->em_expr)) { ec2 = cur_ec; + ec2_idx = foreach_current_index(lc1); em2 = cur_em; if (ec1) break; @@ -371,7 +374,7 @@ process_equivalence(PlannerInfo *root, ec1->ec_max_security = Max(ec1->ec_max_security, ec2->ec_max_security); ec2->ec_merged = ec1; - root->eq_classes = list_delete_ptr(root->eq_classes, ec2); + root->eq_classes = list_delete_nth_cell(root->eq_classes, ec2_idx); /* just to avoid debugging confusion w/ dangling pointers: */ ec2->ec_members = NIL; ec2->ec_sources = NIL; @@ -634,12 +637,6 @@ get_eclass_for_sort_expr(PlannerInfo *root, */ expr = canonicalize_ec_expression(expr, opcintype, collation); - /* - * Get the precise set of nullable relids appearing in the expression. - */ - expr_relids = pull_varnos((Node *) expr); - nullable_relids = bms_intersect(nullable_relids, expr_relids); - /* * Scan through the existing EquivalenceClasses for a match */ @@ -716,6 +713,12 @@ get_eclass_for_sort_expr(PlannerInfo *root, if (newec->ec_has_volatile && sortref == 0) /* should not happen */ elog(ERROR, "volatile EquivalenceClass has no sortref"); + /* + * Get the precise set of nullable relids appearing in the expression. + */ + expr_relids = pull_varnos((Node *) expr); + nullable_relids = bms_intersect(nullable_relids, expr_relids); + newem = add_eq_member(newec, copyObject(expr), expr_relids, nullable_relids, false, opcintype); @@ -795,6 +798,76 @@ find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel) return NULL; } +/* + * Find an equivalence class member expression that can be safely used by a + * sort node on top of the provided relation. The rules here must match those + * applied in prepare_sort_from_pathkeys. + */ +Expr * +find_em_expr_usable_for_sorting_rel(EquivalenceClass *ec, RelOptInfo *rel) +{ + ListCell *lc_em; + + /* + * If there is more than one equivalence member matching these + * requirements we'll be content to choose any one of them. + */ + foreach(lc_em, ec->ec_members) + { + EquivalenceMember *em = lfirst(lc_em); + Expr *em_expr = em->em_expr; + PathTarget *target = rel->reltarget; + ListCell *lc_target_expr; + + /* + * We shouldn't be trying to sort by an equivalence class that + * contains a constant, so no need to consider such cases any further. + */ + if (em->em_is_const) + continue; + + /* + * Any Vars in the equivalence class member need to come from this + * relation. This is a superset of prepare_sort_from_pathkeys ignoring + * child members unless they belong to the rel being sorted. + */ + if (!bms_is_subset(em->em_relids, rel->relids)) + continue; + + /* + * As long as the expression isn't volatile then + * prepare_sort_from_pathkeys is able to generate a new target entry, + * so there's no need to verify that one already exists. + */ + if (!ec->ec_has_volatile) + return em->em_expr; + + /* + * If, however, it's volatile, we have to verify that the + * equivalence member's expr is already generated in the + * relation's target (we do strip relabels first from both + * expressions, which is cheap and might allow us to match + * more expressions). + */ + while (em_expr && IsA(em_expr, RelabelType)) + em_expr = ((RelabelType *) em_expr)->arg; + + foreach(lc_target_expr, target->exprs) + { + Expr *target_expr = lfirst(lc_target_expr); + + while (target_expr && IsA(target_expr, RelabelType)) + target_expr = ((RelabelType *) target_expr)->arg; + + if (equal(target_expr, em_expr)) + return em->em_expr; + } + } + + /* We didn't find any suitable equivalence class expression */ + return NULL; +} + /* * generate_base_implied_equalities * Generate any restriction clauses that we can deduce from equivalence @@ -838,10 +911,8 @@ find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel) * scanning of the quals and before Path construction begins. * * We make no attempt to avoid generating duplicate RestrictInfos here: we - * don't search ec_sources for matches, nor put the created RestrictInfos - * into ec_derives. Doing so would require some slightly ugly changes in - * initsplan.c's API, and there's no real advantage, because the clauses - * generated here can't duplicate anything we will generate for joins anyway. + * don't search ec_sources or ec_derives for matches. It doesn't really + * seem worth the trouble to do so. */ void generate_base_implied_equalities(PlannerInfo *root) @@ -967,6 +1038,7 @@ generate_base_implied_equalities_const(PlannerInfo *root, { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc); Oid eq_op; + RestrictInfo *rinfo; Assert(!cur_em->em_is_child); /* no children yet */ if (cur_em == const_em) @@ -980,14 +1052,31 @@ generate_base_implied_equalities_const(PlannerInfo *root, ec->ec_broken = true; break; } - process_implied_equality(root, eq_op, ec->ec_collation, - cur_em->em_expr, const_em->em_expr, - bms_copy(ec->ec_relids), - bms_union(cur_em->em_nullable_relids, - const_em->em_nullable_relids), - ec->ec_min_security, - ec->ec_below_outer_join, - cur_em->em_is_const); + rinfo = process_implied_equality(root, eq_op, ec->ec_collation, + cur_em->em_expr, const_em->em_expr, + bms_copy(ec->ec_relids), + bms_union(cur_em->em_nullable_relids, + const_em->em_nullable_relids), + ec->ec_min_security, + ec->ec_below_outer_join, + cur_em->em_is_const); + + /* + * If the clause didn't degenerate to a constant, fill in the correct + * markings for a mergejoinable clause, and save it in ec_derives. (We + * will not re-use such clauses directly, but selectivity estimation + * may consult the list later. Note that this use of ec_derives does + * not overlap with its use for join clauses, since we never generate + * join clauses from an ec_has_const eclass.) + */ + if (rinfo && rinfo->mergeopfamilies) + { + /* it's not redundant, so don't set parent_ec */ + rinfo->left_ec = rinfo->right_ec = ec; + rinfo->left_em = cur_em; + rinfo->right_em = const_em; + ec->ec_derives = lappend(ec->ec_derives, rinfo); + } } } @@ -1026,6 +1115,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root, { EquivalenceMember *prev_em = prev_ems[relid]; Oid eq_op; + RestrictInfo *rinfo; eq_op = select_equality_operator(ec, prev_em->em_datatype, @@ -1036,14 +1126,29 @@ generate_base_implied_equalities_no_const(PlannerInfo *root, ec->ec_broken = true; break; } - process_implied_equality(root, eq_op, ec->ec_collation, - prev_em->em_expr, cur_em->em_expr, - bms_copy(ec->ec_relids), - bms_union(prev_em->em_nullable_relids, - cur_em->em_nullable_relids), - ec->ec_min_security, - ec->ec_below_outer_join, - false); + rinfo = process_implied_equality(root, eq_op, ec->ec_collation, + prev_em->em_expr, cur_em->em_expr, + bms_copy(ec->ec_relids), + bms_union(prev_em->em_nullable_relids, + cur_em->em_nullable_relids), + ec->ec_min_security, + ec->ec_below_outer_join, + false); + + /* + * If the clause didn't degenerate to a constant, fill in the + * correct markings for a mergejoinable clause. We don't put it + * in ec_derives however; we don't currently need to re-find such + * clauses, and we don't want to clutter that list with non-join + * clauses. + */ + if (rinfo && rinfo->mergeopfamilies) + { + /* it's not redundant, so don't set parent_ec */ + rinfo->left_ec = rinfo->right_ec = ec; + rinfo->left_em = prev_em; + rinfo->right_em = cur_em; + } } prev_ems[relid] = cur_em; } @@ -1172,9 +1277,9 @@ generate_join_implied_equalities(PlannerInfo *root, } /* - * Get all eclasses in common between inner_rel's relids and outer_relids + * Get all eclasses that mention both inner and outer sides of the join */ - matching_ecs = get_common_eclass_indexes(root, inner_rel->relids, + matching_ecs = get_common_eclass_indexes(root, nominal_inner_relids, outer_relids); i = -1; @@ -1965,6 +2070,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) bool matchleft; bool matchright; ListCell *lc2; + int coal_idx = -1; /* Ignore EC unless it contains pseudoconstants */ if (!cur_ec->ec_has_const) @@ -2009,6 +2115,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) if (equal(leftvar, cfirst) && equal(rightvar, csecond)) { + coal_idx = foreach_current_index(lc2); match = true; break; } @@ -2073,7 +2180,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) */ if (matchleft && matchright) { - cur_ec->ec_members = list_delete_ptr(cur_ec->ec_members, coal_em); + cur_ec->ec_members = list_delete_nth_cell(cur_ec->ec_members, coal_idx); return true; } @@ -2147,6 +2254,10 @@ exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2) * we ignore that fine point here.) This is much like exprs_known_equal, * except that we insist on the comparison operator matching the eclass, so * that the result is definite not approximate. + * + * On success, we also set fkinfo->eclass[colno] to the matching eclass, + * and set fkinfo->fk_eclass_member[colno] to the eclass member for the + * referencing Var. */ EquivalenceClass * match_eclasses_to_foreign_key_col(PlannerInfo *root, @@ -2176,8 +2287,8 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, { EquivalenceClass *ec = (EquivalenceClass *) list_nth(root->eq_classes, i); - bool item1member = false; - bool item2member = false; + EquivalenceMember *item1_em = NULL; + EquivalenceMember *item2_em = NULL; ListCell *lc2; /* Never match to a volatile EC */ @@ -2202,12 +2313,12 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, /* Match? */ if (var->varno == var1varno && var->varattno == var1attno) - item1member = true; + item1_em = em; else if (var->varno == var2varno && var->varattno == var2attno) - item2member = true; + item2_em = em; /* Have we found both PK and FK column in this EC? */ - if (item1member && item2member) + if (item1_em && item2_em) { /* * Succeed if eqop matches EC's opfamilies. We could test @@ -2217,7 +2328,11 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, if (opfamilies == NIL) /* compute if we didn't already */ opfamilies = get_mergejoin_opfamilies(eqop); if (equal(opfamilies, ec->ec_opfamilies)) + { + fkinfo->eclass[colno] = ec; + fkinfo->fk_eclass_member[colno] = item2_em; return ec; + } /* Otherwise, done with this EC, move on to the next */ break; } @@ -2226,6 +2341,37 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, return NULL; } +/* + * find_derived_clause_for_ec_member + * Search for a previously-derived clause mentioning the given EM. + * + * The eclass should be an ec_has_const EC, of which the EM is a non-const + * member. This should ensure there is just one derived clause mentioning + * the EM (and equating it to a constant). + * Returns NULL if no such clause can be found. + */ +RestrictInfo * +find_derived_clause_for_ec_member(EquivalenceClass *ec, + EquivalenceMember *em) +{ + ListCell *lc; + + Assert(ec->ec_has_const); + Assert(!em->em_is_const); + foreach(lc, ec->ec_derives) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + + /* + * generate_base_implied_equalities_const will have put non-const + * members on the left side of derived clauses. + */ + if (rinfo->left_em == em) + return rinfo; + } + return NULL; +} + /* * add_child_rel_equivalences @@ -2381,6 +2527,7 @@ add_child_join_rel_equivalences(PlannerInfo *root, Relids top_parent_relids = child_joinrel->top_parent_relids; Relids child_relids = child_joinrel->relids; Bitmapset *matching_ecs; + MemoryContext oldcontext; int i; Assert(IS_JOIN_REL(child_joinrel) && IS_JOIN_REL(parent_joinrel)); @@ -2388,6 +2535,16 @@ add_child_join_rel_equivalences(PlannerInfo *root, /* We need consider only ECs that mention the parent joinrel */ matching_ecs = get_eclass_indexes_for_relids(root, top_parent_relids); + /* + * If we're being called during GEQO join planning, we still have to + * create any new EC members in the main planner context, to avoid having + * a corrupt EC data structure after the GEQO context is reset. This is + * problematic since we'll leak memory across repeated GEQO cycles. For + * now, though, bloat is better than crash. If it becomes a real issue + * we'll have to do something to avoid generating duplicate EC members. + */ + oldcontext = MemoryContextSwitchTo(root->planner_cxt); + i = -1; while ((i = bms_next_member(matching_ecs, i)) >= 0) { @@ -2487,6 +2644,8 @@ add_child_join_rel_equivalences(PlannerInfo *root, } } } + + MemoryContextSwitchTo(oldcontext); } diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index f49ebe141201..34274116874c 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -1072,8 +1072,8 @@ sort_inner_and_outer(PlannerInfo *root, /* Make a pathkey list with this guy first */ if (l != list_head(all_pathkeys)) outerkeys = lcons(front_pathkey, - list_delete_ptr(list_copy(all_pathkeys), - front_pathkey)); + list_delete_nth_cell(list_copy(all_pathkeys), + foreach_current_index(l))); else outerkeys = all_pathkeys; /* no work at first one... */ diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 0a6ec9e66f67..8ed66829601c 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -371,7 +371,7 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids) * Likewise remove references from PlaceHolderVar data structures, * removing any no-longer-needed placeholders entirely. * - * Removal is a bit tricker than it might seem: we can remove PHVs that + * Removal is a bit trickier than it might seem: we can remove PHVs that * are used at the target rel and/or in the join qual, but not those that * are used at join partner rels or above the join. It's not that easy to * distinguish PHVs used at partner rels from those used in the join qual, diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 5196148cf3af..2475bf1345ce 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -1350,7 +1350,6 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) * do partition pruning. */ if (enable_partition_pruning && - rel->reloptkind == RELOPT_BASEREL && best_path->partitioned_rels != NIL) { List *prunequal; @@ -1530,7 +1529,6 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path, * do partition pruning. */ if (enable_partition_pruning && - rel->reloptkind == RELOPT_BASEREL && best_path->partitioned_rels != NIL) { List *prunequal; @@ -2559,7 +2557,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) { bool is_first_sort = ((RollupData *) linitial(rollups))->is_hashed; - for_each_cell(lc, rollups, list_second_cell(rollups)) + for_each_from(lc, rollups, 1) { RollupData *rollup = lfirst(lc); AttrNumber *new_grpColIdx; @@ -6711,7 +6709,11 @@ make_foreignscan(List *qptlist, plan->lefttree = outer_plan; plan->righttree = NULL; node->scan.scanrelid = scanrelid; + + /* these may be overridden by the FDW's PlanDirectModify callback. */ node->operation = CMD_SELECT; + node->resultRelation = 0; + /* fs_server will be filled in by create_foreignscan_plan */ node->fs_server = InvalidOid; node->fdw_exprs = fdw_exprs; @@ -8110,8 +8112,6 @@ make_modifytable(PlannerInfo *root, node->rootRelation = rootRelation; node->partColsUpdated = partColsUpdated; node->resultRelations = resultRelations; - node->resultRelIndex = -1; /* will be set correctly in setrefs.c */ - node->rootResultRelIndex = -1; /* will be set correctly in setrefs.c */ node->plans = subplans; if (!onconflict) { diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 58319a91b4ba..fcd783e3eb4e 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -69,14 +69,12 @@ static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root, JoinType jointype, List *clause); static void compute_semijoin_info(SpecialJoinInfo *sjinfo, List *clause); static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, - bool is_deduced, bool below_outer_join, JoinType jointype, Index security_level, Relids qualscope, Relids ojscope, Relids outerjoin_nonnullable, - Relids deduced_nullable_relids, List **postponed_qual_list); static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p, Relids *nullable_relids_p, bool is_pushed_down); @@ -846,9 +844,9 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, if (bms_is_subset(pq->relids, *qualscope)) distribute_qual_to_rels(root, pq->qual, - false, below_outer_join, JOIN_INNER, + below_outer_join, JOIN_INNER, root->qual_security_level, - *qualscope, NULL, NULL, NULL, + *qualscope, NULL, NULL, NULL); else *postponed_qual_list = lappend(*postponed_qual_list, pq); @@ -862,9 +860,9 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, Node *qual = (Node *) lfirst(l); distribute_qual_to_rels(root, qual, - false, below_outer_join, JOIN_INNER, + below_outer_join, JOIN_INNER, root->qual_security_level, - *qualscope, NULL, NULL, NULL, + *qualscope, NULL, NULL, postponed_qual_list); } } @@ -1054,10 +1052,10 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, Node *qual = (Node *) lfirst(l); distribute_qual_to_rels(root, qual, - false, below_outer_join, j->jointype, + below_outer_join, j->jointype, root->qual_security_level, *qualscope, - ojscope, nonnullable_rels, NULL, + ojscope, nonnullable_rels, postponed_qual_list); } @@ -1156,14 +1154,12 @@ process_security_barrier_quals(PlannerInfo *root, * than being pushed up to top of tree, which we don't want. */ distribute_qual_to_rels(root, qual, - false, below_outer_join, JOIN_INNER, security_level, qualscope, qualscope, NULL, - NULL, NULL); } security_level++; @@ -1631,7 +1627,6 @@ compute_semijoin_info(SpecialJoinInfo *sjinfo, List *clause) * as belonging to a higher join level, just add it to postponed_qual_list. * * 'clause': the qual clause to be distributed - * 'is_deduced': true if the qual came from implied-equality deduction * 'below_outer_join': true if the qual is from a JOIN/ON that is below the * nullable side of a higher-level outer join * 'jointype': type of join the qual is from (JOIN_INNER for a WHERE clause) @@ -1643,8 +1638,6 @@ compute_semijoin_info(SpecialJoinInfo *sjinfo, List *clause) * baserels appearing on the outer (nonnullable) side of the join * (for FULL JOIN this includes both sides of the join, and must in fact * equal qualscope) - * 'deduced_nullable_relids': if is_deduced is true, the nullable relids to - * impute to the clause; otherwise NULL * 'postponed_qual_list': list of PostponedQual structs, which we can add * this qual to if it turns out to belong to a higher join level. * Can be NULL if caller knows postponement is impossible. @@ -1653,23 +1646,17 @@ compute_semijoin_info(SpecialJoinInfo *sjinfo, List *clause) * 'ojscope' is needed if we decide to force the qual up to the outer-join * level, which will be ojscope not necessarily qualscope. * - * In normal use (when is_deduced is false), at the time this is called, - * root->join_info_list must contain entries for all and only those special - * joins that are syntactically below this qual. But when is_deduced is true, - * we are adding new deduced clauses after completion of deconstruct_jointree, - * so it cannot be assumed that root->join_info_list has anything to do with - * qual placement. + * At the time this is called, root->join_info_list must contain entries for + * all and only those special joins that are syntactically below this qual. */ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, - bool is_deduced, bool below_outer_join, JoinType jointype, Index security_level, Relids qualscope, Relids ojscope, Relids outerjoin_nonnullable, - Relids deduced_nullable_relids, List **postponed_qual_list) { Relids relids; @@ -1703,7 +1690,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, Assert(root->hasLateralRTEs); /* shouldn't happen otherwise */ Assert(jointype == JOIN_INNER); /* mustn't postpone past outer join */ - Assert(!is_deduced); /* shouldn't be deduced, either */ pq->qual = clause; pq->relids = relids; *postponed_qual_list = lappend(*postponed_qual_list, pq); @@ -1804,24 +1790,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, * This seems like another reason why it should perhaps be rethought. *---------- */ - if (is_deduced) - { - /* - * If the qual came from implied-equality deduction, it should not be - * outerjoin-delayed, else deducer blew it. But we can't check this - * because the join_info_list may now contain OJs above where the qual - * belongs. For the same reason, we must rely on caller to supply the - * correct nullable_relids set. - */ - Assert(!ojscope); - is_pushed_down = true; - outerjoin_delayed = false; - nullable_relids = deduced_nullable_relids; - /* Don't feed it back for more deductions */ - maybe_equivalence = false; - maybe_outer_join = false; - } - else if (bms_overlap(relids, outerjoin_nonnullable)) + if (bms_overlap(relids, outerjoin_nonnullable)) { /* * The qual is attached to an outer join and mentions (some of the) @@ -2391,14 +2360,18 @@ distribute_restrictinfo_to_rels(PlannerInfo *root, * can produce constant TRUE or constant FALSE. (Otherwise it's not, * because the expressions went through eval_const_expressions already.) * + * Returns the generated RestrictInfo, if any. The result will be NULL + * if both_const is true and we successfully reduced the clause to + * constant TRUE. + * * Note: this function will copy item1 and item2, but it is caller's * responsibility to make sure that the Relids parameters are fresh copies * not shared with other uses. * - * This is currently used only when an EquivalenceClass is found to - * contain pseudoconstants. See path/pathkeys.c for more details. + * Note: we do not do initialize_mergeclause_eclasses() here. It is + * caller's responsibility that left_ec/right_ec be set as necessary. */ -void +RestrictInfo * process_implied_equality(PlannerInfo *root, Oid opno, Oid collation, @@ -2410,24 +2383,27 @@ process_implied_equality(PlannerInfo *root, bool below_outer_join, bool both_const) { - Expr *clause; + RestrictInfo *restrictinfo; + Node *clause; + Relids relids; + bool pseudoconstant = false; /* * Build the new clause. Copy to ensure it shares no substructure with * original (this is necessary in case there are subselects in there...) */ - clause = make_opclause(opno, - BOOLOID, /* opresulttype */ - false, /* opretset */ - copyObject(item1), - copyObject(item2), - InvalidOid, - collation); + clause = (Node *) make_opclause(opno, + BOOLOID, /* opresulttype */ + false, /* opretset */ + copyObject(item1), + copyObject(item2), + InvalidOid, + collation); /* If both constant, try to reduce to a boolean constant. */ if (both_const) { - clause = (Expr *) eval_const_expressions(root, (Node *) clause); + clause = eval_const_expressions(root, clause); /* If we produced const TRUE, just drop the clause */ if (clause && IsA(clause, Const)) @@ -2436,25 +2412,106 @@ process_implied_equality(PlannerInfo *root, Assert(cclause->consttype == BOOLOID); if (!cclause->constisnull && DatumGetBool(cclause->constvalue)) - return; + return NULL; + } + } + + /* + * The rest of this is a very cut-down version of distribute_qual_to_rels. + * We can skip most of the work therein, but there are a couple of special + * cases we still have to handle. + * + * Retrieve all relids mentioned within the possibly-simplified clause. + */ + relids = pull_varnos(clause); + Assert(bms_is_subset(relids, qualscope)); + + /* + * If the clause is variable-free, our normal heuristic for pushing it + * down to just the mentioned rels doesn't work, because there are none. + * Apply at the given qualscope, or at the top of tree if it's nonvolatile + * (which it very likely is, but we'll check, just to be sure). + */ + if (bms_is_empty(relids)) + { + /* eval at original syntactic level */ + relids = bms_copy(qualscope); + if (!contain_volatile_functions(clause)) + { + /* mark as gating qual */ + pseudoconstant = true; + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + /* if not below outer join, push it to top of tree */ + if (!below_outer_join) + { + relids = + get_relids_in_jointree((Node *) root->parse->jointree, + false); + } } } + /* + * Build the RestrictInfo node itself. + */ + restrictinfo = make_restrictinfo((Expr *) clause, + true, /* is_pushed_down */ + false, /* outerjoin_delayed */ + pseudoconstant, + security_level, + relids, + NULL, /* outer_relids */ + nullable_relids); + + /* + * If it's a join clause, add vars used in the clause to targetlists of + * their relations, so that they will be emitted by the plan nodes that + * scan those relations (else they won't be available at the join node!). + * + * Typically, we'd have already done this when the component expressions + * were first seen by distribute_qual_to_rels; but it is possible that + * some of the Vars could have missed having that done because they only + * appeared in single-relation clauses originally. So do it here for + * safety. + */ + if (bms_membership(relids) == BMS_MULTIPLE) + { + List *vars = pull_var_clause(clause, + PVC_RECURSE_AGGREGATES | + PVC_RECURSE_WINDOWFUNCS | + PVC_INCLUDE_PLACEHOLDERS); + + add_vars_to_targetlist(root, vars, relids, false); + list_free(vars); + } + + /* + * Check mergejoinability. This will usually succeed, since the op came + * from an EquivalenceClass; but we could have reduced the original clause + * to a constant. + */ + check_mergejoinable(restrictinfo); + + /* + * Note we don't do initialize_mergeclause_eclasses(); the caller can + * handle that much more cheaply than we can. It's okay to call + * distribute_restrictinfo_to_rels() before that happens. + */ + /* * Push the new clause into all the appropriate restrictinfo lists. */ - distribute_qual_to_rels(root, (Node *) clause, - true, below_outer_join, JOIN_INNER, - security_level, - qualscope, NULL, NULL, nullable_relids, - NULL); + distribute_restrictinfo_to_rels(root, restrictinfo); + + return restrictinfo; } /* * build_implied_join_equality --- build a RestrictInfo for a derived equality * * This overlaps the functionality of process_implied_equality(), but we - * must return the RestrictInfo, not push it into the joininfo tree. + * must not push the RestrictInfo into the joininfo tree. * * Note: this function will copy item1 and item2, but it is caller's * responsibility to make sure that the Relids parameters are fresh copies @@ -2569,18 +2626,19 @@ match_foreign_keys_to_quals(PlannerInfo *root) */ for (colno = 0; colno < fkinfo->nkeys; colno++) { + EquivalenceClass *ec; AttrNumber con_attno, ref_attno; Oid fpeqop; ListCell *lc2; - fkinfo->eclass[colno] = match_eclasses_to_foreign_key_col(root, - fkinfo, - colno); + ec = match_eclasses_to_foreign_key_col(root, fkinfo, colno); /* Don't bother looking for loose quals if we got an EC match */ - if (fkinfo->eclass[colno] != NULL) + if (ec != NULL) { fkinfo->nmatched_ec++; + if (ec->ec_has_const) + fkinfo->nconst_ec++; continue; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 411354e9eadb..282bbd0786f3 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -420,7 +420,6 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, glob->finalrtable = NIL; glob->finalrowmarks = NIL; glob->resultRelations = NIL; - glob->rootResultRelations = NIL; glob->appendRelations = NIL; glob->relationOids = NIL; glob->invalItems = NIL; @@ -672,7 +671,6 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, Assert(glob->finalrowmarks == NIL); Assert(glob->resultRelations == NIL); Assert(parse == root->parse); - Assert(glob->rootResultRelations == NIL); Assert(glob->appendRelations == NIL); if (Gp_role == GP_ROLE_DISPATCH) @@ -736,7 +734,6 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, result->slices = glob->slices; result->rtable = glob->finalrtable; result->resultRelations = glob->resultRelations; - result->rootResultRelations = glob->rootResultRelations; result->appendRelations = glob->appendRelations; result->subplans = glob->subplans; result->subplan_sliceIds = glob->subplan_sliceIds; @@ -852,6 +849,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse, Assert(config); root->config = config; + root->hasPseudoConstantQuals = false; + root->hasAlternativeSubPlans = false; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = assign_special_exec_param(root); @@ -1010,9 +1009,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, */ root->hasHavingQual = (parse->havingQual != NULL); - /* Clear this flag; might get set in distribute_qual_to_rels */ - root->hasPseudoConstantQuals = false; - /* * Do expression preprocessing on targetlist and quals, as well as other * random expressions in the querytree. Note that we do not need to @@ -5013,7 +5009,7 @@ consider_groupingsets_paths(PlannerInfo *root, * below, must use the same condition. */ i = 0; - for_each_cell(lc, gd->rollups, list_second_cell(gd->rollups)) + for_each_from(lc, gd->rollups, 1) { RollupData *rollup = lfirst_node(RollupData, lc); @@ -5047,7 +5043,7 @@ consider_groupingsets_paths(PlannerInfo *root, rollups = list_make1(linitial(gd->rollups)); i = 0; - for_each_cell(lc, gd->rollups, list_second_cell(gd->rollups)) + for_each_from(lc, gd->rollups, 1) { RollupData *rollup = lfirst_node(RollupData, lc); @@ -5165,14 +5161,17 @@ create_window_paths(PlannerInfo *root, /* * Consider computing window functions starting from the existing * cheapest-total path (which will likely require a sort) as well as any - * existing paths that satisfy root->window_pathkeys (which won't). + * existing paths that satisfy or partially satisfy root->window_pathkeys. */ foreach(lc, input_rel->pathlist) { Path *path = (Path *) lfirst(lc); + int presorted_keys; if (path == input_rel->cheapest_total_path || - pathkeys_contained_in(root->window_pathkeys, path->pathkeys)) + pathkeys_count_contained_in(root->window_pathkeys, path->pathkeys, + &presorted_keys) || + presorted_keys > 0) create_one_window_path(root, window_rel, path, @@ -5247,6 +5246,10 @@ create_one_window_path(PlannerInfo *root, { WindowClause *wc = lfirst_node(WindowClause, l); List *window_pathkeys; +#if 0 /* GPDB_14_MERGE_FIXME: enable incremental sort */ + int presorted_keys; + bool is_sorted; +#endif window_pathkeys = make_pathkeys_for_window(root, wc, @@ -5273,6 +5276,38 @@ create_one_window_path(PlannerInfo *root, -1.0, wc->partitionClause, NIL); +#if 0 /* GPDB_14_MERGE_FIXME: enable incremental sort */ + is_sorted = pathkeys_count_contained_in(window_pathkeys, + path->pathkeys, + &presorted_keys); + + /* Sort if necessary */ + if (!is_sorted) + { + /* + * No presorted keys or incremental sort disabled, just perform a + * complete sort. + */ + if (presorted_keys == 0 || !enable_incremental_sort) + path = (Path *) create_sort_path(root, window_rel, + path, + window_pathkeys, + -1.0); + else + { + /* + * Since we have presorted keys and incremental sort is + * enabled, just use incremental sort. + */ + path = (Path *) create_incremental_sort_path(root, + window_rel, + path, + window_pathkeys, + presorted_keys, + -1.0); + } + } +#endif if (lnext(activeWindows, l)) { @@ -5754,7 +5789,7 @@ create_ordered_paths(PlannerInfo *root, foreach(lc, input_rel->partial_pathlist) { Path *input_path = (Path *) lfirst(lc); - Path *sorted_path = input_path; + Path *sorted_path; bool is_sorted; int presorted_keys; double total_groups; diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 8c39f7905c7b..66eea459f10c 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -59,6 +59,7 @@ typedef struct { PlannerInfo *root; int rtoffset; + double num_exec; } fix_scan_expr_context; typedef struct @@ -70,6 +71,7 @@ typedef struct int rtoffset; bool use_outer_tlist_for_matching_nonvars; bool use_inner_tlist_for_matching_nonvars; + double num_exec; } fix_join_expr_context; typedef struct @@ -78,6 +80,7 @@ typedef struct indexed_tlist *subplan_itlist; Index newvarno; int rtoffset; + double num_exec; } fix_upper_expr_context; typedef struct @@ -86,6 +89,25 @@ typedef struct plan_tree_base_prefix base; } cdb_extract_plan_dependencies_context; +/* + * Selecting the best alternative in an AlternativeSubPlan expression requires + * estimating how many times that expression will be evaluated. For an + * expression in a plan node's targetlist, the plan's estimated number of + * output rows is clearly what to use, but for an expression in a qual it's + * far less clear. Since AlternativeSubPlans aren't heavily used, we don't + * want to expend a lot of cycles making such estimates. What we use is twice + * the number of output rows. That's not entirely unfounded: we know that + * clause_selectivity() would fall back to a default selectivity estimate + * of 0.5 for any SubPlan, so if the qual containing the SubPlan is the last + * to be applied (which it likely would be, thanks to order_qual_clauses()), + * this matches what we could have estimated in a far more laborious fashion. + * Obviously there are many other scenarios, but it's probably not worth the + * trouble to try to improve on this estimate, especially not when we don't + * have a better estimate for the selectivity of the SubPlan qual itself. + */ +#define NUM_EXEC_TLIST(parentplan) ((parentplan)->plan_rows) +#define NUM_EXEC_QUAL(parentplan) ((parentplan)->plan_rows * 2.0) + /* * Check if a Const node is a regclass value. We accept plain OID too, * since a regclass Const will get folded to that type if it's an argument @@ -97,8 +119,8 @@ typedef struct (((con)->consttype == REGCLASSOID || (con)->consttype == OIDOID) && \ !(con)->constisnull) -#define fix_scan_list(root, lst, rtoffset) \ - ((List *) fix_scan_expr(root, (Node *) (lst), rtoffset)) +#define fix_scan_list(root, lst, rtoffset, num_exec) \ + ((List *) fix_scan_expr(root, (Node *) (lst), rtoffset, num_exec)) static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing); static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte); @@ -127,7 +149,8 @@ static Plan *set_mergeappend_references(PlannerInfo *root, int rtoffset); static void set_hash_references(PlannerInfo *root, Plan *plan, int rtoffset); static Relids offset_relid_set(Relids relids, int rtoffset); -static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset); +static Node *fix_scan_expr(PlannerInfo *root, Node *node, + int rtoffset, double num_exec); static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context); static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context); static void set_join_references(PlannerInfo *root, Join *join, int rtoffset); @@ -153,25 +176,27 @@ static List *fix_join_expr(PlannerInfo *root, List *clauses, indexed_tlist *outer_itlist, indexed_tlist *inner_itlist, - Index acceptable_rel, int rtoffset); + Index acceptable_rel, + int rtoffset, double num_exec); static Node *fix_join_expr_mutator(Node *node, fix_join_expr_context *context); static List *fix_hashclauses(PlannerInfo *root, List *clauses, indexed_tlist *outer_itlist, indexed_tlist *inner_itlist, - Index acceptable_rel, int rtoffset); + Index acceptable_rel, int rtoffset, + double num_exec); static List *fix_child_hashclauses(PlannerInfo *root, List *clauses, indexed_tlist *outer_itlist, indexed_tlist *inner_itlist, Index acceptable_rel, int rtoffset, - Index child); + Index child, double num_exec); static Node *fix_upper_expr(PlannerInfo *root, Node *node, indexed_tlist *subplan_itlist, Index newvarno, - int rtoffset); + int rtoffset, double num_exec); static Node *fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context); static List *set_returning_clause_references(PlannerInfo *root, @@ -309,17 +334,20 @@ static void set_plan_references_output_asserts(PlannerGlobal *glob, Plan *plan) * 5. PARAM_MULTIEXPR Params are replaced by regular PARAM_EXEC Params, * now that we have finished planning all MULTIEXPR subplans. * - * 6. We compute regproc OIDs for operators (ie, we look up the function + * 6. AlternativeSubPlan expressions are replaced by just one of their + * alternatives, using an estimate of how many times they'll be executed. + * + * 7. We compute regproc OIDs for operators (ie, we look up the function * that implements each op). * - * 7. We create lists of specific objects that the plan depends on. + * 8. We create lists of specific objects that the plan depends on. * This will be used by plancache.c to drive invalidation of cached plans. * Relation dependencies are represented by OIDs, and everything else by * PlanInvalItems (this distinction is motivated by the shared-inval APIs). * Currently, relations, user-defined functions, and domains are the only * types of objects that are explicitly tracked this way. * - * 8. We assign every plan node in the tree a unique ID. + * 9. We assign every plan node in the tree a unique ID. * * We also perform one final optimization step, which is to delete * SubqueryScan, Append, and MergeAppend plan nodes that aren't doing @@ -563,9 +591,9 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob) * In the flat rangetable, we zero out substructure pointers that are not * needed by the executor; this reduces the storage space and copying cost * for cached plans. We keep only the ctename, alias and eref Alias fields, - * which are needed by EXPLAIN, and the selectedCols, insertedCols and - * updatedCols bitmaps, which are needed for executor-startup permissions - * checking and for trigger event checking. + * which are needed by EXPLAIN, and the selectedCols, insertedCols, + * updatedCols, and extraUpdatedCols bitmaps, which are needed for + * executor-startup permissions checking and for trigger event checking. */ static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte) @@ -653,9 +681,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) #endif splan->plan.targetlist = - fix_scan_list(root, splan->plan.targetlist, rtoffset); + fix_scan_list(root, splan->plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); splan->plan.qual = - fix_scan_list(root, splan->plan.qual, rtoffset); + fix_scan_list(root, splan->plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); } break; case T_SampleScan: @@ -664,11 +694,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = - fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, splan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); splan->scan.plan.qual = - fix_scan_list(root, splan->scan.plan.qual, rtoffset); + fix_scan_list(root, splan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); splan->tablesample = (TableSampleClause *) - fix_scan_expr(root, (Node *) splan->tablesample, rtoffset); + fix_scan_expr(root, (Node *) splan->tablesample, + rtoffset, 1); } break; case T_IndexScan: @@ -681,17 +714,23 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = - fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, splan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); splan->scan.plan.qual = - fix_scan_list(root, splan->scan.plan.qual, rtoffset); + fix_scan_list(root, splan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); splan->indexqual = - fix_scan_list(root, splan->indexqual, rtoffset); + fix_scan_list(root, splan->indexqual, + rtoffset, 1); splan->indexqualorig = - fix_scan_list(root, splan->indexqualorig, rtoffset); + fix_scan_list(root, splan->indexqualorig, + rtoffset, NUM_EXEC_QUAL(plan)); splan->indexorderby = - fix_scan_list(root, splan->indexorderby, rtoffset); + fix_scan_list(root, splan->indexorderby, + rtoffset, 1); splan->indexorderbyorig = - fix_scan_list(root, splan->indexorderbyorig, rtoffset); + fix_scan_list(root, splan->indexorderbyorig, + rtoffset, NUM_EXEC_QUAL(plan)); } break; case T_IndexOnlyScan: @@ -710,9 +749,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) Assert(splan->scan.plan.targetlist == NIL); Assert(splan->scan.plan.qual == NIL); splan->indexqual = - fix_scan_list(root, splan->indexqual, rtoffset); + fix_scan_list(root, splan->indexqual, rtoffset, 1); splan->indexqualorig = - fix_scan_list(root, splan->indexqualorig, rtoffset); + fix_scan_list(root, splan->indexqualorig, + rtoffset, NUM_EXEC_QUAL(plan)); } break; case T_BitmapHeapScan: @@ -725,11 +765,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = - fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, splan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); splan->scan.plan.qual = - fix_scan_list(root, splan->scan.plan.qual, rtoffset); + fix_scan_list(root, splan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); splan->bitmapqualorig = - fix_scan_list(root, splan->bitmapqualorig, rtoffset); + fix_scan_list(root, splan->bitmapqualorig, + rtoffset, NUM_EXEC_QUAL(plan)); } break; case T_TidScan: @@ -741,11 +784,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = - fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, splan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); splan->scan.plan.qual = - fix_scan_list(root, splan->scan.plan.qual, rtoffset); + fix_scan_list(root, splan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); splan->tidquals = - fix_scan_list(root, splan->tidquals, rtoffset); + fix_scan_list(root, splan->tidquals, + rtoffset, 1); } break; case T_SubqueryScan: @@ -776,11 +822,13 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) /* adjust for the new range table offset */ tplan->scan.scanrelid += rtoffset; tplan->scan.plan.targetlist = - fix_scan_list(root, tplan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, tplan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); tplan->scan.plan.qual = - fix_scan_list(root, tplan->scan.plan.qual, rtoffset); + fix_scan_list(root, tplan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); tplan->function = (RangeTblFunction *) - fix_scan_expr(root, (Node *) tplan->function, rtoffset); + fix_scan_expr(root, (Node *) tplan->function, rtoffset, 1); return plan; } @@ -793,11 +841,13 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = - fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, splan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); splan->scan.plan.qual = - fix_scan_list(root, splan->scan.plan.qual, rtoffset); + fix_scan_list(root, splan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); splan->functions = - fix_scan_list(root, splan->functions, rtoffset); + fix_scan_list(root, splan->functions, rtoffset, 1); } break; case T_TableFuncScan: @@ -806,11 +856,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = - fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, splan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); splan->scan.plan.qual = - fix_scan_list(root, splan->scan.plan.qual, rtoffset); + fix_scan_list(root, splan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); splan->tablefunc = (TableFunc *) - fix_scan_expr(root, (Node *) splan->tablefunc, rtoffset); + fix_scan_expr(root, (Node *) splan->tablefunc, + rtoffset, 1); } break; case T_ValuesScan: @@ -822,11 +875,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = - fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, splan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); splan->scan.plan.qual = - fix_scan_list(root, splan->scan.plan.qual, rtoffset); + fix_scan_list(root, splan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); splan->values_lists = - fix_scan_list(root, splan->values_lists, rtoffset); + fix_scan_list(root, splan->values_lists, + rtoffset, 1); } break; case T_CteScan: @@ -835,9 +891,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = - fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, splan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); splan->scan.plan.qual = - fix_scan_list(root, splan->scan.plan.qual, rtoffset); + fix_scan_list(root, splan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); } break; case T_NamedTuplestoreScan: @@ -846,9 +904,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = - fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, splan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); splan->scan.plan.qual = - fix_scan_list(root, splan->scan.plan.qual, rtoffset); + fix_scan_list(root, splan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); } break; case T_WorkTableScan: @@ -857,9 +917,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = - fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, splan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); splan->scan.plan.qual = - fix_scan_list(root, splan->scan.plan.qual, rtoffset); + fix_scan_list(root, splan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); } break; case T_ForeignScan: @@ -955,10 +1017,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) pinfo->initial_pruning_steps = (List *) fix_upper_expr(root, (Node *) pinfo->initial_pruning_steps, - childplan_itlist, OUTER_VAR, rtoffset); + childplan_itlist, OUTER_VAR, rtoffset, 1); pinfo->exec_pruning_steps = (List *) fix_upper_expr(root, (Node *) pinfo->exec_pruning_steps, - childplan_itlist, OUTER_VAR, rtoffset); + childplan_itlist, OUTER_VAR, rtoffset, + NUM_EXEC_TLIST(plan)); } } } @@ -1000,9 +1063,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) Assert(splan->plan.qual == NIL); splan->limitOffset = - fix_scan_expr(root, splan->limitOffset, rtoffset); + fix_scan_expr(root, splan->limitOffset, rtoffset, 1); splan->limitCount = - fix_scan_expr(root, splan->limitCount, rtoffset); + fix_scan_expr(root, splan->limitCount, rtoffset, 1); } break; case T_Agg: @@ -1052,7 +1115,8 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) (Node *)dqaExpr->agg_filter, subplan_itlist, OUTER_VAR, - rtoffset); + rtoffset, + NUM_EXEC_TLIST(plan)); lfirst(lc) = dqaExpr; } @@ -1082,10 +1146,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) wplan->startOffset = fix_upper_expr(root, wplan->startOffset, - subplan_itlist, OUTER_VAR, rtoffset); + subplan_itlist, OUTER_VAR, rtoffset, 1); wplan->endOffset = fix_upper_expr(root, wplan->endOffset, - subplan_itlist, OUTER_VAR, rtoffset); + subplan_itlist, OUTER_VAR, rtoffset, 1); pfree(subplan_itlist); } } @@ -1103,13 +1167,15 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) else { splan->plan.targetlist = - fix_scan_list(root, splan->plan.targetlist, rtoffset); + fix_scan_list(root, splan->plan.targetlist, + rtoffset, NUM_EXEC_TLIST(plan)); splan->plan.qual = - fix_scan_list(root, splan->plan.qual, rtoffset); + fix_scan_list(root, splan->plan.qual, + rtoffset, NUM_EXEC_QUAL(plan)); } /* resconstantqual can't contain any subplan variable refs */ splan->resconstantqual = - fix_scan_expr(root, splan->resconstantqual, rtoffset); + fix_scan_expr(root, splan->resconstantqual, rtoffset, 1); } break; case T_ProjectSet: @@ -1123,7 +1189,8 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) Assert(splan->plan.qual == NIL); splan->withCheckOptionLists = - fix_scan_list(root, splan->withCheckOptionLists, rtoffset); + fix_scan_list(root, splan->withCheckOptionLists, + rtoffset, 1); if (splan->returningLists) { @@ -1184,18 +1251,18 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) fix_join_expr(root, splan->onConflictSet, NULL, itlist, linitial_int(splan->resultRelations), - rtoffset); + rtoffset, NUM_EXEC_QUAL(plan)); splan->onConflictWhere = (Node *) fix_join_expr(root, (List *) splan->onConflictWhere, NULL, itlist, linitial_int(splan->resultRelations), - rtoffset); + rtoffset, NUM_EXEC_QUAL(plan)); pfree(itlist); splan->exclRelTlist = - fix_scan_list(root, splan->exclRelTlist, rtoffset); + fix_scan_list(root, splan->exclRelTlist, rtoffset, 1); } splan->nominalRelation += rtoffset; @@ -1223,26 +1290,15 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) /* * Append this ModifyTable node's final result relation RT - * index(es) to the global list for the plan, and set its - * resultRelIndex to reflect their starting position in the - * global list. + * index(es) to the global list for the plan. */ - splan->resultRelIndex = list_length(root->glob->resultRelations); root->glob->resultRelations = list_concat(root->glob->resultRelations, splan->resultRelations); - - /* - * If the main target relation is a partitioned table, also - * add the partition root's RT index to rootResultRelations, - * and remember its index in that list in rootResultRelIndex. - */ if (splan->rootRelation) { - splan->rootResultRelIndex = - list_length(root->glob->rootResultRelations); - root->glob->rootResultRelations = - lappend_int(root->glob->rootResultRelations, + root->glob->resultRelations = + lappend_int(root->glob->resultRelations, splan->rootRelation); } } @@ -1299,7 +1355,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) build_tlist_index(plan->lefttree->targetlist); motion->hashExprs = (List *) - fix_upper_expr(root, (Node*) motion->hashExprs, childplan_itlist, OUTER_VAR, rtoffset); + fix_upper_expr(root, (Node*) motion->hashExprs, + childplan_itlist, OUTER_VAR, rtoffset, + NUM_EXEC_TLIST(plan)); /* no need to fix targetlist and qual */ Assert(plan->qual == NIL); @@ -1355,21 +1413,27 @@ set_indexonlyscan_references(PlannerInfo *root, (Node *) plan->scan.plan.targetlist, index_itlist, INDEX_VAR, - rtoffset); + rtoffset, + NUM_EXEC_TLIST((Plan *) plan)); plan->scan.plan.qual = (List *) fix_upper_expr(root, (Node *) plan->scan.plan.qual, index_itlist, INDEX_VAR, - rtoffset); - /* indexqual is already transformed to reference index columns */ - plan->indexqual = fix_scan_list(root, plan->indexqual, rtoffset); + rtoffset, + NUM_EXEC_QUAL((Plan *) plan)); /* indexqualorig is already transformed to reference index columns */ - plan->indexqualorig = fix_scan_list(root, plan->indexqualorig, rtoffset); + plan->indexqualorig = fix_scan_list(root, plan->indexqualorig, + rtoffset, 1); + /* indexqual is already transformed to reference index columns */ + plan->indexqual = fix_scan_list(root, plan->indexqual, + rtoffset, 1); /* indexorderby is already transformed to reference index columns */ - plan->indexorderby = fix_scan_list(root, plan->indexorderby, rtoffset); + plan->indexorderby = fix_scan_list(root, plan->indexorderby, + rtoffset, 1); /* indextlist must NOT be transformed to reference index columns */ - plan->indextlist = fix_scan_list(root, plan->indextlist, rtoffset); + plan->indextlist = fix_scan_list(root, plan->indextlist, + rtoffset, NUM_EXEC_TLIST((Plan *) plan)); pfree(index_itlist); @@ -1418,9 +1482,11 @@ set_subqueryscan_references(PlannerInfo *root, //Assert(plan->scan.scanrelid <= list_length(glob->finalrtable) && "Scan node's relid is outside the finalrtable!"); plan->scan.plan.targetlist = - fix_scan_list(root, plan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, plan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST((Plan *) plan)); plan->scan.plan.qual = - fix_scan_list(root, plan->scan.plan.qual, rtoffset); + fix_scan_list(root, plan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL((Plan *) plan)); result = (Plan *) plan; } @@ -1538,29 +1604,34 @@ set_foreignscan_references(PlannerInfo *root, (Node *) fscan->scan.plan.targetlist, itlist, INDEX_VAR, - rtoffset); + rtoffset, + NUM_EXEC_TLIST((Plan *) fscan)); fscan->scan.plan.qual = (List *) fix_upper_expr(root, (Node *) fscan->scan.plan.qual, itlist, INDEX_VAR, - rtoffset); + rtoffset, + NUM_EXEC_QUAL((Plan *) fscan)); fscan->fdw_exprs = (List *) fix_upper_expr(root, (Node *) fscan->fdw_exprs, itlist, INDEX_VAR, - rtoffset); + rtoffset, + NUM_EXEC_QUAL((Plan *) fscan)); fscan->fdw_recheck_quals = (List *) fix_upper_expr(root, (Node *) fscan->fdw_recheck_quals, itlist, INDEX_VAR, - rtoffset); + rtoffset, + NUM_EXEC_QUAL((Plan *) fscan)); pfree(itlist); /* fdw_scan_tlist itself just needs fix_scan_list() adjustments */ fscan->fdw_scan_tlist = - fix_scan_list(root, fscan->fdw_scan_tlist, rtoffset); + fix_scan_list(root, fscan->fdw_scan_tlist, + rtoffset, NUM_EXEC_TLIST((Plan *) fscan)); } else { @@ -1569,16 +1640,24 @@ set_foreignscan_references(PlannerInfo *root, * way */ fscan->scan.plan.targetlist = - fix_scan_list(root, fscan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, fscan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST((Plan *) fscan)); fscan->scan.plan.qual = - fix_scan_list(root, fscan->scan.plan.qual, rtoffset); + fix_scan_list(root, fscan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL((Plan *) fscan)); fscan->fdw_exprs = - fix_scan_list(root, fscan->fdw_exprs, rtoffset); + fix_scan_list(root, fscan->fdw_exprs, + rtoffset, NUM_EXEC_QUAL((Plan *) fscan)); fscan->fdw_recheck_quals = - fix_scan_list(root, fscan->fdw_recheck_quals, rtoffset); + fix_scan_list(root, fscan->fdw_recheck_quals, + rtoffset, NUM_EXEC_QUAL((Plan *) fscan)); } fscan->fs_relids = offset_relid_set(fscan->fs_relids, rtoffset); + + /* Adjust resultRelation if it's valid */ + if (fscan->resultRelation > 0) + fscan->resultRelation += rtoffset; } /* @@ -1606,33 +1685,40 @@ set_customscan_references(PlannerInfo *root, (Node *) cscan->scan.plan.targetlist, itlist, INDEX_VAR, - rtoffset); + rtoffset, + NUM_EXEC_TLIST((Plan *) cscan)); cscan->scan.plan.qual = (List *) fix_upper_expr(root, (Node *) cscan->scan.plan.qual, itlist, INDEX_VAR, - rtoffset); + rtoffset, + NUM_EXEC_QUAL((Plan *) cscan)); cscan->custom_exprs = (List *) fix_upper_expr(root, (Node *) cscan->custom_exprs, itlist, INDEX_VAR, - rtoffset); + rtoffset, + NUM_EXEC_QUAL((Plan *) cscan)); pfree(itlist); /* custom_scan_tlist itself just needs fix_scan_list() adjustments */ cscan->custom_scan_tlist = - fix_scan_list(root, cscan->custom_scan_tlist, rtoffset); + fix_scan_list(root, cscan->custom_scan_tlist, + rtoffset, NUM_EXEC_TLIST((Plan *) cscan)); } else { /* Adjust tlist, qual, custom_exprs in the standard way */ cscan->scan.plan.targetlist = - fix_scan_list(root, cscan->scan.plan.targetlist, rtoffset); + fix_scan_list(root, cscan->scan.plan.targetlist, + rtoffset, NUM_EXEC_TLIST((Plan *) cscan)); cscan->scan.plan.qual = - fix_scan_list(root, cscan->scan.plan.qual, rtoffset); + fix_scan_list(root, cscan->scan.plan.qual, + rtoffset, NUM_EXEC_QUAL((Plan *) cscan)); cscan->custom_exprs = - fix_scan_list(root, cscan->custom_exprs, rtoffset); + fix_scan_list(root, cscan->custom_exprs, + rtoffset, NUM_EXEC_QUAL((Plan *) cscan)); } /* Adjust child plan-nodes recursively, if needed */ @@ -1794,7 +1880,8 @@ set_hash_references(PlannerInfo *root, Plan *plan, int rtoffset) (Node *) hplan->hashkeys, outer_itlist, OUTER_VAR, - rtoffset); + rtoffset, + NUM_EXEC_QUAL(plan)); /* Hash doesn't project */ set_dummy_tlist_references(plan, rtoffset); @@ -1963,6 +2050,69 @@ fix_param_node(PlannerInfo *root, Param *p) return (Node *) copyObject(p); } +/* + * fix_alternative_subplan + * Do set_plan_references processing on an AlternativeSubPlan + * + * Choose one of the alternative implementations and return just that one, + * discarding the rest of the AlternativeSubPlan structure. + * Note: caller must still recurse into the result! + * + * We don't make any attempt to fix up cost estimates in the parent plan + * node or higher-level nodes. However, we do remove the rejected subplan(s) + * from root->glob->subplans, to minimize cycles expended on them later. + */ +static Node * +fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan, + double num_exec) +{ + SubPlan *bestplan = NULL; + Cost bestcost = 0; + ListCell *lc; + + /* + * Compute the estimated cost of each subplan assuming num_exec + * executions, and keep the cheapest one. Replace discarded subplans with + * NULL pointers in the global subplans list. In event of exact equality + * of estimates, we prefer the later plan; this is a bit arbitrary, but in + * current usage it biases us to break ties against fast-start subplans. + */ + Assert(asplan->subplans != NIL); + + foreach(lc, asplan->subplans) + { + SubPlan *curplan = (SubPlan *) lfirst(lc); + Cost curcost; + + curcost = curplan->startup_cost + num_exec * curplan->per_call_cost; + if (bestplan == NULL) + { + bestplan = curplan; + bestcost = curcost; + } + else if (curcost <= bestcost) + { + /* drop old bestplan */ + ListCell *lc2 = list_nth_cell(root->glob->subplans, + bestplan->plan_id - 1); + + lfirst(lc2) = NULL; + bestplan = curplan; + bestcost = curcost; + } + else + { + /* drop curplan */ + ListCell *lc2 = list_nth_cell(root->glob->subplans, + curplan->plan_id - 1); + + lfirst(lc2) = NULL; + } + } + + return (Node *) bestplan; +} + /* * fix_scan_expr * Do set_plan_references processing on a scan-level expression @@ -1970,21 +2120,24 @@ fix_param_node(PlannerInfo *root, Param *p) * This consists of incrementing all Vars' varnos by rtoffset, * replacing PARAM_MULTIEXPR Params, expanding PlaceHolderVars, * replacing Aggref nodes that should be replaced by initplan output Params, + * choosing the best implementation for AlternativeSubPlans, * looking up operator opcode info for OpExpr and related nodes, * and adding OIDs from regclass Const nodes into root->glob->relationOids. */ static Node * -fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset) +fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec) { fix_scan_expr_context context; context.root = root; context.rtoffset = rtoffset; + context.num_exec = num_exec; if (rtoffset != 0 || root->multiexpr_params != NIL || root->glob->lastPHId != 0 || - root->minmax_aggs != NIL) + root->minmax_aggs != NIL || + root->hasAlternativeSubPlans) { return fix_scan_expr_mutator(node, &context); } @@ -1995,7 +2148,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset) * are no MULTIEXPR subqueries then we don't need to replace * PARAM_MULTIEXPR Params, and if there are no placeholders anywhere * we won't need to remove them, and if there are no minmax Aggrefs we - * won't need to replace them. Then it's OK to just scribble on the + * won't need to replace them, and if there are no AlternativeSubPlans + * we won't need to remove them. Then it's OK to just scribble on the * input node tree instead of copying (since the only change, filling * in any unset opfuncid fields, is harmless). This saves just enough * cycles to be noticeable on trivial queries. @@ -2069,6 +2223,11 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context) return fix_scan_expr_mutator((Node *) phv->phexpr, context); } + if (IsA(node, AlternativeSubPlan)) + return fix_scan_expr_mutator(fix_alternative_subplan(context->root, + (AlternativeSubPlan *) node, + context->num_exec), + context); fix_expr_common(context->root, node); return expression_tree_mutator(node, fix_scan_expr_mutator, (void *) context); @@ -2080,6 +2239,7 @@ fix_scan_expr_walker(Node *node, fix_scan_expr_context *context) if (node == NULL) return false; Assert(!IsA(node, PlaceHolderVar)); + Assert(!IsA(node, AlternativeSubPlan)); fix_expr_common(context->root, node); return expression_tree_walker(node, fix_scan_expr_walker, (void *) context); @@ -2116,7 +2276,8 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) outer_itlist, inner_itlist, (Index) 0, - rtoffset); + rtoffset, + NUM_EXEC_QUAL((Plan *) join)); /* Now do join-type-specific stuff */ if (IsA(join, NestLoop)) @@ -2132,7 +2293,8 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) (Node *) nlp->paramval, outer_itlist, OUTER_VAR, - rtoffset); + rtoffset, + NUM_EXEC_TLIST(outer_plan)); /* Check we replaced any PlaceHolderVar with simple Var */ if (!(IsA(nlp->paramval, Var) && nlp->paramval->varno == OUTER_VAR)) @@ -2148,7 +2310,8 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) outer_itlist, inner_itlist, (Index) 0, - rtoffset); + rtoffset, + NUM_EXEC_QUAL((Plan *) join)); } else if (IsA(join, HashJoin)) { @@ -2159,14 +2322,16 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) outer_itlist, inner_itlist, (Index) 0, - rtoffset); + rtoffset, + NUM_EXEC_QUAL((Plan *) join)); hj->hashqualclauses = fix_join_expr(root, hj->hashqualclauses, outer_itlist, inner_itlist, (Index) 0, - rtoffset); + rtoffset, + NUM_EXEC_QUAL((Plan *) join)); /* * HashJoin's hashkeys are used to look for matching tuples from its * outer plan (not the Hash node!) in the hashtable. @@ -2175,7 +2340,8 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) (Node *) hj->hashkeys, outer_itlist, OUTER_VAR, - rtoffset); + rtoffset, + NUM_EXEC_QUAL((Plan *) join)); } /* @@ -2214,13 +2380,15 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) outer_itlist, inner_itlist, (Index) 0, - rtoffset); + rtoffset, + NUM_EXEC_TLIST((Plan *) join)); join->plan.qual = fix_join_expr(root, join->plan.qual, outer_itlist, inner_itlist, (Index) 0, - rtoffset); + rtoffset, + NUM_EXEC_QUAL((Plan *) join)); pfree(outer_itlist); pfree(inner_itlist); @@ -2273,14 +2441,16 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset) (Node *) tle->expr, subplan_itlist, OUTER_VAR, - rtoffset); + rtoffset, + NUM_EXEC_TLIST(plan)); } else newexpr = fix_upper_expr(root, (Node *) tle->expr, subplan_itlist, OUTER_VAR, - rtoffset); + rtoffset, + NUM_EXEC_TLIST(plan)); tle = flatCopyTargetEntry(tle); tle->expr = (Expr *) newexpr; output_targetlist = lappend(output_targetlist, tle); @@ -2292,7 +2462,8 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset) (Node *) plan->qual, subplan_itlist, OUTER_VAR, - rtoffset); + rtoffset, + NUM_EXEC_QUAL(plan)); pfree(subplan_itlist); } @@ -2850,6 +3021,7 @@ search_indexed_tlist_for_sortgroupref(Expr *node, * 'acceptable_rel' is either zero or the rangetable index of a relation * whose Vars may appear in the clause without provoking an error * 'rtoffset': how much to increment varnos by + * 'num_exec': estimated number of executions of expression * * Returns the new expression tree. The original clause structure is * not modified. @@ -2860,7 +3032,8 @@ fix_join_expr(PlannerInfo *root, indexed_tlist *outer_itlist, indexed_tlist *inner_itlist, Index acceptable_rel, - int rtoffset) + int rtoffset, + double num_exec) { fix_join_expr_context context; @@ -2871,6 +3044,7 @@ fix_join_expr(PlannerInfo *root, context.rtoffset = rtoffset; context.use_outer_tlist_for_matching_nonvars = true; context.use_inner_tlist_for_matching_nonvars = true; + context.num_exec = num_exec; return (List *) fix_join_expr_mutator((Node *) clauses, &context); } @@ -2886,7 +3060,9 @@ static List *fix_hashclauses(PlannerInfo *root, List *clauses, indexed_tlist *outer_itlist, indexed_tlist *inner_itlist, - Index acceptable_rel, int rtoffset) + Index acceptable_rel, + int rtoffset, + double num_exec) { Assert(clauses); ListCell *lc = NULL; @@ -2911,7 +3087,8 @@ static List *fix_hashclauses(PlannerInfo *root, inner_itlist, (Index) 0, rtoffset, - OUTER_VAR); + OUTER_VAR, + num_exec); /* * for inner argument, we cannot refer to target entries * in join's outer child target list, otherwise hash table @@ -2924,7 +3101,8 @@ static List *fix_hashclauses(PlannerInfo *root, inner_itlist, (Index) 0, rtoffset, - INNER_VAR); + INNER_VAR, + num_exec); new_args = lappend(new_args, new_outer_arg); new_args = lappend(new_args, new_inner_arg); /* replace old arguments with the fixed arguments */ @@ -2957,7 +3135,8 @@ fix_child_hashclauses(PlannerInfo *root, indexed_tlist *inner_itlist, Index acceptable_rel, int rtoffset, - Index child) + Index child, + double num_exec) { fix_join_expr_context context; context.root = root; @@ -2965,6 +3144,7 @@ fix_child_hashclauses(PlannerInfo *root, context.inner_itlist = inner_itlist; context.acceptable_rel = acceptable_rel; context.rtoffset = rtoffset; + context.num_exec = num_exec; if (INNER_VAR == child) { /* skips using outer target list when matching non-vars */ @@ -3074,6 +3254,11 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context) /* Special cases (apply only AFTER failing to match to lower tlist) */ if (IsA(node, Param)) return fix_param_node(context->root, (Param *) node); + if (IsA(node, AlternativeSubPlan)) + return fix_join_expr_mutator(fix_alternative_subplan(context->root, + (AlternativeSubPlan *) node, + context->num_exec), + context); fix_expr_common(context->root, node); return expression_tree_mutator(node, fix_join_expr_mutator, @@ -3105,6 +3290,7 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context) * 'subplan_itlist': indexed target list for subplan (or index) * 'newvarno': varno to use for Vars referencing tlist elements * 'rtoffset': how much to increment varnos by + * 'num_exec': estimated number of executions of expression * * The resulting tree is a copy of the original in which all Var nodes have * varno = newvarno, varattno = resno of corresponding targetlist element. @@ -3115,7 +3301,8 @@ fix_upper_expr(PlannerInfo *root, Node *node, indexed_tlist *subplan_itlist, Index newvarno, - int rtoffset) + int rtoffset, + double num_exec) { fix_upper_expr_context context; @@ -3123,6 +3310,7 @@ fix_upper_expr(PlannerInfo *root, context.subplan_itlist = subplan_itlist; context.newvarno = newvarno; context.rtoffset = rtoffset; + context.num_exec = num_exec; return fix_upper_expr_mutator(node, &context); } @@ -3195,6 +3383,11 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context) } /* If no match, just fall through to process it normally */ } + if (IsA(node, AlternativeSubPlan)) + return fix_upper_expr_mutator(fix_alternative_subplan(context->root, + (AlternativeSubPlan *) node, + context->num_exec), + context); fix_expr_common(context->root, node); return expression_tree_mutator(node, fix_upper_expr_mutator, @@ -3259,7 +3452,8 @@ set_returning_clause_references(PlannerInfo *root, itlist, NULL, resultRelation, - rtoffset); + rtoffset, + NUM_EXEC_TLIST(topplan)); pfree(itlist); diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index d69075289810..461de6bad6ba 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -88,6 +88,7 @@ static List *generate_subquery_params(PlannerInfo *root, List *tlist, static Node *convert_testexpr_mutator(Node *node, convert_testexpr_context *context); static bool subplan_is_hashable(PlannerInfo *root, Plan *plan); +static bool subpath_is_hashable(PlannerInfo *root, Path *path); static bool testexpr_is_hashable(Node *testexpr, List *param_ids); static bool test_opexpr_is_hashable(OpExpr *testexpr, List *param_ids); static bool hash_ok_operator(OpExpr *expr); @@ -442,7 +443,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, * likely to be better (it depends on the expected number of executions of * the EXISTS qual, and we are much too early in planning the outer query * to be able to guess that). So we generate both plans, if possible, and - * leave it to the executor to decide which to use. + * leave it to setrefs.c to decide which to use. */ if (simple_exists && IsA(result, SubPlan)) { @@ -468,25 +469,26 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, plan_params = root->plan_params; root->plan_params = NIL; - /* Select best Path and turn it into a Plan */ + /* Select best Path */ final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL); best_path = final_rel->cheapest_total_path; - subroot->curSlice = palloc0(sizeof(PlanSlice)); - subroot->curSlice->gangType = GANGTYPE_UNALLOCATED; - - plan = create_plan(subroot, best_path, subroot->curSlice); - /* Decorate the top node of the plan with a Flow node. */ - plan->flow = cdbpathtoplan_create_flow(subroot, best_path->locus); - /* Now we can check if it'll fit in hash_mem */ - /* XXX can we check this at the Path stage? */ - if (subplan_is_hashable(root, plan)) + if (subpath_is_hashable(root, best_path)) { SubPlan *hashplan; AlternativeSubPlan *asplan; - /* OK, convert to SubPlan format. */ + subroot->curSlice = palloc0(sizeof(PlanSlice)); + subroot->curSlice->gangType = GANGTYPE_UNALLOCATED; + + /* OK, finish planning the ANY subquery */ + plan = create_plan(subroot, best_path, subroot->curSlice); + /* Decorate the top node of the plan with a Flow node. */ + plan->flow = cdbpathtoplan_create_flow(subroot, + best_path->locus); + + /* ... and convert to SubPlan format */ hashplan = castNode(SubPlan, build_subplan(root, plan, subroot, plan_params, @@ -498,10 +500,11 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, Assert(hashplan->parParam == NIL); Assert(hashplan->useHashTable); - /* Leave it to the executor to decide which plan to use */ + /* Leave it to setrefs.c to decide which plan to use */ asplan = makeNode(AlternativeSubPlan); asplan->subplans = list_make2(result, hashplan); result = (Node *) asplan; + root->hasAlternativeSubPlans = true; } } } @@ -941,6 +944,9 @@ convert_testexpr_mutator(Node *node, /* * subplan_is_hashable: can we implement an ANY subplan by hashing? + * + * This is not responsible for checking whether the combining testexpr + * is suitable for hashing. We only look at the subquery itself. */ static bool subplan_is_hashable(PlannerInfo *root, Plan *plan) @@ -961,6 +967,30 @@ subplan_is_hashable(PlannerInfo *root, Plan *plan) return true; } +/* + * subpath_is_hashable: can we implement an ANY subplan by hashing? + * + * Identical to subplan_is_hashable, but work from a Path for the subplan. + */ +static bool +subpath_is_hashable(PlannerInfo *root, Path *path) +{ + double subquery_size; + + /* + * The estimated size of the subquery result must fit in hash_mem. (Note: + * we use heap tuple overhead here even though the tuples will actually be + * stored as MinimalTuples; this provides some fudge factor for hashtable + * overhead.) + */ + subquery_size = path->rows * + (MAXALIGN(path->pathtarget->width) + MAXALIGN(SizeofHeapTupleHeader)); + if (subquery_size > global_work_mem(root)) + return false; + + return true; +} + /* * testexpr_is_hashable: is an ANY SubLink's test expression hashable? * diff --git a/src/backend/optimizer/plan/transform.c b/src/backend/optimizer/plan/transform.c index bedf15703ff7..b97e3163bb71 100644 --- a/src/backend/optimizer/plan/transform.c +++ b/src/backend/optimizer/plan/transform.c @@ -213,7 +213,8 @@ is_sirv_funcexpr(FuncExpr *fe) if (fe->funcresulttype == RECORDOID) return false; /* Record types cannot be handled currently */ - if (fe->funcid == F_NEXTVAL_OID || fe->funcid == F_CURRVAL_OID || fe-> funcid == F_SETVAL_OID) + if (fe->funcid == F_NEXTVAL || fe->funcid == F_CURRVAL || + fe->funcid == F_SETVAL_REGCLASS_INT8) return false; /* Function cannot be sequence related */ return true; diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index ad70005c9dbb..06536e05e919 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -857,7 +857,7 @@ contain_volatile_functions_not_nextval(Node *clause) static bool contain_volatile_functions_not_nextval_checker(Oid func_id, void *context) { - return (func_id != F_NEXTVAL_OID && + return (func_id != F_NEXTVAL && func_volatile(func_id) == PROVOLATILE_VOLATILE); } @@ -4901,7 +4901,8 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid, * needed; that's probably not important, but let's be careful. */ querytree_list = list_make1(querytree); - if (check_sql_fn_retval(querytree_list, result_type, rettupdesc, + if (check_sql_fn_retval(list_make1(querytree_list), + result_type, rettupdesc, false, NULL)) goto fail; /* reject whole-tuple-result cases */ @@ -5419,7 +5420,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) * shows it's returning a whole tuple result; otherwise what it's * returning is a single composite column which is not what we need. */ - if (!check_sql_fn_retval(querytree_list, + if (!check_sql_fn_retval(list_make1(querytree_list), fexpr->funcresulttype, rettupdesc, true, NULL) && (functypclass == TYPEFUNC_COMPOSITE || @@ -5431,7 +5432,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) * check_sql_fn_retval might've inserted a projection step, but that's * fine; just make sure we use the upper Query. */ - querytree = linitial(querytree_list); + querytree = linitial_node(Query, querytree_list); /* * Looks good --- substitute parameters into the query. diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 769f822d955c..c4d9bcea335f 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -5293,15 +5293,18 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel, if (lc == list_head(subpaths)) /* first node? */ pathnode->path.startup_cost = subpath->startup_cost; pathnode->path.total_cost += subpath->total_cost; - pathnode->path.rows += subpath->rows; - total_size += subpath->pathtarget->width * subpath->rows; + if (returningLists != NIL) + { + pathnode->path.rows += subpath->rows; + total_size += subpath->pathtarget->width * subpath->rows; + } } /* * Set width to the average width of the subpath outputs. XXX this is - * totally wrong: we should report zero if no RETURNING, else an average - * of the RETURNING tlist widths. But it's what happened historically, - * and improving it is a task for another day. + * totally wrong: we should return an average of the RETURNING tlist + * widths. But it's what happened historically, and improving it is a task + * for another day. */ if (pathnode->path.rows > 0) total_size /= pathnode->path.rows; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index dc017bca4f60..ed0d9760cc92 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -30,6 +30,7 @@ #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/heap.h" +#include "catalog/index.h" #include "catalog/pg_am.h" #include "catalog/pg_proc.h" #include "catalog/pg_statistic_ext.h" @@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, indexRelation = index_open(indexoid, lmode); index = indexRelation->rd_index; + /* Warn if any dependent collations' versions have moved. */ + if (!IsSystemRelation(relation) && + !indexRelation->rd_version_checked) + { + index_check_collation_versions(indexoid); + indexRelation->rd_version_checked = true; + } + /* * Ignore invalid indexes, since they can't safely be used for * queries. Note that this is OK because the data structure we @@ -672,7 +681,7 @@ cdb_estimate_partitioned_numtuples(Relation rel) childtuples = childrel->rd_rel->reltuples; - if (gp_enable_relsize_collection && childtuples == 0) + if (gp_enable_relsize_collection && childtuples <= 0) { RelOptInfo *dummy_reloptinfo; BlockNumber numpages; @@ -689,11 +698,18 @@ cdb_estimate_partitioned_numtuples(Relation rel) &allvisfrac); pfree(dummy_reloptinfo); } - if (childtuples == 0 && rel_is_external_table(RelationGetRelid(childrel))) + if (childtuples <= 0 && rel_is_external_table(RelationGetRelid(childrel))) { childtuples = DEFAULT_EXTERNAL_TABLE_TUPLES; } - totaltuples += childtuples; + + + /* + * reltuples of -1 indicates the relation was never analyzed. + * Treat this the same way as an empty relation. + */ + if (childtuples > 0) + totaltuples += childtuples; if (childrel != rel) table_close(childrel, NoLock); @@ -794,9 +810,11 @@ get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel, memcpy(info->conpfeqop, cachedfk->conpfeqop, sizeof(info->conpfeqop)); /* zero out fields to be filled by match_foreign_keys_to_quals */ info->nmatched_ec = 0; + info->nconst_ec = 0; info->nmatched_rcols = 0; info->nmatched_ri = 0; memset(info->eclass, 0, sizeof(info->eclass)); + memset(info->fk_eclass_member, 0, sizeof(info->fk_eclass_member)); memset(info->rinfos, 0, sizeof(info->rinfos)); root->fkey_list = lappend(root->fkey_list, info); @@ -1204,11 +1222,6 @@ estimate_rel_size(Relation rel, int32 *attr_widths, /* it has storage, ok to call the smgr */ curpages = RelationGetNumberOfBlocks(rel); - /* coerce values in pg_class to more desirable types */ - relpages = (BlockNumber) rel->rd_rel->relpages; - reltuples = (double) rel->rd_rel->reltuples; - relallvisible = (BlockNumber) rel->rd_rel->relallvisible; - /* report estimated # pages */ *pages = curpages; /* quick exit if rel is clearly empty */ @@ -1218,6 +1231,7 @@ estimate_rel_size(Relation rel, int32 *attr_widths, *allvisfrac = 0; break; } + /* coerce values in pg_class to more desirable types */ relpages = (BlockNumber) rel->rd_rel->relpages; reltuples = (double) rel->rd_rel->reltuples; @@ -1236,12 +1250,12 @@ estimate_rel_size(Relation rel, int32 *attr_widths, } /* estimate number of tuples from previous tuple density */ - if (relpages > 0) + if (reltuples >= 0 && relpages > 0) density = reltuples / (double) relpages; else { /* - * When we have no data because the relation was truncated, + * If we have no data because the relation was never vacuumed, * estimate tuple width from attribute datatypes. We assume * here that the pages are completely full, which is OK for * tables (since they've presumably not been VACUUMed yet) but @@ -1289,6 +1303,7 @@ estimate_rel_size(Relation rel, int32 *attr_widths, break; case RELKIND_FOREIGN_TABLE: /* Just use whatever's in pg_class */ + /* Note that FDW must cope if reltuples is -1! */ *pages = rel->rd_rel->relpages; *tuples = rel->rd_rel->reltuples; *allvisfrac = 0; diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 9a682275f4a8..686772256c43 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -262,7 +262,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->all_partrels = NULL; rel->partexprs = NULL; rel->nullable_partexprs = NULL; - rel->partitioned_child_rels = NIL; /* * Pass assorted information down the inheritance hierarchy. @@ -710,7 +709,6 @@ build_join_rel(PlannerInfo *root, joinrel->all_partrels = NULL; joinrel->partexprs = NULL; joinrel->nullable_partexprs = NULL; - joinrel->partitioned_child_rels = NIL; /* Compute information relevant to the foreign relations. */ set_foreign_rel_properties(joinrel, outer_rel, inner_rel); @@ -895,7 +893,6 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, joinrel->all_partrels = NULL; joinrel->partexprs = NULL; joinrel->nullable_partexprs = NULL; - joinrel->partitioned_child_rels = NIL; joinrel->top_parent_relids = bms_union(outer_rel->top_parent_relids, inner_rel->top_parent_relids); diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 5c263161cf94..fc44c11c7824 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -1585,9 +1585,8 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) for (i = 0; i < sublist_length; i++) { Oid coltype; - int32 coltypmod = -1; + int32 coltypmod; Oid colcoll; - bool first = true; coltype = select_common_type(pstate, colexprs[i], "VALUES", NULL); @@ -1597,19 +1596,9 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) col = coerce_to_common_type(pstate, col, coltype, "VALUES"); lfirst(lc) = (void *) col; - if (first) - { - coltypmod = exprTypmod(col); - first = false; - } - else - { - /* As soon as we see a non-matching typmod, fall back to -1 */ - if (coltypmod >= 0 && coltypmod != exprTypmod(col)) - coltypmod = -1; - } } + coltypmod = select_common_typmod(pstate, colexprs[i], coltype); colcoll = select_common_collation(pstate, colexprs[i], true); coltypes = lappend_oid(coltypes, coltype); @@ -2455,8 +2444,6 @@ coerceSetOpTypes(ParseState *pstate, Node *sop, Node *rcolnode = (Node *) rtle->expr; Oid lcoltype = exprType(lcolnode); Oid rcoltype = exprType(rcolnode); - int32 lcoltypmod = exprTypmod(lcolnode); - int32 rcoltypmod = exprTypmod(rcolnode); Node *bestexpr = NULL; int bestlocation; Oid rescoltype = pct ? lfirst_oid(pct) : InvalidOid; @@ -2476,9 +2463,6 @@ coerceSetOpTypes(ParseState *pstate, Node *sop, context, &bestexpr); bestlocation = exprLocation(bestexpr); - /* if same type and same typmod, use typmod; else default */ - if (lcoltype == rcoltype && lcoltypmod == rcoltypmod) - rescoltypmod = lcoltypmod; } else { @@ -2539,6 +2523,10 @@ coerceSetOpTypes(ParseState *pstate, Node *sop, rtle->expr = (Expr *) rcolnode; } + rescoltypmod = select_common_typmod(pstate, + list_make2(lcolnode, rcolnode), + rescoltype); + /* * Select common collation. A common collation is required for * all set operators except UNION ALL; see SQL:2008 7.13 p_target_relation->rd_att; tlist = transformTargetList(pstate, origTlist, EXPR_KIND_UPDATE_SOURCE); @@ -2823,41 +2810,9 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist) if (orig_tl != NULL) elog(ERROR, "UPDATE target count mismatch --- internal error"); - fill_extraUpdatedCols(target_rte, tupdesc); - return tlist; } -/* - * Record in extraUpdatedCols generated columns referencing updated base - * columns. - */ -void -fill_extraUpdatedCols(RangeTblEntry *target_rte, TupleDesc tupdesc) -{ - if (tupdesc->constr && - tupdesc->constr->has_generated_stored) - { - for (int i = 0; i < tupdesc->constr->num_defval; i++) - { - AttrDefault defval = tupdesc->constr->defval[i]; - Node *expr; - Bitmapset *attrs_used = NULL; - - /* skip if not generated column */ - if (!TupleDescAttr(tupdesc, defval.adnum - 1)->attgenerated) - continue; - - expr = stringToNode(defval.adbin); - pull_varattnos(expr, 1, &attrs_used); - - if (bms_overlap(target_rte->updatedCols, attrs_used)) - target_rte->extraUpdatedCols = bms_add_member(target_rte->extraUpdatedCols, - defval.adnum - FirstLowInvalidHeapAttributeNumber); - } - } -} - /* * transformReturningList - * handle a RETURNING clause in INSERT/UPDATE/DELETE diff --git a/src/backend/parser/check_keywords.pl b/src/backend/parser/check_keywords.pl index 702c97bba2aa..e6c6c98fb5ec 100644 --- a/src/backend/parser/check_keywords.pl +++ b/src/backend/parser/check_keywords.pl @@ -6,8 +6,8 @@ # src/backend/parser/check_keywords.pl # Copyright (c) 2009-2020, PostgreSQL Global Development Group -use warnings; use strict; +use warnings; my $gram_filename = $ARGV[0]; my $kwlist_filename = $ARGV[1]; @@ -21,6 +21,28 @@ sub error return; } +# Check alphabetical order of a set of keyword symbols +# (note these are NOT the actual keyword strings) +sub check_alphabetical_order +{ + my ($listname, $list) = @_; + my $prevkword = ''; + + foreach my $kword (@$list) + { + # Some symbols have a _P suffix. Remove it for the comparison. + my $bare_kword = $kword; + $bare_kword =~ s/_P$//; + if ($bare_kword le $prevkword) + { + error + "'$bare_kword' after '$prevkword' in $listname list is misplaced"; + } + $prevkword = $bare_kword; + } + return; +} + $, = ' '; # set output field separator $\ = "\n"; # set output record separator @@ -33,9 +55,11 @@ sub error open(my $gram, '<', $gram_filename) || die("Could not open : $gram_filename"); my $kcat; +my $in_bare_labels; my $comment; my @arr; my %keywords; +my @bare_label_keywords; line: while (my $S = <$gram>) { @@ -51,7 +75,7 @@ sub error $s = '[/][*]', $S =~ s#$s# /* #g; $s = '[*][/]', $S =~ s#$s# */ #g; - if (!($kcat)) + if (!($kcat) && !($in_bare_labels)) { # Is this the beginning of a keyword list? @@ -63,6 +87,10 @@ sub error next line; } } + + # Is this the beginning of the bare_label_keyword list? + $in_bare_labels = 1 if ($S =~ m/^bare_label_keyword:/); + next line; } @@ -97,7 +125,8 @@ sub error { # end of keyword list - $kcat = ''; + undef $kcat; + undef $in_bare_labels; next; } @@ -107,31 +136,21 @@ sub error } # Put this keyword into the right list - push @{ $keywords{$kcat} }, $arr[$fieldIndexer]; + if ($in_bare_labels) + { + push @bare_label_keywords, $arr[$fieldIndexer]; + } + else + { + push @{ $keywords{$kcat} }, $arr[$fieldIndexer]; + } } } close $gram; # Check that each keyword list is in alphabetical order (just for neatnik-ism) -my ($prevkword, $bare_kword); -foreach my $kcat (keys %keyword_categories) -{ - $prevkword = ''; - - foreach my $kword (@{ $keywords{$kcat} }) - { - - # Some keyword have a _P suffix. Remove it for the comparison. - $bare_kword = $kword; - $bare_kword =~ s/_P$//; - if ($bare_kword le $prevkword) - { - error - "'$bare_kword' after '$prevkword' in $kcat list is misplaced"; - } - $prevkword = $bare_kword; - } -} +check_alphabetical_order($_, $keywords{$_}) for (keys %keyword_categories); +check_alphabetical_order('bare_label_keyword', \@bare_label_keywords); # Transform the keyword lists into hashes. # kwhashes is a hash of hashes, keyed by keyword category id, @@ -147,6 +166,7 @@ sub error $kwhashes{$kcat_id} = $hash; } +my %bare_label_keywords = map { $_ => 1 } @bare_label_keywords; # Now read in kwlist.h @@ -160,11 +180,12 @@ sub error { my ($line) = $_; - if ($line =~ /^PG_KEYWORD\(\"(.*)\", (.*), (.*)\)/) + if ($line =~ /^PG_KEYWORD\(\"(.*)\", (.*), (.*), (.*)\)/) { my ($kwstring) = $1; my ($kwname) = $2; my ($kwcat_id) = $3; + my ($collabel) = $4; # Check that the list is in alphabetical order (critical!) if ($kwstring le $prevkwstring) @@ -197,7 +218,7 @@ sub error "keyword name '$kwname' doesn't match keyword string '$kwstring'"; } - # Check that the keyword is present in the grammar + # Check that the keyword is present in the right category list %kwhash = %{ $kwhashes{$kwcat_id} }; if (!(%kwhash)) @@ -219,6 +240,29 @@ sub error delete $kwhashes{$kwcat_id}->{$kwname}; } } + + # Check that the keyword's collabel property matches gram.y + if ($collabel eq 'BARE_LABEL') + { + unless ($bare_label_keywords{$kwname}) + { + error + "'$kwname' is marked as BARE_LABEL in kwlist.h, but it is missing from gram.y's bare_label_keyword rule"; + } + } + elsif ($collabel eq 'AS_LABEL') + { + if ($bare_label_keywords{$kwname}) + { + error + "'$kwname' is marked as AS_LABEL in kwlist.h, but it is listed in gram.y's bare_label_keyword rule"; + } + } + else + { + error + "'$collabel' not recognized in kwlist.h. Expected either 'BARE_LABEL' or 'AS_LABEL'"; + } } } close $kwlist; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 9b3d2eec1281..08f0f59a473a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -173,7 +173,7 @@ static RoleSpec *makeRoleSpec(RoleSpecType type, int location); static void check_qualified_name(List *names, core_yyscan_t yyscanner); static List *check_func_name(List *names, core_yyscan_t yyscanner); static List *check_indirection(List *indirection, core_yyscan_t yyscanner); -static List *extractArgTypes(List *parameters); +static List *extractArgTypes(ObjectType objtype, List *parameters); static List *extractAggrArgTypes(List *aggrargs); static List *makeOrderedSetArgs(List *directargs, List *orderedargs, core_yyscan_t yyscanner); @@ -268,7 +268,7 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_ } %type stmt schema_stmt - AlterEventTrigStmt AlterCollationStmt + AlterEventTrigStmt AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt AlterObjectDependsStmt AlterObjectSchemaStmt AlterOwnerStmt @@ -419,8 +419,8 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_ %type privilege %type privileges privilege_list %type privilege_target -%type function_with_argtypes aggregate_with_argtypes operator_with_argtypes -%type function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list +%type function_with_argtypes aggregate_with_argtypes operator_with_argtypes procedure_with_argtypes function_with_argtypes_common +%type function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list procedure_with_argtypes_list %type defacl_privilege_target %type DefACLOption %type DefACLOptionList @@ -601,17 +601,18 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_ %type RoleId opt_boolean_or_string %type QueueId %type var_list -%type ColId ColLabel ColLabelNoAs var_name type_function_name param_name +%type ColId ColLabel BareColLabel %type PartitionIdentKeyword %type PartitionColId %type NonReservedWord NonReservedWord_or_Sconst +%type var_name type_function_name param_name %type createdb_opt_name %type var_value zone_value %type auth_ident RoleSpec opt_granted_by %type unreserved_keyword type_func_name_keyword %type col_name_keyword reserved_keyword -%type keywords_ok_in_alias_no_as +%type bare_label_keyword %type TableConstraint TableLikeClause %type TableLikeOptionList TableLikeOption @@ -883,24 +884,16 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_ %nonassoc '<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS %nonassoc BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA %nonassoc ESCAPE /* ESCAPE must be just above LIKE/ILIKE/SIMILAR */ -%left POSTFIXOP /* dummy for postfix Op rules */ /* - * To support target_el without AS, we must give IDENT an explicit priority - * between POSTFIXOP and Op. We can safely assign the same priority to - * various unreserved keywords as needed to resolve ambiguities (this can't - * have any bad effects since obviously the keywords will still behave the - * same as if they weren't keywords). We need to do this: - * for PARTITION, RANGE, ROWS, GROUPS to support opt_existing_window_name; - * for RANGE, ROWS, GROUPS so that they can follow a_expr without creating - * postfix-operator problems; - * for GENERATED so that it can follow b_expr; - * and for NULL so that it can follow b_expr in ColQualList without creating - * postfix-operator problems. + * To support target_el without AS, it used to be necessary to assign IDENT an + * explicit precedence just less than Op. While that's not really necessary + * since we removed postfix operators, it's still helpful to do so because + * there are some other unreserved keywords that need precedence assignments. + * If those keywords have the same precedence as IDENT then they clearly act + * the same as non-keywords, reducing the risk of unwanted precedence effects. * - * To support CUBE and ROLLUP in GROUP BY without reserving them, we give them - * an explicit priority lower than '(', so that a rule with CUBE '(' will shift - * rather than reducing a conflicting rule that takes CUBE as a function name. - * Using the same precedence as IDENT seems right for the reasons given above. + * We need to do this for PARTITION, RANGE, ROWS, and GROUPS to support + * opt_existing_window_name (see comment there). * * The frame_bound productions UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING * are even messier: since UNBOUNDED is an unreserved keyword (per spec!), @@ -910,9 +903,14 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_ * appear to cause UNBOUNDED to be treated differently from other unreserved * keywords anywhere else in the grammar, but it's definitely risky. We can * blame any funny behavior of UNBOUNDED on the SQL standard, though. + * + * To support CUBE and ROLLUP in GROUP BY without reserving them, we give them + * an explicit priority lower than '(', so that a rule with CUBE '(' will shift + * rather than reducing a conflicting rule that takes CUBE as a function name. + * Using the same precedence as IDENT seems right for the reasons given above. */ -%nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */ -%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP +%nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */ +%nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP /* * This is a bit ugly... To allow these to be column aliases without @@ -1240,8 +1238,6 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_ * left-associativity among the JOIN rules themselves. */ %left JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL -/* kluge to keep xml_whitespace_option from causing shift/reduce conflicts */ -%right PRESERVE STRIP_P %% @@ -1287,7 +1283,6 @@ stmtmulti: stmtmulti ';' stmt stmt : AlterEventTrigStmt - | AlterCollationStmt | AlterDatabaseStmt | AlterDatabaseSetStmt | AlterDefaultPrivilegesStmt @@ -3460,6 +3455,14 @@ alter_table_cmd: n->subtype = AT_NoForceRowSecurity; $$ = (Node *)n; } + /* ALTER INDEX ALTER COLLATION ... REFRESH VERSION */ + | ALTER COLLATION any_name REFRESH VERSION_P + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_AlterCollationRefreshVersion; + n->object = $3; + $$ = (Node *)n; + } | alter_generic_options { AlterTableCmd *n = makeNode(AlterTableCmd); @@ -7120,7 +7123,7 @@ AlterExtensionContentsStmt: n->object = (Node *) lcons(makeString($9), $7); $$ = (Node *)n; } - | ALTER EXTENSION name add_drop PROCEDURE function_with_argtypes + | ALTER EXTENSION name add_drop PROCEDURE procedure_with_argtypes { AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt); n->extname = $3; @@ -7129,7 +7132,7 @@ AlterExtensionContentsStmt: n->object = (Node *) $6; $$ = (Node *)n; } - | ALTER EXTENSION name add_drop ROUTINE function_with_argtypes + | ALTER EXTENSION name add_drop ROUTINE procedure_with_argtypes { AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt); n->extname = $3; @@ -8904,7 +8907,7 @@ CommentStmt: n->comment = $8; $$ = (Node *) n; } - | COMMENT ON PROCEDURE function_with_argtypes IS comment_text + | COMMENT ON PROCEDURE procedure_with_argtypes IS comment_text { CommentStmt *n = makeNode(CommentStmt); n->objtype = OBJECT_PROCEDURE; @@ -8912,7 +8915,7 @@ CommentStmt: n->comment = $6; $$ = (Node *) n; } - | COMMENT ON ROUTINE function_with_argtypes IS comment_text + | COMMENT ON ROUTINE procedure_with_argtypes IS comment_text { CommentStmt *n = makeNode(CommentStmt); n->objtype = OBJECT_ROUTINE; @@ -9058,7 +9061,7 @@ SecLabelStmt: n->label = $9; $$ = (Node *) n; } - | SECURITY LABEL opt_provider ON PROCEDURE function_with_argtypes + | SECURITY LABEL opt_provider ON PROCEDURE procedure_with_argtypes IS security_label { SecLabelStmt *n = makeNode(SecLabelStmt); @@ -9419,7 +9422,7 @@ privilege_target: n->objs = $2; $$ = n; } - | PROCEDURE function_with_argtypes_list + | PROCEDURE procedure_with_argtypes_list { PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); n->targtype = ACL_TARGET_OBJECT; @@ -9427,7 +9430,7 @@ privilege_target: n->objs = $2; $$ = n; } - | ROUTINE function_with_argtypes_list + | ROUTINE procedure_with_argtypes_list { PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); n->targtype = ACL_TARGET_OBJECT; @@ -9974,20 +9977,33 @@ function_with_argtypes_list: { $$ = lappend($1, $3); } ; +procedure_with_argtypes_list: + procedure_with_argtypes { $$ = list_make1($1); } + | procedure_with_argtypes_list ',' procedure_with_argtypes + { $$ = lappend($1, $3); } + ; + function_with_argtypes: func_name func_args { ObjectWithArgs *n = makeNode(ObjectWithArgs); n->objname = $1; - n->objargs = extractArgTypes($2); + n->objargs = extractArgTypes(OBJECT_FUNCTION, $2); $$ = n; } + | function_with_argtypes_common + { + $$ = $1; + } + ; + +function_with_argtypes_common: /* * Because of reduce/reduce conflicts, we can't use func_name * below, but we can write it out the long way, which actually * allows more cases. */ - | type_func_name_keyword + type_func_name_keyword { ObjectWithArgs *n = makeNode(ObjectWithArgs); n->objname = list_make1(makeString(pstrdup($1))); @@ -10011,6 +10027,24 @@ function_with_argtypes: } ; +/* + * This is different from function_with_argtypes in the call to + * extractArgTypes(). + */ +procedure_with_argtypes: + func_name func_args + { + ObjectWithArgs *n = makeNode(ObjectWithArgs); + n->objname = $1; + n->objargs = extractArgTypes(OBJECT_PROCEDURE, $2); + $$ = n; + } + | function_with_argtypes_common + { + $$ = $1; + } + ; + /* * func_args_with_defaults is separate because we only want to accept * defaults in CREATE FUNCTION, not in ALTER etc. @@ -10425,7 +10459,7 @@ AlterFunctionStmt: n->actions = $4; $$ = (Node *) n; } - | ALTER PROCEDURE function_with_argtypes alterfunc_opt_list opt_restrict + | ALTER PROCEDURE procedure_with_argtypes alterfunc_opt_list opt_restrict { AlterFunctionStmt *n = makeNode(AlterFunctionStmt); n->objtype = OBJECT_PROCEDURE; @@ -10433,7 +10467,7 @@ AlterFunctionStmt: n->actions = $4; $$ = (Node *) n; } - | ALTER ROUTINE function_with_argtypes alterfunc_opt_list opt_restrict + | ALTER ROUTINE procedure_with_argtypes alterfunc_opt_list opt_restrict { AlterFunctionStmt *n = makeNode(AlterFunctionStmt); n->objtype = OBJECT_ROUTINE; @@ -10489,7 +10523,7 @@ RemoveFuncStmt: n->concurrent = false; $$ = (Node *)n; } - | DROP PROCEDURE function_with_argtypes_list opt_drop_behavior + | DROP PROCEDURE procedure_with_argtypes_list opt_drop_behavior { DropStmt *n = makeNode(DropStmt); n->removeType = OBJECT_PROCEDURE; @@ -10499,7 +10533,7 @@ RemoveFuncStmt: n->concurrent = false; $$ = (Node *)n; } - | DROP PROCEDURE IF_P EXISTS function_with_argtypes_list opt_drop_behavior + | DROP PROCEDURE IF_P EXISTS procedure_with_argtypes_list opt_drop_behavior { DropStmt *n = makeNode(DropStmt); n->removeType = OBJECT_PROCEDURE; @@ -10509,7 +10543,7 @@ RemoveFuncStmt: n->concurrent = false; $$ = (Node *)n; } - | DROP ROUTINE function_with_argtypes_list opt_drop_behavior + | DROP ROUTINE procedure_with_argtypes_list opt_drop_behavior { DropStmt *n = makeNode(DropStmt); n->removeType = OBJECT_ROUTINE; @@ -10519,7 +10553,7 @@ RemoveFuncStmt: n->concurrent = false; $$ = (Node *)n; } - | DROP ROUTINE IF_P EXISTS function_with_argtypes_list opt_drop_behavior + | DROP ROUTINE IF_P EXISTS procedure_with_argtypes_list opt_drop_behavior { DropStmt *n = makeNode(DropStmt); n->removeType = OBJECT_ROUTINE; @@ -10775,12 +10809,11 @@ ReindexStmt: { ReindexStmt *n = makeNode(ReindexStmt); n->kind = $2; - n->concurrent = $3; n->relation = $4; n->name = NULL; n->options = 0; - if (n->concurrent) + if ($3) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("REINDEX CONCURRENTLY is not supported"))); @@ -10791,12 +10824,11 @@ ReindexStmt: { ReindexStmt *n = makeNode(ReindexStmt); n->kind = $2; - n->concurrent = $3; n->name = $4; n->relation = NULL; n->options = 0; - if (n->concurrent) + if ($3) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("REINDEX CONCURRENTLY is not supported"))); @@ -10807,12 +10839,11 @@ ReindexStmt: { ReindexStmt *n = makeNode(ReindexStmt); n->kind = $5; - n->concurrent = $6; n->relation = $7; n->name = NULL; n->options = $3; - if (n->concurrent) + if ($6) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("REINDEX CONCURRENTLY is not supported"))); @@ -10823,12 +10854,11 @@ ReindexStmt: { ReindexStmt *n = makeNode(ReindexStmt); n->kind = $5; - n->concurrent = $6; n->name = $7; n->relation = NULL; n->options = $3; - if (n->concurrent) + if ($6) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("REINDEX CONCURRENTLY is not supported"))); @@ -11029,7 +11059,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name n->missing_ok = true; $$ = (Node *)n; } - | ALTER PROCEDURE function_with_argtypes RENAME TO name + | ALTER PROCEDURE procedure_with_argtypes RENAME TO name { RenameStmt *n = makeNode(RenameStmt); n->renameType = OBJECT_PROCEDURE; @@ -11047,7 +11077,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name n->missing_ok = false; $$ = (Node *)n; } - | ALTER ROUTINE function_with_argtypes RENAME TO name + | ALTER ROUTINE procedure_with_argtypes RENAME TO name { RenameStmt *n = makeNode(RenameStmt); n->renameType = OBJECT_ROUTINE; @@ -11467,7 +11497,7 @@ AlterObjectDependsStmt: n->remove = $4; $$ = (Node *)n; } - | ALTER PROCEDURE function_with_argtypes opt_no DEPENDS ON EXTENSION name + | ALTER PROCEDURE procedure_with_argtypes opt_no DEPENDS ON EXTENSION name { AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt); n->objectType = OBJECT_PROCEDURE; @@ -11476,7 +11506,7 @@ AlterObjectDependsStmt: n->remove = $4; $$ = (Node *)n; } - | ALTER ROUTINE function_with_argtypes opt_no DEPENDS ON EXTENSION name + | ALTER ROUTINE procedure_with_argtypes opt_no DEPENDS ON EXTENSION name { AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt); n->objectType = OBJECT_ROUTINE; @@ -11607,7 +11637,7 @@ AlterObjectSchemaStmt: n->missing_ok = false; $$ = (Node *)n; } - | ALTER PROCEDURE function_with_argtypes SET SCHEMA name + | ALTER PROCEDURE procedure_with_argtypes SET SCHEMA name { AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); n->objectType = OBJECT_PROCEDURE; @@ -11616,7 +11646,7 @@ AlterObjectSchemaStmt: n->missing_ok = false; $$ = (Node *)n; } - | ALTER ROUTINE function_with_argtypes SET SCHEMA name + | ALTER ROUTINE procedure_with_argtypes SET SCHEMA name { AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); n->objectType = OBJECT_ROUTINE; @@ -11918,7 +11948,7 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec n->newowner = $9; $$ = (Node *)n; } - | ALTER PROCEDURE function_with_argtypes OWNER TO RoleSpec + | ALTER PROCEDURE procedure_with_argtypes OWNER TO RoleSpec { AlterOwnerStmt *n = makeNode(AlterOwnerStmt); n->objectType = OBJECT_PROCEDURE; @@ -11926,7 +11956,7 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec n->newowner = $6; $$ = (Node *)n; } - | ALTER ROUTINE function_with_argtypes OWNER TO RoleSpec + | ALTER ROUTINE procedure_with_argtypes OWNER TO RoleSpec { AlterOwnerStmt *n = makeNode(AlterOwnerStmt); n->objectType = OBJECT_ROUTINE; @@ -12791,21 +12821,6 @@ drop_option: } ; -/***************************************************************************** - * - * ALTER COLLATION - * - *****************************************************************************/ - -AlterCollationStmt: ALTER COLLATION any_name REFRESH VERSION_P - { - AlterCollationStmt *n = makeNode(AlterCollationStmt); - n->collname = $3; - $$ = (Node *)n; - } - ; - - /***************************************************************************** * * ALTER SYSTEM @@ -15776,8 +15791,6 @@ a_expr: c_expr { $$ = $1; } { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); } | qual_Op a_expr %prec Op { $$ = (Node *) makeA_Expr(AEXPR_OP, $1, NULL, $2, @1); } - | a_expr qual_Op %prec POSTFIXOP - { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, NULL, @2); } | a_expr AND a_expr { $$ = makeAndExpr($1, $3, @2); } @@ -16191,8 +16204,6 @@ b_expr: c_expr { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); } | qual_Op b_expr %prec Op { $$ = (Node *) makeA_Expr(AEXPR_OP, $1, NULL, $2, @1); } - | b_expr qual_Op %prec POSTFIXOP - { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, NULL, @2); } | b_expr IS DISTINCT FROM b_expr %prec IS { $$ = (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", $1, $5, @2); @@ -17598,22 +17609,8 @@ target_el: a_expr AS ColLabel * expression and a column label? We prefer to resolve this * as an infix expression, which we accomplish by assigning * IDENT a precedence higher than POSTFIXOP. - * - * In GPDB, we extend this to allow most unreserved_keywords by - * also assigning them a precedence. There are certain keywords - * that can't work without the as: reserved_keywords, the date - * modifier suffixes (DAY, MONTH, YEAR, etc) and a few other - * obscure cases. */ - | a_expr IDENT - { - $$ = makeNode(ResTarget); - $$->name = $2; - $$->indirection = NIL; - $$->val = (Node *)$1; - $$->location = @1; - } - | a_expr ColLabelNoAs + | a_expr BareColLabel { $$ = makeNode(ResTarget); $$->name = $2; @@ -17884,6 +17881,13 @@ RoleId: RoleSpec "CURRENT_USER"), parser_errposition(@1))); break; + case ROLESPEC_CURRENT_ROLE: + ereport(ERROR, + (errcode(ERRCODE_RESERVED_NAME), + errmsg("%s cannot be used as a role name here", + "CURRENT_ROLE"), + parser_errposition(@1))); + break; } } ; @@ -17915,6 +17919,10 @@ RoleSpec: NonReservedWord } $$ = n; } + | CURRENT_ROLE + { + $$ = makeRoleSpec(ROLESPEC_CURRENT_ROLE, @1); + } | CURRENT_USER { $$ = makeRoleSpec(ROLESPEC_CURRENT_USER, @1); @@ -17974,6 +17982,13 @@ ColLabel: IDENT { $$ = $1; } | reserved_keyword { $$ = pstrdup($1); } ; +/* Bare column label --- names that can be column labels without writing "AS". + * This classification is orthogonal to the other keyword categories. + */ +BareColLabel: IDENT { $$ = $1; } + | bare_label_keyword { $$ = pstrdup($1); } + ; + /* * Keyword category lists. Generally, every keyword present in @@ -18354,16 +18369,6 @@ unreserved_keyword: * the grammar. */ -ColLabelNoAs: keywords_ok_in_alias_no_as { $$=pstrdup($1); } - ; - -keywords_ok_in_alias_no_as: PartitionIdentKeyword - | TABLESPACE - | ADD_P - | ALTER - | AT - ; - PartitionColId: PartitionIdentKeyword { $$ = pstrdup($1); } | IDENT { $$ = pstrdup($1); } ; @@ -18846,182 +18851,651 @@ reserved_keyword: | WITH ; -%% - /* - * The signature of this function is required by bison. However, we - * ignore the passed yylloc and instead use the last token position - * available from the scanner. + * While all keywords can be used as column labels when preceded by AS, + * not all of them can be used as a "bare" column label without AS. + * Those that can be used as a bare label must be listed here, + * in addition to appearing in one of the category lists above. + * + * Always add a new keyword to this list if possible. Mark it BARE_LABEL + * in kwlist.h if it is included here, or AS_LABEL if it is not. */ -static void -base_yyerror(YYLTYPE *yylloc, core_yyscan_t yyscanner, const char *msg) -{ - parser_yyerror(msg); -} - -static RawStmt * -makeRawStmt(Node *stmt, int stmt_location) -{ - RawStmt *rs = makeNode(RawStmt); - - rs->stmt = stmt; - rs->stmt_location = stmt_location; - rs->stmt_len = 0; /* might get changed later */ - return rs; -} - -/* Adjust a RawStmt to reflect that it doesn't run to the end of the string */ -static void -updateRawStmtEnd(RawStmt *rs, int end_location) -{ - /* - * If we already set the length, don't change it. This is for situations - * like "select foo ;; select bar" where the same statement will be last - * in the string for more than one semicolon. - */ - if (rs->stmt_len > 0) - return; - - /* OK, update length of RawStmt */ - rs->stmt_len = end_location - rs->stmt_location; -} - -static Node * -makeColumnRef(char *colname, List *indirection, - int location, core_yyscan_t yyscanner) -{ - /* - * Generate a ColumnRef node, with an A_Indirection node added if there - * is any subscripting in the specified indirection list. However, - * any field selection at the start of the indirection list must be - * transposed into the "fields" part of the ColumnRef node. - */ - ColumnRef *c = makeNode(ColumnRef); - int nfields = 0; - ListCell *l; - - c->location = location; - foreach(l, indirection) - { - if (IsA(lfirst(l), A_Indices)) - { - A_Indirection *i = makeNode(A_Indirection); - - if (nfields == 0) - { - /* easy case - all indirection goes to A_Indirection */ - c->fields = list_make1(makeString(colname)); - i->indirection = check_indirection(indirection, yyscanner); - } - else - { - /* got to split the list in two */ - i->indirection = check_indirection(list_copy_tail(indirection, - nfields), - yyscanner); - indirection = list_truncate(indirection, nfields); - c->fields = lcons(makeString(colname), indirection); - } - i->arg = (Node *) c; - return (Node *) i; - } - else if (IsA(lfirst(l), A_Star)) - { - /* We only allow '*' at the end of a ColumnRef */ - if (lnext(indirection, l) != NULL) - parser_yyerror("improper use of \"*\""); - } - nfields++; - } - /* No subscripting, so all indirection gets added to field list */ - c->fields = lcons(makeString(colname), indirection); - return (Node *) c; -} - -static Node * -makeTypeCast(Node *arg, TypeName *typename, int location) -{ - TypeCast *n = makeNode(TypeCast); - n->arg = arg; - n->typeName = typename; - n->location = location; - return (Node *) n; -} - -static Node * -makeStringConst(char *str, int location) -{ - A_Const *n = makeNode(A_Const); - - n->val.type = T_String; - n->val.val.str = str; - n->location = location; - - return (Node *)n; -} - -static Node * -makeStringConstCast(char *str, int location, TypeName *typename) -{ - Node *s = makeStringConst(str, location); - - return makeTypeCast(s, typename, -1); -} - -static Node * -makeIntConst(int val, int location) -{ - A_Const *n = makeNode(A_Const); - - n->val.type = T_Integer; - n->val.val.ival = val; - n->location = location; - - return (Node *)n; -} - -static Node * -makeFloatConst(char *str, int location) -{ - A_Const *n = makeNode(A_Const); - - n->val.type = T_Float; - n->val.val.str = str; - n->location = location; - - return (Node *)n; -} - -static Node * -makeBitStringConst(char *str, int location) -{ - A_Const *n = makeNode(A_Const); - - n->val.type = T_BitString; - n->val.val.str = str; - n->location = location; - - return (Node *)n; -} - -static Node * -makeNullAConst(int location) -{ - A_Const *n = makeNode(A_Const); - - n->val.type = T_Null; - n->location = location; - - return (Node *)n; -} - -static Node * -makeAConst(Value *v, int location) -{ - Node *n; - - switch (v->type) - { +bare_label_keyword: + ABORT_P + | ABSOLUTE_P + | ACCESS + | ACTION + | ACTIVE + | ADD_P + | ADMIN + | AFTER + | AGGREGATE + | ALL + | ALSO + | ALTER + | ALWAYS + | ANALYSE + | ANALYZE + | AND + | ANY + | ASC + | ASSERTION + | ASSIGNMENT + | ASYMMETRIC + | AT + | ATTACH + | ATTRIBUTE + | AUTHORIZATION + | BACKWARD + | BEFORE + | BEGIN_P + | BETWEEN + | BIGINT + | BINARY + | BIT + | BOOLEAN_P + | BOTH + | BY + | CACHE + | CALL + | CALLED + | CASCADE + | CASCADED + | CASE + | CAST + | CATALOG_P + | CHAIN + | CHARACTERISTICS + | CHECK + | CHECKPOINT + | CLASS + | CLOSE + | CLUSTER + | COALESCE + | COLLATE + | COLLATION + | COLUMN + | COLUMNS + | COMMENT + | COMMENTS + | COMMIT + | COMMITTED + | CONCURRENCY + | CONCURRENTLY + | CONFIGURATION + | CONFLICT + | CONNECTION + | CONSTRAINT + | CONSTRAINTS + | CONTAINS + | CONTENT_P + | CONTINUE_P + | CONVERSION_P + | COPY + | COST + | CPUSET + | CPU_RATE_LIMIT + | CREATEEXTTABLE + | CROSS + | CSV + | CUBE + | CURRENT_P + | CURRENT_CATALOG + | CURRENT_DATE + | CURRENT_ROLE + | CURRENT_SCHEMA + | CURRENT_TIME + | CURRENT_TIMESTAMP + | CURRENT_USER + | CURSOR + | CYCLE + | DATA_P + | DATABASE + | DEALLOCATE + | DEC + | DECIMAL_P + | DECLARE + | DEFAULT + | DEFAULTS + | DEFERRABLE + | DEFERRED + | DEFINER + | DELETE_P + | DELIMITER + | DELIMITERS + | DEPENDS + | DESC + | DETACH + | DICTIONARY + | DISABLE_P + | DISCARD + | DISTINCT + | DO + | DOCUMENT_P + | DOMAIN_P + | DOUBLE_P + | DROP + | EACH + | ELSE + | ENABLE_P + | ENCODING + | ENCRYPTED + | END_P + | ENDPOINT + | ENUM_P + | ERRORS + | ESCAPE + | EVENT + | EXCHANGE + | EXCLUDE + | EXCLUDING + | EXCLUSIVE + | EXECUTE + | EXISTS + | EXPLAIN + | EXPRESSION + | EXTENSION + | EXTERNAL + | EXTRACT + | FALSE_P + | FAMILY + | FIELDS + | FILL + | FIRST_P + | FLOAT_P + | FOLLOWING + | FORCE + | FOREIGN + | FORMAT + | FORWARD + | FREEZE + | FULL + | FUNCTION + | FUNCTIONS + | GENERATED + | GLOBAL + | GRANTED + | GREATEST + | GROUPING + | GROUPS + | HANDLER + | HASH + | HEADER_P + | HOLD + | HOST + | IDENTITY_P + | IF_P + | ILIKE + | IMMEDIATE + | IMMUTABLE + | IMPLICIT_P + | IMPORT_P + | IN_P + | INCLUDE + | INCLUDING + | INCLUSIVE + | INCREMENT + | INDEX + | INDEXES + | INHERIT + | INHERITS + | INITIALLY + | INLINE_P + | INNER_P + | INOUT + | INPUT_P + | INSENSITIVE + | INSERT + | INSTEAD + | INT_P + | INTEGER + | INTERVAL + | INVOKER + | IS + | ISOLATION + | JOIN + | KEY + | LABEL + | LANGUAGE + | LARGE_P + | LAST_P + | LATERAL_P + | LEADING + | LEAKPROOF + | LEAST + | LEFT + | LEVEL + | LIKE + | LIST + | LISTEN + | LOAD + | LOCAL + | LOCALTIME + | LOCALTIMESTAMP + | LOCATION + | LOCK_P + | LOCKED + | LOG_P + | LOGGED + | MAPPING + | MASTER + | MATCH + | MATERIALIZED + | MAXVALUE + | MEDIAN + | MEMORY_LIMIT + | MEMORY_SHARED_QUOTA + | MEMORY_SPILL_RATIO + | METHOD + | MINVALUE + | MISSING + | MODE + | MODIFIES + | MOVE + | NAME_P + | NAMES + | NATIONAL + | NATURAL + | NCHAR + | NEW + | NEWLINE + | NEXT + | NFC + | NFD + | NFKC + | NFKD + | NO + | NOCREATEEXTTABLE + | NONE + | NOOVERCOMMIT + | NORMALIZE + | NORMALIZED + | NOT + | NOTHING + | NOTIFY + | NOWAIT + | NULL_P + | NULLIF + | NULLS_P + | NUMERIC + | OBJECT_P + | OF + | OFF + | OIDS + | OLD + | ONLY + | OPERATOR + | OPTION + | OPTIONS + | OR + | ORDINALITY + | OTHERS + | OUT_P + | OUTER_P + | OVERCOMMIT + | OVERLAY + | OVERRIDING + | OWNED + | OWNER + | PARALLEL + | PARSER + | PARTIAL + | PARTITIONS + | PASSING + | PASSWORD + | PERCENT + | PERSISTENTLY + | PLACING + | PLANS + | POLICY + | POSITION + | PRECEDING + | PREPARE + | PREPARED + | PRESERVE + | PRIMARY + | PRIOR + | PRIVILEGES + | PROCEDURAL + | PROCEDURE + | PROCEDURES + | PROGRAM + | PROTOCOL + | PUBLICATION + | QUEUE + | QUOTE + | RANDOMLY + | RANGE + | READ + | READABLE + | READS + | REAL + | REASSIGN + | RECHECK + | RECURSIVE + | REF + | REFERENCES + | REFERENCING + | REFRESH + | REINDEX + | REJECT_P + | RELATIVE_P + | RELEASE + | RENAME + | REPEATABLE + | REPLACE + | REPLICA + | RESET + | RESOURCE + | RESTART + | RESTRICT + | RETRIEVE + | RETURNS + | REVOKE + | RIGHT + | ROLE + | ROLLBACK + | ROLLUP + | ROUTINE + | ROUTINES + | ROW + | ROWS + | RULE + | SAVEPOINT + | SCHEMA + | SCHEMAS + | SCROLL + | SEARCH + | SECURITY + | SEGMENT + | SEGMENTS + | SELECT + | SEQUENCE + | SEQUENCES + | SERIALIZABLE + | SERVER + | SESSION + | SESSION_USER + | SET + | SETOF + | SETS + | SHARE + | SHOW + | SIMILAR + | SIMPLE + | SKIP + | SMALLINT + | SNAPSHOT + | SOME + | SPLIT + | SQL_P + | STABLE + | STANDALONE_P + | START + | STATEMENT + | STATISTICS + | STDIN + | STDOUT + | STORAGE + | STORED + | STRICT_P + | STRIP_P + | SUBPARTITION + | SUBSCRIPTION + | SUBSTRING + | SUPPORT + | SYMMETRIC + | SYSID + | SYSTEM_P + | TABLE + | TABLES + | TABLESAMPLE + | TABLESPACE + | TEMP + | TEMPLATE + | TEMPORARY + | TEXT_P + | THEN + | THRESHOLD + | TIES + | TIME + | TIMESTAMP + | TRAILING + | TRANSACTION + | TRANSFORM + | TREAT + | TRIGGER + | TRIM + | TRUE_P + | TRUNCATE + | TRUSTED + | TYPE_P + | TYPES_P + | UESCAPE + | UNBOUNDED + | UNCOMMITTED + | UNENCRYPTED + | UNIQUE + | UNKNOWN + | UNLISTEN + | UNLOGGED + | UNTIL + | UPDATE + | USER + | USING + | VACUUM + | VALID + | VALIDATE + | VALIDATION + | VALIDATOR + | VALUE_P + | VALUES + | VARCHAR + | VARIADIC + | VERBOSE + | VERSION_P + | VIEW + | VIEWS + | VOLATILE + | WEB + | WHEN + | WHITESPACE_P + | WORK + | WRAPPER + | WRITABLE + | WRITE + | XML_P + | XMLATTRIBUTES + | XMLCONCAT + | XMLELEMENT + | XMLEXISTS + | XMLFOREST + | XMLNAMESPACES + | XMLPARSE + | XMLPI + | XMLROOT + | XMLSERIALIZE + | XMLTABLE + | YES_P + | ZONE + ; + +%% + +/* + * The signature of this function is required by bison. However, we + * ignore the passed yylloc and instead use the last token position + * available from the scanner. + */ +static void +base_yyerror(YYLTYPE *yylloc, core_yyscan_t yyscanner, const char *msg) +{ + parser_yyerror(msg); +} + +static RawStmt * +makeRawStmt(Node *stmt, int stmt_location) +{ + RawStmt *rs = makeNode(RawStmt); + + rs->stmt = stmt; + rs->stmt_location = stmt_location; + rs->stmt_len = 0; /* might get changed later */ + return rs; +} + +/* Adjust a RawStmt to reflect that it doesn't run to the end of the string */ +static void +updateRawStmtEnd(RawStmt *rs, int end_location) +{ + /* + * If we already set the length, don't change it. This is for situations + * like "select foo ;; select bar" where the same statement will be last + * in the string for more than one semicolon. + */ + if (rs->stmt_len > 0) + return; + + /* OK, update length of RawStmt */ + rs->stmt_len = end_location - rs->stmt_location; +} + +static Node * +makeColumnRef(char *colname, List *indirection, + int location, core_yyscan_t yyscanner) +{ + /* + * Generate a ColumnRef node, with an A_Indirection node added if there + * is any subscripting in the specified indirection list. However, + * any field selection at the start of the indirection list must be + * transposed into the "fields" part of the ColumnRef node. + */ + ColumnRef *c = makeNode(ColumnRef); + int nfields = 0; + ListCell *l; + + c->location = location; + foreach(l, indirection) + { + if (IsA(lfirst(l), A_Indices)) + { + A_Indirection *i = makeNode(A_Indirection); + + if (nfields == 0) + { + /* easy case - all indirection goes to A_Indirection */ + c->fields = list_make1(makeString(colname)); + i->indirection = check_indirection(indirection, yyscanner); + } + else + { + /* got to split the list in two */ + i->indirection = check_indirection(list_copy_tail(indirection, + nfields), + yyscanner); + indirection = list_truncate(indirection, nfields); + c->fields = lcons(makeString(colname), indirection); + } + i->arg = (Node *) c; + return (Node *) i; + } + else if (IsA(lfirst(l), A_Star)) + { + /* We only allow '*' at the end of a ColumnRef */ + if (lnext(indirection, l) != NULL) + parser_yyerror("improper use of \"*\""); + } + nfields++; + } + /* No subscripting, so all indirection gets added to field list */ + c->fields = lcons(makeString(colname), indirection); + return (Node *) c; +} + +static Node * +makeTypeCast(Node *arg, TypeName *typename, int location) +{ + TypeCast *n = makeNode(TypeCast); + n->arg = arg; + n->typeName = typename; + n->location = location; + return (Node *) n; +} + +static Node * +makeStringConst(char *str, int location) +{ + A_Const *n = makeNode(A_Const); + + n->val.type = T_String; + n->val.val.str = str; + n->location = location; + + return (Node *)n; +} + +static Node * +makeStringConstCast(char *str, int location, TypeName *typename) +{ + Node *s = makeStringConst(str, location); + + return makeTypeCast(s, typename, -1); +} + +static Node * +makeIntConst(int val, int location) +{ + A_Const *n = makeNode(A_Const); + + n->val.type = T_Integer; + n->val.val.ival = val; + n->location = location; + + return (Node *)n; +} + +static Node * +makeFloatConst(char *str, int location) +{ + A_Const *n = makeNode(A_Const); + + n->val.type = T_Float; + n->val.val.str = str; + n->location = location; + + return (Node *)n; +} + +static Node * +makeBitStringConst(char *str, int location) +{ + A_Const *n = makeNode(A_Const); + + n->val.type = T_BitString; + n->val.val.str = str; + n->location = location; + + return (Node *)n; +} + +static Node * +makeNullAConst(int location) +{ + A_Const *n = makeNode(A_Const); + + n->val.type = T_Null; + n->location = location; + + return (Node *)n; +} + +static Node * +makeAConst(Value *v, int location) +{ + Node *n; + + switch (v->type) + { case T_Float: n = makeFloatConst(v->val.str, location); break; @@ -19125,13 +19599,14 @@ check_indirection(List *indirection, core_yyscan_t yyscanner) } /* extractArgTypes() + * * Given a list of FunctionParameter nodes, extract a list of just the - * argument types (TypeNames) for input parameters only. This is what - * is needed to look up an existing function, which is what is wanted by - * the productions that use this call. + * argument types (TypeNames) for signature parameters only (e.g., only input + * parameters for functions). This is what is needed to look up an existing + * function, which is what is wanted by the productions that use this call. */ static List * -extractArgTypes(List *parameters) +extractArgTypes(ObjectType objtype, List *parameters) { List *result = NIL; ListCell *i; @@ -19140,7 +19615,7 @@ extractArgTypes(List *parameters) { FunctionParameter *p = (FunctionParameter *) lfirst(i); - if (p->mode != FUNC_PARAM_OUT && p->mode != FUNC_PARAM_TABLE) + if ((p->mode != FUNC_PARAM_OUT || objtype == OBJECT_PROCEDURE) && p->mode != FUNC_PARAM_TABLE) result = lappend(result, p->argType); } return result; @@ -19153,7 +19628,7 @@ static List * extractAggrArgTypes(List *aggrargs) { Assert(list_length(aggrargs) == 2); - return extractArgTypes((List *) linitial(aggrargs)); + return extractArgTypes(OBJECT_AGGREGATE, (List *) linitial(aggrargs)); } /* makeOrderedSetArgs() @@ -19166,7 +19641,7 @@ makeOrderedSetArgs(List *directargs, List *orderedargs, core_yyscan_t yyscanner) { FunctionParameter *lastd = (FunctionParameter *) llast(directargs); - int ndirectargs; + Value *ndirectargs; /* No restriction unless last direct arg is VARIADIC */ if (lastd->mode == FUNC_PARAM_VARIADIC) @@ -19190,10 +19665,10 @@ makeOrderedSetArgs(List *directargs, List *orderedargs, } /* don't merge into the next line, as list_concat changes directargs */ - ndirectargs = list_length(directargs); + ndirectargs = makeInteger(list_length(directargs)); return list_make2(list_concat(directargs, orderedargs), - makeInteger(ndirectargs)); + ndirectargs); } /* insertSelectOptions() @@ -19252,7 +19727,7 @@ insertSelectOptions(SelectStmt *stmt, if (!stmt->sortClause && limitClause->limitOption == LIMIT_OPTION_WITH_TIES) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("WITH TIES options can not be specified without ORDER BY clause"))); + errmsg("WITH TIES cannot be specified without ORDER BY clause"))); stmt->limitOption = limitClause->limitOption; } if (withClause) diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index efb6aa1c46a4..653ab6b04f5c 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -1153,7 +1153,7 @@ parseCheckAggregates(ParseState *pstate, Query *qry) if (gset_common) { - for_each_cell(l, gsets, list_second_cell(gsets)) + for_each_from(l, gsets, 1) { gset_common = list_intersection_int(gset_common, lfirst(l)); if (!gset_common) @@ -1854,7 +1854,7 @@ expand_grouping_sets(List *groupingSets, int limit) result = lappend(result, list_union_int(NIL, (List *) lfirst(lc))); } - for_each_cell(lc, expanded_groups, list_second_cell(expanded_groups)) + for_each_from(lc, expanded_groups, 1) { List *p = lfirst(lc); List *new_result = NIL; diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 9ab00b0e66a8..959354873951 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -1780,24 +1780,13 @@ buildMergedJoinVar(ParseState *pstate, JoinType jointype, *r_node, *res_node; - /* - * Choose output type if input types are dissimilar. - */ - outcoltype = l_colvar->vartype; - outcoltypmod = l_colvar->vartypmod; - if (outcoltype != r_colvar->vartype) - { - outcoltype = select_common_type(pstate, + outcoltype = select_common_type(pstate, + list_make2(l_colvar, r_colvar), + "JOIN/USING", + NULL); + outcoltypmod = select_common_typmod(pstate, list_make2(l_colvar, r_colvar), - "JOIN/USING", - NULL); - outcoltypmod = -1; /* ie, unknown */ - } - else if (outcoltypmod != r_colvar->vartypmod) - { - /* same type, but not same typmod */ - outcoltypmod = -1; /* ie, unknown */ - } + outcoltype); /* * Insert coercion functions if needed. Note that a difference in typmod @@ -1982,7 +1971,7 @@ transformLimitClause(ParseState *pstate, Node *clause, IsA(clause, A_Const) && ((A_Const *) clause)->val.type == T_Null) ereport(ERROR, (errcode(ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE), - errmsg("row count cannot be NULL in FETCH FIRST ... WITH TIES clause"))); + errmsg("row count cannot be null in FETCH FIRST ... WITH TIES clause"))); return qual; } diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index d2c8b28e78e0..7fb72211587e 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -1566,6 +1566,43 @@ coerce_to_common_type(ParseState *pstate, Node *node, return node; } +/* + * select_common_typmod() + * Determine the common typmod of a list of input expressions. + * + * common_type is the selected common type of the expressions, typically + * computed using select_common_type(). + */ +int32 +select_common_typmod(ParseState *pstate, List *exprs, Oid common_type) +{ + ListCell *lc; + bool first = true; + int32 result = -1; + + foreach(lc, exprs) + { + Node *expr = (Node *) lfirst(lc); + + /* Types must match */ + if (exprType(expr) != common_type) + return -1; + else if (first) + { + result = exprTypmod(expr); + first = false; + } + else + { + /* As soon as we see a non-matching typmod, fall back to -1 */ + if (result != exprTypmod(expr)) + return -1; + } + } + + return result; +} + /* * check_generic_type_consistency() * Are the actual arguments potentially compatible with a @@ -2199,8 +2236,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, else { /* - * Only way to get here is if all the polymorphic args have - * UNKNOWN inputs + * Only way to get here is if all the family-1 polymorphic + * arguments have UNKNOWN inputs. */ ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), @@ -2298,10 +2335,10 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, else { /* - * Only way to get here is if all the ANYCOMPATIBLE args have - * UNKNOWN inputs. Resolve to TEXT as select_common_type() - * would do. That doesn't license us to use TEXTRANGE, - * though. + * Only way to get here is if all the family-2 polymorphic + * arguments have UNKNOWN inputs. Resolve to TEXT as + * select_common_type() would do. That doesn't license us to + * use TEXTRANGE, though. */ anycompatible_typeid = TEXTOID; anycompatible_array_typeid = TEXTARRAYOID; @@ -2313,7 +2350,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, } } - /* replace polymorphic types by selected types */ + /* replace family-2 polymorphic types by selected types */ for (int j = 0; j < nargs; j++) { Oid decl_type = declared_arg_types[j]; @@ -2329,11 +2366,11 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, } /* - * If we had any UNKNOWN inputs for polymorphic arguments, re-scan to - * assign correct types to them. + * If we had any UNKNOWN inputs for family-1 polymorphic arguments, + * re-scan to assign correct types to them. * * Note: we don't have to consider unknown inputs that were matched to - * ANYCOMPATIBLE-family arguments, because we forcibly updated their + * family-2 polymorphic arguments, because we forcibly updated their * declared_arg_types[] positions just above. */ if (have_poly_unknowns) diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 2403b09f8282..a089a4beae58 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -57,7 +57,7 @@ bool Transform_null_equals = false; #define PREC_GROUP_NOT_LIKE 9 /* NOT LIKE/ILIKE/SIMILAR */ #define PREC_GROUP_NOT_BETWEEN 10 /* NOT BETWEEN */ #define PREC_GROUP_NOT_IN 11 /* NOT IN */ -#define PREC_GROUP_POSTFIX_OP 12 /* generic postfix operators */ +#define PREC_GROUP_ANY_ALL 12 /* ANY/ALL */ #define PREC_GROUP_INFIX_OP 13 /* generic infix operators */ #define PREC_GROUP_PREFIX_OP 14 /* generic prefix operators */ @@ -71,7 +71,7 @@ bool Transform_null_equals = false; * 4. LIKE ILIKE SIMILAR * 5. BETWEEN * 6. IN - * 7. generic postfix Op + * 7. ANY ALL * 8. generic Op, including <= => <> * 9. generic prefix Op * 10. IS tests (NullTest, BooleanTest, etc) @@ -1053,7 +1053,7 @@ transformAExprOpAny(ParseState *pstate, A_Expr *a) Node *rexpr = a->rexpr; if (operator_precedence_warning) - emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_OP, + emit_precedence_warnings(pstate, PREC_GROUP_ANY_ALL, strVal(llast(a->name)), lexpr, NULL, a->location); @@ -1076,7 +1076,7 @@ transformAExprOpAll(ParseState *pstate, A_Expr *a) Node *rexpr = a->rexpr; if (operator_precedence_warning) - emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_OP, + emit_precedence_warnings(pstate, PREC_GROUP_ANY_ALL, strVal(llast(a->name)), lexpr, NULL, a->location); @@ -1744,11 +1744,12 @@ transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref) /* * If we're at the last column, delete the RowExpr from * p_multiassign_exprs; we don't need it anymore, and don't want it in - * the finished UPDATE tlist. + * the finished UPDATE tlist. We assume this is still the last entry + * in p_multiassign_exprs. */ if (maref->colno == maref->ncolumns) pstate->p_multiassign_exprs = - list_delete_ptr(pstate->p_multiassign_exprs, tle); + list_delete_last(pstate->p_multiassign_exprs); return result; } @@ -2092,7 +2093,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) sublink->testexpr, NULL, sublink->location); else - emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_OP, + emit_precedence_warnings(pstate, PREC_GROUP_ANY_ALL, strVal(llast(sublink->operName)), sublink->testexpr, NULL, sublink->location); @@ -3386,28 +3387,11 @@ operator_precedence_group(Node *node, const char **nodename) group = PREC_GROUP_PREFIX_OP; } } - else if (aexpr->kind == AEXPR_OP && - aexpr->lexpr != NULL && - aexpr->rexpr == NULL) - { - /* postfix operator */ - if (list_length(aexpr->name) == 1) - { - *nodename = strVal(linitial(aexpr->name)); - group = PREC_GROUP_POSTFIX_OP; - } - else - { - /* schema-qualified operator syntax */ - *nodename = "OPERATOR()"; - group = PREC_GROUP_POSTFIX_OP; - } - } else if (aexpr->kind == AEXPR_OP_ANY || aexpr->kind == AEXPR_OP_ALL) { *nodename = strVal(llast(aexpr->name)); - group = PREC_GROUP_POSTFIX_OP; + group = PREC_GROUP_ANY_ALL; } else if (aexpr->kind == AEXPR_DISTINCT || aexpr->kind == AEXPR_NOT_DISTINCT) @@ -3498,7 +3482,7 @@ operator_precedence_group(Node *node, const char **nodename) else { *nodename = strVal(llast(s->operName)); - group = PREC_GROUP_POSTFIX_OP; + group = PREC_GROUP_ANY_ALL; } } } @@ -3574,9 +3558,8 @@ emit_precedence_warnings(ParseState *pstate, * Complain if left child, which should be same or higher precedence * according to current rules, used to be lower precedence. * - * Exception to precedence rules: if left child is IN or NOT IN or a - * postfix operator, the grouping is syntactically forced regardless of - * precedence. + * Exception to precedence rules: if left child is IN or NOT IN the + * grouping is syntactically forced regardless of precedence. */ cgroup = operator_precedence_group(lchild, &copname); if (cgroup > 0) @@ -3584,7 +3567,7 @@ emit_precedence_warnings(ParseState *pstate, if (oldprecedence_l[cgroup] < oldprecedence_r[opgroup] && cgroup != PREC_GROUP_IN && cgroup != PREC_GROUP_NOT_IN && - cgroup != PREC_GROUP_POSTFIX_OP && + cgroup != PREC_GROUP_ANY_ALL && cgroup != PREC_GROUP_POSTFIX_IS) ereport(WARNING, (errmsg("operator precedence change: %s is now lower precedence than %s", diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 8efe093c691c..f17b66055692 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -1804,6 +1804,7 @@ unify_hypothetical_args(ParseState *pstate, ListCell *harg = list_nth_cell(fargs, hargpos); ListCell *aarg = list_nth_cell(fargs, aargpos); Oid commontype; + int32 commontypmod; /* A mismatch means AggregateCreate didn't check properly ... */ if (declared_arg_types[hargpos] != declared_arg_types[aargpos]) @@ -1822,6 +1823,9 @@ unify_hypothetical_args(ParseState *pstate, list_make2(lfirst(aarg), lfirst(harg)), "WITHIN GROUP", NULL); + commontypmod = select_common_typmod(pstate, + list_make2(lfirst(aarg), lfirst(harg)), + commontype); /* * Perform the coercions. We don't need to worry about NamedArgExprs @@ -1830,7 +1834,7 @@ unify_hypothetical_args(ParseState *pstate, lfirst(harg) = coerce_type(pstate, (Node *) lfirst(harg), actual_arg_types[hargpos], - commontype, -1, + commontype, commontypmod, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1); @@ -1838,7 +1842,7 @@ unify_hypothetical_args(ParseState *pstate, lfirst(aarg) = coerce_type(pstate, (Node *) lfirst(aarg), actual_arg_types[aargpos], - commontype, -1, + commontype, commontypmod, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1); diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c index 2749974f6384..6613a3a8f879 100644 --- a/src/backend/parser/parse_oper.c +++ b/src/backend/parser/parse_oper.c @@ -52,7 +52,7 @@ typedef struct OprCacheKey { char oprname[NAMEDATALEN]; Oid left_arg; /* Left input OID, or 0 if prefix op */ - Oid right_arg; /* Right input OID, or 0 if postfix op */ + Oid right_arg; /* Right input OID */ Oid search_path[MAX_CACHED_PATH_LEN]; } OprCacheKey; @@ -88,8 +88,7 @@ static void InvalidateOprCacheCallBack(Datum arg, int cacheid, uint32 hashvalue) * Given a possibly-qualified operator name and exact input datatypes, * look up the operator. * - * Pass oprleft = InvalidOid for a prefix op, oprright = InvalidOid for - * a postfix op. + * Pass oprleft = InvalidOid for a prefix op. * * If the operator name is not schema-qualified, it is sought in the current * namespace search path. @@ -115,10 +114,16 @@ LookupOperName(ParseState *pstate, List *opername, Oid oprleft, Oid oprright, if (!OidIsValid(oprleft)) oprkind = 'l'; - else if (!OidIsValid(oprright)) - oprkind = 'r'; - else + else if (OidIsValid(oprright)) oprkind = 'b'; + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("postfix operators are not supported"), + parser_errposition(pstate, location))); + oprkind = 0; /* keep compiler quiet */ + } ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), @@ -507,85 +512,6 @@ compatible_oper_opid(List *op, Oid arg1, Oid arg2, bool noError) } -/* right_oper() -- search for a unary right operator (postfix operator) - * Given operator name and type of arg, return oper struct. - * - * IMPORTANT: the returned operator (if any) is only promised to be - * coercion-compatible with the input datatype. Do not use this if - * you need an exact- or binary-compatible match. - * - * If no matching operator found, return NULL if noError is true, - * raise an error if it is false. pstate and location are used only to report - * the error position; pass NULL/-1 if not available. - * - * NOTE: on success, the returned object is a syscache entry. The caller - * must ReleaseSysCache() the entry when done with it. - */ -Operator -right_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location) -{ - Oid operOid; - OprCacheKey key; - bool key_ok; - FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND; - HeapTuple tup = NULL; - - /* - * Try to find the mapping in the lookaside cache. - */ - key_ok = make_oper_cache_key(pstate, &key, op, arg, InvalidOid, location); - - if (key_ok) - { - operOid = find_oper_cache_entry(&key); - if (OidIsValid(operOid)) - { - tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); - if (HeapTupleIsValid(tup)) - return (Operator) tup; - } - } - - /* - * First try for an "exact" match. - */ - operOid = OpernameGetOprid(op, arg, InvalidOid); - if (!OidIsValid(operOid)) - { - /* - * Otherwise, search for the most suitable candidate. - */ - FuncCandidateList clist; - - /* Get postfix operators of given name */ - clist = OpernameGetCandidates(op, 'r', false); - - /* No operators found? Then fail... */ - if (clist != NULL) - { - /* - * We must run oper_select_candidate even if only one candidate, - * otherwise we may falsely return a non-type-compatible operator. - */ - fdresult = oper_select_candidate(1, &arg, clist, &operOid); - } - } - - if (OidIsValid(operOid)) - tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); - - if (HeapTupleIsValid(tup)) - { - if (key_ok) - make_oper_cache_entry(&key, operOid); - } - else if (!noError) - op_error(pstate, op, 'r', arg, InvalidOid, fdresult, location); - - return (Operator) tup; -} - - /* left_oper() -- search for a unary left operator (prefix operator) * Given operator name and type of arg, return oper struct. * @@ -696,8 +622,7 @@ op_signature_string(List *op, char oprkind, Oid arg1, Oid arg2) appendStringInfoString(&argbuf, NameListToString(op)); - if (oprkind != 'r') - appendStringInfo(&argbuf, " %s", format_type_be(arg2)); + appendStringInfo(&argbuf, " %s", format_type_be(arg2)); return argbuf.data; /* return palloc'd string buffer */ } @@ -758,17 +683,16 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, Oid rettype; OpExpr *result; - /* Select the operator */ + /* Check it's not a postfix operator */ if (rtree == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("postfix operators are not supported"))); + + /* Select the operator */ + if (ltree == NULL) { - /* right operator */ - ltypeId = exprType(ltree); - rtypeId = InvalidOid; - tup = right_oper(pstate, opname, ltypeId, false, location); - } - else if (ltree == NULL) - { - /* left operator */ + /* prefix operator */ rtypeId = exprType(rtree); ltypeId = InvalidOid; tup = left_oper(pstate, opname, rtypeId, false, location); @@ -795,17 +719,9 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, parser_errposition(pstate, location))); /* Do typecasting and build the expression tree */ - if (rtree == NULL) - { - /* right operator */ - args = list_make1(ltree); - actual_arg_types[0] = ltypeId; - declared_arg_types[0] = opform->oprleft; - nargs = 1; - } - else if (ltree == NULL) + if (ltree == NULL) { - /* left operator */ + /* prefix operator */ args = list_make1(rtree); actual_arg_types[0] = rtypeId; declared_arg_types[0] = opform->oprright; diff --git a/src/backend/parser/parse_param.c b/src/backend/parser/parse_param.c index 17a96abfa8c3..93c9d82d017d 100644 --- a/src/backend/parser/parse_param.c +++ b/src/backend/parser/parse_param.c @@ -163,6 +163,15 @@ variable_paramref_hook(ParseState *pstate, ParamRef *pref) if (*pptype == InvalidOid) *pptype = UNKNOWNOID; + /* + * If the argument is of type void and it's procedure call, interpret it + * as unknown. This allows the JDBC driver to not have to distinguish + * function and procedure calls. See also another component of this hack + * in ParseFuncOrColumn(). + */ + if (*pptype == VOIDOID && pstate->p_expr_kind == EXPR_KIND_CALL_ARGUMENT) + *pptype = UNKNOWNOID; + param = makeNode(Param); param->paramkind = PARAM_EXTERN; param->paramid = paramno; diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 3de940bf9e59..7473d3415b60 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -1942,16 +1942,46 @@ addRangeTableEntryForFunction(ParseState *pstate, /* * A coldeflist is required if the function returns RECORD and hasn't - * got a predetermined record type, and is prohibited otherwise. + * got a predetermined record type, and is prohibited otherwise. This + * can be a bit confusing, so we expend some effort on delivering a + * relevant error message. */ if (coldeflist != NIL) { - if (functypclass != TYPEFUNC_RECORD) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("a column definition list is only allowed for functions returning \"record\""), - parser_errposition(pstate, - exprLocation((Node *) coldeflist)))); + switch (functypclass) + { + case TYPEFUNC_RECORD: + /* ok */ + break; + case TYPEFUNC_COMPOSITE: + case TYPEFUNC_COMPOSITE_DOMAIN: + + /* + * If the function's raw result type is RECORD, we must + * have resolved it using its OUT parameters. Otherwise, + * it must have a named composite type. + */ + if (exprType(funcexpr) == RECORDOID) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a column definition list is redundant for a function with OUT parameters"), + parser_errposition(pstate, + exprLocation((Node *) coldeflist)))); + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a column definition list is redundant for a function returning a named composite type"), + parser_errposition(pstate, + exprLocation((Node *) coldeflist)))); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a column definition list is only allowed for functions returning \"record\""), + parser_errposition(pstate, + exprLocation((Node *) coldeflist)))); + break; + } } else { diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 772f5ebc5627..8b56ccb2b8df 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -414,7 +414,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle, ste = get_tle_by_resno(GetCTETargetList(cte), attnum); if (ste == NULL || ste->resjunk) - elog(ERROR, "subquery %s does not have attribute %d", + elog(ERROR, "CTE %s does not have attribute %d", rte->eref->aliasname, attnum); tle->resorigtbl = ste->resorigtbl; tle->resorigcol = ste->resorigcol; @@ -1611,7 +1611,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) ste = get_tle_by_resno(GetCTETargetList(cte), attnum); if (ste == NULL || ste->resjunk) - elog(ERROR, "subquery %s does not have attribute %d", + elog(ERROR, "CTE %s does not have attribute %d", rte->eref->aliasname, attnum); expr = (Node *) ste->expr; if (IsA(expr, Var)) diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index d11711357fb0..efa82b2a69bb 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -97,8 +97,11 @@ typedef struct List *ckconstraints; /* CHECK constraints */ List *fkconstraints; /* FOREIGN KEY constraints */ List *ixconstraints; /* index-creating constraints */ + List *inh_indexes; /* cloned indexes from INCLUDING INDEXES + * GPDB: used by transformDistributedBy + * Note: Attribute numbers in expressions + * might not be correct, only use names */ List *attr_encodings; /* List of ColumnReferenceStorageDirectives */ - List *inh_indexes; /* cloned indexes from INCLUDING INDEXES */ List *extstats; /* cloned extended statistics */ List *blist; /* "before list" of things to do before * creating the table */ @@ -172,6 +175,9 @@ static DistributedBy *transformDistributedBy(ParseState *pstate, * Returns a List of utility commands to be done in sequence. One of these * will be the transformed CreateStmt, but there may be additional actions * to be done before and after the actual DefineRelation() call. + * In addition to normal utility commands such as AlterTableStmt and + * IndexStmt, the result list may contain TableLikeClause(s), representing + * the need to perform additional parse analysis after DefineRelation(). * * SQL allows constraints to be scattered all over, so thumb through * the columns and collect all constraints into one place. @@ -289,7 +295,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; - cxt.inh_indexes = NIL; + cxt.inh_indexes = NIL; /* GPDB: used by transformDistributedBy */ cxt.extstats = NIL; cxt.attr_encodings = stmt->attr_encodings; cxt.blist = NIL; @@ -447,6 +453,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, AlterSeqStmt *altseqstmt; List *attnamelist; bool has_cache_option = false; + int nameEl_idx = -1; /* * Determine namespace and name to use for the sequence. @@ -473,6 +480,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); nameEl = defel; + nameEl_idx = foreach_current_index(option); } if (strcmp(defel->defname, "cache") == 0) @@ -495,7 +503,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, } sname = rv->relname; /* Remove the SEQUENCE NAME item from seqoptions */ - seqoptions = list_delete_ptr(seqoptions, nameEl); + seqoptions = list_delete_nth_cell(seqoptions, nameEl_idx); } else { @@ -1017,8 +1025,11 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) * transformTableLikeClause * * Change the LIKE portion of a CREATE TABLE statement into - * column definitions which recreate the user defined column portions of - * . + * column definitions that recreate the user defined column portions of + * . Also, if there are any LIKE options that we can't fully + * process at this point, add the TableLikeClause to cxt->alist, which + * will cause utility.c to call expandTableLikeClause() after the new + * table has been created. * * GPDB: if forceBareCol is true we disallow inheriting any indexes/constr/defaults. */ @@ -1027,11 +1038,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla bool forceBareCol, CreateStmt *stmt) { AttrNumber parent_attno; - AttrNumber new_attno; Relation relation; TupleDesc tupleDesc; - TupleConstr *constr; - AttrMap *attmap; AclResult aclresult; char *comment; ParseCallbackState pcbstate; @@ -1051,6 +1059,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("LIKE is not supported for creating foreign tables"))); + /* Open the relation referenced by the LIKE clause */ relation = relation_openrv(table_like_clause->relation, AccessShareLock); if (relation->rd_rel->relkind != RELKIND_RELATION && @@ -1087,37 +1096,18 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla } tupleDesc = RelationGetDescr(relation); - constr = tupleDesc->constr; /* * Initialize column number map for map_variable_attnos(). We need this * since dropped columns in the source table aren't copied, so the new * table can have different column numbers. */ - attmap = make_attrmap(tupleDesc->natts); - - /* - * We must fill the attmap now so that it can be used to process generated - * column default expressions in the per-column loop below. - */ - new_attno = 1; - for (parent_attno = 1; parent_attno <= tupleDesc->natts; - parent_attno++) - { - Form_pg_attribute attribute = TupleDescAttr(tupleDesc, - parent_attno - 1); - - /* - * Ignore dropped columns in the parent. attmap entry is left zero. - */ - if (attribute->attisdropped) - continue; - - attmap->attnums[parent_attno - 1] = list_length(cxt->columns) + (new_attno++); - } + AttrMap *attmap = make_attrmap(tupleDesc->natts); /* * Insert the copied attributes into the cxt for the new table definition. + * We must do this now so that they appear in the table in the relative + * position where the LIKE clause is, as required by SQL99. */ for (parent_attno = 1; parent_attno <= tupleDesc->natts; parent_attno++) @@ -1159,54 +1149,15 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla * Add to column list */ cxt->columns = lappend(cxt->columns, def); + attmap->attnums[parent_attno - 1] = list_length(cxt->columns); /* - * Copy default, if present and it should be copied. We have separate - * options for plain default expressions and GENERATED defaults. + * Although we don't transfer the column's default/generation + * expression now, we need to mark it GENERATED if appropriate. */ - if (attribute->atthasdef && - (attribute->attgenerated ? - (table_like_clause->options & CREATE_TABLE_LIKE_GENERATED) : - (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS))) - { - Node *this_default = NULL; - AttrDefault *attrdef; - int i; - bool found_whole_row; - - /* Find default in constraint structure */ - Assert(constr != NULL); - attrdef = constr->defval; - for (i = 0; i < constr->num_defval; i++) - { - if (attrdef[i].adnum == parent_attno) - { - this_default = stringToNode(attrdef[i].adbin); - break; - } - } - Assert(this_default != NULL); - - def->cooked_default = map_variable_attnos(this_default, - 1, 0, - attmap, - InvalidOid, &found_whole_row); - - /* - * Prevent this for the same reason as for constraints below. Note - * that defaults cannot contain any vars, so it's OK that the - * error message refers to generated columns. - */ - if (found_whole_row) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot convert whole-row table reference"), - errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".", - attributeName, - RelationGetRelationName(relation)))); - + if (attribute->atthasdef && attribute->attgenerated && + (table_like_clause->options & CREATE_TABLE_LIKE_GENERATED)) def->generated = attribute->attgenerated; - } /* * Copy identity if requested @@ -1254,14 +1205,279 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla } } + /* + * We cannot yet deal with defaults, CHECK constraints, or indexes, since + * we don't yet know what column numbers the copied columns will have in + * the finished table. If any of those options are specified, add the + * LIKE clause to cxt->alist so that expandTableLikeClause will be called + * after we do know that. + */ + if (table_like_clause->options & + (CREATE_TABLE_LIKE_DEFAULTS | + CREATE_TABLE_LIKE_GENERATED | + CREATE_TABLE_LIKE_CONSTRAINTS | + CREATE_TABLE_LIKE_INDEXES)) + cxt->alist = lappend(cxt->alist, table_like_clause); + + /* + * GPDB_12_MERGE_FIXME: + * This is wrong and creates unspecified behaviour when multiple like + * clauses are present in the statement. + * + * Try to use a unified interface for encoding handling in a manner + * similar to CREATE/ALTER commands. + */ + /* + * If STORAGE is included, we need to copy over the table storage params + * as well as the attribute encodings. + */ + if (stmt && table_like_clause->options & CREATE_TABLE_LIKE_STORAGE) + { + MemoryContext oldcontext; + /* + * As we are modifying the utility statement we must make sure these + * DefElem allocations can survive outside of this context. + */ + oldcontext = MemoryContextSwitchTo(CurTransactionContext); + + if (RelationIsAppendOptimized(relation)) + { + int32 blocksize; + int32 safefswritersize; + int16 compresslevel; + bool checksum; + NameData compresstype; + + GetAppendOnlyEntryAttributes(relation->rd_id, &blocksize, + &safefswritersize,&compresslevel, + &checksum,&compresstype); + + stmt->accessMethod = get_am_name(relation->rd_rel->relam); + + stmt->options = lappend(stmt->options, + makeDefElem("blocksize", (Node *) makeInteger(blocksize), -1)); + stmt->options = lappend(stmt->options, + makeDefElem("checksum", (Node *) makeInteger(checksum), -1)); + stmt->options = lappend(stmt->options, + makeDefElem("compresslevel", (Node *) makeInteger(compresslevel), -1)); + if (strlen(NameStr(compresstype)) > 0) + stmt->options = lappend(stmt->options, + makeDefElem("compresstype", (Node *) makeString(pstrdup(NameStr(compresstype))), -1)); + } + + /* + * Set the attribute encodings. + */ + cxt->attr_encodings = list_union(cxt->attr_encodings, rel_get_column_encodings(relation)); + MemoryContextSwitchTo(oldcontext); + } + + /* + * We may copy extended statistics if requested, since the representation + * of CreateStatsStmt doesn't depend on column numbers. + */ + if (table_like_clause->options & CREATE_TABLE_LIKE_STATISTICS) + { + List *parent_extstats; + ListCell *l; + + parent_extstats = RelationGetStatExtList(relation); + + foreach(l, parent_extstats) + { + Oid parent_stat_oid = lfirst_oid(l); + CreateStatsStmt *stats_stmt; + + stats_stmt = generateClonedExtStatsStmt(cxt->relation, + RelationGetRelid(relation), + parent_stat_oid); + + /* Copy comment on statistics object, if requested */ + if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) + { + comment = GetComment(parent_stat_oid, StatisticExtRelationId, 0); + + /* + * We make use of CreateStatsStmt's stxcomment option, so as + * not to need to know now what name the statistics will have. + */ + stats_stmt->stxcomment = comment; + } + + cxt->extstats = lappend(cxt->extstats, stats_stmt); + } + + list_free(parent_extstats); + } + + /* + * Copy indexes for Greengage choosing distributed-by keys. + * PostgreSQL processes index statements after here in expandTableLikeClause(), + * but we need indexes in transformDistributedBy() which is before expandTableLikeClause(), + * So we both retain the index statements processing here and expandTableLikeClause. + * the process here is just used by transformDistributedBy(). + */ + if ((table_like_clause->options & CREATE_TABLE_LIKE_INDEXES) && + relation->rd_rel->relhasindex) + { + List *parent_indexes; + ListCell *l; + + parent_indexes = RelationGetIndexList(relation); + + foreach(l, parent_indexes) + { + Oid parent_index_oid = lfirst_oid(l); + Relation parent_index; + IndexStmt *index_stmt; + + parent_index = index_open(parent_index_oid, AccessShareLock); + + /* Build CREATE INDEX statement to recreate the parent_index */ + index_stmt = generateClonedIndexStmt(stmt->relation, + parent_index, + attmap, + NULL); + + /* Save it in the inh_indexes list for the time being */ + cxt->inh_indexes = lappend(cxt->inh_indexes, index_stmt); + + index_close(parent_index, AccessShareLock); + } + } + /* + * Close the parent rel, but keep our AccessShareLock on it until xact + * commit. That will prevent someone else from deleting or ALTERing the + * parent before we can run expandTableLikeClause. + */ + table_close(relation, NoLock); +} + +/* + * expandTableLikeClause + * + * Process LIKE options that require knowing the final column numbers + * assigned to the new table's columns. This executes after we have + * run DefineRelation for the new table. It returns a list of utility + * commands that should be run to generate indexes etc. + */ +List * +expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause) +{ + List *result = NIL; + List *atsubcmds = NIL; + AttrNumber parent_attno; + Relation relation; + Relation childrel; + TupleDesc tupleDesc; + TupleConstr *constr; + AttrMap *attmap; + char *comment; + + /* + * Open the relation referenced by the LIKE clause. We should still have + * the table lock obtained by transformTableLikeClause (and this'll throw + * an assertion failure if not). Hence, no need to recheck privileges + * etc. + */ + relation = relation_openrv(table_like_clause->relation, NoLock); + + tupleDesc = RelationGetDescr(relation); + constr = tupleDesc->constr; + + /* + * Open the newly-created child relation; we have lock on that too. + */ + childrel = relation_openrv(heapRel, NoLock); + + /* + * Construct a map from the LIKE relation's attnos to the child rel's. + * This re-checks type match etc, although it shouldn't be possible to + * have a failure since both tables are locked. + */ + attmap = build_attrmap_by_name(RelationGetDescr(childrel), + tupleDesc); + + /* + * Process defaults, if required. + */ + if ((table_like_clause->options & + (CREATE_TABLE_LIKE_DEFAULTS | CREATE_TABLE_LIKE_GENERATED)) && + constr != NULL) + { + AttrDefault *attrdef = constr->defval; + + for (parent_attno = 1; parent_attno <= tupleDesc->natts; + parent_attno++) + { + Form_pg_attribute attribute = TupleDescAttr(tupleDesc, + parent_attno - 1); + + /* + * Ignore dropped columns in the parent. + */ + if (attribute->attisdropped) + continue; + + /* + * Copy default, if present and it should be copied. We have + * separate options for plain default expressions and GENERATED + * defaults. + */ + if (attribute->atthasdef && + (attribute->attgenerated ? + (table_like_clause->options & CREATE_TABLE_LIKE_GENERATED) : + (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS))) + { + Node *this_default = NULL; + AlterTableCmd *atsubcmd; + bool found_whole_row; + + /* Find default in constraint structure */ + for (int i = 0; i < constr->num_defval; i++) + { + if (attrdef[i].adnum == parent_attno) + { + this_default = stringToNode(attrdef[i].adbin); + break; + } + } + Assert(this_default != NULL); + + atsubcmd = makeNode(AlterTableCmd); + atsubcmd->subtype = AT_CookedColumnDefault; + atsubcmd->num = attmap->attnums[parent_attno - 1]; + atsubcmd->def = map_variable_attnos(this_default, + 1, 0, + attmap, + InvalidOid, + &found_whole_row); + + /* + * Prevent this for the same reason as for constraints below. + * Note that defaults cannot contain any vars, so it's OK that + * the error message refers to generated columns. + */ + if (found_whole_row) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert whole-row table reference"), + errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".", + NameStr(attribute->attname), + RelationGetRelationName(relation)))); + + atsubcmds = lappend(atsubcmds, atsubcmd); + } + } + } + /* * Copy CHECK constraints if requested, being careful to adjust attribute * numbers so they match the child. */ if ((table_like_clause->options & CREATE_TABLE_LIKE_CONSTRAINTS) && - tupleDesc->constr) + constr != NULL) { - TupleConstr *constr = tupleDesc->constr; int ccnum; for (ccnum = 0; ccnum < constr->num_check; ccnum++) @@ -1269,9 +1485,10 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla char *ccname = constr->check[ccnum].ccname; char *ccbin = constr->check[ccnum].ccbin; bool ccnoinherit = constr->check[ccnum].ccnoinherit; - Constraint *n = makeNode(Constraint); Node *ccbin_node; bool found_whole_row; + Constraint *n; + AlterTableCmd *atsubcmd; ccbin_node = map_variable_attnos(stringToNode(ccbin), 1, 0, @@ -1292,13 +1509,22 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla ccname, RelationGetRelationName(relation)))); + n = makeNode(Constraint); n->contype = CONSTR_CHECK; n->conname = pstrdup(ccname); n->location = -1; n->is_no_inherit = ccnoinherit; n->raw_expr = NULL; n->cooked_expr = nodeToString(ccbin_node); - cxt->ckconstraints = lappend(cxt->ckconstraints, n); + + /* We can skip validation, since the new table should be empty. */ + n->skip_validation = true; + n->initially_valid = true; + + atsubcmd = makeNode(AlterTableCmd); + atsubcmd->subtype = AT_AddConstraint; + atsubcmd->def = (Node *) n; + atsubcmds = lappend(atsubcmds, atsubcmd); /* Copy comment on constraint */ if ((table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) && @@ -1310,18 +1536,34 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla CommentStmt *stmt = makeNode(CommentStmt); stmt->objtype = OBJECT_TABCONSTRAINT; - stmt->object = (Node *) list_make3(makeString(cxt->relation->schemaname), - makeString(cxt->relation->relname), + stmt->object = (Node *) list_make3(makeString(heapRel->schemaname), + makeString(heapRel->relname), makeString(n->conname)); stmt->comment = comment; - cxt->alist = lappend(cxt->alist, stmt); + result = lappend(result, stmt); } } } /* - * Likewise, copy indexes if requested + * If we generated any ALTER TABLE actions above, wrap them into a single + * ALTER TABLE command. Stick it at the front of the result, so it runs + * before any CommentStmts we made above. + */ + if (atsubcmds) + { + AlterTableStmt *atcmd = makeNode(AlterTableStmt); + + atcmd->relation = copyObject(heapRel); + atcmd->cmds = atsubcmds; + atcmd->objtype = OBJECT_TABLE; + atcmd->missing_ok = false; + result = lcons(atcmd, result); + } + + /* + * Process indexes if required. */ if ((table_like_clause->options & CREATE_TABLE_LIKE_INDEXES) && relation->rd_rel->relhasindex) @@ -1340,7 +1582,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla parent_index = index_open(parent_index_oid, AccessShareLock); /* Build CREATE INDEX statement to recreate the parent_index */ - index_stmt = generateClonedIndexStmt(cxt->relation, + index_stmt = generateClonedIndexStmt(heapRel, parent_index, attmap, NULL); @@ -1357,102 +1599,14 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla index_stmt->idxcomment = comment; } - /* Save it in the inh_indexes list for the time being */ - cxt->inh_indexes = lappend(cxt->inh_indexes, index_stmt); + result = lappend(result, index_stmt); index_close(parent_index, AccessShareLock); } } - /* - * GPDB_12_MERGE_FIXME: - * This is wrong and creates unspecified behaviour when multiple like - * clauses are present in the statement. - * - * Try to use a unified interface for encoding handling in a manner - * similar to CREATE/ALTER commands. - */ - /* - * If STORAGE is included, we need to copy over the table storage params - * as well as the attribute encodings. - */ - if (stmt && table_like_clause->options & CREATE_TABLE_LIKE_STORAGE) - { - MemoryContext oldcontext; - /* - * As we are modifying the utility statement we must make sure these - * DefElem allocations can survive outside of this context. - */ - oldcontext = MemoryContextSwitchTo(CurTransactionContext); - - if (RelationIsAppendOptimized(relation)) - { - int32 blocksize; - int32 safefswritersize; - int16 compresslevel; - bool checksum; - NameData compresstype; - - GetAppendOnlyEntryAttributes(relation->rd_id, &blocksize, - &safefswritersize,&compresslevel, - &checksum,&compresstype); - - stmt->accessMethod = get_am_name(relation->rd_rel->relam); - - stmt->options = lappend(stmt->options, - makeDefElem("blocksize", (Node *) makeInteger(blocksize), -1)); - stmt->options = lappend(stmt->options, - makeDefElem("checksum", (Node *) makeInteger(checksum), -1)); - stmt->options = lappend(stmt->options, - makeDefElem("compresslevel", (Node *) makeInteger(compresslevel), -1)); - if (strlen(NameStr(compresstype)) > 0) - stmt->options = lappend(stmt->options, - makeDefElem("compresstype", (Node *) makeString(pstrdup(NameStr(compresstype))), -1)); - } - - /* - * Set the attribute encodings. - */ - cxt->attr_encodings = list_union(cxt->attr_encodings, rel_get_column_encodings(relation)); - MemoryContextSwitchTo(oldcontext); - } - - /* - * Likewise, copy extended statistics if requested - */ - if (table_like_clause->options & CREATE_TABLE_LIKE_STATISTICS) - { - List *parent_extstats; - ListCell *l; - - parent_extstats = RelationGetStatExtList(relation); - - foreach(l, parent_extstats) - { - Oid parent_stat_oid = lfirst_oid(l); - CreateStatsStmt *stats_stmt; - - stats_stmt = generateClonedExtStatsStmt(cxt->relation, - RelationGetRelid(relation), - parent_stat_oid); - - /* Copy comment on statistics object, if requested */ - if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) - { - comment = GetComment(parent_stat_oid, StatisticExtRelationId, 0); - - /* - * We make use of CreateStatsStmt's stxcomment option, so as - * not to need to know now what name the statistics will have. - */ - stats_stmt->stxcomment = comment; - } - - cxt->extstats = lappend(cxt->extstats, stats_stmt); - } - - list_free(parent_extstats); - } + /* Done with child rel */ + table_close(childrel, NoLock); /* * Close the parent rel, but keep our AccessShareLock on it until xact @@ -1460,6 +1614,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla * parent before the child is committed. */ table_close(relation, NoLock); + + return result; } static void @@ -1753,7 +1909,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, attmap, InvalidOid, &found_whole_row); - /* As in transformTableLikeClause, reject whole-row variables */ + /* As in expandTableLikeClause, reject whole-row variables */ if (found_whole_row) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -1822,7 +1978,6 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, char *attname; attname = get_attname(indrelid, attnum, false); - keycoltype = get_atttype(indrelid, attnum); iparam->name = attname; iparam->expr = NULL; @@ -1862,7 +2017,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, attmap, InvalidOid, &found_whole_row); - /* As in transformTableLikeClause, reject whole-row variables */ + /* As in expandTableLikeClause, reject whole-row variables */ if (found_whole_row) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -2069,6 +2224,7 @@ transformCreateExternalStmt(CreateExternalStmt *stmt, const char *queryString) cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; + cxt.inh_indexes = NIL; /* GPDB: used by transformDistributedBy */ cxt.attr_encodings = NIL; cxt.pkey = NULL; cxt.rel = NULL; @@ -2982,24 +3138,6 @@ transformIndexConstraints(CreateStmtContext *cxt) indexlist = lappend(indexlist, index); } - /* Add in any indexes defined by LIKE ... INCLUDING INDEXES */ - foreach(lc, cxt->inh_indexes) - { - index = (IndexStmt *) lfirst(lc); - - if (index->primary) - { - if (cxt->pkey != NULL) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TABLE_DEFINITION), - errmsg("multiple primary keys for table \"%s\" are not allowed", - cxt->relation->relname))); - cxt->pkey = index; - } - - indexlist = lappend(indexlist, index); - } - /* * Scan the index list and remove any redundant index specifications. This * can happen if, for instance, the user writes UNIQUE PRIMARY KEY. A @@ -4217,7 +4355,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; - cxt.inh_indexes = NIL; + cxt.inh_indexes = NIL; /* GPDB: used by transformDistributedBy */ cxt.attr_encodings = NIL; cxt.extstats = NIL; cxt.blist = NIL; @@ -5256,7 +5394,7 @@ validateInfiniteBounds(ParseState *pstate, List *blist) } /* - * Transform one constant in a partition bound spec + * Transform one entry in a partition bound spec, producing a constant. */ Const * transformPartitionBoundValue(ParseState *pstate, Node *val, @@ -5269,50 +5407,17 @@ transformPartitionBoundValue(ParseState *pstate, Node *val, value = transformExpr(pstate, val, EXPR_KIND_PARTITION_BOUND); /* - * Check that the input expression's collation is compatible with one - * specified for the parent's partition key (partcollation). Don't throw - * an error if it's the default collation which we'll replace with the - * parent's collation anyway. + * transformExpr() should have already rejected column references, + * subqueries, aggregates, window functions, and SRFs, based on the + * EXPR_KIND_ of a partition bound expression. */ - if (IsA(value, CollateExpr)) - { - Oid exprCollOid = exprCollation(value); - - /* - * Check we have a collation iff it is a collatable type. The only - * expected failures here are (1) COLLATE applied to a noncollatable - * type, or (2) partition bound expression had an unresolved - * collation. But we might as well code this to be a complete - * consistency check. - */ - if (type_is_collatable(colType)) - { - if (!OidIsValid(exprCollOid)) - ereport(ERROR, - (errcode(ERRCODE_INDETERMINATE_COLLATION), - errmsg("could not determine which collation to use for partition bound expression"), - errhint("Use the COLLATE clause to set the collation explicitly."))); - } - else - { - if (OidIsValid(exprCollOid)) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("collations are not supported by type %s", - format_type_be(colType)))); - } - - if (OidIsValid(exprCollOid) && - exprCollOid != DEFAULT_COLLATION_OID && - exprCollOid != partCollation) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("collation of partition bound value for column \"%s\" does not match partition key collation \"%s\"", - colName, get_collation_name(partCollation)), - parser_errposition(pstate, exprLocation(value)))); - } + Assert(!contain_var_clause(value)); - /* Coerce to correct type */ + /* + * Coerce to the correct type. This might cause an explicit coercion step + * to be added on top of the expression, which must be evaluated before + * returning the result to the caller. + */ value = coerce_to_target_type(pstate, value, exprType(value), colType, @@ -5328,25 +5433,36 @@ transformPartitionBoundValue(ParseState *pstate, Node *val, format_type_be(colType), colName), parser_errposition(pstate, exprLocation(val)))); - /* Simplify the expression, in case we had a coercion */ - if (!IsA(value, Const)) - value = (Node *) expression_planner((Expr *) value); - /* - * transformExpr() should have already rejected column references, - * subqueries, aggregates, window functions, and SRFs, based on the - * EXPR_KIND_ for a default expression. + * Evaluate the expression, if needed, assigning the partition key's data + * type and collation to the resulting Const node. */ - Assert(!contain_var_clause(value)); + if (!IsA(value, Const)) + { + assign_expr_collations(pstate, value); + value = (Node *) expression_planner((Expr *) value); + value = (Node *) evaluate_expr((Expr *) value, colType, colTypmod, + partCollation); + if (!IsA(value, Const)) + elog(ERROR, "could not evaluate partition bound expression"); + } + else + { + /* + * If the expression is already a Const, as is often the case, we can + * skip the rather expensive steps above. But we still have to insert + * the right collation, since coerce_to_target_type doesn't handle + * that. + */ + ((Const *) value)->constcollid = partCollation; + } /* - * Evaluate the expression, assigning the partition key's collation to the - * resulting Const expression. + * Attach original expression's parse location to the Const, so that + * that's what will be reported for any later errors related to this + * partition bound. */ - value = (Node *) evaluate_expr((Expr *) value, colType, colTypmod, - partCollation); - if (!IsA(value, Const)) - elog(ERROR, "could not evaluate partition bound expression"); + ((Const *) value)->location = exprLocation(val); return (Const *) value; } diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l index 50ba68abd4f5..56ecdda01ad5 100644 --- a/src/backend/parser/scan.l +++ b/src/backend/parser/scan.l @@ -73,7 +73,7 @@ bool standard_conforming_strings = true; * callers need to pass it to scanner_init, if they are using the * standard keyword list ScanKeywords. */ -#define PG_KEYWORD(kwname, value, category) value, +#define PG_KEYWORD(kwname, value, category, collabel) value, const uint16 ScanKeywordTokens[] = { #include "parser/kwlist.h" diff --git a/src/backend/parser/scansup.c b/src/backend/parser/scansup.c index cac70d5df7af..d07cbafcee75 100644 --- a/src/backend/parser/scansup.c +++ b/src/backend/parser/scansup.c @@ -1,8 +1,7 @@ /*------------------------------------------------------------------------- * * scansup.c - * support routines for the lex/flex scanner, used by both the normal - * backend as well as the bootstrap backend + * scanner support routines used by the core lexer * * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -20,98 +19,6 @@ #include "mb/pg_wchar.h" #include "parser/scansup.h" -/* ---------------- - * scanstr - * - * if the string passed in has escaped codes, map the escape codes to actual - * chars - * - * the string returned is palloc'd and should eventually be pfree'd by the - * caller! - * ---------------- - */ - -char * -scanstr(const char *s) -{ - char *newStr; - int len, - i, - j; - - if (s == NULL || s[0] == '\0') - return pstrdup(""); - - len = strlen(s); - - newStr = palloc(len + 1); /* string cannot get longer */ - - for (i = 0, j = 0; i < len; i++) - { - if (s[i] == '\'') - { - /* - * Note: if scanner is working right, unescaped quotes can only - * appear in pairs, so there should be another character. - */ - i++; - /* The bootstrap parser is not as smart, so check here. */ - Assert(s[i] == '\''); - newStr[j] = s[i]; - } - else if (s[i] == '\\') - { - i++; - switch (s[i]) - { - case 'b': - newStr[j] = '\b'; - break; - case 'f': - newStr[j] = '\f'; - break; - case 'n': - newStr[j] = '\n'; - break; - case 'r': - newStr[j] = '\r'; - break; - case 't': - newStr[j] = '\t'; - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - { - int k; - long octVal = 0; - - for (k = 0; - s[i + k] >= '0' && s[i + k] <= '7' && k < 3; - k++) - octVal = (octVal << 3) + (s[i + k] - '0'); - i += k - 1; - newStr[j] = ((char) octVal); - } - break; - default: - newStr[j] = s[i]; - break; - } /* switch */ - } /* s[i] == '\\' */ - else - newStr[j] = s[i]; - j++; - } - newStr[j] = '\0'; - return newStr; -} - /* * downcase_truncate_identifier() --- do appropriate downcasing and diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c index b36de561ff36..23e1aa9d8a06 100644 --- a/src/backend/partitioning/partbounds.c +++ b/src/backend/partitioning/partbounds.c @@ -223,7 +223,7 @@ static int32 partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc, static int partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc, Oid *partcollation, PartitionBoundInfo boundinfo, - PartitionRangeBound *probe, bool *is_equal); + PartitionRangeBound *probe, int32 *cmpval); static int get_partition_bound_num_indexes(PartitionBoundInfo b); static Expr *make_partition_op_expr(PartitionKey key, int keynum, uint16 strategy, Expr *arg1, Expr *arg2); @@ -1020,8 +1020,6 @@ partition_bounds_merge(int partnatts, JoinType jointype, List **outer_parts, List **inner_parts) { - PartitionBoundInfo outer_binfo = outer_rel->boundinfo; - /* * Currently, this function is called only from try_partitionwise_join(), * so the join type should be INNER, LEFT, FULL, SEMI, or ANTI. @@ -1031,10 +1029,10 @@ partition_bounds_merge(int partnatts, jointype == JOIN_ANTI); /* The partitioning strategies should be the same. */ - Assert(outer_binfo->strategy == inner_rel->boundinfo->strategy); + Assert(outer_rel->boundinfo->strategy == inner_rel->boundinfo->strategy); *outer_parts = *inner_parts = NIL; - switch (outer_binfo->strategy) + switch (outer_rel->boundinfo->strategy) { case PARTITION_STRATEGY_HASH: @@ -1075,7 +1073,7 @@ partition_bounds_merge(int partnatts, default: elog(ERROR, "unexpected partition strategy: %d", - (int) outer_binfo->strategy); + (int) outer_rel->boundinfo->strategy); return NULL; /* keep compiler quiet */ } } @@ -1528,7 +1526,7 @@ merge_range_bounds(int partnatts, FmgrInfo *partsupfuncs, &next_index); Assert(merged_index >= 0); - /* Get the range of the merged partition. */ + /* Get the range bounds of the merged partition. */ get_merged_range_bounds(partnatts, partsupfuncs, partcollations, jointype, &outer_lb, &outer_ub, @@ -1785,7 +1783,7 @@ merge_matching_partitions(PartitionMap *outer_map, PartitionMap *inner_map, if (outer_merged_index >= 0 && inner_merged_index >= 0) { /* - * If the mereged partitions are the same, no need to do anything; + * If the merged partitions are the same, no need to do anything; * return the index of the merged partitions. Otherwise, if each of * the given partitions has been merged with a dummy partition on the * other side, re-map them to either of the two merged partitions. @@ -1833,7 +1831,7 @@ merge_matching_partitions(PartitionMap *outer_map, PartitionMap *inner_map, /* * If neither of them has been merged, merge them. Otherwise, if one has - * been merged with a dummy relation on the other side (and the other + * been merged with a dummy partition on the other side (and the other * hasn't yet been merged with anything), re-merge them. Otherwise, they * can't be merged, so return -1. */ @@ -2705,10 +2703,10 @@ add_merged_range_bounds(int partnatts, FmgrInfo *partsupfuncs, prev_ub.lower = false; /* - * We pass to partition_rbound_cmp() lower1 as false to prevent it - * from considering the last upper bound to be smaller than the lower - * bound of the merged partition when the values of the two range - * bounds compare equal. + * We pass lower1 = false to partition_rbound_cmp() to prevent it from + * considering the last upper bound to be smaller than the lower bound + * of the merged partition when the values of the two range bounds + * compare equal. */ cmpval = partition_rbound_cmp(partnatts, partsupfuncs, partcollations, merged_lb->datums, merged_lb->kind, @@ -2807,14 +2805,14 @@ partitions_are_ordered(PartitionBoundInfo boundinfo, int nparts) */ void check_new_partition_bound(char *relname, Relation parent, - PartitionBoundSpec *spec) + PartitionBoundSpec *spec, ParseState *pstate) { PartitionKey key = RelationGetPartitionKey(parent); PartitionDesc partdesc = RelationGetPartitionDesc(parent); PartitionBoundInfo boundinfo = partdesc->boundinfo; - ParseState *pstate = make_parsestate(NULL); int with = -1; bool overlap = false; + int overlap_location = -1; if (spec->is_default) { @@ -2909,6 +2907,7 @@ check_new_partition_bound(char *relname, Relation parent, if (boundinfo->indexes[remainder] != -1) { overlap = true; + overlap_location = spec->location; with = boundinfo->indexes[remainder]; break; } @@ -2938,6 +2937,7 @@ check_new_partition_bound(char *relname, Relation parent, { Const *val = castNode(Const, lfirst(cell)); + overlap_location = val->location; if (!val->constisnull) { int offset; @@ -2971,6 +2971,7 @@ check_new_partition_bound(char *relname, Relation parent, { PartitionRangeBound *lower, *upper; + int cmpval; Assert(spec->strategy == PARTITION_STRATEGY_RANGE); lower = make_one_partition_rbound(key, -1, spec->lowerdatums, true); @@ -2978,12 +2979,22 @@ check_new_partition_bound(char *relname, Relation parent, /* * First check if the resulting range would be empty with - * specified lower and upper bounds + * specified lower and upper bounds. partition_rbound_cmp + * cannot return zero here, since the lower-bound flags are + * different. */ - if (partition_rbound_cmp(key->partnatts, key->partsupfunc, - key->partcollation, lower->datums, - lower->kind, true, upper) >= 0) + cmpval = partition_rbound_cmp(key->partnatts, + key->partsupfunc, + key->partcollation, + lower->datums, lower->kind, + true, upper); + Assert(cmpval != 0); + if (cmpval > 0) { + /* Point to problematic key in the lower datums list. */ + PartitionRangeDatum *datum = list_nth(spec->lowerdatums, + cmpval - 1); + ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("empty range bound specified for partition \"%s\"", @@ -2991,13 +3002,12 @@ check_new_partition_bound(char *relname, Relation parent, errdetail("Specified lower bound %s is greater than or equal to upper bound %s.", get_range_partbound_string(spec->lowerdatums), get_range_partbound_string(spec->upperdatums)), - parser_errposition(pstate, spec->location))); + parser_errposition(pstate, datum->location))); } if (partdesc->nparts > 0) { int offset; - bool equal; Assert(boundinfo && boundinfo->strategy == PARTITION_STRATEGY_RANGE && @@ -3023,7 +3033,7 @@ check_new_partition_bound(char *relname, Relation parent, key->partsupfunc, key->partcollation, boundinfo, lower, - &equal); + &cmpval); if (boundinfo->indexes[offset + 1] < 0) { @@ -3035,7 +3045,6 @@ check_new_partition_bound(char *relname, Relation parent, */ if (offset + 1 < boundinfo->ndatums) { - int32 cmpval; Datum *datums; PartitionRangeDatumKind *kind; bool is_lower; @@ -3051,12 +3060,20 @@ check_new_partition_bound(char *relname, Relation parent, is_lower, upper); if (cmpval < 0) { + /* + * Point to problematic key in the upper + * datums list. + */ + PartitionRangeDatum *datum = + list_nth(spec->upperdatums, Abs(cmpval) - 1); + /* * The new partition overlaps with the * existing partition between offset + 1 and * offset + 2. */ overlap = true; + overlap_location = datum->location; with = boundinfo->indexes[offset + 2]; } } @@ -3067,7 +3084,16 @@ check_new_partition_bound(char *relname, Relation parent, * The new partition overlaps with the existing * partition between offset and offset + 1. */ + PartitionRangeDatum *datum; + + /* + * Point to problematic key in the lower datums list; + * if we have equality, point to the first one. + */ + datum = cmpval == 0 ? linitial(spec->lowerdatums) : + list_nth(spec->lowerdatums, Abs(cmpval) - 1); overlap = true; + overlap_location = datum->location; with = boundinfo->indexes[offset + 1]; } } @@ -3087,7 +3113,7 @@ check_new_partition_bound(char *relname, Relation parent, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("partition \"%s\" would overlap partition \"%s\"", relname, get_rel_name(partdesc->oids[with])), - parser_errposition(pstate, spec->location))); + parser_errposition(pstate, overlap_location))); } } @@ -3320,8 +3346,12 @@ make_one_partition_rbound(PartitionKey key, int index, List *datums, bool lower) /* * partition_rbound_cmp * - * Return for two range bounds whether the 1st one (specified in datums1, - * kind1, and lower1) is <, =, or > the bound specified in *b2. + * For two range bounds this decides whether the 1st one (specified by + * datums1, kind1, and lower1) is <, =, or > the bound specified in *b2. + * + * 0 is returned if they are equal, otherwise a non-zero integer whose sign + * indicates the ordering, and whose absolute value gives the 1-based + * partition key number of the first mismatching column. * * partnatts, partsupfunc and partcollation give the number of attributes in the * bounds to be compared, comparison function to be used and the collations of @@ -3340,6 +3370,7 @@ partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc, Datum *datums1, PartitionRangeDatumKind *kind1, bool lower1, PartitionRangeBound *b2) { + int32 colnum = 0; int32 cmpval = 0; /* placate compiler */ int i; Datum *datums2 = b2->datums; @@ -3348,6 +3379,9 @@ partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc, for (i = 0; i < partnatts; i++) { + /* Track column number in case we need it for result */ + colnum++; + /* * First, handle cases where the column is unbounded, which should not * invoke the comparison procedure, and should not consider any later @@ -3355,17 +3389,18 @@ partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc, * compare the same way as the values they represent. */ if (kind1[i] < kind2[i]) - return -1; + return -colnum; else if (kind1[i] > kind2[i]) - return 1; + return colnum; else if (kind1[i] != PARTITION_RANGE_DATUM_VALUE) - + { /* * The column bounds are both MINVALUE or both MAXVALUE. No later * columns should be considered, but we still need to compare * whether they are upper or lower bounds. */ break; + } cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i], partcollation[i], @@ -3384,7 +3419,7 @@ partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc, if (cmpval == 0 && lower1 != lower2) cmpval = lower1 ? 1 : -1; - return cmpval; + return cmpval == 0 ? 0 : (cmpval < 0 ? -colnum : colnum); } /* @@ -3396,7 +3431,6 @@ partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc, * n_tuple_datums, partsupfunc and partcollation give number of attributes in * the bounds to be compared, comparison function to be used and the collations * of attributes resp. - * */ int32 partition_rbound_datum_cmp(FmgrInfo *partsupfunc, Oid *partcollation, @@ -3489,14 +3523,17 @@ partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation, * equal to the given range bound or -1 if all of the range bounds are * greater * - * *is_equal is set to true if the range bound at the returned index is equal - * to the input range bound + * Upon return from this function, *cmpval is set to 0 if the bound at the + * returned index matches the input range bound exactly, otherwise a + * non-zero integer whose sign indicates the ordering, and whose absolute + * value gives the 1-based partition key number of the first mismatching + * column. */ static int partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc, Oid *partcollation, PartitionBoundInfo boundinfo, - PartitionRangeBound *probe, bool *is_equal) + PartitionRangeBound *probe, int32 *cmpval) { int lo, hi, @@ -3506,21 +3543,17 @@ partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc, hi = boundinfo->ndatums - 1; while (lo < hi) { - int32 cmpval; - mid = (lo + hi + 1) / 2; - cmpval = partition_rbound_cmp(partnatts, partsupfunc, - partcollation, - boundinfo->datums[mid], - boundinfo->kind[mid], - (boundinfo->indexes[mid] == -1), - probe); - if (cmpval <= 0) + *cmpval = partition_rbound_cmp(partnatts, partsupfunc, + partcollation, + boundinfo->datums[mid], + boundinfo->kind[mid], + (boundinfo->indexes[mid] == -1), + probe); + if (*cmpval <= 0) { lo = mid; - *is_equal = (cmpval == 0); - - if (*is_equal) + if (*cmpval == 0) break; } else @@ -3531,7 +3564,7 @@ partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc, } /* - * partition_range_bsearch + * partition_range_datum_bsearch * Returns the index of the greatest range bound that is less than or * equal to the given tuple or -1 if all of the range bounds are greater * @@ -3660,9 +3693,9 @@ qsort_partition_rbound_cmp(const void *a, const void *b, void *arg) PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b); PartitionKey key = (PartitionKey) arg; - return partition_rbound_cmp(key->partnatts, key->partsupfunc, - key->partcollation, b1->datums, b1->kind, - b1->lower, b2); + return compare_range_bounds(key->partnatts, key->partsupfunc, + key->partcollation, + b1, b2); } /* @@ -4260,10 +4293,6 @@ get_qual_for_range(Relation parent, PartitionBoundSpec *spec, return result; } - lower_or_start_datum = list_head(spec->lowerdatums); - upper_or_start_datum = list_head(spec->upperdatums); - num_or_arms = key->partnatts; - /* * If it is the recursive call for default, we skip the get_range_nulltest * to avoid accumulating the NullTest on the same keys for each partition. diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index f3b6d3fbe547..35468476da6f 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -145,7 +145,7 @@ static List *make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, int *relid_subplan_map, Relids available_relids, - List *partitioned_rels, List *prunequal, + Relids partrelids, List *prunequal, Bitmapset **matchedsubplans); static void gen_partprune_steps(RelOptInfo *rel, List *clauses, Relids available_relids, @@ -287,14 +287,14 @@ make_partition_pruneinfo_ext(PlannerInfo *root, RelOptInfo *parentrel, prunerelinfos = NIL; foreach(lc, partitioned_rels) { - List *rels = (List *) lfirst(lc); + Relids partrelids = (Relids) lfirst(lc); List *pinfolist; Bitmapset *matchedsubplans = NULL; pinfolist = make_partitionedrel_pruneinfo(root, parentrel, relid_subplan_map, available_relids, - rels, prunequal, + partrelids, prunequal, &matchedsubplans); /* When pruning is possible, record the matched subplans */ @@ -364,7 +364,7 @@ static List * make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, int *relid_subplan_map, Relids available_relids, - List *partitioned_rels, List *prunequal, + Relids partrelids, List *prunequal, Bitmapset **matchedsubplans) { RelOptInfo *targetpart = NULL; @@ -373,6 +373,7 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, int *relid_subpart_map; Bitmapset *subplansfound = NULL; ListCell *lc; + int rti; int i; /* @@ -386,9 +387,9 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, relid_subpart_map = palloc0(sizeof(int) * root->simple_rel_array_size); i = 1; - foreach(lc, partitioned_rels) + rti = -1; + while ((rti = bms_next_member(partrelids, rti)) > 0) { - Index rti = lfirst_int(lc); RelOptInfo *subpart = find_base_rel(root, rti); PartitionedRelPruneInfo *pinfo; List *partprunequal; @@ -401,14 +402,11 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * Fill the mapping array. * * relid_subpart_map maps relid of a non-leaf partition to the index - * in 'partitioned_rels' of that rel (which will also be the index in - * the returned PartitionedRelPruneInfo list of the info for that - * partition). We use 1-based indexes here, so that zero can - * represent an un-filled array entry. + * in the returned PartitionedRelPruneInfo list of the info for that + * partition. We use 1-based indexes here, so that zero can represent + * an un-filled array entry. */ Assert(rti < root->simple_rel_array_size); - /* No duplicates please */ - Assert(relid_subpart_map[rti] == 0); relid_subpart_map[rti] = i++; /* @@ -606,6 +604,13 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, present_parts = bms_add_member(present_parts, i); } + /* + * Ensure there were no stray PartitionedRelPruneInfo generated for + * partitioned tables that we have no sub-paths or + * sub-PartitionedRelPruneInfo for. + */ + Assert(!bms_is_empty(present_parts)); + /* Record the maps and other information. */ pinfo->present_parts = present_parts; pinfo->nparts = nparts; diff --git a/src/backend/port/win32/crashdump.c b/src/backend/port/win32/crashdump.c index e6c68379b20e..47114d916cc1 100644 --- a/src/backend/port/win32/crashdump.c +++ b/src/backend/port/win32/crashdump.c @@ -122,7 +122,7 @@ crashDumpHandler(struct _EXCEPTION_POINTERS *pExceptionInfo) return EXCEPTION_CONTINUE_SEARCH; } - pDump = (MINIDUMPWRITEDUMP) GetProcAddress(hDll, "MiniDumpWriteDump"); + pDump = (MINIDUMPWRITEDUMP) (pg_funcptr_t) GetProcAddress(hDll, "MiniDumpWriteDump"); if (pDump == NULL) { diff --git a/src/backend/port/win32/socket.c b/src/backend/port/win32/socket.c index 6fbd1ed6fb49..7c7611a01e23 100644 --- a/src/backend/port/win32/socket.c +++ b/src/backend/port/win32/socket.c @@ -120,13 +120,21 @@ TranslateSocketError(void) case WSAEADDRNOTAVAIL: errno = EADDRNOTAVAIL; break; - case WSAEHOSTUNREACH: case WSAEHOSTDOWN: + errno = EHOSTDOWN; + break; + case WSAEHOSTUNREACH: case WSAHOST_NOT_FOUND: + errno = EHOSTUNREACH; + break; case WSAENETDOWN: + errno = ENETDOWN; + break; case WSAENETUNREACH: + errno = ENETUNREACH; + break; case WSAENETRESET: - errno = EHOSTUNREACH; + errno = ENETRESET; break; case WSAENOTCONN: case WSAESHUTDOWN: diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 03458f53d61f..85c82ed9f24e 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -506,8 +506,8 @@ AutoVacLauncherMain(int argc, char *argv[]) pqsignal(SIGHUP, SignalHandlerForConfigReload); pqsignal(SIGINT, StatementCancelHandler); pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ - pqsignal(SIGQUIT, quickdie); InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); @@ -547,6 +547,13 @@ AutoVacLauncherMain(int argc, char *argv[]) * If an exception is encountered, processing resumes here. * * This code is a stripped down version of PostgresMain error recovery. + * + * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask + * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus, + * signals other than SIGQUIT will be blocked until we complete error + * recovery. It might seem that this policy makes the HOLD_INTERRUPTS() + * call redundant, but it is not since InterruptPending might be set + * already. */ if (sigsetjmp(local_sigjmp_buf, 1) != 0) { @@ -1603,7 +1610,8 @@ AutoVacWorkerMain(int argc, char *argv[]) */ pqsignal(SIGINT, StatementCancelHandler); pqsignal(SIGTERM, die); - pqsignal(SIGQUIT, quickdie); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); @@ -1628,7 +1636,15 @@ AutoVacWorkerMain(int argc, char *argv[]) /* * If an exception is encountered, processing resumes here. * - * See notes in postgres.c about the design of this coding. + * Unlike most auxiliary processes, we don't attempt to continue + * processing after an error; we just clean up and exit. The autovac + * launcher is responsible for spawning another worker later. + * + * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask + * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus, + * signals other than SIGQUIT will be blocked until we exit. It might + * seem that this policy makes the HOLD_INTERRUPTS() call redundant, but + * it is not since InterruptPending might be set already. */ if (sigsetjmp(local_sigjmp_buf, 1) != 0) { @@ -3193,6 +3209,10 @@ relation_needs_vacanalyze(Oid relid, instuples = tabentry->inserts_since_vacuum; anltuples = tabentry->changes_since_analyze; + /* If the table hasn't yet been vacuumed, take reltuples as zero */ + if (reltuples < 0) + reltuples = 0; + vacthresh = (float4) vac_base_thresh + vac_scale_factor * reltuples; vacinsthresh = (float4) vac_ins_base_thresh + vac_ins_scale_factor * reltuples; anlthresh = (float4) anl_base_thresh + anl_scale_factor * reltuples; diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c index 5b4ed8858634..a98a88c14229 100644 --- a/src/backend/postmaster/bgworker.c +++ b/src/backend/postmaster/bgworker.c @@ -775,9 +775,9 @@ StartBackgroundWorker(void) pqsignal(SIGFPE, SIG_IGN); } pqsignal(SIGTERM, bgworker_die); + /* SIGQUIT handler was already set up by InitPostmasterChild */ pqsignal(SIGHUP, SIG_IGN); - pqsignal(SIGQUIT, SignalHandlerForCrashExit); InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); @@ -787,7 +787,7 @@ StartBackgroundWorker(void) /* * If an exception is encountered, processing resumes here. * - * See notes in postgres.c about the design of this coding. + * We just need to clean up, report the error, and go away. */ if (sigsetjmp(local_sigjmp_buf, 1) != 0) { @@ -797,7 +797,14 @@ StartBackgroundWorker(void) /* Prevent interrupts while cleaning up */ HOLD_INTERRUPTS(); - /* Report the error to the server log */ + /* + * sigsetjmp will have blocked all signals, but we may need to accept + * signals while communicating with our parallel leader. Once we've + * done HOLD_INTERRUPTS() it should be safe to unblock signals. + */ + BackgroundWorkerUnblockSignals(); + + /* Report the error to the parallel leader and the server log */ EmitErrorReport(); /* diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c index 3b6f7f8b784c..46785c34b3e0 100644 --- a/src/backend/postmaster/bgwriter.c +++ b/src/backend/postmaster/bgwriter.c @@ -118,9 +118,6 @@ BackgroundWriterMain(void) */ pqsignal(SIGCHLD, SIG_DFL); - /* We allow SIGQUIT (quickdie) at all times */ - sigdelset(&BlockSig, SIGQUIT); - /* * We just started, assume there has been either a shutdown or * end-of-recovery snapshot. @@ -143,7 +140,20 @@ BackgroundWriterMain(void) /* * If an exception is encountered, processing resumes here. * - * See notes in postgres.c about the design of this coding. + * You might wonder why this isn't coded as an infinite loop around a + * PG_TRY construct. The reason is that this is the bottom of the + * exception stack, and so with PG_TRY there would be no exception handler + * in force at all during the CATCH part. By leaving the outermost setjmp + * always active, we have at least some chance of recovering from an error + * during error recovery. (If we get into an infinite loop thereby, it + * will soon be stopped by overflow of elog.c's internal state stack.) + * + * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask + * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus, + * signals other than SIGQUIT will be blocked until we complete error + * recovery. It might seem that this policy makes the HOLD_INTERRUPTS() + * call redundant, but it is not since InterruptPending might be set + * already. */ if (sigsetjmp(local_sigjmp_buf, 1) != 0) { diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c index 9ae26ca338a1..640acfb3334b 100644 --- a/src/backend/postmaster/checkpointer.c +++ b/src/backend/postmaster/checkpointer.c @@ -199,7 +199,7 @@ CheckpointerMain(void) pqsignal(SIGHUP, SignalHandlerForConfigReload); pqsignal(SIGINT, ReqCheckpointHandler); /* request checkpoint */ pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */ - pqsignal(SIGQUIT, SignalHandlerForCrashExit); + /* SIGQUIT handler was already set up by InitPostmasterChild */ pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); pqsignal(SIGUSR1, procsignal_sigusr1_handler); @@ -210,9 +210,6 @@ CheckpointerMain(void) */ pqsignal(SIGCHLD, SIG_DFL); - /* We allow SIGQUIT (quickdie) at all times */ - sigdelset(&BlockSig, SIGQUIT); - /* * Initialize so that first time-driven event happens at the correct time. */ @@ -232,7 +229,20 @@ CheckpointerMain(void) /* * If an exception is encountered, processing resumes here. * - * See notes in postgres.c about the design of this coding. + * You might wonder why this isn't coded as an infinite loop around a + * PG_TRY construct. The reason is that this is the bottom of the + * exception stack, and so with PG_TRY there would be no exception handler + * in force at all during the CATCH part. By leaving the outermost setjmp + * always active, we have at least some chance of recovering from an error + * during error recovery. (If we get into an infinite loop thereby, it + * will soon be stopped by overflow of elog.c's internal state stack.) + * + * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask + * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus, + * signals other than SIGQUIT will be blocked until we complete error + * recovery. It might seem that this policy makes the HOLD_INTERRUPTS() + * call redundant, but it is not since InterruptPending might be set + * already. */ if (sigsetjmp(local_sigjmp_buf, 1) != 0) { @@ -495,6 +505,9 @@ CheckpointerMain(void) */ pgstat_send_bgwriter(); + /* Send WAL statistics to the stats collector. */ + pgstat_send_wal(); + /* * If any checkpoint flags have been set, redo the loop to handle the * checkpoint without sleeping. diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c index 3d02439b79ce..ee7dbf924ae6 100644 --- a/src/backend/postmaster/interrupt.c +++ b/src/backend/postmaster/interrupt.c @@ -92,7 +92,7 @@ SignalHandlerForCrashExit(SIGNAL_ARGS) * Simple signal handler for triggering a long-running background process to * shut down and exit. * - * Typically, this handler would be used for SIGTERM, but some procesess use + * Typically, this handler would be used for SIGTERM, but some processes use * other signals. In particular, the checkpointer exits on SIGUSR2, the * stats collector on SIGQUIT, and the WAL writer exits on either SIGINT * or SIGTERM. diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index a5a64855db3c..0e3ab69ff6a4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -101,7 +101,6 @@ static pid_t pgarch_forkexec(void); #endif NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -static void pgarch_exit(SIGNAL_ARGS); static void pgarch_waken(SIGNAL_ARGS); static void pgarch_waken_stop(SIGNAL_ARGS); static void pgarch_MainLoop(void); @@ -234,7 +233,7 @@ PgArchiverMain(int argc, char *argv[]) pqsignal(SIGHUP, SignalHandlerForConfigReload); pqsignal(SIGINT, SIG_IGN); pqsignal(SIGTERM, SignalHandlerForShutdownRequest); - pqsignal(SIGQUIT, pgarch_exit); + /* SIGQUIT handler was already set up by InitPostmasterChild */ pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); pqsignal(SIGUSR1, pgarch_waken); @@ -251,14 +250,6 @@ PgArchiverMain(int argc, char *argv[]) exit(0); } -/* SIGQUIT signal handler for archiver process */ -static void -pgarch_exit(SIGNAL_ARGS) -{ - /* SIGQUIT means curl up and die ... */ - exit(1); -} - /* SIGUSR1 signal handler for archiver process */ static void pgarch_waken(SIGNAL_ARGS) diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 580a270439b0..9727c6965603 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -53,6 +53,7 @@ #include "postmaster/fork_process.h" #include "postmaster/interrupt.h" #include "postmaster/postmaster.h" +#include "replication/slot.h" #include "replication/walsender.h" #include "storage/backendid.h" #include "storage/dsm.h" @@ -150,11 +151,12 @@ char *pgstat_stat_filename = NULL; char *pgstat_stat_tmpname = NULL; /* - * BgWriter global statistics counters (unused in other processes). - * Stored directly in a stats message structure so it can be sent - * without needing to copy things around. We assume this inits to zeroes. + * BgWriter and WAL global statistics counters. + * Stored directly in a stats message structure so they can be sent + * without needing to copy things around. We assume these init to zeroes. */ PgStat_MsgBgWriter BgWriterStats; +PgStat_MsgWal WalStats; /* * List of SLRU names that we keep stats for. There is no central registry of @@ -305,7 +307,10 @@ static int localNumBackends = 0; */ static PgStat_ArchiverStats archiverStats; static PgStat_GlobalStats globalStats; +static PgStat_WalStats walStats; static PgStat_SLRUStats slruStats[SLRU_NUM_ELEMENTS]; +static PgStat_ReplSlotStats *replSlotStats; +static int nReplSlotStats; /* * List of OIDs of databases we need to write out. If an entry is InvalidOid, @@ -355,6 +360,9 @@ static void pgstat_read_current_status(void); static bool pgstat_write_statsfile_needed(void); static bool pgstat_db_requested(Oid databaseid); +static int pgstat_replslot_index(const char *name, bool create_it); +static void pgstat_reset_replslot(int i, TimestampTz ts); + static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg); static void pgstat_send_funcstats(void); static void pgstat_send_slru(void); @@ -381,18 +389,21 @@ static void pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len); static void pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len); static void pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter *msg, int len); static void pgstat_recv_resetslrucounter(PgStat_MsgResetslrucounter *msg, int len); +static void pgstat_recv_resetreplslotcounter(PgStat_MsgResetreplslotcounter *msg, int len); static void pgstat_recv_autovac(PgStat_MsgAutovacStart *msg, int len); static void pgstat_recv_vacuum(PgStat_MsgVacuum *msg, int len); static void pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len); static void pgstat_recv_archiver(PgStat_MsgArchiver *msg, int len); static void pgstat_recv_queuestat(PgStat_MsgQueuestat *msg, int len); /* GPDB */ static void pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len); +static void pgstat_recv_wal(PgStat_MsgWal *msg, int len); static void pgstat_recv_slru(PgStat_MsgSLRU *msg, int len); static void pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len); static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len); static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len); static void pgstat_recv_deadlock(PgStat_MsgDeadlock *msg, int len); static void pgstat_recv_checksum_failure(PgStat_MsgChecksumFailure *msg, int len); +static void pgstat_recv_replslot(PgStat_MsgReplSlot *msg, int len); static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len); /* ------------------------------------------------------------ @@ -973,6 +984,9 @@ pgstat_report_stat(bool force) /* Now, send function statistics */ pgstat_send_funcstats(); + /* Send WAL statistics */ + pgstat_send_wal(); + /* Finally send SLRU statistics */ pgstat_send_slru(); } @@ -1405,11 +1419,13 @@ pgstat_reset_shared_counters(const char *target) msg.m_resettarget = RESET_ARCHIVER; else if (strcmp(target, "bgwriter") == 0) msg.m_resettarget = RESET_BGWRITER; + else if (strcmp(target, "wal") == 0) + msg.m_resettarget = RESET_WAL; else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognized reset target: \"%s\"", target), - errhint("Target must be \"archiver\" or \"bgwriter\"."))); + errhint("Target must be \"archiver\", \"bgwriter\" or \"wal\"."))); pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER); pgstat_send(&msg, sizeof(msg)); @@ -1464,6 +1480,61 @@ pgstat_reset_slru_counter(const char *name) pgstat_send(&msg, sizeof(msg)); } +/* ---------- + * pgstat_reset_replslot_counter() - + * + * Tell the statistics collector to reset a single replication slot + * counter, or all replication slots counters (when name is null). + * + * Permission checking for this function is managed through the normal + * GRANT system. + * ---------- + */ +void +pgstat_reset_replslot_counter(const char *name) +{ + PgStat_MsgResetreplslotcounter msg; + + if (pgStatSock == PGINVALID_SOCKET) + return; + + if (name) + { + ReplicationSlot *slot; + + /* + * Check if the slot exits with the given name. It is possible that by + * the time this message is executed the slot is dropped but at least + * this check will ensure that the given name is for a valid slot. + */ + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); + slot = SearchNamedReplicationSlot(name); + LWLockRelease(ReplicationSlotControlLock); + + if (!slot) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("replication slot \"%s\" does not exist", + name))); + + /* + * Nothing to do for physical slots as we collect stats only for + * logical slots. + */ + if (SlotIsPhysical(slot)) + return; + + memcpy(&msg.m_slotname, name, NAMEDATALEN); + msg.clearall = false; + } + else + msg.clearall = true; + + pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETREPLSLOTCOUNTER); + + pgstat_send(&msg, sizeof(msg)); +} + /* ---------- * pgstat_report_autovac() - * @@ -1664,6 +1735,49 @@ pgstat_report_tempfile(size_t filesize) pgstat_send(&msg, sizeof(msg)); } +/* ---------- + * pgstat_report_replslot() - + * + * Tell the collector about replication slot statistics. + * ---------- + */ +void +pgstat_report_replslot(const char *slotname, int spilltxns, int spillcount, + int spillbytes, int streamtxns, int streamcount, int streambytes) +{ + PgStat_MsgReplSlot msg; + + /* + * Prepare and send the message + */ + pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_REPLSLOT); + memcpy(&msg.m_slotname, slotname, NAMEDATALEN); + msg.m_drop = false; + msg.m_spill_txns = spilltxns; + msg.m_spill_count = spillcount; + msg.m_spill_bytes = spillbytes; + msg.m_stream_txns = streamtxns; + msg.m_stream_count = streamcount; + msg.m_stream_bytes = streambytes; + pgstat_send(&msg, sizeof(PgStat_MsgReplSlot)); +} + +/* ---------- + * pgstat_report_replslot_drop() - + * + * Tell the collector about dropping the replication slot. + * ---------- + */ +void +pgstat_report_replslot_drop(const char *slotname) +{ + PgStat_MsgReplSlot msg; + + pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_REPLSLOT); + memcpy(&msg.m_slotname, slotname, NAMEDATALEN); + msg.m_drop = true; + pgstat_send(&msg, sizeof(PgStat_MsgReplSlot)); +} /* ---------- * pgstat_ping() - @@ -2709,6 +2823,21 @@ pgstat_fetch_global(void) return &globalStats; } +/* + * --------- + * pgstat_fetch_stat_wal() - + * + * Support function for the SQL-callable pgstat* functions. Returns + * a pointer to the WAL statistics struct. + * --------- + */ +PgStat_WalStats * +pgstat_fetch_stat_wal(void) +{ + backend_read_statsfile(); + + return &walStats; +} /* * --------- @@ -2726,6 +2855,23 @@ pgstat_fetch_slru(void) return slruStats; } +/* + * --------- + * pgstat_fetch_replslot() - + * + * Support function for the SQL-callable pgstat* functions. Returns + * a pointer to the replication slot statistics struct and sets the + * number of entries in nslots_p. + * --------- + */ +PgStat_ReplSlotStats * +pgstat_fetch_replslot(int *nslots_p) +{ + backend_read_statsfile(); + + *nslots_p = nReplSlotStats; + return replSlotStats; +} /* ------------------------------------------------------------ * Functions for management of the shared-memory PgBackendStatus array @@ -4082,6 +4228,9 @@ pgstat_get_wait_io(WaitEventIO w) case WAIT_EVENT_BUFFILE_WRITE: event_name = "BufFileWrite"; break; + case WAIT_EVENT_BUFFILE_TRUNCATE: + event_name = "BufFileTruncate"; + break; case WAIT_EVENT_CONTROL_FILE_READ: event_name = "ControlFileRead"; break; @@ -4280,6 +4429,18 @@ pgstat_get_wait_io(WaitEventIO w) case WAIT_EVENT_WAL_WRITE: event_name = "WALWrite"; break; + case WAIT_EVENT_LOGICAL_CHANGES_READ: + event_name = "LogicalChangesRead"; + break; + case WAIT_EVENT_LOGICAL_CHANGES_WRITE: + event_name = "LogicalChangesWrite"; + break; + case WAIT_EVENT_LOGICAL_SUBXACT_READ: + event_name = "LogicalSubxactRead"; + break; + case WAIT_EVENT_LOGICAL_SUBXACT_WRITE: + event_name = "LogicalSubxactWrite"; + break; /* no default case, so that compiler will warn */ } @@ -4794,6 +4955,38 @@ pgstat_combine_from_qe(CdbDispatchResults *results, int writerSliceIndex) } } +/* ---------- + * pgstat_send_wal() - + * + * Send WAL statistics to the collector + * ---------- + */ +void +pgstat_send_wal(void) +{ + /* We assume this initializes to zeroes */ + static const PgStat_MsgWal all_zeroes; + + /* + * This function can be called even if nothing at all has happened. In + * this case, avoid sending a completely empty message to the stats + * collector. + */ + if (memcmp(&WalStats, &all_zeroes, sizeof(PgStat_MsgWal)) == 0) + return; + + /* + * Prepare and send the message + */ + pgstat_setheader(&WalStats.m_hdr, PGSTAT_MTYPE_WAL); + pgstat_send(&WalStats, sizeof(WalStats)); + + /* + * Clear out the statistics buffer, so it can be re-used. + */ + MemSet(&WalStats, 0, sizeof(WalStats)); +} + /* ---------- * pgstat_send_slru() - * @@ -5013,6 +5206,11 @@ PgstatCollectorMain(int argc, char *argv[]) len); break; + case PGSTAT_MTYPE_RESETREPLSLOTCOUNTER: + pgstat_recv_resetreplslotcounter(&msg.msg_resetreplslotcounter, + len); + break; + case PGSTAT_MTYPE_AUTOVAC_START: pgstat_recv_autovac(&msg.msg_autovacuum_start, len); break; @@ -5037,6 +5235,10 @@ PgstatCollectorMain(int argc, char *argv[]) pgstat_recv_queuestat((PgStat_MsgQueuestat *) &msg, len); break; + case PGSTAT_MTYPE_WAL: + pgstat_recv_wal(&msg.msg_wal, len); + break; + case PGSTAT_MTYPE_SLRU: pgstat_recv_slru(&msg.msg_slru, len); break; @@ -5067,6 +5269,10 @@ PgstatCollectorMain(int argc, char *argv[]) len); break; + case PGSTAT_MTYPE_REPLSLOT: + pgstat_recv_replslot(&msg.msg_replslot, len); + break; + default: break; } @@ -5268,6 +5474,7 @@ pgstat_write_statsfiles(bool permanent, bool allDbs) const char *tmpfile = permanent ? PGSTAT_STAT_PERMANENT_TMPFILE : pgstat_stat_tmpname; const char *statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : pgstat_stat_filename; int rc; + int i; elog(DEBUG2, "writing stats file \"%s\"", statfile); @@ -5308,6 +5515,12 @@ pgstat_write_statsfiles(bool permanent, bool allDbs) rc = fwrite(&archiverStats, sizeof(archiverStats), 1, fpout); (void) rc; /* we'll check for error with ferror */ + /* + * Write WAL stats struct + */ + rc = fwrite(&walStats, sizeof(walStats), 1, fpout); + (void) rc; /* we'll check for error with ferror */ + /* * Write SLRU stats struct */ @@ -5351,6 +5564,16 @@ pgstat_write_statsfiles(bool permanent, bool allDbs) fwrite(queueentry, sizeof(PgStat_StatQueueEntry), 1, fpout); } + /* + * Write replication slot stats struct + */ + for (i = 0; i < nReplSlotStats; i++) + { + fputc('R', fpout); + rc = fwrite(&replSlotStats[i], sizeof(PgStat_ReplSlotStats), 1, fpout); + (void) rc; /* we'll check for error with ferror */ + } + /* * No more output to be done. Close the temp file and replace the old * pgstat.stat with it. The ferror() check replaces testing for error @@ -5591,12 +5814,17 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); pgStatQueueHash = queuehash; + /* Allocate the space for replication slot statistics */ + replSlotStats = palloc0(max_replication_slots * sizeof(PgStat_ReplSlotStats)); + nReplSlotStats = 0; + /* - * Clear out global and archiver statistics so they start from zero in - * case we can't load an existing statsfile. + * Clear out global, archiver, WAL and SLRU statistics so they start from + * zero in case we can't load an existing statsfile. */ memset(&globalStats, 0, sizeof(globalStats)); memset(&archiverStats, 0, sizeof(archiverStats)); + memset(&walStats, 0, sizeof(walStats)); memset(&slruStats, 0, sizeof(slruStats)); /* @@ -5605,6 +5833,7 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) */ globalStats.stat_reset_timestamp = GetCurrentTimestamp(); archiverStats.stat_reset_timestamp = globalStats.stat_reset_timestamp; + walStats.stat_reset_timestamp = globalStats.stat_reset_timestamp; /* * Set the same reset timestamp for all SLRU items too. @@ -5612,6 +5841,12 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) for (i = 0; i < SLRU_NUM_ELEMENTS; i++) slruStats[i].stat_reset_timestamp = globalStats.stat_reset_timestamp; + /* + * Set the same reset timestamp for all replication slots too. + */ + for (i = 0; i < max_replication_slots; i++) + replSlotStats[i].stat_reset_timestamp = globalStats.stat_reset_timestamp; + /* * Try to open the stats file. If it doesn't exist, the backends simply * return zero for anything and the collector simply starts from scratch @@ -5674,6 +5909,17 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) goto done; } + /* + * Read WAL stats struct + */ + if (fread(&walStats, 1, sizeof(walStats), fpin) != sizeof(walStats)) + { + ereport(pgStatRunningInCollector ? LOG : WARNING, + (errmsg("corrupted statistics file \"%s\"", statfile))); + memset(&walStats, 0, sizeof(walStats)); + goto done; + } + /* * Read SLRU stats struct */ @@ -5810,6 +6056,23 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) memcpy(queueentry, &queuebuf, sizeof(PgStat_StatQueueEntry)); break; + /* + * 'R' A PgStat_ReplSlotStats struct describing a replication + * slot follows. + */ + case 'R': + if (fread(&replSlotStats[nReplSlotStats], 1, sizeof(PgStat_ReplSlotStats), fpin) + != sizeof(PgStat_ReplSlotStats)) + { + ereport(pgStatRunningInCollector ? LOG : WARNING, + (errmsg("corrupted statistics file \"%s\"", + statfile))); + memset(&replSlotStats[nReplSlotStats], 0, sizeof(PgStat_ReplSlotStats)); + goto done; + } + nReplSlotStats++; + break; + case 'E': goto done; @@ -5998,7 +6261,8 @@ pgstat_read_db_statsfile(Oid databaseid, HTAB *tabhash, HTAB *funchash, * pgstat_read_db_statsfile_timestamp() - * * Attempt to determine the timestamp of the last db statfile write. - * Returns true if successful; the timestamp is stored in *ts. + * Returns true if successful; the timestamp is stored in *ts. The caller must + * rely on timestamp stored in *ts iff the function returns true. * * This needs to be careful about handling databases for which no stats file * exists, such as databases without a stat entry or those not yet written: @@ -6019,7 +6283,9 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent, PgStat_StatQueueEntry queuebuf; /* GPDB */ PgStat_GlobalStats myGlobalStats; PgStat_ArchiverStats myArchiverStats; + PgStat_WalStats myWalStats; PgStat_SLRUStats mySLRUStats[SLRU_NUM_ELEMENTS]; + PgStat_ReplSlotStats myReplSlotStats; FILE *fpin; int32 format_id; const char *statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : pgstat_stat_filename; @@ -6074,6 +6340,17 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent, return false; } + /* + * Read WAL stats struct + */ + if (fread(&myWalStats, 1, sizeof(myWalStats), fpin) != sizeof(myWalStats)) + { + ereport(pgStatRunningInCollector ? LOG : WARNING, + (errmsg("corrupted statistics file \"%s\"", statfile))); + FreeFile(fpin); + return false; + } + /* * Read SLRU stats struct */ @@ -6107,7 +6384,8 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent, ereport(pgStatRunningInCollector ? LOG : WARNING, (errmsg("corrupted statistics file \"%s\"", statfile))); - goto done; + FreeFile(fpin); + return false; } /* @@ -6136,14 +6414,33 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent, } break; + /* + * 'R' A PgStat_ReplSlotStats struct describing a replication + * slot follows. + */ + case 'R': + if (fread(&myReplSlotStats, 1, sizeof(PgStat_ReplSlotStats), fpin) + != sizeof(PgStat_ReplSlotStats)) + { + ereport(pgStatRunningInCollector ? LOG : WARNING, + (errmsg("corrupted statistics file \"%s\"", + statfile))); + FreeFile(fpin); + return false; + } + break; + case 'E': goto done; default: - ereport(pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", - statfile))); - goto done; + { + ereport(pgStatRunningInCollector ? LOG : WARNING, + (errmsg("corrupted statistics file \"%s\"", + statfile))); + FreeFile(fpin); + return false; + } } } @@ -6664,6 +6961,12 @@ pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len) memset(&archiverStats, 0, sizeof(archiverStats)); archiverStats.stat_reset_timestamp = GetCurrentTimestamp(); } + else if (msg->m_resettarget == RESET_WAL) + { + /* Reset the WAL statistics for the cluster. */ + memset(&walStats, 0, sizeof(walStats)); + walStats.stat_reset_timestamp = GetCurrentTimestamp(); + } /* * Presumably the sender of this message validated the target, don't @@ -6722,6 +7025,46 @@ pgstat_recv_resetslrucounter(PgStat_MsgResetslrucounter *msg, int len) } } +/* ---------- + * pgstat_recv_resetreplslotcounter() - + * + * Reset some replication slot statistics of the cluster. + * ---------- + */ +static void +pgstat_recv_resetreplslotcounter(PgStat_MsgResetreplslotcounter *msg, + int len) +{ + int i; + int idx = -1; + TimestampTz ts; + + ts = GetCurrentTimestamp(); + if (msg->clearall) + { + for (i = 0; i < nReplSlotStats; i++) + pgstat_reset_replslot(i, ts); + } + else + { + /* Get the index of replication slot statistics to reset */ + idx = pgstat_replslot_index(msg->m_slotname, false); + + /* + * Nothing to do if the given slot entry is not found. This could + * happen when the slot with the given name is removed and the + * corresponding statistics entry is also removed before receiving the + * reset message. + */ + if (idx < 0) + return; + + /* Reset the stats for the requested replication slot */ + pgstat_reset_replslot(idx, ts); + } +} + + /* ---------- * pgstat_recv_autovac() - * @@ -7070,6 +7413,18 @@ pgstat_fetch_stat_queueentry(Oid queueid) } +/* ---------- + * pgstat_recv_wal() - + * + * Process a WAL message. + * ---------- + */ +static void +pgstat_recv_wal(PgStat_MsgWal *msg, int len) +{ + walStats.wal_buffers_full += msg->m_wal_buffers_full; +} + /* ---------- * pgstat_recv_slru() - * @@ -7161,6 +7516,54 @@ pgstat_recv_checksum_failure(PgStat_MsgChecksumFailure *msg, int len) dbentry->last_checksum_failure = msg->m_failure_time; } +/* ---------- + * pgstat_recv_replslot() - + * + * Process a REPLSLOT message. + * ---------- + */ +static void +pgstat_recv_replslot(PgStat_MsgReplSlot *msg, int len) +{ + int idx; + + /* + * Get the index of replication slot statistics. On dropping, we don't + * create the new statistics. + */ + idx = pgstat_replslot_index(msg->m_slotname, !msg->m_drop); + + /* + * The slot entry is not found or there is no space to accommodate the new + * entry. This could happen when the message for the creation of a slot + * reached before the drop message even though the actual operations + * happen in reverse order. In such a case, the next update of the + * statistics for the same slot will create the required entry. + */ + if (idx < 0) + return; + + Assert(idx >= 0 && idx <= max_replication_slots); + if (msg->m_drop) + { + /* Remove the replication slot statistics with the given name */ + memcpy(&replSlotStats[idx], &replSlotStats[nReplSlotStats - 1], + sizeof(PgStat_ReplSlotStats)); + nReplSlotStats--; + Assert(nReplSlotStats >= 0); + } + else + { + /* Update the replication slot statistics */ + replSlotStats[idx].spill_txns += msg->m_spill_txns; + replSlotStats[idx].spill_count += msg->m_spill_count; + replSlotStats[idx].spill_bytes += msg->m_spill_bytes; + replSlotStats[idx].stream_txns += msg->m_stream_txns; + replSlotStats[idx].stream_count += msg->m_stream_count; + replSlotStats[idx].stream_bytes += msg->m_stream_bytes; + } +} + /* ---------- * pgstat_recv_tempfile() - * @@ -7343,6 +7746,60 @@ pgstat_clip_activity(const char *raw_activity) return activity; } +/* ---------- + * pgstat_replslot_index + * + * Return the index of entry of a replication slot with the given name, or + * -1 if the slot is not found. + * + * create_it tells whether to create the new slot entry if it is not found. + * ---------- + */ +static int +pgstat_replslot_index(const char *name, bool create_it) +{ + int i; + + Assert(nReplSlotStats <= max_replication_slots); + for (i = 0; i < nReplSlotStats; i++) + { + if (strcmp(replSlotStats[i].slotname, name) == 0) + return i; /* found */ + } + + /* + * The slot is not found. We don't want to register the new statistics if + * the list is already full or the caller didn't request. + */ + if (i == max_replication_slots || !create_it) + return -1; + + /* Register new slot */ + memset(&replSlotStats[nReplSlotStats], 0, sizeof(PgStat_ReplSlotStats)); + memcpy(&replSlotStats[nReplSlotStats].slotname, name, NAMEDATALEN); + + return nReplSlotStats++; +} + +/* ---------- + * pgstat_reset_replslot + * + * Reset the replication slot stats at index 'i'. + * ---------- + */ +static void +pgstat_reset_replslot(int i, TimestampTz ts) +{ + /* reset only counters. Don't clear slot name */ + replSlotStats[i].spill_txns = 0; + replSlotStats[i].spill_count = 0; + replSlotStats[i].spill_bytes = 0; + replSlotStats[i].stream_txns = 0; + replSlotStats[i].stream_count = 0; + replSlotStats[i].stream_bytes = 0; + replSlotStats[i].stat_reset_timestamp = ts; +} + /* * pgstat_slru_index * diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 7e13799866f2..fe557d864e12 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -121,6 +121,7 @@ #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" #include "postmaster/fork_process.h" +#include "postmaster/interrupt.h" #include "postmaster/pgarch.h" #include "postmaster/postmaster.h" #include "postmaster/fts.h" @@ -499,7 +500,7 @@ static void SIGHUP_handler(SIGNAL_ARGS); static void pmdie(SIGNAL_ARGS); static void reaper(SIGNAL_ARGS); static void sigusr1_handler(SIGNAL_ARGS); -static void startup_die(SIGNAL_ARGS); +static void process_startup_packet_die(SIGNAL_ARGS); static void dummy_handler(SIGNAL_ARGS); static void StartupPacketTimeoutHandler(void); static void CleanupBackend(int pid, int exitstatus); @@ -2132,6 +2133,8 @@ ServerLoop(void) } #endif /* We were gentle with them before. Not anymore */ + ereport(LOG, + (errmsg("issuing SIGKILL to recalcitrant children"))); TerminateChildren(SIGKILL); /* reset flag so we don't SIGKILL again */ AbortStartTime = 0; @@ -4811,6 +4814,8 @@ report_fork_failure_to_client(Port *port, int errnum) * returns: nothing. Will not return at all if there's any failure. * * Note: this code does not depend on having any access to shared memory. + * Indeed, our approach to SIGTERM/timeout handling *requires* that + * shared memory not have been touched yet; see comments within. * In the EXEC_BACKEND case, we are physically attached to shared memory * but have not yet set up most of our local pointers to shmem structures. */ @@ -4854,22 +4859,17 @@ BackendInitialize(Port *port) whereToSendOutput = DestRemote; /* now safe to ereport to client */ /* - * We arrange for a simple exit(1) if we receive SIGTERM or SIGQUIT or - * timeout while trying to collect the startup packet. Otherwise the - * postmaster cannot shutdown the database FAST or IMMED cleanly if a - * buggy client fails to send the packet promptly. XXX it follows that - * the remainder of this function must tolerate losing control at any - * instant. Likewise, any pg_on_exit_callback registered before or during - * this function must be prepared to execute at any instant between here - * and the end of this function. Furthermore, affected callbacks execute - * partially or not at all when a second exit-inducing signal arrives - * after proc_exit_prepare() decrements on_proc_exit_index. (Thanks to - * that mechanic, callbacks need not anticipate more than one call.) This - * is fragile; it ought to instead follow the norm of handling interrupts - * at selected, safe opportunities. - */ - pqsignal(SIGTERM, startup_die); - pqsignal(SIGQUIT, startup_die); + * We arrange to do _exit(1) if we receive SIGTERM or timeout while trying + * to collect the startup packet; while SIGQUIT results in _exit(2). + * Otherwise the postmaster cannot shutdown the database FAST or IMMED + * cleanly if a buggy client fails to send the packet promptly. + * + * Exiting with _exit(1) is only possible because we have not yet touched + * shared memory; therefore no outside-the-process state needs to get + * cleaned up. + */ + pqsignal(SIGTERM, process_startup_packet_die); + /* SIGQUIT handler was already set up by InitPostmasterChild */ InitializeTimeouts(); /* establishes SIGALRM handler */ PG_SETMASK(&StartupBlockSig); @@ -4925,8 +4925,8 @@ BackendInitialize(Port *port) port->remote_hostname = strdup(remote_host); /* - * Ready to begin client interaction. We will give up and exit(1) after a - * time delay, so that a broken client can't hog a connection + * Ready to begin client interaction. We will give up and _exit(1) after + * a time delay, so that a broken client can't hog a connection * indefinitely. PreAuthDelay and any DNS interactions above don't count * against the time limit. * @@ -4948,6 +4948,23 @@ BackendInitialize(Port *port) */ status = ProcessStartupPacket(port, false, false); + /* + * Disable the timeout, and prevent SIGTERM again. + */ + disable_timeout(STARTUP_PACKET_TIMEOUT, false); + PG_SETMASK(&BlockSig); + + /* + * As a safety check that nothing in startup has yet performed + * shared-memory modifications that would need to be undone if we had + * exited through SIGTERM or timeout above, check that no on_shmem_exit + * handlers have been registered yet. (This isn't terribly bulletproof, + * since someone might misuse an on_proc_exit handler for shmem cleanup, + * but it's a cheap and helpful check. We cannot disallow on_proc_exit + * handlers unfortunately, since pq_init() already registered one.) + */ + check_on_shmem_exit_lists_are_empty(); + /* * Stop here if it was bad or a cancel packet. ProcessStartupPacket * already did any appropriate error reporting. @@ -4979,12 +4996,6 @@ BackendInitialize(Port *port) pfree(ps_data.data); set_ps_display("initializing"); - - /* - * Disable the timeout, and prevent SIGTERM/SIGQUIT again. - */ - disable_timeout(STARTUP_PACKET_TIMEOUT, false); - PG_SETMASK(&BlockSig); } @@ -5493,10 +5504,6 @@ SubPostmasterMain(int argc, char *argv[]) if (strcmp(argv[1], "--forkavworker") == 0) AutovacuumWorkerIAm(); - /* In EXEC_BACKEND case we will not have inherited these settings */ - pqinitmask(); - PG_SETMASK(&BlockSig); - /* Read in remaining GUC variables */ read_nondefault_variables(); @@ -5930,18 +5937,22 @@ sigusr1_handler(SIGNAL_ARGS) } /* - * SIGTERM or SIGQUIT while processing startup packet. - * Clean up and exit(1). + * SIGTERM while processing startup packet. + * + * Running proc_exit() from a signal handler would be quite unsafe. + * However, since we have not yet touched shared memory, we can just + * pull the plug and exit without running any atexit handlers. * - * XXX: possible future improvement: try to send a message indicating - * why we are disconnecting. Problem is to be sure we don't block while - * doing so, nor mess up SSL initialization. In practice, if the client - * has wedged here, it probably couldn't do anything with the message anyway. + * One might be tempted to try to send a message, or log one, indicating + * why we are disconnecting. However, that would be quite unsafe in itself. + * Also, it seems undesirable to provide clues about the database's state + * to a client that has not yet completed authentication, or even sent us + * a startup packet. */ static void -startup_die(SIGNAL_ARGS) +process_startup_packet_die(SIGNAL_ARGS) { - proc_exit(1); + _exit(1); } /* @@ -5960,12 +5971,12 @@ dummy_handler(SIGNAL_ARGS) /* * Timeout while processing startup packet. - * As for startup_die(), we clean up and exit(1). + * As for process_startup_packet_die(), we exit via _exit(1). */ static void StartupPacketTimeoutHandler(void) { - proc_exit(1); + _exit(1); } diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c index 24fb076ec0ec..a2d91fd12bbc 100644 --- a/src/backend/postmaster/startup.c +++ b/src/backend/postmaster/startup.c @@ -38,7 +38,6 @@ bool am_startup = false; /* * Flags set by interrupt handlers for later service in the redo loop. */ -static volatile sig_atomic_t got_SIGHUP = false; static volatile sig_atomic_t shutdown_requested = false; static volatile sig_atomic_t promote_signaled = false; @@ -50,7 +49,6 @@ static volatile sig_atomic_t in_restore_command = false; /* Signal handlers */ static void StartupProcTriggerHandler(SIGNAL_ARGS); -static void StartupProcSigHupHandler(SIGNAL_ARGS); /* -------------------------------- @@ -65,19 +63,7 @@ StartupProcTriggerHandler(SIGNAL_ARGS) int save_errno = errno; promote_signaled = true; - WakeupRecovery(); - - errno = save_errno; -} - -/* SIGHUP: set flag to re-read config file at next convenient time */ -static void -StartupProcSigHupHandler(SIGNAL_ARGS) -{ - int save_errno = errno; - - got_SIGHUP = true; - WakeupRecovery(); + SetLatch(MyLatch); errno = save_errno; } @@ -92,7 +78,7 @@ StartupProcShutdownHandler(SIGNAL_ARGS) proc_exit(1); else shutdown_requested = true; - WakeupRecovery(); + SetLatch(MyLatch); errno = save_errno; } @@ -138,9 +124,9 @@ HandleStartupProcInterrupts(void) /* * Process any requests or signals received recently. */ - if (got_SIGHUP) + if (ConfigReloadPending) { - got_SIGHUP = false; + ConfigReloadPending = false; StartupRereadConfig(); } @@ -186,10 +172,10 @@ StartupProcessMain(void) /* * Properly accept or ignore signals the postmaster might send us. */ - pqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); /* reload config file */ pqsignal(SIGINT, SIG_IGN); /* ignore query cancel */ pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */ - pqsignal(SIGQUIT, SignalHandlerForCrashExit); + /* SIGQUIT handler was already set up by InitPostmasterChild */ InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); pqsignal(SIGUSR1, procsignal_sigusr1_handler); diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c index ef0c1a3cc133..58a1ceded638 100644 --- a/src/backend/postmaster/syslogger.c +++ b/src/backend/postmaster/syslogger.c @@ -39,6 +39,7 @@ #include "pgstat.h" #include "pgtime.h" #include "postmaster/fork_process.h" +#include "postmaster/interrupt.h" #include "postmaster/postmaster.h" #include "postmaster/syslogger.h" #include "storage/dsm.h" @@ -146,7 +147,6 @@ static void syslogger_flush_chunks(void); /* * Flags set by interrupt handlers for later service in the main loop. */ -static volatile sig_atomic_t got_SIGHUP = false; static volatile sig_atomic_t rotation_requested = false; @@ -171,7 +171,6 @@ static bool logfile_rotate(bool time_based_rotation, bool size_based_rotation, c FILE **fh, char **last_log_file_name); static char *logfile_getname(pg_time_t timestamp, const char *suffix, const char *log_directory, const char *log_file_pattern); static void set_next_rotation_time(void); -static void sigHupHandler(SIGNAL_ARGS); static void sigUsr1Handler(SIGNAL_ARGS); static void update_metainfo_datafile(void); @@ -339,7 +338,7 @@ SysLoggerMain(int argc, char *argv[]) * broken backends... */ - pqsignal(SIGHUP, sigHupHandler); /* set flag to read config file */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config file */ pqsignal(SIGINT, SIG_IGN); pqsignal(SIGTERM, SIG_IGN); pqsignal(SIGQUIT, SIG_IGN); @@ -433,9 +432,9 @@ SysLoggerMain(int argc, char *argv[]) /* * Process any requests or signals received recently. */ - if (got_SIGHUP) + if (ConfigReloadPending) { - got_SIGHUP = false; + ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); /* @@ -2598,18 +2597,6 @@ RemoveLogrotateSignalFiles(void) unlink(LOGROTATE_SIGNAL_FILE); } -/* SIGHUP: set flag to reload config file */ -static void -sigHupHandler(SIGNAL_ARGS) -{ - int save_errno = errno; - - got_SIGHUP = true; - SetLatch(MyLatch); - - errno = save_errno; -} - /* SIGUSR1: set flag to rotate logfile */ static void sigUsr1Handler(SIGNAL_ARGS) diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c index 45a2757969be..a52832fe900a 100644 --- a/src/backend/postmaster/walwriter.c +++ b/src/backend/postmaster/walwriter.c @@ -101,7 +101,7 @@ WalWriterMain(void) pqsignal(SIGHUP, SignalHandlerForConfigReload); pqsignal(SIGINT, SignalHandlerForShutdownRequest); pqsignal(SIGTERM, SignalHandlerForShutdownRequest); - pqsignal(SIGQUIT, SignalHandlerForCrashExit); + /* SIGQUIT handler was already set up by InitPostmasterChild */ pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); pqsignal(SIGUSR1, procsignal_sigusr1_handler); @@ -112,9 +112,6 @@ WalWriterMain(void) */ pqsignal(SIGCHLD, SIG_DFL); - /* We allow SIGQUIT (quickdie) at all times */ - sigdelset(&BlockSig, SIGQUIT); - /* * Create a memory context that we will do all our work in. We do this so * that we can reset the context during error recovery and thereby avoid @@ -129,7 +126,20 @@ WalWriterMain(void) /* * If an exception is encountered, processing resumes here. * - * This code is heavily based on bgwriter.c, q.v. + * You might wonder why this isn't coded as an infinite loop around a + * PG_TRY construct. The reason is that this is the bottom of the + * exception stack, and so with PG_TRY there would be no exception handler + * in force at all during the CATCH part. By leaving the outermost setjmp + * always active, we have at least some chance of recovering from an error + * during error recovery. (If we get into an infinite loop thereby, it + * will soon be stopped by overflow of elog.c's internal state stack.) + * + * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask + * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus, + * signals other than SIGQUIT will be blocked until we complete error + * recovery. It might seem that this policy makes the HOLD_INTERRUPTS() + * call redundant, but it is not since InterruptPending might be set + * already. */ if (sigsetjmp(local_sigjmp_buf, 1) != 0) { diff --git a/src/backend/replication/backup_manifest.c b/src/backend/replication/backup_manifest.c index 1ef1effd465a..7609aef18e5d 100644 --- a/src/backend/replication/backup_manifest.c +++ b/src/backend/replication/backup_manifest.c @@ -112,7 +112,7 @@ AddFileToBackupManifest(backup_manifest_info *manifest, const char *spcoid, initStringInfo(&buf); if (manifest->first_file) { - appendStringInfoString(&buf, "\n"); + appendStringInfoChar(&buf, '\n'); manifest->first_file = false; } else @@ -152,7 +152,7 @@ AddFileToBackupManifest(backup_manifest_info *manifest, const char *spcoid, enlargeStringInfo(&buf, 128); buf.len += pg_strftime(&buf.data[buf.len], 128, "%Y-%m-%d %H:%M:%S %Z", pg_gmtime(&mtime)); - appendStringInfoString(&buf, "\""); + appendStringInfoChar(&buf, '"'); /* Add checksum information. */ if (checksum_ctx->type != CHECKSUM_TYPE_NONE) @@ -168,7 +168,7 @@ AddFileToBackupManifest(backup_manifest_info *manifest, const char *spcoid, enlargeStringInfo(&buf, 2 * checksumlen); buf.len += hex_encode((char *) checksumbuf, checksumlen, &buf.data[buf.len]); - appendStringInfoString(&buf, "\""); + appendStringInfoChar(&buf, '"'); } /* Close out the object. */ @@ -272,7 +272,7 @@ AddWALInfoToBackupManifest(backup_manifest_info *manifest, XLogRecPtr startptr, */ if (!found_start_timeline) ereport(ERROR, - errmsg("start timeline %u not found history of timeline %u", + errmsg("start timeline %u not found in history of timeline %u", starttli, endtli)); /* Terminate the list of WAL ranges. */ diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c index 287792ab5951..3ac207029890 100644 --- a/src/backend/replication/basebackup.c +++ b/src/backend/replication/basebackup.c @@ -767,7 +767,10 @@ perform_base_backup(basebackup_options *opt) { if (total_checksum_failures > 1) ereport(WARNING, - (errmsg("%lld total checksum verification failures", total_checksum_failures))); + (errmsg_plural("%lld total checksum verification failure", + "%lld total checksum verification failures", + total_checksum_failures, + total_checksum_failures))); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c index 0ce5318cf3b2..f2fdf4c993b2 100644 --- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c +++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c @@ -433,6 +433,10 @@ libpqrcv_startstreaming(WalReceiverConn *conn, appendStringInfo(&cmd, "proto_version '%u'", options->proto.logical.proto_version); + if (options->proto.logical.streaming && + PQserverVersion(conn->streamConn) >= 140000) + appendStringInfoString(&cmd, ", streaming 'on'"); + pubnames = options->proto.logical.publication_names; pubnames_str = stringlist_to_identifierstr(conn->streamConn, pubnames); if (!pubnames_str) diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index 84666bd90c72..d8cd37870504 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -662,6 +662,12 @@ DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf, /* replay actions of all transaction + subtransactions in order */ ReorderBufferCommit(ctx->reorder, xid, buf->origptr, buf->endptr, commit_time, origin_id, origin_lsn); + + /* + * Update the decoding stats at transaction commit/abort. It is not clear + * that sending more or less frequently than this would be better. + */ + UpdateDecodingStats(ctx); } /* @@ -681,6 +687,9 @@ DecodeAbort(LogicalDecodingContext *ctx, XLogRecordBuffer *buf, } ReorderBufferAbort(ctx->reorder, xid, buf->record->EndRecPtr); + + /* update the decoding stats */ + UpdateDecodingStats(ctx); } /* diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index 0f6af952f939..d5cfbeaa4aff 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -32,6 +32,7 @@ #include "access/xlog_internal.h" #include "fmgr.h" #include "miscadmin.h" +#include "pgstat.h" #include "replication/decode.h" #include "replication/logical.h" #include "replication/origin.h" @@ -1460,3 +1461,38 @@ ResetLogicalStreamingState(void) CheckXidAlive = InvalidTransactionId; bsysscan = false; } + +/* + * Report stats for a slot. + */ +void +UpdateDecodingStats(LogicalDecodingContext *ctx) +{ + ReorderBuffer *rb = ctx->reorder; + + /* + * Nothing to do if we haven't spilled or streamed anything since the last + * time the stats has been sent. + */ + if (rb->spillBytes <= 0 && rb->streamBytes <= 0) + return; + + elog(DEBUG2, "UpdateDecodingStats: updating stats %p %lld %lld %lld %lld %lld %lld", + rb, + (long long) rb->spillTxns, + (long long) rb->spillCount, + (long long) rb->spillBytes, + (long long) rb->streamTxns, + (long long) rb->streamCount, + (long long) rb->streamBytes); + + pgstat_report_replslot(NameStr(ctx->slot->data.name), + rb->spillTxns, rb->spillCount, rb->spillBytes, + rb->streamTxns, rb->streamCount, rb->streamBytes); + rb->spillTxns = 0; + rb->spillCount = 0; + rb->spillBytes = 0; + rb->streamTxns = 0; + rb->streamCount = 0; + rb->streamBytes = 0; +} diff --git a/src/backend/replication/logical/message.c b/src/backend/replication/logical/message.c index db33cbe5a7a2..bd4b08543e66 100644 --- a/src/backend/replication/logical/message.c +++ b/src/backend/replication/logical/message.c @@ -59,6 +59,7 @@ LogLogicalMessage(const char *prefix, const char *message, size_t size, xlrec.dbId = MyDatabaseId; xlrec.transactional = transactional; + /* trailing zero is critical; see logicalmsg_desc */ xlrec.prefix_size = strlen(prefix) + 1; xlrec.message_size = size; diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c index 9ff8097bf5fd..fdb31182d77f 100644 --- a/src/backend/replication/logical/proto.c +++ b/src/backend/replication/logical/proto.c @@ -44,7 +44,7 @@ static const char *logicalrep_read_namespace(StringInfo in); void logicalrep_write_begin(StringInfo out, ReorderBufferTXN *txn) { - pq_sendbyte(out, 'B'); /* BEGIN */ + pq_sendbyte(out, LOGICAL_REP_MSG_BEGIN); /* fixed fields */ pq_sendint64(out, txn->final_lsn); @@ -76,7 +76,7 @@ logicalrep_write_commit(StringInfo out, ReorderBufferTXN *txn, { uint8 flags = 0; - pq_sendbyte(out, 'C'); /* sending COMMIT */ + pq_sendbyte(out, LOGICAL_REP_MSG_COMMIT); /* send the flags field (unused for now) */ pq_sendbyte(out, flags); @@ -112,7 +112,7 @@ void logicalrep_write_origin(StringInfo out, const char *origin, XLogRecPtr origin_lsn) { - pq_sendbyte(out, 'O'); /* ORIGIN */ + pq_sendbyte(out, LOGICAL_REP_MSG_ORIGIN); /* fixed fields */ pq_sendint64(out, origin_lsn); @@ -138,9 +138,14 @@ logicalrep_read_origin(StringInfo in, XLogRecPtr *origin_lsn) * Write INSERT to the output stream. */ void -logicalrep_write_insert(StringInfo out, Relation rel, HeapTuple newtuple, bool binary) +logicalrep_write_insert(StringInfo out, TransactionId xid, Relation rel, + HeapTuple newtuple, bool binary) { - pq_sendbyte(out, 'I'); /* action INSERT */ + pq_sendbyte(out, LOGICAL_REP_MSG_INSERT); + + /* transaction ID (if not valid, we're not streaming) */ + if (TransactionIdIsValid(xid)) + pq_sendint32(out, xid); /* use Oid as relation identifier */ pq_sendint32(out, RelationGetRelid(rel)); @@ -177,15 +182,19 @@ logicalrep_read_insert(StringInfo in, LogicalRepTupleData *newtup) * Write UPDATE to the output stream. */ void -logicalrep_write_update(StringInfo out, Relation rel, HeapTuple oldtuple, - HeapTuple newtuple, bool binary) +logicalrep_write_update(StringInfo out, TransactionId xid, Relation rel, + HeapTuple oldtuple, HeapTuple newtuple, bool binary) { - pq_sendbyte(out, 'U'); /* action UPDATE */ + pq_sendbyte(out, LOGICAL_REP_MSG_UPDATE); Assert(rel->rd_rel->relreplident == REPLICA_IDENTITY_DEFAULT || rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL || rel->rd_rel->relreplident == REPLICA_IDENTITY_INDEX); + /* transaction ID (if not valid, we're not streaming) */ + if (TransactionIdIsValid(xid)) + pq_sendint32(out, xid); + /* use Oid as relation identifier */ pq_sendint32(out, RelationGetRelid(rel)); @@ -247,13 +256,18 @@ logicalrep_read_update(StringInfo in, bool *has_oldtuple, * Write DELETE to the output stream. */ void -logicalrep_write_delete(StringInfo out, Relation rel, HeapTuple oldtuple, bool binary) +logicalrep_write_delete(StringInfo out, TransactionId xid, Relation rel, + HeapTuple oldtuple, bool binary) { Assert(rel->rd_rel->relreplident == REPLICA_IDENTITY_DEFAULT || rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL || rel->rd_rel->relreplident == REPLICA_IDENTITY_INDEX); - pq_sendbyte(out, 'D'); /* action DELETE */ + pq_sendbyte(out, LOGICAL_REP_MSG_DELETE); + + /* transaction ID (if not valid, we're not streaming) */ + if (TransactionIdIsValid(xid)) + pq_sendint32(out, xid); /* use Oid as relation identifier */ pq_sendint32(out, RelationGetRelid(rel)); @@ -295,6 +309,7 @@ logicalrep_read_delete(StringInfo in, LogicalRepTupleData *oldtup) */ void logicalrep_write_truncate(StringInfo out, + TransactionId xid, int nrelids, Oid relids[], bool cascade, bool restart_seqs) @@ -302,7 +317,11 @@ logicalrep_write_truncate(StringInfo out, int i; uint8 flags = 0; - pq_sendbyte(out, 'T'); /* action TRUNCATE */ + pq_sendbyte(out, LOGICAL_REP_MSG_TRUNCATE); + + /* transaction ID (if not valid, we're not streaming) */ + if (TransactionIdIsValid(xid)) + pq_sendint32(out, xid); pq_sendint32(out, nrelids); @@ -346,11 +365,15 @@ logicalrep_read_truncate(StringInfo in, * Write relation description to the output stream. */ void -logicalrep_write_rel(StringInfo out, Relation rel) +logicalrep_write_rel(StringInfo out, TransactionId xid, Relation rel) { char *relname; - pq_sendbyte(out, 'R'); /* sending RELATION */ + pq_sendbyte(out, LOGICAL_REP_MSG_RELATION); + + /* transaction ID (if not valid, we're not streaming) */ + if (TransactionIdIsValid(xid)) + pq_sendint32(out, xid); /* use Oid as relation identifier */ pq_sendint32(out, RelationGetRelid(rel)); @@ -396,13 +419,17 @@ logicalrep_read_rel(StringInfo in) * This function will always write base type info. */ void -logicalrep_write_typ(StringInfo out, Oid typoid) +logicalrep_write_typ(StringInfo out, TransactionId xid, Oid typoid) { Oid basetypoid = getBaseType(typoid); HeapTuple tup; Form_pg_type typtup; - pq_sendbyte(out, 'Y'); /* sending TYPE */ + pq_sendbyte(out, LOGICAL_REP_MSG_TYPE); + + /* transaction ID (if not valid, we're not streaming) */ + if (TransactionIdIsValid(xid)) + pq_sendint32(out, xid); tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(basetypoid)); if (!HeapTupleIsValid(tup)) @@ -720,3 +747,126 @@ logicalrep_read_namespace(StringInfo in) return nspname; } + +/* + * Write the information for the start stream message to the output stream. + */ +void +logicalrep_write_stream_start(StringInfo out, + TransactionId xid, bool first_segment) +{ + pq_sendbyte(out, LOGICAL_REP_MSG_STREAM_START); + + Assert(TransactionIdIsValid(xid)); + + /* transaction ID (we're starting to stream, so must be valid) */ + pq_sendint32(out, xid); + + /* 1 if this is the first streaming segment for this xid */ + pq_sendbyte(out, first_segment ? 1 : 0); +} + +/* + * Read the information about the start stream message from output stream. + */ +TransactionId +logicalrep_read_stream_start(StringInfo in, bool *first_segment) +{ + TransactionId xid; + + Assert(first_segment); + + xid = pq_getmsgint(in, 4); + *first_segment = (pq_getmsgbyte(in) == 1); + + return xid; +} + +/* + * Write the stop stream message to the output stream. + */ +void +logicalrep_write_stream_stop(StringInfo out) +{ + pq_sendbyte(out, LOGICAL_REP_MSG_STREAM_END); +} + +/* + * Write STREAM COMMIT to the output stream. + */ +void +logicalrep_write_stream_commit(StringInfo out, ReorderBufferTXN *txn, + XLogRecPtr commit_lsn) +{ + uint8 flags = 0; + + pq_sendbyte(out, LOGICAL_REP_MSG_STREAM_COMMIT); + + Assert(TransactionIdIsValid(txn->xid)); + + /* transaction ID */ + pq_sendint32(out, txn->xid); + + /* send the flags field (unused for now) */ + pq_sendbyte(out, flags); + + /* send fields */ + pq_sendint64(out, commit_lsn); + pq_sendint64(out, txn->end_lsn); + pq_sendint64(out, txn->commit_time); +} + +/* + * Read STREAM COMMIT from the output stream. + */ +TransactionId +logicalrep_read_stream_commit(StringInfo in, LogicalRepCommitData *commit_data) +{ + TransactionId xid; + uint8 flags; + + xid = pq_getmsgint(in, 4); + + /* read flags (unused for now) */ + flags = pq_getmsgbyte(in); + + if (flags != 0) + elog(ERROR, "unrecognized flags %u in commit message", flags); + + /* read fields */ + commit_data->commit_lsn = pq_getmsgint64(in); + commit_data->end_lsn = pq_getmsgint64(in); + commit_data->committime = pq_getmsgint64(in); + + return xid; +} + +/* + * Write STREAM ABORT to the output stream. Note that xid and subxid will be + * same for the top-level transaction abort. + */ +void +logicalrep_write_stream_abort(StringInfo out, TransactionId xid, + TransactionId subxid) +{ + pq_sendbyte(out, LOGICAL_REP_MSG_STREAM_ABORT); + + Assert(TransactionIdIsValid(xid) && TransactionIdIsValid(subxid)); + + /* transaction ID */ + pq_sendint32(out, xid); + pq_sendint32(out, subxid); +} + +/* + * Read STREAM ABORT from the output stream. + */ +void +logicalrep_read_stream_abort(StringInfo in, TransactionId *xid, + TransactionId *subxid) +{ + Assert(xid && subxid); + + *xid = pq_getmsgint(in, 4); + *subxid = pq_getmsgint(in, 4); +} diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c index a60c73d74d5b..25c7a804fad1 100644 --- a/src/backend/replication/logical/relation.c +++ b/src/backend/replication/logical/relation.c @@ -1,6 +1,6 @@ /*------------------------------------------------------------------------- * relation.c - * PostgreSQL logical replication + * PostgreSQL logical replication relation mapping cache * * Copyright (c) 2016-2020, PostgreSQL Global Development Group * @@ -8,8 +8,9 @@ * src/backend/replication/logical/relation.c * * NOTES - * This file contains helper functions for logical replication relation - * mapping cache. + * Routines in this file mainly have to do with mapping the properties + * of local replication target relations to the properties of their + * remote counterpart. * *------------------------------------------------------------------------- */ @@ -77,7 +78,7 @@ logicalrep_relmap_invalidate_cb(Datum arg, Oid reloid) { if (entry->localreloid == reloid) { - entry->localreloid = InvalidOid; + entry->localrelvalid = false; hash_seq_term(&status); break; } @@ -91,7 +92,7 @@ logicalrep_relmap_invalidate_cb(Datum arg, Oid reloid) hash_seq_init(&status, LogicalRepRelMap); while ((entry = (LogicalRepRelMapEntry *) hash_seq_search(&status)) != NULL) - entry->localreloid = InvalidOid; + entry->localrelvalid = false; } } @@ -227,18 +228,53 @@ logicalrep_rel_att_by_name(LogicalRepRelation *remoterel, const char *attname) return -1; } +/* + * Report error with names of the missing local relation column(s), if any. + */ +static void +logicalrep_report_missing_attrs(LogicalRepRelation *remoterel, + Bitmapset *missingatts) +{ + if (!bms_is_empty(missingatts)) + { + StringInfoData missingattsbuf; + int missingattcnt = 0; + int i; + + initStringInfo(&missingattsbuf); + + while ((i = bms_first_member(missingatts)) >= 0) + { + missingattcnt++; + if (missingattcnt == 1) + appendStringInfo(&missingattsbuf, _("\"%s\""), + remoterel->attnames[i]); + else + appendStringInfo(&missingattsbuf, _(", \"%s\""), + remoterel->attnames[i]); + } + + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg_plural("logical replication target relation \"%s.%s\" is missing replicated column: %s", + "logical replication target relation \"%s.%s\" is missing replicated columns: %s", + missingattcnt, + remoterel->nspname, + remoterel->relname, + missingattsbuf.data))); + } +} + /* * Open the local relation associated with the remote one. * - * Optionally rebuilds the Relcache mapping if it was invalidated - * by local DDL. + * Rebuilds the Relcache mapping if it was invalidated by local DDL. */ LogicalRepRelMapEntry * logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode) { LogicalRepRelMapEntry *entry; bool found; - Oid relid = InvalidOid; LogicalRepRelation *remoterel; if (LogicalRepRelMap == NULL) @@ -254,14 +290,45 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode) remoterel = &entry->remoterel; + /* Ensure we don't leak a relcache refcount. */ + if (entry->localrel) + elog(ERROR, "remote relation ID %u is already open", remoteid); + /* * When opening and locking a relation, pending invalidation messages are - * processed which can invalidate the relation. We need to update the - * local cache both when we are first time accessing the relation and when - * the relation is invalidated (aka entry->localreloid is set InvalidOid). + * processed which can invalidate the relation. Hence, if the entry is + * currently considered valid, try to open the local relation by OID and + * see if invalidation ensues. + */ + if (entry->localrelvalid) + { + entry->localrel = try_table_open(entry->localreloid, lockmode, false); + if (!entry->localrel) + { + /* Table was renamed or dropped. */ + entry->localrelvalid = false; + } + else if (!entry->localrelvalid) + { + /* Note we release the no-longer-useful lock here. */ + table_close(entry->localrel, lockmode); + entry->localrel = NULL; + } + } + + /* + * If the entry has been marked invalid since we last had lock on it, + * re-open the local relation by name and rebuild all derived data. */ - if (!OidIsValid(entry->localreloid)) + if (!entry->localrelvalid) { + Oid relid; + Bitmapset *idkey; + TupleDesc desc; + MemoryContext oldctx; + int i; + Bitmapset *missingatts; + /* Try to find and lock the relation by name. */ relid = RangeVarGetRelid(makeRangeVar(remoterel->nspname, remoterel->relname, -1), @@ -272,21 +339,7 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode) errmsg("logical replication target relation \"%s.%s\" does not exist", remoterel->nspname, remoterel->relname))); entry->localrel = table_open(relid, NoLock); - - } - else - { - relid = entry->localreloid; - entry->localrel = table_open(entry->localreloid, lockmode); - } - - if (!OidIsValid(entry->localreloid)) - { - int found; - Bitmapset *idkey; - TupleDesc desc; - MemoryContext oldctx; - int i; + entry->localreloid = relid; /* Check for supported relkind. */ CheckSubscriptionRelkind(entry->localrel->rd_rel->relkind, @@ -302,7 +355,8 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode) entry->attrmap = make_attrmap(desc->natts); MemoryContextSwitchTo(oldctx); - found = 0; + /* check and report missing attrs, if any */ + missingatts = bms_add_range(NULL, 0, remoterel->natts - 1); for (i = 0; i < desc->natts; i++) { int attnum; @@ -319,16 +373,13 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode) entry->attrmap->attnums[i] = attnum; if (attnum >= 0) - found++; + missingatts = bms_del_member(missingatts, attnum); } - /* TODO, detail message with names of missing columns */ - if (found < remoterel->natts) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("logical replication target relation \"%s.%s\" is missing " - "some replicated columns", - remoterel->nspname, remoterel->relname))); + logicalrep_report_missing_attrs(remoterel, missingatts); + + /* be tidy */ + bms_free(missingatts); /* * Check that replica identity matches. We allow for stricter replica @@ -380,14 +431,13 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode) } } - entry->localreloid = relid; + entry->localrelvalid = true; } if (entry->state != SUBREL_STATE_READY) entry->state = GetSubscriptionRelState(MySubscription->oid, entry->localreloid, - &entry->statelsn, - true); + &entry->statelsn); return entry; } @@ -523,7 +573,7 @@ logicalrep_partmap_invalidate_cb(Datum arg, Oid reloid) { if (entry->localreloid == reloid) { - entry->localreloid = InvalidOid; + entry->localrelvalid = false; hash_seq_term(&status); break; } @@ -537,7 +587,7 @@ logicalrep_partmap_invalidate_cb(Datum arg, Oid reloid) hash_seq_init(&status, LogicalRepPartMap); while ((entry = (LogicalRepRelMapEntry *) hash_seq_search(&status)) != NULL) - entry->localreloid = InvalidOid; + entry->localrelvalid = false; } } @@ -631,8 +681,8 @@ logicalrep_partition_open(LogicalRepRelMapEntry *root, /* * If the partition's attributes don't match the root relation's, we'll * need to make a new attrmap which maps partition attribute numbers to - * remoterel's, instead the original which maps root relation's attribute - * numbers to remoterel's. + * remoterel's, instead of the original which maps root relation's + * attribute numbers to remoterel's. * * Note that 'map' which comes from the tuple routing data structure * contains 1-based attribute numbers (of the parent relation). However, @@ -656,6 +706,8 @@ logicalrep_partition_open(LogicalRepRelMapEntry *root, entry->updatable = root->updatable; + entry->localrelvalid = true; + /* state and statelsn are left set to 0. */ MemoryContextSwitchTo(oldctx); diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 1975d629a6e2..c1bd68011c59 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -235,7 +235,7 @@ static void ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, static ReorderBufferChange *ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state); static void ReorderBufferIterTXNFinish(ReorderBuffer *rb, ReorderBufferIterTXNState *state); -static void ReorderBufferExecuteInvalidations(ReorderBuffer *rb, ReorderBufferTXN *txn); +static void ReorderBufferExecuteInvalidations(uint32 nmsgs, SharedInvalidationMessage *msgs); /* * --------------------------------------- @@ -343,6 +343,13 @@ ReorderBufferAllocate(void) buffer->outbufsize = 0; buffer->size = 0; + buffer->spillTxns = 0; + buffer->spillCount = 0; + buffer->spillBytes = 0; + buffer->streamTxns = 0; + buffer->streamCount = 0; + buffer->streamBytes = 0; + buffer->current_restart_decoding_lsn = InvalidXLogRecPtr; dlist_init(&buffer->toplevel_by_lsn); @@ -482,6 +489,11 @@ ReorderBufferReturnChange(ReorderBuffer *rb, ReorderBufferChange *change, pfree(change->data.msg.message); change->data.msg.message = NULL; break; + case REORDER_BUFFER_CHANGE_INVALIDATION: + if (change->data.inval.invalidations) + pfree(change->data.inval.invalidations); + change->data.inval.invalidations = NULL; + break; case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT: if (change->data.snapshot) { @@ -1428,7 +1440,7 @@ ReorderBufferCleanupTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) ReorderBufferCleanupTXN(rb, subtxn); } - /* cleanup changes in the toplevel txn */ + /* cleanup changes in the txn */ dlist_foreach_modify(iter, &txn->changes) { ReorderBufferChange *change; @@ -1529,7 +1541,7 @@ ReorderBufferTruncateTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) ReorderBufferTruncateTXN(rb, subtxn); } - /* cleanup changes in the toplevel txn */ + /* cleanup changes in the txn */ dlist_foreach_modify(iter, &txn->changes) { ReorderBufferChange *change; @@ -1579,6 +1591,13 @@ ReorderBufferTruncateTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) { ReorderBufferRestoreCleanup(rb, txn); txn->txn_flags &= ~RBTXN_IS_SERIALIZED; + + /* + * We set this flag to indicate if the transaction is ever serialized. + * We need this to accurately update the stats as otherwise the same + * transaction can be counted as serialized multiple times. + */ + txn->txn_flags |= RBTXN_IS_SERIALIZED_CLEAR; } /* also reset the number of entries in the transaction */ @@ -2183,6 +2202,13 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, ReorderBufferApplyMessage(rb, txn, change, streaming); break; + case REORDER_BUFFER_CHANGE_INVALIDATION: + /* Execute the invalidation messages locally */ + ReorderBufferExecuteInvalidations( + change->data.inval.ninvalidations, + change->data.inval.invalidations); + break; + case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT: /* get rid of the old */ TeardownHistoricSnapshot(false); @@ -2233,13 +2259,6 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, TeardownHistoricSnapshot(false); SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash); - - /* - * Every time the CommandId is incremented, we could - * see new catalog contents, so execute all - * invalidations. - */ - ReorderBufferExecuteInvalidations(rb, txn); } break; @@ -2306,7 +2325,7 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, AbortCurrentTransaction(); /* make sure there's no cache pollution */ - ReorderBufferExecuteInvalidations(rb, txn); + ReorderBufferExecuteInvalidations(txn->ninvalidations, txn->invalidations); if (using_subtxn) RollbackAndReleaseCurrentSubTransaction(); @@ -2345,7 +2364,8 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, AbortCurrentTransaction(); /* make sure there's no cache pollution */ - ReorderBufferExecuteInvalidations(rb, txn); + ReorderBufferExecuteInvalidations(txn->ninvalidations, + txn->invalidations); if (using_subtxn) RollbackAndReleaseCurrentSubTransaction(); @@ -2802,10 +2822,13 @@ ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid, * Setup the invalidation of the toplevel transaction. * * This needs to be called for each XLOG_XACT_INVALIDATIONS message and - * accumulates all the invalidation messages in the toplevel transaction. - * This is required because in some cases where we skip processing the - * transaction (see ReorderBufferForget), we need to execute all the - * invalidations together. + * accumulates all the invalidation messages in the toplevel transaction as + * well as in the form of change in reorder buffer. We require to record it in + * form of the change so that we can execute only the required invalidations + * instead of executing all the invalidations on each CommandId increment. We + * also need to accumulate these in the toplevel transaction because in some + * cases we skip processing the transaction (see ReorderBufferForget), we need + * to execute all the invalidations together. */ void ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid, @@ -2813,12 +2836,16 @@ ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid, SharedInvalidationMessage *msgs) { ReorderBufferTXN *txn; + MemoryContext oldcontext; + ReorderBufferChange *change; txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true); + oldcontext = MemoryContextSwitchTo(rb->context); + /* - * We collect all the invalidations under the top transaction so that we - * can execute them all together. + * Collect all the invalidations under the top transaction so that we can + * execute them all together. See comment atop this function */ if (txn->toptxn) txn = txn->toptxn; @@ -2830,8 +2857,7 @@ ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid, { txn->ninvalidations = nmsgs; txn->invalidations = (SharedInvalidationMessage *) - MemoryContextAlloc(rb->context, - sizeof(SharedInvalidationMessage) * nmsgs); + palloc(sizeof(SharedInvalidationMessage) * nmsgs); memcpy(txn->invalidations, msgs, sizeof(SharedInvalidationMessage) * nmsgs); } @@ -2845,6 +2871,18 @@ ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid, nmsgs * sizeof(SharedInvalidationMessage)); txn->ninvalidations += nmsgs; } + + change = ReorderBufferGetChange(rb); + change->action = REORDER_BUFFER_CHANGE_INVALIDATION; + change->data.inval.ninvalidations = nmsgs; + change->data.inval.invalidations = (SharedInvalidationMessage *) + palloc(sizeof(SharedInvalidationMessage) * nmsgs); + memcpy(change->data.inval.invalidations, msgs, + sizeof(SharedInvalidationMessage) * nmsgs); + + ReorderBufferQueueChange(rb, xid, lsn, change, false); + + MemoryContextSwitchTo(oldcontext); } /* @@ -2852,12 +2890,12 @@ ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid, * in the changestream but we don't know which those are. */ static void -ReorderBufferExecuteInvalidations(ReorderBuffer *rb, ReorderBufferTXN *txn) +ReorderBufferExecuteInvalidations(uint32 nmsgs, SharedInvalidationMessage *msgs) { int i; - for (i = 0; i < txn->ninvalidations; i++) - LocalExecuteInvalidationMessage(&txn->invalidations[i]); + for (i = 0; i < nmsgs; i++) + LocalExecuteInvalidationMessage(&msgs[i]); } /* @@ -3112,6 +3150,7 @@ ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) int fd = -1; XLogSegNo curOpenSegNo = 0; Size spilled = 0; + Size size = txn->size; elog(DEBUG2, "spill %u changes in XID %u to disk", (uint32) txn->nentries_mem, txn->xid); @@ -3170,6 +3209,16 @@ ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) spilled++; } + /* update the statistics iff we have spilled anything */ + if (spilled) + { + rb->spillCount += 1; + rb->spillBytes += size; + + /* don't consider already serialized transactions */ + rb->spillTxns += (rbtxn_is_serialized(txn) || rbtxn_is_serialized_clear(txn)) ? 0 : 1; + } + Assert(spilled == txn->nentries_mem); Assert(dlist_is_empty(&txn->changes)); txn->nentries_mem = 0; @@ -3279,6 +3328,24 @@ ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn, change->data.msg.message_size); data += change->data.msg.message_size; + break; + } + case REORDER_BUFFER_CHANGE_INVALIDATION: + { + char *data; + Size inval_size = sizeof(SharedInvalidationMessage) * + change->data.inval.ninvalidations; + + sz += inval_size; + + ReorderBufferSerializeReserve(rb, sz); + data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange); + + /* might have been reallocated above */ + ondisk = (ReorderBufferDiskChange *) rb->outbuf; + memcpy(data, change->data.inval.invalidations, inval_size); + data += inval_size; + break; } case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT: @@ -3418,6 +3485,8 @@ ReorderBufferStreamTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) { Snapshot snapshot_now; CommandId command_id; + Size stream_bytes; + bool txn_is_streamed; /* We can never reach here for a subtransaction. */ Assert(txn->toptxn == NULL); @@ -3498,10 +3567,25 @@ ReorderBufferStreamTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) txn->snapshot_now = NULL; } + /* + * Remember this information to be used later to update stats. We can't + * update the stats here as an error while processing the changes would + * lead to the accumulation of stats even though we haven't streamed all + * the changes. + */ + txn_is_streamed = rbtxn_is_streamed(txn); + stream_bytes = txn->total_size; + /* Process and send the changes to output plugin. */ ReorderBufferProcessTXN(rb, txn, InvalidXLogRecPtr, snapshot_now, command_id, true); + rb->streamCount += 1; + rb->streamBytes += stream_bytes; + + /* Don't consider already streamed transaction. */ + rb->streamTxns += (txn_is_streamed) ? 0 : 1; + Assert(dlist_is_empty(&txn->changes)); Assert(txn->nentries == 0); Assert(txn->nentries_mem == 0); @@ -3556,6 +3640,12 @@ ReorderBufferChangeSize(ReorderBufferChange *change) break; } + case REORDER_BUFFER_CHANGE_INVALIDATION: + { + sz += sizeof(SharedInvalidationMessage) * + change->data.inval.ninvalidations; + break; + } case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT: { Snapshot snap; @@ -3822,6 +3912,19 @@ ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn, change->data.msg.message_size); data += change->data.msg.message_size; + break; + } + case REORDER_BUFFER_CHANGE_INVALIDATION: + { + Size inval_size = sizeof(SharedInvalidationMessage) * + change->data.inval.ninvalidations; + + change->data.inval.invalidations = + MemoryContextAlloc(rb->context, inval_size); + + /* read the message */ + memcpy(change->data.inval.invalidations, data, inval_size); + break; } case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT: diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c index 374ed42ae3d1..698353b61a67 100644 --- a/src/backend/replication/logical/tablesync.c +++ b/src/backend/replication/logical/tablesync.c @@ -1,6 +1,6 @@ /*------------------------------------------------------------------------- * tablesync.c - * PostgreSQL logical replication + * PostgreSQL logical replication: initial table data synchronization * * Copyright (c) 2012-2020, PostgreSQL Global Development Group * @@ -26,26 +26,30 @@ * - It allows us to synchronize any tables added after the initial * synchronization has finished. * - * The stream position synchronization works in multiple steps. - * - Sync finishes copy and sets worker state as SYNCWAIT and waits for - * state to change in a loop. - * - Apply periodically checks tables that are synchronizing for SYNCWAIT. - * When the desired state appears, it will set the worker state to - * CATCHUP and starts loop-waiting until either the table state is set - * to SYNCDONE or the sync worker exits. + * The stream position synchronization works in multiple steps: + * - Apply worker requests a tablesync worker to start, setting the new + * table state to INIT. + * - Tablesync worker starts; changes table state from INIT to DATASYNC while + * copying. + * - Tablesync worker finishes the copy and sets table state to SYNCWAIT; + * waits for state change. + * - Apply worker periodically checks for tables in SYNCWAIT state. When + * any appear, it sets the table state to CATCHUP and starts loop-waiting + * until either the table state is set to SYNCDONE or the sync worker + * exits. * - After the sync worker has seen the state change to CATCHUP, it will * read the stream and apply changes (acting like an apply worker) until * it catches up to the specified stream position. Then it sets the * state to SYNCDONE. There might be zero changes applied between * CATCHUP and SYNCDONE, because the sync worker might be ahead of the * apply worker. - * - Once the state was set to SYNCDONE, the apply will continue tracking + * - Once the state is set to SYNCDONE, the apply will continue tracking * the table until it reaches the SYNCDONE stream position, at which * point it sets state to READY and stops tracking. Again, there might * be zero changes in between. * - * So the state progression is always: INIT -> DATASYNC -> SYNCWAIT -> CATCHUP -> - * SYNCDONE -> READY. + * So the state progression is always: INIT -> DATASYNC -> SYNCWAIT -> + * CATCHUP -> SYNCDONE -> READY. * * The catalog pg_subscription_rel is used to keep information about * subscribed tables and their state. Some transient state during data @@ -67,7 +71,8 @@ * -> continue rep * apply:11 * -> set in catalog READY - * - Sync in front: + * + * - Sync is in front: * sync:10 * -> set in memory SYNCWAIT * apply:8 @@ -142,13 +147,14 @@ finish_sync_worker(void) } /* - * Wait until the relation synchronization state is set in the catalog to the - * expected one. + * Wait until the relation sync state is set in the catalog to the expected + * one; return true when it happens. * - * Used when transitioning from CATCHUP state to SYNCDONE. + * Returns false if the table sync worker or the table itself have + * disappeared, or the table state has been reset. * - * Returns false if the synchronization worker has disappeared or the table state - * has been reset. + * Currently, this is used in the apply worker when transitioning from + * CATCHUP state to SYNCDONE. */ static bool wait_for_relation_state_change(Oid relid, char expected_state) @@ -162,28 +168,23 @@ wait_for_relation_state_change(Oid relid, char expected_state) CHECK_FOR_INTERRUPTS(); - /* XXX use cache invalidation here to improve performance? */ - PushActiveSnapshot(GetLatestSnapshot()); + InvalidateCatalogSnapshot(); state = GetSubscriptionRelState(MyLogicalRepWorker->subid, - relid, &statelsn, true); - PopActiveSnapshot(); + relid, &statelsn); if (state == SUBREL_STATE_UNKNOWN) - return false; + break; if (state == expected_state) return true; /* Check if the sync worker is still running and bail if not. */ LWLockAcquire(LogicalRepWorkerLock, LW_SHARED); - - /* Check if the opposite worker is still running and bail if not. */ - worker = logicalrep_worker_find(MyLogicalRepWorker->subid, - am_tablesync_worker() ? InvalidOid : relid, + worker = logicalrep_worker_find(MyLogicalRepWorker->subid, relid, false); LWLockRelease(LogicalRepWorkerLock); if (!worker) - return false; + break; (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, @@ -774,7 +775,7 @@ copy_table(Relation rel) * For non-tables, we need to do COPY (SELECT ...), but we can't just * do SELECT * because we need to not copy generated columns. */ - appendStringInfo(&cmd, "COPY (SELECT "); + appendStringInfoString(&cmd, "COPY (SELECT "); for (int i = 0; i < lrel.natts; i++) { appendStringInfoString(&cmd, quote_identifier(lrel.attnames[i])); @@ -812,6 +813,9 @@ copy_table(Relation rel) /* * Start syncing the table in the sync worker. * + * If nothing needs to be done to sync the table, we exit the worker without + * any further action. + * * The returned slot name is palloc'ed in current memory context. */ char * @@ -821,12 +825,14 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos) char *err; char relstate; XLogRecPtr relstate_lsn; + Relation rel; + WalRcvExecResult *res; /* Check the state of the table synchronization. */ StartTransactionCommand(); relstate = GetSubscriptionRelState(MyLogicalRepWorker->subid, MyLogicalRepWorker->relid, - &relstate_lsn, true); + &relstate_lsn); CommitTransactionCommand(); SpinLockAcquire(&MyLogicalRepWorker->relmutex); @@ -834,6 +840,18 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos) MyLogicalRepWorker->relstate_lsn = relstate_lsn; SpinLockRelease(&MyLogicalRepWorker->relmutex); + /* + * If synchronization is already done or no longer necessary, exit now + * that we've updated shared memory state. + */ + switch (relstate) + { + case SUBREL_STATE_SYNCDONE: + case SUBREL_STATE_READY: + case SUBREL_STATE_UNKNOWN: + finish_sync_worker(); /* doesn't return */ + } + /* * To build a slot name for the sync work, we are limited to NAMEDATALEN - * 1 characters. We cut the original slot name to NAMEDATALEN - 28 chars @@ -858,134 +876,87 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos) ereport(ERROR, (errmsg("could not connect to the publisher: %s", err))); - switch (MyLogicalRepWorker->relstate) - { - case SUBREL_STATE_INIT: - case SUBREL_STATE_DATASYNC: - { - Relation rel; - WalRcvExecResult *res; + Assert(MyLogicalRepWorker->relstate == SUBREL_STATE_INIT || + MyLogicalRepWorker->relstate == SUBREL_STATE_DATASYNC); - SpinLockAcquire(&MyLogicalRepWorker->relmutex); - MyLogicalRepWorker->relstate = SUBREL_STATE_DATASYNC; - MyLogicalRepWorker->relstate_lsn = InvalidXLogRecPtr; - SpinLockRelease(&MyLogicalRepWorker->relmutex); - - /* Update the state and make it visible to others. */ - StartTransactionCommand(); - UpdateSubscriptionRelState(MyLogicalRepWorker->subid, - MyLogicalRepWorker->relid, - MyLogicalRepWorker->relstate, - MyLogicalRepWorker->relstate_lsn); - CommitTransactionCommand(); - pgstat_report_stat(false); + SpinLockAcquire(&MyLogicalRepWorker->relmutex); + MyLogicalRepWorker->relstate = SUBREL_STATE_DATASYNC; + MyLogicalRepWorker->relstate_lsn = InvalidXLogRecPtr; + SpinLockRelease(&MyLogicalRepWorker->relmutex); - /* - * We want to do the table data sync in a single transaction. - */ - StartTransactionCommand(); + /* Update the state and make it visible to others. */ + StartTransactionCommand(); + UpdateSubscriptionRelState(MyLogicalRepWorker->subid, + MyLogicalRepWorker->relid, + MyLogicalRepWorker->relstate, + MyLogicalRepWorker->relstate_lsn); + CommitTransactionCommand(); + pgstat_report_stat(false); - /* - * Use a standard write lock here. It might be better to - * disallow access to the table while it's being synchronized. - * But we don't want to block the main apply process from - * working and it has to open the relation in RowExclusiveLock - * when remapping remote relation id to local one. - */ - rel = table_open(MyLogicalRepWorker->relid, RowExclusiveLock); + /* + * We want to do the table data sync in a single transaction. + */ + StartTransactionCommand(); - /* - * Create a temporary slot for the sync process. We do this - * inside the transaction so that we can use the snapshot made - * by the slot to get existing data. - */ - res = walrcv_exec(wrconn, - "BEGIN READ ONLY ISOLATION LEVEL " - "REPEATABLE READ", 0, NULL); - if (res->status != WALRCV_OK_COMMAND) - ereport(ERROR, - (errmsg("table copy could not start transaction on publisher"), - errdetail("The error was: %s", res->err))); - walrcv_clear_result(res); + /* + * Use a standard write lock here. It might be better to disallow access + * to the table while it's being synchronized. But we don't want to block + * the main apply process from working and it has to open the relation in + * RowExclusiveLock when remapping remote relation id to local one. + */ + rel = table_open(MyLogicalRepWorker->relid, RowExclusiveLock); - /* - * Create new temporary logical decoding slot. - * - * We'll use slot for data copy so make sure the snapshot is - * used for the transaction; that way the COPY will get data - * that is consistent with the lsn used by the slot to start - * decoding. - */ - walrcv_create_slot(wrconn, slotname, true, - CRS_USE_SNAPSHOT, origin_startpos); + /* + * Start a transaction in the remote node in REPEATABLE READ mode. This + * ensures that both the replication slot we create (see below) and the + * COPY are consistent with each other. + */ + res = walrcv_exec(wrconn, + "BEGIN READ ONLY ISOLATION LEVEL REPEATABLE READ", + 0, NULL); + if (res->status != WALRCV_OK_COMMAND) + ereport(ERROR, + (errmsg("table copy could not start transaction on publisher"), + errdetail("The error was: %s", res->err))); + walrcv_clear_result(res); - PushActiveSnapshot(GetTransactionSnapshot()); - copy_table(rel); - PopActiveSnapshot(); + /* + * Create a new temporary logical decoding slot. This slot will be used + * for the catchup phase after COPY is done, so tell it to use the + * snapshot to make the final data consistent. + */ + walrcv_create_slot(wrconn, slotname, true, + CRS_USE_SNAPSHOT, origin_startpos); - res = walrcv_exec(wrconn, "COMMIT", 0, NULL); - if (res->status != WALRCV_OK_COMMAND) - ereport(ERROR, - (errmsg("table copy could not finish transaction on publisher"), - errdetail("The error was: %s", res->err))); - walrcv_clear_result(res); + /* Now do the initial data copy */ + PushActiveSnapshot(GetTransactionSnapshot()); + copy_table(rel); + PopActiveSnapshot(); - table_close(rel, NoLock); + res = walrcv_exec(wrconn, "COMMIT", 0, NULL); + if (res->status != WALRCV_OK_COMMAND) + ereport(ERROR, + (errmsg("table copy could not finish transaction on publisher"), + errdetail("The error was: %s", res->err))); + walrcv_clear_result(res); - /* Make the copy visible. */ - CommandCounterIncrement(); + table_close(rel, NoLock); - /* - * We are done with the initial data synchronization, update - * the state. - */ - SpinLockAcquire(&MyLogicalRepWorker->relmutex); - MyLogicalRepWorker->relstate = SUBREL_STATE_SYNCWAIT; - MyLogicalRepWorker->relstate_lsn = *origin_startpos; - SpinLockRelease(&MyLogicalRepWorker->relmutex); - - /* Wait for main apply worker to tell us to catchup. */ - wait_for_worker_state_change(SUBREL_STATE_CATCHUP); - - /*---------- - * There are now two possible states here: - * a) Sync is behind the apply. If that's the case we need to - * catch up with it by consuming the logical replication - * stream up to the relstate_lsn. For that, we exit this - * function and continue in ApplyWorkerMain(). - * b) Sync is caught up with the apply. So it can just set - * the state to SYNCDONE and finish. - *---------- - */ - if (*origin_startpos >= MyLogicalRepWorker->relstate_lsn) - { - /* - * Update the new state in catalog. No need to bother - * with the shmem state as we are exiting for good. - */ - UpdateSubscriptionRelState(MyLogicalRepWorker->subid, - MyLogicalRepWorker->relid, - SUBREL_STATE_SYNCDONE, - *origin_startpos); - finish_sync_worker(); - } - break; - } - case SUBREL_STATE_SYNCDONE: - case SUBREL_STATE_READY: - case SUBREL_STATE_UNKNOWN: + /* Make the copy visible. */ + CommandCounterIncrement(); - /* - * Nothing to do here but finish. (UNKNOWN means the relation was - * removed from pg_subscription_rel before the sync worker could - * start.) - */ - finish_sync_worker(); - break; - default: - elog(ERROR, "unknown relation state \"%c\"", - MyLogicalRepWorker->relstate); - } + /* + * We are done with the initial data synchronization, update the state. + */ + SpinLockAcquire(&MyLogicalRepWorker->relmutex); + MyLogicalRepWorker->relstate = SUBREL_STATE_SYNCWAIT; + MyLogicalRepWorker->relstate_lsn = *origin_startpos; + SpinLockRelease(&MyLogicalRepWorker->relmutex); + /* + * Finally, wait until the main apply worker tells us to catch up and then + * return to let LogicalRepApplyLoop do it. + */ + wait_for_worker_state_change(SUBREL_STATE_CATCHUP); return slotname; } diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index b95025d3ae9d..f15c9e47e2e0 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -18,11 +18,45 @@ * This module includes server facing code and shares libpqwalreceiver * module with walreceiver for providing the libpq specific functionality. * + * + * STREAMED TRANSACTIONS + * --------------------- + * Streamed transactions (large transactions exceeding a memory limit on the + * upstream) are not applied immediately, but instead, the data is written + * to temporary files and then applied at once when the final commit arrives. + * + * Unlike the regular (non-streamed) case, handling streamed transactions has + * to handle aborts of both the toplevel transaction and subtransactions. This + * is achieved by tracking offsets for subtransactions, which is then used + * to truncate the file with serialized changes. + * + * The files are placed in tmp file directory by default, and the filenames + * include both the XID of the toplevel transaction and OID of the + * subscription. This is necessary so that different workers processing a + * remote transaction with the same XID doesn't interfere. + * + * We use BufFiles instead of using normal temporary files because (a) the + * BufFile infrastructure supports temporary files that exceed the OS file size + * limit, (b) provides a way for automatic clean up on the error and (c) provides + * a way to survive these files across local transactions and allow to open and + * close at stream start and close. We decided to use SharedFileSet + * infrastructure as without that it deletes the files on the closure of the + * file and if we decide to keep stream files open across the start/stop stream + * then it will consume a lot of memory (more than 8K for each BufFile and + * there could be multiple such BufFiles as the subscriber could receive + * multiple start/stop streams for different transactions before getting the + * commit). Moreover, if we don't use SharedFileSet then we also need to invent + * a new way to pass filenames to BufFile APIs so that we are allowed to open + * the file we desired across multiple stream-open calls for the same + * transaction. *------------------------------------------------------------------------- */ #include "postgres.h" +#include +#include + #include "access/table.h" #include "access/tableam.h" #include "access/xact.h" @@ -33,7 +67,9 @@ #include "catalog/pg_inherits.h" #include "catalog/pg_subscription.h" #include "catalog/pg_subscription_rel.h" +#include "catalog/pg_tablespace.h" #include "commands/tablecmds.h" +#include "commands/tablespace.h" #include "commands/trigger.h" #include "executor/executor.h" #include "executor/execPartition.h" @@ -45,8 +81,6 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "optimizer/optimizer.h" -#include "parser/analyze.h" -#include "parser/parse_relation.h" #include "pgstat.h" #include "postmaster/bgworker.h" #include "postmaster/interrupt.h" @@ -63,7 +97,9 @@ #include "replication/walreceiver.h" #include "replication/worker_internal.h" #include "rewrite/rewriteHandler.h" +#include "storage/buffile.h" #include "storage/bufmgr.h" +#include "storage/fd.h" #include "storage/ipc.h" #include "storage/lmgr.h" #include "storage/proc.h" @@ -71,6 +107,7 @@ #include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/catcache.h" +#include "utils/dynahash.h" #include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/guc.h" @@ -99,9 +136,26 @@ typedef struct SlotErrCallbackArg int remote_attnum; } SlotErrCallbackArg; +/* + * Stream xid hash entry. Whenever we see a new xid we create this entry in the + * xidhash and along with it create the streaming file and store the fileset handle. + * The subxact file is created iff there is any subxact info under this xid. This + * entry is used on the subsequent streams for the xid to get the corresponding + * fileset handles, so storing them in hash makes the search faster. + */ +typedef struct StreamXidHash +{ + TransactionId xid; /* xid is the hash key and must be first */ + SharedFileSet *stream_fileset; /* shared file set for stream data */ + SharedFileSet *subxact_fileset; /* shared file set for subxact info */ +} StreamXidHash; + static MemoryContext ApplyMessageContext = NULL; MemoryContext ApplyContext = NULL; +/* per stream context for streaming transactions */ +static MemoryContext LogicalStreamingContext = NULL; + WalReceiverConn *wrconn = NULL; Subscription *MySubscription = NULL; @@ -110,12 +164,66 @@ bool MySubscriptionValid = false; bool in_remote_transaction = false; static XLogRecPtr remote_final_lsn = InvalidXLogRecPtr; +/* fields valid only when processing streamed transaction */ +bool in_streamed_transaction = false; + +static TransactionId stream_xid = InvalidTransactionId; + +/* + * Hash table for storing the streaming xid information along with shared file + * set for streaming and subxact files. + */ +static HTAB *xidhash = NULL; + +/* BufFile handle of the current streaming file */ +static BufFile *stream_fd = NULL; + +typedef struct SubXactInfo +{ + TransactionId xid; /* XID of the subxact */ + int fileno; /* file number in the buffile */ + off_t offset; /* offset in the file */ +} SubXactInfo; + +/* Sub-transaction data for the current streaming transaction */ +typedef struct ApplySubXactData +{ + uint32 nsubxacts; /* number of sub-transactions */ + uint32 nsubxacts_max; /* current capacity of subxacts */ + TransactionId subxact_last; /* xid of the last sub-transaction */ + SubXactInfo *subxacts; /* sub-xact offset in changes file */ +} ApplySubXactData; + +static ApplySubXactData subxact_data = {0, 0, InvalidTransactionId, NULL}; + +static inline void subxact_filename(char *path, Oid subid, TransactionId xid); +static inline void changes_filename(char *path, Oid subid, TransactionId xid); + +/* + * Information about subtransactions of a given toplevel transaction. + */ +static void subxact_info_write(Oid subid, TransactionId xid); +static void subxact_info_read(Oid subid, TransactionId xid); +static void subxact_info_add(TransactionId xid); +static inline void cleanup_subxact_info(void); + +/* + * Serialize and deserialize changes for a toplevel transaction. + */ +static void stream_cleanup_files(Oid subid, TransactionId xid); +static void stream_open_file(Oid subid, TransactionId xid, bool first); +static void stream_write_change(char action, StringInfo s); +static void stream_close_file(void); + static void send_feedback(XLogRecPtr recvpos, bool force, bool requestReply); static void store_flush_position(XLogRecPtr remote_lsn); static void maybe_reread_subscription(void); +/* prototype needed because of stream_commit */ +static void apply_dispatch(StringInfo s); + static void apply_handle_insert_internal(ResultRelInfo *relinfo, EState *estate, TupleTableSlot *remoteslot); static void apply_handle_update_internal(ResultRelInfo *relinfo, @@ -187,6 +295,42 @@ ensure_transaction(void) return true; } +/* + * Handle streamed transactions. + * + * If in streaming mode (receiving a block of streamed transaction), we + * simply redirect it to a file for the proper toplevel transaction. + * + * Returns true for streamed transactions, false otherwise (regular mode). + */ +static bool +handle_streamed_transaction(const char action, StringInfo s) +{ + TransactionId xid; + + /* not in streaming mode */ + if (!in_streamed_transaction) + return false; + + Assert(stream_fd != NULL); + Assert(TransactionIdIsValid(stream_xid)); + + /* + * We should have received XID of the subxact as the first part of the + * message, so extract it. + */ + xid = pq_getmsgint(s, 4); + + Assert(TransactionIdIsValid(xid)); + + /* Add the new subxact to the array (unless already there). */ + subxact_info_add(xid); + + /* write the change to the current file */ + stream_write_change(action, s); + + return true; +} /* * Executor state preparation for evaluation of constraint expressions, @@ -198,7 +342,6 @@ static EState * create_estate_for_relation(LogicalRepRelMapEntry *rel) { EState *estate; - ResultRelInfo *resultRelInfo; RangeTblEntry *rte; estate = CreateExecutorState(); @@ -210,13 +353,6 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel) rte->rellockmode = AccessShareLock; ExecInitRangeTable(estate, list_make1(rte)); - resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0); - - estate->es_result_relations = resultRelInfo; - estate->es_num_result_relations = 1; - estate->es_result_relation_info = resultRelInfo; - estate->es_output_cid = GetCurrentCommandId(true); /* Prepare to catch AFTER triggers. */ @@ -612,16 +748,335 @@ static void apply_handle_origin(StringInfo s) { /* - * ORIGIN message can only come inside remote transaction and before any - * actual writes. + * ORIGIN message can only come inside streaming transaction or inside + * remote transaction and before any actual writes. */ - if (!in_remote_transaction || - (IsTransactionState() && !am_tablesync_worker())) + if (!in_streamed_transaction && + (!in_remote_transaction || + (IsTransactionState() && !am_tablesync_worker()))) ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("ORIGIN message sent out of order"))); } +/* + * Handle STREAM START message. + */ +static void +apply_handle_stream_start(StringInfo s) +{ + bool first_segment; + HASHCTL hash_ctl; + + Assert(!in_streamed_transaction); + + /* + * Start a transaction on stream start, this transaction will be committed + * on the stream stop. We need the transaction for handling the buffile, + * used for serializing the streaming data and subxact info. + */ + ensure_transaction(); + + /* notify handle methods we're processing a remote transaction */ + in_streamed_transaction = true; + + /* extract XID of the top-level transaction */ + stream_xid = logicalrep_read_stream_start(s, &first_segment); + + /* + * Initialize the xidhash table if we haven't yet. This will be used for + * the entire duration of the apply worker so create it in permanent + * context. + */ + if (xidhash == NULL) + { + hash_ctl.keysize = sizeof(TransactionId); + hash_ctl.entrysize = sizeof(StreamXidHash); + hash_ctl.hcxt = ApplyContext; + xidhash = hash_create("StreamXidHash", 1024, &hash_ctl, + HASH_ELEM | HASH_CONTEXT); + } + + /* open the spool file for this transaction */ + stream_open_file(MyLogicalRepWorker->subid, stream_xid, first_segment); + + /* if this is not the first segment, open existing subxact file */ + if (!first_segment) + subxact_info_read(MyLogicalRepWorker->subid, stream_xid); + + pgstat_report_activity(STATE_RUNNING, NULL); +} + +/* + * Handle STREAM STOP message. + */ +static void +apply_handle_stream_stop(StringInfo s) +{ + Assert(in_streamed_transaction); + + /* + * Close the file with serialized changes, and serialize information about + * subxacts for the toplevel transaction. + */ + subxact_info_write(MyLogicalRepWorker->subid, stream_xid); + stream_close_file(); + + /* We must be in a valid transaction state */ + Assert(IsTransactionState()); + + /* Commit the per-stream transaction */ + CommitTransactionCommand(); + + in_streamed_transaction = false; + + /* Reset per-stream context */ + MemoryContextReset(LogicalStreamingContext); + + pgstat_report_activity(STATE_IDLE, NULL); +} + +/* + * Handle STREAM abort message. + */ +static void +apply_handle_stream_abort(StringInfo s) +{ + TransactionId xid; + TransactionId subxid; + + Assert(!in_streamed_transaction); + + logicalrep_read_stream_abort(s, &xid, &subxid); + + /* + * If the two XIDs are the same, it's in fact abort of toplevel xact, so + * just delete the files with serialized info. + */ + if (xid == subxid) + stream_cleanup_files(MyLogicalRepWorker->subid, xid); + else + { + /* + * OK, so it's a subxact. We need to read the subxact file for the + * toplevel transaction, determine the offset tracked for the subxact, + * and truncate the file with changes. We also remove the subxacts + * with higher offsets (or rather higher XIDs). + * + * We intentionally scan the array from the tail, because we're likely + * aborting a change for the most recent subtransactions. + * + * We can't use the binary search here as subxact XIDs won't + * necessarily arrive in sorted order, consider the case where we have + * released the savepoint for multiple subtransactions and then + * performed rollback to savepoint for one of the earlier + * sub-transaction. + */ + + int64 i; + int64 subidx; + BufFile *fd; + bool found = false; + char path[MAXPGPATH]; + StreamXidHash *ent; + + subidx = -1; + ensure_transaction(); + subxact_info_read(MyLogicalRepWorker->subid, xid); + + for (i = subxact_data.nsubxacts; i > 0; i--) + { + if (subxact_data.subxacts[i - 1].xid == subxid) + { + subidx = (i - 1); + found = true; + break; + } + } + + /* + * If it's an empty sub-transaction then we will not find the subxid + * here so just cleanup the subxact info and return. + */ + if (!found) + { + /* Cleanup the subxact info */ + cleanup_subxact_info(); + CommitTransactionCommand(); + return; + } + + Assert((subidx >= 0) && (subidx < subxact_data.nsubxacts)); + + ent = (StreamXidHash *) hash_search(xidhash, + (void *) &xid, + HASH_FIND, + &found); + Assert(found); + + /* open the changes file */ + changes_filename(path, MyLogicalRepWorker->subid, xid); + fd = BufFileOpenShared(ent->stream_fileset, path, O_RDWR); + + /* OK, truncate the file at the right offset */ + BufFileTruncateShared(fd, subxact_data.subxacts[subidx].fileno, + subxact_data.subxacts[subidx].offset); + BufFileClose(fd); + + /* discard the subxacts added later */ + subxact_data.nsubxacts = subidx; + + /* write the updated subxact list */ + subxact_info_write(MyLogicalRepWorker->subid, xid); + CommitTransactionCommand(); + } +} + +/* + * Handle STREAM COMMIT message. + */ +static void +apply_handle_stream_commit(StringInfo s) +{ + TransactionId xid; + StringInfoData s2; + int nchanges; + char path[MAXPGPATH]; + char *buffer = NULL; + bool found; + LogicalRepCommitData commit_data; + StreamXidHash *ent; + MemoryContext oldcxt; + BufFile *fd; + + Assert(!in_streamed_transaction); + + xid = logicalrep_read_stream_commit(s, &commit_data); + + elog(DEBUG1, "received commit for streamed transaction %u", xid); + + ensure_transaction(); + + /* + * Allocate file handle and memory required to process all the messages in + * TopTransactionContext to avoid them getting reset after each message is + * processed. + */ + oldcxt = MemoryContextSwitchTo(TopTransactionContext); + + /* open the spool file for the committed transaction */ + changes_filename(path, MyLogicalRepWorker->subid, xid); + elog(DEBUG1, "replaying changes from file \"%s\"", path); + ent = (StreamXidHash *) hash_search(xidhash, + (void *) &xid, + HASH_FIND, + &found); + Assert(found); + fd = BufFileOpenShared(ent->stream_fileset, path, O_RDONLY); + + buffer = palloc(BLCKSZ); + initStringInfo(&s2); + + MemoryContextSwitchTo(oldcxt); + + remote_final_lsn = commit_data.commit_lsn; + + /* + * Make sure the handle apply_dispatch methods are aware we're in a remote + * transaction. + */ + in_remote_transaction = true; + pgstat_report_activity(STATE_RUNNING, NULL); + + /* + * Read the entries one by one and pass them through the same logic as in + * apply_dispatch. + */ + nchanges = 0; + while (true) + { + int nbytes; + int len; + + CHECK_FOR_INTERRUPTS(); + + /* read length of the on-disk record */ + nbytes = BufFileRead(fd, &len, sizeof(len)); + + /* have we reached end of the file? */ + if (nbytes == 0) + break; + + /* do we have a correct length? */ + if (nbytes != sizeof(len)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from streaming transaction's changes file \"%s\": %m", + path))); + + Assert(len > 0); + + /* make sure we have sufficiently large buffer */ + buffer = repalloc(buffer, len); + + /* and finally read the data into the buffer */ + if (BufFileRead(fd, buffer, len) != len) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from streaming transaction's changes file \"%s\": %m", + path))); + + /* copy the buffer to the stringinfo and call apply_dispatch */ + resetStringInfo(&s2); + appendBinaryStringInfo(&s2, buffer, len); + + /* Ensure we are reading the data into our memory context. */ + oldcxt = MemoryContextSwitchTo(ApplyMessageContext); + + apply_dispatch(&s2); + + MemoryContextReset(ApplyMessageContext); + + MemoryContextSwitchTo(oldcxt); + + nchanges++; + + if (nchanges % 1000 == 0) + elog(DEBUG1, "replayed %d changes from file '%s'", + nchanges, path); + } + + BufFileClose(fd); + + /* + * Update origin state so we can restart streaming from correct position + * in case of crash. + */ + replorigin_session_origin_lsn = commit_data.end_lsn; + replorigin_session_origin_timestamp = commit_data.committime; + + pfree(buffer); + pfree(s2.data); + + CommitTransactionCommand(); + pgstat_report_stat(false); + + store_flush_position(commit_data.end_lsn); + + elog(DEBUG1, "replayed %d (all) changes from file \"%s\"", + nchanges, path); + + in_remote_transaction = false; + + /* Process any tables that are being synchronized in parallel. */ + process_syncing_tables(commit_data.end_lsn); + + /* unlink the files with serialized changes and subxact info */ + stream_cleanup_files(MyLogicalRepWorker->subid, xid); + + pgstat_report_activity(STATE_IDLE, NULL); +} + /* * Handle RELATION message. * @@ -635,6 +1090,9 @@ apply_handle_relation(StringInfo s) { LogicalRepRelation *rel; + if (handle_streamed_transaction('R', s)) + return; + rel = logicalrep_read_rel(s); logicalrep_relmap_update(rel); } @@ -650,6 +1108,9 @@ apply_handle_type(StringInfo s) { LogicalRepTyp typ; + if (handle_streamed_transaction('Y', s)) + return; + logicalrep_read_typ(s, &typ); logicalrep_typmap_update(&typ); } @@ -679,6 +1140,7 @@ GetRelationIdentityOrPK(Relation rel) static void apply_handle_insert(StringInfo s) { + ResultRelInfo *resultRelInfo; LogicalRepRelMapEntry *rel; LogicalRepTupleData newtup; LogicalRepRelId relid; @@ -686,6 +1148,9 @@ apply_handle_insert(StringInfo s) TupleTableSlot *remoteslot; MemoryContext oldctx; + if (handle_streamed_transaction('I', s)) + return; + ensure_transaction(); relid = logicalrep_read_insert(s, &newtup); @@ -705,6 +1170,8 @@ apply_handle_insert(StringInfo s) remoteslot = ExecInitExtraTupleSlot(estate, RelationGetDescr(rel->localrel), &TTSOpsVirtual); + resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0); /* Input functions may need an active snapshot, so get one */ PushActiveSnapshot(GetTransactionSnapshot()); @@ -717,10 +1184,10 @@ apply_handle_insert(StringInfo s) /* For a partitioned table, insert the tuple into a partition. */ if (rel->localrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - apply_handle_tuple_routing(estate->es_result_relation_info, estate, + apply_handle_tuple_routing(resultRelInfo, estate, remoteslot, NULL, rel, CMD_INSERT); else - apply_handle_insert_internal(estate->es_result_relation_info, estate, + apply_handle_insert_internal(resultRelInfo, estate, remoteslot); PopActiveSnapshot(); @@ -744,7 +1211,7 @@ apply_handle_insert_internal(ResultRelInfo *relinfo, ExecOpenIndices(relinfo, false); /* Do the insert. */ - ExecSimpleRelationInsert(estate, remoteslot); + ExecSimpleRelationInsert(relinfo, estate, remoteslot); /* Cleanup. */ ExecCloseIndices(relinfo); @@ -791,6 +1258,7 @@ check_relation_updatable(LogicalRepRelMapEntry *rel) static void apply_handle_update(StringInfo s) { + ResultRelInfo *resultRelInfo; LogicalRepRelMapEntry *rel; LogicalRepRelId relid; EState *estate; @@ -801,6 +1269,9 @@ apply_handle_update(StringInfo s) RangeTblEntry *target_rte; MemoryContext oldctx; + if (handle_streamed_transaction('U', s)) + return; + ensure_transaction(); relid = logicalrep_read_update(s, &has_oldtup, &oldtup, @@ -824,6 +1295,8 @@ apply_handle_update(StringInfo s) remoteslot = ExecInitExtraTupleSlot(estate, RelationGetDescr(rel->localrel), &TTSOpsVirtual); + resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0); /* * Populate updatedCols so that per-column triggers can fire. This could @@ -848,7 +1321,8 @@ apply_handle_update(StringInfo s) } } - fill_extraUpdatedCols(target_rte, RelationGetDescr(rel->localrel)); + /* Also populate extraUpdatedCols, in case we have generated columns */ + fill_extraUpdatedCols(target_rte, rel->localrel); PushActiveSnapshot(GetTransactionSnapshot()); @@ -860,10 +1334,10 @@ apply_handle_update(StringInfo s) /* For a partitioned table, apply update to correct partition. */ if (rel->localrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - apply_handle_tuple_routing(estate->es_result_relation_info, estate, + apply_handle_tuple_routing(resultRelInfo, estate, remoteslot, &newtup, rel, CMD_UPDATE); else - apply_handle_update_internal(estate->es_result_relation_info, estate, + apply_handle_update_internal(resultRelInfo, estate, remoteslot, &newtup, rel); PopActiveSnapshot(); @@ -915,7 +1389,8 @@ apply_handle_update_internal(ResultRelInfo *relinfo, EvalPlanQualSetSlot(&epqstate, remoteslot); /* Do the actual update. */ - ExecSimpleRelationUpdate(estate, &epqstate, localslot, remoteslot); + ExecSimpleRelationUpdate(relinfo, estate, &epqstate, localslot, + remoteslot); } else { @@ -943,6 +1418,7 @@ apply_handle_update_internal(ResultRelInfo *relinfo, static void apply_handle_delete(StringInfo s) { + ResultRelInfo *resultRelInfo; LogicalRepRelMapEntry *rel; LogicalRepTupleData oldtup; LogicalRepRelId relid; @@ -950,6 +1426,9 @@ apply_handle_delete(StringInfo s) TupleTableSlot *remoteslot; MemoryContext oldctx; + if (handle_streamed_transaction('D', s)) + return; + ensure_transaction(); relid = logicalrep_read_delete(s, &oldtup); @@ -972,6 +1451,8 @@ apply_handle_delete(StringInfo s) remoteslot = ExecInitExtraTupleSlot(estate, RelationGetDescr(rel->localrel), &TTSOpsVirtual); + resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0); PushActiveSnapshot(GetTransactionSnapshot()); @@ -982,10 +1463,10 @@ apply_handle_delete(StringInfo s) /* For a partitioned table, apply delete to correct partition. */ if (rel->localrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - apply_handle_tuple_routing(estate->es_result_relation_info, estate, + apply_handle_tuple_routing(resultRelInfo, estate, remoteslot, NULL, rel, CMD_DELETE); else - apply_handle_delete_internal(estate->es_result_relation_info, estate, + apply_handle_delete_internal(resultRelInfo, estate, remoteslot, &rel->remoterel); PopActiveSnapshot(); @@ -1024,7 +1505,7 @@ apply_handle_delete_internal(ResultRelInfo *relinfo, EState *estate, EvalPlanQualSetSlot(&epqstate, localslot); /* Do the actual delete. */ - ExecSimpleRelationDelete(estate, &epqstate, localslot); + ExecSimpleRelationDelete(relinfo, estate, &epqstate, localslot); } else { @@ -1090,7 +1571,6 @@ apply_handle_tuple_routing(ResultRelInfo *relinfo, ResultRelInfo *partrelinfo; Relation partrel; TupleTableSlot *remoteslot_part; - PartitionRoutingInfo *partinfo; TupleConversionMap *map; MemoryContext oldctx; @@ -1117,11 +1597,10 @@ apply_handle_tuple_routing(ResultRelInfo *relinfo, * partition's rowtype. Convert if needed or just copy, using a dedicated * slot to store the tuple in any case. */ - partinfo = partrelinfo->ri_PartitionInfo; - remoteslot_part = partinfo->pi_PartitionTupleSlot; + remoteslot_part = partrelinfo->ri_PartitionTupleSlot; if (remoteslot_part == NULL) remoteslot_part = table_slot_create(partrel, &estate->es_tupleTable); - map = partinfo->pi_RootToPartitionMap; + map = partrelinfo->ri_RootToPartitionMap; if (map != NULL) remoteslot_part = execute_attr_map_slot(map->attrMap, remoteslot, remoteslot_part); @@ -1132,7 +1611,6 @@ apply_handle_tuple_routing(ResultRelInfo *relinfo, } MemoryContextSwitchTo(oldctx); - estate->es_result_relation_info = partrelinfo; switch (operation) { case CMD_INSERT: @@ -1196,7 +1674,7 @@ apply_handle_tuple_routing(ResultRelInfo *relinfo, * Does the updated tuple still satisfy the current * partition's constraint? */ - if (partrelinfo->ri_PartitionCheck == NULL || + if (!partrel->rd_rel->relispartition || ExecPartitionCheck(partrelinfo, remoteslot_part, estate, false)) { @@ -1213,8 +1691,8 @@ apply_handle_tuple_routing(ResultRelInfo *relinfo, ExecOpenIndices(partrelinfo, false); EvalPlanQualSetSlot(&epqstate, remoteslot_part); - ExecSimpleRelationUpdate(estate, &epqstate, localslot, - remoteslot_part); + ExecSimpleRelationUpdate(partrelinfo, estate, &epqstate, + localslot, remoteslot_part); ExecCloseIndices(partrelinfo); EvalPlanQualEnd(&epqstate); } @@ -1255,7 +1733,6 @@ apply_handle_tuple_routing(ResultRelInfo *relinfo, Assert(partrelinfo_new != partrelinfo); /* DELETE old tuple found in the old partition. */ - estate->es_result_relation_info = partrelinfo; apply_handle_delete_internal(partrelinfo, estate, localslot, &relmapentry->remoterel); @@ -1268,12 +1745,11 @@ apply_handle_tuple_routing(ResultRelInfo *relinfo, */ oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); partrel = partrelinfo_new->ri_RelationDesc; - partinfo = partrelinfo_new->ri_PartitionInfo; - remoteslot_part = partinfo->pi_PartitionTupleSlot; + remoteslot_part = partrelinfo_new->ri_PartitionTupleSlot; if (remoteslot_part == NULL) remoteslot_part = table_slot_create(partrel, &estate->es_tupleTable); - map = partinfo->pi_RootToPartitionMap; + map = partrelinfo_new->ri_RootToPartitionMap; if (map != NULL) { remoteslot_part = execute_attr_map_slot(map->attrMap, @@ -1287,7 +1763,6 @@ apply_handle_tuple_routing(ResultRelInfo *relinfo, slot_getallattrs(remoteslot); } MemoryContextSwitchTo(oldctx); - estate->es_result_relation_info = partrelinfo_new; apply_handle_insert_internal(partrelinfo_new, estate, remoteslot_part); } @@ -1320,6 +1795,9 @@ apply_handle_truncate(StringInfo s) List *relids_logged = NIL; ListCell *lc; + if (handle_streamed_transaction('T', s)) + return; + ensure_transaction(); remote_relids = logicalrep_read_truncate(s, &cascade, &restart_seqs); @@ -1419,51 +1897,66 @@ apply_handle_truncate(StringInfo s) static void apply_dispatch(StringInfo s) { - char action = pq_getmsgbyte(s); + LogicalRepMsgType action = pq_getmsgbyte(s); switch (action) { - /* BEGIN */ - case 'B': + case LOGICAL_REP_MSG_BEGIN: apply_handle_begin(s); - break; - /* COMMIT */ - case 'C': + return; + + case LOGICAL_REP_MSG_COMMIT: apply_handle_commit(s); - break; - /* INSERT */ - case 'I': + return; + + case LOGICAL_REP_MSG_INSERT: apply_handle_insert(s); - break; - /* UPDATE */ - case 'U': + return; + + case LOGICAL_REP_MSG_UPDATE: apply_handle_update(s); - break; - /* DELETE */ - case 'D': + return; + + case LOGICAL_REP_MSG_DELETE: apply_handle_delete(s); - break; - /* TRUNCATE */ - case 'T': + return; + + case LOGICAL_REP_MSG_TRUNCATE: apply_handle_truncate(s); - break; - /* RELATION */ - case 'R': + return; + + case LOGICAL_REP_MSG_RELATION: apply_handle_relation(s); - break; - /* TYPE */ - case 'Y': + return; + + case LOGICAL_REP_MSG_TYPE: apply_handle_type(s); - break; - /* ORIGIN */ - case 'O': + return; + + case LOGICAL_REP_MSG_ORIGIN: apply_handle_origin(s); - break; - default: - ereport(ERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("invalid logical replication message type \"%c\"", action))); + return; + + case LOGICAL_REP_MSG_STREAM_START: + apply_handle_stream_start(s); + return; + + case LOGICAL_REP_MSG_STREAM_END: + apply_handle_stream_stop(s); + return; + + case LOGICAL_REP_MSG_STREAM_ABORT: + apply_handle_stream_abort(s); + return; + + case LOGICAL_REP_MSG_STREAM_COMMIT: + apply_handle_stream_commit(s); + return; } + + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("invalid logical replication message type \"%c\"", action))); } /* @@ -1562,6 +2055,8 @@ static void LogicalRepApplyLoop(XLogRecPtr last_received) { TimestampTz last_recv_timestamp = GetCurrentTimestamp(); + bool ping_sent = false; + TimeLineID tli; /* * Init the ApplyMessageContext which we clean up after each replication @@ -1571,9 +2066,18 @@ LogicalRepApplyLoop(XLogRecPtr last_received) "ApplyMessageContext", ALLOCSET_DEFAULT_SIZES); + /* + * This memory context is used for per-stream data when the streaming mode + * is enabled. This context is reset on each stream stop. + */ + LogicalStreamingContext = AllocSetContextCreate(ApplyContext, + "LogicalStreamingContext", + ALLOCSET_DEFAULT_SIZES); + /* mark as idle, before starting to loop */ pgstat_report_activity(STATE_IDLE, NULL); + /* This outer loop iterates once per wait. */ for (;;) { pgsocket fd = PGINVALID_SOCKET; @@ -1581,7 +2085,6 @@ LogicalRepApplyLoop(XLogRecPtr last_received) int len; char *buf = NULL; bool endofstream = false; - bool ping_sent = false; long wait_time; CHECK_FOR_INTERRUPTS(); @@ -1592,7 +2095,7 @@ LogicalRepApplyLoop(XLogRecPtr last_received) if (len != 0) { - /* Process the data */ + /* Loop to process all available data (without blocking). */ for (;;) { CHECK_FOR_INTERRUPTS(); @@ -1675,7 +2178,7 @@ LogicalRepApplyLoop(XLogRecPtr last_received) /* confirm all writes so far */ send_feedback(last_received, false, false); - if (!in_remote_transaction) + if (!in_remote_transaction && !in_streamed_transaction) { /* * If we didn't get any transactions for a while there might be @@ -1695,12 +2198,7 @@ LogicalRepApplyLoop(XLogRecPtr last_received) /* Check if we need to exit the streaming loop. */ if (endofstream) - { - TimeLineID tli; - - walrcv_endstreaming(wrconn, &tli); break; - } /* * Wait for more data or latch. If we have unflushed transactions, @@ -1761,10 +2259,7 @@ LogicalRepApplyLoop(XLogRecPtr last_received) ereport(ERROR, (errmsg("terminating logical replication worker due to timeout"))); - /* - * We didn't receive anything new, for half of receiver - * replication timeout. Ping the server. - */ + /* Check to see if it's time for a ping. */ if (!ping_sent) { timeout = TimestampTzPlusMilliseconds(last_recv_timestamp, @@ -1780,6 +2275,9 @@ LogicalRepApplyLoop(XLogRecPtr last_received) send_feedback(last_received, requestReply, requestReply); } } + + /* All done */ + walrcv_endstreaming(wrconn, &tli); } /* @@ -1939,6 +2437,7 @@ maybe_reread_subscription(void) strcmp(newsub->name, MySubscription->name) != 0 || strcmp(newsub->slotname, MySubscription->slotname) != 0 || newsub->binary != MySubscription->binary || + newsub->stream != MySubscription->stream || !equal(newsub->publications, MySubscription->publications)) { ereport(LOG, @@ -1980,6 +2479,443 @@ subscription_change_cb(Datum arg, int cacheid, uint32 hashvalue) MySubscriptionValid = false; } +/* + * subxact_info_write + * Store information about subxacts for a toplevel transaction. + * + * For each subxact we store offset of it's first change in the main file. + * The file is always over-written as a whole. + * + * XXX We should only store subxacts that were not aborted yet. + */ +static void +subxact_info_write(Oid subid, TransactionId xid) +{ + char path[MAXPGPATH]; + bool found; + Size len; + StreamXidHash *ent; + BufFile *fd; + + Assert(TransactionIdIsValid(xid)); + + /* find the xid entry in the xidhash */ + ent = (StreamXidHash *) hash_search(xidhash, + (void *) &xid, + HASH_FIND, + &found); + /* we must found the entry for its top transaction by this time */ + Assert(found); + + /* + * If there is no subtransaction then nothing to do, but if already have + * subxact file then delete that. + */ + if (subxact_data.nsubxacts == 0) + { + if (ent->subxact_fileset) + { + cleanup_subxact_info(); + SharedFileSetDeleteAll(ent->subxact_fileset); + pfree(ent->subxact_fileset); + ent->subxact_fileset = NULL; + } + return; + } + + subxact_filename(path, subid, xid); + + /* + * Create the subxact file if it not already created, otherwise open the + * existing file. + */ + if (ent->subxact_fileset == NULL) + { + MemoryContext oldctx; + workfile_set *work_set; + + /* + * We need to maintain shared fileset across multiple stream + * start/stop calls. So, need to allocate it in a persistent context. + */ + oldctx = MemoryContextSwitchTo(ApplyContext); + ent->subxact_fileset = palloc(sizeof(SharedFileSet)); + SharedFileSetInit(ent->subxact_fileset, NULL); + MemoryContextSwitchTo(oldctx); + + work_set = workfile_mgr_create_set("SubxactInfo", path, false /* hold pin */); + fd = BufFileCreateShared(ent->subxact_fileset, path, work_set); + } + else + fd = BufFileOpenShared(ent->subxact_fileset, path, O_RDWR); + + len = sizeof(SubXactInfo) * subxact_data.nsubxacts; + + /* Write the subxact count and subxact info */ + BufFileWrite(fd, &subxact_data.nsubxacts, sizeof(subxact_data.nsubxacts)); + BufFileWrite(fd, subxact_data.subxacts, len); + + BufFileClose(fd); + + /* free the memory allocated for subxact info */ + cleanup_subxact_info(); +} + +/* + * subxact_info_read + * Restore information about subxacts of a streamed transaction. + * + * Read information about subxacts into the structure subxact_data that can be + * used later. + */ +static void +subxact_info_read(Oid subid, TransactionId xid) +{ + char path[MAXPGPATH]; + bool found; + Size len; + BufFile *fd; + StreamXidHash *ent; + MemoryContext oldctx; + + Assert(TransactionIdIsValid(xid)); + Assert(!subxact_data.subxacts); + Assert(subxact_data.nsubxacts == 0); + Assert(subxact_data.nsubxacts_max == 0); + + /* Find the stream xid entry in the xidhash */ + ent = (StreamXidHash *) hash_search(xidhash, + (void *) &xid, + HASH_FIND, + &found); + + /* + * If subxact_fileset is not valid that mean we don't have any subxact + * info + */ + if (ent->subxact_fileset == NULL) + return; + + subxact_filename(path, subid, xid); + + fd = BufFileOpenShared(ent->subxact_fileset, path, O_RDONLY); + + /* read number of subxact items */ + if (BufFileRead(fd, &subxact_data.nsubxacts, + sizeof(subxact_data.nsubxacts)) != + sizeof(subxact_data.nsubxacts)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from streaming transaction's subxact file \"%s\": %m", + path))); + + len = sizeof(SubXactInfo) * subxact_data.nsubxacts; + + /* we keep the maximum as a power of 2 */ + subxact_data.nsubxacts_max = 1 << my_log2(subxact_data.nsubxacts); + + /* + * Allocate subxact information in the logical streaming context. We need + * this information during the complete stream so that we can add the sub + * transaction info to this. On stream stop we will flush this information + * to the subxact file and reset the logical streaming context. + */ + oldctx = MemoryContextSwitchTo(LogicalStreamingContext); + subxact_data.subxacts = palloc(subxact_data.nsubxacts_max * + sizeof(SubXactInfo)); + MemoryContextSwitchTo(oldctx); + + if ((len > 0) && ((BufFileRead(fd, subxact_data.subxacts, len)) != len)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from streaming transaction's subxact file \"%s\": %m", + path))); + + BufFileClose(fd); +} + +/* + * subxact_info_add + * Add information about a subxact (offset in the main file). + */ +static void +subxact_info_add(TransactionId xid) +{ + SubXactInfo *subxacts = subxact_data.subxacts; + int64 i; + + /* We must have a valid top level stream xid and a stream fd. */ + Assert(TransactionIdIsValid(stream_xid)); + Assert(stream_fd != NULL); + + /* + * If the XID matches the toplevel transaction, we don't want to add it. + */ + if (stream_xid == xid) + return; + + /* + * In most cases we're checking the same subxact as we've already seen in + * the last call, so make sure to ignore it (this change comes later). + */ + if (subxact_data.subxact_last == xid) + return; + + /* OK, remember we're processing this XID. */ + subxact_data.subxact_last = xid; + + /* + * Check if the transaction is already present in the array of subxact. We + * intentionally scan the array from the tail, because we're likely adding + * a change for the most recent subtransactions. + * + * XXX Can we rely on the subxact XIDs arriving in sorted order? That + * would allow us to use binary search here. + */ + for (i = subxact_data.nsubxacts; i > 0; i--) + { + /* found, so we're done */ + if (subxacts[i - 1].xid == xid) + return; + } + + /* This is a new subxact, so we need to add it to the array. */ + if (subxact_data.nsubxacts == 0) + { + MemoryContext oldctx; + + subxact_data.nsubxacts_max = 128; + + /* + * Allocate this memory for subxacts in per-stream context, see + * subxact_info_read. + */ + oldctx = MemoryContextSwitchTo(LogicalStreamingContext); + subxacts = palloc(subxact_data.nsubxacts_max * sizeof(SubXactInfo)); + MemoryContextSwitchTo(oldctx); + } + else if (subxact_data.nsubxacts == subxact_data.nsubxacts_max) + { + subxact_data.nsubxacts_max *= 2; + subxacts = repalloc(subxacts, + subxact_data.nsubxacts_max * sizeof(SubXactInfo)); + } + + subxacts[subxact_data.nsubxacts].xid = xid; + + /* + * Get the current offset of the stream file and store it as offset of + * this subxact. + */ + BufFileTell(stream_fd, + &subxacts[subxact_data.nsubxacts].fileno, + &subxacts[subxact_data.nsubxacts].offset); + + subxact_data.nsubxacts++; + subxact_data.subxacts = subxacts; +} + +/* format filename for file containing the info about subxacts */ +static inline void +subxact_filename(char *path, Oid subid, TransactionId xid) +{ + snprintf(path, MAXPGPATH, "%u-%u.subxacts", subid, xid); +} + +/* format filename for file containing serialized changes */ +static inline void +changes_filename(char *path, Oid subid, TransactionId xid) +{ + snprintf(path, MAXPGPATH, "%u-%u.changes", subid, xid); +} + +/* + * stream_cleanup_files + * Cleanup files for a subscription / toplevel transaction. + * + * Remove files with serialized changes and subxact info for a particular + * toplevel transaction. Each subscription has a separate set of files. + */ +static void +stream_cleanup_files(Oid subid, TransactionId xid) +{ + char path[MAXPGPATH]; + StreamXidHash *ent; + + /* Remove the xid entry from the stream xid hash */ + ent = (StreamXidHash *) hash_search(xidhash, + (void *) &xid, + HASH_REMOVE, + NULL); + /* By this time we must have created the transaction entry */ + Assert(ent != NULL); + + /* Delete the change file and release the stream fileset memory */ + changes_filename(path, subid, xid); + SharedFileSetDeleteAll(ent->stream_fileset); + pfree(ent->stream_fileset); + ent->stream_fileset = NULL; + + /* Delete the subxact file and release the memory, if it exist */ + if (ent->subxact_fileset) + { + subxact_filename(path, subid, xid); + SharedFileSetDeleteAll(ent->subxact_fileset); + pfree(ent->subxact_fileset); + ent->subxact_fileset = NULL; + } +} + +/* + * stream_open_file + * Open a file that we'll use to serialize changes for a toplevel + * transaction. + * + * Open a file for streamed changes from a toplevel transaction identified + * by stream_xid (global variable). If it's the first chunk of streamed + * changes for this transaction, initialize the shared fileset and create the + * buffile, otherwise open the previously created file. + * + * This can only be called at the beginning of a "streaming" block, i.e. + * between stream_start/stream_stop messages from the upstream. + */ +static void +stream_open_file(Oid subid, TransactionId xid, bool first_segment) +{ + char path[MAXPGPATH]; + bool found; + MemoryContext oldcxt; + StreamXidHash *ent; + + Assert(in_streamed_transaction); + Assert(OidIsValid(subid)); + Assert(TransactionIdIsValid(xid)); + Assert(stream_fd == NULL); + + /* create or find the xid entry in the xidhash */ + ent = (StreamXidHash *) hash_search(xidhash, + (void *) &xid, + HASH_ENTER | HASH_FIND, + &found); + Assert(first_segment || found); + changes_filename(path, subid, xid); + elog(DEBUG1, "opening file \"%s\" for streamed changes", path); + + /* + * Create/open the buffiles under the logical streaming context so that we + * have those files until stream stop. + */ + oldcxt = MemoryContextSwitchTo(LogicalStreamingContext); + + /* + * If this is the first streamed segment, the file must not exist, so make + * sure we're the ones creating it. Otherwise just open the file for + * writing, in append mode. + */ + if (first_segment) + { + MemoryContext savectx; + SharedFileSet *fileset; + workfile_set *work_set; + + /* + * We need to maintain shared fileset across multiple stream + * start/stop calls. So, need to allocate it in a persistent context. + */ + savectx = MemoryContextSwitchTo(ApplyContext); + fileset = palloc(sizeof(SharedFileSet)); + + SharedFileSetInit(fileset, NULL); + MemoryContextSwitchTo(savectx); + + work_set = workfile_mgr_create_set("ChangeInfo", path, false /* hold pin */); + stream_fd = BufFileCreateShared(fileset, path, work_set); + + /* Remember the fileset for the next stream of the same transaction */ + ent->xid = xid; + ent->stream_fileset = fileset; + ent->subxact_fileset = NULL; + } + else + { + /* + * Open the file and seek to the end of the file because we always + * append the changes file. + */ + stream_fd = BufFileOpenShared(ent->stream_fileset, path, O_RDWR); + BufFileSeek(stream_fd, 0, 0, SEEK_END); + } + + MemoryContextSwitchTo(oldcxt); +} + +/* + * stream_close_file + * Close the currently open file with streamed changes. + * + * This can only be called at the end of a streaming block, i.e. at stream_stop + * message from the upstream. + */ +static void +stream_close_file(void) +{ + Assert(in_streamed_transaction); + Assert(TransactionIdIsValid(stream_xid)); + Assert(stream_fd != NULL); + + BufFileClose(stream_fd); + + stream_xid = InvalidTransactionId; + stream_fd = NULL; +} + +/* + * stream_write_change + * Serialize a change to a file for the current toplevel transaction. + * + * The change is serialized in a simple format, with length (not including + * the length), action code (identifying the message type) and message + * contents (without the subxact TransactionId value). + */ +static void +stream_write_change(char action, StringInfo s) +{ + int len; + + Assert(in_streamed_transaction); + Assert(TransactionIdIsValid(stream_xid)); + Assert(stream_fd != NULL); + + /* total on-disk size, including the action type character */ + len = (s->len - s->cursor) + sizeof(char); + + /* first write the size */ + BufFileWrite(stream_fd, &len, sizeof(len)); + + /* then the action */ + BufFileWrite(stream_fd, &action, sizeof(action)); + + /* and finally the remaining part of the buffer (after the XID) */ + len = (s->len - s->cursor); + + BufFileWrite(stream_fd, &s->data[s->cursor], len); +} + +/* + * Cleanup the memory for subxacts and reset the related variables. + */ +static inline void +cleanup_subxact_info() +{ + if (subxact_data.subxacts) + pfree(subxact_data.subxacts); + + subxact_data.subxacts = NULL; + subxact_data.subxact_last = InvalidTransactionId; + subxact_data.nsubxacts = 0; + subxact_data.nsubxacts_max = 0; +} + /* Logical Replication Apply worker entry point */ void ApplyWorkerMain(Datum main_arg) @@ -2093,10 +3029,8 @@ ApplyWorkerMain(Datum main_arg) /* This is table synchronization worker, call initial sync. */ syncslotname = LogicalRepSyncTableStart(&origin_startpos); - /* The slot name needs to be allocated in permanent memory context. */ - oldctx = MemoryContextSwitchTo(ApplyContext); - myslotname = pstrdup(syncslotname); - MemoryContextSwitchTo(oldctx); + /* allocate slot name in long-lived context */ + myslotname = MemoryContextStrdup(ApplyContext, syncslotname); pfree(syncslotname); } @@ -2140,7 +3074,6 @@ ApplyWorkerMain(Datum main_arg) * does some initializations on the upstream so let's still call it. */ (void) walrcv_identify_system(wrconn, &startpointTLI); - } /* @@ -2155,9 +3088,12 @@ ApplyWorkerMain(Datum main_arg) options.logical = true; options.startpoint = origin_startpos; options.slotname = myslotname; - options.proto.logical.proto_version = LOGICALREP_PROTO_VERSION_NUM; + options.proto.logical.proto_version = + walrcv_server_version(wrconn) >= 140000 ? + LOGICALREP_PROTO_STREAM_VERSION_NUM : LOGICALREP_PROTO_VERSION_NUM; options.proto.logical.publication_names = MySubscription->publications; options.proto.logical.binary = MySubscription->binary; + options.proto.logical.streaming = MySubscription->stream; /* Start normal logical streaming replication. */ walrcv_startstreaming(wrconn, &options); diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index 81ef7dc4c1a3..9c997aed8367 100644 --- a/src/backend/replication/pgoutput/pgoutput.c +++ b/src/backend/replication/pgoutput/pgoutput.c @@ -47,17 +47,40 @@ static void pgoutput_truncate(LogicalDecodingContext *ctx, ReorderBufferChange *change); static bool pgoutput_origin_filter(LogicalDecodingContext *ctx, RepOriginId origin_id); +static void pgoutput_stream_start(struct LogicalDecodingContext *ctx, + ReorderBufferTXN *txn); +static void pgoutput_stream_stop(struct LogicalDecodingContext *ctx, + ReorderBufferTXN *txn); +static void pgoutput_stream_abort(struct LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr abort_lsn); +static void pgoutput_stream_commit(struct LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr commit_lsn); static bool publications_valid; +static bool in_streaming; static List *LoadPublications(List *pubnames); static void publication_invalidation_cb(Datum arg, int cacheid, uint32 hashvalue); -static void send_relation_and_attrs(Relation relation, LogicalDecodingContext *ctx); +static void send_relation_and_attrs(Relation relation, TransactionId xid, + LogicalDecodingContext *ctx); /* * Entry in the map used to remember which relation schemas we sent. * + * The schema_sent flag determines if the current schema record was already + * sent to the subscriber (in which case we don't need to send it again). + * + * The schema cache on downstream is however updated only at commit time, + * and with streamed transactions the commit order may be different from + * the order the transactions are sent in. Also, the (sub) transactions + * might get aborted so we need to send the schema for each (sub) transaction + * so that we don't lose the schema information on abort. For handling this, + * we maintain the list of xids (streamed_txns) for those we have already sent + * the schema. + * * For partitions, 'pubactions' considers not only the table's own * publications, but also those of all of its ancestors. */ @@ -70,6 +93,8 @@ typedef struct RelationSyncEntry * have been sent for this to be true. */ bool schema_sent; + List *streamed_txns; /* streamed toplevel transactions with this + * schema */ bool replicate_valid; PublicationActions pubactions; @@ -95,10 +120,15 @@ typedef struct RelationSyncEntry static HTAB *RelationSyncCache = NULL; static void init_rel_sync_cache(MemoryContext decoding_context); +static void cleanup_rel_sync_cache(TransactionId xid, bool is_commit); static RelationSyncEntry *get_rel_sync_entry(PGOutputData *data, Oid relid); static void rel_sync_cache_relation_cb(Datum arg, Oid relid); static void rel_sync_cache_publication_cb(Datum arg, int cacheid, uint32 hashvalue); +static void set_schema_sent_in_streamed_txn(RelationSyncEntry *entry, + TransactionId xid); +static bool get_schema_sent_in_streamed_txn(RelationSyncEntry *entry, + TransactionId xid); /* * Specify output plugin callbacks @@ -115,16 +145,26 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb) cb->commit_cb = pgoutput_commit_txn; cb->filter_by_origin_cb = pgoutput_origin_filter; cb->shutdown_cb = pgoutput_shutdown; + + /* transaction streaming */ + cb->stream_start_cb = pgoutput_stream_start; + cb->stream_stop_cb = pgoutput_stream_stop; + cb->stream_abort_cb = pgoutput_stream_abort; + cb->stream_commit_cb = pgoutput_stream_commit; + cb->stream_change_cb = pgoutput_change; + cb->stream_truncate_cb = pgoutput_truncate; } static void parse_output_parameters(List *options, uint32 *protocol_version, - List **publication_names, bool *binary) + List **publication_names, bool *binary, + bool *enable_streaming) { ListCell *lc; bool protocol_version_given = false; bool publication_names_given = false; bool binary_option_given = false; + bool streaming_given = false; *binary = false; @@ -182,6 +222,16 @@ parse_output_parameters(List *options, uint32 *protocol_version, *binary = defGetBoolean(defel); } + else if (strcmp(defel->defname, "streaming") == 0) + { + if (streaming_given) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + streaming_given = true; + + *enable_streaming = defGetBoolean(defel); + } else elog(ERROR, "unrecognized pgoutput option: %s", defel->defname); } @@ -194,6 +244,7 @@ static void pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, bool is_init) { + bool enable_streaming = false; PGOutputData *data = palloc0(sizeof(PGOutputData)); /* Create our memory context for private allocations. */ @@ -217,14 +268,15 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, parse_output_parameters(ctx->output_plugin_options, &data->protocol_version, &data->publication_names, - &data->binary); + &data->binary, + &enable_streaming); /* Check if we support requested protocol */ - if (data->protocol_version > LOGICALREP_PROTO_VERSION_NUM) + if (data->protocol_version > LOGICALREP_PROTO_MAX_VERSION_NUM) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("client sent proto_version=%d but we only support protocol %d or lower", - data->protocol_version, LOGICALREP_PROTO_VERSION_NUM))); + data->protocol_version, LOGICALREP_PROTO_MAX_VERSION_NUM))); if (data->protocol_version < LOGICALREP_PROTO_MIN_VERSION_NUM) ereport(ERROR, @@ -237,6 +289,27 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("publication_names parameter missing"))); + /* + * Decide whether to enable streaming. It is disabled by default, in + * which case we just update the flag in decoding context. Otherwise + * we only allow it with sufficient version of the protocol, and when + * the output plugin supports it. + */ + if (!enable_streaming) + ctx->streaming = false; + else if (data->protocol_version < LOGICALREP_PROTO_STREAM_VERSION_NUM) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("requested proto_version=%d does not support streaming, need %d or higher", + data->protocol_version, LOGICALREP_PROTO_STREAM_VERSION_NUM))); + else if (!ctx->streaming) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("streaming requested, but not supported by output plugin"))); + + /* Also remember we're currently not streaming any transaction. */ + in_streaming = false; + /* Init publication state. */ data->publications = NIL; publications_valid = false; @@ -247,6 +320,11 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, /* Initialize relation schema cache. */ init_rel_sync_cache(CacheMemoryContext); } + else + { + /* Disable the streaming during the slot initialization mode. */ + ctx->streaming = false; + } } /* @@ -305,9 +383,47 @@ pgoutput_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, */ static void maybe_send_schema(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, ReorderBufferChange *change, Relation relation, RelationSyncEntry *relentry) { - if (relentry->schema_sent) + bool schema_sent; + TransactionId xid = InvalidTransactionId; + TransactionId topxid = InvalidTransactionId; + + /* + * Remember XID of the (sub)transaction for the change. We don't care if + * it's top-level transaction or not (we have already sent that XID in + * start of the current streaming block). + * + * If we're not in a streaming block, just use InvalidTransactionId and + * the write methods will not include it. + */ + if (in_streaming) + xid = change->txn->xid; + + if (change->txn->toptxn) + topxid = change->txn->toptxn->xid; + else + topxid = xid; + + /* + * Do we need to send the schema? We do track streamed transactions + * separately, because those may be applied later (and the regular + * transactions won't see their effects until then) and in an order that + * we don't know at this point. + * + * XXX There is a scope of optimization here. Currently, we always send + * the schema first time in a streaming transaction but we can probably + * avoid that by checking 'relentry->schema_sent' flag. However, before + * doing that we need to study its impact on the case where we have a mix + * of streaming and non-streaming transactions. + */ + if (in_streaming) + schema_sent = get_schema_sent_in_streamed_txn(relentry, topxid); + else + schema_sent = relentry->schema_sent; + + if (schema_sent) return; /* If needed, send the ancestor's schema first. */ @@ -323,19 +439,24 @@ maybe_send_schema(LogicalDecodingContext *ctx, relentry->map = convert_tuples_by_name(CreateTupleDescCopy(indesc), CreateTupleDescCopy(outdesc)); MemoryContextSwitchTo(oldctx); - send_relation_and_attrs(ancestor, ctx); + send_relation_and_attrs(ancestor, xid, ctx); RelationClose(ancestor); } - send_relation_and_attrs(relation, ctx); - relentry->schema_sent = true; + send_relation_and_attrs(relation, xid, ctx); + + if (in_streaming) + set_schema_sent_in_streamed_txn(relentry, topxid); + else + relentry->schema_sent = true; } /* * Sends a relation */ static void -send_relation_and_attrs(Relation relation, LogicalDecodingContext *ctx) +send_relation_and_attrs(Relation relation, TransactionId xid, + LogicalDecodingContext *ctx) { TupleDesc desc = RelationGetDescr(relation); int i; @@ -359,17 +480,19 @@ send_relation_and_attrs(Relation relation, LogicalDecodingContext *ctx) continue; OutputPluginPrepareWrite(ctx, false); - logicalrep_write_typ(ctx->out, att->atttypid); + logicalrep_write_typ(ctx->out, xid, att->atttypid); OutputPluginWrite(ctx, false); } OutputPluginPrepareWrite(ctx, false); - logicalrep_write_rel(ctx->out, relation); + logicalrep_write_rel(ctx->out, xid, relation); OutputPluginWrite(ctx, false); } /* * Sends the decoded DML over wire. + * + * This is called both in streaming and non-streaming modes. */ static void pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, @@ -378,10 +501,20 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, PGOutputData *data = (PGOutputData *) ctx->output_plugin_private; MemoryContext old; RelationSyncEntry *relentry; + TransactionId xid = InvalidTransactionId; if (!is_publishable_relation(relation)) return; + /* + * Remember the xid for the change in streaming mode. We need to send xid + * with each change in the streaming mode so that subscriber can make + * their association and on aborts, it can discard the corresponding + * changes. + */ + if (in_streaming) + xid = change->txn->xid; + relentry = get_rel_sync_entry(data, RelationGetRelid(relation)); /* First check the table filter */ @@ -406,7 +539,7 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, /* Avoid leaking memory by using and resetting our own context */ old = MemoryContextSwitchTo(data->context); - maybe_send_schema(ctx, relation, relentry); + maybe_send_schema(ctx, txn, change, relation, relentry); /* Send the data */ switch (change->action) @@ -426,7 +559,7 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, } OutputPluginPrepareWrite(ctx, true); - logicalrep_write_insert(ctx->out, relation, tuple, + logicalrep_write_insert(ctx->out, xid, relation, tuple, data->binary); OutputPluginWrite(ctx, true); break; @@ -451,8 +584,8 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, } OutputPluginPrepareWrite(ctx, true); - logicalrep_write_update(ctx->out, relation, oldtuple, newtuple, - data->binary); + logicalrep_write_update(ctx->out, xid, relation, oldtuple, + newtuple, data->binary); OutputPluginWrite(ctx, true); break; } @@ -472,7 +605,7 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, } OutputPluginPrepareWrite(ctx, true); - logicalrep_write_delete(ctx->out, relation, oldtuple, + logicalrep_write_delete(ctx->out, xid, relation, oldtuple, data->binary); OutputPluginWrite(ctx, true); } @@ -498,6 +631,11 @@ pgoutput_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, int i; int nrelids; Oid *relids; + TransactionId xid = InvalidTransactionId; + + /* Remember the xid for the change in streaming mode. See pgoutput_change. */ + if (in_streaming) + xid = change->txn->xid; old = MemoryContextSwitchTo(data->context); @@ -526,13 +664,14 @@ pgoutput_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, continue; relids[nrelids++] = relid; - maybe_send_schema(ctx, relation, relentry); + maybe_send_schema(ctx, txn, change, relation, relentry); } if (nrelids > 0) { OutputPluginPrepareWrite(ctx, true); logicalrep_write_truncate(ctx->out, + xid, nrelids, relids, change->data.truncate.cascade, @@ -605,6 +744,118 @@ publication_invalidation_cb(Datum arg, int cacheid, uint32 hashvalue) rel_sync_cache_publication_cb(arg, cacheid, hashvalue); } +/* + * START STREAM callback + */ +static void +pgoutput_stream_start(struct LogicalDecodingContext *ctx, + ReorderBufferTXN *txn) +{ + bool send_replication_origin = txn->origin_id != InvalidRepOriginId; + + /* we can't nest streaming of transactions */ + Assert(!in_streaming); + + /* + * If we already sent the first stream for this transaction then don't + * send the origin id in the subsequent streams. + */ + if (rbtxn_is_streamed(txn)) + send_replication_origin = false; + + OutputPluginPrepareWrite(ctx, !send_replication_origin); + logicalrep_write_stream_start(ctx->out, txn->xid, !rbtxn_is_streamed(txn)); + + if (send_replication_origin) + { + char *origin; + + /* Message boundary */ + OutputPluginWrite(ctx, false); + OutputPluginPrepareWrite(ctx, true); + + if (replorigin_by_oid(txn->origin_id, true, &origin)) + logicalrep_write_origin(ctx->out, origin, InvalidXLogRecPtr); + } + + OutputPluginWrite(ctx, true); + + /* we're streaming a chunk of transaction now */ + in_streaming = true; +} + +/* + * STOP STREAM callback + */ +static void +pgoutput_stream_stop(struct LogicalDecodingContext *ctx, + ReorderBufferTXN *txn) +{ + /* we should be streaming a trasanction */ + Assert(in_streaming); + + OutputPluginPrepareWrite(ctx, true); + logicalrep_write_stream_stop(ctx->out); + OutputPluginWrite(ctx, true); + + /* we've stopped streaming a transaction */ + in_streaming = false; +} + +/* + * Notify downstream to discard the streamed transaction (along with all + * it's subtransactions, if it's a toplevel transaction). + */ +static void +pgoutput_stream_abort(struct LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr abort_lsn) +{ + ReorderBufferTXN *toptxn; + + /* + * The abort should happen outside streaming block, even for streamed + * transactions. The transaction has to be marked as streamed, though. + */ + Assert(!in_streaming); + + /* determine the toplevel transaction */ + toptxn = (txn->toptxn) ? txn->toptxn : txn; + + Assert(rbtxn_is_streamed(toptxn)); + + OutputPluginPrepareWrite(ctx, true); + logicalrep_write_stream_abort(ctx->out, toptxn->xid, txn->xid); + OutputPluginWrite(ctx, true); + + cleanup_rel_sync_cache(toptxn->xid, false); +} + +/* + * Notify downstream to apply the streamed transaction (along with all + * it's subtransactions). + */ +static void +pgoutput_stream_commit(struct LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr commit_lsn) +{ + /* + * The commit should happen outside streaming block, even for streamed + * transactions. The transaction has to be marked as streamed, though. + */ + Assert(!in_streaming); + Assert(rbtxn_is_streamed(txn)); + + OutputPluginUpdateProgress(ctx); + + OutputPluginPrepareWrite(ctx, true); + logicalrep_write_stream_commit(ctx->out, txn, commit_lsn); + OutputPluginWrite(ctx, true); + + cleanup_rel_sync_cache(txn->xid, true); +} + /* * Initialize the relation schema sync cache for a decoding session. * @@ -641,6 +892,39 @@ init_rel_sync_cache(MemoryContext cachectx) (Datum) 0); } +/* + * We expect relatively small number of streamed transactions. + */ +static bool +get_schema_sent_in_streamed_txn(RelationSyncEntry *entry, TransactionId xid) +{ + ListCell *lc; + + foreach(lc, entry->streamed_txns) + { + if (xid == (uint32) lfirst_int(lc)) + return true; + } + + return false; +} + +/* + * Add the xid in the rel sync entry for which we have already sent the schema + * of the relation. + */ +static void +set_schema_sent_in_streamed_txn(RelationSyncEntry *entry, TransactionId xid) +{ + MemoryContext oldctx; + + oldctx = MemoryContextSwitchTo(CacheMemoryContext); + + entry->streamed_txns = lappend_int(entry->streamed_txns, xid); + + MemoryContextSwitchTo(oldctx); +} + /* * Find or create entry in the relation schema cache. * @@ -661,16 +945,26 @@ get_rel_sync_entry(PGOutputData *data, Oid relid) Assert(RelationSyncCache != NULL); - /* Find cached function info, creating if not found */ - oldctx = MemoryContextSwitchTo(CacheMemoryContext); + /* Find cached relation info, creating if not found */ entry = (RelationSyncEntry *) hash_search(RelationSyncCache, (void *) &relid, HASH_ENTER, &found); - MemoryContextSwitchTo(oldctx); Assert(entry != NULL); /* Not found means schema wasn't sent */ - if (!found || !entry->replicate_valid) + if (!found) + { + /* immediately make a new entry valid enough to satisfy callbacks */ + entry->schema_sent = false; + entry->streamed_txns = NIL; + entry->replicate_valid = false; + entry->pubactions.pubinsert = entry->pubactions.pubupdate = + entry->pubactions.pubdelete = entry->pubactions.pubtruncate = false; + entry->publish_as_relid = InvalidOid; + } + + /* Validate the entry */ + if (!entry->replicate_valid) { List *pubids = GetRelationPublications(relid); ListCell *lc; @@ -693,9 +987,6 @@ get_rel_sync_entry(PGOutputData *data, Oid relid) * relcache considers all publications given relation is in, but here * we only need to consider ones that the subscriber requested. */ - entry->pubactions.pubinsert = entry->pubactions.pubupdate = - entry->pubactions.pubdelete = entry->pubactions.pubtruncate = false; - foreach(lc, data->publications) { Publication *pub = lfirst(lc); @@ -770,12 +1061,53 @@ get_rel_sync_entry(PGOutputData *data, Oid relid) entry->replicate_valid = true; } - if (!found) - entry->schema_sent = false; - return entry; } +/* + * Cleanup list of streamed transactions and update the schema_sent flag. + * + * When a streamed transaction commits or aborts, we need to remove the + * toplevel XID from the schema cache. If the transaction aborted, the + * subscriber will simply throw away the schema records we streamed, so + * we don't need to do anything else. + * + * If the transaction is committed, the subscriber will update the relation + * cache - so tweak the schema_sent flag accordingly. + */ +static void +cleanup_rel_sync_cache(TransactionId xid, bool is_commit) +{ + HASH_SEQ_STATUS hash_seq; + RelationSyncEntry *entry; + ListCell *lc; + + Assert(RelationSyncCache != NULL); + + hash_seq_init(&hash_seq, RelationSyncCache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + /* + * We can set the schema_sent flag for an entry that has committed xid + * in the list as that ensures that the subscriber would have the + * corresponding schema and we don't need to send it unless there is + * any invalidation for that relation. + */ + foreach(lc, entry->streamed_txns) + { + if (xid == (uint32) lfirst_int(lc)) + { + if (is_commit) + entry->schema_sent = true; + + entry->streamed_txns = + foreach_delete_current(entry->streamed_txns, lc); + break; + } + } + } +} + /* * Relcache invalidation callback */ @@ -811,7 +1143,11 @@ rel_sync_cache_relation_cb(Datum arg, Oid relid) * Reset schema sent status as the relation definition may have changed. */ if (entry != NULL) + { entry->schema_sent = false; + list_free(entry->streamed_txns); + entry->streamed_txns = NIL; + } } /* diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index f7e34a6ff874..efe72d4539a7 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -99,7 +99,6 @@ ReplicationSlot *MyReplicationSlot = NULL; int max_replication_slots = 0; /* the maximum number of replication * slots */ -static ReplicationSlot *SearchNamedReplicationSlot(const char *name); static int ReplicationSlotAcquireInternal(ReplicationSlot *slot, const char *name, SlotAcquireBehavior behavior); static void ReplicationSlotDropAcquired(void); @@ -314,6 +313,15 @@ ReplicationSlotCreate(const char *name, bool db_specific, LWLockRelease(ReplicationSlotControlLock); + /* + * Create statistics entry for the new logical slot. We don't collect any + * stats for physical slots, so no need to create an entry for the same. + * See ReplicationSlotDropPtr for why we need to do this before releasing + * ReplicationSlotAllocationLock. + */ + if (SlotIsLogical(slot)) + pgstat_report_replslot(NameStr(slot->data.name), 0, 0, 0, 0, 0, 0); + /* * Now that the slot has been marked as in_use and active, it's safe to * let somebody else try to allocate a slot. @@ -331,7 +339,7 @@ ReplicationSlotCreate(const char *name, bool db_specific, * * The caller must hold ReplicationSlotControlLock in shared mode. */ -static ReplicationSlot * +ReplicationSlot * SearchNamedReplicationSlot(const char *name) { int i; @@ -699,6 +707,19 @@ ReplicationSlotDropPtr(ReplicationSlot *slot) ereport(WARNING, (errmsg("could not remove directory \"%s\"", tmppath))); + /* + * Send a message to drop the replication slot to the stats collector. + * Since there is no guarantee of the order of message transfer on a UDP + * connection, it's possible that a message for creating a new slot + * reaches before a message for removing the old slot. We send the drop + * and create messages while holding ReplicationSlotAllocationLock to + * reduce that possibility. If the messages reached in reverse, we would + * lose one statistics update message. But the next update message will + * create the statistics for the replication slot. + */ + if (SlotIsLogical(slot)) + pgstat_report_replslot_drop(NameStr(slot->data.name)); + /* * We release this at the very end, so that nobody starts trying to create * a slot while we're still cleaning up the detritus of the old one. diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index db32fa9f3be3..b442fffb90b6 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -527,9 +527,6 @@ pg_logical_replication_slot_advance(XLogRecPtr moveto) */ XLogBeginRead(ctx->reader, MyReplicationSlot->data.restart_lsn); - /* Initialize our return value in case we don't do anything */ - retlsn = MyReplicationSlot->data.confirmed_flush; - /* invalidate non-timetravel entries */ InvalidateSystemCaches(); diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c index d05851fa8789..b0a0007f2279 100644 --- a/src/backend/replication/syncrep.c +++ b/src/backend/replication/syncrep.c @@ -160,6 +160,28 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit) const char *old_status; int mode; + /* + * Fast exit if user has not requested sync replication, or there are no + * sync replication standby names defined. + * + * Since this routine gets called every commit time, it's important to + * exit quickly if sync replication is not requested. So we check + * WalSndCtl->sync_standbys_defined flag without the lock and exit + * immediately if it's false. If it's true, we need to check it again later + * while holding the lock, to check the flag and operate the sync rep + * queue atomically. This is necessary to avoid the race condition + * described in SyncRepUpdateSyncStandbysDefined(). On the other + * hand, if it's false, the lock is not necessary because we don't touch + * the queue. + * + * GGDB: the coordinator should be able to commit even if standby is not + * available. Therefore, at the coordinator, we make a separate check below + * about whether synchronous replication is currently available or not. + */ + if (!IS_QUERY_DISPATCHER() && (!SyncRepRequested() || + !((volatile WalSndCtlData *) WalSndCtl)->sync_standbys_defined)) + return; + /* Cap the level for anything other than commit to remote flush only. */ if (commit) mode = SyncRepWaitMode; @@ -171,12 +193,6 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit) "syncrep wait -- This backend's commit LSN for syncrep is %X/%X.", (uint32) (lsn >> 32), (uint32) lsn); - /* - * Fast exit if user has not requested sync replication. - */ - if (!SyncRepRequested()) - return; - Assert(SHMQueueIsDetached(&(MyProc->syncRepLinks))); Assert(WalSndCtl != NULL); diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c index e1b4494b1e28..9979634abcf3 100644 --- a/src/backend/replication/walreceiver.c +++ b/src/backend/replication/walreceiver.c @@ -276,7 +276,7 @@ WalReceiverMain(void) pqsignal(SIGHUP, WalRcvSigHupHandler); /* set flag to read config file */ pqsignal(SIGINT, SIG_IGN); pqsignal(SIGTERM, WalRcvShutdownHandler); /* request shutdown */ - pqsignal(SIGQUIT, SignalHandlerForCrashExit); + /* SIGQUIT handler was already set up by InitPostmasterChild */ pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); pqsignal(SIGUSR1, procsignal_sigusr1_handler); @@ -285,9 +285,6 @@ WalReceiverMain(void) /* Reset some signals that are accepted by postmaster but not here */ pqsignal(SIGCHLD, SIG_DFL); - /* We allow SIGQUIT (quickdie) at all times */ - sigdelset(&BlockSig, SIGQUIT); - /* Load the libpq-specific functions */ libpqwalreceiver_PG_init(); if (WalReceiverFunctions == NULL) @@ -767,6 +764,15 @@ WalRcvFetchTimeLineHistoryFiles(TimeLineID first, TimeLineID last) */ writeTimeLineHistoryFile(tli, content, len); + /* + * Mark the streamed history file as ready for archiving + * if archive_mode is always. + */ + if (XLogArchiveMode != ARCHIVE_MODE_ALWAYS) + XLogArchiveForceDone(fname); + else + XLogArchiveNotify(fname); + pfree(fname); pfree(content); } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 0821c8575761..c5d87d56f137 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -829,7 +829,7 @@ StartReplication(StartReplicationCmd *cmd) } /* Send CommandComplete message */ - pq_puttextmessage('C', "START_STREAMING"); + EndReplicationCommand("START_STREAMING"); } /* @@ -1160,11 +1160,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) static void DropReplicationSlot(DropReplicationSlotCmd *cmd) { - QueryCompletion qc; - ReplicationSlotDrop(cmd->slotname, !cmd->wait); - SetQueryCompletion(&qc, CMDTAG_DROP_REPLICATION_SLOT, 0); - EndCommand(&qc, DestRemote, false); } /* @@ -1555,9 +1551,9 @@ exec_replication_command(const char *cmd_string) { int parse_rc; Node *cmd_node; + const char *cmdtag; MemoryContext cmd_context; MemoryContext old_context; - QueryCompletion qc; /* * If WAL sender has been told that shutdown is getting close, switch its @@ -1583,6 +1579,9 @@ exec_replication_command(const char *cmd_string) CHECK_FOR_INTERRUPTS(); + /* + * Parse the command. + */ cmd_context = AllocSetContextCreate(CurrentMemoryContext, "Replication command context", ALLOCSET_DEFAULT_SIZES); @@ -1595,31 +1594,47 @@ exec_replication_command(const char *cmd_string) (errcode(ERRCODE_SYNTAX_ERROR), errmsg_internal("replication command parser returned %d", parse_rc))); + replication_scanner_finish(); cmd_node = replication_parse_result; /* - * Log replication command if log_replication_commands is enabled. Even - * when it's disabled, log the command with DEBUG1 level for backward - * compatibility. Note that SQL commands are not logged here, and will be - * logged later if log_statement is enabled. + * If it's a SQL command, just clean up our mess and return false; the + * caller will take care of executing it. */ - if (cmd_node->type != T_SQLCmd) - ereport(log_replication_commands ? LOG : DEBUG1, - (errmsg("received replication command: %s", cmd_string))); + if (IsA(cmd_node, SQLCmd)) + { + if (MyDatabaseId == InvalidOid) + ereport(ERROR, + (errmsg("cannot execute SQL commands in WAL sender for physical replication"))); + + MemoryContextSwitchTo(old_context); + MemoryContextDelete(cmd_context); + + /* Tell the caller that this wasn't a WalSender command. */ + return false; + } /* - * CREATE_REPLICATION_SLOT ... LOGICAL exports a snapshot. If it was - * called outside of transaction the snapshot should be cleared here. + * Report query to various monitoring facilities. For this purpose, we + * report replication commands just like SQL commands. */ - if (!IsTransactionBlock()) - SnapBuildClearExportedSnapshot(); + debug_query_string = cmd_string; + + pgstat_report_activity(STATE_RUNNING, cmd_string); /* - * For aborted transactions, don't allow anything except pure SQL, the - * exec_simple_query() will handle it correctly. + * Log replication command if log_replication_commands is enabled. Even + * when it's disabled, log the command with DEBUG1 level for backward + * compatibility. */ - if (IsAbortedTransactionBlockState() && !IsA(cmd_node, SQLCmd)) + ereport(log_replication_commands ? LOG : DEBUG1, + (errmsg("received replication command: %s", cmd_string))); + + /* + * Disallow replication commands in aborted transaction blocks. + */ + if (IsAbortedTransactionBlockState()) ereport(ERROR, (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION), errmsg("current transaction is aborted, " @@ -1635,46 +1650,63 @@ exec_replication_command(const char *cmd_string) initStringInfo(&reply_message); initStringInfo(&tmpbuf); - /* Report to pgstat that this process is running */ - pgstat_report_activity(STATE_RUNNING, NULL); - switch (cmd_node->type) { case T_IdentifySystemCmd: + cmdtag = "IDENTIFY_SYSTEM"; + set_ps_display(cmdtag); IdentifySystem(); + EndReplicationCommand(cmdtag); break; case T_BaseBackupCmd: - PreventInTransactionBlock(true, "BASE_BACKUP"); + cmdtag = "BASE_BACKUP"; + set_ps_display(cmdtag); + PreventInTransactionBlock(true, cmdtag); SendBaseBackup((BaseBackupCmd *) cmd_node); + EndReplicationCommand(cmdtag); break; case T_CreateReplicationSlotCmd: + cmdtag = "CREATE_REPLICATION_SLOT"; + set_ps_display(cmdtag); CreateReplicationSlot((CreateReplicationSlotCmd *) cmd_node); + EndReplicationCommand(cmdtag); break; case T_DropReplicationSlotCmd: + cmdtag = "DROP_REPLICATION_SLOT"; + set_ps_display(cmdtag); DropReplicationSlot((DropReplicationSlotCmd *) cmd_node); + EndReplicationCommand(cmdtag); break; case T_StartReplicationCmd: { StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node; - PreventInTransactionBlock(true, "START_REPLICATION"); + cmdtag = "START_REPLICATION"; + set_ps_display(cmdtag); + PreventInTransactionBlock(true, cmdtag); if (cmd->kind == REPLICATION_KIND_PHYSICAL) StartReplication(cmd); else StartLogicalReplication(cmd); + /* dupe, but necessary per libpqrcv_endstreaming */ + EndReplicationCommand(cmdtag); + Assert(xlogreader != NULL); break; } case T_TimeLineHistoryCmd: - PreventInTransactionBlock(true, "TIMELINE_HISTORY"); + cmdtag = "TIMELINE_HISTORY"; + set_ps_display(cmdtag); + PreventInTransactionBlock(true, cmdtag); SendTimeLineHistory((TimeLineHistoryCmd *) cmd_node); + EndReplicationCommand(cmdtag); break; case T_VariableShowStmt: @@ -1682,24 +1714,17 @@ exec_replication_command(const char *cmd_string) DestReceiver *dest = CreateDestReceiver(DestRemoteSimple); VariableShowStmt *n = (VariableShowStmt *) cmd_node; + cmdtag = "SHOW"; + set_ps_display(cmdtag); + /* syscache access needs a transaction environment */ StartTransactionCommand(); GetPGVariable(n->name, dest); CommitTransactionCommand(); + EndReplicationCommand(cmdtag); } break; - case T_SQLCmd: - if (MyDatabaseId == InvalidOid) - ereport(ERROR, - (errmsg("cannot execute SQL commands in WAL sender for physical replication"))); - - /* Report to pgstat that this process is now idle */ - pgstat_report_activity(STATE_IDLE, NULL); - - /* Tell the caller that this wasn't a WalSender command. */ - return false; - default: elog(ERROR, "unrecognized replication command node tag: %u", cmd_node->type); @@ -1709,12 +1734,12 @@ exec_replication_command(const char *cmd_string) MemoryContextSwitchTo(old_context); MemoryContextDelete(cmd_context); - /* Send CommandComplete message */ - SetQueryCompletion(&qc, CMDTAG_SELECT, 0); - EndCommand(&qc, DestRemote, true); - - /* Report to pgstat that this process is now idle */ - pgstat_report_activity(STATE_IDLE, NULL); + /* + * We need not update ps display or pg_stat_activity, because PostgresMain + * will reset those to "idle". But we must reset debug_query_string to + * ensure it doesn't become a dangling pointer. + */ + debug_query_string = NULL; return true; } @@ -3187,7 +3212,7 @@ WalSndSignals(void) pqsignal(SIGHUP, SignalHandlerForConfigReload); pqsignal(SIGINT, StatementCancelHandler); /* query cancel */ pqsignal(SIGTERM, die); /* request shutdown */ - pqsignal(SIGQUIT, quickdie); /* hard crash time */ + /* SIGQUIT handler was already set up by InitPostmasterChild */ InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); pqsignal(SIGUSR1, procsignal_sigusr1_handler); diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index fbea979d4320..28db142fe5dd 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -668,7 +668,7 @@ DefineQueryRewrite(const char *rulename, classForm->relam = InvalidOid; classForm->reltablespace = InvalidOid; classForm->relpages = 0; - classForm->reltuples = 0; + classForm->reltuples = -1; classForm->relallvisible = 0; classForm->reltoastrelid = InvalidOid; classForm->relhasindex = false; diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index e0f73f8d67c0..660dedf058a4 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -32,6 +32,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/optimizer.h" #include "parser/analyze.h" #include "parser/parse_coerce.h" #include "parser/parse_relation.h" @@ -706,11 +707,7 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index) if (IsA(rtr, RangeTblRef) && rtr->rtindex == rt_index) { - newjointree = list_delete_ptr(newjointree, rtr); - - /* - * foreach is safe because we exit loop after list_delete... - */ + newjointree = foreach_delete_current(newjointree, l); break; } } @@ -1620,6 +1617,42 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte, } } +/* + * Record in target_rte->extraUpdatedCols the indexes of any generated columns + * that depend on any columns mentioned in target_rte->updatedCols. + */ +void +fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation) +{ + TupleDesc tupdesc = RelationGetDescr(target_relation); + TupleConstr *constr = tupdesc->constr; + + target_rte->extraUpdatedCols = NULL; + + if (constr && constr->has_generated_stored) + { + for (int i = 0; i < constr->num_defval; i++) + { + AttrDefault *defval = &constr->defval[i]; + Node *expr; + Bitmapset *attrs_used = NULL; + + /* skip if not generated column */ + if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated) + continue; + + /* identify columns this generated column depends on */ + expr = stringToNode(defval->adbin); + pull_varattnos(expr, 1, &attrs_used); + + if (bms_overlap(target_rte->updatedCols, attrs_used)) + target_rte->extraUpdatedCols = + bms_add_member(target_rte->extraUpdatedCols, + defval->adnum - FirstLowInvalidHeapAttributeNumber); + } + } +} + /* * matchLocks - @@ -1751,6 +1784,7 @@ ApplyRetrieveRule(Query *parsetree, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * For the most part, Vars referencing the view should remain as @@ -3734,6 +3768,9 @@ RewriteQuery(Query *parsetree, List *rewrite_events) parsetree->override, rt_entry_relation, parsetree->resultRelation); + + /* Also populate extraUpdatedCols (for generated columns) */ + fill_extraUpdatedCols(rt_entry, rt_entry_relation); } else if (event == CMD_DELETE) { diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c index 9cc123dab4b7..f62a3a7c16cb 100644 --- a/src/backend/statistics/dependencies.c +++ b/src/backend/statistics/dependencies.c @@ -1259,7 +1259,7 @@ dependencies_clauselist_selectivity(PlannerInfo *root, * of clauses. We must return 1.0 so the calling function's selectivity is * unaffected. */ - if (bms_num_members(clauses_attnums) < 2) + if (bms_membership(clauses_attnums) != BMS_MULTIPLE) { bms_free(clauses_attnums); pfree(list_attnums); @@ -1286,18 +1286,18 @@ dependencies_clauselist_selectivity(PlannerInfo *root, { StatisticExtInfo *stat = (StatisticExtInfo *) lfirst(l); Bitmapset *matched; - int num_matched; + BMS_Membership membership; /* skip statistics that are not of the correct type */ if (stat->kind != STATS_EXT_DEPENDENCIES) continue; matched = bms_intersect(clauses_attnums, stat->keys); - num_matched = bms_num_members(matched); + membership = bms_membership(matched); bms_free(matched); /* skip objects matching fewer than two attributes from clauses */ - if (num_matched < 2) + if (membership != BMS_MULTIPLE) continue; func_dependencies[nfunc_dependencies] @@ -1318,7 +1318,7 @@ dependencies_clauselist_selectivity(PlannerInfo *root, /* * Work out which dependencies we can apply, starting with the - * widest/stongest ones, and proceeding to smaller/weaker ones. + * widest/strongest ones, and proceeding to smaller/weaker ones. */ dependencies = (MVDependency **) palloc(sizeof(MVDependency *) * total_ndeps); diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c index d332da8c9333..b80bb70265a4 100644 --- a/src/backend/statistics/extended_stats.c +++ b/src/backend/statistics/extended_stats.c @@ -233,7 +233,7 @@ ComputeExtStatisticsRows(Relation onerel, foreach(lc, lstats) { StatExtEntry *stat = (StatExtEntry *) lfirst(lc); - int stattarget = stat->stattarget; + int stattarget; VacAttrStats **stats; int nattrs = bms_num_members(stat->columns); @@ -1416,7 +1416,7 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli stat_sel = mcv_sel + other_sel; CLAMP_PROBABILITY(stat_sel); - /* Factor the estimate from this MCV to the oveall estimate. */ + /* Factor the estimate from this MCV to the overall estimate. */ sel *= stat_sel; } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index ac935eaae8e9..1596ea884a57 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -640,7 +640,7 @@ PrefetchSharedBuffer(SMgrRelation smgr_reln, * could be used by the caller to avoid the need for a later buffer lookup, but * it's not pinned, so the caller must recheck it. * - * 2. If the kernel has been asked to initiate I/O, the initated_io member is + * 2. If the kernel has been asked to initiate I/O, the initiated_io member is * true. Currently there is no way to know if the data was already cached by * the kernel and therefore didn't really initiate I/O, and no way to know when * the I/O completes other than using synchronous ReadBuffer(). @@ -705,7 +705,8 @@ ReadBuffer(Relation reln, BlockNumber blockNum) * * In RBM_NORMAL mode, the page is read from disk, and the page header is * validated. An error is thrown if the page header is not valid. (But - * note that an all-zero page is considered "valid"; see PageIsVerified().) + * note that an all-zero page is considered "valid"; see + * PageIsVerifiedExtended().) * * RBM_ZERO_ON_ERROR is like the normal mode, but if the page header is not * valid, the page is zeroed instead of throwing an error. This is intended @@ -1011,7 +1012,8 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, } /* check for garbage data */ - if (!PageIsVerified((Page) bufBlock, blockNum)) + if (!PageIsVerifiedExtended((Page) bufBlock, blockNum, + PIV_LOG_WARNING | PIV_REPORT_STAT)) { if (mode == RBM_ZERO_ON_ERROR || zero_damaged_pages) { @@ -2771,14 +2773,7 @@ PrintBufferLeakWarning(Buffer buffer) void CheckPointBuffers(int flags) { - TRACE_POSTGRESQL_BUFFER_CHECKPOINT_START(flags); - CheckpointStats.ckpt_write_t = GetCurrentTimestamp(); BufferSync(flags); - CheckpointStats.ckpt_sync_t = GetCurrentTimestamp(); - TRACE_POSTGRESQL_BUFFER_CHECKPOINT_SYNC_START(); - ProcessSyncRequests(); - CheckpointStats.ckpt_sync_end_t = GetCurrentTimestamp(); - TRACE_POSTGRESQL_BUFFER_CHECKPOINT_DONE(); } diff --git a/src/backend/storage/file/buffile.c b/src/backend/storage/file/buffile.c index 404297b8d29f..c8f09c15daaa 100644 --- a/src/backend/storage/file/buffile.c +++ b/src/backend/storage/file/buffile.c @@ -32,10 +32,14 @@ * (by opening multiple fd.c temporary files). This is an essential feature * for sorts and hashjoins on large amounts of data. * - * BufFile supports temporary files that can be made read-only and shared with - * other backends, as infrastructure for parallel execution. Such files need - * to be created as a member of a SharedFileSet that all participants are - * attached to. + * BufFile supports temporary files that can be shared with other backends, as + * infrastructure for parallel execution. Such files need to be created as a + * member of a SharedFileSet that all participants are attached to. + * + * BufFile also supports temporary files that can be used by the single backend + * when the corresponding files need to be survived across the transaction and + * need to be opened and closed multiple times. Such files need to be created + * as a member of a SharedFileSet. *------------------------------------------------------------------------- */ @@ -390,7 +394,7 @@ BufFileCreateShared(SharedFileSet *fileset, const char *name, workfile_set *work * backends and render it read-only. */ BufFile * -BufFileOpenShared(SharedFileSet *fileset, const char *name) +BufFileOpenShared(SharedFileSet *fileset, const char *name, int mode) { BufFile *file; char segment_name[MAXPGPATH]; @@ -414,7 +418,7 @@ BufFileOpenShared(SharedFileSet *fileset, const char *name) } /* Try to load a segment. */ SharedSegmentName(segment_name, name, nfiles); - files[nfiles] = SharedFileSetOpen(fileset, segment_name); + files[nfiles] = SharedFileSetOpen(fileset, segment_name, mode); if (files[nfiles] <= 0) break; ++nfiles; @@ -434,7 +438,7 @@ BufFileOpenShared(SharedFileSet *fileset, const char *name) file = makeBufFileCommon(nfiles); file->files = files; - file->readOnly = true; /* Can't write to files opened this way */ + file->readOnly = (mode == O_RDONLY) ? true : false; file->fileset = fileset; file->name = pstrdup(name); @@ -910,6 +914,7 @@ BufFileSeek(BufFile *file, int fileno, off_t offset, int whence) newOffset = (file->curOffset + file->pos) + offset; break; case SEEK_END: + /* * The file size of the last file gives us the end offset of that * file. @@ -922,9 +927,9 @@ BufFileSeek(BufFile *file, int fileno, off_t offset, int whence) ereport(ERROR, (errcode_for_file_access(), errmsg("could not determine size of temporary file \"%s\" from BufFile \"%s\": %m", - FilePathName(file->files[file->numFiles - 1]), - file->name))); - break; + FilePathName(file->files[file->numFiles - 1]), + file->name))); + break; default: elog(ERROR, "invalid whence: %d", whence); return EOF; @@ -1491,3 +1496,98 @@ BufFileLoadCompressedBuffer(BufFile *file, void *buffer, size_t bufsize) } #endif /* USE_ZSTD */ + +/* + * Truncate a BufFile created by BufFileCreateShared up to the given fileno and + * the offset. + */ +void +BufFileTruncateShared(BufFile *file, int fileno, off_t offset) +{ + int numFiles = file->numFiles; + int newFile = fileno; + off_t newOffset = file->curOffset; + char segment_name[MAXPGPATH]; + int i; + + /* + * Loop over all the files up to the given fileno and remove the files + * that are greater than the fileno and truncate the given file up to the + * offset. Note that we also remove the given fileno if the offset is 0 + * provided it is not the first file in which we truncate it. + */ + for (i = file->numFiles - 1; i >= fileno; i--) + { + if ((i != fileno || offset == 0) && i != 0) + { + SharedSegmentName(segment_name, file->name, i); + FileClose(file->files[i]); + if (!SharedFileSetDelete(file->fileset, segment_name, true)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not delete shared fileset \"%s\": %m", + segment_name))); + numFiles--; + newOffset = MAX_PHYSICAL_FILESIZE; + + /* + * This is required to indicate that we have deleted the given + * fileno. + */ + if (i == fileno) + newFile--; + } + else + { + if (FileTruncate(file->files[i], offset, + WAIT_EVENT_BUFFILE_TRUNCATE) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not truncate file \"%s\": %m", + FilePathName(file->files[i])))); + newOffset = offset; + } + } + + file->numFiles = numFiles; + + /* + * If the truncate point is within existing buffer then we can just adjust + * pos within buffer. + */ + if (newFile == file->curFile && + newOffset >= file->curOffset && + newOffset <= file->curOffset + file->nbytes) + { + /* No need to reset the current pos if the new pos is greater. */ + if (newOffset <= file->curOffset + file->pos) + file->pos = (int) (newOffset - file->curOffset); + + /* Adjust the nbytes for the current buffer. */ + file->nbytes = (int) (newOffset - file->curOffset); + } + else if (newFile == file->curFile && + newOffset < file->curOffset) + { + /* + * The truncate point is within the existing file but prior to the + * current position, so we can forget the current buffer and reset the + * current position. + */ + file->curOffset = newOffset; + file->pos = 0; + file->nbytes = 0; + } + else if (newFile < file->curFile) + { + /* + * The truncate point is prior to the current file, so need to reset + * the current position accordingly. + */ + file->curFile = newFile; + file->curOffset = newOffset; + file->pos = 0; + file->nbytes = 0; + } + /* Nothing to do, if the truncate point is beyond current file. */ +} diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index 77777e6d45b5..3d453c37e3e2 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -92,6 +92,7 @@ #include "catalog/pg_tablespace.h" #include "cdb/cdbvars.h" #include "common/file_perm.h" +#include "common/file_utils.h" #include "miscadmin.h" #include "pgstat.h" #include "portability/mem.h" @@ -1806,18 +1807,17 @@ PathNameCreateTemporaryFile(const char *path, bool error_on_failure) /* * Open a file that was created with PathNameCreateTemporaryFile, possibly in * another backend. Files opened this way don't count against the - * temp_file_limit of the caller, are read-only and are automatically closed - * at the end of the transaction but are not deleted on close. + * temp_file_limit of the caller, are automatically closed at the end of the + * transaction but are not deleted on close. */ File -PathNameOpenTemporaryFile(const char *path) +PathNameOpenTemporaryFile(const char *path, int mode) { File file; ResourceOwnerEnlargeFiles(CurrentResourceOwner); - /* We open the file read-only. */ - file = PathNameOpenFile(path, O_RDONLY | PG_BINARY); + file = PathNameOpenFile(path, mode | PG_BINARY); /* If no such file, then we don't raise an error. */ if (file <= 0 && errno != ENOENT) @@ -3506,8 +3506,6 @@ walkdir(const char *path, while ((de = ReadDirExtended(dir, path, elevel)) != NULL) { char subpath[MAXPGPATH * 2]; - struct stat fst; - int sret; CHECK_FOR_INTERRUPTS(); @@ -3517,23 +3515,23 @@ walkdir(const char *path, snprintf(subpath, sizeof(subpath), "%s/%s", path, de->d_name); - if (process_symlinks) - sret = stat(subpath, &fst); - else - sret = lstat(subpath, &fst); - - if (sret < 0) + switch (get_dirent_type(subpath, de, process_symlinks, elevel)) { - ereport(elevel, - (errcode_for_file_access(), - errmsg("could not stat file \"%s\": %m", subpath))); - continue; - } + case PGFILETYPE_REG: + (*action) (subpath, false, elevel); + break; + case PGFILETYPE_DIR: + walkdir(subpath, action, false, elevel); + break; + default: - if (S_ISREG(fst.st_mode)) - (*action) (subpath, false, elevel); - else if (S_ISDIR(fst.st_mode)) - walkdir(subpath, action, false, elevel); + /* + * Errors are already reported directly by get_dirent_type(), + * and any remaining symlinks and unknown file types are + * ignored. + */ + break; + } } FreeDir(dir); /* we ignore any error here */ diff --git a/src/backend/storage/file/sharedfileset.c b/src/backend/storage/file/sharedfileset.c index 16b7594756c6..859c22e79b62 100644 --- a/src/backend/storage/file/sharedfileset.c +++ b/src/backend/storage/file/sharedfileset.c @@ -13,6 +13,10 @@ * files can be discovered by name, and a shared ownership semantics so that * shared files survive until the last user detaches. * + * SharedFileSets can be used by backends when the temporary files need to be + * opened/closed multiple times and the underlying files need to survive across + * transactions. + * *------------------------------------------------------------------------- */ @@ -25,25 +29,36 @@ #include "common/hashfn.h" #include "miscadmin.h" #include "storage/dsm.h" +#include "storage/ipc.h" #include "storage/sharedfileset.h" #include "utils/builtins.h" +static List *filesetlist = NIL; + static void SharedFileSetOnDetach(dsm_segment *segment, Datum datum); +static void SharedFileSetDeleteOnProcExit(int status, Datum arg); static void SharedFileSetPath(char *path, SharedFileSet *fileset, Oid tablespace); static void SharedFilePath(char *path, SharedFileSet *fileset, const char *name); static Oid ChooseTablespace(const SharedFileSet *fileset, const char *name); /* - * Initialize a space for temporary files that can be opened for read-only - * access by other backends. Other backends must attach to it before - * accessing it. Associate this SharedFileSet with 'seg'. Any contained - * files will be deleted when the last backend detaches. + * Initialize a space for temporary files that can be opened by other backends. + * Other backends must attach to it before accessing it. Associate this + * SharedFileSet with 'seg'. Any contained files will be deleted when the + * last backend detaches. + * + * We can also use this interface if the temporary files are used only by + * single backend but the files need to be opened and closed multiple times + * and also the underlying files need to survive across transactions. For + * such cases, dsm segment 'seg' should be passed as NULL. Callers are + * expected to explicitly remove such files by using SharedFileSetDelete/ + * SharedFileSetDeleteAll or we remove such files on proc exit. * * Files will be distributed over the tablespaces configured in * temp_tablespaces. * * Under the covers the set is one or more directories which will eventually - * be deleted when there are no backends attached. + * be deleted. */ void SharedFileSetInit(SharedFileSet *fileset, dsm_segment *seg) @@ -84,7 +99,25 @@ SharedFileSetInit(SharedFileSet *fileset, dsm_segment *seg) } /* Register our cleanup callback. */ - on_dsm_detach(seg, SharedFileSetOnDetach, PointerGetDatum(fileset)); + if (seg) + on_dsm_detach(seg, SharedFileSetOnDetach, PointerGetDatum(fileset)); + else + { + static bool registered_cleanup = false; + + if (!registered_cleanup) + { + /* + * We must not have registered any fileset before registering the + * fileset clean up. + */ + Assert(filesetlist == NIL); + on_proc_exit(SharedFileSetDeleteOnProcExit, 0); + registered_cleanup = true; + } + + filesetlist = lcons((void *) fileset, filesetlist); + } } /* @@ -147,13 +180,13 @@ SharedFileSetCreate(SharedFileSet *fileset, const char *name) * another backend. */ File -SharedFileSetOpen(SharedFileSet *fileset, const char *name) +SharedFileSetOpen(SharedFileSet *fileset, const char *name, int mode) { char path[MAXPGPATH]; File file; SharedFilePath(path, fileset, name); - file = PathNameOpenTemporaryFile(path); + file = PathNameOpenTemporaryFile(path, mode); return file; } @@ -192,6 +225,9 @@ SharedFileSetDeleteAll(SharedFileSet *fileset) SharedFileSetPath(dirpath, fileset, fileset->tablespaces[i]); PathNameDeleteTemporaryDir(dirpath); } + + /* Unregister the shared fileset */ + SharedFileSetUnregister(fileset); } /* @@ -222,6 +258,62 @@ SharedFileSetOnDetach(dsm_segment *segment, Datum datum) SharedFileSetDeleteAll(fileset); } +/* + * Callback function that will be invoked on the process exit. This will + * process the list of all the registered sharedfilesets and delete the + * underlying files. + */ +static void +SharedFileSetDeleteOnProcExit(int status, Datum arg) +{ + /* + * Remove all the pending shared fileset entries. We don't use foreach() here + * because SharedFileSetDeleteAll will remove the current element in + * filesetlist. Though we have used foreach_delete_current() to remove the + * element from filesetlist it could only fix up the state of one of the + * loops, see SharedFileSetUnregister. + */ + while (list_length(filesetlist) > 0) + { + SharedFileSet *fileset = (SharedFileSet *) linitial(filesetlist); + + SharedFileSetDeleteAll(fileset); + } + + filesetlist = NIL; +} + +/* + * Unregister the shared fileset entry registered for cleanup on proc exit. + */ +void +SharedFileSetUnregister(SharedFileSet *input_fileset) +{ + ListCell *l; + + /* + * If the caller is following the dsm based cleanup then we don't maintain + * the filesetlist so return. + */ + if (filesetlist == NIL) + return; + + foreach(l, filesetlist) + { + SharedFileSet *fileset = (SharedFileSet *) lfirst(l); + + /* Remove the entry from the list */ + if (input_fileset == fileset) + { + filesetlist = foreach_delete_current(filesetlist, l); + return; + } + } + + /* Should have found a match */ + Assert(false); +} + /* * Build the path for the directory holding the files backing a SharedFileSet * in a given tablespace. diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c index 4fcc53b4237f..f26d03748a3c 100644 --- a/src/backend/storage/ipc/ipc.c +++ b/src/backend/storage/ipc/ipc.c @@ -408,9 +408,9 @@ on_shmem_exit(pg_on_exit_callback function, Datum arg) * cancel_before_shmem_exit * * this function removes a previously-registered before_shmem_exit - * callback. For simplicity, only the latest entry can be - * removed. (We could work harder but there is no need for - * current uses.) + * callback. We only look at the latest entry for removal, as we + * expect callers to add and remove temporary before_shmem_exit + * callbacks in strict LIFO order. * ---------------------------------------------------------------- */ void @@ -421,6 +421,9 @@ cancel_before_shmem_exit(pg_on_exit_callback function, Datum arg) == function && before_shmem_exit_list[before_shmem_exit_index - 1].arg == arg) --before_shmem_exit_index; + else + elog(ERROR, "before_shmem_exit callback (%p,0x%llx) is not the latest entry", + function, (long long) arg); } /* ---------------------------------------------------------------- @@ -440,3 +443,20 @@ on_exit_reset(void) on_proc_exit_index = 0; reset_on_dsm_detach(); } + +/* ---------------------------------------------------------------- + * check_on_shmem_exit_lists_are_empty + * + * Debugging check that no shmem cleanup handlers have been registered + * prematurely in the current process. + * ---------------------------------------------------------------- + */ +void +check_on_shmem_exit_lists_are_empty(void) +{ + if (before_shmem_exit_index) + elog(FATAL, "before_shmem_exit has been called prematurely"); + if (on_shmem_exit_index) + elog(FATAL, "on_shmem_exit has been called prematurely"); + /* Checking DSM detach state seems unnecessary given the above */ +} diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c index e7f9107fbf8c..d674d869ec42 100644 --- a/src/backend/storage/ipc/latch.c +++ b/src/backend/storage/ipc/latch.c @@ -925,7 +925,22 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch) if (events == WL_LATCH_SET) { + if (latch && latch->owner_pid != MyProcPid) + elog(ERROR, "cannot wait on a latch owned by another process"); set->latch = latch; + /* + * On Unix, we don't need to modify the kernel object because the + * underlying pipe is the same for all latches so we can return + * immediately. On Windows, we need to update our array of handles, + * but we leave the old one in place and tolerate spurious wakeups if + * the latch is disabled. + */ +#if defined(WAIT_USE_WIN32) + if (!latch) + return; +#else + return; +#endif } #if defined(WAIT_USE_EPOLL) @@ -1134,7 +1149,8 @@ WaitEventAdjustKqueue(WaitEventSet *set, WaitEvent *event, int old_events) if (rc < 0) { - if (event->events == WL_POSTMASTER_DEATH && errno == ESRCH) + if (event->events == WL_POSTMASTER_DEATH && + (errno == ESRCH || errno == EACCES)) set->report_postmaster_not_running = true; else ereport(ERROR, @@ -1387,7 +1403,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout, /* There's data in the self-pipe, clear it. */ drainSelfPipe(); - if (set->latch->is_set) + if (set->latch && set->latch->is_set) { occurred_events->fd = PGINVALID_SOCKET; occurred_events->events = WL_LATCH_SET; @@ -1478,7 +1494,10 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout, timeout_p = &timeout; } - /* Report events discovered by WaitEventAdjustKqueue(). */ + /* + * Report postmaster events discovered by WaitEventAdjustKqueue() or an + * earlier call to WaitEventSetWait(). + */ if (unlikely(set->report_postmaster_not_running)) { if (set->exit_on_postmaster_death) @@ -1537,7 +1556,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout, /* There's data in the self-pipe, clear it. */ drainSelfPipe(); - if (set->latch->is_set) + if (set->latch && set->latch->is_set) { occurred_events->fd = PGINVALID_SOCKET; occurred_events->events = WL_LATCH_SET; @@ -1549,6 +1568,13 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout, cur_kqueue_event->filter == EVFILT_PROC && (cur_kqueue_event->fflags & NOTE_EXIT) != 0) { + /* + * The kernel will tell this kqueue object only once about the exit + * of the postmaster, so let's remember that for next time so that + * we provide level-triggered semantics. + */ + set->report_postmaster_not_running = true; + if (set->exit_on_postmaster_death) proc_exit(1); occurred_events->fd = PGINVALID_SOCKET; @@ -1646,7 +1672,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout, /* There's data in the self-pipe, clear it. */ drainSelfPipe(); - if (set->latch->is_set) + if (set->latch && set->latch->is_set) { occurred_events->fd = PGINVALID_SOCKET; occurred_events->events = WL_LATCH_SET; @@ -1813,7 +1839,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout, if (!ResetEvent(set->latch->event)) elog(ERROR, "ResetEvent failed: error code %lu", GetLastError()); - if (set->latch->is_set) + if (set->latch && set->latch->is_set) { occurred_events->fd = PGINVALID_SOCKET; occurred_events->events = WL_LATCH_SET; diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 4bb244bf2e43..685c094a725d 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -143,7 +143,7 @@ typedef struct ProcArrayStruct * different types of relations. As e.g. a normal user defined table in one * database is inaccessible to backends connected to another database, a test * specific to a relation can be more aggressive than a test for a shared - * relation. Currently we track three different states: + * relation. Currently we track four different states: * * 1) GlobalVisSharedRels, which only considers an XID's * effects visible-to-everyone if neither snapshots in any database, nor a @@ -158,13 +158,16 @@ typedef struct ProcArrayStruct * I.e. the difference to GlobalVisSharedRels is that * snapshot in other databases are ignored. * - * 3) GlobalVisCatalogRels, which only considers an XID's + * 3) GlobalVisDataRels, which only considers an XID's * effects visible-to-everyone if neither snapshots in the current * database, nor a replication slot's xmin consider XID as running. * * I.e. the difference to GlobalVisCatalogRels is that * replication slot's catalog_xmin is not taken into account. * + * 4) GlobalVisTempRels, which only considers the current session, as temp + * tables are not visible to other sessions. + * * GlobalVisTestFor(relation) returns the appropriate state * for the relation. * @@ -246,6 +249,13 @@ typedef struct ComputeXidHorizonsResult * defined tables. */ TransactionId data_oldest_nonremovable; + + /* + * Oldest xid for which deleted tuples need to be retained in this + * session's temporary tables. + */ + TransactionId temp_oldest_nonremovable; + } ComputeXidHorizonsResult; @@ -270,12 +280,13 @@ static TransactionId standbySnapshotPendingXmin; /* * State for visibility checks on different types of relations. See struct - * GlobalVisState for details. As shared, catalog, and user defined + * GlobalVisState for details. As shared, catalog, normal and temporary * relations can have different horizons, one such state exists for each. */ static GlobalVisState GlobalVisSharedRels; static GlobalVisState GlobalVisCatalogRels; static GlobalVisState GlobalVisDataRels; +static GlobalVisState GlobalVisTempRels; /* * This backend's RecentXmin at the last time the accurate xmin horizon was @@ -1667,7 +1678,7 @@ TransactionIdIsActive(TransactionId xid) * See the definition of ComputedXidHorizonsResult for the various computed * horizons. * - * For VACUUM separate horizons (used to to decide which deleted tuples must + * For VACUUM separate horizons (used to decide which deleted tuples must * be preserved), for shared and non-shared tables are computed. For shared * relations backends in all databases must be considered, but for non-shared * relations that's not required, since only backends in my own database could @@ -1753,6 +1764,23 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) h->oldest_considered_running = initial; h->shared_oldest_nonremovable = initial; h->data_oldest_nonremovable = initial; + + /* + * Only modifications made by this backend affect the horizon for + * temporary relations. Instead of a check in each iteration of the + * loop over all PGPROCs it is cheaper to just initialize to the + * current top-level xid any. + * + * Without an assigned xid we could use a horizon as agressive as + * ReadNewTransactionid(), but we can get away with the much cheaper + * latestCompletedXid + 1: If this backend has no xid there, by + * definition, can't be any newer changes in the temp table than + * latestCompletedXid. + */ + if (TransactionIdIsValid(MyProc->xid)) + h->temp_oldest_nonremovable = MyProc->xid; + else + h->temp_oldest_nonremovable = initial; } /* @@ -1784,8 +1812,8 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) */ xmin = TransactionIdOlder(xmin, xid); - /* if neither is set, this proc doesn't influence the horizon */ - if (!TransactionIdIsValid(xmin)) + /* if neither is set, this proc doesn't influence the horizon */ + if (!TransactionIdIsValid(xmin)) continue; /* @@ -1814,9 +1842,16 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) * the shared horizon. But in recovery we cannot compute an accurate * per-database horizon as all xids are managed via the * KnownAssignedXids machinery. + * + * Be careful to compute a pessimistic value when MyDatabaseId is not + * set. If this is a backend in the process of starting up, we may not + * use a "too aggressive" horizon (otherwise we could end up using it + * to prune still needed data away). If the current backend never + * connects to a database that is harmless, because + * data_oldest_nonremovable will never be utilized. */ if (in_recovery || - proc->databaseId == MyDatabaseId || + MyDatabaseId == InvalidOid || proc->databaseId == MyDatabaseId || proc->databaseId == 0) /* always include WalSender */ { h->data_oldest_nonremovable = @@ -1845,6 +1880,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) TransactionIdOlder(h->shared_oldest_nonremovable, kaxmin); h->data_oldest_nonremovable = TransactionIdOlder(h->data_oldest_nonremovable, kaxmin); + /* temp relations cannot be accessed in recovery */ } else { @@ -1870,6 +1906,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) h->data_oldest_nonremovable = TransactionIdRetreatedBy(h->data_oldest_nonremovable, vacuum_defer_cleanup_age); + /* defer doesn't apply to temp relations */ } /* @@ -1929,6 +1966,8 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) h->catalog_oldest_nonremovable)); Assert(TransactionIdPrecedesOrEquals(h->oldest_considered_running, h->data_oldest_nonremovable)); + Assert(TransactionIdPrecedesOrEquals(h->oldest_considered_running, + h->temp_oldest_nonremovable)); Assert(!TransactionIdIsValid(h->slot_xmin) || TransactionIdPrecedesOrEquals(h->oldest_considered_running, h->slot_xmin)); @@ -1987,6 +2026,8 @@ GetLocalOldestNonRemovableTransactionId(Relation rel) return horizons.shared_oldest_nonremovable; else if (RelationIsAccessibleInLogicalDecoding(rel)) return horizons.catalog_oldest_nonremovable; + else if (RELATION_IS_LOCAL(rel)) + return horizons.temp_oldest_nonremovable; else return horizons.data_oldest_nonremovable; } @@ -2699,8 +2740,8 @@ GetSnapshotDataReuse(Snapshot snapshot) * RecentXmin: the xmin computed for the most recent snapshot. XIDs * older than this are known not running any more. * - * And try to advance the bounds of GlobalVisSharedRels, GlobalVisCatalogRels, - * GlobalVisDataRels for the benefit of theGlobalVisTest* family of functions. + * And try to advance the bounds of GlobalVis{Shared,Catalog,Data,Temp}Rels + * for the benefit of theGlobalVisTest* family of functions. * * Note: this function should probably not be called with an argument that's * not statically allocated (see xip allocation below). @@ -3134,6 +3175,15 @@ GetSnapshotData(Snapshot snapshot, DtxContext distributedTransactionContext) GlobalVisDataRels.definitely_needed = FullTransactionIdNewer(def_vis_fxid_data, GlobalVisDataRels.definitely_needed); + /* See temp_oldest_nonremovable computation in ComputeXidHorizons() */ + if (TransactionIdIsNormal(myxid)) + GlobalVisTempRels.definitely_needed = + FullXidRelativeTo(latest_completed, myxid); + else + { + GlobalVisTempRels.definitely_needed = latest_completed; + FullTransactionIdAdvance(&GlobalVisTempRels.definitely_needed); + } /* * Check if we know that we can initialize or increase the lower @@ -3152,6 +3202,8 @@ GetSnapshotData(Snapshot snapshot, DtxContext distributedTransactionContext) GlobalVisDataRels.maybe_needed = FullTransactionIdNewer(GlobalVisDataRels.maybe_needed, oldestfxid); + /* accurate value known */ + GlobalVisTempRels.maybe_needed = GlobalVisTempRels.definitely_needed; } RecentXmin = xmin; @@ -4285,7 +4337,6 @@ CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending) { ProcArrayStruct *arrayP = procArray; int index; - pid_t pid = 0; /* tell all backends to die */ LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); @@ -4298,6 +4349,7 @@ CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending) if (databaseid == InvalidOid || proc->databaseId == databaseid) { VirtualTransactionId procvxid; + pid_t pid; GET_VXID_FROM_PGPROC(procvxid, *proc); @@ -4523,7 +4575,7 @@ TerminateOtherDBBackends(Oid databaseId) if (nprepared > 0) ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), - errmsg("database \"%s\" is being used by prepared transaction", + errmsg("database \"%s\" is being used by prepared transactions", get_database_name(databaseId)), errdetail_plural("There is %d prepared transaction using the database.", "There are %d prepared transactions using the database.", @@ -4823,6 +4875,8 @@ GlobalVisTestFor(Relation rel) state = &GlobalVisSharedRels; else if (need_catalog) state = &GlobalVisCatalogRels; + else if (RELATION_IS_LOCAL(rel)) + state = &GlobalVisTempRels; else state = &GlobalVisDataRels; @@ -4873,6 +4927,9 @@ GlobalVisUpdateApply(ComputeXidHorizonsResult *horizons) GlobalVisDataRels.maybe_needed = FullXidRelativeTo(horizons->latest_completed, GetDistOldestXmin(horizons->data_oldest_nonremovable)); + GlobalVisTempRels.maybe_needed = + FullXidRelativeTo(horizons->latest_completed, + GetDistOldestXmin(horizons->temp_oldest_nonremovable)); /* * In longer running transactions it's possible that transactions we @@ -4888,6 +4945,7 @@ GlobalVisUpdateApply(ComputeXidHorizonsResult *horizons) GlobalVisDataRels.definitely_needed = FullTransactionIdNewer(GlobalVisDataRels.maybe_needed, GlobalVisDataRels.definitely_needed); + GlobalVisTempRels.definitely_needed = GlobalVisTempRels.maybe_needed; ComputeXidHorizonsResultLastXmin = RecentXmin; } @@ -5037,7 +5095,7 @@ GlobalVisCheckRemovableXid(Relation rel, TransactionId xid) * * Be very careful about when to use this function. It can only safely be used * when there is a guarantee that xid is within MaxTransactionId / 2 xids of - * rel. That e.g. can be guaranteed if the the caller assures a snapshot is + * rel. That e.g. can be guaranteed if the caller assures a snapshot is * held by the backend and xid is from a table (where vacuum/freezing ensures * the xid has to be within that range), or if xid is from the procarray and * prevents xid wraparound that way. @@ -5211,6 +5269,9 @@ ExpireTreeKnownAssignedTransactionIds(TransactionId xid, int nsubxids, /* As in ProcArrayEndTransaction, advance latestCompletedXid */ MaintainLatestCompletedXidRecovery(max_xid); + /* ... and xactCompletionCount */ + ShmemVariableCache->xactCompletionCount++; + LWLockRelease(ProcArrayLock); } diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c index 541c8d1acbaf..d5840ae04a47 100644 --- a/src/backend/storage/ipc/procsignal.c +++ b/src/backend/storage/ipc/procsignal.c @@ -433,7 +433,7 @@ WaitForProcSignalBarrier(uint64 generation) * cannot safely access the barrier generation inside the signal handler as * 64bit atomics might use spinlock based emulation, even for reads. As this * routine only gets called when PROCSIG_BARRIER is sent that won't cause a - * lot fo unnecessary work. + * lot of unnecessary work. */ static void HandleProcSignalBarrierInterrupt(void) diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c index 07efec07f25e..27312d44c152 100644 --- a/src/backend/storage/ipc/shm_mq.c +++ b/src/backend/storage/ipc/shm_mq.c @@ -24,6 +24,7 @@ #include "storage/procsignal.h" #include "storage/shm_mq.h" #include "storage/spin.h" +#include "utils/memutils.h" /* * This structure represents the actual queue, stored in shared memory. @@ -360,6 +361,13 @@ shm_mq_sendv(shm_mq_handle *mqh, shm_mq_iovec *iov, int iovcnt, bool nowait) for (i = 0; i < iovcnt; ++i) nbytes += iov[i].len; + /* Prevent writing messages overwhelming the receiver. */ + if (nbytes > MaxAllocSize) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("cannot send a message of size %zu via shared memory queue", + nbytes))); + /* Try to write, or finish writing, the length word into the buffer. */ while (!mqh->mqh_length_word_complete) { @@ -675,6 +683,17 @@ shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bool nowait) } nbytes = mqh->mqh_expected_bytes; + /* + * Should be disallowed on the sending side already, but better check and + * error out on the receiver side as well rather than trying to read a + * prohibitively large message. + */ + if (nbytes > MaxAllocSize) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("invalid message size %zu in shared memory queue", + nbytes))); + if (mqh->mqh_partial_bytes == 0) { /* @@ -703,8 +722,13 @@ shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bool nowait) { Size newbuflen = Max(mqh->mqh_buflen, MQH_INITIAL_BUFSIZE); + /* + * Double the buffer size until the payload fits, but limit to + * MaxAllocSize. + */ while (newbuflen < nbytes) newbuflen *= 2; + newbuflen = Min(newbuflen, MaxAllocSize); if (mqh->mqh_buffer != NULL) { diff --git a/src/backend/storage/lmgr/generate-lwlocknames.pl b/src/backend/storage/lmgr/generate-lwlocknames.pl index ca54acdfb0f8..39cb97f5c3d9 100644 --- a/src/backend/storage/lmgr/generate-lwlocknames.pl +++ b/src/backend/storage/lmgr/generate-lwlocknames.pl @@ -3,8 +3,8 @@ # Generate lwlocknames.h and lwlocknames.c from lwlocknames.txt # Copyright (c) 2000-2020, PostgreSQL Global Development Group -use warnings; use strict; +use warnings; my $lastlockidx = -1; my $continue = "\n"; diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index 706536d0cf69..7357d7e3f72a 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -821,9 +821,7 @@ SerialInit(void) SerialSlruCtl->PagePrecedes = SerialPagePrecedesLogically; SimpleLruInit(SerialSlruCtl, "Serial", NUM_SERIAL_BUFFERS, 0, SerialSLRULock, "pg_serial", - LWTRANCHE_SERIAL_BUFFER); - /* Override default assumption that writes should be fsync'd */ - SerialSlruCtl->do_fsync = false; + LWTRANCHE_SERIAL_BUFFER, SYNC_HANDLER_NONE); /* * Create or attach to the SerialControl structure. @@ -1052,7 +1050,7 @@ CheckPointPredicate(void) SimpleLruTruncate(SerialSlruCtl, tailPage); /* - * Flush dirty SLRU pages to disk + * Write dirty SLRU pages to disk * * This is not actually necessary from a correctness point of view. We do * it merely as a debugging aid. @@ -1061,7 +1059,7 @@ CheckPointPredicate(void) * before deleting the file in which they sit, which would be completely * pointless. */ - SimpleLruFlush(SerialSlruCtl, true); + SimpleLruWriteAll(SerialSlruCtl, true); } /*------------------------------------------------------------------------*/ diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index b6f8e5124b5c..ac0f340fc6ca 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1532,7 +1532,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) * Only do it if the worker is not working to protect against Xid * wraparound. */ - vacuumFlags = ProcGlobal->vacuumFlags[proc->pgxactoff]; + vacuumFlags = ProcGlobal->vacuumFlags[autovac->pgxactoff]; if ((vacuumFlags & PROC_IS_AUTOVACUUM) && !(vacuumFlags & PROC_VACUUM_FOR_WRAPAROUND)) { @@ -1583,7 +1583,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) else LWLockRelease(ProcArrayLock); - /* prevent signal from being resent more than once */ + /* prevent signal from being sent again more than once */ allow_autovacuum_cancel = false; } diff --git a/src/backend/storage/page/Makefile b/src/backend/storage/page/Makefile index 10021e2bb31e..da539b113a69 100644 --- a/src/backend/storage/page/Makefile +++ b/src/backend/storage/page/Makefile @@ -19,5 +19,5 @@ OBJS = \ include $(top_srcdir)/src/backend/common.mk -# important optimizations flags for checksum.c -checksum.o: CFLAGS += ${CFLAGS_VECTOR} +# Provide special optimization flags for checksum.c +checksum.o: CFLAGS += ${CFLAGS_UNROLL_LOOPS} ${CFLAGS_VECTORIZE} diff --git a/src/backend/storage/page/bufpage.c b/src/backend/storage/page/bufpage.c index d708117a4067..ddf18079e2fb 100644 --- a/src/backend/storage/page/bufpage.c +++ b/src/backend/storage/page/bufpage.c @@ -61,7 +61,7 @@ PageInit(Page page, Size pageSize, Size specialSize) /* - * PageIsVerified + * PageIsVerifiedExtended * Check that the page header and checksum (if any) appear valid. * * This is called when a page has just been read in from disk. The idea is @@ -77,9 +77,15 @@ PageInit(Page page, Size pageSize, Size specialSize) * allow zeroed pages here, and are careful that the page access macros * treat such a page as empty and without free space. Eventually, VACUUM * will clean up such a page and make it usable. + * + * If flag PIV_LOG_WARNING is set, a WARNING is logged in the event of + * a checksum failure. + * + * If flag PIV_REPORT_STAT is set, a checksum failure is reported directly + * to pgstat. */ bool -PageIsVerified(Page page, BlockNumber blkno) +PageIsVerifiedExtended(Page page, BlockNumber blkno, int flags) { PageHeader p = (PageHeader) page; size_t *pagebytes; @@ -140,12 +146,14 @@ PageIsVerified(Page page, BlockNumber blkno) */ if (checksum_failure) { - ereport(WARNING, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg("page verification failed, calculated checksum %u but expected %u", - checksum, p->pd_checksum))); + if ((flags & PIV_LOG_WARNING) != 0) + ereport(WARNING, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("page verification failed, calculated checksum %u but expected %u", + checksum, p->pd_checksum))); - pgstat_report_checksum_failure(); + if ((flags & PIV_REPORT_STAT) != 0) + pgstat_report_checksum_failure(); if (header_sane && ignore_checksum_failure) return true; @@ -411,51 +419,250 @@ PageRestoreTempPage(Page tempPage, Page oldPage) } /* - * sorting support for PageRepairFragmentation and PageIndexMultiDelete + * Tuple defrag support for PageRepairFragmentation and PageIndexMultiDelete */ -typedef struct itemIdSortData +typedef struct itemIdCompactData { uint16 offsetindex; /* linp array index */ int16 itemoff; /* page offset of item data */ uint16 alignedlen; /* MAXALIGN(item data len) */ -} itemIdSortData; -typedef itemIdSortData *itemIdSort; - -static int -itemoffcompare(const void *itemidp1, const void *itemidp2) -{ - /* Sort in decreasing itemoff order */ - return ((itemIdSort) itemidp2)->itemoff - - ((itemIdSort) itemidp1)->itemoff; -} +} itemIdCompactData; +typedef itemIdCompactData *itemIdCompact; /* * After removing or marking some line pointers unused, move the tuples to - * remove the gaps caused by the removed items. + * remove the gaps caused by the removed items and reorder them back into + * reverse line pointer order in the page. + * + * This function can often be fairly hot, so it pays to take some measures to + * make it as optimal as possible. + * + * Callers may pass 'presorted' as true if the 'itemidbase' array is sorted in + * descending order of itemoff. When this is true we can just memmove() + * tuples towards the end of the page. This is quite a common case as it's + * the order that tuples are initially inserted into pages. When we call this + * function to defragment the tuples in the page then any new line pointers + * added to the page will keep that presorted order, so hitting this case is + * still very common for tables that are commonly updated. + * + * When the 'itemidbase' array is not presorted then we're unable to just + * memmove() tuples around freely. Doing so could cause us to overwrite the + * memory belonging to a tuple we've not moved yet. In this case, we copy all + * the tuples that need to be moved into a temporary buffer. We can then + * simply memcpy() out of that temp buffer back into the page at the correct + * location. Tuples are copied back into the page in the same order as the + * 'itemidbase' array, so we end up reordering the tuples back into reverse + * line pointer order. This will increase the chances of hitting the + * presorted case the next time around. + * + * Callers must ensure that nitems is > 0 */ static void -compactify_tuples(itemIdSort itemidbase, int nitems, Page page) +compactify_tuples(itemIdCompact itemidbase, int nitems, Page page, bool presorted) { PageHeader phdr = (PageHeader) page; Offset upper; + Offset copy_tail; + Offset copy_head; + itemIdCompact itemidptr; int i; - /* sort itemIdSortData array into decreasing itemoff order */ - qsort((char *) itemidbase, nitems, sizeof(itemIdSortData), - itemoffcompare); + /* Code within will not work correctly if nitems == 0 */ + Assert(nitems > 0); - upper = phdr->pd_special; - for (i = 0; i < nitems; i++) + if (presorted) { - itemIdSort itemidptr = &itemidbase[i]; - ItemId lp; - lp = PageGetItemId(page, itemidptr->offsetindex + 1); - upper -= itemidptr->alignedlen; +#ifdef USE_ASSERT_CHECKING + { + /* + * Verify we've not gotten any new callers that are incorrectly + * passing a true presorted value. + */ + Offset lastoff = phdr->pd_special; + + for (i = 0; i < nitems; i++) + { + itemidptr = &itemidbase[i]; + + Assert(lastoff > itemidptr->itemoff); + + lastoff = itemidptr->itemoff; + } + } +#endif /* USE_ASSERT_CHECKING */ + + /* + * 'itemidbase' is already in the optimal order, i.e, lower item + * pointers have a higher offset. This allows us to memmove() the + * tuples up to the end of the page without having to worry about + * overwriting other tuples that have not been moved yet. + * + * There's a good chance that there are tuples already right at the + * end of the page that we can simply skip over because they're + * already in the correct location within the page. We'll do that + * first... + */ + upper = phdr->pd_special; + i = 0; + do + { + itemidptr = &itemidbase[i]; + if (upper != itemidptr->itemoff + itemidptr->alignedlen) + break; + upper -= itemidptr->alignedlen; + + i++; + } while (i < nitems); + + /* + * Now that we've found the first tuple that needs to be moved, we can + * do the tuple compactification. We try and make the least number of + * memmove() calls and only call memmove() when there's a gap. When + * we see a gap we just move all tuples after the gap up until the + * point of the last move operation. + */ + copy_tail = copy_head = itemidptr->itemoff + itemidptr->alignedlen; + for (; i < nitems; i++) + { + ItemId lp; + + itemidptr = &itemidbase[i]; + lp = PageGetItemId(page, itemidptr->offsetindex + 1); + + if (copy_head != itemidptr->itemoff + itemidptr->alignedlen) + { + memmove((char *) page + upper, + page + copy_head, + copy_tail - copy_head); + + /* + * We've now moved all tuples already seen, but not the + * current tuple, so we set the copy_tail to the end of this + * tuple so it can be moved in another iteration of the loop. + */ + copy_tail = itemidptr->itemoff + itemidptr->alignedlen; + } + /* shift the target offset down by the length of this tuple */ + upper -= itemidptr->alignedlen; + /* point the copy_head to the start of this tuple */ + copy_head = itemidptr->itemoff; + + /* update the line pointer to reference the new offset */ + lp->lp_off = upper; + + } + + /* move the remaining tuples. */ memmove((char *) page + upper, - (char *) page + itemidptr->itemoff, - itemidptr->alignedlen); - lp->lp_off = upper; + page + copy_head, + copy_tail - copy_head); + } + else + { + PGAlignedBlock scratch; + char *scratchptr = scratch.data; + + /* + * Non-presorted case: The tuples in the itemidbase array may be in + * any order. So, in order to move these to the end of the page we + * must make a temp copy of each tuple that needs to be moved before + * we copy them back into the page at the new offset. + * + * If a large percentage of tuples have been pruned (>75%) then we'll + * copy these into the temp buffer tuple-by-tuple, otherwise, we'll + * just do a single memcpy() for all tuples that need to be moved. + * When so many tuples have been removed there's likely to be a lot of + * gaps and it's unlikely that many non-movable tuples remain at the + * end of the page. + */ + if (nitems < PageGetMaxOffsetNumber(page) / 4) + { + i = 0; + do + { + itemidptr = &itemidbase[i]; + memcpy(scratchptr + itemidptr->itemoff, page + itemidptr->itemoff, + itemidptr->alignedlen); + i++; + } while (i < nitems); + + /* Set things up for the compactification code below */ + i = 0; + itemidptr = &itemidbase[0]; + upper = phdr->pd_special; + } + else + { + upper = phdr->pd_special; + + /* + * Many tuples are likely to already be in the correct location. + * There's no need to copy these into the temp buffer. Instead + * we'll just skip forward in the itemidbase array to the position + * that we do need to move tuples from so that the code below just + * leaves these ones alone. + */ + i = 0; + do + { + itemidptr = &itemidbase[i]; + if (upper != itemidptr->itemoff + itemidptr->alignedlen) + break; + upper -= itemidptr->alignedlen; + + i++; + } while (i < nitems); + + /* Copy all tuples that need to be moved into the temp buffer */ + memcpy(scratchptr + phdr->pd_upper, + page + phdr->pd_upper, + upper - phdr->pd_upper); + } + + /* + * Do the tuple compactification. itemidptr is already pointing to + * the first tuple that we're going to move. Here we collapse the + * memcpy calls for adjacent tuples into a single call. This is done + * by delaying the memcpy call until we find a gap that needs to be + * closed. + */ + copy_tail = copy_head = itemidptr->itemoff + itemidptr->alignedlen; + for (; i < nitems; i++) + { + ItemId lp; + + itemidptr = &itemidbase[i]; + lp = PageGetItemId(page, itemidptr->offsetindex + 1); + + /* copy pending tuples when we detect a gap */ + if (copy_head != itemidptr->itemoff + itemidptr->alignedlen) + { + memcpy((char *) page + upper, + scratchptr + copy_head, + copy_tail - copy_head); + + /* + * We've now copied all tuples already seen, but not the + * current tuple, so we set the copy_tail to the end of this + * tuple. + */ + copy_tail = itemidptr->itemoff + itemidptr->alignedlen; + } + /* shift the target offset down by the length of this tuple */ + upper -= itemidptr->alignedlen; + /* point the copy_head to the start of this tuple */ + copy_head = itemidptr->itemoff; + + /* update the line pointer to reference the new offset */ + lp->lp_off = upper; + + } + + /* Copy the remaining chunk */ + memcpy((char *) page + upper, + scratchptr + copy_head, + copy_tail - copy_head); } phdr->pd_upper = upper; @@ -477,14 +684,16 @@ PageRepairFragmentation(Page page) Offset pd_lower = ((PageHeader) page)->pd_lower; Offset pd_upper = ((PageHeader) page)->pd_upper; Offset pd_special = ((PageHeader) page)->pd_special; - itemIdSortData itemidbase[MaxHeapTuplesPerPage]; - itemIdSort itemidptr; + Offset last_offset; + itemIdCompactData itemidbase[MaxHeapTuplesPerPage]; + itemIdCompact itemidptr; ItemId lp; int nline, nstorage, nunused; int i; Size totallen; + bool presorted = true; /* For now */ /* * It's worth the trouble to be more paranoid here than in most places, @@ -509,6 +718,7 @@ PageRepairFragmentation(Page page) nline = PageGetMaxOffsetNumber(page); itemidptr = itemidbase; nunused = totallen = 0; + last_offset = pd_special; for (i = FirstOffsetNumber; i <= nline; i++) { lp = PageGetItemId(page, i); @@ -518,6 +728,12 @@ PageRepairFragmentation(Page page) { itemidptr->offsetindex = i - 1; itemidptr->itemoff = ItemIdGetOffset(lp); + + if (last_offset > itemidptr->itemoff) + last_offset = itemidptr->itemoff; + else + presorted = false; + if (unlikely(itemidptr->itemoff < (int) pd_upper || itemidptr->itemoff >= (int) pd_special)) ereport(ERROR, @@ -552,7 +768,7 @@ PageRepairFragmentation(Page page) errmsg("corrupted item lengths: total %u, available space %u", (unsigned int) totallen, pd_special - pd_lower))); - compactify_tuples(itemidbase, nstorage, page); + compactify_tuples(itemidbase, nstorage, page, presorted); } /* Set hint bit for PageAddItem */ @@ -831,9 +1047,10 @@ PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems) Offset pd_lower = phdr->pd_lower; Offset pd_upper = phdr->pd_upper; Offset pd_special = phdr->pd_special; - itemIdSortData itemidbase[MaxIndexTuplesPerPage]; + Offset last_offset; + itemIdCompactData itemidbase[MaxIndexTuplesPerPage]; ItemIdData newitemids[MaxIndexTuplesPerPage]; - itemIdSort itemidptr; + itemIdCompact itemidptr; ItemId lp; int nline, nused; @@ -842,6 +1059,7 @@ PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems) unsigned offset; int nextitm; OffsetNumber offnum; + bool presorted = true; /* For now */ Assert(nitems <= MaxIndexTuplesPerPage); @@ -883,6 +1101,7 @@ PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems) totallen = 0; nused = 0; nextitm = 0; + last_offset = pd_special; for (offnum = FirstOffsetNumber; offnum <= nline; offnum = OffsetNumberNext(offnum)) { lp = PageGetItemId(page, offnum); @@ -906,6 +1125,12 @@ PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems) { itemidptr->offsetindex = nused; /* where it will go */ itemidptr->itemoff = offset; + + if (last_offset > itemidptr->itemoff) + last_offset = itemidptr->itemoff; + else + presorted = false; + itemidptr->alignedlen = MAXALIGN(size); totallen += itemidptr->alignedlen; newitemids[nused] = *lp; @@ -932,7 +1157,10 @@ PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems) phdr->pd_lower = SizeOfPageHeaderData + nused * sizeof(ItemIdData); /* and compactify the tuple data */ - compactify_tuples(itemidbase, nused, page); + if (nused > 0) + compactify_tuples(itemidbase, nused, page, presorted); + else + phdr->pd_upper = pd_special; } diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 18ced8df11bd..53455bc819a6 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -823,9 +823,11 @@ mdwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, BlockNumber mdnblocks(SMgrRelation reln, ForkNumber forknum) { - MdfdVec *v = mdopenfork(reln, forknum, EXTENSION_FAIL); + MdfdVec *v; BlockNumber nblocks; - BlockNumber segno = 0; + BlockNumber segno; + + mdopenfork(reln, forknum, EXTENSION_FAIL); /* mdopen has opened the first segment */ Assert(reln->md_num_open_segs[forknum] > 0); diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c index 9f2d7df2ae15..208c660d67cf 100644 --- a/src/backend/storage/sync/sync.c +++ b/src/backend/storage/sync/sync.c @@ -18,6 +18,10 @@ #include #include +#include "access/commit_ts.h" +#include "access/clog.h" +#include "access/multixact.h" +#include "access/distributedlog.h" #include "access/xlog.h" #include "access/xlogutils.h" #include "commands/tablespace.h" @@ -92,18 +96,41 @@ typedef struct SyncOps const FileTag *candidate); } SyncOps; +/* + * These indexes must correspond to the values of the SyncRequestHandler enum. + */ static const SyncOps syncsw[] = { /* magnetic disk */ - { + [SYNC_HANDLER_MD] = { .sync_syncfiletag = mdsyncfiletag, .sync_unlinkfiletag = mdunlinkfiletag, .sync_filetagmatches = mdfiletagmatches }, /* append-optimized storage */ - { + [SYNC_HANDLER_AO] = { .sync_syncfiletag = aosyncfiletag, .sync_unlinkfiletag = mdunlinkfiletag, .sync_filetagmatches = mdfiletagmatches + }, + /* pg_distributedlog */ + [SYNC_HANDLER_DISTRIBUTED_CLOG] = { + .sync_syncfiletag = DistributedLog_syncfiletag, + }, + /* pg_xact */ + [SYNC_HANDLER_CLOG] = { + .sync_syncfiletag = clogsyncfiletag + }, + /* pg_commit_ts */ + [SYNC_HANDLER_COMMIT_TS] = { + .sync_syncfiletag = committssyncfiletag + }, + /* pg_multixact/offsets */ + [SYNC_HANDLER_MULTIXACT_OFFSET] = { + .sync_syncfiletag = multixactoffsetssyncfiletag + }, + /* pg_multixact/members */ + [SYNC_HANDLER_MULTIXACT_MEMBER] = { + .sync_syncfiletag = multixactmemberssyncfiletag } }; @@ -547,8 +574,8 @@ RememberSyncRequest(const FileTag *ftag, SyncRequestType type) (void *) ftag, HASH_ENTER, &found); - /* if new entry, initialize it */ - if (!found) + /* if new entry, or was previously canceled, initialize it */ + if (!found || entry->canceled) { entry->cycle_ctr = sync_cycle_ctr; entry->canceled = false; diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c index 8dfe57a7d098..6ecc911d3dcc 100644 --- a/src/backend/tcop/dest.c +++ b/src/backend/tcop/dest.c @@ -213,6 +213,18 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o } } +/* ---------------- + * EndReplicationCommand - stripped down version of EndCommand + * + * For use by replication commands. + * ---------------- + */ +void +EndReplicationCommand(const char *commandTag) +{ + pq_putmessage('C', commandTag, strlen(commandTag) + 1); +} + /* ---------------- * NullCommand - tell dest that an empty query string was recognized * diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index aac611af5c2e..7eac3a33ed15 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -4811,7 +4811,8 @@ PostgresMain(int argc, char *argv[], } /* - * Set up signal handlers and masks. + * Set up signal handlers. (InitPostmasterChild or InitStandaloneProcess + * has already set up BlockSig and made that the active signal mask.) * * Note that postmaster blocked all signals before forking child process, * so there is no race condition whereby we might receive a signal before @@ -4833,6 +4834,9 @@ PostgresMain(int argc, char *argv[], pqsignal(SIGTERM, die); /* cancel current query and exit */ /* + * In a postmaster child backend, replace SignalHandlerForCrashExit + * with quickdie, so we can tell the client we're dying. + * * In a standalone backend, SIGQUIT can be generated from the keyboard * easily, while SIGTERM cannot, so we make both signals do die() * rather than quickdie(). @@ -4873,16 +4877,6 @@ PostgresMain(int argc, char *argv[], #endif } - pqinitmask(); - - if (IsUnderPostmaster) - { - /* We allow SIGQUIT (quickdie) at all times */ - sigdelset(&BlockSig, SIGQUIT); - } - - PG_SETMASK(&BlockSig); /* block everything except SIGQUIT */ - if (!IsUnderPostmaster) { /* diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 5e523a465b87..b78c558428ab 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1088,7 +1088,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, { ReindexStmt *stmt = (ReindexStmt *) parsetree; - if (stmt->concurrent) + if ((stmt->options & REINDEXOPT_CONCURRENTLY) != 0) PreventInTransactionBlock(isTopLevel, "REINDEX CONCURRENTLY"); @@ -1115,7 +1115,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, (stmt->kind == REINDEX_OBJECT_SCHEMA) ? "REINDEX SCHEMA" : (stmt->kind == REINDEX_OBJECT_SYSTEM) ? "REINDEX SYSTEM" : "REINDEX DATABASE"); - ReindexMultipleTables(stmt->name, stmt->kind, stmt->options, stmt->concurrent); + ReindexMultipleTables(stmt->name, stmt->kind, stmt->options); break; default: elog(ERROR, "unrecognized object type: %d", @@ -1312,6 +1312,7 @@ ProcessUtilitySlow(ParseState *pstate, List *stmts; ListCell *l; List *more_stmts = NIL; + RangeVar *table_rv = NULL; /* Run parse analysis ... */ /* @@ -1354,15 +1355,18 @@ ProcessUtilitySlow(ParseState *pstate, else cstmt->relKind = relKind; + /* Remember transformed RangeVar for LIKE */ + table_rv = cstmt->relation; + /* * GPDB: Don't dispatch it yet, as we haven't * created the toast and other auxiliary tables * yet. */ /* Create the table itself */ - address = DefineRelation((CreateStmt *) stmt, + address = DefineRelation(cstmt, relKind, - ((CreateStmt *) stmt)->ownerid, NULL, + cstmt->ownerid, NULL, queryString, false, true, cstmt->intoPolicy); @@ -1407,7 +1411,7 @@ ProcessUtilitySlow(ParseState *pstate, * table */ toast_options = transformRelOptions((Datum) 0, - ((CreateStmt *) stmt)->options, + cstmt->options, "toast", validnsps, true, @@ -1450,21 +1454,49 @@ ProcessUtilitySlow(ParseState *pstate, } else if (IsA(stmt, CreateForeignTableStmt)) { + CreateForeignTableStmt *cstmt = (CreateForeignTableStmt *) stmt; + + /* Remember transformed RangeVar for LIKE */ + table_rv = cstmt->base.relation; + /* Create the table itself */ - address = DefineRelation((CreateStmt *) stmt, + address = DefineRelation(&cstmt->base, RELKIND_FOREIGN_TABLE, InvalidOid, NULL, queryString, true, true, NULL); - CreateForeignTable((CreateForeignTableStmt *) stmt, + CreateForeignTable(cstmt, address.objectId, false /* skip_permission_checks */); EventTriggerCollectSimpleCommand(address, secondaryObject, stmt); } + else if (IsA(stmt, TableLikeClause)) + { + /* + * Do delayed processing of LIKE options. This + * will result in additional sub-statements for us + * to process. We can just tack those onto the + * to-do list. + */ + TableLikeClause *like = (TableLikeClause *) stmt; + List *morestmts; + + Assert(table_rv != NULL); + + morestmts = expandTableLikeClause(table_rv, like); + stmts = list_concat(stmts, morestmts); + + /* + * We don't need a CCI now, besides which the "l" + * list pointer is now possibly invalid, so just + * skip the CCI test below. + */ + continue; + } else { /* @@ -1747,6 +1779,7 @@ ProcessUtilitySlow(ParseState *pstate, IndexStmt *stmt = (IndexStmt *) parsetree; Oid relid; LOCKMODE lockmode; + bool is_alter_table = false; // see below if (stmt->concurrent) PreventInTransactionBlock(isTopLevel, @@ -1816,6 +1849,41 @@ ProcessUtilitySlow(ParseState *pstate, list_free(inheritors); } + /* + * Greengage specific behavior: + * Postgres will pass false for is_alter_table for DefineIndex. + * This argument is only used at two places in DefineIndex (in original postgres code): + * 1. the function index_check_primary_key + * 2. print a debug log on what the statement is + * + * In fact when calling DefineIndex here, we can always pass + * false for is_alter_table when it actually comes from expandTableLikeClause: + * for 1, we are sure relationHasPrimaryKey check will pass because we are + * building a new relation with index here. + * for 2, I do not think it will mislead the user if we print it as CreateStmt. + * + * But for Greengage, is_alter_table matters a lot and has to be set false here: + * DefineIndex need to dispatch, and if it is_alter_table is true, Greengage will + * take this as a sub command of AlterTable stmt, thus it will not dispatch and + * lead to errors. Thus, we comment off the following code and pass false for + * is_alter_table for DefineIndex here. + * + * See following discussion for details: + * https://www.postgresql.org/message-id/CANerzActdrdFO1r4RSqK0M2d0Xtwu5t5bH%3DZOoLsAQ%3DHhZrB%3Dg%40mail.gmail.com + */ +#if 0 + /* + * If the IndexStmt is already transformed, it must have + * come from generateClonedIndexStmt, which in current + * usage means it came from expandTableLikeClause rather + * than from original parse analysis. And that means we + * must treat it like ALTER TABLE ADD INDEX, not CREATE. + * (This is a bit grotty, but currently it doesn't seem + * worth adding a separate bool field for the purpose.) + */ + is_alter_table = stmt->transformed; +#endif + /* Run parse analysis ... */ stmt = transformIndexStmt(relid, stmt, queryString); @@ -1827,7 +1895,7 @@ ProcessUtilitySlow(ParseState *pstate, InvalidOid, /* no predefined OID */ InvalidOid, /* no parent index */ InvalidOid, /* no parent constraint */ - false, /* is_alter_table */ + is_alter_table, true, /* check_rights */ true, /* check_not_in_use */ false, /* skip_build */ @@ -2159,10 +2227,6 @@ ProcessUtilitySlow(ParseState *pstate, address = AlterStatistics((AlterStatsStmt *) parsetree); break; - case T_AlterCollationStmt: - address = AlterCollation((AlterCollationStmt *) parsetree); - break; - default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(parsetree)); @@ -3421,10 +3485,6 @@ CreateCommandTag(Node *parsetree) tag = CMDTAG_DROP_SUBSCRIPTION; break; - case T_AlterCollationStmt: - tag = CMDTAG_ALTER_COLLATION; - break; - case T_PrepareStmt: tag = CMDTAG_PREPARE; break; @@ -4050,10 +4110,6 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; - case T_AlterCollationStmt: - lev = LOGSTMT_DDL; - break; - /* already-planned queries */ case T_PlannedStmt: { diff --git a/src/backend/tsearch/dict_thesaurus.c b/src/backend/tsearch/dict_thesaurus.c index cb0835982d85..64c979086d1e 100644 --- a/src/backend/tsearch/dict_thesaurus.c +++ b/src/backend/tsearch/dict_thesaurus.c @@ -286,11 +286,6 @@ thesaurusRead(const char *filename, DictThesaurus *d) (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("unexpected end of line"))); - /* - * Note: currently, tsearch_readline can't return lines exceeding 4KB, - * so overflow of the word counts is impossible. But that may not - * always be true, so let's check. - */ if (nwrd != (uint16) nwrd || posinsubst != (uint16) posinsubst) ereport(ERROR, (errcode(ERRCODE_CONFIG_FILE_ERROR), diff --git a/src/backend/tsearch/spell.c b/src/backend/tsearch/spell.c index 8aab96d3b066..05d08cfc0102 100644 --- a/src/backend/tsearch/spell.c +++ b/src/backend/tsearch/spell.c @@ -1710,7 +1710,7 @@ void NISortDictionary(IspellDict *Conf) { int i; - int naffix = 0; + int naffix; int curaffix; /* compress affixes */ @@ -2124,7 +2124,6 @@ CheckAffix(const char *word, size_t len, AFFIX *Affix, int flagflags, char *neww } else { - int err; pg_wchar *data; size_t data_len; int newword_len; @@ -2134,7 +2133,8 @@ CheckAffix(const char *word, size_t len, AFFIX *Affix, int flagflags, char *neww data = (pg_wchar *) palloc((newword_len + 1) * sizeof(pg_wchar)); data_len = pg_mb2wchar_with_len(newword, data, newword_len); - if (!(err = pg_regexec(&(Affix->reg.regex), data, data_len, 0, NULL, 0, NULL, 0))) + if (pg_regexec(&(Affix->reg.regex), data, data_len, + 0, NULL, 0, NULL, 0) == REG_OKAY) { pfree(data); return newword; diff --git a/src/backend/tsearch/ts_locale.c b/src/backend/tsearch/ts_locale.c index 4a7fa607ce78..fd6a8a13a56c 100644 --- a/src/backend/tsearch/ts_locale.c +++ b/src/backend/tsearch/ts_locale.c @@ -14,6 +14,7 @@ #include "postgres.h" #include "catalog/pg_collation.h" +#include "common/string.h" #include "storage/fd.h" #include "tsearch/ts_locale.h" #include "tsearch/ts_public.h" @@ -139,6 +140,7 @@ tsearch_readline_begin(tsearch_readline_state *stp, return false; stp->filename = filename; stp->lineno = 0; + initStringInfo(&stp->buf); stp->curline = NULL; /* Setup error traceback support for ereport() */ stp->cb.callback = tsearch_readline_callback; @@ -156,13 +158,43 @@ tsearch_readline_begin(tsearch_readline_state *stp, char * tsearch_readline(tsearch_readline_state *stp) { - char *result; + char *recoded; + /* Advance line number to use in error reports */ stp->lineno++; - stp->curline = NULL; - result = t_readline(stp->fp); - stp->curline = result; - return result; + + /* Clear curline, it's no longer relevant */ + if (stp->curline) + { + if (stp->curline != stp->buf.data) + pfree(stp->curline); + stp->curline = NULL; + } + + /* Collect next line, if there is one */ + if (!pg_get_line_buf(stp->fp, &stp->buf)) + return NULL; + + /* Validate the input as UTF-8, then convert to DB encoding if needed */ + recoded = pg_any_to_server(stp->buf.data, stp->buf.len, PG_UTF8); + + /* Save the correctly-encoded string for possible error reports */ + stp->curline = recoded; /* might be equal to buf.data */ + + /* + * We always return a freshly pstrdup'd string. This is clearly necessary + * if pg_any_to_server() returned buf.data, and we need a second copy even + * if encoding conversion did occur. The caller is entitled to pfree the + * returned string at any time, which would leave curline pointing to + * recycled storage, causing problems if an error occurs after that point. + * (It's preferable to return the result of pstrdup instead of the output + * of pg_any_to_server, because the conversion result tends to be + * over-allocated. Since callers might save the result string directly + * into a long-lived dictionary structure, we don't want it to be a larger + * palloc chunk than necessary. We'll reclaim the conversion result on + * the next call.) + */ + return pstrdup(recoded); } /* @@ -171,7 +203,18 @@ tsearch_readline(tsearch_readline_state *stp) void tsearch_readline_end(tsearch_readline_state *stp) { + /* Suppress use of curline in any error reported below */ + if (stp->curline) + { + if (stp->curline != stp->buf.data) + pfree(stp->curline); + stp->curline = NULL; + } + + /* Release other resources */ + pfree(stp->buf.data); FreeFile(stp->fp); + /* Pop the error context stack */ error_context_stack = stp->cb.previous; } @@ -187,8 +230,7 @@ tsearch_readline_callback(void *arg) /* * We can't include the text of the config line for errors that occur - * during t_readline() itself. This is only partly a consequence of our - * arms-length use of that routine: the major cause of such errors is + * during tsearch_readline() itself. The major cause of such errors is * encoding violations, and we daren't try to print error messages * containing badly-encoded data. */ @@ -204,43 +246,6 @@ tsearch_readline_callback(void *arg) } -/* - * Read the next line from a tsearch data file (expected to be in UTF-8), and - * convert it to database encoding if needed. The returned string is palloc'd. - * NULL return means EOF. - * - * Note: direct use of this function is now deprecated. Go through - * tsearch_readline() to provide better error reporting. - */ -char * -t_readline(FILE *fp) -{ - int len; - char *recoded; - char buf[4096]; /* lines must not be longer than this */ - - if (fgets(buf, sizeof(buf), fp) == NULL) - return NULL; - - len = strlen(buf); - - /* Make sure the input is valid UTF-8 */ - (void) pg_verify_mbstr(PG_UTF8, buf, len, false); - - /* And convert */ - recoded = pg_any_to_server(buf, len, PG_UTF8); - if (recoded == buf) - { - /* - * conversion didn't pstrdup, so we must. We can use the length of the - * original string, because no conversion was done. - */ - recoded = pnstrdup(recoded, len); - } - - return recoded; -} - /* * lowerstr --- fold null-terminated string to lower case * diff --git a/src/backend/utils/Gen_fmgrtab.pl b/src/backend/utils/Gen_fmgrtab.pl index b7c7b4c8fae1..8228ad6db60d 100644 --- a/src/backend/utils/Gen_fmgrtab.pl +++ b/src/backend/utils/Gen_fmgrtab.pl @@ -64,22 +64,26 @@ # Collect certain fields from pg_proc.dat. my @fmgr = (); +my %proname_counts; foreach my $row (@{ $catalog_data{pg_proc} }) { my %bki_values = %$row; - # Select out just the rows for internal-language procedures. - next if $bki_values{prolang} ne 'internal'; - push @fmgr, { oid => $bki_values{oid}, + name => $bki_values{proname}, + lang => $bki_values{prolang}, strict => $bki_values{proisstrict}, retset => $bki_values{proretset}, nargs => $bki_values{pronargs}, + args => $bki_values{proargtypes}, prosrc => $bki_values{prosrc}, }; + + # Count so that we can detect overloaded pronames. + $proname_counts{ $bki_values{proname} }++; } # Emit headers for both files @@ -122,13 +126,10 @@ /* * Constant macros for the OIDs of entries in pg_proc. * - * NOTE: macros are named after the prosrc value, ie the actual C name - * of the implementing function, not the proname which may be overloaded. - * For example, we want to be able to assign different macro names to both - * char_text() and name_text() even though these both appear with proname - * 'text'. If the same C function appears in more than one pg_proc entry, - * its equivalent macro will be defined with the lowest OID among those - * entries. + * F_XXX macros are named after the proname field; if that is not unique, + * we append the proargtypes field, replacing spaces with underscores. + * For example, we have F_OIDEQ because that proname is unique, but + * F_POW_FLOAT8_FLOAT8 (among others) because that proname is not. */ OFH @@ -186,14 +187,20 @@ TFH -# Emit #define's and extern's -- only one per prosrc value +# Emit fmgroids.h and fmgrprotos.h entries in OID order. my %seenit; foreach my $s (sort { $a->{oid} <=> $b->{oid} } @fmgr) { - next if $seenit{ $s->{prosrc} }; - $seenit{ $s->{prosrc} } = 1; - print $ofh "#define F_" . uc $s->{prosrc} . " $s->{oid}\n"; - print $pfh "extern Datum $s->{prosrc}(PG_FUNCTION_ARGS);\n"; + my $sqlname = $s->{name}; + $sqlname .= "_" . $s->{args} if ($proname_counts{ $s->{name} } > 1); + $sqlname =~ s/\s+/_/g; + print $ofh "#define F_" . uc $sqlname . " $s->{oid}\n"; + # We want only one extern per internal-language function + if ($s->{lang} eq 'internal' && !$seenit{ $s->{prosrc} }) + { + $seenit{ $s->{prosrc} } = 1; + print $pfh "extern Datum $s->{prosrc}(PG_FUNCTION_ARGS);\n"; + } } # Create the fmgr_builtins table, collect data for fmgr_builtin_oid_index @@ -206,22 +213,16 @@ my $fmgr_count = 0; foreach my $s (sort { $a->{oid} <=> $b->{oid} } @fmgr) { + next if $s->{lang} ne 'internal'; + + print $tfh ",\n" if ($fmgr_count > 0); print $tfh " { $s->{oid}, $s->{nargs}, $bmap{$s->{strict}}, $bmap{$s->{retset}}, \"$s->{prosrc}\", $s->{prosrc} }"; $fmgr_builtin_oid_index[ $s->{oid} ] = $fmgr_count++; $last_builtin_oid = $s->{oid}; - - if ($fmgr_count <= $#fmgr) - { - print $tfh ",\n"; - } - else - { - print $tfh "\n"; - } } -print $tfh "};\n"; +print $tfh "\n};\n"; printf $tfh qq| const int fmgr_nbuiltins = (sizeof(fmgr_builtins) / sizeof(FmgrBuiltin)); diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 6f18b06ca4b8..5cf6a64911a2 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -58,6 +58,7 @@ OBJS = \ lockfuncs.o \ mac.o \ mac8.o \ + mcxtfuncs.o \ misc.o \ name.o \ network.o \ @@ -125,6 +126,9 @@ clean distclean maintainer-clean: like.o: like.c like_match.c +# Some code in numeric.c benefits from auto-vectorization +numeric.o: CFLAGS += ${CFLAGS_VECTORIZE} + varlena.o: varlena.c levenshtein.c # GPDB additions diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 5ead3dc88e7c..8e848a3a7cc5 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -5365,6 +5365,7 @@ get_rolespec_oid(const RoleSpec *role, bool missing_ok) oid = get_role_oid(role->rolename, missing_ok); break; + case ROLESPEC_CURRENT_ROLE: case ROLESPEC_CURRENT_USER: oid = GetUserId(); break; @@ -5407,6 +5408,7 @@ get_rolespec_tuple(const RoleSpec *role) errmsg("role \"%s\" does not exist", role->rolename))); break; + case ROLESPEC_CURRENT_ROLE: case ROLESPEC_CURRENT_USER: tuple = SearchSysCache1(AUTHOID, GetUserId()); if (!HeapTupleIsValid(tuple)) diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c index 6515fc8ec695..d093ce80386f 100644 --- a/src/backend/utils/adt/cash.c +++ b/src/backend/utils/adt/cash.c @@ -1042,7 +1042,7 @@ cash_numeric(PG_FUNCTION_ARGS) fpoint = 2; /* convert the integral money value to numeric */ - result = DirectFunctionCall1(int8_numeric, Int64GetDatum(money)); + result = NumericGetDatum(int64_to_numeric(money)); /* scale appropriately, if needed */ if (fpoint > 0) @@ -1056,8 +1056,7 @@ cash_numeric(PG_FUNCTION_ARGS) scale = 1; for (i = 0; i < fpoint; i++) scale *= 10; - numeric_scale = DirectFunctionCall1(int8_numeric, - Int64GetDatum(scale)); + numeric_scale = NumericGetDatum(int64_to_numeric(scale)); /* * Given integral inputs approaching INT64_MAX, select_div_scale() @@ -1107,7 +1106,7 @@ numeric_cash(PG_FUNCTION_ARGS) scale *= 10; /* multiply the input amount by scale factor */ - numeric_scale = DirectFunctionCall1(int8_numeric, Int64GetDatum(scale)); + numeric_scale = NumericGetDatum(int64_to_numeric(scale)); amount = DirectFunctionCall2(numeric_mul, amount, numeric_scale); /* note that numeric_int8 will round to nearest integer for us */ diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index b1a71a307742..119f98fd0b24 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -299,20 +299,31 @@ EncodeSpecialDate(DateADT dt, char *str) DateADT GetSQLCurrentDate(void) { - TimestampTz ts; - struct pg_tm tt, - *tm = &tt; - fsec_t fsec; - int tz; + struct pg_tm tm; - ts = GetCurrentTransactionStartTimestamp(); + static int cache_year = 0; + static int cache_mon = 0; + static int cache_mday = 0; + static DateADT cache_date; - if (timestamp2tm(ts, &tz, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + GetCurrentDateTime(&tm); - return date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE; + /* + * date2j involves several integer divisions; moreover, unless our session + * lives across local midnight, we don't really have to do it more than + * once. So it seems worth having a separate cache here. + */ + if (tm.tm_year != cache_year || + tm.tm_mon != cache_mon || + tm.tm_mday != cache_mday) + { + cache_date = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE; + cache_year = tm.tm_year; + cache_mon = tm.tm_mon; + cache_mday = tm.tm_mday; + } + + return cache_date; } /* @@ -322,18 +333,12 @@ TimeTzADT * GetSQLCurrentTime(int32 typmod) { TimeTzADT *result; - TimestampTz ts; struct pg_tm tt, *tm = &tt; fsec_t fsec; int tz; - ts = GetCurrentTransactionStartTimestamp(); - - if (timestamp2tm(ts, &tz, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + GetCurrentTimeUsec(tm, &fsec, &tz); result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); tm2timetz(tm, fsec, tz, result); @@ -348,18 +353,12 @@ TimeADT GetSQLLocalTime(int32 typmod) { TimeADT result; - TimestampTz ts; struct pg_tm tt, *tm = &tt; fsec_t fsec; int tz; - ts = GetCurrentTransactionStartTimestamp(); - - if (timestamp2tm(ts, &tz, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + GetCurrentTimeUsec(tm, &fsec, &tz); tm2time(tm, fsec, &result); AdjustTimeForTypmod(&result, typmod); @@ -555,15 +554,24 @@ date_mii(PG_FUNCTION_ARGS) /* * Promote date to timestamp. * - * On overflow error is thrown if 'overflow' is NULL. Otherwise, '*overflow' - * is set to -1 (+1) when result value exceed lower (upper) boundary and zero - * returned. + * On successful conversion, *overflow is set to zero if it's not NULL. + * + * If the date is finite but out of the valid range for timestamp, then: + * if overflow is NULL, we throw an out-of-range error. + * if overflow is not NULL, we store +1 or -1 there to indicate the sign + * of the overflow, and return the appropriate timestamp infinity. + * + * Note: *overflow = -1 is actually not possible currently, since both + * datatypes have the same lower bound, Julian day zero. */ Timestamp date2timestamp_opt_overflow(DateADT dateVal, int *overflow) { Timestamp result; + if (overflow) + *overflow = 0; + if (DATE_IS_NOBEGIN(dateVal)) TIMESTAMP_NOBEGIN(result); else if (DATE_IS_NOEND(dateVal)) @@ -571,7 +579,6 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow) else { /* - * Date's range is wider than timestamp's, so check for boundaries. * Since dates have the same minimum values as timestamps, only upper * boundary need be checked for overflow. */ @@ -580,7 +587,8 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow) if (overflow) { *overflow = 1; - return (Timestamp) 0; + TIMESTAMP_NOEND(result); + return result; } else { @@ -598,7 +606,7 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow) } /* - * Single-argument version of date2timestamp_opt_overflow(). + * Promote date to timestamp, throwing error for overflow. */ static TimestampTz date2timestamp(DateADT dateVal) @@ -609,9 +617,12 @@ date2timestamp(DateADT dateVal) /* * Promote date to timestamp with time zone. * - * On overflow error is thrown if 'overflow' is NULL. Otherwise, '*overflow' - * is set to -1 (+1) when result value exceed lower (upper) boundary and zero - * returned. + * On successful conversion, *overflow is set to zero if it's not NULL. + * + * If the date is finite but out of the valid range for timestamptz, then: + * if overflow is NULL, we throw an out-of-range error. + * if overflow is not NULL, we store +1 or -1 there to indicate the sign + * of the overflow, and return the appropriate timestamptz infinity. */ TimestampTz date2timestamptz_opt_overflow(DateADT dateVal, int *overflow) @@ -621,6 +632,9 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow) *tm = &tt; int tz; + if (overflow) + *overflow = 0; + if (DATE_IS_NOBEGIN(dateVal)) TIMESTAMP_NOBEGIN(result); else if (DATE_IS_NOEND(dateVal)) @@ -628,7 +642,6 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow) else { /* - * Date's range is wider than timestamp's, so check for boundaries. * Since dates have the same minimum values as timestamps, only upper * boundary need be checked for overflow. */ @@ -637,7 +650,8 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow) if (overflow) { *overflow = 1; - return (TimestampTz) 0; + TIMESTAMP_NOEND(result); + return result; } else { @@ -665,13 +679,15 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow) if (overflow) { if (result < MIN_TIMESTAMP) + { *overflow = -1; + TIMESTAMP_NOBEGIN(result); + } else { - Assert(result >= END_TIMESTAMP); *overflow = 1; + TIMESTAMP_NOEND(result); } - return (TimestampTz) 0; } else { @@ -686,7 +702,7 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow) } /* - * Single-argument version of date2timestamptz_opt_overflow(). + * Promote date to timestamptz, throwing error for overflow. */ static TimestampTz date2timestamptz(DateADT dateVal) @@ -727,16 +743,30 @@ date2timestamp_no_overflow(DateADT dateVal) * Crosstype comparison functions for dates */ +int32 +date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2) +{ + Timestamp dt1; + int overflow; + + dt1 = date2timestamp_opt_overflow(dateVal, &overflow); + if (overflow > 0) + { + /* dt1 is larger than any finite timestamp, but less than infinity */ + return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1; + } + Assert(overflow == 0); /* -1 case cannot occur */ + + return timestamp_cmp_internal(dt1, dt2); +} + Datum date_eq_timestamp(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); Timestamp dt2 = PG_GETARG_TIMESTAMP(1); - Timestamp dt1; - dt1 = date2timestamp(dateVal); - - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) == 0); + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) == 0); } Datum @@ -744,11 +774,8 @@ date_ne_timestamp(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); Timestamp dt2 = PG_GETARG_TIMESTAMP(1); - Timestamp dt1; - - dt1 = date2timestamp(dateVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) != 0); + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) != 0); } Datum @@ -756,11 +783,8 @@ date_lt_timestamp(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); Timestamp dt2 = PG_GETARG_TIMESTAMP(1); - Timestamp dt1; - - dt1 = date2timestamp(dateVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) < 0); + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) < 0); } Datum @@ -768,11 +792,8 @@ date_gt_timestamp(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); Timestamp dt2 = PG_GETARG_TIMESTAMP(1); - Timestamp dt1; - - dt1 = date2timestamp(dateVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) > 0); + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) > 0); } Datum @@ -780,11 +801,8 @@ date_le_timestamp(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); Timestamp dt2 = PG_GETARG_TIMESTAMP(1); - Timestamp dt1; - dt1 = date2timestamp(dateVal); - - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) <= 0); + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) <= 0); } Datum @@ -792,11 +810,8 @@ date_ge_timestamp(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); Timestamp dt2 = PG_GETARG_TIMESTAMP(1); - Timestamp dt1; - - dt1 = date2timestamp(dateVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) >= 0); + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) >= 0); } Datum @@ -804,11 +819,29 @@ date_cmp_timestamp(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); Timestamp dt2 = PG_GETARG_TIMESTAMP(1); - Timestamp dt1; - dt1 = date2timestamp(dateVal); + PG_RETURN_INT32(date_cmp_timestamp_internal(dateVal, dt2)); +} + +int32 +date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2) +{ + TimestampTz dt1; + int overflow; - PG_RETURN_INT32(timestamp_cmp_internal(dt1, dt2)); + dt1 = date2timestamptz_opt_overflow(dateVal, &overflow); + if (overflow > 0) + { + /* dt1 is larger than any finite timestamp, but less than infinity */ + return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1; + } + if (overflow < 0) + { + /* dt1 is less than any finite timestamp, but more than -infinity */ + return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1; + } + + return timestamptz_cmp_internal(dt1, dt2); } Datum @@ -816,11 +849,8 @@ date_eq_timestamptz(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - dt1 = date2timestamptz(dateVal); - - PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) == 0); + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) == 0); } Datum @@ -828,11 +858,8 @@ date_ne_timestamptz(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - - dt1 = date2timestamptz(dateVal); - PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) != 0); + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) != 0); } Datum @@ -840,11 +867,8 @@ date_lt_timestamptz(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - - dt1 = date2timestamptz(dateVal); - PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) < 0); + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) < 0); } Datum @@ -852,11 +876,8 @@ date_gt_timestamptz(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - - dt1 = date2timestamptz(dateVal); - PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) > 0); + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) > 0); } Datum @@ -864,11 +885,8 @@ date_le_timestamptz(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - dt1 = date2timestamptz(dateVal); - - PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) <= 0); + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) <= 0); } Datum @@ -876,11 +894,8 @@ date_ge_timestamptz(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - - dt1 = date2timestamptz(dateVal); - PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) >= 0); + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) >= 0); } Datum @@ -888,11 +903,8 @@ date_cmp_timestamptz(PG_FUNCTION_ARGS) { DateADT dateVal = PG_GETARG_DATEADT(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - - dt1 = date2timestamptz(dateVal); - PG_RETURN_INT32(timestamptz_cmp_internal(dt1, dt2)); + PG_RETURN_INT32(date_cmp_timestamptz_internal(dateVal, dt2)); } Datum @@ -900,11 +912,8 @@ timestamp_eq_date(PG_FUNCTION_ARGS) { Timestamp dt1 = PG_GETARG_TIMESTAMP(0); DateADT dateVal = PG_GETARG_DATEADT(1); - Timestamp dt2; - - dt2 = date2timestamp(dateVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) == 0); + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) == 0); } Datum @@ -912,11 +921,8 @@ timestamp_ne_date(PG_FUNCTION_ARGS) { Timestamp dt1 = PG_GETARG_TIMESTAMP(0); DateADT dateVal = PG_GETARG_DATEADT(1); - Timestamp dt2; - dt2 = date2timestamp(dateVal); - - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) != 0); + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) != 0); } Datum @@ -924,11 +930,8 @@ timestamp_lt_date(PG_FUNCTION_ARGS) { Timestamp dt1 = PG_GETARG_TIMESTAMP(0); DateADT dateVal = PG_GETARG_DATEADT(1); - Timestamp dt2; - - dt2 = date2timestamp(dateVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) < 0); + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) > 0); } Datum @@ -936,11 +939,8 @@ timestamp_gt_date(PG_FUNCTION_ARGS) { Timestamp dt1 = PG_GETARG_TIMESTAMP(0); DateADT dateVal = PG_GETARG_DATEADT(1); - Timestamp dt2; - dt2 = date2timestamp(dateVal); - - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) > 0); + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) < 0); } Datum @@ -948,11 +948,8 @@ timestamp_le_date(PG_FUNCTION_ARGS) { Timestamp dt1 = PG_GETARG_TIMESTAMP(0); DateADT dateVal = PG_GETARG_DATEADT(1); - Timestamp dt2; - - dt2 = date2timestamp(dateVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) <= 0); + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) >= 0); } Datum @@ -960,11 +957,8 @@ timestamp_ge_date(PG_FUNCTION_ARGS) { Timestamp dt1 = PG_GETARG_TIMESTAMP(0); DateADT dateVal = PG_GETARG_DATEADT(1); - Timestamp dt2; - - dt2 = date2timestamp(dateVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) >= 0); + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) <= 0); } Datum @@ -972,11 +966,8 @@ timestamp_cmp_date(PG_FUNCTION_ARGS) { Timestamp dt1 = PG_GETARG_TIMESTAMP(0); DateADT dateVal = PG_GETARG_DATEADT(1); - Timestamp dt2; - dt2 = date2timestamp(dateVal); - - PG_RETURN_INT32(timestamp_cmp_internal(dt1, dt2)); + PG_RETURN_INT32(-date_cmp_timestamp_internal(dateVal, dt1)); } Datum @@ -984,11 +975,8 @@ timestamptz_eq_date(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); DateADT dateVal = PG_GETARG_DATEADT(1); - TimestampTz dt2; - - dt2 = date2timestamptz(dateVal); - PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) == 0); + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) == 0); } Datum @@ -996,11 +984,8 @@ timestamptz_ne_date(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); DateADT dateVal = PG_GETARG_DATEADT(1); - TimestampTz dt2; - - dt2 = date2timestamptz(dateVal); - PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) != 0); + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) != 0); } Datum @@ -1008,11 +993,8 @@ timestamptz_lt_date(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); DateADT dateVal = PG_GETARG_DATEADT(1); - TimestampTz dt2; - dt2 = date2timestamptz(dateVal); - - PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) < 0); + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) > 0); } Datum @@ -1020,11 +1002,8 @@ timestamptz_gt_date(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); DateADT dateVal = PG_GETARG_DATEADT(1); - TimestampTz dt2; - - dt2 = date2timestamptz(dateVal); - PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) > 0); + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) < 0); } Datum @@ -1032,11 +1011,8 @@ timestamptz_le_date(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); DateADT dateVal = PG_GETARG_DATEADT(1); - TimestampTz dt2; - - dt2 = date2timestamptz(dateVal); - PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) <= 0); + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) >= 0); } Datum @@ -1044,11 +1020,8 @@ timestamptz_ge_date(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); DateADT dateVal = PG_GETARG_DATEADT(1); - TimestampTz dt2; - dt2 = date2timestamptz(dateVal); - - PG_RETURN_BOOL(timestamptz_cmp_internal(dt1, dt2) >= 0); + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) <= 0); } Datum @@ -1056,11 +1029,8 @@ timestamptz_cmp_date(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); DateADT dateVal = PG_GETARG_DATEADT(1); - TimestampTz dt2; - - dt2 = date2timestamptz(dateVal); - PG_RETURN_INT32(timestamptz_cmp_internal(dt1, dt2)); + PG_RETURN_INT32(-date_cmp_timestamptz_internal(dateVal, dt1)); } /* @@ -1080,6 +1050,7 @@ in_range_date_interval(PG_FUNCTION_ARGS) Timestamp valStamp; Timestamp baseStamp; + /* XXX we could support out-of-range cases here, perhaps */ valStamp = date2timestamp(val); baseStamp = date2timestamp(base); diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index d3197a9df071..a9df1518bfc0 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -353,35 +353,80 @@ j2day(int date) /* * GetCurrentDateTime() * - * Get the transaction start time ("now()") broken down as a struct pg_tm. + * Get the transaction start time ("now()") broken down as a struct pg_tm, + * converted according to the session timezone setting. + * + * This is just a convenience wrapper for GetCurrentTimeUsec, to cover the + * case where caller doesn't need either fractional seconds or tz offset. */ void GetCurrentDateTime(struct pg_tm *tm) { - int tz; fsec_t fsec; - timestamp2tm(GetCurrentTransactionStartTimestamp(), &tz, tm, &fsec, - NULL, NULL); - /* Note: don't pass NULL tzp to timestamp2tm; affects behavior */ + GetCurrentTimeUsec(tm, &fsec, NULL); } /* * GetCurrentTimeUsec() * * Get the transaction start time ("now()") broken down as a struct pg_tm, - * including fractional seconds and timezone offset. + * including fractional seconds and timezone offset. The time is converted + * according to the session timezone setting. + * + * Callers may pass tzp = NULL if they don't need the offset, but this does + * not affect the conversion behavior (unlike timestamp2tm()). + * + * Internally, we cache the result, since this could be called many times + * in a transaction, within which now() doesn't change. */ void GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp) { - int tz; + TimestampTz cur_ts = GetCurrentTransactionStartTimestamp(); + + /* + * The cache key must include both current time and current timezone. By + * representing the timezone by just a pointer, we're assuming that + * distinct timezone settings could never have the same pointer value. + * This is true by virtue of the hashtable used inside pg_tzset(); + * however, it might need another look if we ever allow entries in that + * hash to be recycled. + */ + static TimestampTz cache_ts = 0; + static pg_tz *cache_timezone = NULL; + static struct pg_tm cache_tm; + static fsec_t cache_fsec; + static int cache_tz; + + if (cur_ts != cache_ts || session_timezone != cache_timezone) + { + /* + * Make sure cache is marked invalid in case of error after partial + * update within timestamp2tm. + */ + cache_timezone = NULL; + + /* + * Perform the computation, storing results into cache. We do not + * really expect any error here, since current time surely ought to be + * within range, but check just for sanity's sake. + */ + if (timestamp2tm(cur_ts, &cache_tz, &cache_tm, &cache_fsec, + NULL, session_timezone) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + /* OK, so mark the cache valid. */ + cache_ts = cur_ts; + cache_timezone = session_timezone; + } - timestamp2tm(GetCurrentTransactionStartTimestamp(), &tz, tm, fsec, - NULL, NULL); - /* Note: don't pass NULL tzp to timestamp2tm; affects behavior */ + *tm = cache_tm; + *fsec = cache_fsec; if (tzp != NULL) - *tzp = tz; + *tzp = cache_tz; } diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c index 26d4fd4ac9de..62decfdbb607 100644 --- a/src/backend/utils/adt/dbsize.c +++ b/src/backend/utils/adt/dbsize.c @@ -831,14 +831,6 @@ numeric_to_cstring(Numeric n) return DatumGetCString(DirectFunctionCall1(numeric_out, d)); } -static Numeric -int64_to_numeric(int64 v) -{ - Datum d = Int64GetDatum(v); - - return DatumGetNumeric(DirectFunctionCall1(int8_numeric, d)); -} - static bool numeric_is_less(Numeric a, Numeric b) { @@ -867,9 +859,9 @@ numeric_half_rounded(Numeric n) Datum two; Datum result; - zero = DirectFunctionCall1(int8_numeric, Int64GetDatum(0)); - one = DirectFunctionCall1(int8_numeric, Int64GetDatum(1)); - two = DirectFunctionCall1(int8_numeric, Int64GetDatum(2)); + zero = NumericGetDatum(int64_to_numeric(0)); + one = NumericGetDatum(int64_to_numeric(1)); + two = NumericGetDatum(int64_to_numeric(2)); if (DatumGetBool(DirectFunctionCall2(numeric_ge, d, zero))) d = DirectFunctionCall2(numeric_add, d, one); @@ -884,12 +876,10 @@ static Numeric numeric_shift_right(Numeric n, unsigned count) { Datum d = NumericGetDatum(n); - Datum divisor_int64; Datum divisor_numeric; Datum result; - divisor_int64 = Int64GetDatum((int64) (1LL << count)); - divisor_numeric = DirectFunctionCall1(int8_numeric, divisor_int64); + divisor_numeric = NumericGetDatum(int64_to_numeric(((int64) 1) << count)); result = DirectFunctionCall2(numeric_div_trunc, d, divisor_numeric); return DatumGetNumeric(result); } @@ -1084,8 +1074,7 @@ pg_size_bytes(PG_FUNCTION_ARGS) { Numeric mul_num; - mul_num = DatumGetNumeric(DirectFunctionCall1(int8_numeric, - Int64GetDatum(multiplier))); + mul_num = int64_to_numeric(multiplier); num = DatumGetNumeric(DirectFunctionCall2(numeric_mul, NumericGetDatum(mul_num), diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index a43cda318722..0da7009d81bb 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -1381,10 +1381,12 @@ parse_format(FormatNode *node, const char *str, const KeyWord *kw, { int chlen; - if (flags & STD_FLAG) + if ((flags & STD_FLAG) && *str != '"') { /* - * Standard mode, allow only following separators: "-./,':; " + * Standard mode, allow only following separators: "-./,':; ". + * However, we support double quotes even in standard mode + * (see below). This is our extension of standard mode. */ if (strchr("-./,':; ", *str) == NULL) ereport(ERROR, @@ -1510,8 +1512,7 @@ static const char * get_th(char *num, int type) { int len = strlen(num), - last, - seclast; + last; last = *(num + (len - 1)); if (!isdigit((unsigned char) last)) @@ -1523,7 +1524,7 @@ get_th(char *num, int type) * All "teens" (1[0-9]) get 'TH/th', while [02-9][123] still get * 'ST/st', 'ND/nd', 'RD/rd', respectively */ - if ((len > 1) && ((seclast = num[len - 2]) == '1')) + if ((len > 1) && (num[len - 2] == '1')) last = 0; switch (last) @@ -3347,7 +3348,19 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, } else { - s += pg_mblen(s); + int chlen = pg_mblen(s); + + /* + * Standard mode requires strict match of format characters. + */ + if (std && n->type == NODE_TYPE_CHAR && + strncmp(s, n->character, chlen) != 0) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("unmatched format character \"%s\"", + n->character)))); + + s += chlen; } continue; } @@ -4567,8 +4580,11 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, { /* If a 4-digit year is provided, we use that and ignore CC. */ tm->tm_year = tmfc.year; - if (tmfc.bc && tm->tm_year > 0) - tm->tm_year = -(tm->tm_year - 1); + if (tmfc.bc) + tm->tm_year = -tm->tm_year; + /* correct for our representation of BC years */ + if (tm->tm_year < 0) + tm->tm_year++; } fmask |= DTK_M(YEAR); } @@ -4943,9 +4959,9 @@ NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree) static char * int_to_roman(int number) { - int len = 0, - num = 0; - char *p = NULL, + int len, + num; + char *p, *result, numstr[12]; @@ -4961,7 +4977,7 @@ int_to_roman(int number) for (p = numstr; *p != '\0'; p++, --len) { - num = *p - 49; /* 48 ascii + 1 */ + num = *p - ('0' + 1); if (num < 0) continue; @@ -6082,10 +6098,8 @@ numeric_to_number(PG_FUNCTION_ARGS) if (IS_MULTI(&Num)) { Numeric x; - Numeric a = DatumGetNumeric(DirectFunctionCall1(int4_numeric, - Int32GetDatum(10))); - Numeric b = DatumGetNumeric(DirectFunctionCall1(int4_numeric, - Int32GetDatum(-Num.multi))); + Numeric a = int64_to_numeric(10); + Numeric b = int64_to_numeric(-Num.multi); x = DatumGetNumeric(DirectFunctionCall2(numeric_power, NumericGetDatum(a), @@ -6129,7 +6143,7 @@ numeric_to_char(PG_FUNCTION_ARGS) x = DatumGetNumeric(DirectFunctionCall2(numeric_round, NumericGetDatum(value), Int32GetDatum(0))); - numstr = orgnum = + numstr = int_to_roman(DatumGetInt32(DirectFunctionCall1(numeric_int4, NumericGetDatum(x)))); } @@ -6174,10 +6188,8 @@ numeric_to_char(PG_FUNCTION_ARGS) if (IS_MULTI(&Num)) { - Numeric a = DatumGetNumeric(DirectFunctionCall1(int4_numeric, - Int32GetDatum(10))); - Numeric b = DatumGetNumeric(DirectFunctionCall1(int4_numeric, - Int32GetDatum(Num.multi))); + Numeric a = int64_to_numeric(10); + Numeric b = int64_to_numeric(Num.multi); x = DatumGetNumeric(DirectFunctionCall2(numeric_power, NumericGetDatum(a), @@ -6250,7 +6262,7 @@ int4_to_char(PG_FUNCTION_ARGS) * On DateType depend part (int32) */ if (IS_ROMAN(&Num)) - numstr = orgnum = int_to_roman(value); + numstr = int_to_roman(value); else if (IS_EEEE(&Num)) { /* we can do it easily because float8 won't lose any precision */ @@ -6346,16 +6358,13 @@ int8_to_char(PG_FUNCTION_ARGS) if (IS_ROMAN(&Num)) { /* Currently don't support int8 conversion to roman... */ - numstr = orgnum = int_to_roman(DatumGetInt32(DirectFunctionCall1(int84, Int64GetDatum(value)))); + numstr = int_to_roman(DatumGetInt32(DirectFunctionCall1(int84, Int64GetDatum(value)))); } else if (IS_EEEE(&Num)) { /* to avoid loss of precision, must go via numeric not float8 */ - Numeric val; - - val = DatumGetNumeric(DirectFunctionCall1(int8_numeric, - Int64GetDatum(value))); - orgnum = numeric_out_sci(val, Num.post); + orgnum = numeric_out_sci(int64_to_numeric(value), + Num.post); /* * numeric_out_sci() does not emit a sign for positive numbers. We @@ -6445,13 +6454,12 @@ float4_to_char(PG_FUNCTION_ARGS) int out_pre_spaces = 0, sign = 0; char *numstr, - *orgnum, *p; NUM_TOCHAR_prepare; if (IS_ROMAN(&Num)) - numstr = orgnum = int_to_roman((int) rint(value)); + numstr = int_to_roman((int) rint(value)); else if (IS_EEEE(&Num)) { if (isnan(value) || isinf(value)) @@ -6467,20 +6475,19 @@ float4_to_char(PG_FUNCTION_ARGS) } else { - numstr = orgnum = psprintf("%+.*e", Num.post, value); + numstr = psprintf("%+.*e", Num.post, value); /* * Swap a leading positive sign for a space. */ - if (*orgnum == '+') - *orgnum = ' '; - - numstr = orgnum; + if (*numstr == '+') + *numstr = ' '; } } else { float4 val = value; + char *orgnum; int numstr_pre_len; if (IS_MULTI(&Num)) @@ -6491,7 +6498,7 @@ float4_to_char(PG_FUNCTION_ARGS) Num.pre += Num.multi; } - orgnum = (char *) psprintf("%.0f", fabs(val)); + orgnum = psprintf("%.0f", fabs(val)); numstr_pre_len = strlen(orgnum); /* adjust post digits to fit max float digits */ @@ -6549,13 +6556,12 @@ float8_to_char(PG_FUNCTION_ARGS) int out_pre_spaces = 0, sign = 0; char *numstr, - *orgnum, *p; NUM_TOCHAR_prepare; if (IS_ROMAN(&Num)) - numstr = orgnum = int_to_roman((int) rint(value)); + numstr = int_to_roman((int) rint(value)); else if (IS_EEEE(&Num)) { if (isnan(value) || isinf(value)) @@ -6571,20 +6577,19 @@ float8_to_char(PG_FUNCTION_ARGS) } else { - numstr = orgnum = (char *) psprintf("%+.*e", Num.post, value); + numstr = psprintf("%+.*e", Num.post, value); /* * Swap a leading positive sign for a space. */ - if (*orgnum == '+') - *orgnum = ' '; - - numstr = orgnum; + if (*numstr == '+') + *numstr = ' '; } } else { float8 val = value; + char *orgnum; int numstr_pre_len; if (IS_MULTI(&Num)) @@ -6594,6 +6599,7 @@ float8_to_char(PG_FUNCTION_ARGS) val = value * multi; Num.pre += Num.multi; } + orgnum = psprintf("%.0f", fabs(val)); numstr_pre_len = strlen(orgnum); diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index a7a91b72f69b..420d3cdcbb9d 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -990,7 +990,7 @@ catenate_stringinfo_string(StringInfo buffer, const char *addon) Datum json_build_object(PG_FUNCTION_ARGS) { - int nargs = PG_NARGS(); + int nargs; int i; const char *sep = ""; StringInfo result; diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index b1a62deea6e8..8e39df8e15aa 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -4689,8 +4689,8 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, rk1, rk2; - r1 = rk1 = JsonbIteratorNext(it1, &v1, false); - r2 = rk2 = JsonbIteratorNext(it2, &v2, false); + rk1 = JsonbIteratorNext(it1, &v1, false); + rk2 = JsonbIteratorNext(it2, &v2, false); /* * Both elements are objects. @@ -4698,15 +4698,15 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, if (rk1 == WJB_BEGIN_OBJECT && rk2 == WJB_BEGIN_OBJECT) { /* - * Append the all tokens from v1 to res, except last WJB_END_OBJECT + * Append all the tokens from v1 to res, except last WJB_END_OBJECT * (because res will not be finished yet). */ - pushJsonbValue(state, r1, NULL); + pushJsonbValue(state, rk1, NULL); while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_OBJECT) pushJsonbValue(state, r1, &v1); /* - * Append the all tokens from v2 to res, include last WJB_END_OBJECT + * Append all the tokens from v2 to res, include last WJB_END_OBJECT * (the concatenation will be completed). */ while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE) @@ -4718,7 +4718,7 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, */ else if (rk1 == WJB_BEGIN_ARRAY && rk2 == WJB_BEGIN_ARRAY) { - pushJsonbValue(state, r1, NULL); + pushJsonbValue(state, rk1, NULL); while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY) { @@ -4738,10 +4738,8 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, else if (((rk1 == WJB_BEGIN_ARRAY && !(*it1)->isScalar) && rk2 == WJB_BEGIN_OBJECT) || (rk1 == WJB_BEGIN_OBJECT && (rk2 == WJB_BEGIN_ARRAY && !(*it2)->isScalar))) { - JsonbIterator **it_array = rk1 == WJB_BEGIN_ARRAY ? it1 : it2; JsonbIterator **it_object = rk1 == WJB_BEGIN_OBJECT ? it1 : it2; - bool prepend = (rk1 == WJB_BEGIN_OBJECT); pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 3c0dc38a7f84..31d9d92d14ed 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -660,7 +660,7 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, else if (v->content.anybounds.first == v->content.anybounds.last) { if (v->content.anybounds.first == PG_UINT32_MAX) - appendStringInfo(buf, "**{last}"); + appendStringInfoString(buf, "**{last}"); else appendStringInfo(buf, "**{%u}", v->content.anybounds.first); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index f146767bfc3a..1059f34130ae 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -35,7 +35,7 @@ * executeItemOptUnwrapTarget() function have 'unwrap' argument, which indicates * whether unwrapping of array is needed. When unwrap == true, each of array * members is passed to executeItemOptUnwrapTarget() again but with unwrap == false - * in order to evade subsequent array unwrapping. + * in order to avoid subsequent array unwrapping. * * All boolean expressions (predicates) are evaluated by executeBoolItem() * function, which returns tri-state JsonPathBool. When error is occurred @@ -842,9 +842,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, lastjbv = hasNext ? &tmpjbv : palloc(sizeof(*lastjbv)); lastjbv->type = jbvNumeric; - lastjbv->val.numeric = - DatumGetNumeric(DirectFunctionCall1(int4_numeric, - Int32GetDatum(last))); + lastjbv->val.numeric = int64_to_numeric(last); res = executeNextItem(cxt, jsp, &elem, lastjbv, found, hasNext); @@ -1012,9 +1010,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, jb = palloc(sizeof(*jb)); jb->type = jbvNumeric; - jb->val.numeric = - DatumGetNumeric(DirectFunctionCall1(int4_numeric, - Int32GetDatum(size))); + jb->val.numeric = int64_to_numeric(size); res = executeNextItem(cxt, jsp, NULL, jb, found, false); } @@ -1837,16 +1833,22 @@ executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, /* * According to SQL/JSON standard enumerate ISO formats for: date, * timetz, time, timestamptz, timestamp. + * + * We also support ISO 8601 for timestamps, because to_json[b]() + * functions use this format. */ static const char *fmt_str[] = { "yyyy-mm-dd", - "HH24:MI:SS TZH:TZM", - "HH24:MI:SS TZH", + "HH24:MI:SSTZH:TZM", + "HH24:MI:SSTZH", "HH24:MI:SS", - "yyyy-mm-dd HH24:MI:SS TZH:TZM", - "yyyy-mm-dd HH24:MI:SS TZH", - "yyyy-mm-dd HH24:MI:SS" + "yyyy-mm-dd HH24:MI:SSTZH:TZM", + "yyyy-mm-dd HH24:MI:SSTZH", + "yyyy-mm-dd HH24:MI:SS", + "yyyy-mm-dd\"T\"HH24:MI:SSTZH:TZM", + "yyyy-mm-dd\"T\"HH24:MI:SSTZH", + "yyyy-mm-dd\"T\"HH24:MI:SS" }; /* cache for format texts */ @@ -1979,8 +1981,7 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, id += (int64) cxt->baseObject.id * INT64CONST(10000000000); idval.type = jbvNumeric; - idval.val.numeric = DatumGetNumeric(DirectFunctionCall1(int8_numeric, - Int64GetDatum(id))); + idval.val.numeric = int64_to_numeric(id); it = JsonbIteratorInit(jbc); @@ -2587,9 +2588,9 @@ checkTimezoneIsUsedForCast(bool useTz, const char *type1, const char *type2) if (!useTz) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot convert value from %s to %s without timezone usage", + errmsg("cannot convert value from %s to %s without time zone usage", type1, type2), - errhint("Use *_tz() function for timezone support."))); + errhint("Use *_tz() function for time zone support."))); } /* Convert time datum to timetz datum */ @@ -2601,93 +2602,36 @@ castTimeToTimeTz(Datum time, bool useTz) return DirectFunctionCall1(time_timetz, time); } -/*--- - * Compares 'ts1' and 'ts2' timestamp, assuming that ts1 might be overflowed - * during cast from another datatype. - * - * 'overflow1' specifies overflow of 'ts1' value: - * 0 - no overflow, - * -1 - exceed lower boundary, - * 1 - exceed upper boundary. - */ -static int -cmpTimestampWithOverflow(Timestamp ts1, int overflow1, Timestamp ts2) -{ - /* - * All the timestamps we deal with in jsonpath are produced by - * to_datetime() method. So, they should be valid. - */ - Assert(IS_VALID_TIMESTAMP(ts2)); - - /* - * Timestamp, which exceed lower (upper) bound, is always lower (higher) - * than any valid timestamp except minus (plus) infinity. - */ - if (overflow1) - { - if (overflow1 < 0) - { - if (TIMESTAMP_IS_NOBEGIN(ts2)) - return 1; - else - return -1; - } - if (overflow1 > 0) - { - if (TIMESTAMP_IS_NOEND(ts2)) - return -1; - else - return 1; - } - } - - return timestamp_cmp_internal(ts1, ts2); -} - /* - * Compare date to timestamptz without throwing overflow error during cast. + * Compare date to timestamp. + * Note that this doesn't involve any timezone considerations. */ static int cmpDateToTimestamp(DateADT date1, Timestamp ts2, bool useTz) { - TimestampTz ts1; - int overflow = 0; - - ts1 = date2timestamp_opt_overflow(date1, &overflow); - - return cmpTimestampWithOverflow(ts1, overflow, ts2); + return date_cmp_timestamp_internal(date1, ts2); } /* - * Compare date to timestamptz without throwing overflow error during cast. + * Compare date to timestamptz. */ static int cmpDateToTimestampTz(DateADT date1, TimestampTz tstz2, bool useTz) { - TimestampTz tstz1; - int overflow = 0; - checkTimezoneIsUsedForCast(useTz, "date", "timestamptz"); - tstz1 = date2timestamptz_opt_overflow(date1, &overflow); - - return cmpTimestampWithOverflow(tstz1, overflow, tstz2); + return date_cmp_timestamptz_internal(date1, tstz2); } /* - * Compare timestamp to timestamptz without throwing overflow error during cast. + * Compare timestamp to timestamptz. */ static int cmpTimestampToTimestampTz(Timestamp ts1, TimestampTz tstz2, bool useTz) { - TimestampTz tstz1; - int overflow = 0; - checkTimezoneIsUsedForCast(useTz, "timestamp", "timestamptz"); - tstz1 = timestamp2timestamptz_opt_overflow(ts1, &overflow); - - return cmpTimestampWithOverflow(tstz1, overflow, tstz2); + return timestamp_cmp_timestamptz_internal(ts1, tstz2); } /* diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 88ef9550e9db..53f422260c38 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -441,7 +441,7 @@ makeItemList(List *list) while (end->next) end = end->next; - for_each_cell(cell, list, list_second_cell(list)) + for_each_from(cell, list, 1) { JsonPathParseItem *c = (JsonPathParseItem *) lfirst(cell); diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c new file mode 100644 index 000000000000..50e1b07ff02c --- /dev/null +++ b/src/backend/utils/adt/mcxtfuncs.c @@ -0,0 +1,157 @@ +/*------------------------------------------------------------------------- + * + * mcxtfuncs.c + * Functions to show backend memory context. + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/mcxtfuncs.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "funcapi.h" +#include "miscadmin.h" +#include "mb/pg_wchar.h" +#include "utils/builtins.h" + +/* ---------- + * The max bytes for showing identifiers of MemoryContext. + * ---------- + */ +#define MEMORY_CONTEXT_IDENT_DISPLAY_SIZE 1024 + +/* + * PutMemoryContextsStatsTupleStore + * One recursion level for pg_get_backend_memory_contexts. + */ +static void +PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore, + TupleDesc tupdesc, MemoryContext context, + const char *parent, int level) +{ +#define PG_GET_BACKEND_MEMORY_CONTEXTS_COLS 9 + + Datum values[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS]; + bool nulls[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS]; + MemoryContextCounters stat; + MemoryContext child; + const char *name; + const char *ident; + + AssertArg(MemoryContextIsValid(context)); + + name = context->name; + ident = context->ident; + + /* + * To be consistent with logging output, we label dynahash contexts + * with just the hash table name as with MemoryContextStatsPrint(). + */ + if (ident && strcmp(name, "dynahash") == 0) + { + name = ident; + ident = NULL; + } + + /* Examine the context itself */ + memset(&stat, 0, sizeof(stat)); + (*context->methods->stats) (context, NULL, (void *) &level, &stat); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + if (name) + values[0] = CStringGetTextDatum(name); + else + nulls[0] = true; + + if (ident) + { + int idlen = strlen(ident); + char clipped_ident[MEMORY_CONTEXT_IDENT_DISPLAY_SIZE]; + + /* + * Some identifiers such as SQL query string can be very long, + * truncate oversize identifiers. + */ + if (idlen >= MEMORY_CONTEXT_IDENT_DISPLAY_SIZE) + idlen = pg_mbcliplen(ident, idlen, MEMORY_CONTEXT_IDENT_DISPLAY_SIZE - 1); + + memcpy(clipped_ident, ident, idlen); + clipped_ident[idlen] = '\0'; + values[1] = CStringGetTextDatum(clipped_ident); + } + else + nulls[1] = true; + + if (parent) + values[2] = CStringGetTextDatum(parent); + else + nulls[2] = true; + + values[3] = Int32GetDatum(level); + values[4] = Int64GetDatum(stat.totalspace); + values[5] = Int64GetDatum(stat.nblocks); + values[6] = Int64GetDatum(stat.freespace); + values[7] = Int64GetDatum(stat.freechunks); + values[8] = Int64GetDatum(stat.totalspace - stat.freespace); + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + + for (child = context->firstchild; child != NULL; child = child->nextchild) + { + PutMemoryContextsStatsTupleStore(tupstore, tupdesc, + child, name, level + 1); + } +} + +/* + * pg_get_backend_memory_contexts + * SQL SRF showing backend memory context. + */ +Datum +pg_get_backend_memory_contexts(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + PutMemoryContextsStatsTupleStore(tupstore, tupdesc, + TopMemoryContext, NULL, 0); + + /* clean up and return the tuplestore */ + tuplestore_donestoring(tupstore); + + return (Datum) 0; +} diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c index dd5335c1c38d..558568a5b7ea 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -429,12 +429,16 @@ pg_get_keywords(PG_FUNCTION_ARGS) funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - tupdesc = CreateTemplateTupleDesc(3); + tupdesc = CreateTemplateTupleDesc(5); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "word", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "catcode", CHAROID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "catdesc", + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "barelabel", + BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "catdesc", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "baredesc", TEXTOID, -1, 0); funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc); @@ -446,7 +450,7 @@ pg_get_keywords(PG_FUNCTION_ARGS) if (funcctx->call_cntr < ScanKeywords.num_keywords) { - char *values[3]; + char *values[5]; HeapTuple tuple; /* cast-away-const is ugly but alternatives aren't much better */ @@ -458,26 +462,37 @@ pg_get_keywords(PG_FUNCTION_ARGS) { case UNRESERVED_KEYWORD: values[1] = "U"; - values[2] = _("unreserved"); + values[3] = _("unreserved"); break; case COL_NAME_KEYWORD: values[1] = "C"; - values[2] = _("unreserved (cannot be function or type name)"); + values[3] = _("unreserved (cannot be function or type name)"); break; case TYPE_FUNC_NAME_KEYWORD: values[1] = "T"; - values[2] = _("reserved (can be function or type name)"); + values[3] = _("reserved (can be function or type name)"); break; case RESERVED_KEYWORD: values[1] = "R"; - values[2] = _("reserved"); + values[3] = _("reserved"); break; default: /* shouldn't be possible */ values[1] = NULL; - values[2] = NULL; + values[3] = NULL; break; } + if (ScanKeywordBareLabel[funcctx->call_cntr]) + { + values[2] = "true"; + values[4] = _("can be bare label"); + } + else + { + values[2] = "false"; + values[4] = _("requires AS"); + } + tuple = BuildTupleFromCStrings(funcctx->attinmeta, values); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index f4472245ae1a..affab61a3b2e 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -634,7 +634,8 @@ static void round_var(NumericVar *var, int rscale); static void trunc_var(NumericVar *var, int rscale); static void strip_var(NumericVar *var); static void compute_bucket(Numeric operand, Numeric bound1, Numeric bound2, - const NumericVar *count_var, NumericVar *result_var); + const NumericVar *count_var, bool reversed_bounds, + NumericVar *result_var); static void accum_sum_add(NumericSumAccum *accum, const NumericVar *var1); static void accum_sum_rescale(NumericSumAccum *accum, const NumericVar *val); @@ -1786,10 +1787,11 @@ width_bucket_numeric(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), errmsg("operand, lower bound, and upper bound cannot be NaN"))); - else + /* We allow "operand" to be infinite; cmp_numerics will cope */ + if (NUMERIC_IS_INF(bound1) || NUMERIC_IS_INF(bound2)) ereport(ERROR, (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), - errmsg("operand, lower bound, and upper bound cannot be infinity"))); + errmsg("lower and upper bounds must be finite"))); } quick_init_var(&result_var); @@ -1813,8 +1815,8 @@ width_bucket_numeric(PG_FUNCTION_ARGS) else if (cmp_numerics(operand, bound2) >= 0) add_var(&count_var, &const_one, &result_var); else - compute_bucket(operand, bound1, bound2, - &count_var, &result_var); + compute_bucket(operand, bound1, bound2, &count_var, false, + &result_var); break; /* bound1 > bound2 */ @@ -1824,8 +1826,8 @@ width_bucket_numeric(PG_FUNCTION_ARGS) else if (cmp_numerics(operand, bound2) <= 0) add_var(&count_var, &const_one, &result_var); else - compute_bucket(operand, bound1, bound2, - &count_var, &result_var); + compute_bucket(operand, bound1, bound2, &count_var, true, + &result_var); break; } @@ -1844,11 +1846,13 @@ width_bucket_numeric(PG_FUNCTION_ARGS) /* * If 'operand' is not outside the bucket range, determine the correct * bucket for it to go. The calculations performed by this function - * are derived directly from the SQL2003 spec. + * are derived directly from the SQL2003 spec. Note however that we + * multiply by count before dividing, to avoid unnecessary roundoff error. */ static void compute_bucket(Numeric operand, Numeric bound1, Numeric bound2, - const NumericVar *count_var, NumericVar *result_var) + const NumericVar *count_var, bool reversed_bounds, + NumericVar *result_var) { NumericVar bound1_var; NumericVar bound2_var; @@ -1858,23 +1862,21 @@ compute_bucket(Numeric operand, Numeric bound1, Numeric bound2, init_var_from_num(bound2, &bound2_var); init_var_from_num(operand, &operand_var); - if (cmp_var(&bound1_var, &bound2_var) < 0) + if (!reversed_bounds) { sub_var(&operand_var, &bound1_var, &operand_var); sub_var(&bound2_var, &bound1_var, &bound2_var); - div_var(&operand_var, &bound2_var, result_var, - select_div_scale(&operand_var, &bound2_var), true); } else { sub_var(&bound1_var, &operand_var, &operand_var); - sub_var(&bound1_var, &bound2_var, &bound1_var); - div_var(&operand_var, &bound1_var, result_var, - select_div_scale(&operand_var, &bound1_var), true); + sub_var(&bound1_var, &bound2_var, &bound2_var); } - mul_var(result_var, count_var, result_var, - result_var->dscale + count_var->dscale); + mul_var(&operand_var, count_var, &operand_var, + operand_var.dscale + count_var->dscale); + div_var(&operand_var, &bound2_var, result_var, + select_div_scale(&operand_var, &bound2_var), true); add_var(result_var, &const_one, result_var); floor_var(result_var, result_var); @@ -4412,23 +4414,29 @@ numeric_trim_scale(PG_FUNCTION_ARGS) * ---------------------------------------------------------------------- */ - -Datum -int4_numeric(PG_FUNCTION_ARGS) +Numeric +int64_to_numeric(int64 val) { - int32 val = PG_GETARG_INT32(0); Numeric res; NumericVar result; quick_init_var(&result); - int64_to_numericvar((int64) val, &result); + int64_to_numericvar(val, &result); res = make_result(&result); free_var(&result); - PG_RETURN_NUMERIC(res); + return res; +} + +Datum +int4_numeric(PG_FUNCTION_ARGS) +{ + int32 val = PG_GETARG_INT32(0); + + PG_RETURN_NUMERIC(int64_to_numeric(val)); } int32 @@ -4513,18 +4521,8 @@ Datum int8_numeric(PG_FUNCTION_ARGS) { int64 val = PG_GETARG_INT64(0); - Numeric res; - NumericVar result; - - quick_init_var(&result); - - int64_to_numericvar(val, &result); - - res = make_result(&result); - free_var(&result); - - PG_RETURN_NUMERIC(res); + PG_RETURN_NUMERIC(int64_to_numeric(val)); } @@ -4563,18 +4561,8 @@ Datum int2_numeric(PG_FUNCTION_ARGS) { int16 val = PG_GETARG_INT16(0); - Numeric res; - NumericVar result; - - quick_init_var(&result); - - int64_to_numericvar((int64) val, &result); - - res = make_result(&result); - - free_var(&result); - PG_RETURN_NUMERIC(res); + PG_RETURN_NUMERIC(int64_to_numeric(val)); } @@ -5653,11 +5641,7 @@ int2_accum(PG_FUNCTION_ARGS) #ifdef HAVE_INT128 do_int128_accum(state, (int128) PG_GETARG_INT16(1)); #else - Numeric newval; - - newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric, - PG_GETARG_DATUM(1))); - do_numeric_accum(state, newval); + do_numeric_accum(state, int64_to_numeric(PG_GETARG_INT16(1))); #endif } @@ -5680,11 +5664,7 @@ int4_accum(PG_FUNCTION_ARGS) #ifdef HAVE_INT128 do_int128_accum(state, (int128) PG_GETARG_INT32(1)); #else - Numeric newval; - - newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric, - PG_GETARG_DATUM(1))); - do_numeric_accum(state, newval); + do_numeric_accum(state, int64_to_numeric(PG_GETARG_INT32(1))); #endif } @@ -5703,13 +5683,7 @@ int8_accum(PG_FUNCTION_ARGS) state = makeNumericAggState(fcinfo, true); if (!PG_ARGISNULL(1)) - { - Numeric newval; - - newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric, - PG_GETARG_DATUM(1))); - do_numeric_accum(state, newval); - } + do_numeric_accum(state, int64_to_numeric(PG_GETARG_INT64(1))); PG_RETURN_POINTER(state); } @@ -5943,11 +5917,7 @@ int8_avg_accum(PG_FUNCTION_ARGS) #ifdef HAVE_INT128 do_int128_accum(state, (int128) PG_GETARG_INT64(1)); #else - Numeric newval; - - newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric, - PG_GETARG_DATUM(1))); - do_numeric_accum(state, newval); + do_numeric_accum(state, int64_to_numeric(PG_GETARG_INT64(1))); #endif } @@ -6150,13 +6120,8 @@ int2_accum_inv(PG_FUNCTION_ARGS) #ifdef HAVE_INT128 do_int128_discard(state, (int128) PG_GETARG_INT16(1)); #else - Numeric newval; - - newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric, - PG_GETARG_DATUM(1))); - /* Should never fail, all inputs have dscale 0 */ - if (!do_numeric_discard(state, newval)) + if (!do_numeric_discard(state, int64_to_numeric(PG_GETARG_INT16(1)))) elog(ERROR, "do_numeric_discard failed unexpectedly"); #endif } @@ -6180,13 +6145,8 @@ int4_accum_inv(PG_FUNCTION_ARGS) #ifdef HAVE_INT128 do_int128_discard(state, (int128) PG_GETARG_INT32(1)); #else - Numeric newval; - - newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric, - PG_GETARG_DATUM(1))); - /* Should never fail, all inputs have dscale 0 */ - if (!do_numeric_discard(state, newval)) + if (!do_numeric_discard(state, int64_to_numeric(PG_GETARG_INT32(1)))) elog(ERROR, "do_numeric_discard failed unexpectedly"); #endif } @@ -6207,13 +6167,8 @@ int8_accum_inv(PG_FUNCTION_ARGS) if (!PG_ARGISNULL(1)) { - Numeric newval; - - newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric, - PG_GETARG_DATUM(1))); - /* Should never fail, all inputs have dscale 0 */ - if (!do_numeric_discard(state, newval)) + if (!do_numeric_discard(state, int64_to_numeric(PG_GETARG_INT64(1)))) elog(ERROR, "do_numeric_discard failed unexpectedly"); } @@ -6236,13 +6191,8 @@ int8_avg_accum_inv(PG_FUNCTION_ARGS) #ifdef HAVE_INT128 do_int128_discard(state, (int128) PG_GETARG_INT64(1)); #else - Numeric newval; - - newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric, - PG_GETARG_DATUM(1))); - /* Should never fail, all inputs have dscale 0 */ - if (!do_numeric_discard(state, newval)) + if (!do_numeric_discard(state, int64_to_numeric(PG_GETARG_INT64(1)))) elog(ERROR, "do_numeric_discard failed unexpectedly"); #endif } @@ -6297,8 +6247,7 @@ numeric_poly_avg(PG_FUNCTION_ARGS) int128_to_numericvar(state->sumX, &result); - countd = DirectFunctionCall1(int8_numeric, - Int64GetDatumFast(state->N)); + countd = NumericGetDatum(int64_to_numeric(state->N)); sumd = NumericGetDatum(make_result(&result)); free_var(&result); @@ -6334,7 +6283,7 @@ numeric_avg(PG_FUNCTION_ARGS) if (state->nInfcount > 0) PG_RETURN_NUMERIC(make_result(&const_ninf)); - N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N)); + N_datum = NumericGetDatum(int64_to_numeric(state->N)); init_var(&sumX_var); accum_sum_final(&state->sumX, &sumX_var); @@ -6794,7 +6743,6 @@ Datum int8_sum(PG_FUNCTION_ARGS) { Numeric oldsum; - Datum newval; if (PG_ARGISNULL(0)) { @@ -6802,8 +6750,7 @@ int8_sum(PG_FUNCTION_ARGS) if (PG_ARGISNULL(1)) PG_RETURN_NULL(); /* still no non-null */ /* This is the first non-null input. */ - newval = DirectFunctionCall1(int8_numeric, PG_GETARG_DATUM(1)); - PG_RETURN_DATUM(newval); + PG_RETURN_NUMERIC(int64_to_numeric(PG_GETARG_INT64(1))); } /* @@ -6819,10 +6766,9 @@ int8_sum(PG_FUNCTION_ARGS) PG_RETURN_NUMERIC(oldsum); /* OK to do the addition. */ - newval = DirectFunctionCall1(int8_numeric, PG_GETARG_DATUM(1)); - PG_RETURN_DATUM(DirectFunctionCall2(numeric_add, - NumericGetDatum(oldsum), newval)); + NumericGetDatum(oldsum), + NumericGetDatum(int64_to_numeric(PG_GETARG_INT64(1))))); } /* @@ -7000,10 +6946,8 @@ int8_avg(PG_FUNCTION_ARGS) if (transdata->count == 0) PG_RETURN_NULL(); - countd = DirectFunctionCall1(int8_numeric, - Int64GetDatumFast(transdata->count)); - sumd = DirectFunctionCall1(int8_numeric, - Int64GetDatumFast(transdata->sum)); + countd = NumericGetDatum(int64_to_numeric(transdata->count)); + sumd = NumericGetDatum(int64_to_numeric(transdata->sum)); PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, sumd, countd)); } @@ -8743,11 +8687,22 @@ mul_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result, * Add the appropriate multiple of var2 into the accumulator. * * As above, digits of var2 can be ignored if they don't contribute, - * so we only include digits for which i1+i2+2 <= res_ndigits - 1. + * so we only include digits for which i1+i2+2 < res_ndigits. + * + * This inner loop is the performance bottleneck for multiplication, + * so we want to keep it simple enough so that it can be + * auto-vectorized. Accordingly, process the digits left-to-right + * even though schoolbook multiplication would suggest right-to-left. + * Since we aren't propagating carries in this loop, the order does + * not matter. */ - for (i2 = Min(var2ndigits - 1, res_ndigits - i1 - 3), i = i1 + i2 + 2; - i2 >= 0; i2--) - dig[i--] += var1digit * var2digits[i2]; + { + int i2limit = Min(var2ndigits, res_ndigits - i1 - 2); + int *dig_i1_2 = &dig[i1 + 2]; + + for (i2 = 0; i2 < i2limit; i2++) + dig_i1_2[i2] += var1digit * var2digits[i2]; + } } /* diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index 2b671801357d..a6d29be5f9a9 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -57,8 +57,9 @@ #include "access/htup_details.h" #include "catalog/pg_collation.h" #include "catalog/pg_control.h" +#include "catalog/pg_database.h" #include "mb/pg_wchar.h" -#include "utils/faultinjector.h" +#include "miscadmin.h" #include "utils/builtins.h" #include "utils/formatting.h" #include "utils/hsearch.h" @@ -67,6 +68,8 @@ #include "utils/pg_locale.h" #include "utils/syscache.h" +#include "utils/faultinjector.h" + #ifdef USE_ICU #include #endif @@ -140,6 +143,9 @@ static char *IsoLocaleName(const char *); /* MSVC specific */ static void icu_set_collation_attributes(UCollator *collator, const char *loc); #endif +static char *get_collation_actual_version(char collprovider, + const char *collcollate); + /* * pg_perm_setlocale * @@ -1518,8 +1524,6 @@ pg_newlocale_from_collation(Oid collid) const char *collctype pg_attribute_unused(); struct pg_locale_struct result; pg_locale_t resultp; - Datum collversion; - bool isnull; tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid)); if (!HeapTupleIsValid(tp)) @@ -1621,41 +1625,6 @@ pg_newlocale_from_collation(Oid collid) #endif /* not USE_ICU */ } - collversion = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collversion, - &isnull); - if (!isnull) - { - char *actual_versionstr; - char *collversionstr; - - actual_versionstr = get_collation_actual_version(collform->collprovider, collcollate); - if (!actual_versionstr) - { - /* - * This could happen when specifying a version in CREATE - * COLLATION for a libc locale, or manually creating a mess in - * the catalogs. - */ - ereport(ERROR, - (errmsg("collation \"%s\" has no actual version, but a version was specified", - NameStr(collform->collname)))); - } - collversionstr = TextDatumGetCString(collversion); - - if (strcmp(actual_versionstr, collversionstr) != 0) - ereport(WARNING, - (errmsg("collation \"%s\" has version mismatch", - NameStr(collform->collname)), - errdetail("The collation in the database was created using version %s, " - "but the operating system provides version %s.", - collversionstr, actual_versionstr), - errhint("Rebuild all objects affected by this collation and run " - "ALTER COLLATION %s REFRESH VERSION, " - "or build PostgreSQL with the right library version.", - quote_qualified_identifier(get_namespace_name(collform->collnamespace), - NameStr(collform->collname))))); - } - ReleaseSysCache(tp); /* We'll keep the pg_locale_t structures in TopMemoryContext */ @@ -1672,7 +1641,7 @@ pg_newlocale_from_collation(Oid collid) * Get provider-specific collation version string for the given collation from * the operating system/library. */ -char * +static char * get_collation_actual_version(char collprovider, const char *collcollate) { char *collversion = NULL; @@ -1739,10 +1708,22 @@ get_collation_actual_version(char collprovider, const char *collcollate) MultiByteToWideChar(CP_ACP, 0, collcollate, -1, wide_collcollate, LOCALE_NAME_MAX_LENGTH); if (!GetNLSVersionEx(COMPARE_STRING, wide_collcollate, &version)) + { + /* + * GetNLSVersionEx() wants a language tag such as "en-US", not a + * locale name like "English_United States.1252". Until those + * values can be prevented from entering the system, or 100% + * reliably converted to the more useful tag format, tolerate the + * resulting error and report that we have no version data. + */ + if (GetLastError() == ERROR_INVALID_PARAMETER) + return NULL; + ereport(ERROR, (errmsg("could not get collation version for locale \"%s\": error code %lu", collcollate, GetLastError()))); + } collversion = psprintf("%d.%d,%d.%d", (version.dwNLSVersion >> 8) & 0xFFFF, version.dwNLSVersion & 0xFF, @@ -1754,6 +1735,45 @@ get_collation_actual_version(char collprovider, const char *collcollate) return collversion; } +/* + * Get provider-specific collation version string for a given collation OID. + * Return NULL if the provider doesn't support versions. + */ +char * +get_collation_version_for_oid(Oid oid) +{ + HeapTuple tp; + char *version = NULL; + + Assert(oid != C_COLLATION_OID && oid != POSIX_COLLATION_OID); + + if (oid == DEFAULT_COLLATION_OID) + { + Form_pg_database dbform; + + tp = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); + dbform = (Form_pg_database) GETSTRUCT(tp); + version = get_collation_actual_version(COLLPROVIDER_LIBC, + NameStr(dbform->datcollate)); + } + else + { + Form_pg_collation collform; + + tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(oid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for collation %u", oid); + collform = (Form_pg_collation) GETSTRUCT(tp); + version = get_collation_actual_version(collform->collprovider, + NameStr(collform->collcollate)); + } + + ReleaseSysCache(tp); + + return version; +} #ifdef USE_ICU /* diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c index 4b7e1b35abfc..18474207eca0 100644 --- a/src/backend/utils/adt/pg_upgrade_support.c +++ b/src/backend/utils/adt/pg_upgrade_support.c @@ -14,6 +14,7 @@ #include "access/transam.h" #include "catalog/binary_upgrade.h" #include "catalog/heap.h" +#include "catalog/index.h" #include "catalog/namespace.h" #include "catalog/oid_dispatch.h" #include "catalog/pg_authid.h" diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index a2c14ab2cf47..67dfd64dc8be 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -1758,6 +1758,42 @@ pg_stat_get_buf_alloc(PG_FUNCTION_ARGS) PG_RETURN_INT64(pgstat_fetch_global()->buf_alloc); } +/* + * Returns statistics of WAL activity + */ +Datum +pg_stat_get_wal(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_WAL_COLS 2 + TupleDesc tupdesc; + Datum values[PG_STAT_GET_WAL_COLS]; + bool nulls[PG_STAT_GET_WAL_COLS]; + PgStat_WalStats *wal_stats; + + /* Initialise values and NULL flags arrays */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls)); + + /* Initialise attributes information in the tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_WAL_COLS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "wal_buffers_full", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "stats_reset", + TIMESTAMPTZOID, -1, 0); + + BlessTupleDesc(tupdesc); + + /* Get statistics about WAL activity */ + wal_stats = pgstat_fetch_stat_wal(); + + /* Fill values and NULLs */ + values[0] = Int64GetDatum(wal_stats->wal_buffers_full); + values[1] = TimestampTzGetDatum(wal_stats->stat_reset_timestamp); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + /* * Returns statistics of SLRU caches. */ @@ -2182,6 +2218,20 @@ pg_stat_reset_slru(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +/* Reset replication slots stats (a specific one or all of them). */ +Datum +pg_stat_reset_replication_slot(PG_FUNCTION_ARGS) +{ + char *target = NULL; + + if (!PG_ARGISNULL(0)) + target = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + pgstat_reset_replslot_counter(target); + + PG_RETURN_VOID(); +} + Datum pg_stat_get_archiver(PG_FUNCTION_ARGS) { @@ -2247,3 +2297,72 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS) /* Returns the record as Datum */ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } + +/* Get the statistics for the replication slots */ +Datum +pg_stat_get_replication_slots(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_REPLICATION_SLOT_COLS 8 + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + PgStat_ReplSlotStats *slotstats; + int nstats; + int i; + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + slotstats = pgstat_fetch_replslot(&nstats); + for (i = 0; i < nstats; i++) + { + Datum values[PG_STAT_GET_REPLICATION_SLOT_COLS]; + bool nulls[PG_STAT_GET_REPLICATION_SLOT_COLS]; + PgStat_ReplSlotStats *s = &(slotstats[i]); + + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls)); + + values[0] = PointerGetDatum(cstring_to_text(s->slotname)); + values[1] = Int64GetDatum(s->spill_txns); + values[2] = Int64GetDatum(s->spill_count); + values[3] = Int64GetDatum(s->spill_bytes); + values[4] = Int64GetDatum(s->stream_txns); + values[5] = Int64GetDatum(s->stream_count); + values[6] = Int64GetDatum(s->stream_bytes); + + if (s->stat_reset_timestamp == 0) + nulls[7] = true; + else + values[7] = TimestampTzGetDatum(s->stat_reset_timestamp); + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + + tuplestore_donestoring(tupstore); + + return (Datum) 0; +} diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 06cf16d9d716..7e2b2e3dd646 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -1663,7 +1663,7 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) appendStringInfo(&querybuf, ") WHERE %s AND (", constraintDef); else - appendStringInfo(&querybuf, ") WHERE ("); + appendStringInfoString(&querybuf, ") WHERE ("); sep = ""; for (i = 0; i < riinfo->nkeys; i++) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 810d93a4529a..0c906a8abe62 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -4771,7 +4771,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, bool is_instead; char *ev_qual; char *ev_action; - List *actions = NIL; + List *actions; Relation ev_relation; TupleDesc viewResultDesc = NULL; int fno; @@ -4801,14 +4801,16 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, Assert(!isnull); is_instead = DatumGetBool(dat); - /* these could be nulls */ fno = SPI_fnumber(rulettc, "ev_qual"); ev_qual = SPI_getvalue(ruletup, rulettc, fno); + Assert(ev_qual != NULL); fno = SPI_fnumber(rulettc, "ev_action"); ev_action = SPI_getvalue(ruletup, rulettc, fno); - if (ev_action != NULL) - actions = (List *) stringToNode(ev_action); + Assert(ev_action != NULL); + actions = (List *) stringToNode(ev_action); + if (actions == NIL) + elog(ERROR, "invalid empty ev_action list"); ev_relation = table_open(ev_class, AccessShareLock); @@ -4858,9 +4860,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, generate_qualified_relation_name(ev_class)); /* If the rule has an event qualification, add it */ - if (ev_qual == NULL) - ev_qual = ""; - if (strlen(ev_qual) > 0 && strcmp(ev_qual, "<>") != 0) + if (strcmp(ev_qual, "<>") != 0) { Node *qual; Query *query; @@ -4932,10 +4932,6 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, } appendStringInfoString(buf, ");"); } - else if (list_length(actions) == 0) - { - appendStringInfoString(buf, "NOTHING;"); - } else { Query *query; @@ -4965,7 +4961,7 @@ make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, bool is_instead; char *ev_qual; char *ev_action; - List *actions = NIL; + List *actions; Relation ev_relation; int fno; Datum dat; @@ -4989,14 +4985,14 @@ make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, Assert(!isnull); is_instead = DatumGetBool(dat); - /* these could be nulls */ fno = SPI_fnumber(rulettc, "ev_qual"); ev_qual = SPI_getvalue(ruletup, rulettc, fno); + Assert(ev_qual != NULL); fno = SPI_fnumber(rulettc, "ev_action"); ev_action = SPI_getvalue(ruletup, rulettc, fno); - if (ev_action != NULL) - actions = (List *) stringToNode(ev_action); + Assert(ev_action != NULL); + actions = (List *) stringToNode(ev_action); if (list_length(actions) != 1) { @@ -5302,7 +5298,7 @@ get_select_query_def(Query *query, deparse_context *context, appendContextKeyword(context, " FETCH FIRST ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); get_rule_expr(query->limitCount, context, false); - appendStringInfo(buf, " ROWS WITH TIES"); + appendStringInfoString(buf, " ROWS WITH TIES"); } else { @@ -8202,7 +8198,7 @@ get_rule_expr(Node *node, deparse_context *context, { BoolExpr *expr = (BoolExpr *) node; Node *first_arg = linitial(expr->args); - ListCell *arg = list_second_cell(expr->args); + ListCell *arg; switch (expr->boolop) { @@ -8211,12 +8207,11 @@ get_rule_expr(Node *node, deparse_context *context, appendStringInfoChar(buf, '('); get_rule_expr_paren(first_arg, context, false, node); - while (arg) + for_each_from(arg, expr->args, 1) { appendStringInfoString(buf, " AND "); get_rule_expr_paren((Node *) lfirst(arg), context, false, node); - arg = lnext(expr->args, arg); } if (!PRETTY_PAREN(context)) appendStringInfoChar(buf, ')'); @@ -8227,12 +8222,11 @@ get_rule_expr(Node *node, deparse_context *context, appendStringInfoChar(buf, '('); get_rule_expr_paren(first_arg, context, false, node); - while (arg) + for_each_from(arg, expr->args, 1) { appendStringInfoString(buf, " OR "); get_rule_expr_paren((Node *) lfirst(arg), context, false, node); - arg = lnext(expr->args, arg); } if (!PRETTY_PAREN(context)) appendStringInfoChar(buf, ')'); @@ -8281,7 +8275,12 @@ get_rule_expr(Node *node, deparse_context *context, AlternativeSubPlan *asplan = (AlternativeSubPlan *) node; ListCell *lc; - /* As above, this can only happen during EXPLAIN */ + /* + * This case cannot be reached in normal usage, since no + * AlternativeSubPlan can appear either in parsetrees or + * finished plan trees. We keep it just in case somebody + * wants to use this code to print planner data structures. + */ appendStringInfoString(buf, "(alternatives: "); foreach(lc, asplan->subplans) { @@ -9504,35 +9503,14 @@ get_oper_expr(OpExpr *expr, deparse_context *context) } else { - /* unary operator --- but which side? */ + /* prefix operator */ Node *arg = (Node *) linitial(args); - HeapTuple tp; - Form_pg_operator optup; - - tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); - if (!HeapTupleIsValid(tp)) - elog(ERROR, "cache lookup failed for operator %u", opno); - optup = (Form_pg_operator) GETSTRUCT(tp); - switch (optup->oprkind) - { - case 'l': - appendStringInfo(buf, "%s ", - generate_operator_name(opno, - InvalidOid, - exprType(arg))); - get_rule_expr_paren(arg, context, true, (Node *) expr); - break; - case 'r': - get_rule_expr_paren(arg, context, true, (Node *) expr); - appendStringInfo(buf, " %s", - generate_operator_name(opno, - exprType(arg), - InvalidOid)); - break; - default: - elog(ERROR, "bogus oprkind: %d", optup->oprkind); - } - ReleaseSysCache(tp); + + appendStringInfo(buf, "%s ", + generate_operator_name(opno, + InvalidOid, + exprType(arg))); + get_rule_expr_paren(arg, context, true, (Node *) expr); } if (!PRETTY_PAREN(context)) appendStringInfoChar(buf, ')'); @@ -10562,7 +10540,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); if (!IsA(rtfunc->funcexpr, FuncExpr) || - ((FuncExpr *) rtfunc->funcexpr)->funcid != F_ARRAY_UNNEST || + ((FuncExpr *) rtfunc->funcexpr)->funcid != F_UNNEST_ANYARRAY || rtfunc->funccolnames != NIL) { all_unnest = false; @@ -11542,10 +11520,6 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2) p_result = left_oper(NULL, list_make1(makeString(oprname)), arg2, true, -1); break; - case 'r': - p_result = right_oper(NULL, list_make1(makeString(oprname)), arg1, - true, -1); - break; default: elog(ERROR, "unrecognized oprkind: %d", operform->oprkind); p_result = NULL; /* keep compiler quiet */ @@ -11933,7 +11907,7 @@ get_range_partbound_string(List *bound_datums) memset(&context, 0, sizeof(deparse_context)); context.buf = buf; - appendStringInfoString(buf, "("); + appendStringInfoChar(buf, '('); sep = ""; foreach(cell, bound_datums) { diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index f90450730ced..c2763e73130f 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -3557,7 +3557,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, * for remaining Vars on other rels. */ relvarinfos = lappend(relvarinfos, varinfo1); - for_each_cell(l, varinfos, list_second_cell(varinfos)) + for_each_from(l, varinfos, 1) { GroupVarInfo *varinfo2 = (GroupVarInfo *) lfirst(l); diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index bd5ca0c65c41..1fbe812b6c48 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -909,17 +909,21 @@ make_timestamp_internal(int year, int month, int day, TimeOffset date; TimeOffset time; int dterr; + bool bc = false; Timestamp result; tm.tm_year = year; tm.tm_mon = month; tm.tm_mday = day; - /* - * Note: we'll reject zero or negative year values. Perhaps negatives - * should be allowed to represent BC years? - */ - dterr = ValidateDate(DTK_DATE_M, false, false, false, &tm); + /* Handle negative years as BC */ + if (tm.tm_year < 0) + { + bc = true; + tm.tm_year = -tm.tm_year; + } + + dterr = ValidateDate(DTK_DATE_M, false, false, bc, &tm); if (dterr != 0) ereport(ERROR, @@ -2505,16 +2509,34 @@ timestamp_hash_extended(PG_FUNCTION_ARGS) * Cross-type comparison functions for timestamp vs timestamptz */ +int32 +timestamp_cmp_timestamptz_internal(Timestamp timestampVal, TimestampTz dt2) +{ + TimestampTz dt1; + int overflow; + + dt1 = timestamp2timestamptz_opt_overflow(timestampVal, &overflow); + if (overflow > 0) + { + /* dt1 is larger than any finite timestamp, but less than infinity */ + return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1; + } + if (overflow < 0) + { + /* dt1 is less than any finite timestamp, but more than -infinity */ + return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1; + } + + return timestamptz_cmp_internal(dt1, dt2); +} + Datum timestamp_eq_timestamptz(PG_FUNCTION_ARGS) { Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - - dt1 = timestamp2timestamptz(timestampVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) == 0); + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) == 0); } Datum @@ -2522,11 +2544,8 @@ timestamp_ne_timestamptz(PG_FUNCTION_ARGS) { Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - - dt1 = timestamp2timestamptz(timestampVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) != 0); + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) != 0); } Datum @@ -2534,11 +2553,8 @@ timestamp_lt_timestamptz(PG_FUNCTION_ARGS) { Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - dt1 = timestamp2timestamptz(timestampVal); - - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) < 0); + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) < 0); } Datum @@ -2546,11 +2562,8 @@ timestamp_gt_timestamptz(PG_FUNCTION_ARGS) { Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - - dt1 = timestamp2timestamptz(timestampVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) > 0); + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) > 0); } Datum @@ -2558,11 +2571,8 @@ timestamp_le_timestamptz(PG_FUNCTION_ARGS) { Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - - dt1 = timestamp2timestamptz(timestampVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) <= 0); + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) <= 0); } Datum @@ -2570,11 +2580,8 @@ timestamp_ge_timestamptz(PG_FUNCTION_ARGS) { Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - dt1 = timestamp2timestamptz(timestampVal); - - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) >= 0); + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) >= 0); } Datum @@ -2582,11 +2589,8 @@ timestamp_cmp_timestamptz(PG_FUNCTION_ARGS) { Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); - TimestampTz dt1; - dt1 = timestamp2timestamptz(timestampVal); - - PG_RETURN_INT32(timestamp_cmp_internal(dt1, dt2)); + PG_RETURN_INT32(timestamp_cmp_timestamptz_internal(timestampVal, dt2)); } Datum @@ -2594,11 +2598,8 @@ timestamptz_eq_timestamp(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); - TimestampTz dt2; - dt2 = timestamp2timestamptz(timestampVal); - - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) == 0); + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) == 0); } Datum @@ -2606,11 +2607,8 @@ timestamptz_ne_timestamp(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); - TimestampTz dt2; - dt2 = timestamp2timestamptz(timestampVal); - - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) != 0); + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) != 0); } Datum @@ -2618,11 +2616,8 @@ timestamptz_lt_timestamp(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); - TimestampTz dt2; - - dt2 = timestamp2timestamptz(timestampVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) < 0); + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) > 0); } Datum @@ -2630,11 +2625,8 @@ timestamptz_gt_timestamp(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); - TimestampTz dt2; - - dt2 = timestamp2timestamptz(timestampVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) > 0); + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) < 0); } Datum @@ -2642,11 +2634,8 @@ timestamptz_le_timestamp(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); - TimestampTz dt2; - - dt2 = timestamp2timestamptz(timestampVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) <= 0); + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) >= 0); } Datum @@ -2654,11 +2643,8 @@ timestamptz_ge_timestamp(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); - TimestampTz dt2; - - dt2 = timestamp2timestamptz(timestampVal); - PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) >= 0); + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) <= 0); } Datum @@ -2666,11 +2652,8 @@ timestamptz_cmp_timestamp(PG_FUNCTION_ARGS) { TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); - TimestampTz dt2; - dt2 = timestamp2timestamptz(timestampVal); - - PG_RETURN_INT32(timestamp_cmp_internal(dt1, dt2)); + PG_RETURN_INT32(-timestamp_cmp_timestamptz_internal(timestampVal, dt1)); } @@ -5832,9 +5815,12 @@ timestamp_timestamptz(PG_FUNCTION_ARGS) /* * Convert timestamp to timestamp with time zone. * - * On overflow error is thrown if 'overflow' is NULL. Otherwise, '*overflow' - * is set to -1 (+1) when result value exceed lower (upper) boundary and zero - * returned. + * On successful conversion, *overflow is set to zero if it's not NULL. + * + * If the timestamp is finite but out of the valid range for timestamptz, then: + * if overflow is NULL, we throw an out-of-range error. + * if overflow is not NULL, we store +1 or -1 there to indicate the sign + * of the overflow, and return the appropriate timestamptz infinity. */ TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow) @@ -5845,10 +5831,14 @@ timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow) fsec_t fsec = 0; int tz; + if (overflow) + *overflow = 0; + if (TIMESTAMP_NOT_FINITE(timestamp)) return timestamp; - if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL)) + /* We don't expect this to fail, but check it pro forma */ + if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) == 0) { tz = DetermineTimeZoneOffset(tm, session_timezone); @@ -5861,13 +5851,16 @@ timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow) else if (overflow) { if (result < MIN_TIMESTAMP) + { *overflow = -1; + TIMESTAMP_NOBEGIN(result); + } else { - Assert(result >= END_TIMESTAMP); *overflow = 1; + TIMESTAMP_NOEND(result); } - return (TimestampTz) 0; + return result; } } @@ -5879,7 +5872,7 @@ timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow) } /* - * Single-argument version of timestamp2timestamptz_opt_overflow(). + * Promote timestamp to timestamptz, throwing error for overflow. */ static TimestampTz timestamp2timestamptz(Timestamp timestamp) diff --git a/src/backend/utils/adt/tsrank.c b/src/backend/utils/adt/tsrank.c index c88ebfc7d411..38b413f6ffff 100644 --- a/src/backend/utils/adt/tsrank.c +++ b/src/backend/utils/adt/tsrank.c @@ -857,8 +857,7 @@ calc_rank_cd(const float4 *arrdata, TSVector txt, TSQuery query, int method) double Wdoc = 0.0; double invws[lengthof(weights)]; double SumDist = 0.0, - PrevExtPos = 0.0, - CurExtPos = 0.0; + PrevExtPos = 0.0; int NExtent = 0; QueryRepresentation qr; @@ -889,6 +888,7 @@ calc_rank_cd(const float4 *arrdata, TSVector txt, TSQuery query, int method) { double Cpos = 0.0; double InvSum = 0.0; + double CurExtPos; int nNoise; DocRepresentation *ptr = ext.begin; diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c index 0f63a3532cbc..de7b1555cd90 100644 --- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -26,6 +26,7 @@ #include "lib/hyperloglog.h" #include "libpq/pqformat.h" #include "miscadmin.h" +#include "nodes/execnodes.h" #include "parser/scansup.h" #include "port/pg_bswap.h" #include "regex/regex.h" @@ -92,6 +93,17 @@ typedef struct pg_locale_t locale; } VarStringSortSupport; +/* + * Output data for split_text(): we output either to an array or a table. + * tupstore and tupdesc must be set up in advance to output to a table. + */ +typedef struct +{ + ArrayBuildState *astate; + Tuplestorestate *tupstore; + TupleDesc tupdesc; +} SplitTextOutputData; + /* * This should be large enough that most strings will fit, but small enough * that we feel comfortable putting it on the stack @@ -139,7 +151,11 @@ static bytea *bytea_substring(Datum str, bool length_not_specified); static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl); static void appendStringInfoText(StringInfo str, const text *t); -static Datum text_to_array_internal(PG_FUNCTION_ARGS); +static bool split_text(FunctionCallInfo fcinfo, SplitTextOutputData *tstate); +static void split_text_accum_result(SplitTextOutputData *tstate, + text *field_value, + text *null_string, + Oid collation); static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v, const char *fldsep, const char *null_string); static StringInfo makeStringAggState(FunctionCallInfo fcinfo); @@ -4564,13 +4580,13 @@ replace_text_regexp(text *src_text, void *regexp, } /* - * split_text + * split_part * parse input string * return ord item (1 based) * based on provided field separator */ Datum -split_text(PG_FUNCTION_ARGS) +split_part(PG_FUNCTION_ARGS) { text *inputstring = PG_GETARG_TEXT_PP(0); text *fldsep = PG_GETARG_TEXT_PP(1); @@ -4599,7 +4615,6 @@ split_text(PG_FUNCTION_ARGS) /* empty field separator */ if (fldsep_len < 1) { - text_position_cleanup(&state); /* if first field, return input string, else empty string */ if (fldnum == 1) PG_RETURN_TEXT_P(inputstring); @@ -4679,7 +4694,19 @@ text_isequal(text *txt1, text *txt2, Oid collid) Datum text_to_array(PG_FUNCTION_ARGS) { - return text_to_array_internal(fcinfo); + SplitTextOutputData tstate; + + /* For array output, tstate should start as all zeroes */ + memset(&tstate, 0, sizeof(tstate)); + + if (!split_text(fcinfo, &tstate)) + PG_RETURN_NULL(); + + if (tstate.astate == NULL) + PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID)); + + PG_RETURN_ARRAYTYPE_P(makeArrayResult(tstate.astate, + CurrentMemoryContext)); } /* @@ -4693,30 +4720,90 @@ text_to_array(PG_FUNCTION_ARGS) Datum text_to_array_null(PG_FUNCTION_ARGS) { - return text_to_array_internal(fcinfo); + return text_to_array(fcinfo); +} + +/* + * text_to_table + * parse input string and return table of elements, + * based on provided field separator + */ +Datum +text_to_table(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; + SplitTextOutputData tstate; + MemoryContext old_cxt; + + /* check to see if caller supports us returning a tuplestore */ + if (rsi == NULL || !IsA(rsi, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsi->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + /* OK, prepare tuplestore in per-query memory */ + old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory); + + tstate.astate = NULL; + tstate.tupdesc = CreateTupleDescCopy(rsi->expectedDesc); + tstate.tupstore = tuplestore_begin_heap(true, false, work_mem); + + MemoryContextSwitchTo(old_cxt); + + (void) split_text(fcinfo, &tstate); + + tuplestore_donestoring(tstate.tupstore); + + rsi->returnMode = SFRM_Materialize; + rsi->setResult = tstate.tupstore; + rsi->setDesc = tstate.tupdesc; + + return (Datum) 0; +} + +/* + * text_to_table_null + * parse input string and return table of elements, + * based on provided field separator and null string + * + * This is a separate entry point only to prevent the regression tests from + * complaining about different argument sets for the same internal function. + */ +Datum +text_to_table_null(PG_FUNCTION_ARGS) +{ + return text_to_table(fcinfo); } /* - * common code for text_to_array and text_to_array_null functions + * Common code for text_to_array, text_to_array_null, text_to_table + * and text_to_table_null functions. * * These are not strict so we have to test for null inputs explicitly. + * Returns false if result is to be null, else returns true. + * + * Note that if the result is valid but empty (zero elements), we return + * without changing *tstate --- caller must handle that case, too. */ -static Datum -text_to_array_internal(PG_FUNCTION_ARGS) +static bool +split_text(FunctionCallInfo fcinfo, SplitTextOutputData *tstate) { text *inputstring; text *fldsep; text *null_string; + Oid collation = PG_GET_COLLATION(); int inputstring_len; int fldsep_len; char *start_ptr; text *result_text; - bool is_null; - ArrayBuildState *astate = NULL; /* when input string is NULL, then result is NULL too */ if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); + return false; inputstring = PG_GETARG_TEXT_PP(0); @@ -4743,35 +4830,19 @@ text_to_array_internal(PG_FUNCTION_ARGS) inputstring_len = VARSIZE_ANY_EXHDR(inputstring); fldsep_len = VARSIZE_ANY_EXHDR(fldsep); - /* return empty array for empty input string */ + /* return empty set for empty input string */ if (inputstring_len < 1) - PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID)); + return true; - /* - * empty field separator: return the input string as a one-element - * array - */ + /* empty field separator: return input string as a one-element set */ if (fldsep_len < 1) { - Datum elems[1]; - bool nulls[1]; - int dims[1]; - int lbs[1]; - - /* single element can be a NULL too */ - is_null = null_string ? text_isequal(inputstring, null_string, PG_GET_COLLATION()) : false; - - elems[0] = PointerGetDatum(inputstring); - nulls[0] = is_null; - dims[0] = 1; - lbs[0] = 1; - /* XXX: this hardcodes assumptions about the text type */ - PG_RETURN_ARRAYTYPE_P(construct_md_array(elems, nulls, - 1, dims, lbs, - TEXTOID, -1, false, TYPALIGN_INT)); + split_text_accum_result(tstate, inputstring, + null_string, collation); + return true; } - text_position_setup(inputstring, fldsep, PG_GET_COLLATION(), &state); + text_position_setup(inputstring, fldsep, collation, &state); start_ptr = VARDATA_ANY(inputstring); @@ -4797,16 +4868,12 @@ text_to_array_internal(PG_FUNCTION_ARGS) chunk_len = end_ptr - start_ptr; } - /* must build a temp text datum to pass to accumArrayResult */ + /* build a temp text datum to pass to split_text_accum_result */ result_text = cstring_to_text_with_len(start_ptr, chunk_len); - is_null = null_string ? text_isequal(result_text, null_string, PG_GET_COLLATION()) : false; /* stash away this field */ - astate = accumArrayResult(astate, - PointerGetDatum(result_text), - is_null, - TEXTOID, - CurrentMemoryContext); + split_text_accum_result(tstate, result_text, + null_string, collation); pfree(result_text); @@ -4821,16 +4888,12 @@ text_to_array_internal(PG_FUNCTION_ARGS) else { /* - * When fldsep is NULL, each character in the inputstring becomes an - * element in the result array. The separator is effectively the - * space between characters. + * When fldsep is NULL, each character in the input string becomes a + * separate element in the result set. The separator is effectively + * the space between characters. */ inputstring_len = VARSIZE_ANY_EXHDR(inputstring); - /* return empty array for empty input string */ - if (inputstring_len < 1) - PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID)); - start_ptr = VARDATA_ANY(inputstring); while (inputstring_len > 0) @@ -4839,16 +4902,12 @@ text_to_array_internal(PG_FUNCTION_ARGS) CHECK_FOR_INTERRUPTS(); - /* must build a temp text datum to pass to accumArrayResult */ + /* build a temp text datum to pass to split_text_accum_result */ result_text = cstring_to_text_with_len(start_ptr, chunk_len); - is_null = null_string ? text_isequal(result_text, null_string, PG_GET_COLLATION()) : false; /* stash away this field */ - astate = accumArrayResult(astate, - PointerGetDatum(result_text), - is_null, - TEXTOID, - CurrentMemoryContext); + split_text_accum_result(tstate, result_text, + null_string, collation); pfree(result_text); @@ -4857,8 +4916,47 @@ text_to_array_internal(PG_FUNCTION_ARGS) } } - PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, - CurrentMemoryContext)); + return true; +} + +/* + * Add text item to result set (table or array). + * + * This is also responsible for checking to see if the item matches + * the null_string, in which case we should emit NULL instead. + */ +static void +split_text_accum_result(SplitTextOutputData *tstate, + text *field_value, + text *null_string, + Oid collation) +{ + bool is_null = false; + + if (null_string && text_isequal(field_value, null_string, collation)) + is_null = true; + + if (tstate->tupstore) + { + Datum values[1]; + bool nulls[1]; + + values[0] = PointerGetDatum(field_value); + nulls[0] = is_null; + + tuplestore_putvalues(tstate->tupstore, + tstate->tupdesc, + values, + nulls); + } + else + { + tstate->astate = accumArrayResult(tstate->astate, + PointerGetDatum(field_value), + is_null, + TEXTOID, + CurrentMemoryContext); + } } /* @@ -6082,7 +6180,7 @@ unicode_normalize_func(PG_FUNCTION_ARGS) /* * Check whether the string is in the specified Unicode normalization form. * - * This is done by convering the string to the specified normal form and then + * This is done by converting the string to the specified normal form and then * comparing that to the original string. To speed that up, we also apply the * "quick check" algorithm specified in UAX #15, which can give a yes or no * answer for many strings by just scanning the string once. diff --git a/src/backend/utils/adt/xid.c b/src/backend/utils/adt/xid.c index 20389aff1d12..a4762014ba1f 100644 --- a/src/backend/utils/adt/xid.c +++ b/src/backend/utils/adt/xid.c @@ -23,9 +23,6 @@ #include "utils/builtins.h" #include "utils/xid8.h" -#define PG_GETARG_TRANSACTIONID(n) DatumGetTransactionId(PG_GETARG_DATUM(n)) -#define PG_RETURN_TRANSACTIONID(x) return TransactionIdGetDatum(x) - #define PG_GETARG_COMMANDID(n) DatumGetCommandId(PG_GETARG_DATUM(n)) #define PG_RETURN_COMMANDID(x) return CommandIdGetDatum(x) diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index ea3796ebd1d5..ebe14314580b 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -1024,7 +1024,7 @@ get_attnum(Oid relid, const char *attname) /* * get_attgenerated * - * Given the relation id and the attribute name, + * Given the relation id and the attribute number, * return the "attgenerated" field from the attribute relation. * * Errors if not found. diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index f79f3bd7b180..97a063516b5f 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -44,6 +44,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "catalog/catalog.h" +#include "catalog/index.h" #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/partition.h" @@ -1286,14 +1287,6 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) if (insertIt) RelationCacheInsert(relation, true); - /* - * For RelationNeedsWAL() to answer correctly on parallel workers, restore - * rd_firstRelfilenodeSubid. No subtransactions start or end while in - * parallel mode, so the specific SubTransactionId does not matter. - */ - if (IsParallelWorker() && RelFileNodeSkippingWAL(relation->rd_node)) - relation->rd_firstRelfilenodeSubid = TopSubTransactionId; - /* It's fully valid */ relation->rd_isvalid = true; @@ -1316,6 +1309,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) static void RelationInitPhysicalAddr(Relation relation) { + Oid oldnode = relation->rd_node.relNode; + /* these relations kinds never have storage */ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind)) return; @@ -1373,6 +1368,19 @@ RelationInitPhysicalAddr(Relation relation) elog(ERROR, "could not find relation mapping for relation \"%s\", OID %u", RelationGetRelationName(relation), relation->rd_id); } + + /* + * For RelationNeedsWAL() to answer correctly on parallel workers, restore + * rd_firstRelfilenodeSubid. No subtransactions start or end while in + * parallel mode, so the specific SubTransactionId does not matter. + */ + if (IsParallelWorker() && oldnode != relation->rd_node.relNode) + { + if (RelFileNodeSkippingWAL(relation->rd_node)) + relation->rd_firstRelfilenodeSubid = TopSubTransactionId; + else + relation->rd_firstRelfilenodeSubid = InvalidSubTransactionId; + } } /* @@ -1797,7 +1805,7 @@ RelationInitTableAccessMethod(Relation relation) * seem prudent to show that in the catalog. So just overwrite it * here. */ - relation->rd_amhandler = HEAP_TABLE_AM_HANDLER_OID; + relation->rd_amhandler = F_HEAP_TABLEAM_HANDLER; } else if (IsCatalogRelation(relation)) { @@ -1805,7 +1813,7 @@ RelationInitTableAccessMethod(Relation relation) * Avoid doing a syscache lookup for catalog tables. */ Assert(relation->rd_rel->relam == HEAP_TABLE_AM_OID); - relation->rd_amhandler = HEAP_TABLE_AM_HANDLER_OID; + relation->rd_amhandler = F_HEAP_TABLEAM_HANDLER; } else { @@ -1919,7 +1927,7 @@ formrdesc(const char *relationName, Oid relationReltype, relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING; relation->rd_rel->relpages = 0; - relation->rd_rel->reltuples = 0; + relation->rd_rel->reltuples = -1; relation->rd_rel->relallvisible = 0; relation->rd_rel->relkind = RELKIND_RELATION; relation->rd_rel->relnatts = (int16) natts; @@ -3841,7 +3849,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence) if (relation->rd_rel->relkind != RELKIND_SEQUENCE) { classform->relpages = 0; /* it's empty until further notice */ - classform->reltuples = 0; + classform->reltuples = -1; classform->relallvisible = 0; } classform->relfrozenxid = freezeXid; @@ -6121,6 +6129,7 @@ load_relcache_init_file(bool shared) rel->rd_idattr = NULL; rel->rd_pubactions = NULL; rel->rd_statvalid = false; + rel->rd_version_checked = false; rel->rd_statlist = NIL; rel->rd_fkeyvalid = false; rel->rd_fkeylist = NIL; diff --git a/src/backend/utils/error/assert.c b/src/backend/utils/error/assert.c index dc0805c6d4c7..c4aa7e51780f 100644 --- a/src/backend/utils/error/assert.c +++ b/src/backend/utils/error/assert.c @@ -1,7 +1,7 @@ /*------------------------------------------------------------------------- * * assert.c - * Assert code. + * Assert support code. * * Portions Copyright (c) 2005-2009, Greenplum inc * Portions Copyright (c) 2012-Present VMware, Inc. or its affiliates. @@ -12,9 +12,6 @@ * IDENTIFICATION * src/backend/utils/error/assert.c * - * NOTE - * This should eventually work with elog() - * *------------------------------------------------------------------------- */ #include "postgres.h" @@ -29,6 +26,10 @@ /* * ExceptionalCondition - Handles the failure of an Assert() + * + * We intentionally do not go through elog() here, on the grounds of + * wanting to minimize the amount of infrastructure that has to be + * working to report an assertion failure. */ void ExceptionalCondition(const char *conditionName, @@ -42,17 +43,20 @@ ExceptionalCondition(const char *conditionName, || !PointerIsValid(errorType)) ereport(FATAL, errFatalReturn(gp_reraise_signal), - errmsg("TRAP: ExceptionalCondition: bad arguments")); + errmsg("TRAP: ExceptionalCondition: bad arguments in PID %d"), + (int) getpid()); else ereport(FATAL, errFatalReturn(gp_reraise_signal), errmsg("Unexpected internal error"), - errdetail("%s(\"%s\", File: \"%s\", Line: %d)\n", - errorType, conditionName, fileName, lineNumber)); + errdetail("%s(\"%s\", File: \"%s\", Line: %d, PID: %d)\n", + errorType, conditionName, fileName, lineNumber, + (int) getpid())); /* Usually this shouldn't be needed, but make sure the msg went out */ fflush(stderr); + /* If we have support for it, dump a simple backtrace */ #ifdef HAVE_BACKTRACE_SYMBOLS { void *buf[100]; @@ -63,12 +67,12 @@ ExceptionalCondition(const char *conditionName, } #endif -#ifdef SLEEP_ON_ASSERT - /* - * It would be nice to use pg_usleep() here, but only does 2000 sec or 33 - * minutes, which seems too short. + * If configured to do so, sleep indefinitely to allow user to attach a + * debugger. It would be nice to use pg_usleep() here, but that can sleep + * at most 2G usec or ~33 minutes, which seems too short. */ +#ifdef SLEEP_ON_ASSERT sleep(1000000); #endif diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 96170dc38330..91e8facf4b13 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -941,10 +941,7 @@ errcode_for_socket_access(void) switch (edata->saved_errno) { /* Loss of connection */ - case EPIPE: -#ifdef ECONNRESET - case ECONNRESET: -#endif + case ALL_CONNECTION_FAILURE_ERRNOS: edata->sqlerrcode = ERRCODE_CONNECTION_FAILURE; break; diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 05d87da41e9e..14a8994770e5 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -2009,7 +2009,7 @@ get_fn_opclass_options(FmgrInfo *flinfo) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("opclass options info is absent in function call context"))); + errmsg("operator class options info is absent in function call context"))); return NULL; } diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index 1a0d9320fdec..ecf0d54af759 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -1269,7 +1269,8 @@ get_func_trftypes(HeapTuple procTup, * are set to NULL. You don't get anything if proargnames is NULL. */ int -get_func_input_arg_names(Datum proargnames, Datum proargmodes, +get_func_input_arg_names(char prokind, + Datum proargnames, Datum proargmodes, char ***arg_names) { ArrayType *arr; @@ -1327,6 +1328,7 @@ get_func_input_arg_names(Datum proargnames, Datum proargmodes, if (argmodes == NULL || argmodes[i] == PROARGMODE_IN || argmodes[i] == PROARGMODE_INOUT || + (argmodes[i] == PROARGMODE_OUT && prokind == PROKIND_PROCEDURE) || argmodes[i] == PROARGMODE_VARIADIC) { char *pname = TextDatumGetCString(argnames[i]); diff --git a/src/backend/utils/generate-errcodes.pl b/src/backend/utils/generate-errcodes.pl index 868a163578d7..1a071fbb1f43 100644 --- a/src/backend/utils/generate-errcodes.pl +++ b/src/backend/utils/generate-errcodes.pl @@ -3,8 +3,8 @@ # Generate the errcodes.h header from errcodes.txt # Copyright (c) 2000-2020, PostgreSQL Global Development Group -use warnings; use strict; +use warnings; print "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n"; diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c index 3b3570f2a35d..ca0ec55567d7 100644 --- a/src/backend/utils/hash/dynahash.c +++ b/src/backend/utils/hash/dynahash.c @@ -122,7 +122,6 @@ #define DEF_SEGSIZE 256 #define DEF_SEGSIZE_SHIFT 8 /* must be log2(DEF_SEGSIZE) */ #define DEF_DIRSIZE 256 -#define DEF_FFACTOR 1 /* default fill factor */ /* Number of freelists to be used for a partitioned hash table. */ #define NUM_FREELISTS 32 @@ -191,7 +190,6 @@ struct HASHHDR Size keysize; /* hash key length in bytes */ Size entrysize; /* total user element size in bytes */ long num_partitions; /* # partitions (must be power of 2), or 0 */ - long ffactor; /* target fill factor */ long max_dsize; /* 'dsize' limit if directory is fixed size */ long ssize; /* segment size --- must be power of 2 */ int sshift; /* segment shift = log2(ssize) */ @@ -497,8 +495,6 @@ hash_create(const char *tabname, long nelem, HASHCTL *info, int flags) /* ssize had better be a power of 2 */ Assert(hctl->ssize == (1L << hctl->sshift)); } - if (flags & HASH_FFACTOR) - hctl->ffactor = info->ffactor; /* * SHM hash tables have fixed directory size passed by the caller. @@ -603,8 +599,6 @@ hdefault(HTAB *hashp) hctl->num_partitions = 0; /* not partitioned */ - hctl->ffactor = DEF_FFACTOR; - /* table has no fixed maximum size */ hctl->max_dsize = NO_MAX_DSIZE; @@ -670,11 +664,10 @@ init_htab(HTAB *hashp, long nelem) SpinLockInit(&(hctl->freeList[i].mutex)); /* - * Divide number of elements by the fill factor to determine a desired - * number of buckets. Allocate space for the next greater power of two - * number of buckets + * Allocate space for the next greater power of two number of buckets, + * assuming a desired maximum load factor of 1. */ - nbuckets = next_pow2_int((nelem - 1) / hctl->ffactor + 1); + nbuckets = next_pow2_int(nelem); /* * In a partitioned table, nbuckets must be at least equal to @@ -733,7 +726,6 @@ init_htab(HTAB *hashp, long nelem) "DIRECTORY SIZE ", hctl->dsize, "SEGMENT SIZE ", hctl->ssize, "SEGMENT SHIFT ", hctl->sshift, - "FILL FACTOR ", hctl->ffactor, "MAX BUCKET ", hctl->max_bucket, "HIGH MASK ", hctl->high_mask, "LOW MASK ", hctl->low_mask, @@ -761,7 +753,7 @@ hash_estimate_size(long num_entries, Size entrysize) elementAllocCnt; /* estimate number of buckets wanted */ - nBuckets = next_pow2_long((num_entries - 1) / DEF_FFACTOR + 1); + nBuckets = next_pow2_long(num_entries); /* # of segments needed for nBuckets */ nSegments = next_pow2_long((nBuckets - 1) / DEF_SEGSIZE + 1); /* directory entries */ @@ -804,7 +796,7 @@ hash_select_dirsize(long num_entries) nDirEntries; /* estimate number of buckets wanted */ - nBuckets = next_pow2_long((num_entries - 1) / DEF_FFACTOR + 1); + nBuckets = next_pow2_long(num_entries); /* # of segments needed for nBuckets */ nSegments = next_pow2_long((nBuckets - 1) / DEF_SEGSIZE + 1); /* directory entries */ @@ -971,11 +963,10 @@ hash_search_with_hash_value(HTAB *hashp, { /* * Can't split if running in partitioned mode, nor if frozen, nor if - * table is the subject of any active hash_seq_search scans. Strange - * order of these tests is to try to check cheaper conditions first. + * table is the subject of any active hash_seq_search scans. */ - if (!IS_PARTITIONED(hctl) && !hashp->frozen && - hctl->freeList[0].nentries / (long) (hctl->max_bucket + 1) >= hctl->ffactor && + if (hctl->freeList[0].nentries > (long) hctl->max_bucket && + !IS_PARTITIONED(hctl) && !hashp->frozen && !has_seq_scans(hashp)) (void) expand_table(hashp); } diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 50d6054cebc0..935a69630aa5 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -32,11 +32,12 @@ #include "catalog/pg_authid.h" #include "common/file_perm.h" #include "libpq/libpq.h" +#include "libpq/pqsignal.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "pgstat.h" #include "postmaster/autovacuum.h" -#include "postmaster/fts.h" +#include "postmaster/interrupt.h" #include "postmaster/postmaster.h" #include "postmaster/startup.h" #include "replication/walsender.h" @@ -48,8 +49,6 @@ #include "storage/proc.h" #include "storage/procarray.h" #include "utils/builtins.h" -#include "utils/faultinjector.h" -#include "utils/gdd.h" #include "utils/guc.h" #include "utils/inval.h" #include "utils/memutils.h" @@ -58,6 +57,9 @@ #include "utils/varlena.h" #include "cdb/cdbvars.h" +#include "postmaster/fts.h" +#include "utils/faultinjector.h" +#include "utils/gdd.h" #include "utils/resgroup.h" #include "utils/resource_manager.h" #include "utils/resscheduler.h" @@ -143,6 +145,23 @@ InitPostmasterChild(void) elog(FATAL, "setsid() failed: %m"); #endif + /* In EXEC_BACKEND case we will not have inherited BlockSig etc values */ +#ifdef EXEC_BACKEND + pqinitmask(); +#endif + + /* + * Every postmaster child process is expected to respond promptly to + * SIGQUIT at all times. Therefore we centrally remove SIGQUIT from + * BlockSig and install a suitable signal handler. (Client-facing + * processes may choose to replace this default choice of handler with + * quickdie().) All other blockable signals remain blocked for now. + */ + pqsignal(SIGQUIT, SignalHandlerForCrashExit); + + sigdelset(&BlockSig, SIGQUIT); + PG_SETMASK(&BlockSig); + /* Request a signal if the postmaster dies, if possible. */ PostmasterDeathSignalInit(); } @@ -165,6 +184,13 @@ InitStandaloneProcess(const char *argv0) InitLatch(MyLatch); InitializeLatchWaitSet(); + /* + * For consistency with InitPostmasterChild, initialize signal mask here. + * But we don't unblock SIGQUIT or provide a default handler for it. + */ + pqinitmask(); + PG_SETMASK(&BlockSig); + /* Compute paths, no postmaster to inherit from */ if (my_exec_path[0] == '\0') { diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l index b1eb66677ef6..c3cbbd9e4bbe 100644 --- a/src/backend/utils/misc/guc-file.l +++ b/src/backend/utils/misc/guc-file.l @@ -70,7 +70,6 @@ static void record_config_file_error(const char *errmsg, ConfigVariable **tail_p); static int GUC_flex_fatal(const char *msg); -static char *GUC_scanstr(const char *s); /* LCOV_EXCL_START */ @@ -816,7 +815,7 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel, token != GUC_UNQUOTED_STRING) goto parse_error; if (token == GUC_STRING) /* strip quotes and escapes */ - opt_value = GUC_scanstr(yytext); + opt_value = DeescapeQuotedString(yytext); else opt_value = pstrdup(yytext); @@ -1151,22 +1150,25 @@ FreeConfigVariable(ConfigVariable *item) /* - * scanstr + * DeescapeQuotedString * * Strip the quotes surrounding the given string, and collapse any embedded * '' sequences and backslash escapes. * - * the string returned is palloc'd and should eventually be pfree'd by the + * The string returned is palloc'd and should eventually be pfree'd by the * caller. + * + * This is exported because it is also used by the bootstrap scanner. */ -static char * -GUC_scanstr(const char *s) +char * +DeescapeQuotedString(const char *s) { char *newStr; int len, i, j; + /* We just Assert that there are leading and trailing quotes */ Assert(s != NULL && s[0] == '\''); len = strlen(s); Assert(len >= 2); diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 2c5c3f50f633..d821e8817e19 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -2893,7 +2893,7 @@ static struct config_int ConfigureNamesInt[] = gettext_noop("Sets the minimum execution time above which " "a sample of statements will be logged." " Sampling is determined by log_statement_sample_rate."), - gettext_noop("Zero log a sample of all queries. -1 turns this feature off."), + gettext_noop("Zero logs a sample of all queries. -1 turns this feature off."), GUC_UNIT_MS }, &log_min_duration_sample, @@ -3198,7 +3198,7 @@ static struct config_int ConfigureNamesInt[] = }, { {"autovacuum_vacuum_insert_threshold", PGC_SIGHUP, AUTOVACUUM, - gettext_noop("Minimum number of tuple inserts prior to vacuum, or -1 to disable insert vacuums"), + gettext_noop("Minimum number of tuple inserts prior to vacuum, or -1 to disable insert vacuums."), NULL }, &autovacuum_vac_ins_thresh, @@ -3770,7 +3770,7 @@ static struct config_string ConfigureNamesString[] = { {"restore_command", PGC_POSTMASTER, WAL_ARCHIVE_RECOVERY, - gettext_noop("Sets the shell command that will retrieve an archived WAL file."), + gettext_noop("Sets the shell command that will be called to retrieve an archived WAL file."), NULL }, &recoveryRestoreCommand, @@ -7484,6 +7484,10 @@ set_config_option(const char *name, const char *value, if (prohibitValueChange) { + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + if (*conf->variable != newval) { record->status |= GUC_PENDING_RESTART; @@ -7574,6 +7578,10 @@ set_config_option(const char *name, const char *value, if (prohibitValueChange) { + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + if (*conf->variable != newval) { record->status |= GUC_PENDING_RESTART; @@ -7664,6 +7672,10 @@ set_config_option(const char *name, const char *value, if (prohibitValueChange) { + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + if (*conf->variable != newval) { record->status |= GUC_PENDING_RESTART; @@ -7770,9 +7782,21 @@ set_config_option(const char *name, const char *value, if (prohibitValueChange) { + bool newval_different; + /* newval shouldn't be NULL, so we're a bit sloppy here */ - if (*conf->variable == NULL || newval == NULL || - strcmp(*conf->variable, newval) != 0) + newval_different = (*conf->variable == NULL || + newval == NULL || + strcmp(*conf->variable, newval) != 0); + + /* Release newval, unless it's reset_val */ + if (newval && !string_field_used(conf, newval)) + free(newval); + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + + if (newval_different) { record->status |= GUC_PENDING_RESTART; ereport(elevel, @@ -7867,6 +7891,10 @@ set_config_option(const char *name, const char *value, if (prohibitValueChange) { + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); + if (*conf->variable != newval) { record->status |= GUC_PENDING_RESTART; diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c index 609231275893..d50d87a6021c 100644 --- a/src/backend/utils/misc/pg_controldata.c +++ b/src/backend/utils/misc/pg_controldata.c @@ -94,9 +94,9 @@ pg_control_checkpoint(PG_FUNCTION_ARGS) */ tupdesc = CreateTemplateTupleDesc(18); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "checkpoint_lsn", - LSNOID, -1, 0); + PG_LSNOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "redo_lsn", - LSNOID, -1, 0); + PG_LSNOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 3, "redo_wal_file", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 4, "timeline_id", @@ -223,13 +223,13 @@ pg_control_recovery(PG_FUNCTION_ARGS) */ tupdesc = CreateTemplateTupleDesc(5); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "min_recovery_end_lsn", - LSNOID, -1, 0); + PG_LSNOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "min_recovery_end_timeline", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 3, "backup_start_lsn", - LSNOID, -1, 0); + PG_LSNOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 4, "backup_end_lsn", - LSNOID, -1, 0); + PG_LSNOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 5, "end_of_backup_record_required", BOOLOID, -1, 0); tupdesc = BlessTupleDesc(tupdesc); diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index 056989c062a8..64e32b430d38 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -23,10 +23,8 @@ #include "postgres.h" -#include "funcapi.h" #include "mb/pg_wchar.h" #include "miscadmin.h" -#include "utils/builtins.h" #include "utils/memdebug.h" #include "utils/memutils.h" @@ -94,11 +92,6 @@ static void MemoryContextStatsPrint(MemoryContext context, void *passthru, #define AssertNotInCriticalSection(context) #endif -/* ---------- - * The max bytes for showing identifiers of MemoryContext. - * ---------- - */ -#define MEMORY_CONTEXT_IDENT_DISPLAY_SIZE 1024 /***************************************************************************** * EXPORTED ROUTINES * @@ -644,7 +637,7 @@ MemoryContextMemAllocated(MemoryContext context, bool recurse) if (recurse) { - MemoryContext child = context->firstchild; + MemoryContext child; for (child = context->firstchild; child != NULL; @@ -1460,133 +1453,3 @@ pchomp(const char *in) n--; return pnstrdup(in, n); } - -/* - * PutMemoryContextsStatsTupleStore - * One recursion level for pg_get_backend_memory_contexts. - */ -static void -PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore, - TupleDesc tupdesc, MemoryContext context, - const char *parent, int level) -{ -#define PG_GET_BACKEND_MEMORY_CONTEXTS_COLS 9 - - Datum values[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS]; - bool nulls[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS]; - MemoryContextCounters stat; - MemoryContext child; - const char *name; - const char *ident; - - AssertArg(MemoryContextIsValid(context)); - - name = context->name; - ident = context->ident; - - /* - * To be consistent with logging output, we label dynahash contexts - * with just the hash table name as with MemoryContextStatsPrint(). - */ - if (ident && strcmp(name, "dynahash") == 0) - { - name = ident; - ident = NULL; - } - - /* Examine the context itself */ - memset(&stat, 0, sizeof(stat)); - (*context->methods->stats) (context, NULL, (void *) &level, &stat); - - memset(values, 0, sizeof(values)); - memset(nulls, 0, sizeof(nulls)); - - if (name) - values[0] = CStringGetTextDatum(name); - else - nulls[0] = true; - - if (ident) - { - int idlen = strlen(ident); - char clipped_ident[MEMORY_CONTEXT_IDENT_DISPLAY_SIZE]; - - /* - * Some identifiers such as SQL query string can be very long, - * truncate oversize identifiers. - */ - if (idlen >= MEMORY_CONTEXT_IDENT_DISPLAY_SIZE) - idlen = pg_mbcliplen(ident, idlen, MEMORY_CONTEXT_IDENT_DISPLAY_SIZE - 1); - - memcpy(clipped_ident, ident, idlen); - clipped_ident[idlen] = '\0'; - values[1] = CStringGetTextDatum(clipped_ident); - } - else - nulls[1] = true; - - if (parent) - values[2] = CStringGetTextDatum(parent); - else - nulls[2] = true; - - values[3] = Int32GetDatum(level); - values[4] = Int64GetDatum(stat.totalspace); - values[5] = Int64GetDatum(stat.nblocks); - values[6] = Int64GetDatum(stat.freespace); - values[7] = Int64GetDatum(stat.freechunks); - values[8] = Int64GetDatum(stat.totalspace - stat.freespace); - tuplestore_putvalues(tupstore, tupdesc, values, nulls); - - for (child = context->firstchild; child != NULL; child = child->nextchild) - { - PutMemoryContextsStatsTupleStore(tupstore, tupdesc, - child, name, level + 1); - } -} - -/* - * pg_get_backend_memory_contexts - * SQL SRF showing backend memory context. - */ -Datum -pg_get_backend_memory_contexts(PG_FUNCTION_ARGS) -{ - ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - TupleDesc tupdesc; - Tuplestorestate *tupstore; - MemoryContext per_query_ctx; - MemoryContext oldcontext; - - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - - per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; - oldcontext = MemoryContextSwitchTo(per_query_ctx); - - tupstore = tuplestore_begin_heap(true, false, work_mem); - rsinfo->returnMode = SFRM_Materialize; - rsinfo->setResult = tupstore; - rsinfo->setDesc = tupdesc; - - MemoryContextSwitchTo(oldcontext); - - PutMemoryContextsStatsTupleStore(tupstore, tupdesc, - TopMemoryContext, NULL, 0); - - /* clean up and return the tuplestore */ - tuplestore_donestoring(tupstore); - - return (Datum) 0; -} diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index c9a73deb0feb..1f9af5ca1c68 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -240,8 +240,8 @@ CreatePortal(const char *name, bool allowDup, bool dupSilent) /* put portal in table (sets portal->name) */ PortalHashTableInsert(portal, name); - /* reuse portal->name copy */ - MemoryContextSetIdentifier(portal->portalContext, portal->name); + /* for named portals reuse portal->name copy */ + MemoryContextSetIdentifier(portal->portalContext, portal->name[0] ? portal->name : ""); return portal; } diff --git a/src/backend/utils/sort/gen_qsort_tuple.pl b/src/backend/utils/sort/gen_qsort_tuple.pl index eb0f7c5814f4..4c305806c7ce 100644 --- a/src/backend/utils/sort/gen_qsort_tuple.pl +++ b/src/backend/utils/sort/gen_qsort_tuple.pl @@ -115,7 +115,8 @@ sub emit_qsort_boilerplate { do { - SortTuple t = *a; + SortTuple t = *a; + *a++ = *b; *b++ = t; } while (--n > 0); @@ -143,9 +144,9 @@ sub emit_qsort_implementation { return cmp_$SUFFIX(a, b$CMPPARAMS) < 0 ? (cmp_$SUFFIX(b, c$CMPPARAMS) < 0 ? b : - (cmp_$SUFFIX(a, c$CMPPARAMS) < 0 ? c : a)) + (cmp_$SUFFIX(a, c$CMPPARAMS) < 0 ? c : a)) : (cmp_$SUFFIX(b, c$CMPPARAMS) > 0 ? b : - (cmp_$SUFFIX(a, c$CMPPARAMS) < 0 ? a : c)); + (cmp_$SUFFIX(a, c$CMPPARAMS) < 0 ? a : c)); } static void diff --git a/src/backend/utils/sort/logtape.c b/src/backend/utils/sort/logtape.c index 37589c59e089..0718352b37cd 100644 --- a/src/backend/utils/sort/logtape.c +++ b/src/backend/utils/sort/logtape.c @@ -78,12 +78,17 @@ #include "postgres.h" +#include + #include "storage/buffile.h" #include "utils/builtins.h" #include "utils/logtape.h" #include "utils/memdebug.h" #include "utils/memutils.h" +/* GPDB */ +#include "miscadmin.h" + /* * A TapeBlockTrailer is stored at the end of each BLCKSZ block. * @@ -210,6 +215,7 @@ struct LogicalTapeSet long *freeBlocks; /* resizable array holding minheap */ long nFreeBlocks; /* # of currently free blocks */ Size freeBlocksLen; /* current allocated length of freeBlocks[] */ + bool enable_prealloc; /* preallocate write blocks? */ /* The array of logical tapes. */ int nTapes; /* # of logical tapes in set */ @@ -218,6 +224,7 @@ struct LogicalTapeSet static void ltsWriteBlock(LogicalTapeSet *lts, long blocknum, void *buffer); static void ltsReadBlock(LogicalTapeSet *lts, long blocknum, void *buffer); +static long ltsGetBlock(LogicalTapeSet *lts, LogicalTape *lt); static long ltsGetFreeBlock(LogicalTapeSet *lts); static long ltsGetPreallocBlock(LogicalTapeSet *lts, LogicalTape *lt); static void ltsReleaseBlock(LogicalTapeSet *lts, long blocknum); @@ -245,12 +252,8 @@ ltsWriteBlock(LogicalTapeSet *lts, long blocknum, void *buffer) * that's past the current end of file, fill the space between the current * end of file and the target block with zeros. * - * This should happen rarely, otherwise you are not writing very - * sequentially. In current use, this only happens when the sort ends - * writing a run, and switches to another tape. The last block of the - * previous tape isn't flushed to disk until the end of the sort, so you - * get one-block hole, where the last block of the previous tape will - * later go. + * This can happen either when tapes preallocate blocks; or for the last + * block of a tape which might not have been flushed. * * Note that BufFile concatenation can leave "holes" in BufFile between * worker-owned block ranges. These are tracked for reporting purposes @@ -376,8 +379,20 @@ parent_offset(unsigned long i) } /* - * Select the lowest currently unused block by taking the first element from - * the freelist min heap. + * Get the next block for writing. + */ +static long +ltsGetBlock(LogicalTapeSet *lts, LogicalTape *lt) +{ + if (lts->enable_prealloc) + return ltsGetPreallocBlock(lts, lt); + else + return ltsGetFreeBlock(lts); +} + +/* + * Select the lowest currently unused block from the tape set's global free + * list min heap. */ static long ltsGetFreeBlock(LogicalTapeSet *lts) @@ -433,7 +448,8 @@ ltsGetFreeBlock(LogicalTapeSet *lts) /* * Return the lowest free block number from the tape's preallocation list. - * Refill the preallocation list if necessary. + * Refill the preallocation list with blocks from the tape set's free list if + * necessary. */ static long ltsGetPreallocBlock(LogicalTapeSet *lts, LogicalTape *lt) @@ -494,7 +510,7 @@ ltsReleaseBlock(LogicalTapeSet *lts, long blocknum) * If the freelist becomes very large, just return and leak this free * block. */ - if (lts->freeBlocksLen * 2 > MaxAllocSize) + if (lts->freeBlocksLen * 2 * sizeof(long) > MaxAllocSize) return; lts->freeBlocksLen *= 2; @@ -556,7 +572,7 @@ ltsConcatWorkerTapes(LogicalTapeSet *lts, TapeShare *shared, lt = <s->tapes[i]; pg_itoa(i, filename); - file = BufFileOpenShared(fileset, filename); + file = BufFileOpenShared(fileset, filename, O_RDONLY); filesize = BufFileSize(file); /* @@ -674,8 +690,8 @@ ltsInitReadBuffer(LogicalTapeSet *lts, LogicalTape *lt) * infrastructure that may be lifted in the future. */ LogicalTapeSet * -LogicalTapeSetCreate(int ntapes, TapeShare *shared, SharedFileSet *fileset, - int worker) +LogicalTapeSetCreate(int ntapes, bool preallocate, TapeShare *shared, + SharedFileSet *fileset, int worker) { LogicalTapeSet *lts; int i; @@ -692,6 +708,7 @@ LogicalTapeSetCreate(int ntapes, TapeShare *shared, SharedFileSet *fileset, lts->freeBlocksLen = 32; /* reasonable initial guess */ lts->freeBlocks = (long *) palloc(lts->freeBlocksLen * sizeof(long)); lts->nFreeBlocks = 0; + lts->enable_prealloc = preallocate; lts->nTapes = ntapes; lts->tapes = (LogicalTape *) palloc(ntapes * sizeof(LogicalTape)); @@ -789,7 +806,7 @@ LogicalTapeWrite(LogicalTapeSet *lts, int tapenum, Assert(lt->firstBlockNumber == -1); Assert(lt->pos == 0); - lt->curBlockNumber = ltsGetPreallocBlock(lts, lt); + lt->curBlockNumber = ltsGetBlock(lts, lt); lt->firstBlockNumber = lt->curBlockNumber; TapeBlockGetTrailer(lt->buffer)->prev = -1L; @@ -813,7 +830,7 @@ LogicalTapeWrite(LogicalTapeSet *lts, int tapenum, * First allocate the next block, so that we can store it in the * 'next' pointer of this block. */ - nextBlockNumber = ltsGetPreallocBlock(lts, lt); + nextBlockNumber = ltsGetBlock(lts, lt); /* set the next-pointer and dump the current block. */ TapeBlockGetTrailer(lt->buffer)->next = nextBlockNumber; @@ -1259,9 +1276,26 @@ LogicalTapeTell(LogicalTapeSet *lts, int tapenum, /* * Obtain total disk space currently used by a LogicalTapeSet, in blocks. + * + * This should not be called while there are open write buffers; otherwise it + * may not account for buffered data. */ long LogicalTapeSetBlocks(LogicalTapeSet *lts) { - return lts->nBlocksAllocated - lts->nHoleBlocks; +#ifdef USE_ASSERT_CHECKING + /* + * GPDB interrupts the sort and set QueryFinishPending on purpose in the + * test query_finish_pending.sql, skipping the assertion for that case. + */ + if (!QueryFinishPending) + { + for (int i = 0; i < lts->nTapes; i++) + { + LogicalTape *lt = <s->tapes[i]; + Assert(!lt->writing || lt->buffer == NULL); + } + } +#endif + return lts->nBlocksWritten - lts->nHoleBlocks; } diff --git a/src/backend/utils/sort/sharedtuplestore.c b/src/backend/utils/sort/sharedtuplestore.c index f018cf015b88..dc968e9c0d8b 100644 --- a/src/backend/utils/sort/sharedtuplestore.c +++ b/src/backend/utils/sort/sharedtuplestore.c @@ -561,14 +561,14 @@ sts_parallel_scan_next(SharedTuplestoreAccessor *accessor, void *meta_data) sts_filename(name, accessor, accessor->read_participant); accessor->read_file = - BufFileOpenShared(accessor->fileset, name); + BufFileOpenShared(accessor->fileset, name, O_RDONLY); } /* Seek and load the chunk header. */ if (BufFileSeekBlock(accessor->read_file, read_page) != 0) ereport(ERROR, (errcode_for_file_access(), - errmsg("could not seek block %u in shared tuplestore temporary file", + errmsg("could not seek to block %u in shared tuplestore temporary file", read_page))); nread = BufFileRead(accessor->read_file, &chunk_header, STS_CHUNK_HEADER_SIZE); diff --git a/src/backend/utils/sort/sortsupport.c b/src/backend/utils/sort/sortsupport.c index fcfe6e831a19..c436fbb4ce1e 100644 --- a/src/backend/utils/sort/sortsupport.c +++ b/src/backend/utils/sort/sortsupport.c @@ -15,6 +15,7 @@ #include "postgres.h" +#include "access/gist.h" #include "access/nbtree.h" #include "catalog/pg_am.h" #include "fmgr.h" @@ -175,3 +176,36 @@ PrepareSortSupportFromIndexRel(Relation indexRel, int16 strategy, FinishSortSupportFunction(opfamily, opcintype, ssup); } + +/* + * Fill in SortSupport given a GiST index relation + * + * Caller must previously have zeroed the SortSupportData structure and then + * filled in ssup_cxt, ssup_attno, ssup_collation, and ssup_nulls_first. This + * will fill in ssup_reverse (always false for GiST index build), as well as + * the comparator function pointer. + */ +void +PrepareSortSupportFromGistIndexRel(Relation indexRel, SortSupport ssup) +{ + Oid opfamily = indexRel->rd_opfamily[ssup->ssup_attno - 1]; + Oid opcintype = indexRel->rd_opcintype[ssup->ssup_attno - 1]; + Oid sortSupportFunction; + + Assert(ssup->comparator == NULL); + + if (indexRel->rd_rel->relam != GIST_AM_OID) + elog(ERROR, "unexpected non-gist AM: %u", indexRel->rd_rel->relam); + ssup->ssup_reverse = false; + + /* + * Look up the sort support function. This is simpler than for B-tree + * indexes because we don't support the old-style btree comparators. + */ + sortSupportFunction = get_opfamily_proc(opfamily, opcintype, opcintype, + GIST_SORTSUPPORT_PROC); + if (!OidIsValid(sortSupportFunction)) + elog(ERROR, "missing support function %d(%u,%u) in opfamily %u", + GIST_SORTSUPPORT_PROC, opcintype, opcintype, opfamily); + OidFunctionCall1(sortSupportFunction, PointerGetDatum(ssup)); +} diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c index 47b56710e331..bcabb38e0727 100644 --- a/src/backend/utils/sort/tuplesort.c +++ b/src/backend/utils/sort/tuplesort.c @@ -1180,6 +1180,63 @@ tuplesort_begin_index_hash(Relation heapRel, return state; } +Tuplesortstate * +tuplesort_begin_index_gist(Relation heapRel, + Relation indexRel, + int workMem, + SortCoordinate coordinate, + bool randomAccess) +{ + Tuplesortstate *state = tuplesort_begin_common(workMem, coordinate, + randomAccess); + MemoryContext oldcontext; + int i; + + oldcontext = MemoryContextSwitchTo(state->sortcontext); + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "begin index sort: workMem = %d, randomAccess = %c", + workMem, randomAccess ? 't' : 'f'); +#endif + + state->nKeys = IndexRelationGetNumberOfKeyAttributes(indexRel); + + state->comparetup = comparetup_index_btree; + state->copytup = copytup_index; + state->writetup = writetup_index; + state->readtup = readtup_index; + + state->heapRel = heapRel; + state->indexRel = indexRel; + + /* Prepare SortSupport data for each column */ + state->sortKeys = (SortSupport) palloc0(state->nKeys * + sizeof(SortSupportData)); + + for (i = 0; i < state->nKeys; i++) + { + SortSupport sortKey = state->sortKeys + i; + + sortKey->ssup_cxt = CurrentMemoryContext; + sortKey->ssup_collation = indexRel->rd_indcollation[i]; + sortKey->ssup_nulls_first = false; + sortKey->ssup_attno = i + 1; + /* Convey if abbreviation optimization is applicable in principle */ + sortKey->abbreviate = (i == 0); + + AssertState(sortKey->ssup_attno != 0); + + /* Look for a sort support function */ + PrepareSortSupportFromGistIndexRel(indexRel, sortKey); + } + + MemoryContextSwitchTo(oldcontext); + + return state; +} + Tuplesortstate * tuplesort_begin_datum(Oid datumType, Oid sortOperator, Oid sortCollation, bool nullsFirstFlag, int workMem, @@ -2637,7 +2694,7 @@ inittapes(Tuplesortstate *state, bool mergeruns) /* Create the tape set and allocate the per-tape data arrays */ inittapestate(state, maxTapes); state->tapeset = - LogicalTapeSetCreate(maxTapes, NULL, + LogicalTapeSetCreate(maxTapes, false, NULL, state->shared ? &state->shared->fileset : NULL, state->worker); @@ -4785,8 +4842,9 @@ leader_takeover_tapes(Tuplesortstate *state) * randomAccess is disallowed for parallel sorts. */ inittapestate(state, nParticipants + 1); - state->tapeset = LogicalTapeSetCreate(nParticipants + 1, shared->tapes, - &shared->fileset, state->worker); + state->tapeset = LogicalTapeSetCreate(nParticipants + 1, false, + shared->tapes, &shared->fileset, + state->worker); /* mergeruns() relies on currentRun for # of runs (in one-pass cases) */ state->currentRun = nParticipants; diff --git a/src/backend/utils/sort/tuplestore.c b/src/backend/utils/sort/tuplestore.c index 62715b4be1ad..852aac08fb03 100644 --- a/src/backend/utils/sort/tuplestore.c +++ b/src/backend/utils/sort/tuplestore.c @@ -1774,7 +1774,7 @@ tuplestore_open_shared(SharedFileSet *fileset, const char *filename) state->writetup = writetup_forbidden; state->readtup = readtup_heap; - state->myfile = BufFileOpenShared(fileset, filename); + state->myfile = BufFileOpenShared(fileset, filename, O_RDONLY); state->readptrs[0].file = 0; state->readptrs[0].offset = 0L; state->status = TSS_READFILE; diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index 1264caa2d7db..d1b88c504448 100644 --- a/src/backend/utils/time/snapmgr.c +++ b/src/backend/utils/time/snapmgr.c @@ -64,6 +64,7 @@ #include "storage/spin.h" #include "utils/builtins.h" #include "utils/memutils.h" +#include "utils/old_snapshot.h" #include "utils/rel.h" #include "utils/resowner_private.h" #include "utils/snapmgr.h" @@ -82,59 +83,7 @@ */ int old_snapshot_threshold; /* number of minutes, -1 disables */ -/* - * Structure for dealing with old_snapshot_threshold implementation. - */ -typedef struct OldSnapshotControlData -{ - /* - * Variables for old snapshot handling are shared among processes and are - * only allowed to move forward. - */ - slock_t mutex_current; /* protect current_timestamp */ - TimestampTz current_timestamp; /* latest snapshot timestamp */ - slock_t mutex_latest_xmin; /* protect latest_xmin and next_map_update */ - TransactionId latest_xmin; /* latest snapshot xmin */ - TimestampTz next_map_update; /* latest snapshot valid up to */ - slock_t mutex_threshold; /* protect threshold fields */ - TimestampTz threshold_timestamp; /* earlier snapshot is old */ - TransactionId threshold_xid; /* earlier xid may be gone */ - - /* - * Keep one xid per minute for old snapshot error handling. - * - * Use a circular buffer with a head offset, a count of entries currently - * used, and a timestamp corresponding to the xid at the head offset. A - * count_used value of zero means that there are no times stored; a - * count_used value of OLD_SNAPSHOT_TIME_MAP_ENTRIES means that the buffer - * is full and the head must be advanced to add new entries. Use - * timestamps aligned to minute boundaries, since that seems less - * surprising than aligning based on the first usage timestamp. The - * latest bucket is effectively stored within latest_xmin. The circular - * buffer is updated when we get a new xmin value that doesn't fall into - * the same interval. - * - * It is OK if the xid for a given time slot is from earlier than - * calculated by adding the number of minutes corresponding to the - * (possibly wrapped) distance from the head offset to the time of the - * head entry, since that just results in the vacuuming of old tuples - * being slightly less aggressive. It would not be OK for it to be off in - * the other direction, since it might result in vacuuming tuples that are - * still expected to be there. - * - * Use of an SLRU was considered but not chosen because it is more - * heavyweight than is needed for this, and would probably not be any less - * code to implement. - * - * Persistence is not needed. - */ - int head_offset; /* subscript of oldest tracked time */ - TimestampTz head_timestamp; /* time corresponding to head xid */ - int count_used; /* how many slots are in use */ - TransactionId xid_by_minute[FLEXIBLE_ARRAY_MEMBER]; -} OldSnapshotControlData; - -static volatile OldSnapshotControlData *oldSnapshotControl; +volatile OldSnapshotControlData *oldSnapshotControl; /* @@ -2045,7 +1994,7 @@ TransactionIdLimitedForOldSnapshots(TransactionId recentXmin, if (ts == threshold_timestamp) { /* - * Current timestamp is in same bucket as the the last limit that + * Current timestamp is in same bucket as the last limit that * was applied. Reuse. */ xlimit = threshold_xid; @@ -2190,10 +2139,32 @@ MaintainOldSnapshotTimeMapping(TimestampTz whenTaken, TransactionId xmin) else { /* We need a new bucket, but it might not be the very next one. */ - int advance = ((ts - oldSnapshotControl->head_timestamp) - / USECS_PER_MINUTE); + int distance_to_new_tail; + int distance_to_current_tail; + int advance; - oldSnapshotControl->head_timestamp = ts; + /* + * Our goal is for the new "tail" of the mapping, that is, the entry + * which is newest and thus furthest from the "head" entry, to + * correspond to "ts". Since there's one entry per minute, the + * distance between the current head and the new tail is just the + * number of minutes of difference between ts and the current + * head_timestamp. + * + * The distance from the current head to the current tail is one + * less than the number of entries in the mapping, because the + * entry at the head_offset is for 0 minutes after head_timestamp. + * + * The difference between these two values is the number of minutes + * by which we need to advance the mapping, either adding new entries + * or rotating old ones out. + */ + distance_to_new_tail = + (ts - oldSnapshotControl->head_timestamp) / USECS_PER_MINUTE; + distance_to_current_tail = + oldSnapshotControl->count_used - 1; + advance = distance_to_new_tail - distance_to_current_tail; + Assert(advance > 0); if (advance >= OLD_SNAPSHOT_TIME_MAP_ENTRIES) { @@ -2201,6 +2172,7 @@ MaintainOldSnapshotTimeMapping(TimestampTz whenTaken, TransactionId xmin) oldSnapshotControl->head_offset = 0; oldSnapshotControl->count_used = 1; oldSnapshotControl->xid_by_minute[0] = xmin; + oldSnapshotControl->head_timestamp = ts; } else { @@ -2219,6 +2191,7 @@ MaintainOldSnapshotTimeMapping(TimestampTz whenTaken, TransactionId xmin) else oldSnapshotControl->head_offset = old_head + 1; oldSnapshotControl->xid_by_minute[old_head] = xmin; + oldSnapshotControl->head_timestamp += USECS_PER_MINUTE; } else { diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 6088713dd609..1cf6b3b98309 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -67,6 +67,7 @@ #include "common/file_utils.h" #include "common/logging.h" #include "common/restricted_token.h" +#include "common/string.h" #include "common/username.h" #include "fe_utils/string_utils.h" #include "getaddrinfo.h" @@ -339,12 +340,9 @@ escape_quotes(const char *src) /* * Escape a field value to be inserted into the BKI data. - * Here, we first run the value through escape_quotes (which - * will be inverted by the backend's scanstr() function) and - * then overlay special processing of double quotes, which - * bootscanner.l will only accept as data if converted to octal - * representation ("\042"). We always wrap the value in double - * quotes, even if that isn't strictly necessary. + * Run the value through escape_quotes (which will be inverted + * by the backend's DeescapeQuotedString() function), then wrap + * the value in single quotes, even if that isn't strictly necessary. */ static char * escape_quotes_bki(const char *src) @@ -353,30 +351,13 @@ escape_quotes_bki(const char *src) char *data = escape_quotes(src); char *resultp; char *datap; - int nquotes = 0; - /* count double quotes in data */ - datap = data; - while ((datap = strchr(datap, '"')) != NULL) - { - nquotes++; - datap++; - } - - result = (char *) pg_malloc(strlen(data) + 3 + nquotes * 3); + result = (char *) pg_malloc(strlen(data) + 3); resultp = result; - *resultp++ = '"'; + *resultp++ = '\''; for (datap = data; *datap; datap++) - { - if (*datap == '"') - { - strcpy(resultp, "\\042"); - resultp += 4; - } - else - *resultp++ = *datap; - } - *resultp++ = '"'; + *resultp++ = *datap; + *resultp++ = '\''; *resultp = '\0'; free(data); @@ -476,14 +457,11 @@ filter_lines_with_token(char **lines, const char *token) static char ** readfile(const char *path) { + char **result; FILE *infile; - int maxlength = 1, - linelen = 0; - int nlines = 0; + StringInfoData line; + int maxlines; int n; - char **result; - char *buffer; - int c; if ((infile = fopen(path, "r")) == NULL) { @@ -491,39 +469,28 @@ readfile(const char *path) exit(1); } - /* pass over the file twice - the first time to size the result */ + initStringInfo(&line); + + maxlines = 1024; + result = (char **) pg_malloc(maxlines * sizeof(char *)); - while ((c = fgetc(infile)) != EOF) + n = 0; + while (pg_get_line_buf(infile, &line)) { - linelen++; - if (c == '\n') + /* make sure there will be room for a trailing NULL pointer */ + if (n >= maxlines - 1) { - nlines++; - if (linelen > maxlength) - maxlength = linelen; - linelen = 0; + maxlines *= 2; + result = (char **) pg_realloc(result, maxlines * sizeof(char *)); } - } - - /* handle last line without a terminating newline (yuck) */ - if (linelen) - nlines++; - if (linelen > maxlength) - maxlength = linelen; - /* set up the result and the line buffer */ - result = (char **) pg_malloc((nlines + 1) * sizeof(char *)); - buffer = (char *) pg_malloc(maxlength + 1); + result[n++] = pg_strdup(line.data); + } + result[n] = NULL; - /* now reprocess the file and store the lines */ - rewind(infile); - n = 0; - while (fgets(buffer, maxlength + 1, infile) != NULL && n < nlines) - result[n++] = pg_strdup(buffer); + pfree(line.data); fclose(infile); - free(buffer); - result[n] = NULL; return result; } @@ -1515,23 +1482,25 @@ setup_auth(FILE *cmdfd) static void get_su_pwd(void) { - char pwd1[100]; - char pwd2[100]; + char *pwd1; if (pwprompt) { /* * Read password from terminal */ + char *pwd2; + printf("\n"); fflush(stdout); - simple_prompt("Enter new superuser password: ", pwd1, sizeof(pwd1), false); - simple_prompt("Enter it again: ", pwd2, sizeof(pwd2), false); + pwd1 = simple_prompt("Enter new superuser password: ", false); + pwd2 = simple_prompt("Enter it again: ", false); if (strcmp(pwd1, pwd2) != 0) { fprintf(stderr, _("Passwords didn't match.\n")); exit(1); } + free(pwd2); } else { @@ -1544,7 +1513,6 @@ get_su_pwd(void) * for now. */ FILE *pwf = fopen(pwfilename, "r"); - int i; if (!pwf) { @@ -1552,7 +1520,8 @@ get_su_pwd(void) pwfilename); exit(1); } - if (!fgets(pwd1, sizeof(pwd1), pwf)) + pwd1 = pg_get_line(pwf); + if (!pwd1) { if (ferror(pwf)) pg_log_error("could not read password from file \"%s\": %m", @@ -1564,12 +1533,10 @@ get_su_pwd(void) } fclose(pwf); - i = strlen(pwd1); - while (i > 0 && (pwd1[i - 1] == '\r' || pwd1[i - 1] == '\n')) - pwd1[--i] = '\0'; + (void) pg_strip_crlf(pwd1); } - superuser_password = pg_strdup(pwd1); + superuser_password = pwd1; } /* diff --git a/src/bin/pg_archivecleanup/pg_archivecleanup.c b/src/bin/pg_archivecleanup/pg_archivecleanup.c index 81fa63c742e6..45f590c519fe 100644 --- a/src/bin/pg_archivecleanup/pg_archivecleanup.c +++ b/src/bin/pg_archivecleanup/pg_archivecleanup.c @@ -303,7 +303,7 @@ main(int argc, char **argv) switch (c) { case 'd': /* Debug mode */ - pg_logging_set_level(PG_LOG_DEBUG); + pg_logging_increase_verbosity(); break; case 'n': /* Dry-Run mode */ dryrun = true; diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c index cd05f5fede18..cddc896390da 100644 --- a/src/bin/pg_basebackup/pg_receivewal.c +++ b/src/bin/pg_basebackup/pg_receivewal.c @@ -269,8 +269,8 @@ FindStreamingStart(uint32 *tli) if (statbuf.st_size != WalSegSz) { - pg_log_warning("segment file \"%s\" has incorrect size %d, skipping", - dirent->d_name, (int) statbuf.st_size); + pg_log_warning("segment file \"%s\" has incorrect size %lld, skipping", + dirent->d_name, (long long int) statbuf.st_size); continue; } } diff --git a/src/bin/pg_basebackup/receivelog.c b/src/bin/pg_basebackup/receivelog.c index d3f99d89c5c8..dc97c7e89c4d 100644 --- a/src/bin/pg_basebackup/receivelog.c +++ b/src/bin/pg_basebackup/receivelog.c @@ -46,8 +46,7 @@ static bool ProcessXLogDataMsg(PGconn *conn, StreamCtl *stream, char *copybuf, i XLogRecPtr *blockpos); static PGresult *HandleEndOfCopyStream(PGconn *conn, StreamCtl *stream, char *copybuf, XLogRecPtr blockpos, XLogRecPtr *stoppos); -static bool CheckCopyStreamStop(PGconn *conn, StreamCtl *stream, XLogRecPtr blockpos, - XLogRecPtr *stoppos); +static bool CheckCopyStreamStop(PGconn *conn, StreamCtl *stream, XLogRecPtr blockpos); static long CalculateCopyStreamSleeptime(TimestampTz now, int standby_message_timeout, TimestampTz last_status); @@ -747,7 +746,7 @@ HandleCopyStream(PGconn *conn, StreamCtl *stream, /* * Check if we should continue streaming, or abort at this point. */ - if (!CheckCopyStreamStop(conn, stream, blockpos, stoppos)) + if (!CheckCopyStreamStop(conn, stream, blockpos)) goto error; now = feGetCurrentTimestamp(); @@ -825,7 +824,7 @@ HandleCopyStream(PGconn *conn, StreamCtl *stream, * Check if we should continue streaming, or abort at this * point. */ - if (!CheckCopyStreamStop(conn, stream, blockpos, stoppos)) + if (!CheckCopyStreamStop(conn, stream, blockpos)) goto error; } else @@ -1203,8 +1202,7 @@ HandleEndOfCopyStream(PGconn *conn, StreamCtl *stream, char *copybuf, * Check if we should continue streaming, or abort at this point. */ static bool -CheckCopyStreamStop(PGconn *conn, StreamCtl *stream, XLogRecPtr blockpos, - XLogRecPtr *stoppos) +CheckCopyStreamStop(PGconn *conn, StreamCtl *stream, XLogRecPtr blockpos) { if (still_sending && stream->stream_stop(blockpos, stream->timeline, false)) { diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 19728740846e..ec65e4176aff 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -22,6 +22,7 @@ #include "common/fe_memutils.h" #include "common/file_perm.h" #include "common/logging.h" +#include "common/string.h" #include "datatype/timestamp.h" #include "port/pg_bswap.h" #include "pqexpbuffer.h" @@ -49,8 +50,7 @@ char *dbuser = NULL; char *dbport = NULL; char *dbname = NULL; int dbgetpassword = 0; /* 0=auto, -1=never, 1=always */ -static bool have_password = false; -static char password[100]; +static char *password = NULL; PGconn *conn = NULL; /* @@ -150,20 +150,21 @@ GetConnection(void) } /* If -W was given, force prompt for password, but only the first time */ - need_password = (dbgetpassword == 1 && !have_password); + need_password = (dbgetpassword == 1 && !password); do { /* Get a new password if appropriate */ if (need_password) { - simple_prompt("Password: ", password, sizeof(password), false); - have_password = true; + if (password) + free(password); + password = simple_prompt("Password: ", false); need_password = false; } /* Use (or reuse, on a subsequent connection) password if we have it */ - if (have_password) + if (password) { keywords[i] = "password"; values[i] = password; diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c index c676d8236e98..6158da3f81de 100644 --- a/src/bin/pg_ctl/pg_ctl.c +++ b/src/bin/pg_ctl/pg_ctl.c @@ -1975,7 +1975,7 @@ CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, bool as_ser Advapi32Handle = LoadLibrary("ADVAPI32.DLL"); if (Advapi32Handle != NULL) { - _CreateRestrictedToken = (__CreateRestrictedToken) GetProcAddress(Advapi32Handle, "CreateRestrictedToken"); + _CreateRestrictedToken = (__CreateRestrictedToken) (pg_funcptr_t) GetProcAddress(Advapi32Handle, "CreateRestrictedToken"); } if (_CreateRestrictedToken == NULL) @@ -2049,11 +2049,11 @@ CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, bool as_ser Kernel32Handle = LoadLibrary("KERNEL32.DLL"); if (Kernel32Handle != NULL) { - _IsProcessInJob = (__IsProcessInJob) GetProcAddress(Kernel32Handle, "IsProcessInJob"); - _CreateJobObject = (__CreateJobObject) GetProcAddress(Kernel32Handle, "CreateJobObjectA"); - _SetInformationJobObject = (__SetInformationJobObject) GetProcAddress(Kernel32Handle, "SetInformationJobObject"); - _AssignProcessToJobObject = (__AssignProcessToJobObject) GetProcAddress(Kernel32Handle, "AssignProcessToJobObject"); - _QueryInformationJobObject = (__QueryInformationJobObject) GetProcAddress(Kernel32Handle, "QueryInformationJobObject"); + _IsProcessInJob = (__IsProcessInJob) (pg_funcptr_t) GetProcAddress(Kernel32Handle, "IsProcessInJob"); + _CreateJobObject = (__CreateJobObject) (pg_funcptr_t) GetProcAddress(Kernel32Handle, "CreateJobObjectA"); + _SetInformationJobObject = (__SetInformationJobObject) (pg_funcptr_t) GetProcAddress(Kernel32Handle, "SetInformationJobObject"); + _AssignProcessToJobObject = (__AssignProcessToJobObject) (pg_funcptr_t) GetProcAddress(Kernel32Handle, "AssignProcessToJobObject"); + _QueryInformationJobObject = (__QueryInformationJobObject) (pg_funcptr_t) GetProcAddress(Kernel32Handle, "QueryInformationJobObject"); } /* Verify that we found all functions */ diff --git a/src/bin/pg_dump/parallel.c b/src/bin/pg_dump/parallel.c index f0587f41e492..b51cc76c7dc2 100644 --- a/src/bin/pg_dump/parallel.c +++ b/src/bin/pg_dump/parallel.c @@ -130,7 +130,7 @@ typedef struct /* Windows implementation of pipe access */ static int pgpipe(int handles[2]); -static int piperead(int s, char *buf, int len); +#define piperead(a,b,c) recv(a,b,c,0) #define pipewrite(a,b,c) send(a,b,c,0) #else /* !WIN32 */ @@ -229,19 +229,6 @@ static char *readMessageFromPipe(int fd); (strncmp(msg, prefix, strlen(prefix)) == 0) -/* - * Shutdown callback to clean up socket access - */ -#ifdef WIN32 -static void -shutdown_parallel_dump_utils(int code, void *unused) -{ - /* Call the cleanup function only from the main thread */ - if (mainThreadId == GetCurrentThreadId()) - WSACleanup(); -} -#endif - /* * Initialize parallel dump support --- should be called early in process * startup. (Currently, this is called whether or not we intend parallel @@ -267,8 +254,7 @@ init_parallel_dump_utils(void) pg_log_error("WSAStartup failed: %d", err); exit_nicely(1); } - /* ... and arrange to shut it down at exit */ - on_exit_nicely(shutdown_parallel_dump_utils, NULL); + parallel_init_done = true; } #endif @@ -1817,20 +1803,4 @@ pgpipe(int handles[2]) return 0; } -/* - * Windows implementation of reading from a pipe. - */ -static int -piperead(int s, char *buf, int len) -{ - int ret = recv(s, buf, len, 0); - - if (ret < 0 && WSAGetLastError() == WSAECONNRESET) - { - /* EOF on the pipe! */ - ret = 0; - } - return ret; -} - #endif /* WIN32 */ diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index 18968684e322..0067984ccd7d 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -142,12 +142,9 @@ typedef struct _restoreOptions SimpleStringList tableNames; int useDB; - char *dbname; /* subject to expand_dbname */ - char *pgport; - char *pghost; - char *username; + ConnParams cparams; /* parameters to use if useDB */ + int noDataForFailedTables; - trivalue promptPassword; int exit_on_error; int compression; int suppressDumpWarnings; /* Suppress output of WARNING entries @@ -163,10 +160,7 @@ typedef struct _restoreOptions typedef struct _dumpOptions { - const char *dbname; /* subject to expand_dbname */ - const char *pghost; - const char *pgport; - const char *username; + ConnParams cparams; int binary_upgrade; @@ -207,6 +201,7 @@ typedef struct _dumpOptions int sequence_data; /* dump sequence data even in schema-only mode */ int do_nothing; + int coll_unknown; /* GPDB */ bool dumpGpPolicy; @@ -230,6 +225,8 @@ typedef struct Archive int minRemoteVersion; /* allowable range */ int maxRemoteVersion; + bool hasGenericLockTable; /* can LOCK TABLE do non-table rels */ + int numWorkers; /* number of parallel processes */ char *sync_snapshot_id; /* sync snapshot id for parallel operation */ @@ -291,12 +288,9 @@ typedef void (*SetupWorkerPtrType) (Archive *AH); * Main archiver interface. */ -extern void ConnectDatabase(Archive *AH, - const char *dbname, - const char *pghost, - const char *pgport, - const char *username, - trivalue prompt_password, +extern void ConnectDatabase(Archive *AHX, + const ConnParams *cparams, + bool isReconnect, bool binary_upgrade); extern void DisconnectDatabase(Archive *AHX); extern PGconn *GetConnection(Archive *AHX); diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index d38aafbbfb98..e83878b8ac0e 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -30,8 +30,10 @@ #include #endif +#include "common/string.h" #include "dumputils.h" #include "fe_utils/string_utils.h" +#include "lib/stringinfo.h" #include "libpq/libpq-fs.h" #include "parallel.h" #include "pg_backup_archiver.h" @@ -163,6 +165,7 @@ InitDumpOptions(DumpOptions *opts) memset(opts, 0, sizeof(DumpOptions)); /* set any fields that shouldn't default to zeroes */ opts->include_everything = true; + opts->cparams.promptPassword = TRI_DEFAULT; opts->dumpSections = DUMP_UNSECTIONED; } @@ -176,6 +179,11 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) DumpOptions *dopt = NewDumpOptions(); /* this is the inverse of what's at the end of pg_dump.c's main() */ + dopt->cparams.dbname = ropt->cparams.dbname ? pg_strdup(ropt->cparams.dbname) : NULL; + dopt->cparams.pgport = ropt->cparams.pgport ? pg_strdup(ropt->cparams.pgport) : NULL; + dopt->cparams.pghost = ropt->cparams.pghost ? pg_strdup(ropt->cparams.pghost) : NULL; + dopt->cparams.username = ropt->cparams.username ? pg_strdup(ropt->cparams.username) : NULL; + dopt->cparams.promptPassword = ropt->cparams.promptPassword; dopt->outputClean = ropt->dropSchema; dopt->dataOnly = ropt->dataOnly; dopt->schemaOnly = ropt->schemaOnly; @@ -408,10 +416,7 @@ RestoreArchive(Archive *AHX) AHX->minRemoteVersion = 0; AHX->maxRemoteVersion = 9999999; - ConnectDatabase(AHX, ropt->dbname, - ropt->pghost, ropt->pgport, ropt->username, - ropt->promptPassword, - false); + ConnectDatabase(AHX, &ropt->cparams, false, false); /* * If we're talking to the DB directly, don't send comments since they @@ -843,16 +848,8 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel) if (strcmp(te->desc, "DATABASE") == 0 || strcmp(te->desc, "DATABASE PROPERTIES") == 0) { - PQExpBufferData connstr; - - initPQExpBuffer(&connstr); - appendPQExpBufferStr(&connstr, "dbname="); - appendConnStrVal(&connstr, te->tag); - /* Abandon struct, but keep its buffer until process exit. */ - pg_log_info("connecting to new database \"%s\"", te->tag); _reconnectToDB(AH, te->tag); - ropt->dbname = connstr.data; } } @@ -984,7 +981,7 @@ NewRestoreOptions(void) /* set any fields that shouldn't default to zeroes */ opts->format = archUnknown; - opts->promptPassword = TRI_DEFAULT; + opts->cparams.promptPassword = TRI_DEFAULT; opts->dumpSections = DUMP_UNSECTIONED; /* GPDB_92_MERGE_FIXEME: do we need the following two lines? */ @@ -1417,8 +1414,7 @@ SortTocFromFile(Archive *AHX) ArchiveHandle *AH = (ArchiveHandle *) AHX; RestoreOptions *ropt = AH->public.ropt; FILE *fh; - char buf[100]; - bool incomplete_line; + StringInfoData linebuf; /* Allocate space for the 'wanted' array, and init it */ ropt->idWanted = (bool *) pg_malloc0(sizeof(bool) * AH->maxDumpId); @@ -1428,45 +1424,33 @@ SortTocFromFile(Archive *AHX) if (!fh) fatal("could not open TOC file \"%s\": %m", ropt->tocFile); - incomplete_line = false; - while (fgets(buf, sizeof(buf), fh) != NULL) + initStringInfo(&linebuf); + + while (pg_get_line_buf(fh, &linebuf)) { - bool prev_incomplete_line = incomplete_line; - int buflen; char *cmnt; char *endptr; DumpId id; TocEntry *te; - /* - * Some lines in the file might be longer than sizeof(buf). This is - * no problem, since we only care about the leading numeric ID which - * can be at most a few characters; but we have to skip continuation - * bufferloads when processing a long line. - */ - buflen = strlen(buf); - if (buflen > 0 && buf[buflen - 1] == '\n') - incomplete_line = false; - else - incomplete_line = true; - if (prev_incomplete_line) - continue; - /* Truncate line at comment, if any */ - cmnt = strchr(buf, ';'); + cmnt = strchr(linebuf.data, ';'); if (cmnt != NULL) + { cmnt[0] = '\0'; + linebuf.len = cmnt - linebuf.data; + } /* Ignore if all blank */ - if (strspn(buf, " \t\r\n") == strlen(buf)) + if (strspn(linebuf.data, " \t\r\n") == linebuf.len) continue; /* Get an ID, check it's valid and not already seen */ - id = strtol(buf, &endptr, 10); - if (endptr == buf || id <= 0 || id > AH->maxDumpId || + id = strtol(linebuf.data, &endptr, 10); + if (endptr == linebuf.data || id <= 0 || id > AH->maxDumpId || ropt->idWanted[id - 1]) { - pg_log_warning("line ignored: %s", buf); + pg_log_warning("line ignored: %s", linebuf.data); continue; } @@ -1493,6 +1477,8 @@ SortTocFromFile(Archive *AHX) _moveBefore(AH->toc, te); } + pg_free(linebuf.data); + if (fclose(fh) != 0) fatal("could not close TOC file: %m"); } @@ -1700,16 +1686,17 @@ dump_lo_buf(ArchiveHandle *AH) { if (AH->connection) { - size_t res; + int res; res = lo_write(AH->connection, AH->loFd, AH->lo_buf, AH->lo_buf_used); - pg_log_debug(ngettext("wrote %lu byte of large object data (result = %lu)", - "wrote %lu bytes of large object data (result = %lu)", + pg_log_debug(ngettext("wrote %zu byte of large object data (result = %d)", + "wrote %zu bytes of large object data (result = %d)", AH->lo_buf_used), - (unsigned long) AH->lo_buf_used, (unsigned long) res); + AH->lo_buf_used, res); + /* We assume there are no short writes, only errors */ if (res != AH->lo_buf_used) - fatal("could not write to large object (result: %lu, expected: %lu)", - (unsigned long) res, (unsigned long) AH->lo_buf_used); + warn_or_exit_horribly(AH, "could not write to large object: %s", + PQerrorMessage(AH->connection)); } else { @@ -2326,7 +2313,8 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, { ArchiveHandle *AH; - pg_log_debug("allocating AH for %s, format %d", FileSpec, fmt); + pg_log_debug("allocating AH for %s, format %d", + FileSpec ? FileSpec : "(stdio)", fmt); AH = (ArchiveHandle *) pg_malloc0(sizeof(ArchiveHandle)); @@ -2403,8 +2391,6 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, else AH->format = fmt; - AH->promptPassword = TRI_DEFAULT; - switch (AH->format) { case archCustom: @@ -3277,27 +3263,20 @@ _doSetSessionAuth(ArchiveHandle *AH, const char *user) * If we're currently restoring right into a database, this will * actually establish a connection. Otherwise it puts a \connect into * the script output. - * - * NULL dbname implies reconnecting to the current DB (pretty useless). */ static void _reconnectToDB(ArchiveHandle *AH, const char *dbname) { if (RestoringToDB(AH)) - ReconnectToServer(AH, dbname, NULL); + ReconnectToServer(AH, dbname); else { - if (dbname) - { - PQExpBufferData connectbuf; + PQExpBufferData connectbuf; - initPQExpBuffer(&connectbuf); - appendPsqlMetaConnect(&connectbuf, dbname); - ahprintf(AH, "%s\n", connectbuf.data); - termPQExpBuffer(&connectbuf); - } - else - ahprintf(AH, "%s\n", "\\connect -\n"); + initPQExpBuffer(&connectbuf); + appendPsqlMetaConnect(&connectbuf, dbname); + ahprintf(AH, "%s\n", connectbuf.data); + termPQExpBuffer(&connectbuf); } /* @@ -4255,10 +4234,7 @@ restore_toc_entries_postfork(ArchiveHandle *AH, TocEntry *pending_list) /* * Now reconnect the single parent connection. */ - ConnectDatabase((Archive *) AH, ropt->dbname, - ropt->pghost, ropt->pgport, ropt->username, - ropt->promptPassword, - false); + ConnectDatabase((Archive *) AH, &ropt->cparams, true, false); /* re-establish fixed state */ _doSetFixedOutputState(AH); @@ -4920,54 +4896,16 @@ CloneArchive(ArchiveHandle *AH) clone->public.n_errors = 0; /* - * Connect our new clone object to the database: In parallel restore the - * parent is already disconnected, because we can connect the worker - * processes independently to the database (no snapshot sync required). In - * parallel backup we clone the parent's existing connection. + * Connect our new clone object to the database, using the same connection + * parameters used for the original connection. */ - if (AH->mode == archModeRead) - { - RestoreOptions *ropt = AH->public.ropt; - - Assert(AH->connection == NULL); - - /* this also sets clone->connection */ - ConnectDatabase((Archive *) clone, ropt->dbname, - ropt->pghost, ropt->pgport, ropt->username, - ropt->promptPassword, false); + ConnectDatabase((Archive *) clone, &clone->public.ropt->cparams, true, + false); - /* re-establish fixed state */ + /* re-establish fixed state */ + if (AH->mode == archModeRead) _doSetFixedOutputState(clone); - } - else - { - PQExpBufferData connstr; - char *pghost; - char *pgport; - char *username; - - Assert(AH->connection != NULL); - - /* - * Even though we are technically accessing the parent's database - * object here, these functions are fine to be called like that - * because all just return a pointer and do not actually send/receive - * any data to/from the database. - */ - initPQExpBuffer(&connstr); - appendPQExpBufferStr(&connstr, "dbname="); - appendConnStrVal(&connstr, PQdb(AH->connection)); - pghost = PQhost(AH->connection); - pgport = PQport(AH->connection); - username = PQuser(AH->connection); - - /* this also sets clone->connection */ - ConnectDatabase((Archive *) clone, connstr.data, - pghost, pgport, username, TRI_NO, false); - - termPQExpBuffer(&connstr); - /* setupDumpWorker will fix up connection state */ - } + /* in write case, setupDumpWorker will fix up connection state */ /* Let the format-specific code have a chance too */ clone->ClonePtr(clone); diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h index f50973de9323..d61d8e0e590c 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -303,7 +303,6 @@ struct _archiveHandle /* Stuff for direct DB connection */ char *archdbname; /* DB name *read* from archive */ - trivalue promptPassword; char *savedPassword; /* password for ropt->username, if known */ char *use_role; PGconn *connection; @@ -471,7 +470,7 @@ extern void InitArchiveFmt_Tar(ArchiveHandle *AH); extern bool isValidTarHeader(char *header); -extern void ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *newUser); +extern void ReconnectToServer(ArchiveHandle *AH, const char *dbname); extern void DropBlobIfExists(ArchiveHandle *AH, Oid oid); void ahwrite(const void *ptr, size_t size, size_t nmemb, ArchiveHandle *AH); diff --git a/src/bin/pg_dump/pg_backup_custom.c b/src/bin/pg_dump/pg_backup_custom.c index 971e6adf4875..77d402c323e9 100644 --- a/src/bin/pg_dump/pg_backup_custom.c +++ b/src/bin/pg_dump/pg_backup_custom.c @@ -619,7 +619,6 @@ _skipData(ArchiveHandle *AH) size_t blkLen; char *buf = NULL; int buflen = 0; - size_t cnt; blkLen = ReadInt(AH); while (blkLen != 0) @@ -638,7 +637,7 @@ _skipData(ArchiveHandle *AH) buf = (char *) pg_malloc(blkLen); buflen = blkLen; } - if ((cnt = fread(buf, 1, blkLen, AH->FH)) != blkLen) + if (fread(buf, 1, blkLen, AH->FH) != blkLen) { if (feof(AH->FH)) fatal("could not read from input file: end of file"); @@ -664,9 +663,7 @@ _skipData(ArchiveHandle *AH) static int _WriteByte(ArchiveHandle *AH, const int i) { - int res; - - if ((res = fputc(i, AH->FH)) == EOF) + if (fputc(i, AH->FH) == EOF) WRITE_ERROR_EXIT; return 1; diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c index e249177298c7..4e317af24095 100644 --- a/src/bin/pg_dump/pg_backup_db.c +++ b/src/bin/pg_dump/pg_backup_db.c @@ -18,6 +18,7 @@ #endif #include "common/connect.h" +#include "common/string.h" #include "dumputils.h" #include "fe_utils/string_utils.h" #include "parallel.h" @@ -26,7 +27,6 @@ #include "pg_backup_utils.h" static void _check_database_version(ArchiveHandle *AH); -static PGconn *_connectDB(ArchiveHandle *AH, const char *newdbname, const char *newUser); static void notice_processor(void *arg pg_attribute_unused(), const char *message); static void @@ -72,214 +72,101 @@ _check_database_version(ArchiveHandle *AH) /* * Reconnect to the server. If dbname is not NULL, use that database, - * else the one associated with the archive handle. If username is - * not NULL, use that user name, else the one from the handle. + * else the one associated with the archive handle. */ void -ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username) +ReconnectToServer(ArchiveHandle *AH, const char *dbname) { - PGconn *newConn; - const char *newdbname; - const char *newusername; - - if (!dbname) - newdbname = PQdb(AH->connection); - else - newdbname = dbname; - - if (!username) - newusername = PQuser(AH->connection); - else - newusername = username; - - newConn = _connectDB(AH, newdbname, newusername); - - /* Update ArchiveHandle's connCancel before closing old connection */ - set_archive_cancel_info(AH, newConn); - - PQfinish(AH->connection); - AH->connection = newConn; - - /* Start strict; later phases may override this. */ - PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH, - ALWAYS_SECURE_SEARCH_PATH_SQL)); -} - -/* - * Connect to the db again. - * - * Note: it's not really all that sensible to use a single-entry password - * cache if the username keeps changing. In current usage, however, the - * username never does change, so one savedPassword is sufficient. We do - * update the cache on the off chance that the password has changed since the - * start of the run. - */ -static PGconn * -_connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser) -{ - PQExpBufferData connstr; - PGconn *newConn; - const char *newdb; - const char *newuser; - char *password; - char passbuf[100]; - bool new_pass; - - if (!reqdb) - newdb = PQdb(AH->connection); - else - newdb = reqdb; - - if (!requser || strlen(requser) == 0) - newuser = PQuser(AH->connection); - else - newuser = requser; - - pg_log_info("connecting to database \"%s\" as user \"%s\"", - newdb, newuser); - - password = AH->savedPassword; - - if (AH->promptPassword == TRI_YES && password == NULL) - { - simple_prompt("Password: ", passbuf, sizeof(passbuf), false); - password = passbuf; - } - - initPQExpBuffer(&connstr); - appendPQExpBufferStr(&connstr, "dbname="); - appendConnStrVal(&connstr, newdb); - - do - { - const char *keywords[7]; - const char *values[7]; - - keywords[0] = "host"; - values[0] = PQhost(AH->connection); - keywords[1] = "port"; - values[1] = PQport(AH->connection); - keywords[2] = "user"; - values[2] = newuser; - keywords[3] = "password"; - values[3] = password; - keywords[4] = "dbname"; - values[4] = connstr.data; - keywords[5] = "fallback_application_name"; - values[5] = progname; - keywords[6] = NULL; - values[6] = NULL; - - new_pass = false; - newConn = PQconnectdbParams(keywords, values, true); - - if (!newConn) - fatal("could not reconnect to database"); - - if (PQstatus(newConn) == CONNECTION_BAD) - { - if (!PQconnectionNeedsPassword(newConn)) - fatal("could not reconnect to database: %s", - PQerrorMessage(newConn)); - PQfinish(newConn); - - if (password) - fprintf(stderr, "Password incorrect\n"); - - fprintf(stderr, "Connecting to %s as %s\n", - newdb, newuser); - - if (AH->promptPassword != TRI_NO) - { - simple_prompt("Password: ", passbuf, sizeof(passbuf), false); - password = passbuf; - } - else - fatal("connection needs password"); - - new_pass = true; - } - } while (new_pass); + PGconn *oldConn = AH->connection; + RestoreOptions *ropt = AH->public.ropt; /* - * We want to remember connection's actual password, whether or not we got - * it by prompting. So we don't just store the password variable. + * Save the dbname, if given, in override_dbname so that it will also + * affect any later reconnection attempt. */ - if (PQconnectionUsedPassword(newConn)) - { - if (AH->savedPassword) - free(AH->savedPassword); - AH->savedPassword = pg_strdup(PQpass(newConn)); - } + if (dbname) + ropt->cparams.override_dbname = pg_strdup(dbname); - termPQExpBuffer(&connstr); - - /* check for version mismatch */ - _check_database_version(AH); + /* + * Note: we want to establish the new connection, and in particular update + * ArchiveHandle's connCancel, before closing old connection. Otherwise + * an ill-timed SIGINT could try to access a dead connection. + */ + AH->connection = NULL; /* dodge error check in ConnectDatabase */ - PQsetNoticeProcessor(newConn, notice_processor, NULL); + ConnectDatabase((Archive *) AH, &ropt->cparams, true, false); - return newConn; + PQfinish(oldConn); } - /* - * Make a database connection with the given parameters. The - * connection handle is returned, the parameters are stored in AHX. - * An interactive password prompt is automatically issued if required. + * Make, or remake, a database connection with the given parameters. * + * The resulting connection handle is stored in AHX->connection. + * + * An interactive password prompt is automatically issued if required. + * We store the results of that in AHX->savedPassword. * Note: it's not really all that sensible to use a single-entry password * cache if the username keeps changing. In current usage, however, the * username never does change, so one savedPassword is sufficient. */ void ConnectDatabase(Archive *AHX, - const char *dbname, - const char *pghost, - const char *pgport, - const char *username, - trivalue prompt_password, + const ConnParams *cparams, + bool isReconnect, bool binary_upgrade) { ArchiveHandle *AH = (ArchiveHandle *) AHX; + trivalue prompt_password; char *password; - char passbuf[100]; bool new_pass; if (AH->connection) fatal("already connected to a database"); + /* Never prompt for a password during a reconnection */ + prompt_password = isReconnect ? TRI_NO : cparams->promptPassword; + password = AH->savedPassword; if (prompt_password == TRI_YES && password == NULL) - { - simple_prompt("Password: ", passbuf, sizeof(passbuf), false); - password = passbuf; - } - AH->promptPassword = prompt_password; + password = simple_prompt("Password: ", false); /* * Start the connection. Loop until we have a password if requested by * backend. */ - const char *keywords[8]; - const char *values[8]; + const char *keywords[9]; + const char *values[9]; do { - keywords[0] = "host"; - values[0] = pghost; - keywords[1] = "port"; - values[1] = pgport; - keywords[2] = "user"; - values[2] = username; - keywords[3] = "password"; - values[3] = password; - keywords[4] = "dbname"; - values[4] = dbname; - keywords[5] = "fallback_application_name"; - values[5] = progname; - keywords[6] = NULL; - values[6] = NULL; + int i = 0; + + /* + * If dbname is a connstring, its entries can override the other + * values obtained from cparams; but in turn, override_dbname can + * override the dbname component of it. + */ + keywords[i] = "host"; + values[i++] = cparams->pghost; + keywords[i] = "port"; + values[i++] = cparams->pgport; + keywords[i] = "user"; + values[i++] = cparams->username; + keywords[i] = "password"; + values[i++] = password; + keywords[i] = "dbname"; + values[i++] = cparams->dbname; + if (cparams->override_dbname) + { + keywords[i] = "dbname"; + values[i++] = cparams->override_dbname; + } + keywords[i] = "fallback_application_name"; + values[i++] = progname; + keywords[i] = NULL; + values[i++] = NULL; + Assert(i <= lengthof(keywords)); new_pass = false; AH->connection = PQconnectdbParams(keywords, values, true); @@ -293,22 +180,31 @@ ConnectDatabase(Archive *AHX, prompt_password != TRI_NO) { PQfinish(AH->connection); - simple_prompt("Password: ", passbuf, sizeof(passbuf), false); - password = passbuf; + password = simple_prompt("Password: ", false); new_pass = true; } } while (new_pass); /* check to see that the backend connection was successfully made */ if (PQstatus(AH->connection) == CONNECTION_BAD) - fatal("connection to database \"%s\" failed: %s", - PQdb(AH->connection) ? PQdb(AH->connection) : "", - PQerrorMessage(AH->connection)); + { + if (isReconnect) + fatal("reconnection to database \"%s\" failed: %s", + PQdb(AH->connection) ? PQdb(AH->connection) : "", + PQerrorMessage(AH->connection)); + else + fatal("connection to database \"%s\" failed: %s", + PQdb(AH->connection) ? PQdb(AH->connection) : "", + PQerrorMessage(AH->connection)); + } /* Start strict; later phases may override this. */ PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH, ALWAYS_SECURE_SEARCH_PATH_SQL)); + if (password && password != AH->savedPassword) + free(password); + /* * We want to remember connection's actual password, whether or not we got * it by prompting. So we don't just store the password variable. @@ -331,11 +227,11 @@ ConnectDatabase(Archive *AHX, */ if (binary_upgrade) { - keywords[6] = "options"; - values[6] = AH->public.remoteVersion < GPDB7_MAJOR_PGVERSION ? + keywords[7] = "options"; + values[7] = AH->public.remoteVersion < GPDB7_MAJOR_PGVERSION ? "-c gp_session_role=utility" : "-c gp_role=utility"; - keywords[7] = NULL; - values[7] = NULL; + keywords[8] = NULL; + values[8] = NULL; AH->connection = PQconnectdbParams(keywords, values, true); } PQsetNoticeProcessor(AH->connection, notice_processor, NULL); @@ -651,6 +547,72 @@ EndDBCopyMode(Archive *AHX, const char *tocEntryTag) } } +/* + * Does LOCK TABLE work on non-table relations on this server? + * + * Note: assumes it is called out of any transaction + */ +bool +IsLockTableGeneric(Archive *AHX) +{ + ArchiveHandle *AH = (ArchiveHandle *) AHX; + PGresult *res; + char *sqlstate; + bool retval; + + if (AHX->remoteVersion >= 140000) + return true; + else if (AHX->remoteVersion < 90500) + return false; + + StartTransaction(AHX); + + /* + * Try a LOCK TABLE on a well-known non-table catalog; WRONG_OBJECT_TYPE + * tells us that this server doesn't support locking non-table rels, while + * LOCK_NOT_AVAILABLE and INSUFFICIENT_PRIVILEGE tell us that it does. + * Report anything else as a fatal problem. + */ +#define ERRCODE_INSUFFICIENT_PRIVILEGE "42501" +#define ERRCODE_WRONG_OBJECT_TYPE "42809" +#define ERRCODE_LOCK_NOT_AVAILABLE "55P03" + res = PQexec(AH->connection, + "LOCK TABLE pg_catalog.pg_class_tblspc_relfilenode_index IN ACCESS SHARE MODE NOWAIT"); + switch (PQresultStatus(res)) + { + case PGRES_COMMAND_OK: + retval = true; + break; + case PGRES_FATAL_ERROR: + sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE); + if (sqlstate && + strcmp(sqlstate, ERRCODE_WRONG_OBJECT_TYPE) == 0) + { + retval = false; + break; + } + else if (sqlstate && + (strcmp(sqlstate, ERRCODE_LOCK_NOT_AVAILABLE) == 0 || + strcmp(sqlstate, ERRCODE_INSUFFICIENT_PRIVILEGE) == 0)) + { + retval = true; + break; + } + /* else, falls through */ + default: + warn_or_exit_horribly(AH, "LOCK TABLE failed for \"%s\": %s", + "pg_catalog.pg_class_tblspc_relfilenode_index", + PQerrorMessage(AH->connection)); + retval = false; /* not reached */ + break; + } + PQclear(res); + + CommitTransaction(AHX); + + return retval; +} + void StartTransaction(Archive *AHX) { diff --git a/src/bin/pg_dump/pg_backup_db.h b/src/bin/pg_dump/pg_backup_db.h index b22c6d282534..1e177d69330d 100644 --- a/src/bin/pg_dump/pg_backup_db.h +++ b/src/bin/pg_dump/pg_backup_db.h @@ -19,6 +19,8 @@ extern PGresult *ExecuteSqlQueryForSingleRow(Archive *fout, const char *query); extern void EndDBCopyMode(Archive *AHX, const char *tocEntryTag); +extern bool IsLockTableGeneric(Archive *AHX); + extern void StartTransaction(Archive *AHX); extern void CommitTransaction(Archive *AHX); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index ffe3f97d7944..1e69a24ef282 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -48,6 +48,7 @@ #include "catalog/pg_attribute_d.h" #include "catalog/pg_cast_d.h" #include "catalog/pg_class_d.h" +#include "catalog/pg_collation_d.h" #include "catalog/pg_default_acl_d.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_foreign_server_d.h" @@ -329,6 +330,9 @@ static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer, static const char *getAttrName(int attrnum, const TableInfo *tblInfo); static const char *fmtCopyColumnList(const TableInfo *ti, PQExpBuffer buffer); static bool nonemptyReloptions(const char *reloptions); +static void appendIndexCollationVersion(PQExpBuffer buffer, IndxInfo *indxinfo, + int enc, bool coll_unknown, + Archive *fount); static void appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions, const char *prefix, Archive *fout); static char *get_synchronized_snapshot(Archive *fout); @@ -380,7 +384,6 @@ main(int argc, char **argv) char *use_role = NULL; long rowsPerInsert; int numWorkers = 1; - trivalue prompt_password = TRI_DEFAULT; int compressLevel = -1; int plainText = 0; ArchiveFormat archiveFormat = archUnknown; @@ -461,6 +464,7 @@ main(int argc, char **argv) {"on-conflict-do-nothing", no_argument, &dopt.do_nothing, 1}, {"rows-per-insert", required_argument, NULL, 10}, {"include-foreign-data", required_argument, NULL, 11}, + {"index-collation-versions-unknown", no_argument, &dopt.coll_unknown, 1}, /* START MPP ADDITION */ @@ -530,7 +534,7 @@ main(int argc, char **argv) break; case 'd': /* database name */ - dopt.dbname = pg_strdup(optarg); + dopt.cparams.dbname = pg_strdup(optarg); break; case 'E': /* Dump encoding */ @@ -546,7 +550,7 @@ main(int argc, char **argv) break; case 'h': /* server host */ - dopt.pghost = pg_strdup(optarg); + dopt.cparams.pghost = pg_strdup(optarg); break; case 'j': /* number of dump jobs */ @@ -567,7 +571,7 @@ main(int argc, char **argv) break; case 'p': /* server port */ - dopt.pgport = pg_strdup(optarg); + dopt.cparams.pgport = pg_strdup(optarg); break; case 'R': @@ -592,20 +596,20 @@ main(int argc, char **argv) break; case 'U': - dopt.username = pg_strdup(optarg); + dopt.cparams.username = pg_strdup(optarg); break; case 'v': /* verbose */ g_verbose = true; - pg_logging_set_level(PG_LOG_INFO); + pg_logging_increase_verbosity(); break; case 'w': - prompt_password = TRI_NO; + dopt.cparams.promptPassword = TRI_NO; break; case 'W': - prompt_password = TRI_YES; + dopt.cparams.promptPassword = TRI_YES; break; case 'x': /* skip ACL dump */ @@ -727,8 +731,8 @@ main(int argc, char **argv) * Non-option argument specifies database name as long as it wasn't * already specified with -d / --dbname */ - if (optind < argc && dopt.dbname == NULL) - dopt.dbname = argv[optind++]; + if (optind < argc && dopt.cparams.dbname == NULL) + dopt.cparams.dbname = argv[optind++]; /* Complain if any arguments remain */ if (optind < argc) @@ -827,6 +831,10 @@ main(int argc, char **argv) if (archiveFormat != archDirectory && numWorkers > 1) fatal("parallel backup only supported by the directory format"); + /* Unknown collation versions only relevant in binary upgrade mode */ + if (dopt.coll_unknown && !dopt.binary_upgrade) + fatal("option --index-collation-versions-unknown only works in binary upgrade mode"); + /* Open the output file */ fout = CreateArchive(filename, archiveFormat, compressLevel, dosync, archiveMode, setupDumpWorker); @@ -854,7 +862,7 @@ main(int argc, char **argv) * Open the database using the Archiver, so it knows about it. Errors mean * death. */ - ConnectDatabase(fout, dopt.dbname, dopt.pghost, dopt.pgport, dopt.username, prompt_password, dopt.binary_upgrade); + ConnectDatabase(fout, &dopt.cparams, false, dopt.binary_upgrade); setup_connection(fout, dumpencoding, dumpsnapshot, use_role); /* @@ -1072,6 +1080,11 @@ main(int argc, char **argv) ropt->filename = filename; /* if you change this list, see dumpOptionsFromRestoreOptions */ + ropt->cparams.dbname = dopt.cparams.dbname ? pg_strdup(dopt.cparams.dbname) : NULL; + ropt->cparams.pgport = dopt.cparams.pgport ? pg_strdup(dopt.cparams.pgport) : NULL; + ropt->cparams.pghost = dopt.cparams.pghost ? pg_strdup(dopt.cparams.pghost) : NULL; + ropt->cparams.username = dopt.cparams.username ? pg_strdup(dopt.cparams.username) : NULL; + ropt->cparams.promptPassword = dopt.cparams.promptPassword; ropt->dropSchema = dopt.outputClean; ropt->dataOnly = dopt.dataOnly; ropt->schemaOnly = dopt.schemaOnly; @@ -1338,6 +1351,9 @@ setup_connection(Archive *AH, const char *dumpencoding, ExecuteSqlStatement(AH, "SET row_security = off"); } + /* Detect whether LOCK TABLE can handle non-table relations */ + AH->hasGenericLockTable = IsLockTableGeneric(AH); + /* * Initialize prepared-query state to "nothing prepared". We do this here * so that a parallel dump worker will have its own state. @@ -1543,8 +1559,8 @@ expand_foreign_server_name_patterns(Archive *fout, for (cell = patterns->head; cell; cell = cell->next) { - appendPQExpBuffer(query, - "SELECT oid FROM pg_catalog.pg_foreign_server s\n"); + appendPQExpBufferStr(query, + "SELECT oid FROM pg_catalog.pg_foreign_server s\n"); processSQLNamePattern(GetConnection(fout), query, cell->val, false, false, NULL, "s.srvname", NULL, NULL); @@ -4428,6 +4444,7 @@ getSubscriptions(Archive *fout) int i_oid; int i_subname; int i_subowner; + int i_substream; int i_subconninfo; int i_subslotname; int i_subsynccommit; @@ -4465,16 +4482,19 @@ getSubscriptions(Archive *fout) "s.subpublications,\n"); if (fout->remoteVersion >= 140000) - appendPQExpBuffer(query, - " s.subbinary\n"); + appendPQExpBufferStr(query, " s.subbinary,\n"); else - appendPQExpBuffer(query, - " false AS subbinary\n"); + appendPQExpBufferStr(query, " false AS subbinary,\n"); - appendPQExpBuffer(query, - "FROM pg_subscription s\n" - "WHERE s.subdbid = (SELECT oid FROM pg_database\n" - " WHERE datname = current_database())"); + if (fout->remoteVersion >= 140000) + appendPQExpBufferStr(query, " s.substream\n"); + else + appendPQExpBufferStr(query, " false AS substream\n"); + + appendPQExpBufferStr(query, + "FROM pg_subscription s\n" + "WHERE s.subdbid = (SELECT oid FROM pg_database\n" + " WHERE datname = current_database())"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -4489,6 +4509,7 @@ getSubscriptions(Archive *fout) i_subsynccommit = PQfnumber(res, "subsynccommit"); i_subpublications = PQfnumber(res, "subpublications"); i_subbinary = PQfnumber(res, "subbinary"); + i_substream = PQfnumber(res, "substream"); subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo)); @@ -4512,6 +4533,8 @@ getSubscriptions(Archive *fout) pg_strdup(PQgetvalue(res, i, i_subpublications)); subinfo[i].subbinary = pg_strdup(PQgetvalue(res, i, i_subbinary)); + subinfo[i].substream = + pg_strdup(PQgetvalue(res, i, i_substream)); /* Decide whether we want to dump it */ selectDumpableObject(&(subinfo[i].dobj), fout); @@ -4574,7 +4597,10 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo) appendPQExpBufferStr(query, "NONE"); if (strcmp(subinfo->subbinary, "t") == 0) - appendPQExpBuffer(query, ", binary = true"); + appendPQExpBufferStr(query, ", binary = true"); + + if (strcmp(subinfo->substream, "f") != 0) + appendPQExpBufferStr(query, ", streaming = on"); if (strcmp(subinfo->subsynccommit, "off") != 0) appendPQExpBuffer(query, ", synchronous_commit = %s", fmtId(subinfo->subsynccommit)); @@ -6916,11 +6942,7 @@ getTables(Archive *fout, int *numTables) * assume our lock on the child is enough to prevent schema * alterations to parent tables. * - * NOTE: it'd be kinda nice to lock other relations too, not only - * plain or partitioned tables, but the backend doesn't presently - * allow that. - * - * We only need to lock the table for certain components; see + * We only need to lock the relation for certain components; see * pg_dump.h * * GPDB: Build a single LOCK TABLE statement to lock all interesting tables. @@ -7247,7 +7269,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) i_tablespace, i_indreloptions, i_indstatcols, - i_indstatvals; + i_indstatvals, + i_inddependcollnames, + i_inddependcollversions; /* * We want to perform just one query against pg_index. However, we @@ -7313,14 +7337,37 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) " " FROM pg_catalog.pg_attribute " " WHERE attrelid = i.indexrelid AND " - " attstattarget >= 0) AS indstatvals "); + " attstattarget >= 0) AS indstatvals, "); else appendPQExpBuffer(query, "0 AS parentidx, " "i.indnatts AS indnkeyatts, " "i.indnatts AS indnatts, " "'' AS indstatcols, " - "'' AS indstatvals "); + "'' AS indstatvals, "); + + if (fout->remoteVersion >= 140000) + appendPQExpBuffer(query, + "(SELECT pg_catalog.array_agg(quote_ident(ns.nspname) || '.' || quote_ident(c.collname) ORDER BY refobjid) " + " FROM pg_catalog.pg_depend d " + " JOIN pg_catalog.pg_collation c ON (c.oid = d.refobjid) " + " JOIN pg_catalog.pg_namespace ns ON (c.collnamespace = ns.oid) " + " WHERE d.classid = 'pg_catalog.pg_class'::regclass AND " + " d.objid = i.indexrelid AND " + " d.objsubid = 0 AND " + " d.refclassid = 'pg_catalog.pg_collation'::regclass AND " + " d.refobjversion IS NOT NULL) AS inddependcollnames, " + "(SELECT pg_catalog.array_agg(quote_literal(refobjversion) ORDER BY refobjid) " + " FROM pg_catalog.pg_depend " + " WHERE classid = 'pg_catalog.pg_class'::regclass AND " + " objid = i.indexrelid AND " + " objsubid = 0 AND " + " refclassid = 'pg_catalog.pg_collation'::regclass AND " + " refobjversion IS NOT NULL) AS inddependcollversions "); + else + appendPQExpBuffer(query, + "'{}' AS inddependcollnames, " + "'{}' AS inddependcollversions "); appendPQExpBuffer(query, "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" @@ -7405,6 +7452,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) i_indreloptions = PQfnumber(res, "indreloptions"); i_indstatcols = PQfnumber(res, "indstatcols"); i_indstatvals = PQfnumber(res, "indstatvals"); + i_inddependcollnames = PQfnumber(res, "inddependcollnames"); + i_inddependcollversions = PQfnumber(res, "inddependcollversions"); indxinfo = (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo)); @@ -7465,6 +7514,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) indxinfo[j].indreloptions = pg_strdup(PQgetvalue(res, j, i_indreloptions)); indxinfo[j].indstatcols = pg_strdup(PQgetvalue(res, j, i_indstatcols)); indxinfo[j].indstatvals = pg_strdup(PQgetvalue(res, j, i_indstatvals)); + indxinfo[j].inddependcollnames = pg_strdup(PQgetvalue(res, j, i_inddependcollnames)); + indxinfo[j].inddependcollversions = pg_strdup(PQgetvalue(res, j, i_inddependcollversions)); indxinfo[j].indkeys = (Oid *) pg_malloc(indxinfo[j].indnattrs * sizeof(Oid)); parseOidArray(PQgetvalue(res, j, i_indkey), indxinfo[j].indkeys, indxinfo[j].indnattrs); @@ -12376,68 +12427,68 @@ dumpFunc(Archive *fout, const FuncInfo *finfo) /* Set up query for function-specific details */ appendPQExpBufferStr(query, "PREPARE dumpFunc(pg_catalog.oid) AS\n"); - appendPQExpBuffer(query, - "SELECT\n" - "proretset,\n" - "prosrc,\n" - "probin,\n" - "provolatile,\n" - "proisstrict,\n" - "prosecdef,\n" - "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname,\n " - "proconfig,\n" - "procost,\n" - "prorows,\n" - "prodataaccess,\n" - "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n" - "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n" - "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n" - "(SELECT procallback FROM pg_catalog.pg_proc_callback WHERE profnoid::pg_catalog.oid = p.oid) as callbackfunc,\n"); + appendPQExpBufferStr(query, + "SELECT\n" + "proretset,\n" + "prosrc,\n" + "probin,\n" + "provolatile,\n" + "proisstrict,\n" + "prosecdef,\n" + "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname,\n " + "proconfig,\n" + "procost,\n" + "prorows,\n" + "prodataaccess,\n" + "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n" + "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n" + "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n" + "(SELECT procallback FROM pg_catalog.pg_proc_callback WHERE profnoid::pg_catalog.oid = p.oid) as callbackfunc,\n"); if (fout->remoteVersion >= 90200) - appendPQExpBuffer(query, - "proleakproof,\n"); + appendPQExpBufferStr(query, + "proleakproof,\n"); else - appendPQExpBuffer(query, - "false AS proleakproof,\n"); + appendPQExpBufferStr(query, + "false AS proleakproof,\n"); /* GPDB6 added proexeclocation */ if (fout->remoteVersion >= GPDB6_MAJOR_PGVERSION) - appendPQExpBuffer(query, - "proexeclocation,\n"); + appendPQExpBufferStr(query, + "proexeclocation,\n"); else - appendPQExpBuffer(query, - "'a' as proexeclocation,\n"); + appendPQExpBufferStr(query, + "'a' as proexeclocation,\n"); if (fout->remoteVersion >= 90500) - appendPQExpBuffer(query, - "array_to_string(protrftypes, ' ') AS protrftypes,\n"); + appendPQExpBufferStr(query, + "array_to_string(protrftypes, ' ') AS protrftypes,\n"); if (fout->remoteVersion >= 90600) - appendPQExpBuffer(query, - "proparallel,\n"); + appendPQExpBufferStr(query, + "proparallel,\n"); else - appendPQExpBuffer(query, - "'u' AS proparallel,\n"); + appendPQExpBufferStr(query, + "'u' AS proparallel,\n"); if (fout->remoteVersion >= 110000) - appendPQExpBuffer(query, - "prokind,\n"); + appendPQExpBufferStr(query, + "prokind,\n"); else if (fout->remoteVersion >= 80400) - appendPQExpBuffer(query, - "CASE WHEN proiswindow THEN 'w' ELSE 'f' END AS prokind,\n"); + appendPQExpBufferStr(query, + "CASE WHEN proiswindow THEN 'w' ELSE 'f' END AS prokind,\n"); else - appendPQExpBuffer(query, - "CASE WHEN proiswin THEN 'w' ELSE 'f' END AS prokind,\n"); + appendPQExpBufferStr(query, + "CASE WHEN proiswin THEN 'w' ELSE 'f' END AS prokind,\n"); if (fout->remoteVersion >= 120000) - appendPQExpBuffer(query, - "prosupport\n"); + appendPQExpBufferStr(query, + "prosupport\n"); else - appendPQExpBuffer(query, - "'-' AS prosupport\n"); + appendPQExpBufferStr(query, + "'-' AS prosupport\n"); - appendPQExpBuffer(query, + appendPQExpBufferStr(query, "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n" "WHERE p.oid = $1 " "AND l.oid = p.prolang"); @@ -13169,6 +13220,11 @@ dumpOpr(Archive *fout, const OprInfo *oprinfo) oprcanmerge = PQgetvalue(res, 0, i_oprcanmerge); oprcanhash = PQgetvalue(res, 0, i_oprcanhash); + /* In PG14 upwards postfix operator support does not exist anymore. */ + if (strcmp(oprkind, "r") == 0) + pg_log_warning("postfix operators are not supported anymore (operator \"%s\")", + oprcode); + oprregproc = convertRegProcReference(oprcode); if (oprregproc) { @@ -13181,7 +13237,8 @@ dumpOpr(Archive *fout, const OprInfo *oprinfo) /* * right unary means there's a left arg and left unary means there's a - * right arg + * right arg. (Although the "r" case is dead code for PG14 and later, + * continue to support it in case we're dumping from an old server.) */ if (strcmp(oprkind, "r") == 0 || strcmp(oprkind, "b") == 0) @@ -14066,12 +14123,10 @@ dumpCollation(Archive *fout, const CollInfo *collinfo) if (fout->remoteVersion >= 100000) appendPQExpBufferStr(query, - "collprovider, " - "collversion, "); + "collprovider, "); else appendPQExpBufferStr(query, - "'c' AS collprovider, " - "NULL AS collversion, "); + "'c' AS collprovider, "); if (fout->remoteVersion >= 120000) appendPQExpBufferStr(query, @@ -14132,24 +14187,6 @@ dumpCollation(Archive *fout, const CollInfo *collinfo) appendStringLiteralAH(q, collctype, fout); } - /* - * For binary upgrade, carry over the collation version. For normal - * dump/restore, omit the version, so that it is computed upon restore. - */ - if (dopt->binary_upgrade) - { - int i_collversion; - - i_collversion = PQfnumber(res, "collversion"); - if (!PQgetisnull(res, 0, i_collversion)) - { - appendPQExpBufferStr(q, ", version = "); - appendStringLiteralAH(q, - PQgetvalue(res, 0, i_collversion), - fout); - } - } - appendPQExpBufferStr(q, ");\n"); if (dopt->binary_upgrade) @@ -16600,6 +16637,9 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) /* We had better have loaded per-column details about this table */ Assert(tbinfo->interesting); + /* We had better have loaded per-column details about this table */ + Assert(tbinfo->interesting); + qrelname = pg_strdup(fmtId(tbinfo->dobj.name)); qualrelname = pg_strdup(fmtQualifiedDumpable(tbinfo)); @@ -17749,7 +17789,8 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) /* * If there's an associated constraint, don't dump the index per se, but - * do dump any comment for it. (This is safe because dependency ordering + * do dump any comment, or in binary upgrade mode dependency on a + * collation version for it. (This is safe because dependency ordering * will have ensured the constraint is emitted first.) Note that the * emitted comment has to be shown as depending on the constraint, not the * index, in such cases. @@ -17815,6 +17856,10 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) "pg_catalog.pg_class", "INDEX", qqindxname); + if (dopt->binary_upgrade) + appendIndexCollationVersion(q, indxinfo, fout->encoding, + dopt->coll_unknown, fout); + /* If the index defines identity, we need to record that. */ if (indxinfo->indisreplident) { @@ -17843,6 +17888,21 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) if (indstatvalsarray) free(indstatvalsarray); } + else if (dopt->binary_upgrade) + { + appendIndexCollationVersion(q, indxinfo, fout->encoding, + dopt->coll_unknown, fout); + + if (indxinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) + ArchiveEntry(fout, indxinfo->dobj.catId, indxinfo->dobj.dumpId, + ARCHIVE_OPTS(.tag = indxinfo->dobj.name, + .namespace = tbinfo->dobj.namespace->dobj.name, + .tablespace = indxinfo->tablespace, + .owner = tbinfo->rolname, + .description = "INDEX", + .section = SECTION_POST_DATA, + .createStmt = q->data)); + } /* Dump Index Comments */ if (indxinfo->dobj.dump & DUMP_COMPONENT_COMMENT) @@ -19945,6 +20005,76 @@ nonemptyReloptions(const char *reloptions) return (reloptions != NULL && strlen(reloptions) > 2); } +/* + * Generate UPDATE statements to import the collation versions into the new + * cluster, during a binary upgrade. + */ +static void +appendIndexCollationVersion(PQExpBuffer buffer, IndxInfo *indxinfo, int enc, + bool coll_unknown, Archive *fout) +{ + char *inddependcollnames = indxinfo->inddependcollnames; + char *inddependcollversions = indxinfo->inddependcollversions; + char **inddependcollnamesarray; + char **inddependcollversionsarray; + int ninddependcollnames; + int ninddependcollversions; + + /* + * By default, the new cluster's index will have pg_depends rows with + * current collation versions, meaning that we assume the index isn't + * corrupted if importing from a release that didn't record versions. + * However, if --index-collation-versions-unknown was passed in, then we + * assume such indexes might be corrupted, and clobber versions with + * 'unknown' to trigger version warnings. + */ + if (coll_unknown) + { + appendPQExpBuffer(buffer, + "\n-- For binary upgrade, clobber new index's collation versions\n" + "SET allow_system_table_mods = true;\n"); + appendPQExpBuffer(buffer, + "UPDATE pg_catalog.pg_depend SET refobjversion = 'unknown' WHERE objid = '%u'::pg_catalog.oid AND refclassid = 'pg_catalog.pg_collation'::regclass AND refobjversion IS NOT NULL;\n", + indxinfo->dobj.catId.oid); + appendPQExpBuffer(buffer, "RESET allow_system_table_mods;\n"); + } + + /* Restore the versions that were recorded by the old cluster (if any). */ + parsePGArray(inddependcollnames, + &inddependcollnamesarray, + &ninddependcollnames); + parsePGArray(inddependcollversions, + &inddependcollversionsarray, + &ninddependcollversions); + Assert(ninddependcollnames == ninddependcollversions); + + if (ninddependcollnames > 0) + appendPQExpBufferStr(buffer, + "\n-- For binary upgrade, restore old index's collation versions\n" + "SET allow_system_table_mods = true;\n"); + for (int i = 0; i < ninddependcollnames; i++) + { + /* + * Import refobjversion from the old cluster, being careful to resolve + * the collation OID by name in the new cluster. + */ + appendPQExpBuffer(buffer, + "UPDATE pg_catalog.pg_depend SET refobjversion = %s WHERE objid = '%u'::pg_catalog.oid AND refclassid = 'pg_catalog.pg_collation'::regclass AND refobjversion IS NOT NULL AND refobjid = ", + inddependcollversionsarray[i], + indxinfo->dobj.catId.oid); + appendStringLiteralAH(buffer,inddependcollnamesarray[i], fout); + appendPQExpBuffer(buffer, "::regcollation;\n"); + } + + if (ninddependcollnames > 0) + appendPQExpBufferStr(buffer, "RESET allow_system_table_mods;\n"); + + if (inddependcollnamesarray) + free(inddependcollnamesarray); + if (inddependcollversionsarray) + free(inddependcollversionsarray); +} + /* * Format a reloptions array and append it to the given buffer. * diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index a912e27ac743..76e8e18c0450 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -450,9 +450,11 @@ typedef struct _indxInfo int indnattrs; /* total number of index attributes */ Oid *indkeys; /* In spite of the name 'indkeys' this field * contains both key and nonkey attributes */ + char *inddependcollnames; /* FQ names of depended-on collations */ + char *inddependcollversions; /* versions of the above */ bool indisclustered; bool indisreplident; - Oid parentidx; /* if partitioned, parent index OID */ + Oid parentidx; /* if a partition, parent index OID */ SimplePtrList partattaches; /* if partitioned, partition attach objects */ /* if there is an associated constraint object, its dumpId: */ @@ -704,6 +706,7 @@ typedef struct _SubscriptionInfo char *subconninfo; char *subslotname; char *subbinary; + char *substream; char *subsynccommit; char *subpublications; } SubscriptionInfo; diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 4b4d8204c310..d930789d46c0 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -23,6 +23,7 @@ #include "common/connect.h" #include "common/file_utils.h" #include "common/logging.h" +#include "common/string.h" #include "dumputils.h" #include "fe_utils/string_utils.h" #include "getopt_long.h" @@ -309,7 +310,7 @@ main(int argc, char *argv[]) case 'v': verbose = true; - pg_logging_set_level(PG_LOG_INFO); + pg_logging_increase_verbosity(); appendPQExpBufferStr(pgdumpopts, " -v"); break; @@ -2031,14 +2032,10 @@ connectDatabase(const char *dbname, const char *connection_string, const char **keywords = NULL; const char **values = NULL; PQconninfoOption *conn_opts = NULL; - static bool have_password = false; - static char password[100]; + static char *password = NULL; - if (prompt_password == TRI_YES && !have_password) - { - simple_prompt("Password: ", password, sizeof(password), false); - have_password = true; - } + if (prompt_password == TRI_YES && !password) + password = simple_prompt("Password: ", false); /* * Start the connection. Loop until we have a password if requested by @@ -2118,7 +2115,7 @@ connectDatabase(const char *dbname, const char *connection_string, values[i] = pguser; i++; } - if (have_password) + if (password) { keywords[i] = "password"; values[i] = password; @@ -2145,12 +2142,11 @@ connectDatabase(const char *dbname, const char *connection_string, if (PQstatus(conn) == CONNECTION_BAD && PQconnectionNeedsPassword(conn) && - !have_password && + !password && prompt_password != TRI_NO) { PQfinish(conn); - simple_prompt("Password: ", password, sizeof(password), false); - have_password = true; + password = simple_prompt("Password: ", false); new_pass = true; } } while (new_pass); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index 4531c42656ab..b4c26f0dd08d 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -168,7 +168,7 @@ main(int argc, char **argv) opts->createDB = 1; break; case 'd': - opts->dbname = pg_strdup(optarg); + opts->cparams.dbname = pg_strdup(optarg); break; case 'e': opts->exit_on_error = true; @@ -182,7 +182,7 @@ main(int argc, char **argv) break; case 'h': if (strlen(optarg) != 0) - opts->pghost = pg_strdup(optarg); + opts->cparams.pghost = pg_strdup(optarg); break; case 'j': /* number of restore jobs */ @@ -211,7 +211,7 @@ main(int argc, char **argv) case 'p': if (strlen(optarg) != 0) - opts->pgport = pg_strdup(optarg); + opts->cparams.pgport = pg_strdup(optarg); break; case 'R': /* no-op, still accepted for backwards compatibility */ @@ -245,20 +245,20 @@ main(int argc, char **argv) break; case 'U': - opts->username = pg_strdup(optarg); + opts->cparams.username = pg_strdup(optarg); break; case 'v': /* verbose */ opts->verbose = 1; - pg_logging_set_level(PG_LOG_INFO); + pg_logging_increase_verbosity(); break; case 'w': - opts->promptPassword = TRI_NO; + opts->cparams.promptPassword = TRI_NO; break; case 'W': - opts->promptPassword = TRI_YES; + opts->cparams.promptPassword = TRI_YES; break; case 'x': /* skip ACL dump */ @@ -308,14 +308,14 @@ main(int argc, char **argv) } /* Complain if neither -f nor -d was specified (except if dumping TOC) */ - if (!opts->dbname && !opts->filename && !opts->tocSummary) + if (!opts->cparams.dbname && !opts->filename && !opts->tocSummary) { pg_log_error("one of -d/--dbname and -f/--file must be specified"); exit_nicely(1); } /* Should get at most one of -d and -f, else user is confused */ - if (opts->dbname) + if (opts->cparams.dbname) { if (opts->filename) { diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c index 0f76501786c7..bee33102d415 100644 --- a/src/bin/pg_rewind/copy_fetch.c +++ b/src/bin/pg_rewind/copy_fetch.c @@ -219,9 +219,9 @@ copy_executeFileMap(filemap_t *map) file_entry_t *entry; int i; - for (i = 0; i < map->narray; i++) + for (i = 0; i < map->nentries; i++) { - entry = map->array[i]; + entry = map->entries[i]; execute_pagemap(&entry->target_pages_to_overwrite, entry->path); switch (entry->action) diff --git a/src/bin/pg_rewind/fetch.c b/src/bin/pg_rewind/fetch.c index f18fe5386ed4..f41d0f295ea2 100644 --- a/src/bin/pg_rewind/fetch.c +++ b/src/bin/pg_rewind/fetch.c @@ -37,7 +37,7 @@ fetchSourceFileList(void) * Fetch all relation data files that are marked in the given data page map. */ void -executeFileMap(void) +execute_file_actions(filemap_t *filemap) { if (datadir_source) copy_executeFileMap(filemap); diff --git a/src/bin/pg_rewind/fetch.h b/src/bin/pg_rewind/fetch.h index 7cf8b6ea090d..b20df8b15372 100644 --- a/src/bin/pg_rewind/fetch.h +++ b/src/bin/pg_rewind/fetch.h @@ -25,7 +25,7 @@ */ extern void fetchSourceFileList(void); extern char *fetchFile(const char *filename, size_t *filesize); -extern void executeFileMap(void); +extern void execute_file_actions(filemap_t *filemap); /* in libpq_fetch.c */ extern void libpqProcessFileList(void); diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c index ed5b8c9a3e35..c82ea1080f84 100644 --- a/src/bin/pg_rewind/file_ops.c +++ b/src/bin/pg_rewind/file_ops.c @@ -19,6 +19,7 @@ #include #include "common/file_perm.h" +#include "common/file_utils.h" #include "file_ops.h" #include "filemap.h" #include "pg_rewind.h" @@ -314,6 +315,83 @@ create_target_tablespace_layout(const char *path, const char *link) pfree(newlink); } +/* + * Sync target data directory to ensure that modifications are safely on disk. + * + * We do this once, for the whole data directory, for performance reasons. At + * the end of pg_rewind's run, the kernel is likely to already have flushed + * most dirty buffers to disk. Additionally fsync_pgdata uses a two-pass + * approach (only initiating writeback in the first pass), which often reduces + * the overall amount of IO noticeably. + * + * gpdb: We assume that all files are synchronized before rewinding and thus we + * just need to synchronize those affected files. This is a resonable + * assumption for gpdb since we've ensured that the db state is clean shutdown + * in pg_rewind by running single mode postgres if needed and also we do not + * copy an unsynchronized dababase without sync as the target base. + */ +void +sync_target_dir(filemap_t *filemap) +{ + if (!do_sync || dry_run) + return; + + file_entry_t *entry; + int i; + + if (chdir(datadir_target) < 0) + { + pg_log_error("could not change directory to \"%s\": %m", datadir_target); + exit(1); + } + + for (i = 0; i < filemap->nentries; i++) + { + entry = filemap->entries[i]; + + if (entry->target_pages_to_overwrite.bitmapsize > 0) + fsync_fname(entry->path, false); + else + { + switch (entry->action) + { + case FILE_ACTION_COPY: + case FILE_ACTION_TRUNCATE: + case FILE_ACTION_COPY_TAIL: + fsync_fname(entry->path, false); + break; + + case FILE_ACTION_CREATE: + fsync_fname(entry->path, + entry->source_type == FILE_TYPE_DIRECTORY); + /* FALLTHROUGH */ + case FILE_ACTION_REMOVE: + /* + * Fsync the parent directory if we either create or delete + * files/directories in the parent directory. The parent + * directory might be missing as expected, so fsync it could + * fail but we ignore that error. + */ + fsync_parent_path(entry->path); + break; + + case FILE_ACTION_NONE: + break; + + default: + pg_fatal("no action decided for \"%s\"", entry->path); + break; + } + } + } + + /* fsync some files that are (possibly) written by pg_rewind. */ + fsync_fname("global/pg_control", false); + fsync_fname("backup_label", false); + fsync_fname("postgresql.auto.conf", false); + fsync_fname(".", true); /* due to new file backup_label. */ +} + /* * Read a file into memory. The file to be read is /. * The file contents are returned in a malloc'd buffer, and *filesize diff --git a/src/bin/pg_rewind/file_ops.h b/src/bin/pg_rewind/file_ops.h index 025f24141c98..92a428ffe0ee 100644 --- a/src/bin/pg_rewind/file_ops.h +++ b/src/bin/pg_rewind/file_ops.h @@ -19,6 +19,7 @@ extern void remove_target_file(const char *path, bool missing_ok); extern void truncate_target_file(const char *path, off_t newsize); extern void create_target(file_entry_t *t); extern void remove_target(file_entry_t *t); +extern void sync_target_dir(filemap_t *filemap); extern char *slurpFile(const char *datadir, const char *path, size_t *filesize); diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c index b4e9289eeef8..d4048f649210 100644 --- a/src/bin/pg_rewind/filemap.c +++ b/src/bin/pg_rewind/filemap.c @@ -3,6 +3,19 @@ * filemap.c * A data structure for keeping track of files that have changed. * + * This source file contains the logic to decide what to do with different + * kinds of files, and the data structure to support it. Before modifying + * anything, pg_rewind collects information about all the files and their + * attributes in the target and source data directories. It also scans the + * WAL log in the target, and collects information about data blocks that + * were changed. All this information is stored in a hash table, using the + * file path relative to the root of the data directory as the key. + * + * After collecting all the information required, the decide_file_actions() + * function scans the hash table and decides what action needs to be taken + * for each file. Finally, it sorts the array to the final order that the + * actions should be executed in. + * * Copyright (c) 2013-2020, PostgreSQL Global Development Group * *------------------------------------------------------------------------- @@ -15,22 +28,41 @@ #include "catalog/catalog.h" #include "catalog/pg_tablespace_d.h" +#include "common/hashfn.h" #include "common/string.h" #include "datapagemap.h" #include "filemap.h" #include "pg_rewind.h" #include "storage/fd.h" -filemap_t *filemap = NULL; +/* + * Define a hash table which we can use to store information about the files + * appearing in source and target systems. + */ +static uint32 hash_string_pointer(const char *s); +#define SH_PREFIX filehash +#define SH_ELEMENT_TYPE file_entry_t +#define SH_KEY_TYPE const char * +#define SH_KEY path +#define SH_HASH_KEY(tb, key) hash_string_pointer(key) +#define SH_EQUAL(tb, a, b) (strcmp(a, b) == 0) +#define SH_SCOPE static inline +#define SH_RAW_ALLOCATOR pg_malloc0 +#define SH_DECLARE +#define SH_DEFINE +#include "lib/simplehash.h" + +#define FILEHASH_INITIAL_SIZE 1000 + +static filehash_hash *filehash; static bool isRelDataFile(const char *path); static char *datasegpath(RelFileNode rnode, ForkNumber forknum, BlockNumber segno); -static int path_cmp(const void *a, const void *b); -static file_entry_t *get_filemap_entry(const char *path, bool create); +static file_entry_t *insert_filehash_entry(const char *path); +static file_entry_t *lookup_filehash_entry(const char *path); static int final_filemap_cmp(const void *a, const void *b); -static void filemap_list_to_array(filemap_t *map); static bool check_file_excluded(const char *path, bool is_source); /* @@ -143,54 +175,26 @@ static const struct exclude_list_item excludeFiles[] = }; /* - * Create a new file map (stored in the global pointer "filemap"). + * Initialize the hash table for the file map. */ void -filemap_create(void) +filehash_init(void) { - filemap_t *map; - - map = pg_malloc(sizeof(filemap_t)); - map->first = map->last = NULL; - map->nlist = 0; - map->array = NULL; - map->narray = 0; - - Assert(filemap == NULL); - filemap = map; + filehash = filehash_create(FILEHASH_INITIAL_SIZE, NULL); } -/* Look up or create entry for 'path' */ +/* Look up entry for 'path', creating a new one if it doesn't exist */ static file_entry_t * -get_filemap_entry(const char *path, bool create) +insert_filehash_entry(const char *path) { - filemap_t *map = filemap; file_entry_t *entry; - file_entry_t **e; - file_entry_t key; - file_entry_t *key_ptr; + bool found; - if (map->array) - { - key.path = (char *) path; - key_ptr = &key; - e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *), - path_cmp); - } - else - e = NULL; - - if (e) - entry = *e; - else if (!create) - entry = NULL; - else + entry = filehash_insert(filehash, path, &found); + if (!found) { - /* Create a new entry for this file */ - entry = pg_malloc(sizeof(file_entry_t)); entry->path = pg_strdup(path); entry->isrelfile = isRelDataFile(path); - entry->action = FILE_ACTION_UNDECIDED; entry->target_exists = false; entry->target_type = FILE_TYPE_UNDEFINED; @@ -204,23 +208,18 @@ get_filemap_entry(const char *path, bool create) entry->source_size = 0; entry->source_link_target = NULL; - entry->is_gp_tablespace = false; - - entry->next = NULL; - - if (map->last) - { - map->last->next = entry; - map->last = entry; - } - else - map->first = map->last = entry; - map->nlist++; + entry->action = FILE_ACTION_UNDECIDED; } return entry; } +static file_entry_t * +lookup_filehash_entry(const char *path) +{ + return filehash_lookup(filehash, path); +} + /* * Callback for processing source file list. * @@ -234,8 +233,6 @@ process_source_file(const char *path, file_type_t type, size_t size, { file_entry_t *entry; - Assert(filemap->array == NULL); - /* * Pretend that pg_wal is a directory, even if it's really a symlink. We * don't want to mess with the symlink itself, nor complain if it's a @@ -252,7 +249,9 @@ process_source_file(const char *path, file_type_t type, size_t size, pg_fatal("data file \"%s\" in source is not a regular file", path); /* Remember this source file */ - entry = get_filemap_entry(path, true); + entry = insert_filehash_entry(path); + if (entry->source_exists) + pg_fatal("duplicate source file \"%s\"", path); entry->source_exists = true; entry->source_type = type; entry->source_size = size; @@ -262,15 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t size, /* * Callback for processing target file list. * - * All source files must be already processed before calling this. We record - * the type and size of file, so that decide_file_action() can later decide - * what to do with it. + * Record the type and size of the file, like process_source_file() does. */ void process_target_file(const char *path, file_type_t type, size_t size, const char *link_target) { - filemap_t *map = filemap; file_entry_t *entry; /* @@ -299,22 +295,6 @@ process_target_file(const char *path, file_type_t type, size_t size, return; } - if (map->array == NULL) - { - /* on first call, initialize lookup array */ - if (map->nlist == 0) - { - /* should not happen */ - pg_fatal("source file list is empty"); - } - - filemap_list_to_array(map); - - Assert(map->array != NULL); - - qsort(map->array, map->narray, sizeof(file_entry_t *), path_cmp); - } - /* * Like in process_source_file, pretend that pg_wal is always a directory. */ @@ -322,7 +302,9 @@ process_target_file(const char *path, file_type_t type, size_t size, type = FILE_TYPE_DIRECTORY; /* Remember this target file */ - entry = get_filemap_entry(path, true); + entry = insert_filehash_entry(path); + if (entry->target_exists) + pg_fatal("duplicate source file \"%s\"", path); entry->target_exists = true; entry->target_type = type; entry->target_size = size; @@ -336,7 +318,7 @@ process_target_file(const char *path, file_type_t type, size_t size, * if so, records it in 'target_pages_to_overwrite' bitmap. * * NOTE: All the files on both systems must have already been added to the - * file map! + * hash table! */ void process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode, @@ -347,16 +329,30 @@ process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno_inseg; int segno; - Assert(filemap->array); - segno = blkno / RELSEG_SIZE; blkno_inseg = blkno % RELSEG_SIZE; path = datasegpath(rnode, forknum, segno); - entry = get_filemap_entry(path, false); + entry = lookup_filehash_entry(path); pfree(path); - if (entry && entry->target_exists) + /* + * If the block still exists in both systems, remember it. Otherwise we + * can safely ignore it. + * + * If the block is beyond the EOF in the source system, or the file + * doesn't exist in the source at all, we're going to truncate/remove it + * away from the target anyway. Likewise, if it doesn't exist in the + * target anymore, we will copy it over with the "tail" from the source + * system, anyway. + * + * It is possible to find WAL for a file that doesn't exist on either + * system anymore. It means that the relation was dropped later in the + * target system, and independently on the source system too, or that it + * was created and dropped in the target system and it never existed in + * the source. Either way, we can safely ignore it. + */ + if (entry) { int64 end_offset; @@ -366,28 +362,14 @@ process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode, pg_fatal("unexpected page modification for non-regular file \"%s\"", entry->path); - /* - * If the block beyond the EOF in the source system, no need to - * remember it now, because we're going to truncate it away from the - * target anyway. Also no need to remember the block if it's beyond - * the current EOF in the target system; we will copy it over with the - * "tail" from the source system, anyway. - */ - end_offset = (blkno_inseg + 1) * BLCKSZ; - if (end_offset <= entry->source_size && - end_offset <= entry->target_size) - datapagemap_add(&entry->target_pages_to_overwrite, blkno_inseg); - } - else - { - /* - * If we don't have any record of this file in the file map, it means - * that it's a relation that doesn't exist in the source system. It - * could exist in the target system; we haven't moved the target-only - * entries from the linked list to the array yet! But in any case, if - * it doesn't exist in the source it will be removed from the target - * too, and we can safely ignore it. - */ + if (entry->target_exists && entry->source_exists) + { + off_t end_offset; + + end_offset = (blkno_inseg + 1) * BLCKSZ; + if (end_offset <= entry->source_size && end_offset <= entry->target_size) + datapagemap_add(&entry->target_pages_to_overwrite, blkno_inseg); + } } } @@ -397,10 +379,8 @@ process_target_wal_aofile_change(RelFileNode rnode, int segno, int64 offset) char *path; file_entry_t *entry; - Assert(filemap->array); - path = datasegpath(rnode, MAIN_FORKNUM, segno); - entry = get_filemap_entry(path, false); + entry = lookup_filehash_entry(path); pfree(path); if (entry && entry->target_exists) @@ -508,34 +488,6 @@ check_file_excluded(const char *path, bool is_source) return false; } -/* - * Convert the linked list of entries in map->first/last to the array, - * map->array. - */ -static void -filemap_list_to_array(filemap_t *map) -{ - int narray; - file_entry_t *entry, - *next; - - map->array = (file_entry_t **) - pg_realloc(map->array, - (map->nlist + map->narray) * sizeof(file_entry_t *)); - - narray = map->narray; - for (entry = map->first; entry != NULL; entry = next) - { - map->array[narray++] = entry; - next = entry->next; - entry->next = NULL; - } - Assert(narray == map->nlist + map->narray); - map->narray = narray; - map->nlist = 0; - map->first = map->last = NULL; -} - static const char * action_to_str(file_action_t action) { @@ -563,32 +515,31 @@ action_to_str(file_action_t action) * Calculate the totals needed for progress reports. */ void -calculate_totals(void) +calculate_totals(filemap_t *filemap) { file_entry_t *entry; int i; - filemap_t *map = filemap; - map->total_size = 0; - map->fetch_size = 0; + filemap->total_size = 0; + filemap->fetch_size = 0; - for (i = 0; i < map->narray; i++) + for (i = 0; i < filemap->nentries; i++) { - entry = map->array[i]; + entry = filemap->entries[i]; if (entry->source_type != FILE_TYPE_REGULAR) continue; - map->total_size += entry->source_size; + filemap->total_size += entry->source_size; if (entry->action == FILE_ACTION_COPY) { - map->fetch_size += entry->source_size; + filemap->fetch_size += entry->source_size; continue; } if (entry->action == FILE_ACTION_COPY_TAIL) - map->fetch_size += (entry->source_size - entry->target_size); + filemap->fetch_size += (entry->source_size - entry->target_size); if (entry->target_pages_to_overwrite.bitmapsize > 0) { @@ -597,7 +548,7 @@ calculate_totals(void) iter = datapagemap_iterate(&entry->target_pages_to_overwrite); while (datapagemap_next(iter, &blk)) - map->fetch_size += BLCKSZ; + filemap->fetch_size += BLCKSZ; pg_free(iter); } @@ -605,15 +556,14 @@ calculate_totals(void) } void -print_filemap(void) +print_filemap(filemap_t *filemap) { - filemap_t *map = filemap; file_entry_t *entry; int i; - for (i = 0; i < map->narray; i++) + for (i = 0; i < filemap->nentries; i++) { - entry = map->array[i]; + entry = filemap->entries[i]; if (entry->action != FILE_ACTION_NONE || entry->target_pages_to_overwrite.bitmapsize > 0) { @@ -735,15 +685,6 @@ datasegpath(RelFileNode rnode, ForkNumber forknum, BlockNumber segno) return path; } -static int -path_cmp(const void *a, const void *b) -{ - file_entry_t *fa = *((file_entry_t **) a); - file_entry_t *fb = *((file_entry_t **) b); - - return strcmp(fa->path, fb->path); -} - /* * In the final stage, the filemap is sorted so that removals come last. * From disk space usage point of view, it would be better to do removals @@ -925,22 +866,52 @@ decide_file_action(file_entry_t *entry) /* * Decide what to do with each file. + * + * Returns a 'filemap' with the entries in the order that their actions + * should be executed. */ -void +filemap_t * decide_file_actions(void) { int i; + filehash_iterator it; + file_entry_t *entry; + filemap_t *filemap; - filemap_list_to_array(filemap); - - for (i = 0; i < filemap->narray; i++) + filehash_start_iterate(filehash, &it); + while ((entry = filehash_iterate(filehash, &it)) != NULL) { - file_entry_t *entry = filemap->array[i]; - entry->action = decide_file_action(entry); } - /* Sort the actions to the order that they should be performed */ - qsort(filemap->array, filemap->narray, sizeof(file_entry_t *), + /* + * Turn the hash table into an array, and sort in the order that the + * actions should be performed. + */ + filemap = pg_malloc(offsetof(filemap_t, entries) + + filehash->members * sizeof(file_entry_t *)); + filemap->nentries = filehash->members; + filehash_start_iterate(filehash, &it); + i = 0; + while ((entry = filehash_iterate(filehash, &it)) != NULL) + { + filemap->entries[i++] = entry; + } + + qsort(&filemap->entries, filemap->nentries, sizeof(file_entry_t *), final_filemap_cmp); + + return filemap; +} + + +/* + * Helper function for filemap hash table. + */ +static uint32 +hash_string_pointer(const char *s) +{ + unsigned char *ss = (unsigned char *) s; + + return hash_bytes(ss, strlen(s)); } diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h index a2954f54b293..23bbe0169d02 100644 --- a/src/bin/pg_rewind/filemap.h +++ b/src/bin/pg_rewind/filemap.h @@ -12,15 +12,6 @@ #include "storage/block.h" #include "storage/relfilenode.h" -/* - * For every file found in the local or remote system, we have a file entry - * that contains information about the file on both systems. For relation - * files, there is also a page map that marks pages in the file that were - * changed in the target after the last common checkpoint. Each entry also - * contains an 'action' field, which says what we are going to do with the - * file. - */ - /* these enum values are sorted in the order we want actions to be processed */ typedef enum { @@ -46,9 +37,21 @@ typedef enum FILE_TYPE_SYMLINK } file_type_t; +/* + * For every file found in the local or remote system, we have a file entry + * that contains information about the file on both systems. For relation + * files, there is also a page map that marks pages in the file that were + * changed in the target after the last common checkpoint. + * + * When gathering information, these are kept in a hash table, private to + * filemap.c. decide_file_actions() fills in the 'action' field, sorts all + * the entries, and returns them in an array, ready for executing the actions. + */ typedef struct file_entry_t { - char *path; + uint32 status; /* hash status */ + + const char *path; bool isrelfile; /* is it a relation data file? */ /* @@ -73,50 +76,31 @@ typedef struct file_entry_t size_t source_size; char *source_link_target; /* for a symlink */ - bool is_gp_tablespace; - /* * What will we do to the file? */ file_action_t action; - struct file_entry_t *next; + bool is_gp_tablespace; } file_entry_t; +/* + * This contains the final decisions on what to do with each file. + * 'entries' array contains an entry for each file, sorted in the order + * that their actions should executed. + */ typedef struct filemap_t { - /* - * New entries are accumulated to a linked list, in process_source_file - * and process_target_file. - */ - file_entry_t *first; - file_entry_t *last; - int nlist; /* number of entries currently in list */ - - /* - * After processing all the remote files, the entries in the linked list - * are moved to this array. After processing local files, too, all the - * local entries are added to the array by decide_file_actions(), and - * sorted in the final order. After decide_file_actions(), all the entries - * are in the array, and the linked list is empty. - */ - file_entry_t **array; - int narray; /* current length of array */ - - /* - * Summary information. - */ + /* Summary information, filled by calculate_totals() */ uint64 total_size; /* total size of the source cluster */ uint64 fetch_size; /* number of bytes that needs to be copied */ -} filemap_t; -extern filemap_t *filemap; - -extern void filemap_create(void); -extern void calculate_totals(void); -extern void print_filemap(void); + int nentries; /* size of 'entries' array */ + file_entry_t *entries[FLEXIBLE_ARRAY_MEMBER]; +} filemap_t; /* Functions for populating the filemap */ +extern void filehash_init(void); extern void process_source_file(const char *path, file_type_t type, size_t size, const char *link_target); extern void process_target_file(const char *path, file_type_t type, @@ -127,6 +111,9 @@ extern void process_target_wal_aofile_change(RelFileNode rnode, extern void process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno); -extern void decide_file_actions(void); + +extern filemap_t *decide_file_actions(void); +extern void calculate_totals(filemap_t *filemap); +extern void print_filemap(filemap_t *filemap); #endif /* FILEMAP_H */ diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c index b84a319fb73b..01a36eaa3378 100644 --- a/src/bin/pg_rewind/libpq_fetch.c +++ b/src/bin/pg_rewind/libpq_fetch.c @@ -470,9 +470,9 @@ libpq_executeFileMap(filemap_t *map) PQresultErrorMessage(res)); PQclear(res); - for (i = 0; i < map->narray; i++) + for (i = 0; i < map->nentries; i++) { - entry = map->array[i]; + entry = map->entries[i]; /* If this is a relation file, copy the modified blocks */ execute_pagemap(&entry->target_pages_to_overwrite, entry->path); diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c index 1549cda6601e..5a8e5657c364 100644 --- a/src/bin/pg_rewind/parsexlog.c +++ b/src/bin/pg_rewind/parsexlog.c @@ -210,7 +210,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex, /* * Check if it is a checkpoint record. This checkpoint record needs to * be the latest checkpoint before WAL forked and not the checkpoint - * where the primary has been stopped to be rewinded. + * where the primary has been stopped to be rewound. */ info = XLogRecGetInfo(xlogreader) & ~XLR_INFO_MASK; if (searchptr < forkptr && diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c index 8af5968a56d2..a1f4989a4744 100644 --- a/src/bin/pg_rewind/pg_rewind.c +++ b/src/bin/pg_rewind/pg_rewind.c @@ -19,7 +19,6 @@ #include "catalog/pg_control.h" #include "common/controldata_utils.h" #include "common/file_perm.h" -#include "common/file_utils.h" #include "common/restricted_token.h" #include "common/string.h" #include "fe_utils/recovery_gen.h" @@ -38,7 +37,6 @@ static void createBackupLabel(XLogRecPtr startpoint, TimeLineID starttli, static void digestControlFile(ControlFileData *ControlFile, char *source, size_t size); -static void syncTargetDirectory(void); static void getRestoreCommand(const char *argv0); static void sanityChecks(void); static void findCommonAncestorTimeline(XLogRecPtr *recptr, int *tliIndex); @@ -135,7 +133,8 @@ main(int argc, char **argv) TimeLineID endtli; ControlFileData ControlFile_new; bool writerecoveryconf = false; - char *replication_slot = NULL; + char *replication_slot = NULL; + filemap_t *filemap; pg_logging_init(argv[0]); set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_rewind")); @@ -190,7 +189,7 @@ main(int argc, char **argv) case 3: debug = true; - pg_logging_set_level(PG_LOG_DEBUG); + pg_logging_increase_verbosity(); break; case 'D': /* -D or --target-pgdata */ @@ -396,13 +395,16 @@ main(int argc, char **argv) (uint32) (chkptrec >> 32), (uint32) chkptrec, chkpttli); + /* Initialize the hash table to track the status of each file */ + filehash_init(); + /* * Collect information about all files in the target and source systems. */ - filemap_create(); if (showprogress) pg_log_info("reading source file list"); fetchSourceFileList(); + if (showprogress) pg_log_info("reading target file list"); traverse_datadir(datadir_target, &process_target_file); @@ -423,13 +425,13 @@ main(int argc, char **argv) * We have collected all information we need from both systems. Decide * what to do with each file. */ - decide_file_actions(); + filemap = decide_file_actions(); if (showprogress) - calculate_totals(); + calculate_totals(filemap); /* this is too verbose even for verbose mode */ if (debug) - print_filemap(); + print_filemap(filemap); /* * Ok, we're ready to start copying things over. @@ -449,7 +451,7 @@ main(int argc, char **argv) * modified the target directory and there is no turning back! */ - executeFileMap(); + execute_file_actions(filemap); progress_report(true); @@ -489,7 +491,7 @@ main(int argc, char **argv) if (showprogress) pg_log_info("syncing target data directory"); - syncTargetDirectory(); + sync_target_dir(filemap); if (writerecoveryconf && !dry_run) WriteRecoveryConfig(conn, datadir_target, @@ -837,83 +839,6 @@ digestControlFile(ControlFileData *ControlFile, char *src, size_t size) checkControlFile(ControlFile); } -/* - * Sync target data directory to ensure that modifications are safely on disk. - * - * We do this once, for the whole data directory, for performance reasons. At - * the end of pg_rewind's run, the kernel is likely to already have flushed - * most dirty buffers to disk. Additionally fsync_pgdata uses a two-pass - * approach (only initiating writeback in the first pass), which often reduces - * the overall amount of IO noticeably. - * - * gpdb: We assume that all files are synchronized before rewinding and thus we - * just need to synchronize those affected files. This is a resonable - * assumption for gpdb since we've ensured that the db state is clean shutdown - * in pg_rewind by running single mode postgres if needed and also we do not - * copy an unsynchronized dababase without sync as the target base. - */ -static void -syncTargetDirectory(void) -{ - if (!do_sync || dry_run) - return; - - file_entry_t *entry; - int i; - - if (chdir(datadir_target) < 0) - { - pg_log_error("could not change directory to \"%s\": %m", datadir_target); - exit(1); - } - - for (i = 0; i < filemap->narray; i++) - { - entry = filemap->array[i]; - - if (entry->target_pages_to_overwrite.bitmapsize > 0) - fsync_fname(entry->path, false); - else - { - switch (entry->action) - { - case FILE_ACTION_COPY: - case FILE_ACTION_TRUNCATE: - case FILE_ACTION_COPY_TAIL: - fsync_fname(entry->path, false); - break; - - case FILE_ACTION_CREATE: - fsync_fname(entry->path, - entry->source_type == FILE_TYPE_DIRECTORY); - /* FALLTHROUGH */ - case FILE_ACTION_REMOVE: - /* - * Fsync the parent directory if we either create or delete - * files/directories in the parent directory. The parent - * directory might be missing as expected, so fsync it could - * fail but we ignore that error. - */ - fsync_parent_path(entry->path); - break; - - case FILE_ACTION_NONE: - break; - - default: - pg_fatal("no action decided for \"%s\"", entry->path); - break; - } - } - } - - /* fsync some files that are (possibly) written by pg_rewind. */ - fsync_fname("global/pg_control", false); - fsync_fname("backup_label", false); - fsync_fname("postgresql.auto.conf", false); - fsync_fname(".", true); /* due to new file backup_label. */ -} - static int32 get_target_dbid(const char *argv0) { diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.h index 8c443f0ebcef..da1eef140e52 100644 --- a/src/bin/pg_rewind/pg_rewind.h +++ b/src/bin/pg_rewind/pg_rewind.h @@ -24,6 +24,7 @@ extern char *datadir_source; extern char *connstr_source; extern bool showprogress; extern bool dry_run; +extern bool do_sync; extern int WalSegSz; extern int32 dbid_target; diff --git a/src/bin/pg_test_fsync/.gitignore b/src/bin/pg_test_fsync/.gitignore index f3b593249859..5eb5085f4524 100644 --- a/src/bin/pg_test_fsync/.gitignore +++ b/src/bin/pg_test_fsync/.gitignore @@ -1 +1,3 @@ /pg_test_fsync + +/tmp_check/ diff --git a/src/bin/pg_test_fsync/Makefile b/src/bin/pg_test_fsync/Makefile index 7632c94eb7f6..631d0f38a8e0 100644 --- a/src/bin/pg_test_fsync/Makefile +++ b/src/bin/pg_test_fsync/Makefile @@ -22,8 +22,15 @@ install: all installdirs installdirs: $(MKDIR_P) '$(DESTDIR)$(bindir)' +check: + $(prove_check) + +installcheck: + $(prove_installcheck) + uninstall: rm -f '$(DESTDIR)$(bindir)/pg_test_fsync$(X)' clean distclean maintainer-clean: rm -f pg_test_fsync$(X) $(OBJS) + rm -rf tmp_check diff --git a/src/bin/pg_test_fsync/pg_test_fsync.c b/src/bin/pg_test_fsync/pg_test_fsync.c index 6e4729312331..3eddd983c63b 100644 --- a/src/bin/pg_test_fsync/pg_test_fsync.c +++ b/src/bin/pg_test_fsync/pg_test_fsync.c @@ -5,6 +5,7 @@ #include "postgres_fe.h" +#include #include #include #include @@ -62,7 +63,7 @@ do { \ static const char *progname; -static int secs_per_test = 5; +static unsigned int secs_per_test = 5; static int needs_unlink = 0; static char full_buf[DEFAULT_XLOG_SEG_SIZE], *buf, @@ -148,6 +149,8 @@ handle_args(int argc, char *argv[]) int option; /* Command line option */ int optindex = 0; /* used by getopt_long */ + unsigned long optval; /* used for option parsing */ + char *endptr; if (argc > 1) { @@ -173,7 +176,24 @@ handle_args(int argc, char *argv[]) break; case 's': - secs_per_test = atoi(optarg); + errno = 0; + optval = strtoul(optarg, &endptr, 10); + + if (endptr == optarg || *endptr != '\0' || + errno != 0 || optval != (unsigned int) optval) + { + pg_log_error("invalid argument for option %s", "--secs-per-test"); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); + exit(1); + } + + secs_per_test = (unsigned int) optval; + if (secs_per_test == 0) + { + pg_log_error("%s must be in range %u..%u", + "--secs-per-test", 1, UINT_MAX); + exit(1); + } break; default: @@ -193,8 +213,8 @@ handle_args(int argc, char *argv[]) exit(1); } - printf(ngettext("%d second per test\n", - "%d seconds per test\n", + printf(ngettext("%u second per test\n", + "%u seconds per test\n", secs_per_test), secs_per_test); #if PG_O_DIRECT != 0 diff --git a/src/bin/pg_test_fsync/t/001_basic.pl b/src/bin/pg_test_fsync/t/001_basic.pl new file mode 100644 index 000000000000..fe9c295c4976 --- /dev/null +++ b/src/bin/pg_test_fsync/t/001_basic.pl @@ -0,0 +1,25 @@ +use strict; +use warnings; + +use Config; +use TestLib; +use Test::More tests => 12; + +######################################### +# Basic checks + +program_help_ok('pg_test_fsync'); +program_version_ok('pg_test_fsync'); +program_options_handling_ok('pg_test_fsync'); + +######################################### +# Test invalid option combinations + +command_fails_like( + [ 'pg_test_fsync', '--secs-per-test', 'a' ], + qr/\Qpg_test_fsync: error: invalid argument for option --secs-per-test\E/, + 'pg_test_fsync: invalid argument for option --secs-per-test'); +command_fails_like( + [ 'pg_test_fsync', '--secs-per-test', '0' ], + qr/\Qpg_test_fsync: error: --secs-per-test must be in range 1..4294967295\E/, + 'pg_test_fsync: --secs-per-test must be in range'); diff --git a/src/bin/pg_test_timing/.gitignore b/src/bin/pg_test_timing/.gitignore index f6c664c76576..e5aac2ab120f 100644 --- a/src/bin/pg_test_timing/.gitignore +++ b/src/bin/pg_test_timing/.gitignore @@ -1 +1,3 @@ /pg_test_timing + +/tmp_check/ diff --git a/src/bin/pg_test_timing/Makefile b/src/bin/pg_test_timing/Makefile index 334d6ff5c00d..84d84c38aa86 100644 --- a/src/bin/pg_test_timing/Makefile +++ b/src/bin/pg_test_timing/Makefile @@ -22,8 +22,15 @@ install: all installdirs installdirs: $(MKDIR_P) '$(DESTDIR)$(bindir)' +check: + $(prove_check) + +installcheck: + $(prove_installcheck) + uninstall: rm -f '$(DESTDIR)$(bindir)/pg_test_timing$(X)' clean distclean maintainer-clean: rm -f pg_test_timing$(X) $(OBJS) + rm -rf tmp_check diff --git a/src/bin/pg_test_timing/pg_test_timing.c b/src/bin/pg_test_timing/pg_test_timing.c index e14802372bd6..c29d6f876294 100644 --- a/src/bin/pg_test_timing/pg_test_timing.c +++ b/src/bin/pg_test_timing/pg_test_timing.c @@ -6,15 +6,17 @@ #include "postgres_fe.h" +#include + #include "getopt_long.h" #include "portability/instr_time.h" static const char *progname; -static int32 test_duration = 3; +static unsigned int test_duration = 3; static void handle_args(int argc, char *argv[]); -static uint64 test_timing(int32); +static uint64 test_timing(unsigned int duration); static void output(uint64 loop_count); /* record duration in powers of 2 microseconds */ @@ -47,6 +49,8 @@ handle_args(int argc, char *argv[]) int option; /* Command line option */ int optindex = 0; /* used by getopt_long */ + unsigned long optval; /* used for option parsing */ + char *endptr; if (argc > 1) { @@ -68,7 +72,25 @@ handle_args(int argc, char *argv[]) switch (option) { case 'd': - test_duration = atoi(optarg); + errno = 0; + optval = strtoul(optarg, &endptr, 10); + + if (endptr == optarg || *endptr != '\0' || + errno != 0 || optval != (unsigned int) optval) + { + fprintf(stderr, _("%s: invalid argument for option %s\n"), + progname, "--duration"); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); + exit(1); + } + + test_duration = (unsigned int) optval; + if (test_duration == 0) + { + fprintf(stderr, _("%s: %s must be in range %u..%u\n"), + progname, "--duration", 1, UINT_MAX); + exit(1); + } break; default: @@ -89,26 +111,15 @@ handle_args(int argc, char *argv[]) exit(1); } - if (test_duration > 0) - { - printf(ngettext("Testing timing overhead for %d second.\n", - "Testing timing overhead for %d seconds.\n", - test_duration), - test_duration); - } - else - { - fprintf(stderr, - _("%s: duration must be a positive integer (duration is \"%d\")\n"), - progname, test_duration); - fprintf(stderr, _("Try \"%s --help\" for more information.\n"), - progname); - exit(1); - } + + printf(ngettext("Testing timing overhead for %u second.\n", + "Testing timing overhead for %u seconds.\n", + test_duration), + test_duration); } static uint64 -test_timing(int32 duration) +test_timing(unsigned int duration) { uint64 total_time; int64 time_elapsed = 0; diff --git a/src/bin/pg_test_timing/t/001_basic.pl b/src/bin/pg_test_timing/t/001_basic.pl new file mode 100644 index 000000000000..8bad19c7fad9 --- /dev/null +++ b/src/bin/pg_test_timing/t/001_basic.pl @@ -0,0 +1,25 @@ +use strict; +use warnings; + +use Config; +use TestLib; +use Test::More tests => 12; + +######################################### +# Basic checks + +program_help_ok('pg_test_timing'); +program_version_ok('pg_test_timing'); +program_options_handling_ok('pg_test_timing'); + +######################################### +# Test invalid option combinations + +command_fails_like( + [ 'pg_test_timing', '--duration', 'a' ], + qr/\Qpg_test_timing: invalid argument for option --duration\E/, + 'pg_test_timing: invalid argument for option --duration'); +command_fails_like( + [ 'pg_test_timing', '--duration', '0' ], + qr/\Qpg_test_timing: --duration must be in range 1..4294967295\E/, + 'pg_test_timing: --duration must be in range'); diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c index d81fbc4c5828..c7c26585abfa 100644 --- a/src/bin/pg_upgrade/check.c +++ b/src/bin/pg_upgrade/check.c @@ -23,10 +23,12 @@ static void check_is_install_user(ClusterInfo *cluster); static void check_proper_datallowconn(ClusterInfo *cluster); static void check_for_prepared_transactions(ClusterInfo *cluster); static void check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster); +static void check_for_user_defined_postfix_ops(ClusterInfo *cluster); static void check_for_tables_with_oids(ClusterInfo *cluster); static void check_for_reg_data_type_usage(ClusterInfo *cluster); static void check_for_jsonb_9_4_usage(ClusterInfo *cluster); static void check_for_pg_role_prefix(ClusterInfo *cluster); +static void check_for_new_tablespace_dir(ClusterInfo *new_cluster); static char *get_canonical_locale_name(int category, const char *locale); static void check_for_appendonly_materialized_view_with_relfrozenxid(ClusterInfo *cluster); @@ -111,6 +113,13 @@ check_and_dump_old_cluster(bool live_check, char **sequence_script_file_name) if (GET_MAJOR_VERSION(old_cluster.major_version) <= 905) old_GPDB6_check_for_unsupported_sha256_password_hashes(); + /* + * Pre-PG 14 allowed user defined postfix operators, which are not + * supported anymore. Verify there are none, iff applicable. + */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 1300) + check_for_user_defined_postfix_ops(&old_cluster); + /* * Pre-PG 12 allowed tables to be declared WITH OIDS, which is not * supported anymore. Verify there are none, iff applicable. @@ -201,6 +210,8 @@ check_new_cluster(void) check_is_install_user(&new_cluster); check_for_prepared_transactions(&new_cluster); + + check_for_new_tablespace_dir(&new_cluster); } @@ -261,18 +272,10 @@ void output_completion_banner(char *analyze_script_file_name, char *deletion_script_file_name) { - /* Did we copy the free space files? */ - if (GET_MAJOR_VERSION(old_cluster.major_version) >= 804) - pg_log(PG_REPORT, - "Optimizer statistics are not transferred by pg_upgrade so,\n" - "once you start the new server, consider running:\n" - " %s\n\n", analyze_script_file_name); - else - pg_log(PG_REPORT, - "Optimizer statistics and free space information are not transferred\n" - "by pg_upgrade so, once you start the new server, consider running:\n" - " %s\n\n", analyze_script_file_name); - + pg_log(PG_REPORT, + "Optimizer statistics are not transferred by pg_upgrade so,\n" + "once you start the new server, consider running:\n" + " %s\n\n", analyze_script_file_name); if (deletion_script_file_name) pg_log(PG_REPORT, @@ -357,7 +360,7 @@ check_cluster_compatibility(bool live_check) } /* We read the real port number for PG >= 9.1 */ - if (live_check && GET_MAJOR_VERSION(old_cluster.major_version) < 901 && + if (live_check && GET_MAJOR_VERSION(old_cluster.major_version) <= 900 && old_cluster.port == DEF_PGUPORT) pg_fatal("When checking a pre-PG 9.1 live old server, " "you must specify the old server's port number.\n"); @@ -566,19 +569,12 @@ create_script_for_cluster_analyze(char **analyze_script_file_name) ECHO_QUOTE, ECHO_QUOTE); fprintf(script, "echo %sthis script and run:%s\n", ECHO_QUOTE, ECHO_QUOTE); - fprintf(script, "echo %s \"%s/vacuumdb\" %s--all %s%s\n", ECHO_QUOTE, - new_cluster.bindir, user_specification.data, - /* Did we copy the free space files? */ - (GET_MAJOR_VERSION(old_cluster.major_version) >= 804) ? - "--analyze-only" : "--analyze", ECHO_QUOTE); + fprintf(script, "echo %s \"%s/vacuumdb\" %s--all --analyze-only%s\n", ECHO_QUOTE, + new_cluster.bindir, user_specification.data, ECHO_QUOTE); fprintf(script, "echo%s\n\n", ECHO_BLANK); fprintf(script, "\"%s/vacuumdb\" %s--all --analyze-in-stages\n", new_cluster.bindir, user_specification.data); - /* Did we copy the free space files? */ - if (GET_MAJOR_VERSION(old_cluster.major_version) < 804) - fprintf(script, "\"%s/vacuumdb\" %s--all\n", new_cluster.bindir, - user_specification.data); fprintf(script, "echo%s\n\n", ECHO_BLANK); fprintf(script, "echo %sDone%s\n", @@ -597,6 +593,44 @@ create_script_for_cluster_analyze(char **analyze_script_file_name) check_ok(); } +/* + * A previous run of pg_upgrade might have failed and the new cluster + * directory recreated, but they might have forgotten to remove + * the new cluster's tablespace directories. Therefore, check that + * new cluster tablespace directories do not already exist. If + * they do, it would cause an error while restoring global objects. + * This allows the failure to be detected at check time, rather than + * during schema restore. + * + * Note, v8.4 has no tablespace_suffix, which is fine so long as the + * version being upgraded *to* has a suffix, since it's not allowed + * to pg_upgrade from a version to the same version if tablespaces are + * in use. + */ +static void +check_for_new_tablespace_dir(ClusterInfo *new_cluster) +{ + int tblnum; + char new_tablespace_dir[MAXPGPATH]; + + prep_status("Checking for new cluster tablespace directories"); + + for (tblnum = 0; tblnum < os_info.num_old_tablespaces; tblnum++) + { + struct stat statbuf; + + snprintf(new_tablespace_dir, MAXPGPATH, "%s%s", + os_info.old_tablespaces[tblnum], + new_cluster->tablespace_suffix); + + if (stat(new_tablespace_dir, &statbuf) == 0 || errno != ENOENT) + pg_fatal("new cluster tablespace directory already exists: \"%s\"\n", + new_tablespace_dir); + } + + check_ok(); +} + /* * create_script_for_old_cluster_deletion() * @@ -968,6 +1002,104 @@ check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster) check_ok(); } +/* + * Verify that no user defined postfix operators exist. + */ +static void +check_for_user_defined_postfix_ops(ClusterInfo *cluster) +{ + int dbnum; + FILE *script = NULL; + bool found = false; + char output_path[MAXPGPATH]; + + prep_status("Checking for user-defined postfix operators"); + + snprintf(output_path, sizeof(output_path), + "postfix_ops.txt"); + + /* Find any user defined postfix operators */ + for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++) + { + PGresult *res; + bool db_used = false; + int ntups; + int rowno; + int i_oproid, + i_oprnsp, + i_oprname, + i_typnsp, + i_typname; + DbInfo *active_db = &cluster->dbarr.dbs[dbnum]; + PGconn *conn = connectToServer(cluster, active_db->db_name); + + /* + * The query below hardcodes FirstNormalObjectId as 16384 rather than + * interpolating that C #define into the query because, if that + * #define is ever changed, the cutoff we want to use is the value + * used by pre-version 14 servers, not that of some future version. + */ + res = executeQueryOrDie(conn, + "SELECT o.oid AS oproid, " + " n.nspname AS oprnsp, " + " o.oprname, " + " tn.nspname AS typnsp, " + " t.typname " + "FROM pg_catalog.pg_operator o, " + " pg_catalog.pg_namespace n, " + " pg_catalog.pg_type t, " + " pg_catalog.pg_namespace tn " + "WHERE o.oprnamespace = n.oid AND " + " o.oprleft = t.oid AND " + " t.typnamespace = tn.oid AND " + " o.oprright = 0 AND " + " o.oid >= 16384"); + ntups = PQntuples(res); + i_oproid = PQfnumber(res, "oproid"); + i_oprnsp = PQfnumber(res, "oprnsp"); + i_oprname = PQfnumber(res, "oprname"); + i_typnsp = PQfnumber(res, "typnsp"); + i_typname = PQfnumber(res, "typname"); + for (rowno = 0; rowno < ntups; rowno++) + { + found = true; + if (script == NULL && + (script = fopen_priv(output_path, "w")) == NULL) + pg_fatal("could not open file \"%s\": %s\n", + output_path, strerror(errno)); + if (!db_used) + { + fprintf(script, "In database: %s\n", active_db->db_name); + db_used = true; + } + fprintf(script, " (oid=%s) %s.%s (%s.%s, NONE)\n", + PQgetvalue(res, rowno, i_oproid), + PQgetvalue(res, rowno, i_oprnsp), + PQgetvalue(res, rowno, i_oprname), + PQgetvalue(res, rowno, i_typnsp), + PQgetvalue(res, rowno, i_typname)); + } + + PQclear(res); + + PQfinish(conn); + } + + if (script) + fclose(script); + + if (found) + { + pg_log(PG_REPORT, "fatal\n"); + pg_fatal("Your installation contains user-defined postfix operators, which are not\n" + "supported anymore. Consider dropping the postfix operators and replacing\n" + "them with prefix operators or function calls.\n" + "A list of user-defined postfix operators is in the file:\n" + " %s\n\n", output_path); + } + else + check_ok(); +} /* * Verify that no tables are declared WITH OIDS. diff --git a/src/bin/pg_upgrade/controldata.c b/src/bin/pg_upgrade/controldata.c index 359ddc4aaec3..43d00961cd6d 100644 --- a/src/bin/pg_upgrade/controldata.c +++ b/src/bin/pg_upgrade/controldata.c @@ -183,7 +183,7 @@ get_control_data(ClusterInfo *cluster, bool live_check) } /* pg_resetxlog has been renamed to pg_resetwal in version 10 */ - if (GET_MAJOR_VERSION(cluster->bin_version) < 1000) + if (GET_MAJOR_VERSION(cluster->bin_version) <= 906) resetwal_bin = "pg_resetxlog\" -n"; else resetwal_bin = "pg_resetwal\" -n"; diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c index 0b53f775af9e..e4a36974e02e 100644 --- a/src/bin/pg_upgrade/dump.c +++ b/src/bin/pg_upgrade/dump.c @@ -54,10 +54,12 @@ generate_old_dump(void) parallel_exec_prog(log_file_name, NULL, "%s \"%s/pg_dump\" %s --schema-only --quote-all-identifiers " - "--binary-upgrade --format=custom %s --file=\"%s\" %s", + "--binary-upgrade --format=custom %s %s --file=\"%s\" %s", PG_OPTIONS_UTILITY_MODE_VERSION(old_cluster.major_version), new_cluster.bindir, cluster_conn_opts(&old_cluster), log_opts.verbose ? "--verbose" : "", + user_opts.ind_coll_unknown ? + "--index-collation-versions-unknown" : "", sql_file_name, escaped_connstr.data); termPQExpBuffer(&escaped_connstr); diff --git a/src/bin/pg_upgrade/exec.c b/src/bin/pg_upgrade/exec.c index 565a46c30035..f6296c2367b2 100644 --- a/src/bin/pg_upgrade/exec.c +++ b/src/bin/pg_upgrade/exec.c @@ -345,13 +345,13 @@ check_data_dir(ClusterInfo *cluster) check_single_dir(pg_data, "pg_twophase"); /* pg_xlog has been renamed to pg_wal in v10 */ - if (GET_MAJOR_VERSION(cluster->major_version) < 1000) + if (GET_MAJOR_VERSION(cluster->major_version) <= 906) check_single_dir(pg_data, "pg_xlog"); else check_single_dir(pg_data, "pg_wal"); /* pg_clog has been renamed to pg_xact in v10 */ - if (GET_MAJOR_VERSION(cluster->major_version) < 1000) + if (GET_MAJOR_VERSION(cluster->major_version) <= 906) check_single_dir(pg_data, "pg_clog"); else check_single_dir(pg_data, "pg_xact"); @@ -391,7 +391,7 @@ check_bin_dir(ClusterInfo *cluster) get_bin_version(cluster); /* pg_resetxlog has been renamed to pg_resetwal in version 10 */ - if (GET_MAJOR_VERSION(cluster->bin_version) < 1000) + if (GET_MAJOR_VERSION(cluster->bin_version) <= 906) validate_exec(cluster->bindir, "pg_resetxlog"); else validate_exec(cluster->bindir, "pg_resetwal"); diff --git a/src/bin/pg_upgrade/function.c b/src/bin/pg_upgrade/function.c index 4750dfe1ca4d..3eec19e83640 100644 --- a/src/bin/pg_upgrade/function.c +++ b/src/bin/pg_upgrade/function.c @@ -89,7 +89,7 @@ get_loadable_libraries(void) * http://archives.postgresql.org/pgsql-hackers/2012-03/msg01101.php * http://archives.postgresql.org/pgsql-bugs/2012-05/msg00206.php */ - if (GET_MAJOR_VERSION(old_cluster.major_version) < 901) + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 900) { PGresult *res; @@ -217,7 +217,7 @@ check_loadable_libraries(void) * library name "plpython" in an old PG <= 9.1 cluster must look * for "plpython2" in the new cluster. */ - if (GET_MAJOR_VERSION(old_cluster.major_version) < 901 && + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 900 && strcmp(lib, "$libdir/plpython") == 0) { lib = "$libdir/plpython2"; diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c index e072b55c3d4a..4ff2e08f7dee 100644 --- a/src/bin/pg_upgrade/option.c +++ b/src/bin/pg_upgrade/option.c @@ -57,6 +57,7 @@ parseCommandLine(int argc, char *argv[]) {"socketdir", required_argument, NULL, 's'}, {"verbose", no_argument, NULL, 'v'}, {"clone", no_argument, NULL, 1}, + {"index-collation-versions-unknown", no_argument, NULL, 2}, /* Greenplum specific parameters */ GREENPLUM_OPTIONS @@ -210,6 +211,10 @@ parseCommandLine(int argc, char *argv[]) user_opts.transfer_mode = TRANSFER_MODE_CLONE; break; + case 2: + user_opts.ind_coll_unknown = true; + break; + default: if (!process_greenplum_option(option)) { @@ -321,6 +326,8 @@ usage(void) printf(_(" --clone clone instead of copying files to new cluster\n")); printf(_(" --continue-check-on-fatal goes through all pg_upgrade checks; should be used with -c\n")); printf(_(" --skip-target-check skip all checks and comparisons of new cluster; should be used with -c\n")); + printf(_(" --index-collation-versions-unknown\n")); + printf(_(" mark text indexes as needing to be rebuilt\n")); printf(_(" -?, --help show this help, then exit\n")); printf(_("\n" "Before running pg_upgrade you must:\n" diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c index 0e173388b9f9..7f33c1a9e1fa 100644 --- a/src/bin/pg_upgrade/pg_upgrade.c +++ b/src/bin/pg_upgrade/pg_upgrade.c @@ -598,7 +598,7 @@ create_new_objects(void) * We don't have minmxids for databases or relations in pre-9.3 clusters, * so set those after we have restored the schema. */ - if (GET_MAJOR_VERSION(old_cluster.major_version) < 903) + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 902) set_frozenxids(true); /* update new_cluster info now that we have objects in the databases */ @@ -673,9 +673,9 @@ copy_xact_xlog_xid(void) * Copy old commit logs to new data dir. pg_clog has been renamed to * pg_xact in post-10 clusters. */ - copy_subdir_files(GET_MAJOR_VERSION(old_cluster.major_version) < 1000 ? + copy_subdir_files(GET_MAJOR_VERSION(old_cluster.major_version) <= 906 ? "pg_clog" : "pg_xact", - GET_MAJOR_VERSION(new_cluster.major_version) < 1000 ? + GET_MAJOR_VERSION(new_cluster.major_version) <= 906 ? "pg_clog" : "pg_xact"); /* diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h index 237a0650c2c8..814de352cd8d 100644 --- a/src/bin/pg_upgrade/pg_upgrade.h +++ b/src/bin/pg_upgrade/pg_upgrade.h @@ -399,6 +399,7 @@ typedef struct transferMode transfer_mode; /* copy files or link them? */ int jobs; /* number of processes/threads to use */ char *socketdir; /* directory to use for Unix sockets */ + bool ind_coll_unknown; /* mark unknown index collation versions */ } UserOpts; typedef struct diff --git a/src/bin/pg_upgrade/relfilenode.c b/src/bin/pg_upgrade/relfilenode.c index e8485e0444bb..25aa549281e5 100644 --- a/src/bin/pg_upgrade/relfilenode.c +++ b/src/bin/pg_upgrade/relfilenode.c @@ -178,16 +178,12 @@ transfer_single_new_db(FileNameMap *maps, int size, char *old_tablespace) /* transfer primary file */ transfer_relfile(&maps[mapnum], "", vm_must_add_frozenbit); - /* fsm/vm files added in PG 8.4 */ - if (GET_MAJOR_VERSION(old_cluster.major_version) >= 804) - { - /* - * Copy/link any fsm and vm files, if they exist - */ - transfer_relfile(&maps[mapnum], "_fsm", vm_must_add_frozenbit); - if (vm_crashsafe_match) - transfer_relfile(&maps[mapnum], "_vm", vm_must_add_frozenbit); - } + /* + * Copy/link any fsm and vm files, if they exist + */ + transfer_relfile(&maps[mapnum], "_fsm", vm_must_add_frozenbit); + if (vm_crashsafe_match) + transfer_relfile(&maps[mapnum], "_vm", vm_must_add_frozenbit); } } } diff --git a/src/bin/pg_upgrade/server.c b/src/bin/pg_upgrade/server.c index 8a41bab8597a..12682b0c08b6 100644 --- a/src/bin/pg_upgrade/server.c +++ b/src/bin/pg_upgrade/server.c @@ -228,7 +228,7 @@ start_postmaster(ClusterInfo *cluster, bool report_and_exit_on_error) snprintf(socket_string + strlen(socket_string), sizeof(socket_string) - strlen(socket_string), " -c %s='%s'", - (GET_MAJOR_VERSION(cluster->major_version) < 903) ? + (GET_MAJOR_VERSION(cluster->major_version) <= 902) ? "unix_socket_directory" : "unix_socket_directories", cluster->sockdir); #endif diff --git a/src/bin/pg_upgrade/version.c b/src/bin/pg_upgrade/version.c index 4e5d27f76eb2..db1934124ee3 100644 --- a/src/bin/pg_upgrade/version.c +++ b/src/bin/pg_upgrade/version.c @@ -158,33 +158,33 @@ check_for_data_type_usage(ClusterInfo *cluster, const char *typename, /* Ranges were introduced in 9.2 */ if (GET_MAJOR_VERSION(cluster->major_version) >= 902) - appendPQExpBuffer(&querybuf, - " UNION ALL " + appendPQExpBufferStr(&querybuf, + " UNION ALL " /* ranges containing any type selected so far */ - " SELECT t.oid FROM pg_catalog.pg_type t, pg_catalog.pg_range r, x " - " WHERE t.typtype = 'r' AND r.rngtypid = t.oid AND r.rngsubtype = x.oid"); + " SELECT t.oid FROM pg_catalog.pg_type t, pg_catalog.pg_range r, x " + " WHERE t.typtype = 'r' AND r.rngtypid = t.oid AND r.rngsubtype = x.oid"); - appendPQExpBuffer(&querybuf, - " ) foo " - ") " + appendPQExpBufferStr(&querybuf, + " ) foo " + ") " /* now look for stored columns of any such type */ - "SELECT n.nspname, c.relname, a.attname " - "FROM pg_catalog.pg_class c, " - " pg_catalog.pg_namespace n, " - " pg_catalog.pg_attribute a " - "WHERE c.oid = a.attrelid AND " - " NOT a.attisdropped AND " - " a.atttypid IN (SELECT oid FROM oids) AND " - " c.relkind IN (" - CppAsString2(RELKIND_RELATION) ", " - CppAsString2(RELKIND_MATVIEW) ", " - CppAsString2(RELKIND_INDEX) ") AND " - " c.relnamespace = n.oid AND " + "SELECT n.nspname, c.relname, a.attname " + "FROM pg_catalog.pg_class c, " + " pg_catalog.pg_namespace n, " + " pg_catalog.pg_attribute a " + "WHERE c.oid = a.attrelid AND " + " NOT a.attisdropped AND " + " a.atttypid IN (SELECT oid FROM oids) AND " + " c.relkind IN (" + CppAsString2(RELKIND_RELATION) ", " + CppAsString2(RELKIND_MATVIEW) ", " + CppAsString2(RELKIND_INDEX) ") AND " + " c.relnamespace = n.oid AND " /* exclude possible orphaned temp tables */ - " n.nspname !~ '^pg_temp_' AND " - " n.nspname !~ '^pg_toast_temp_' AND " + " n.nspname !~ '^pg_temp_' AND " + " n.nspname !~ '^pg_toast_temp_' AND " /* exclude system catalogs, too */ - " n.nspname NOT IN ('pg_catalog', 'information_schema')"); + " n.nspname NOT IN ('pg_catalog', 'information_schema')"); res = executeQueryOrDie(conn, "%s", querybuf.data); diff --git a/src/bin/pg_verifybackup/parse_manifest.c b/src/bin/pg_verifybackup/parse_manifest.c index faee423c7ece..608e23538bad 100644 --- a/src/bin/pg_verifybackup/parse_manifest.c +++ b/src/bin/pg_verifybackup/parse_manifest.c @@ -325,7 +325,7 @@ json_manifest_object_field_start(void *state, char *fname, bool isnull) /* It's not a field we recognize. */ json_manifest_parse_failure(parse->context, - "unknown toplevel field"); + "unrecognized top-level field"); break; case JM_EXPECT_THIS_FILE_FIELD: @@ -358,7 +358,7 @@ json_manifest_object_field_start(void *state, char *fname, bool isnull) parse->wal_range_field = JMWRF_END_LSN; else json_manifest_parse_failure(parse->context, - "unexpected wal range field"); + "unexpected WAL range field"); parse->state = JM_EXPECT_THIS_WAL_RANGE_VALUE; break; @@ -469,10 +469,10 @@ json_manifest_finalize_file(JsonManifestParseState *parse) /* Pathname and size are required. */ if (parse->pathname == NULL && parse->encoded_pathname == NULL) - json_manifest_parse_failure(parse->context, "missing pathname"); + json_manifest_parse_failure(parse->context, "missing path name"); if (parse->pathname != NULL && parse->encoded_pathname != NULL) json_manifest_parse_failure(parse->context, - "both pathname and encoded pathname"); + "both path name and encoded path name"); if (parse->size == NULL) json_manifest_parse_failure(parse->context, "missing size"); if (parse->algorithm == NULL && parse->checksum != NULL) @@ -491,7 +491,7 @@ json_manifest_finalize_file(JsonManifestParseState *parse) parse->encoded_pathname, raw_length)) json_manifest_parse_failure(parse->context, - "unable to decode filename"); + "could not decode file name"); parse->pathname[raw_length] = '\0'; pfree(parse->encoded_pathname); parse->encoded_pathname = NULL; @@ -582,10 +582,10 @@ json_manifest_finalize_wal_range(JsonManifestParseState *parse) "timeline is not an integer"); if (!parse_xlogrecptr(&start_lsn, parse->start_lsn)) json_manifest_parse_failure(parse->context, - "unable to parse start LSN"); + "could not parse start LSN"); if (!parse_xlogrecptr(&end_lsn, parse->end_lsn)) json_manifest_parse_failure(parse->context, - "unable to parse end LSN"); + "could not parse end LSN"); /* Invoke the callback with the details we've gathered. */ context->perwalrange_cb(context, tli, start_lsn, end_lsn); diff --git a/src/bin/pg_verifybackup/pg_verifybackup.c b/src/bin/pg_verifybackup/pg_verifybackup.c index 70b6ffdec00b..bb3733b57e20 100644 --- a/src/bin/pg_verifybackup/pg_verifybackup.c +++ b/src/bin/pg_verifybackup/pg_verifybackup.c @@ -411,8 +411,8 @@ parse_manifest_file(char *manifest_path, manifest_files_hash **ht_p, report_fatal_error("could not read file \"%s\": %m", manifest_path); else - report_fatal_error("could not read file \"%s\": read %d of %zu", - manifest_path, rc, (size_t) statbuf.st_size); + report_fatal_error("could not read file \"%s\": read %d of %lld", + manifest_path, rc, (long long int) statbuf.st_size); } /* Close the manifest file. */ @@ -471,7 +471,7 @@ record_manifest_details_for_file(JsonManifestParseContext *context, /* Make a new entry in the hash table for this file. */ m = manifest_files_insert(ht, pathname, &found); if (found) - report_fatal_error("duplicate pathname in backup manifest: \"%s\"", + report_fatal_error("duplicate path name in backup manifest: \"%s\"", pathname); /* Initialize the entry. */ @@ -638,8 +638,8 @@ verify_backup_file(verifier_context *context, char *relpath, char *fullpath) if (m->size != sb.st_size) { report_backup_error(context, - "\"%s\" has size %zu on disk but size %zu in the manifest", - relpath, (size_t) sb.st_size, m->size); + "\"%s\" has size %lld on disk but size %zu in the manifest", + relpath, (long long int) sb.st_size, m->size); m->bad = true; } diff --git a/src/bin/pg_verifybackup/t/005_bad_manifest.pl b/src/bin/pg_verifybackup/t/005_bad_manifest.pl index afd64d1a96b0..5bd5556038c3 100644 --- a/src/bin/pg_verifybackup/t/005_bad_manifest.pl +++ b/src/bin/pg_verifybackup/t/005_bad_manifest.pl @@ -38,7 +38,7 @@ {"PostgreSQL-Backup-Manifest-Version": 1, "Files": true} EOM -test_parse_error('unknown toplevel field', < 0); fprintf(stderr, "creating %d partitions...\n", partitions); + initPQExpBuffer(&query); + for (int p = 1; p <= partitions; p++) { - char query[256]; - if (partition_method == PART_RANGE) { int64 part_size = (naccounts * (int64) scale + partitions - 1) / partitions; - char minvalue[32], - maxvalue[32]; + + printfPQExpBuffer(&query, + "create%s table pgbench_accounts_%d\n" + " partition of pgbench_accounts\n" + " for values from (", + unlogged_tables ? " unlogged" : "", p); /* * For RANGE, we use open-ended partitions at the beginning and @@ -3669,34 +3663,39 @@ createPartitions(PGconn *con) * scale, it is more generic and the performance is better. */ if (p == 1) - sprintf(minvalue, "minvalue"); + appendPQExpBufferStr(&query, "minvalue"); else - sprintf(minvalue, INT64_FORMAT, (p - 1) * part_size + 1); + appendPQExpBuffer(&query, INT64_FORMAT, (p - 1) * part_size + 1); + + appendPQExpBufferStr(&query, ") to ("); if (p < partitions) - sprintf(maxvalue, INT64_FORMAT, p * part_size + 1); + appendPQExpBuffer(&query, INT64_FORMAT, p * part_size + 1); else - sprintf(maxvalue, "maxvalue"); - - snprintf(query, sizeof(query), - "create%s table pgbench_accounts_%d\n" - " partition of pgbench_accounts\n" - " for values from (%s) to (%s)%s\n", - unlogged_tables ? " unlogged" : "", p, - minvalue, maxvalue, ff); + appendPQExpBufferStr(&query, "maxvalue"); + + appendPQExpBufferChar(&query, ')'); } else if (partition_method == PART_HASH) - snprintf(query, sizeof(query), - "create%s table pgbench_accounts_%d\n" - " partition of pgbench_accounts\n" - " for values with (modulus %d, remainder %d)%s\n", - unlogged_tables ? " unlogged" : "", p, - partitions, p - 1, ff); + printfPQExpBuffer(&query, + "create%s table pgbench_accounts_%d\n" + " partition of pgbench_accounts\n" + " for values with (modulus %d, remainder %d)", + unlogged_tables ? " unlogged" : "", p, + partitions, p - 1); else /* cannot get there */ Assert(0); - executeStatement(con, query); + /* + * Per ddlinfo in initCreateTables, fillfactor is needed on table + * pgbench_accounts. + */ + appendPQExpBuffer(&query, " with (fillfactor=%d)", fillfactor); + + executeStatement(con, query.data); } + + termPQExpBuffer(&query); } /* @@ -3755,71 +3754,53 @@ initCreateTables(PGconn *con) } }; int i; + PQExpBufferData query; fprintf(stderr, "creating tables...\n"); + initPQExpBuffer(&query); + for (i = 0; i < lengthof(DDLs); i++) { - char opts[256]; - char buffer[256]; const struct ddlinfo *ddl = &DDLs[i]; - const char *cols; /* Construct new create table statement. */ - opts[0] = '\0'; + printfPQExpBuffer(&query, "create%s table %s(%s)", + unlogged_tables ? " unlogged" : "", + ddl->table, + (scale >= SCALE_32BIT_THRESHOLD) ? ddl->bigcols : ddl->smcols); /* Partition pgbench_accounts table */ if (partition_method != PART_NONE && strcmp(ddl->table, "pgbench_accounts") == 0) - snprintf(opts + strlen(opts), sizeof(opts) - strlen(opts), - " partition by %s (aid)", PARTITION_METHOD[partition_method]); + appendPQExpBuffer(&query, + " partition by %s (aid)", PARTITION_METHOD[partition_method]); else if (ddl->declare_fillfactor) + { /* fillfactor is only expected on actual tables */ - append_fillfactor(opts, sizeof(opts)); + appendPQExpBuffer(&query, " with (fillfactor=%d)", fillfactor); + } else - snprintf(opts + strlen(opts), sizeof(opts) - strlen(opts), - " with (%s)", - storage_clause); + appendPQExpBuffer(&query, " with (%s)", storage_clause); if (tablespace != NULL) { char *escape_tablespace; - escape_tablespace = PQescapeIdentifier(con, tablespace, - strlen(tablespace)); - snprintf(opts + strlen(opts), sizeof(opts) - strlen(opts), - " tablespace %s", escape_tablespace); + escape_tablespace = PQescapeIdentifier(con, tablespace, strlen(tablespace)); + appendPQExpBuffer(&query, " tablespace %s", escape_tablespace); PQfreemem(escape_tablespace); } - snprintf(opts + strlen(opts), sizeof(opts) - strlen(opts), - " distributed by (%s)", - ddl->distributed_col); - - cols = (scale >= SCALE_32BIT_THRESHOLD) ? ddl->bigcols : ddl->smcols; + appendPQExpBuffer(&query, " distributed by (%s)", ddl->distributed_col); - snprintf(buffer, sizeof(buffer), "create%s table %s(%s)%s", - unlogged_tables ? " unlogged" : "", - ddl->table, cols, opts); - - executeStatement(con, buffer); + executeStatement(con, query.data); } + termPQExpBuffer(&query); + if (partition_method != PART_NONE) createPartitions(con); } -/* - * add fillfactor percent option. - * - * XXX - As default is 100, it could be removed in this case. - */ -static void -append_fillfactor(char *opts, int len) -{ - snprintf(opts + strlen(opts), len - strlen(opts), - " with (fillfactor=%d, %s)", - fillfactor, storage_clause); -} - /* * Truncate away any old data, in one command in case there are foreign keys */ @@ -3839,7 +3820,7 @@ initTruncateTables(PGconn *con) static void initGenerateDataClientSide(PGconn *con) { - char sql[256]; + PQExpBufferData sql; PGresult *res; int i; int64 k; @@ -3865,6 +3846,8 @@ initGenerateDataClientSide(PGconn *con) /* truncate away any old data */ initTruncateTables(con); + initPQExpBuffer(&sql); + /* * fill branches, tellers, accounts in that order in case foreign keys * already exist @@ -3872,19 +3855,19 @@ initGenerateDataClientSide(PGconn *con) for (i = 0; i < nbranches * scale; i++) { /* "filler" column defaults to NULL */ - snprintf(sql, sizeof(sql), - "insert into pgbench_branches(bid,bbalance) values(%d,0)", - i + 1); - executeStatement(con, sql); + printfPQExpBuffer(&sql, + "insert into pgbench_branches(bid,bbalance) values(%d,0)", + i + 1); + executeStatement(con, sql.data); } for (i = 0; i < ntellers * scale; i++) { /* "filler" column defaults to NULL */ - snprintf(sql, sizeof(sql), - "insert into pgbench_tellers(tid,bid,tbalance) values (%d,%d,0)", - i + 1, i / ntellers + 1); - executeStatement(con, sql); + printfPQExpBuffer(&sql, + "insert into pgbench_tellers(tid,bid,tbalance) values (%d,%d,0)", + i + 1, i / ntellers + 1); + executeStatement(con, sql.data); } /* @@ -3905,10 +3888,10 @@ initGenerateDataClientSide(PGconn *con) int64 j = k + 1; /* "filler" column defaults to blank padded empty string */ - snprintf(sql, sizeof(sql), - INT64_FORMAT "\t" INT64_FORMAT "\t%d\t\n", - j, k / naccounts + 1, 0); - if (PQputline(con, sql)) + printfPQExpBuffer(&sql, + INT64_FORMAT "\t" INT64_FORMAT "\t%d\t\n", + j, k / naccounts + 1, 0); + if (PQputline(con, sql.data)) { pg_log_fatal("PQputline failed"); exit(1); @@ -3970,6 +3953,8 @@ initGenerateDataClientSide(PGconn *con) exit(1); } + termPQExpBuffer(&sql); + executeStatement(con, "commit"); } @@ -3983,7 +3968,7 @@ initGenerateDataClientSide(PGconn *con) static void initGenerateDataServerSide(PGconn *con) { - char sql[256]; + PQExpBufferData sql; fprintf(stderr, "generating data (server-side)...\n"); @@ -3996,24 +3981,28 @@ initGenerateDataServerSide(PGconn *con) /* truncate away any old data */ initTruncateTables(con); - snprintf(sql, sizeof(sql), - "insert into pgbench_branches(bid,bbalance) " - "select bid, 0 " - "from generate_series(1, %d) as bid", nbranches * scale); - executeStatement(con, sql); - - snprintf(sql, sizeof(sql), - "insert into pgbench_tellers(tid,bid,tbalance) " - "select tid, (tid - 1) / %d + 1, 0 " - "from generate_series(1, %d) as tid", ntellers, ntellers * scale); - executeStatement(con, sql); - - snprintf(sql, sizeof(sql), - "insert into pgbench_accounts(aid,bid,abalance,filler) " - "select aid, (aid - 1) / %d + 1, 0, '' " - "from generate_series(1, " INT64_FORMAT ") as aid", - naccounts, (int64) naccounts * scale); - executeStatement(con, sql); + initPQExpBuffer(&sql); + + printfPQExpBuffer(&sql, + "insert into pgbench_branches(bid,bbalance) " + "select bid, 0 " + "from generate_series(1, %d) as bid", nbranches * scale); + executeStatement(con, sql.data); + + printfPQExpBuffer(&sql, + "insert into pgbench_tellers(tid,bid,tbalance) " + "select tid, (tid - 1) / %d + 1, 0 " + "from generate_series(1, %d) as tid", ntellers, ntellers * scale); + executeStatement(con, sql.data); + + printfPQExpBuffer(&sql, + "insert into pgbench_accounts(aid,bid,abalance,filler) " + "select aid, (aid - 1) / %d + 1, 0, '' " + "from generate_series(1, " INT64_FORMAT ") as aid", + naccounts, (int64) naccounts * scale); + executeStatement(con, sql.data); + + termPQExpBuffer(&sql); executeStatement(con, "commit"); } @@ -4050,16 +4039,19 @@ initCreatePKeys(PGconn *con) StaticAssertStmt(lengthof(DDLINDEXes) == lengthof(NON_UNIQUE_INDEX_DDLINDEXes), "NON_UNIQUE_INDEX_DDLINDEXes must have same size as DDLINDEXes"); int i; + PQExpBufferData query; fprintf(stderr, "creating primary keys...\n"); + initPQExpBuffer(&query); + for (i = 0; i < lengthof(DDLINDEXes); i++) { - char buffer[256]; + resetPQExpBuffer(&query); if (use_unique_key) - strlcpy(buffer, DDLINDEXes[i], sizeof(buffer)); + appendPQExpBufferStr(&query, DDLINDEXes[i]); else - strlcpy(buffer, NON_UNIQUE_INDEX_DDLINDEXes[i], sizeof(buffer)); + appendPQExpBufferStr(&query, NON_UNIQUE_INDEX_DDLINDEXes[i]); if (index_tablespace != NULL) { @@ -4067,13 +4059,14 @@ initCreatePKeys(PGconn *con) escape_tablespace = PQescapeIdentifier(con, index_tablespace, strlen(index_tablespace)); - snprintf(buffer + strlen(buffer), sizeof(buffer) - strlen(buffer), - " using index tablespace %s", escape_tablespace); + appendPQExpBuffer(&query, " using index tablespace %s", escape_tablespace); PQfreemem(escape_tablespace); } - executeStatement(con, buffer); + executeStatement(con, query.data); } + + termPQExpBuffer(&query); } /* @@ -4217,7 +4210,7 @@ runInitSteps(const char *initialize_steps) } /* - * Extract pgbench table informations into global variables scale, + * Extract pgbench table information into global variables scale, * partition_method and partitions. */ static void @@ -5556,7 +5549,7 @@ main(int argc, char **argv) pgport = pg_strdup(optarg); break; case 'd': - pg_logging_set_level(PG_LOG_DEBUG); + pg_logging_increase_verbosity(); break; case 'c': benchmarking_option_set = true; diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl index 52009c352429..61b671d54fd6 100644 --- a/src/bin/pgbench/t/001_pgbench_with_server.pl +++ b/src/bin/pgbench/t/001_pgbench_with_server.pl @@ -287,7 +287,7 @@ sub pgbench [], [ qr{ERROR: invalid input syntax for type json}, - qr{(?!extended query with parameters)} + qr{(?!unnamed portal with parameters)} ], 'server parameter logging', { @@ -314,7 +314,7 @@ sub pgbench [], [ qr{ERROR: division by zero}, - qr{CONTEXT: extended query with parameters: \$1 = '1', \$2 = NULL} + qr{CONTEXT: unnamed portal with parameters: \$1 = '1', \$2 = NULL} ], 'server parameter logging', { @@ -328,7 +328,7 @@ sub pgbench [], [ qr{ERROR: invalid input syntax for type json}, - qr[CONTEXT: JSON data, line 1: \{ invalid\.\.\.[\r\n]+extended query with parameters: \$1 = '\{ invalid ', \$2 = '''Valame Dios!'' dijo Sancho; ''no le dije yo a vuestra merced que \.\.\.']m + qr[CONTEXT: JSON data, line 1: \{ invalid\.\.\.[\r\n]+unnamed portal with parameters: \$1 = '\{ invalid ', \$2 = '''Valame Dios!'' dijo Sancho; ''no le dije yo a vuestra merced que \.\.\.']m ], 'server parameter logging', { @@ -356,7 +356,7 @@ sub pgbench [], [ qr{ERROR: division by zero}, - qr{CONTEXT: extended query with parameters: \$1 = '1', \$2 = NULL} + qr{CONTEXT: unnamed portal with parameters: \$1 = '1', \$2 = NULL} ], 'server parameter logging', { @@ -373,7 +373,7 @@ sub pgbench [], [ qr{ERROR: invalid input syntax for type json}, - qr[CONTEXT: JSON data, line 1: \{ invalid\.\.\.[\r\n]+extended query with parameters: \$1 = '\{ invalid ', \$2 = '''Valame Dios!'' dijo Sancho; ''no le dije yo a vuestra merced que mirase bien lo que hacia\?']m + qr[CONTEXT: JSON data, line 1: \{ invalid\.\.\.[\r\n]+unnamed portal with parameters: \$1 = '\{ invalid ', \$2 = '''Valame Dios!'' dijo Sancho; ''no le dije yo a vuestra merced que mirase bien lo que hacia\?']m ], 'server parameter logging', { diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 79ed39e81720..826cc5f8443c 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -26,6 +26,7 @@ #include "command.h" #include "common.h" #include "common/logging.h" +#include "common/string.h" #include "copy.h" #include "crosstabview.h" #include "describe.h" @@ -1964,11 +1965,11 @@ exec_command_password(PsqlScanState scan_state, bool active_branch) { char *opt0 = psql_scan_slash_option(scan_state, OT_SQLID, NULL, true); - char pw1[100]; - char pw2[100]; + char *pw1; + char *pw2; - simple_prompt("Enter new password: ", pw1, sizeof(pw1), false); - simple_prompt("Enter it again: ", pw2, sizeof(pw2), false); + pw1 = simple_prompt("Enter new password: ", false); + pw2 = simple_prompt("Enter it again: ", false); if (strcmp(pw1, pw2) != 0) { @@ -2013,6 +2014,8 @@ exec_command_password(PsqlScanState scan_state, bool active_branch) if (opt0) free(opt0); + free(pw1); + free(pw2); } else ignore_slash_options(scan_state); @@ -2058,8 +2061,7 @@ exec_command_prompt(PsqlScanState scan_state, bool active_branch, if (!pset.inputfile) { - result = (char *) pg_malloc(4096); - simple_prompt(prompt_text, result, 4096, true); + result = simple_prompt(prompt_text, true); } else { @@ -2982,19 +2984,19 @@ copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf) static char * prompt_for_password(const char *username) { - char buf[100]; + char *result; if (username == NULL || username[0] == '\0') - simple_prompt("Password: ", buf, sizeof(buf), false); + result = simple_prompt("Password: ", false); else { char *prompt_text; prompt_text = psprintf(_("Password for user %s: "), username); - simple_prompt(prompt_text, buf, sizeof(buf), false); + result = simple_prompt(prompt_text, false); free(prompt_text); } - return pg_strdup(buf); + return result; } static bool @@ -3009,34 +3011,13 @@ param_is_newly_set(const char *old_val, const char *new_val) return false; } -/* return whether the connection has 'hostaddr' in its conninfo */ -static bool -has_hostaddr(PGconn *conn) -{ - bool used = false; - PQconninfoOption *ciopt = PQconninfo(conn); - - for (PQconninfoOption *p = ciopt; p->keyword != NULL; p++) - { - if (strcmp(p->keyword, "hostaddr") == 0 && p->val != NULL) - { - used = true; - break; - } - } - - PQconninfoFree(ciopt); - return used; -} - /* * do_connect -- handler for \connect * - * Connects to a database with given parameters. Absent an established - * connection, all parameters are required. Given -reuse-previous=off or a - * connection string without -reuse-previous=on, NULL values will pass through - * to PQconnectdbParams(), so the libpq defaults will be used. Otherwise, NULL - * values will be replaced with the ones in the current connection. + * Connects to a database with given parameters. If we are told to re-use + * parameters, parameters from the previous connection are used where the + * command's own options do not supply a value. Otherwise, libpq defaults + * are used. * * In interactive mode, if connection fails with the given parameters, * the old connection will be kept. @@ -3046,28 +3027,26 @@ do_connect(enum trivalue reuse_previous_specification, char *dbname, char *user, char *host, char *port) { PGconn *o_conn = pset.db, - *n_conn; + *n_conn = NULL; + PQconninfoOption *cinfo; + int nconnopts = 0; + bool same_host = false; char *password = NULL; - char *hostaddr = NULL; - bool keep_password; + bool success = true; + bool keep_password = true; bool has_connection_string; bool reuse_previous; - PQExpBufferData connstr; - if (!o_conn && (!dbname || !user || !host || !port)) + has_connection_string = dbname ? + recognized_connection_string(dbname) : false; + + /* Complain if we have additional arguments after a connection string. */ + if (has_connection_string && (user || host || port)) { - /* - * We don't know the supplied connection parameters and don't want to - * connect to the wrong database by using defaults, so require all - * parameters to be specified. - */ - pg_log_error("All connection parameters must be supplied because no " - "database connection exists"); + pg_log_error("Do not give user, host, or port separately when using a connection string"); return false; } - has_connection_string = dbname ? - recognized_connection_string(dbname) : false; switch (reuse_previous_specification) { case TRI_YES: @@ -3081,68 +3060,164 @@ do_connect(enum trivalue reuse_previous_specification, break; } - /* If the old connection does not exist, there is nothing to reuse. */ - if (!o_conn) - reuse_previous = false; - - /* Silently ignore arguments subsequent to a connection string. */ - if (has_connection_string) - { - user = NULL; - host = NULL; - port = NULL; - } - /* - * Grab missing values from the old connection. If we grab host (or host - * is the same as before) and hostaddr was set, grab that too. + * If we intend to re-use connection parameters, collect them out of the + * old connection, then replace individual values as necessary. (We may + * need to resort to looking at pset.dead_conn, if the connection died + * previously.) Otherwise, obtain a PQconninfoOption array containing + * libpq's defaults, and modify that. Note this function assumes that + * PQconninfo, PQconndefaults, and PQconninfoParse will all produce arrays + * containing the same options in the same order. */ if (reuse_previous) { - if (!user) - user = PQuser(o_conn); - if (host && strcmp(host, PQhost(o_conn)) == 0 && - has_hostaddr(o_conn)) - { - hostaddr = PQhostaddr(o_conn); - } - if (!host) + if (o_conn) + cinfo = PQconninfo(o_conn); + else if (pset.dead_conn) + cinfo = PQconninfo(pset.dead_conn); + else { - host = PQhost(o_conn); - if (has_hostaddr(o_conn)) - hostaddr = PQhostaddr(o_conn); + /* This is reachable after a non-interactive \connect failure */ + pg_log_error("No database connection exists to re-use parameters from"); + return false; } - if (!port) - port = PQport(o_conn); } - - /* - * Any change in the parameters read above makes us discard the password. - * We also discard it if we're to use a conninfo rather than the - * positional syntax. - */ - if (has_connection_string) - keep_password = false; else - keep_password = - (user && PQuser(o_conn) && strcmp(user, PQuser(o_conn)) == 0) && - (host && PQhost(o_conn) && strcmp(host, PQhost(o_conn)) == 0) && - (port && PQport(o_conn) && strcmp(port, PQport(o_conn)) == 0); + cinfo = PQconndefaults(); - /* - * Grab missing dbname from old connection. No password discard if this - * changes: passwords aren't (usually) database-specific. - */ - if (!dbname && reuse_previous) + if (cinfo) { - initPQExpBuffer(&connstr); - appendPQExpBufferStr(&connstr, "dbname="); - appendConnStrVal(&connstr, PQdb(o_conn)); - dbname = connstr.data; - /* has_connection_string=true would be a dead store */ + if (has_connection_string) + { + /* Parse the connstring and insert values into cinfo */ + PQconninfoOption *replcinfo; + char *errmsg; + + replcinfo = PQconninfoParse(dbname, &errmsg); + if (replcinfo) + { + PQconninfoOption *ci; + PQconninfoOption *replci; + bool have_password = false; + + for (ci = cinfo, replci = replcinfo; + ci->keyword && replci->keyword; + ci++, replci++) + { + Assert(strcmp(ci->keyword, replci->keyword) == 0); + /* Insert value from connstring if one was provided */ + if (replci->val) + { + /* + * We know that both val strings were allocated by + * libpq, so the least messy way to avoid memory leaks + * is to swap them. + */ + char *swap = replci->val; + + replci->val = ci->val; + ci->val = swap; + + /* + * Check whether connstring provides options affecting + * password re-use. While any change in user, host, + * hostaddr, or port causes us to ignore the old + * connection's password, we don't force that for + * dbname, since passwords aren't database-specific. + */ + if (replci->val == NULL || + strcmp(ci->val, replci->val) != 0) + { + if (strcmp(replci->keyword, "user") == 0 || + strcmp(replci->keyword, "host") == 0 || + strcmp(replci->keyword, "hostaddr") == 0 || + strcmp(replci->keyword, "port") == 0) + keep_password = false; + } + /* Also note whether connstring contains a password. */ + if (strcmp(replci->keyword, "password") == 0) + have_password = true; + } + } + Assert(ci->keyword == NULL && replci->keyword == NULL); + + /* While here, determine how many option slots there are */ + nconnopts = ci - cinfo; + + PQconninfoFree(replcinfo); + + /* + * If the connstring contains a password, tell the loop below + * that we may use it, regardless of other settings (i.e., + * cinfo's password is no longer an "old" password). + */ + if (have_password) + keep_password = true; + + /* Don't let code below try to inject dbname into params. */ + dbname = NULL; + } + else + { + /* PQconninfoParse failed */ + if (errmsg) + { + pg_log_error("%s", errmsg); + PQfreemem(errmsg); + } + else + pg_log_error("out of memory"); + success = false; + } + } + else + { + /* + * If dbname isn't a connection string, then we'll inject it and + * the other parameters into the keyword array below. (We can't + * easily insert them into the cinfo array because of memory + * management issues: PQconninfoFree would misbehave on Windows.) + * However, to avoid dependencies on the order in which parameters + * appear in the array, make a preliminary scan to set + * keep_password and same_host correctly. + * + * While any change in user, host, or port causes us to ignore the + * old connection's password, we don't force that for dbname, + * since passwords aren't database-specific. + */ + PQconninfoOption *ci; + + for (ci = cinfo; ci->keyword; ci++) + { + if (user && strcmp(ci->keyword, "user") == 0) + { + if (!(ci->val && strcmp(user, ci->val) == 0)) + keep_password = false; + } + else if (host && strcmp(ci->keyword, "host") == 0) + { + if (ci->val && strcmp(host, ci->val) == 0) + same_host = true; + else + keep_password = false; + } + else if (port && strcmp(ci->keyword, "port") == 0) + { + if (!(ci->val && strcmp(port, ci->val) == 0)) + keep_password = false; + } + } + + /* While here, determine how many option slots there are */ + nconnopts = ci - cinfo; + } } else - connstr.data = NULL; + { + /* We failed to create the cinfo structure */ + pg_log_error("out of memory"); + success = false; + } /* * If the user asked to be prompted for a password, ask for one now. If @@ -3154,77 +3229,74 @@ do_connect(enum trivalue reuse_previous_specification, * the postmaster's log. But libpq offers no API that would let us obtain * a password and then continue with the first connection attempt. */ - if (pset.getPassword == TRI_YES) + if (pset.getPassword == TRI_YES && success) { /* - * If a connstring or URI is provided, we can't be sure we know which - * username will be used, since we haven't parsed that argument yet. + * If a connstring or URI is provided, we don't know which username + * will be used, since we haven't dug that out of the connstring. * Don't risk issuing a misleading prompt. As in startup.c, it does - * not seem worth working harder, since this getPassword option is + * not seem worth working harder, since this getPassword setting is * normally only used in noninteractive cases. */ password = prompt_for_password(has_connection_string ? NULL : user); } - else if (o_conn && keep_password) - { - password = PQpass(o_conn); - if (password && *password) - password = pg_strdup(password); - else - password = NULL; - } - while (true) + /* Loop till we have a connection or fail, which we might've already */ + while (success) { -#define PARAMS_ARRAY_SIZE 9 - const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords)); - const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values)); - int paramnum = -1; - - keywords[++paramnum] = "host"; - values[paramnum] = host; - if (hostaddr && *hostaddr) - { - keywords[++paramnum] = "hostaddr"; - values[paramnum] = hostaddr; - } - keywords[++paramnum] = "port"; - values[paramnum] = port; - keywords[++paramnum] = "user"; - values[paramnum] = user; + const char **keywords = pg_malloc((nconnopts + 1) * sizeof(*keywords)); + const char **values = pg_malloc((nconnopts + 1) * sizeof(*values)); + int paramnum = 0; + PQconninfoOption *ci; /* - * Position in the array matters when the dbname is a connection - * string, because settings in a connection string override earlier - * array entries only. Thus, user= in the connection string always - * takes effect, but client_encoding= often will not. + * Copy non-default settings into the PQconnectdbParams parameter + * arrays; but inject any values specified old-style, as well as any + * interactively-obtained password, and a couple of fields we want to + * set forcibly. * - * If you change this code, also change the initial-connection code in - * main(). For no good reason, a connection string password= takes - * precedence in main() but not here. + * If you change this code, see also the initial-connection code in + * main(). */ - keywords[++paramnum] = "dbname"; - values[paramnum] = dbname; - keywords[++paramnum] = "password"; - values[paramnum] = password; - keywords[++paramnum] = "fallback_application_name"; - values[paramnum] = pset.progname; - keywords[++paramnum] = "client_encoding"; - values[paramnum] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto"; - + for (ci = cinfo; ci->keyword; ci++) + { + keywords[paramnum] = ci->keyword; + + if (dbname && strcmp(ci->keyword, "dbname") == 0) + values[paramnum++] = dbname; + else if (user && strcmp(ci->keyword, "user") == 0) + values[paramnum++] = user; + else if (host && strcmp(ci->keyword, "host") == 0) + values[paramnum++] = host; + else if (host && !same_host && strcmp(ci->keyword, "hostaddr") == 0) + { + /* If we're changing the host value, drop any old hostaddr */ + values[paramnum++] = NULL; + } + else if (port && strcmp(ci->keyword, "port") == 0) + values[paramnum++] = port; + /* If !keep_password, we unconditionally drop old password */ + else if ((password || !keep_password) && + strcmp(ci->keyword, "password") == 0) + values[paramnum++] = password; + else if (strcmp(ci->keyword, "fallback_application_name") == 0) + values[paramnum++] = pset.progname; + else if (strcmp(ci->keyword, "client_encoding") == 0) + values[paramnum++] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto"; + else if (ci->val) + values[paramnum++] = ci->val; + /* else, don't bother making libpq parse this keyword */ + } /* add array terminator */ - keywords[++paramnum] = NULL; + keywords[paramnum] = NULL; values[paramnum] = NULL; - n_conn = PQconnectdbParams(keywords, values, true); + /* Note we do not want libpq to re-expand the dbname parameter */ + n_conn = PQconnectdbParams(keywords, values, false); pg_free(keywords); pg_free(values); - /* We can immediately discard the password -- no longer needed */ - if (password) - pg_free(password); - if (PQstatus(n_conn) == CONNECTION_OK) break; @@ -3240,9 +3312,28 @@ do_connect(enum trivalue reuse_previous_specification, */ password = prompt_for_password(PQuser(n_conn)); PQfinish(n_conn); + n_conn = NULL; continue; } + /* + * We'll report the error below ... unless n_conn is NULL, indicating + * that libpq didn't have enough memory to make a PGconn. + */ + if (n_conn == NULL) + pg_log_error("out of memory"); + + success = false; + } /* end retry loop */ + + /* Release locally allocated data, whether we succeeded or not */ + if (password) + pg_free(password); + if (cinfo) + PQconninfoFree(cinfo); + + if (!success) + { /* * Failed to connect to the database. In interactive mode, keep the * previous connection to the DB; in scripting mode, close our @@ -3250,7 +3341,11 @@ do_connect(enum trivalue reuse_previous_specification, */ if (pset.cur_cmd_interactive) { - pg_log_info("%s", PQerrorMessage(n_conn)); + if (n_conn) + { + pg_log_info("%s", PQerrorMessage(n_conn)); + PQfinish(n_conn); + } /* pset.db is left unmodified */ if (o_conn) @@ -3258,27 +3353,39 @@ do_connect(enum trivalue reuse_previous_specification, } else { - pg_log_error("\\connect: %s", PQerrorMessage(n_conn)); + if (n_conn) + { + pg_log_error("\\connect: %s", PQerrorMessage(n_conn)); + PQfinish(n_conn); + } + if (o_conn) { /* - * Transition to having no connection. Keep this bit in sync - * with CheckConnection(). + * Transition to having no connection. + * + * Unlike CheckConnection(), we close the old connection + * immediately to prevent its parameters from being re-used. + * This is so that a script cannot accidentally reuse + * parameters it did not expect to. Otherwise, the state + * cleanup should be the same as in CheckConnection(). */ PQfinish(o_conn); pset.db = NULL; ResetCancelConn(); UnsyncVariables(); } + + /* On the same reasoning, release any dead_conn to prevent reuse */ + if (pset.dead_conn) + { + PQfinish(pset.dead_conn); + pset.dead_conn = NULL; + } } - PQfinish(n_conn); - if (connstr.data) - termPQExpBuffer(&connstr); return false; } - if (connstr.data) - termPQExpBuffer(&connstr); /* * Replace the old connection with the new one, and update @@ -3328,8 +3435,15 @@ do_connect(enum trivalue reuse_previous_specification, PQdb(pset.db), PQuser(pset.db)); } + /* Drop no-longer-needed connection(s) */ if (o_conn) PQfinish(o_conn); + if (pset.dead_conn) + { + PQfinish(pset.dead_conn); + pset.dead_conn = NULL; + } + return true; } diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index 6323a35c91ca..ff673665d869 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -313,10 +313,14 @@ CheckConnection(void) fprintf(stderr, _("Failed.\n")); /* - * Transition to having no connection. Keep this bit in sync with - * do_connect(). + * Transition to having no connection; but stash away the failed + * connection so that we can still refer to its parameters in a + * later \connect attempt. Keep the state cleanup here in sync + * with do_connect(). */ - PQfinish(pset.db); + if (pset.dead_conn) + PQfinish(pset.dead_conn); + pset.dead_conn = pset.db; pset.db = NULL; ResetCancelConn(); UnsyncVariables(); diff --git a/src/bin/psql/create_help.pl b/src/bin/psql/create_help.pl index ee82e645832e..60e093bad490 100644 --- a/src/bin/psql/create_help.pl +++ b/src/bin/psql/create_help.pl @@ -63,11 +63,12 @@ struct _helpStruct { - const char *cmd; /* the command name */ - const char *help; /* the help associated with it */ - const char *docbook_id; /* DocBook XML id (for generating URL) */ - void (*syntaxfunc)(PQExpBuffer); /* function that prints the syntax associated with it */ - int nl_count; /* number of newlines in syntax (for pager) */ + const char *cmd; /* the command name */ + const char *help; /* the help associated with it */ + const char *docbook_id; /* DocBook XML id (for generating URL) */ + void (*syntaxfunc) (PQExpBuffer); /* function that prints the + * syntax associated with it */ + int nl_count; /* number of newlines in syntax (for pager) */ }; extern const struct _helpStruct QL_HELP[]; @@ -190,17 +191,17 @@ { my $id = $_; $id =~ s/ /_/g; - print $cfile_handle " { \"$_\", - N_(\"$entries{$_}{cmddesc}\"), - \"$entries{$_}{cmdid}\", - sql_help_$id, - $entries{$_}{nl_count} }, + print $cfile_handle "\t{\"$_\", +\t\tN_(\"$entries{$_}{cmddesc}\"), +\t\t\"$entries{$_}{cmdid}\", +\t\tsql_help_$id, +\t$entries{$_}{nl_count}}, "; } print $cfile_handle " - { NULL, NULL, NULL } /* End of list marker */ +\t{NULL, NULL, NULL}\t\t\t/* End of list marker */ }; "; @@ -210,7 +211,7 @@ #define QL_MAX_CMD_LEN $maxlen /* largest strlen(cmd) */ -#endif /* $define */ +#endif /* $define */ "; close $cfile_handle; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 104c820d6997..5e7e99cb1c7b 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -994,6 +994,10 @@ describeOperators(const char *pattern, bool verbose, bool showSystem) * anyway, for now, because (1) third-party modules may still be following * the old convention, and (2) we'd need to do it anyway when talking to a * pre-9.1 server. + * + * The support for postfix operators in this query is dead code as of + * Postgres 14, but we need to keep it for as long as we support talking + * to pre-v14 servers. */ printfPQExpBuffer(&buf, @@ -3081,8 +3085,13 @@ describeOneTableDetails(const char *schemaname, " a.attnum = s.attnum AND NOT attisdropped)) AS columns,\n" " 'd' = any(stxkind) AS ndist_enabled,\n" " 'f' = any(stxkind) AS deps_enabled,\n" - " 'm' = any(stxkind) AS mcv_enabled\n" - "FROM pg_catalog.pg_statistic_ext stat " + " 'm' = any(stxkind) AS mcv_enabled,\n"); + + if (pset.sversion >= 130000) + appendPQExpBufferStr(&buf, " stxstattarget\n"); + else + appendPQExpBufferStr(&buf, " -1 AS stxstattarget\n"); + appendPQExpBuffer(&buf, "FROM pg_catalog.pg_statistic_ext stat\n" "WHERE stxrelid = '%s'\n" "ORDER BY 1;", oid); @@ -3130,6 +3139,11 @@ describeOneTableDetails(const char *schemaname, PQgetvalue(result, i, 4), PQgetvalue(result, i, 1)); + /* Show the stats target if it's not default */ + if (strcmp(PQgetvalue(result, i, 8), "-1") != 0) + appendPQExpBuffer(&buf, "; STATISTICS %s", + PQgetvalue(result, i, 8)); + printTableAddFooter(&cont, buf.data); } } @@ -4636,7 +4650,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys PGresult *res; printQueryOpt myopt = pset.popt; int cols_so_far; - bool translate_columns[] = {false, false, true, false, false /* Storage */, false, false, false, false, false}; + bool translate_columns[] = {false, false, true, false, false /* Storage */, false, false, false, false, false, false}; /* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */ if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign)) @@ -4692,7 +4706,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys if (isGPDB7000OrLater()) { /* In GPDB7, we can have user defined access method, display the access method name directly */ - appendPQExpBuffer(&buf, ", a.amname as \"%s\"\n", gettext_noop("Storage")); + appendPQExpBuffer(&buf, ", am.amname as \"%s\"\n", gettext_noop("Storage")); } else { @@ -4739,6 +4753,16 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys * to; this might change with future additions to the output columns. */ + /* + * Access methods exist for tables, materialized views and indexes. + * This has been introduced in PostgreSQL 12 for tables. + */ + if (pset.sversion >= 120000 && !pset.hide_tableam && + (showTables || showMatViews || showIndexes)) + appendPQExpBuffer(&buf, + ",\n am.amname as \"%s\"", + gettext_noop("Access Method")); + /* * As of PostgreSQL 9.0, use pg_table_size() to show a more accurate * size of a table, including FSM, VM and TOAST tables. @@ -4762,7 +4786,13 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"); if (showTables && isGPDB7000OrLater()) appendPQExpBufferStr(&buf, - "\n LEFT JOIN pg_catalog.pg_am a ON a.oid = c.relam"); + "\n LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam"); + else + if (pset.sversion >= 120000 && !pset.hide_tableam && + (showTables || showMatViews || showIndexes)) + appendPQExpBufferStr(&buf, + "\n LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam"); + if (showIndexes) appendPQExpBufferStr(&buf, "\n LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid" @@ -6993,7 +7023,7 @@ describeSubscriptions(const char *pattern, bool verbose) PGresult *res; printQueryOpt myopt = pset.popt; static const bool translate_columns[] = {false, false, false, false, - false, false, false}; + false, false, false, false}; if (pset.sversion < 100000) { @@ -7019,11 +7049,13 @@ describeSubscriptions(const char *pattern, bool verbose) if (verbose) { - /* Binary mode is only supported in v14 and higher */ + /* Binary mode and streaming are only supported in v14 and higher */ if (pset.sversion >= 140000) appendPQExpBuffer(&buf, - ", subbinary AS \"%s\"\n", - gettext_noop("Binary")); + ", subbinary AS \"%s\"\n" + ", substream AS \"%s\"\n", + gettext_noop("Binary"), + gettext_noop("Streaming")); appendPQExpBuffer(&buf, ", subsynccommit AS \"%s\"\n" @@ -7135,17 +7167,16 @@ listOperatorClasses(const char *access_method_pattern, " pg_catalog.pg_get_userbyid(c.opcowner) AS \"%s\"\n", gettext_noop("Operator family"), gettext_noop("Owner")); - appendPQExpBuffer(&buf, - "\nFROM pg_catalog.pg_opclass c\n" - " LEFT JOIN pg_catalog.pg_am am on am.oid = c.opcmethod\n" - " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.opcnamespace\n" - " LEFT JOIN pg_catalog.pg_type t ON t.oid = c.opcintype\n" - " LEFT JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace\n" - ); + appendPQExpBufferStr(&buf, + "\nFROM pg_catalog.pg_opclass c\n" + " LEFT JOIN pg_catalog.pg_am am on am.oid = c.opcmethod\n" + " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.opcnamespace\n" + " LEFT JOIN pg_catalog.pg_type t ON t.oid = c.opcintype\n" + " LEFT JOIN pg_catalog.pg_namespace tn ON tn.oid = t.typnamespace\n"); if (verbose) - appendPQExpBuffer(&buf, - " LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = c.opcfamily\n" - " LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n"); + appendPQExpBufferStr(&buf, + " LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = c.opcfamily\n" + " LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n"); if (access_method_pattern) have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern, @@ -7214,11 +7245,10 @@ listOperatorFamilies(const char *access_method_pattern, appendPQExpBuffer(&buf, ",\n pg_catalog.pg_get_userbyid(f.opfowner) AS \"%s\"\n", gettext_noop("Owner")); - appendPQExpBuffer(&buf, - "\nFROM pg_catalog.pg_opfamily f\n" - " LEFT JOIN pg_catalog.pg_am am on am.oid = f.opfmethod\n" - " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n" - ); + appendPQExpBufferStr(&buf, + "\nFROM pg_catalog.pg_opfamily f\n" + " LEFT JOIN pg_catalog.pg_am am on am.oid = f.opfmethod\n" + " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n"); if (access_method_pattern) have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern, @@ -7238,7 +7268,7 @@ listOperatorFamilies(const char *access_method_pattern, "tn.nspname", "t.typname", "pg_catalog.format_type(t.oid, NULL)", "pg_catalog.pg_type_is_visible(t.oid)"); - appendPQExpBuffer(&buf, " )\n"); + appendPQExpBufferStr(&buf, " )\n"); } appendPQExpBufferStr(&buf, "ORDER BY 1, 2;"); @@ -7305,14 +7335,14 @@ listOpFamilyOperators(const char *access_method_pattern, appendPQExpBuffer(&buf, ", ofs.opfname AS \"%s\"\n", gettext_noop("Sort opfamily")); - appendPQExpBuffer(&buf, - "FROM pg_catalog.pg_amop o\n" - " LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = o.amopfamily\n" - " LEFT JOIN pg_catalog.pg_am am ON am.oid = of.opfmethod AND am.oid = o.amopmethod\n" - " LEFT JOIN pg_catalog.pg_namespace nsf ON of.opfnamespace = nsf.oid\n"); + appendPQExpBufferStr(&buf, + "FROM pg_catalog.pg_amop o\n" + " LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = o.amopfamily\n" + " LEFT JOIN pg_catalog.pg_am am ON am.oid = of.opfmethod AND am.oid = o.amopmethod\n" + " LEFT JOIN pg_catalog.pg_namespace nsf ON of.opfnamespace = nsf.oid\n"); if (verbose) - appendPQExpBuffer(&buf, - " LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n"); + appendPQExpBufferStr(&buf, + " LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n"); if (access_method_pattern) have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern, @@ -7391,12 +7421,12 @@ listOpFamilyFunctions(const char *access_method_pattern, ", ap.amproc::pg_catalog.regprocedure AS \"%s\"\n", gettext_noop("Function")); - appendPQExpBuffer(&buf, - "FROM pg_catalog.pg_amproc ap\n" - " LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = ap.amprocfamily\n" - " LEFT JOIN pg_catalog.pg_am am ON am.oid = of.opfmethod\n" - " LEFT JOIN pg_catalog.pg_namespace ns ON of.opfnamespace = ns.oid\n" - " LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n"); + appendPQExpBufferStr(&buf, + "FROM pg_catalog.pg_amproc ap\n" + " LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = ap.amprocfamily\n" + " LEFT JOIN pg_catalog.pg_am am ON am.oid = of.opfmethod\n" + " LEFT JOIN pg_catalog.pg_namespace ns ON of.opfnamespace = ns.oid\n" + " LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n"); if (access_method_pattern) have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern, diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l index d6bcd95d1821..ed7adb18b174 100644 --- a/src/bin/psql/psqlscanslash.l +++ b/src/bin/psql/psqlscanslash.l @@ -783,7 +783,7 @@ evaluate_backtick(PsqlScanState state) initPQExpBuffer(&cmd_output); - fd = popen(cmd, PG_BINARY_R); + fd = popen(cmd, "r"); if (!fd) { pg_log_error("%s: %m", cmd); @@ -824,7 +824,7 @@ evaluate_backtick(PsqlScanState state) /* If no error, transfer result to output_buf */ if (!error) { - /* strip any trailing newline */ + /* strip any trailing newline (but only one) */ if (cmd_output.len > 0 && cmd_output.data[cmd_output.len - 1] == '\n') cmd_output.len--; diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h index 97941aa10c67..9601f6e90ce8 100644 --- a/src/bin/psql/settings.h +++ b/src/bin/psql/settings.h @@ -117,6 +117,13 @@ typedef struct _psqlSettings VariableSpace vars; /* "shell variable" repository */ + /* + * If we get a connection failure, the now-unusable PGconn is stashed here + * until we can successfully reconnect. Never attempt to do anything with + * this PGconn except extract parameters for a \connect attempt. + */ + PGconn *dead_conn; /* previous connection to backend */ + /* * The remaining fields are set by assign hooks associated with entries in * "vars". They should not be set directly except by those hook diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 392b96eb862d..586fcb33661c 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -17,6 +17,7 @@ #include "command.h" #include "common.h" #include "common/logging.h" +#include "common/string.h" #include "describe.h" #include "fe_utils/print.h" #include "getopt_long.h" @@ -119,8 +120,7 @@ main(int argc, char *argv[]) { struct adhoc_opts options; int successResult; - bool have_password = false; - char password[100]; + char *password = NULL; bool new_pass; pg_logging_init(argv[0]); @@ -145,6 +145,7 @@ main(int argc, char *argv[]) pset.progname = get_progname(argv[0]); pset.db = NULL; + pset.dead_conn = NULL; setDecimalLocale(); pset.encoding = PQenv2encoding(); pset.queryFout = stdout; @@ -233,8 +234,7 @@ main(int argc, char *argv[]) * offer a potentially wrong one. Typical uses of this option are * noninteractive anyway. */ - simple_prompt("Password: ", password, sizeof(password), false); - have_password = true; + password = simple_prompt("Password: ", false); } /* loop until we have a password if requested by backend */ @@ -251,7 +251,7 @@ main(int argc, char *argv[]) keywords[2] = "user"; values[2] = options.username; keywords[3] = "password"; - values[3] = have_password ? password : NULL; + values[3] = password; keywords[4] = "dbname"; /* see do_connect() */ values[4] = (options.list_dbs && options.dbname == NULL) ? "postgres" : options.dbname; @@ -269,7 +269,7 @@ main(int argc, char *argv[]) if (PQstatus(pset.db) == CONNECTION_BAD && PQconnectionNeedsPassword(pset.db) && - !have_password && + !password && pset.getPassword != TRI_NO) { /* @@ -287,9 +287,8 @@ main(int argc, char *argv[]) password_prompt = pg_strdup(_("Password: ")); PQfinish(pset.db); - simple_prompt(password_prompt, password, sizeof(password), false); + password = simple_prompt(password_prompt, false); free(password_prompt); - have_password = true; new_pass = true; } } while (new_pass); @@ -444,7 +443,10 @@ main(int argc, char *argv[]) /* clean up */ if (pset.logfile) fclose(pset.logfile); - PQfinish(pset.db); + if (pset.db) + PQfinish(pset.db); + if (pset.dead_conn) + PQfinish(pset.dead_conn); setQFout(NULL); return successResult; diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 53b50fdf8c99..6c1da58511a8 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -47,6 +47,7 @@ #include "catalog/pg_am_d.h" #include "catalog/pg_class_d.h" +#include "catalog/pg_collation_d.h" #include "common.h" #include "libpq-fe.h" #include "pqexpbuffer.h" @@ -827,6 +828,20 @@ static const SchemaQuery Query_for_list_of_statistics = { " (SELECT tgrelid FROM pg_catalog.pg_trigger "\ " WHERE pg_catalog.quote_ident(tgname)='%s')" +/* the silly-looking length condition is just to eat up the current word */ +#define Query_for_list_of_colls_for_one_index \ +" SELECT DISTINCT pg_catalog.quote_ident(coll.collname) " \ +" FROM pg_catalog.pg_depend d, pg_catalog.pg_collation coll, " \ +" pg_catalog.pg_class c" \ +" WHERE (%d = pg_catalog.length('%s'))" \ +" AND d.refclassid = " CppAsString2(CollationRelationId) \ +" AND d.refobjid = coll.oid " \ +" AND d.classid = " CppAsString2(RelationRelationId) \ +" AND d.objid = c.oid " \ +" AND c.relkind = " CppAsString2(RELKIND_INDEX) \ +" AND pg_catalog.pg_table_is_visible(c.oid) " \ +" AND c.relname = '%s'" + #define Query_for_list_of_ts_configurations \ "SELECT pg_catalog.quote_ident(cfgname) FROM pg_catalog.pg_ts_config "\ " WHERE substring(pg_catalog.quote_ident(cfgname),1,%d)='%s'" @@ -1724,14 +1739,15 @@ psql_completion(const char *text, int start, int end) /* ALTER INDEX */ else if (Matches("ALTER", "INDEX", MatchAny)) COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET", - "RESET", "ATTACH PARTITION", "DEPENDS", "NO DEPENDS"); + "RESET", "ATTACH PARTITION", "DEPENDS", "NO DEPENDS", + "ALTER COLLATION"); else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH")) COMPLETE_WITH("PARTITION"); else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH", "PARTITION")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); /* ALTER INDEX ALTER */ else if (Matches("ALTER", "INDEX", MatchAny, "ALTER")) - COMPLETE_WITH("COLUMN"); + COMPLETE_WITH("COLLATION", "COLUMN"); /* ALTER INDEX ALTER COLUMN */ else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN")) { @@ -1774,6 +1790,15 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("ON EXTENSION"); else if (Matches("ALTER", "INDEX", MatchAny, "DEPENDS")) COMPLETE_WITH("ON EXTENSION"); + /* ALTER INDEX ALTER COLLATION */ + else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLLATION")) + { + completion_info_charp = prev3_wd; + COMPLETE_WITH_QUERY(Query_for_list_of_colls_for_one_index); + } + /* ALTER INDEX ALTER COLLATION */ + else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLLATION", MatchAny)) + COMPLETE_WITH("REFRESH VERSION"); /* ALTER LANGUAGE */ else if (Matches("ALTER", "LANGUAGE", MatchAny)) @@ -1983,10 +2008,10 @@ psql_completion(const char *text, int start, int end) */ else if (Matches("ALTER", "TABLE", MatchAny)) COMPLETE_WITH("ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", - "ENABLE", "INHERIT", "NO INHERIT", "RENAME", "RESET", + "ENABLE", "INHERIT", "NO", "RENAME", "RESET", "OWNER TO", "SET", "VALIDATE CONSTRAINT", "REPLICA IDENTITY", "ATTACH PARTITION", - "DETACH PARTITION"); + "DETACH PARTITION", "FORCE ROW LEVEL SECURITY"); /* ALTER TABLE xxx ENABLE */ else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE")) COMPLETE_WITH("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE", @@ -2016,6 +2041,9 @@ psql_completion(const char *text, int start, int end) /* ALTER TABLE xxx INHERIT */ else if (Matches("ALTER", "TABLE", MatchAny, "INHERIT")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, ""); + /* ALTER TABLE xxx NO */ + else if (Matches("ALTER", "TABLE", MatchAny, "NO")) + COMPLETE_WITH("FORCE ROW LEVEL SECURITY", "INHERIT"); /* ALTER TABLE xxx NO INHERIT */ else if (Matches("ALTER", "TABLE", MatchAny, "NO", "INHERIT")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, ""); @@ -2949,7 +2977,8 @@ psql_completion(const char *text, int start, int end) /* DEALLOCATE */ else if (Matches("DEALLOCATE")) - COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements); + COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements + " UNION SELECT 'ALL'"); /* DECLARE */ else if (Matches("DECLARE", MatchAny)) @@ -3114,19 +3143,27 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE"); /* FETCH && MOVE */ - /* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */ + + /* + * Complete FETCH with one of ABSOLUTE, BACKWARD, FORWARD, RELATIVE, ALL, + * NEXT, PRIOR, FIRST, LAST + */ else if (Matches("FETCH|MOVE")) - COMPLETE_WITH("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE"); - /* Complete FETCH with one of ALL, NEXT, PRIOR */ - else if (Matches("FETCH|MOVE", MatchAny)) - COMPLETE_WITH("ALL", "NEXT", "PRIOR"); + COMPLETE_WITH("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE", + "ALL", "NEXT", "PRIOR", "FIRST", "LAST"); + + /* Complete FETCH BACKWARD or FORWARD with one of ALL, FROM, IN */ + else if (Matches("FETCH|MOVE", "BACKWARD|FORWARD")) + COMPLETE_WITH("ALL", "FROM", "IN"); /* - * Complete FETCH with "FROM" or "IN". These are equivalent, + * Complete FETCH with "FROM" or "IN". These are equivalent, * but we may as well tab-complete both: perhaps some users prefer one * variant or the other. */ - else if (Matches("FETCH|MOVE", MatchAny, MatchAny)) + else if (Matches("FETCH|MOVE", "ABSOLUTE|BACKWARD|FORWARD|RELATIVE", + MatchAnyExcept("FROM|IN")) || + Matches("FETCH|MOVE", "ALL|NEXT|PRIOR|FIRST|LAST")) COMPLETE_WITH("FROM", "IN"); /* FOREIGN DATA WRAPPER */ @@ -3331,6 +3368,17 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("FOREIGN SCHEMA"); else if (Matches("IMPORT", "FOREIGN")) COMPLETE_WITH("SCHEMA"); + else if (Matches("IMPORT", "FOREIGN", "SCHEMA", MatchAny)) + COMPLETE_WITH("EXCEPT (", "FROM SERVER", "LIMIT TO ("); + else if (TailMatches("LIMIT", "TO", "(*)") || + TailMatches("EXCEPT", "(*)")) + COMPLETE_WITH("FROM SERVER"); + else if (TailMatches("FROM", "SERVER", MatchAny)) + COMPLETE_WITH("INTO"); + else if (TailMatches("FROM", "SERVER", MatchAny, "INTO")) + COMPLETE_WITH_QUERY(Query_for_list_of_schemas); + else if (TailMatches("FROM", "SERVER", MatchAny, "INTO", MatchAny)) + COMPLETE_WITH("OPTIONS ("); /* INSERT --- can be inside EXPLAIN, RULE, etc */ /* Complete INSERT with "INTO" */ diff --git a/src/bin/scripts/clusterdb.c b/src/bin/scripts/clusterdb.c index 12972de0e91e..2f786e61037b 100644 --- a/src/bin/scripts/clusterdb.c +++ b/src/bin/scripts/clusterdb.c @@ -17,15 +17,10 @@ #include "fe_utils/string_utils.h" -static void cluster_one_database(const char *dbname, bool verbose, const char *table, - const char *host, const char *port, - const char *username, enum trivalue prompt_password, - const char *progname, bool echo); -static void cluster_all_databases(bool verbose, const char *maintenance_db, - const char *host, const char *port, - const char *username, enum trivalue prompt_password, - const char *progname, bool echo, bool quiet); - +static void cluster_one_database(const ConnParams *cparams, const char *table, + const char *progname, bool verbose, bool echo); +static void cluster_all_databases(ConnParams *cparams, const char *progname, + bool verbose, bool echo, bool quiet); static void help(const char *progname); @@ -58,6 +53,7 @@ main(int argc, char *argv[]) char *port = NULL; char *username = NULL; enum trivalue prompt_password = TRI_DEFAULT; + ConnParams cparams; bool echo = false; bool quiet = false; bool alldb = false; @@ -134,6 +130,13 @@ main(int argc, char *argv[]) exit(1); } + /* fill cparams except for dbname, which is set below */ + cparams.pghost = host; + cparams.pgport = port; + cparams.pguser = username; + cparams.prompt_password = prompt_password; + cparams.override_dbname = NULL; + setup_cancel_handler(NULL); if (alldb) @@ -150,8 +153,9 @@ main(int argc, char *argv[]) exit(1); } - cluster_all_databases(verbose, maintenance_db, host, port, username, prompt_password, - progname, echo, quiet); + cparams.dbname = maintenance_db; + + cluster_all_databases(&cparams, progname, verbose, echo, quiet); } else { @@ -165,21 +169,21 @@ main(int argc, char *argv[]) dbname = get_user_name_or_exit(progname); } + cparams.dbname = dbname; + if (tables.head != NULL) { SimpleStringListCell *cell; for (cell = tables.head; cell; cell = cell->next) { - cluster_one_database(dbname, verbose, cell->val, - host, port, username, prompt_password, - progname, echo); + cluster_one_database(&cparams, cell->val, + progname, verbose, echo); } } else - cluster_one_database(dbname, verbose, NULL, - host, port, username, prompt_password, - progname, echo); + cluster_one_database(&cparams, NULL, + progname, verbose, echo); } exit(0); @@ -187,17 +191,14 @@ main(int argc, char *argv[]) static void -cluster_one_database(const char *dbname, bool verbose, const char *table, - const char *host, const char *port, - const char *username, enum trivalue prompt_password, - const char *progname, bool echo) +cluster_one_database(const ConnParams *cparams, const char *table, + const char *progname, bool verbose, bool echo) { PQExpBufferData sql; PGconn *conn; - conn = connectDatabase(dbname, host, port, username, prompt_password, - progname, echo, false, false); + conn = connectDatabase(cparams, progname, echo, false, false); initPQExpBuffer(&sql); @@ -228,22 +229,17 @@ cluster_one_database(const char *dbname, bool verbose, const char *table, static void -cluster_all_databases(bool verbose, const char *maintenance_db, - const char *host, const char *port, - const char *username, enum trivalue prompt_password, - const char *progname, bool echo, bool quiet) +cluster_all_databases(ConnParams *cparams, const char *progname, + bool verbose, bool echo, bool quiet) { PGconn *conn; PGresult *result; - PQExpBufferData connstr; int i; - conn = connectMaintenanceDatabase(maintenance_db, host, port, username, - prompt_password, progname, echo); + conn = connectMaintenanceDatabase(cparams, progname, echo); result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", echo); PQfinish(conn); - initPQExpBuffer(&connstr); for (i = 0; i < PQntuples(result); i++) { char *dbname = PQgetvalue(result, i, 0); @@ -254,15 +250,10 @@ cluster_all_databases(bool verbose, const char *maintenance_db, fflush(stdout); } - resetPQExpBuffer(&connstr); - appendPQExpBufferStr(&connstr, "dbname="); - appendConnStrVal(&connstr, dbname); + cparams->override_dbname = dbname; - cluster_one_database(connstr.data, verbose, NULL, - host, port, username, prompt_password, - progname, echo); + cluster_one_database(cparams, NULL, progname, verbose, echo); } - termPQExpBuffer(&connstr); PQclear(result); } diff --git a/src/bin/scripts/common.c b/src/bin/scripts/common.c index 43e54a236b96..ea559789f096 100644 --- a/src/bin/scripts/common.c +++ b/src/bin/scripts/common.c @@ -20,6 +20,7 @@ #include "common.h" #include "common/connect.h" #include "common/logging.h" +#include "common/string.h" #include "fe_utils/cancel.h" #include "fe_utils/string_utils.h" @@ -53,7 +54,7 @@ handle_help_version_opts(int argc, char *argv[], * Make a database connection with the given parameters. * * An interactive password prompt is automatically issued if needed and - * allowed by prompt_password. + * allowed by cparams->prompt_password. * * If allow_password_reuse is true, we will try to re-use any password * given during previous calls to this routine. (Callers should not pass @@ -61,48 +62,60 @@ handle_help_version_opts(int argc, char *argv[], * as before, else we might create password exposure hazards.) */ PGconn * -connectDatabase(const char *dbname, const char *pghost, - const char *pgport, const char *pguser, - enum trivalue prompt_password, const char *progname, +connectDatabase(const ConnParams *cparams, const char *progname, bool echo, bool fail_ok, bool allow_password_reuse) { PGconn *conn; bool new_pass; - static bool have_password = false; - static char password[100]; + static char *password = NULL; - if (!allow_password_reuse) - have_password = false; + /* Callers must supply at least dbname; other params can be NULL */ + Assert(cparams->dbname); - if (!have_password && prompt_password == TRI_YES) + if (!allow_password_reuse && password) { - simple_prompt("Password: ", password, sizeof(password), false); - have_password = true; + free(password); + password = NULL; } + if (cparams->prompt_password == TRI_YES && password == NULL) + password = simple_prompt("Password: ", false); + /* * Start the connection. Loop until we have a password if requested by * backend. */ do { - const char *keywords[7]; - const char *values[7]; - - keywords[0] = "host"; - values[0] = pghost; - keywords[1] = "port"; - values[1] = pgport; - keywords[2] = "user"; - values[2] = pguser; - keywords[3] = "password"; - values[3] = have_password ? password : NULL; - keywords[4] = "dbname"; - values[4] = dbname; - keywords[5] = "fallback_application_name"; - values[5] = progname; - keywords[6] = NULL; - values[6] = NULL; + const char *keywords[8]; + const char *values[8]; + int i = 0; + + /* + * If dbname is a connstring, its entries can override the other + * values obtained from cparams; but in turn, override_dbname can + * override the dbname component of it. + */ + keywords[i] = "host"; + values[i++] = cparams->pghost; + keywords[i] = "port"; + values[i++] = cparams->pgport; + keywords[i] = "user"; + values[i++] = cparams->pguser; + keywords[i] = "password"; + values[i++] = password; + keywords[i] = "dbname"; + values[i++] = cparams->dbname; + if (cparams->override_dbname) + { + keywords[i] = "dbname"; + values[i++] = cparams->override_dbname; + } + keywords[i] = "fallback_application_name"; + values[i++] = progname; + keywords[i] = NULL; + values[i++] = NULL; + Assert(i <= lengthof(keywords)); new_pass = false; conn = PQconnectdbParams(keywords, values, true); @@ -110,7 +123,7 @@ connectDatabase(const char *dbname, const char *pghost, if (!conn) { pg_log_error("could not connect to database %s: out of memory", - dbname); + cparams->dbname); exit(1); } @@ -119,11 +132,12 @@ connectDatabase(const char *dbname, const char *pghost, */ if (PQstatus(conn) == CONNECTION_BAD && PQconnectionNeedsPassword(conn) && - prompt_password != TRI_NO) + cparams->prompt_password != TRI_NO) { PQfinish(conn); - simple_prompt("Password: ", password, sizeof(password), false); - have_password = true; + if (password) + free(password); + password = simple_prompt("Password: ", false); new_pass = true; } } while (new_pass); @@ -137,10 +151,11 @@ connectDatabase(const char *dbname, const char *pghost, return NULL; } pg_log_error("could not connect to database %s: %s", - dbname, PQerrorMessage(conn)); + cparams->dbname, PQerrorMessage(conn)); exit(1); } + /* Start strict; callers may override this. */ PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, echo)); return conn; @@ -148,27 +163,30 @@ connectDatabase(const char *dbname, const char *pghost, /* * Try to connect to the appropriate maintenance database. + * + * This differs from connectDatabase only in that it has a rule for + * inserting a default "dbname" if none was given (which is why cparams + * is not const). Note that cparams->dbname should typically come from + * a --maintenance-db command line parameter. */ PGconn * -connectMaintenanceDatabase(const char *maintenance_db, - const char *pghost, const char *pgport, - const char *pguser, enum trivalue prompt_password, +connectMaintenanceDatabase(ConnParams *cparams, const char *progname, bool echo) { PGconn *conn; /* If a maintenance database name was specified, just connect to it. */ - if (maintenance_db) - return connectDatabase(maintenance_db, pghost, pgport, pguser, - prompt_password, progname, echo, false, false); + if (cparams->dbname) + return connectDatabase(cparams, progname, echo, false, false); /* Otherwise, try postgres first and then template1. */ - conn = connectDatabase("postgres", pghost, pgport, pguser, prompt_password, - progname, echo, true, false); + cparams->dbname = "postgres"; + conn = connectDatabase(cparams, progname, echo, true, false); if (!conn) - conn = connectDatabase("template1", pghost, pgport, pguser, - prompt_password, progname, echo, false, false); - + { + cparams->dbname = "template1"; + conn = connectDatabase(cparams, progname, echo, false, false); + } return conn; } @@ -444,14 +462,21 @@ yesno_prompt(const char *question) for (;;) { - char resp[10]; + char *resp; - simple_prompt(prompt, resp, sizeof(resp), true); + resp = simple_prompt(prompt, true); if (strcmp(resp, _(PG_YESLETTER)) == 0) + { + free(resp); return true; + } if (strcmp(resp, _(PG_NOLETTER)) == 0) + { + free(resp); return false; + } + free(resp); printf(_("Please answer \"%s\" or \"%s\".\n"), _(PG_YESLETTER), _(PG_NOLETTER)); diff --git a/src/bin/scripts/common.h b/src/bin/scripts/common.h index ddf6320b47c8..9ec57cdd87c0 100644 --- a/src/bin/scripts/common.h +++ b/src/bin/scripts/common.h @@ -21,20 +21,32 @@ enum trivalue TRI_YES }; +/* Parameters needed by connectDatabase/connectMaintenanceDatabase */ +typedef struct _connParams +{ + /* These fields record the actual command line parameters */ + const char *dbname; /* this may be a connstring! */ + const char *pghost; + const char *pgport; + const char *pguser; + enum trivalue prompt_password; + /* If not NULL, this overrides the dbname obtained from command line */ + /* (but *only* the DB name, not anything else in the connstring) */ + const char *override_dbname; +} ConnParams; + typedef void (*help_handler) (const char *progname); extern void handle_help_version_opts(int argc, char *argv[], const char *fixed_progname, help_handler hlp); -extern PGconn *connectDatabase(const char *dbname, const char *pghost, - const char *pgport, const char *pguser, - enum trivalue prompt_password, const char *progname, - bool echo, bool fail_ok, bool allow_password_reuse); +extern PGconn *connectDatabase(const ConnParams *cparams, + const char *progname, + bool echo, bool fail_ok, + bool allow_password_reuse); -extern PGconn *connectMaintenanceDatabase(const char *maintenance_db, - const char *pghost, const char *pgport, - const char *pguser, enum trivalue prompt_password, +extern PGconn *connectMaintenanceDatabase(ConnParams *cparams, const char *progname, bool echo); extern void disconnectDatabase(PGconn *conn); diff --git a/src/bin/scripts/createdb.c b/src/bin/scripts/createdb.c index 1353af97c49e..91e6e2194bd7 100644 --- a/src/bin/scripts/createdb.c +++ b/src/bin/scripts/createdb.c @@ -51,6 +51,7 @@ main(int argc, char *argv[]) char *port = NULL; char *username = NULL; enum trivalue prompt_password = TRI_DEFAULT; + ConnParams cparams; bool echo = false; char *owner = NULL; char *tablespace = NULL; @@ -180,8 +181,14 @@ main(int argc, char *argv[]) if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0) maintenance_db = "template1"; - conn = connectMaintenanceDatabase(maintenance_db, host, port, username, - prompt_password, progname, echo); + cparams.dbname = maintenance_db; + cparams.pghost = host; + cparams.pgport = port; + cparams.pguser = username; + cparams.prompt_password = prompt_password; + cparams.override_dbname = NULL; + + conn = connectMaintenanceDatabase(&cparams, progname, echo); initPQExpBuffer(&sql); diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c index 9ced079ac759..d6b56f15c3b6 100644 --- a/src/bin/scripts/createuser.c +++ b/src/bin/scripts/createuser.c @@ -13,6 +13,7 @@ #include "postgres_fe.h" #include "common.h" #include "common/logging.h" +#include "common/string.h" #include "fe_utils/simple_list.h" #include "fe_utils/string_utils.h" @@ -58,13 +59,12 @@ main(int argc, char *argv[]) char *username = NULL; SimpleStringList roles = {NULL, NULL}; enum trivalue prompt_password = TRI_DEFAULT; + ConnParams cparams; bool echo = false; bool interactive = false; int conn_limit = -2; /* less than minimum valid value */ bool pwprompt = false; char *newpassword = NULL; - char newuser_buf[128]; - char newpassword_buf[100]; /* Tri-valued variables. */ enum trivalue createdb = TRI_DEFAULT, @@ -191,9 +191,7 @@ main(int argc, char *argv[]) { if (interactive) { - simple_prompt("Enter name of role to add: ", - newuser_buf, sizeof(newuser_buf), true); - newuser = newuser_buf; + newuser = simple_prompt("Enter name of role to add: ", true); } else { @@ -206,17 +204,16 @@ main(int argc, char *argv[]) if (pwprompt) { - char pw2[100]; + char *pw2; - simple_prompt("Enter password for new role: ", - newpassword_buf, sizeof(newpassword_buf), false); - simple_prompt("Enter it again: ", pw2, sizeof(pw2), false); - if (strcmp(newpassword_buf, pw2) != 0) + newpassword = simple_prompt("Enter password for new role: ", false); + pw2 = simple_prompt("Enter it again: ", false); + if (strcmp(newpassword, pw2) != 0) { fprintf(stderr, _("Passwords didn't match.\n")); exit(1); } - newpassword = newpassword_buf; + free(pw2); } if (superuser == 0) @@ -256,8 +253,14 @@ main(int argc, char *argv[]) if (login == 0) login = TRI_YES; - conn = connectDatabase("postgres", host, port, username, prompt_password, - progname, echo, false, false); + cparams.dbname = NULL; /* this program lacks any dbname option... */ + cparams.pghost = host; + cparams.pgport = port; + cparams.pguser = username; + cparams.prompt_password = prompt_password; + cparams.override_dbname = NULL; + + conn = connectMaintenanceDatabase(&cparams, progname, echo); initPQExpBuffer(&sql); diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c index 581c7749c86a..ccbf78e91a86 100644 --- a/src/bin/scripts/dropdb.c +++ b/src/bin/scripts/dropdb.c @@ -48,6 +48,7 @@ main(int argc, char *argv[]) char *port = NULL; char *username = NULL; enum trivalue prompt_password = TRI_DEFAULT; + ConnParams cparams; bool echo = false; bool interactive = false; bool force = false; @@ -137,9 +138,14 @@ main(int argc, char *argv[]) if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0) maintenance_db = "template1"; - conn = connectMaintenanceDatabase(maintenance_db, - host, port, username, prompt_password, - progname, echo); + cparams.dbname = maintenance_db; + cparams.pghost = host; + cparams.pgport = port; + cparams.pguser = username; + cparams.prompt_password = prompt_password; + cparams.override_dbname = NULL; + + conn = connectMaintenanceDatabase(&cparams, progname, echo); if (echo) printf("%s\n", sql.data); diff --git a/src/bin/scripts/dropuser.c b/src/bin/scripts/dropuser.c index fee270d4f6d1..73d7328a88d5 100644 --- a/src/bin/scripts/dropuser.c +++ b/src/bin/scripts/dropuser.c @@ -13,6 +13,7 @@ #include "postgres_fe.h" #include "common.h" #include "common/logging.h" +#include "common/string.h" #include "fe_utils/string_utils.h" @@ -45,9 +46,9 @@ main(int argc, char *argv[]) char *port = NULL; char *username = NULL; enum trivalue prompt_password = TRI_DEFAULT; + ConnParams cparams; bool echo = false; bool interactive = false; - char dropuser_buf[128]; PQExpBufferData sql; @@ -112,9 +113,7 @@ main(int argc, char *argv[]) { if (interactive) { - simple_prompt("Enter name of role to drop: ", - dropuser_buf, sizeof(dropuser_buf), true); - dropuser = dropuser_buf; + dropuser = simple_prompt("Enter name of role to drop: ", true); } else { @@ -131,13 +130,19 @@ main(int argc, char *argv[]) exit(0); } + cparams.dbname = NULL; /* this program lacks any dbname option... */ + cparams.pghost = host; + cparams.pgport = port; + cparams.pguser = username; + cparams.prompt_password = prompt_password; + cparams.override_dbname = NULL; + + conn = connectMaintenanceDatabase(&cparams, progname, echo); + initPQExpBuffer(&sql); appendPQExpBuffer(&sql, "DROP ROLE %s%s;", (if_exists ? "IF EXISTS " : ""), fmtId(dropuser)); - conn = connectDatabase("postgres", host, port, username, prompt_password, - progname, echo, false, false); - if (echo) printf("%s\n", sql.data); result = PQexec(conn, sql.data); diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c index 4282e33e6dfd..0185fe658558 100644 --- a/src/bin/scripts/reindexdb.c +++ b/src/bin/scripts/reindexdb.c @@ -34,15 +34,12 @@ static SimpleStringList *get_parallel_object_list(PGconn *conn, ReindexType type, SimpleStringList *user_list, bool echo); -static void reindex_one_database(const char *dbname, ReindexType type, - SimpleStringList *user_list, const char *host, - const char *port, const char *username, - enum trivalue prompt_password, const char *progname, +static void reindex_one_database(const ConnParams *cparams, ReindexType type, + SimpleStringList *user_list, + const char *progname, bool echo, bool verbose, bool concurrently, int concurrentCons); -static void reindex_all_databases(const char *maintenance_db, - const char *host, const char *port, - const char *username, enum trivalue prompt_password, +static void reindex_all_databases(ConnParams *cparams, const char *progname, bool echo, bool quiet, bool verbose, bool concurrently, int concurrentCons); @@ -86,6 +83,7 @@ main(int argc, char *argv[]) const char *port = NULL; const char *username = NULL; enum trivalue prompt_password = TRI_DEFAULT; + ConnParams cparams; bool syscatalog = false; bool alldb = false; bool echo = false; @@ -188,6 +186,13 @@ main(int argc, char *argv[]) exit(1); } + /* fill cparams except for dbname, which is set below */ + cparams.pghost = host; + cparams.pgport = port; + cparams.pguser = username; + cparams.prompt_password = prompt_password; + cparams.override_dbname = NULL; + setup_cancel_handler(NULL); if (alldb) @@ -218,8 +223,9 @@ main(int argc, char *argv[]) exit(1); } - reindex_all_databases(maintenance_db, host, port, username, - prompt_password, progname, echo, quiet, verbose, + cparams.dbname = maintenance_db; + + reindex_all_databases(&cparams, progname, echo, quiet, verbose, concurrently, concurrentCons); } else if (syscatalog) @@ -256,9 +262,11 @@ main(int argc, char *argv[]) dbname = get_user_name_or_exit(progname); } - reindex_one_database(dbname, REINDEX_SYSTEM, NULL, host, - port, username, prompt_password, progname, - echo, verbose, concurrently, 1); + cparams.dbname = dbname; + + reindex_one_database(&cparams, REINDEX_SYSTEM, NULL, + progname, echo, verbose, + concurrently, 1); } else { @@ -283,40 +291,40 @@ main(int argc, char *argv[]) dbname = get_user_name_or_exit(progname); } + cparams.dbname = dbname; + if (schemas.head != NULL) - reindex_one_database(dbname, REINDEX_SCHEMA, &schemas, host, - port, username, prompt_password, progname, - echo, verbose, concurrently, concurrentCons); + reindex_one_database(&cparams, REINDEX_SCHEMA, &schemas, + progname, echo, verbose, + concurrently, concurrentCons); if (indexes.head != NULL) - reindex_one_database(dbname, REINDEX_INDEX, &indexes, host, - port, username, prompt_password, progname, - echo, verbose, concurrently, 1); + reindex_one_database(&cparams, REINDEX_INDEX, &indexes, + progname, echo, verbose, + concurrently, 1); if (tables.head != NULL) - reindex_one_database(dbname, REINDEX_TABLE, &tables, host, - port, username, prompt_password, progname, - echo, verbose, concurrently, - concurrentCons); + reindex_one_database(&cparams, REINDEX_TABLE, &tables, + progname, echo, verbose, + concurrently, concurrentCons); /* * reindex database only if neither index nor table nor schema is * specified */ if (indexes.head == NULL && tables.head == NULL && schemas.head == NULL) - reindex_one_database(dbname, REINDEX_DATABASE, NULL, host, - port, username, prompt_password, progname, - echo, verbose, concurrently, concurrentCons); + reindex_one_database(&cparams, REINDEX_DATABASE, NULL, + progname, echo, verbose, + concurrently, concurrentCons); } exit(0); } static void -reindex_one_database(const char *dbname, ReindexType type, - SimpleStringList *user_list, const char *host, - const char *port, const char *username, - enum trivalue prompt_password, const char *progname, bool echo, +reindex_one_database(const ConnParams *cparams, ReindexType type, + SimpleStringList *user_list, + const char *progname, bool echo, bool verbose, bool concurrently, int concurrentCons) { PGconn *conn; @@ -328,8 +336,7 @@ reindex_one_database(const char *dbname, ReindexType type, bool failed = false; int items_count = 0; - conn = connectDatabase(dbname, host, port, username, prompt_password, - progname, echo, false, false); + conn = connectDatabase(cparams, progname, echo, false, false); /* * GPDB_12_MERGE_FIXME: do we still report this as PostgreSQL 12 or should @@ -440,8 +447,7 @@ reindex_one_database(const char *dbname, ReindexType type, Assert(process_list != NULL); - slots = ParallelSlotsSetup(dbname, host, port, username, prompt_password, - progname, echo, conn, concurrentCons); + slots = ParallelSlotsSetup(cparams, progname, echo, conn, concurrentCons); cell = process_list->head; do @@ -618,16 +624,16 @@ get_parallel_object_list(PGconn *conn, ReindexType type, { case REINDEX_DATABASE: Assert(user_list == NULL); - appendPQExpBuffer(&catalog_query, - "SELECT c.relname, ns.nspname\n" - " FROM pg_catalog.pg_class c\n" - " JOIN pg_catalog.pg_namespace ns" - " ON c.relnamespace = ns.oid\n" - " WHERE ns.nspname != 'pg_catalog'\n" - " AND c.relkind IN (" - CppAsString2(RELKIND_RELATION) ", " - CppAsString2(RELKIND_MATVIEW) ")\n" - " ORDER BY c.relpages DESC;"); + appendPQExpBufferStr(&catalog_query, + "SELECT c.relname, ns.nspname\n" + " FROM pg_catalog.pg_class c\n" + " JOIN pg_catalog.pg_namespace ns" + " ON c.relnamespace = ns.oid\n" + " WHERE ns.nspname != 'pg_catalog'\n" + " AND c.relkind IN (" + CppAsString2(RELKIND_RELATION) ", " + CppAsString2(RELKIND_MATVIEW) ")\n" + " ORDER BY c.relpages DESC;"); break; case REINDEX_SCHEMA: @@ -641,30 +647,30 @@ get_parallel_object_list(PGconn *conn, ReindexType type, * All the tables from all the listed schemas are grabbed at * once. */ - appendPQExpBuffer(&catalog_query, - "SELECT c.relname, ns.nspname\n" - " FROM pg_catalog.pg_class c\n" - " JOIN pg_catalog.pg_namespace ns" - " ON c.relnamespace = ns.oid\n" - " WHERE c.relkind IN (" - CppAsString2(RELKIND_RELATION) ", " - CppAsString2(RELKIND_MATVIEW) ")\n" - " AND ns.nspname IN ("); + appendPQExpBufferStr(&catalog_query, + "SELECT c.relname, ns.nspname\n" + " FROM pg_catalog.pg_class c\n" + " JOIN pg_catalog.pg_namespace ns" + " ON c.relnamespace = ns.oid\n" + " WHERE c.relkind IN (" + CppAsString2(RELKIND_RELATION) ", " + CppAsString2(RELKIND_MATVIEW) ")\n" + " AND ns.nspname IN ("); for (cell = user_list->head; cell; cell = cell->next) { const char *nspname = cell->val; if (nsp_listed) - appendPQExpBuffer(&catalog_query, ", "); + appendPQExpBufferStr(&catalog_query, ", "); else nsp_listed = true; appendStringLiteralConn(&catalog_query, nspname, conn); } - appendPQExpBuffer(&catalog_query, ")\n" - " ORDER BY c.relpages DESC;"); + appendPQExpBufferStr(&catalog_query, ")\n" + " ORDER BY c.relpages DESC;"); } break; @@ -709,23 +715,18 @@ get_parallel_object_list(PGconn *conn, ReindexType type, } static void -reindex_all_databases(const char *maintenance_db, - const char *host, const char *port, - const char *username, enum trivalue prompt_password, +reindex_all_databases(ConnParams *cparams, const char *progname, bool echo, bool quiet, bool verbose, bool concurrently, int concurrentCons) { PGconn *conn; PGresult *result; - PQExpBufferData connstr; int i; - conn = connectMaintenanceDatabase(maintenance_db, host, port, username, - prompt_password, progname, echo); + conn = connectMaintenanceDatabase(cparams, progname, echo); result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", echo); PQfinish(conn); - initPQExpBuffer(&connstr); for (i = 0; i < PQntuples(result); i++) { char *dbname = PQgetvalue(result, i, 0); @@ -736,16 +737,12 @@ reindex_all_databases(const char *maintenance_db, fflush(stdout); } - resetPQExpBuffer(&connstr); - appendPQExpBufferStr(&connstr, "dbname="); - appendConnStrVal(&connstr, dbname); + cparams->override_dbname = dbname; - reindex_one_database(connstr.data, REINDEX_DATABASE, NULL, host, - port, username, prompt_password, + reindex_one_database(cparams, REINDEX_DATABASE, NULL, progname, echo, verbose, concurrently, concurrentCons); } - termPQExpBuffer(&connstr); PQclear(result); } diff --git a/src/bin/scripts/scripts_parallel.c b/src/bin/scripts/scripts_parallel.c index 01bc6dfeffc9..ec264a269a7d 100644 --- a/src/bin/scripts/scripts_parallel.c +++ b/src/bin/scripts/scripts_parallel.c @@ -205,8 +205,7 @@ ParallelSlotsGetIdle(ParallelSlot *slots, int numslots) * set. */ ParallelSlot * -ParallelSlotsSetup(const char *dbname, const char *host, const char *port, - const char *username, bool prompt_password, +ParallelSlotsSetup(const ConnParams *cparams, const char *progname, bool echo, PGconn *conn, int numslots) { @@ -221,8 +220,7 @@ ParallelSlotsSetup(const char *dbname, const char *host, const char *port, { for (i = 1; i < numslots; i++) { - conn = connectDatabase(dbname, host, port, username, prompt_password, - progname, echo, false, true); + conn = connectDatabase(cparams, progname, echo, false, true); /* * Fail and exit immediately if trying to use a socket in an diff --git a/src/bin/scripts/scripts_parallel.h b/src/bin/scripts/scripts_parallel.h index cf20449ce3e2..c9d9f0623e94 100644 --- a/src/bin/scripts/scripts_parallel.h +++ b/src/bin/scripts/scripts_parallel.h @@ -12,6 +12,7 @@ #ifndef SCRIPTS_PARALLEL_H #define SCRIPTS_PARALLEL_H +#include "common.h" #include "libpq-fe.h" @@ -23,10 +24,7 @@ typedef struct ParallelSlot extern ParallelSlot *ParallelSlotsGetIdle(ParallelSlot *slots, int numslots); -extern ParallelSlot *ParallelSlotsSetup(const char *dbname, const char *host, - const char *port, - const char *username, - bool prompt_password, +extern ParallelSlot *ParallelSlotsSetup(const ConnParams *cparams, const char *progname, bool echo, PGconn *conn, int numslots); diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c index 125ed2ff5a46..8c2eade1d5d2 100644 --- a/src/bin/scripts/vacuumdb.c +++ b/src/bin/scripts/vacuumdb.c @@ -42,19 +42,16 @@ typedef struct vacuumingOptions } vacuumingOptions; -static void vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, +static void vacuum_one_database(const ConnParams *cparams, + vacuumingOptions *vacopts, int stage, SimpleStringList *tables, - const char *host, const char *port, - const char *username, enum trivalue prompt_password, int concurrentCons, const char *progname, bool echo, bool quiet); -static void vacuum_all_databases(vacuumingOptions *vacopts, +static void vacuum_all_databases(ConnParams *cparams, + vacuumingOptions *vacopts, bool analyze_in_stages, - const char *maintenance_db, - const char *host, const char *port, - const char *username, enum trivalue prompt_password, int concurrentCons, const char *progname, bool echo, bool quiet); @@ -112,6 +109,7 @@ main(int argc, char *argv[]) char *port = NULL; char *username = NULL; enum trivalue prompt_password = TRI_DEFAULT; + ConnParams cparams; bool echo = false; bool quiet = false; vacuumingOptions vacopts; @@ -305,12 +303,19 @@ main(int argc, char *argv[]) } if (vacopts.full) { - pg_log_error("cannot use the \"%s\" option when performing full", + pg_log_error("cannot use the \"%s\" option when performing full vacuum", "parallel"); exit(1); } } + /* fill cparams except for dbname, which is set below */ + cparams.pghost = host; + cparams.pgport = port; + cparams.pguser = username; + cparams.prompt_password = prompt_password; + cparams.override_dbname = NULL; + setup_cancel_handler(NULL); /* Avoid opening extra connections. */ @@ -330,10 +335,10 @@ main(int argc, char *argv[]) exit(1); } - vacuum_all_databases(&vacopts, + cparams.dbname = maintenance_db; + + vacuum_all_databases(&cparams, &vacopts, analyze_in_stages, - maintenance_db, - host, port, username, prompt_password, concurrentCons, progname, echo, quiet); } @@ -349,25 +354,25 @@ main(int argc, char *argv[]) dbname = get_user_name_or_exit(progname); } + cparams.dbname = dbname; + if (analyze_in_stages) { int stage; for (stage = 0; stage < ANALYZE_NUM_STAGES; stage++) { - vacuum_one_database(dbname, &vacopts, + vacuum_one_database(&cparams, &vacopts, stage, &tables, - host, port, username, prompt_password, concurrentCons, progname, echo, quiet); } } else - vacuum_one_database(dbname, &vacopts, + vacuum_one_database(&cparams, &vacopts, ANALYZE_NO_STAGE, &tables, - host, port, username, prompt_password, concurrentCons, progname, echo, quiet); } @@ -389,11 +394,10 @@ main(int argc, char *argv[]) * a list of tables from the database. */ static void -vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, +vacuum_one_database(const ConnParams *cparams, + vacuumingOptions *vacopts, int stage, SimpleStringList *tables, - const char *host, const char *port, - const char *username, enum trivalue prompt_password, int concurrentCons, const char *progname, bool echo, bool quiet) { @@ -408,7 +412,6 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, int i; int ntups; bool failed = false; - bool parallel = concurrentCons > 1; bool tables_listed = false; bool has_where = false; const char *stage_commands[] = { @@ -425,8 +428,7 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, Assert(stage == ANALYZE_NO_STAGE || (stage >= 0 && stage < ANALYZE_NUM_STAGES)); - conn = connectDatabase(dbname, host, port, username, prompt_password, - progname, echo, false, true); + conn = connectDatabase(cparams, progname, echo, false, true); if (vacopts->disable_page_skipping && PQserverVersion(conn) < 90600) { @@ -651,27 +653,20 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, PQclear(res); /* - * If there are more connections than vacuumable relations, we don't need - * to use them all. + * Ensure concurrentCons is sane. If there are more connections than + * vacuumable relations, we don't need to use them all. */ - if (parallel) - { - if (concurrentCons > ntups) - concurrentCons = ntups; - if (concurrentCons <= 1) - parallel = false; - } + if (concurrentCons > ntups) + concurrentCons = ntups; + if (concurrentCons <= 0) + concurrentCons = 1; /* * Setup the database connections. We reuse the connection we already have * for the first slot. If not in parallel mode, the first slot in the * array contains the connection. */ - if (concurrentCons <= 0) - concurrentCons = 1; - - slots = ParallelSlotsSetup(dbname, host, port, username, prompt_password, - progname, echo, conn, concurrentCons); + slots = ParallelSlotsSetup(cparams, progname, echo, conn, concurrentCons); /* * Prepare all the connections to run the appropriate analyze stage, if @@ -743,28 +738,23 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, * quickly everywhere before generating more detailed ones. */ static void -vacuum_all_databases(vacuumingOptions *vacopts, +vacuum_all_databases(ConnParams *cparams, + vacuumingOptions *vacopts, bool analyze_in_stages, - const char *maintenance_db, const char *host, - const char *port, const char *username, - enum trivalue prompt_password, int concurrentCons, const char *progname, bool echo, bool quiet) { PGconn *conn; PGresult *result; - PQExpBufferData connstr; int stage; int i; - conn = connectMaintenanceDatabase(maintenance_db, host, port, username, - prompt_password, progname, echo); + conn = connectMaintenanceDatabase(cparams, progname, echo); result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", echo); PQfinish(conn); - initPQExpBuffer(&connstr); if (analyze_in_stages) { /* @@ -779,14 +769,11 @@ vacuum_all_databases(vacuumingOptions *vacopts, { for (i = 0; i < PQntuples(result); i++) { - resetPQExpBuffer(&connstr); - appendPQExpBufferStr(&connstr, "dbname="); - appendConnStrVal(&connstr, PQgetvalue(result, i, 0)); + cparams->override_dbname = PQgetvalue(result, i, 0); - vacuum_one_database(connstr.data, vacopts, + vacuum_one_database(cparams, vacopts, stage, NULL, - host, port, username, prompt_password, concurrentCons, progname, echo, quiet); } @@ -796,19 +783,15 @@ vacuum_all_databases(vacuumingOptions *vacopts, { for (i = 0; i < PQntuples(result); i++) { - resetPQExpBuffer(&connstr); - appendPQExpBufferStr(&connstr, "dbname="); - appendConnStrVal(&connstr, PQgetvalue(result, i, 0)); + cparams->override_dbname = PQgetvalue(result, i, 0); - vacuum_one_database(connstr.data, vacopts, + vacuum_one_database(cparams, vacopts, ANALYZE_NO_STAGE, NULL, - host, port, username, prompt_password, concurrentCons, progname, echo, quiet); } } - termPQExpBuffer(&connstr); PQclear(result); } diff --git a/src/common/Makefile b/src/common/Makefile index c5bb5dd312a0..25c55bd6423c 100644 --- a/src/common/Makefile +++ b/src/common/Makefile @@ -56,6 +56,7 @@ OBJS_COMMON = \ exec.o \ f2s.o \ file_perm.o \ + file_utils.o \ hashfn.o \ ip.o \ jsonapi.o \ @@ -87,16 +88,21 @@ OBJS_COMMON += sha2.o endif # A few files are currently only built for frontend, not server -# (Mkvcbuild.pm has a copy of this list, too) -OBJS_FRONTEND = \ +# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded +# from OBJS_FRONTEND_SHLIB (shared library) as a matter of policy, +# because it is not appropriate for general purpose libraries such +# as libpq to report errors directly. +OBJS_FRONTEND_SHLIB = \ $(OBJS_COMMON) \ fe_memutils.o \ - file_utils.o \ - logging.o \ - restricted_token.o + restricted_token.o \ + sprompt.o +OBJS_FRONTEND = \ + $(OBJS_FRONTEND_SHLIB) \ + logging.o # foo.o, foo_shlib.o, and foo_srv.o are all built from foo.c -OBJS_SHLIB = $(OBJS_FRONTEND:%.o=%_shlib.o) +OBJS_SHLIB = $(OBJS_FRONTEND_SHLIB:%.o=%_shlib.o) OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o) # where to find gen_keywordlist.pl and subsidiary files diff --git a/src/common/file_utils.c b/src/common/file_utils.c index a2faafdf13a3..12474730905c 100644 --- a/src/common/file_utils.c +++ b/src/common/file_utils.c @@ -14,10 +14,10 @@ */ #ifndef FRONTEND -#error "This file is not expected to be compiled for backend code" -#endif - +#include "postgres.h" +#else #include "postgres_fe.h" +#endif #include #include @@ -25,8 +25,11 @@ #include #include "common/file_utils.h" +#ifdef FRONTEND #include "common/logging.h" +#endif +#ifdef FRONTEND /* Define PG_FLUSH_DATA_WORKS if we have an implementation for pg_flush_data */ #if defined(HAVE_SYNC_FILE_RANGE) @@ -167,8 +170,6 @@ walkdir(const char *path, while (errno = 0, (de = readdir(dir)) != NULL) { char subpath[MAXPGPATH * 2]; - struct stat fst; - int sret; if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) @@ -176,21 +177,23 @@ walkdir(const char *path, snprintf(subpath, sizeof(subpath), "%s/%s", path, de->d_name); - if (process_symlinks) - sret = stat(subpath, &fst); - else - sret = lstat(subpath, &fst); - - if (sret < 0) + switch (get_dirent_type(subpath, de, process_symlinks, PG_LOG_ERROR)) { - pg_log_error("could not stat file \"%s\": %m", subpath); - continue; + case PGFILETYPE_REG: + (*action) (subpath, false); + break; + case PGFILETYPE_DIR: + walkdir(subpath, action, false); + break; + default: + + /* + * Errors are already reported directly by get_dirent_type(), + * and any remaining symlinks and unknown file types are + * ignored. + */ + break; } - - if (S_ISREG(fst.st_mode)) - (*action) (subpath, false); - else if (S_ISDIR(fst.st_mode)) - walkdir(subpath, action, false); } if (errno) @@ -394,3 +397,73 @@ durable_rename(const char *oldfile, const char *newfile) return 0; } + +#endif /* FRONTEND */ + +/* + * Return the type of a directory entry. + * + * In frontend code, elevel should be a level from logging.h; in backend code + * it should be a level from elog.h. + */ +PGFileType +get_dirent_type(const char *path, + const struct dirent *de, + bool look_through_symlinks, + int elevel) +{ + PGFileType result; + + /* + * Some systems tell us the type directly in the dirent struct, but that's + * a BSD and Linux extension not required by POSIX. Even when the + * interface is present, sometimes the type is unknown, depending on the + * filesystem. + */ +#if defined(DT_REG) && defined(DT_DIR) && defined(DT_LNK) + if (de->d_type == DT_REG) + result = PGFILETYPE_REG; + else if (de->d_type == DT_DIR) + result = PGFILETYPE_DIR; + else if (de->d_type == DT_LNK && !look_through_symlinks) + result = PGFILETYPE_LNK; + else + result = PGFILETYPE_UNKNOWN; +#else + result = PGFILETYPE_UNKNOWN; +#endif + + if (result == PGFILETYPE_UNKNOWN) + { + struct stat fst; + int sret; + + + if (look_through_symlinks) + sret = stat(path, &fst); + else + sret = lstat(path, &fst); + + if (sret < 0) + { + result = PGFILETYPE_ERROR; +#ifdef FRONTEND + pg_log_generic(elevel, "could not stat file \"%s\": %m", path); +#else + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", path))); +#endif + } + else if (S_ISREG(fst.st_mode)) + result = PGFILETYPE_REG; + else if (S_ISDIR(fst.st_mode)) + result = PGFILETYPE_DIR; +#ifdef S_ISLNK + else if (S_ISLNK(fst.st_mode)) + result = PGFILETYPE_LNK; +#endif + } + + return result; +} diff --git a/src/common/keywords.c b/src/common/keywords.c index 54ed97709613..2de0c717a891 100644 --- a/src/common/keywords.c +++ b/src/common/keywords.c @@ -24,10 +24,25 @@ /* Keyword categories for SQL keywords */ -#define PG_KEYWORD(kwname, value, category) category, +#define PG_KEYWORD(kwname, value, category, collabel) category, const uint8 ScanKeywordCategories[SCANKEYWORDS_NUM_KEYWORDS] = { #include "parser/kwlist.h" }; #undef PG_KEYWORD + +/* Keyword can-be-bare-label flags for SQL keywords */ + +#define PG_KEYWORD(kwname, value, category, collabel) collabel, + +#define BARE_LABEL true +#define AS_LABEL false + +const bool ScanKeywordBareLabel[SCANKEYWORDS_NUM_KEYWORDS] = { +#include "parser/kwlist.h" +}; + +#undef PG_KEYWORD +#undef BARE_LABEL +#undef AS_LABEL diff --git a/src/common/logging.c b/src/common/logging.c index 6a3a437a34bd..d9632fffc8ad 100644 --- a/src/common/logging.c +++ b/src/common/logging.c @@ -157,12 +157,30 @@ pg_logging_config(int new_flags) log_flags = new_flags; } +/* + * pg_logging_init sets the default log level to INFO. Programs that prefer + * a different default should use this to set it, immediately afterward. + */ void pg_logging_set_level(enum pg_log_level new_level) { __pg_log_level = new_level; } +/* + * Command line switches such as --verbose should invoke this. + */ +void +pg_logging_increase_verbosity(void) +{ + /* + * The enum values are chosen such that we have to decrease __pg_log_level + * in order to become more verbose. + */ + if (__pg_log_level > PG_LOG_NOTSET + 1) + __pg_log_level--; +} + void pg_logging_set_pre_callback(void (*cb) (void)) { diff --git a/src/common/pg_get_line.c b/src/common/pg_get_line.c index 2f07826c0912..9eb1a33bbb36 100644 --- a/src/common/pg_get_line.c +++ b/src/common/pg_get_line.c @@ -22,6 +22,52 @@ #include "lib/stringinfo.h" +/* + * pg_get_line() + * + * This is meant to be equivalent to fgets(), except that instead of + * reading into a caller-supplied, fixed-size buffer, it reads into + * a palloc'd (in frontend, really malloc'd) string, which is resized + * as needed to handle indefinitely long input lines. The caller is + * responsible for pfree'ing the result string when appropriate. + * + * As with fgets(), returns NULL if there is a read error or if no + * characters are available before EOF. The caller can distinguish + * these cases by checking ferror(stream). + * + * Since this is meant to be equivalent to fgets(), the trailing newline + * (if any) is not stripped. Callers may wish to apply pg_strip_crlf(). + * + * Note that while I/O errors are reflected back to the caller to be + * dealt with, an OOM condition for the palloc'd buffer will not be; + * there'll be an ereport(ERROR) or exit(1) inside stringinfo.c. + * + * Also note that the palloc'd buffer is usually a lot longer than + * strictly necessary, so it may be inadvisable to use this function + * to collect lots of long-lived data. A less memory-hungry option + * is to use pg_get_line_buf() or pg_get_line_append() in a loop, + * then pstrdup() each line. + */ +char * +pg_get_line(FILE *stream) +{ + StringInfoData buf; + + initStringInfo(&buf); + + if (!pg_get_line_append(stream, &buf)) + { + /* ensure that free() doesn't mess up errno */ + int save_errno = errno; + + pfree(buf.data); + errno = save_errno; + return NULL; + } + + return buf.data; +} + /* * pg_get_line_buf() * @@ -91,4 +137,4 @@ pg_get_line_append(FILE *stream, StringInfo buf) /* No newline at EOF, but we did collect some data */ return true; - } +} diff --git a/src/common/pg_lzcompress.c b/src/common/pg_lzcompress.c index d24d4803a983..f9c29820e30b 100644 --- a/src/common/pg_lzcompress.c +++ b/src/common/pg_lzcompress.c @@ -680,9 +680,12 @@ pglz_compress(const char *source, int32 slen, char *dest, * pglz_decompress - * * Decompresses source into dest. Returns the number of bytes - * decompressed in the destination buffer, and *optionally* - * checks that both the source and dest buffers have been - * fully read and written to, respectively. + * decompressed into the destination buffer, or -1 if the + * compressed data is corrupted. + * + * If check_complete is true, the data is considered corrupted + * if we don't exactly fill the destination buffer. Callers that + * are extracting a slice typically can't apply this check. * ---------- */ int32 @@ -710,7 +713,6 @@ pglz_decompress(const char *source, int32 slen, char *dest, for (ctrlc = 0; ctrlc < 8 && sp < srcend && dp < destend; ctrlc++) { - if (ctrl & 1) { /* @@ -732,41 +734,62 @@ pglz_decompress(const char *source, int32 slen, char *dest, len += *sp++; /* - * Now we copy the bytes specified by the tag from OUTPUT to - * OUTPUT (copy len bytes from dp - off to dp). The copied - * areas could overlap, to preven possible uncertainty, we - * copy only non-overlapping regions. + * Check for corrupt data: if we fell off the end of the + * source, or if we obtained off = 0, we have problems. (We + * must check this, else we risk an infinite loop below in the + * face of corrupt data.) + */ + if (unlikely(sp > srcend || off == 0)) + return -1; + + /* + * Don't emit more data than requested. */ len = Min(len, destend - dp); + + /* + * Now we copy the bytes specified by the tag from OUTPUT to + * OUTPUT (copy len bytes from dp - off to dp). The copied + * areas could overlap, so to avoid undefined behavior in + * memcpy(), be careful to copy only non-overlapping regions. + * + * Note that we cannot use memmove() instead, since while its + * behavior is well-defined, it's also not what we want. + */ while (off < len) { - /*--------- - * When offset is smaller than length - source and - * destination regions overlap. memmove() is resolving - * this overlap in an incompatible way with pglz. Thus we - * resort to memcpy()-ing non-overlapping regions. - * - * Consider input: 112341234123412341234 - * At byte 5 here ^ we have match with length 16 and - * offset 4. 11234M(len=16, off=4) - * We are decoding first period of match and rewrite match - * 112341234M(len=12, off=8) - * - * The same match is now at position 9, it points to the - * same start byte of output, but from another position: - * the offset is doubled. - * - * We iterate through this offset growth until we can - * proceed to usual memcpy(). If we would try to decode - * the match at byte 5 (len=16, off=4) by memmove() we - * would issue memmove(5, 1, 16) which would produce - * 112341234XXXXXXXXXXXX, where series of X is 12 - * undefined bytes, that were at bytes [5:17]. - *--------- + /* + * We can safely copy "off" bytes since that clearly + * results in non-overlapping source and destination. */ memcpy(dp, dp - off, off); len -= off; dp += off; + + /*---------- + * This bit is less obvious: we can double "off" after + * each such step. Consider this raw input: + * 112341234123412341234 + * This will be encoded as 5 literal bytes "11234" and + * then a match tag with length 16 and offset 4. After + * memcpy'ing the first 4 bytes, we will have emitted + * 112341234 + * so we can double "off" to 8, then after the next step + * we have emitted + * 11234123412341234 + * Then we can double "off" again, after which it is more + * than the remaining "len" so we fall out of this loop + * and finish with a non-overlapping copy of the + * remainder. In general, a match tag with off < len + * implies that the decoded data has a repeat length of + * "off". We can handle 1, 2, 4, etc repetitions of the + * repeated string per memcpy until we get to a situation + * where the final copy step is non-overlapping. + * + * (Another way to understand this is that we are keeping + * the copy source point dp - off the same throughout.) + *---------- + */ off += off; } memcpy(dp, dp - off, len); @@ -789,9 +812,7 @@ pglz_decompress(const char *source, int32 slen, char *dest, } /* - * Check we decompressed the right amount. If we are slicing, then we - * won't necessarily be at the end of the source or dest buffers when we - * hit a stop, so we don't test them. + * If requested, check we decompressed the right amount. */ if (check_complete && (dp != destend || sp != srcend)) return -1; @@ -820,21 +841,32 @@ pglz_decompress(const char *source, int32 slen, char *dest, int32 pglz_maximum_compressed_size(int32 rawsize, int32 total_compressed_size) { - int32 compressed_size; + int64 compressed_size; /* - * pglz uses one control bit per byte, so we need (rawsize * 9) bits. We - * care about bytes though, so we add 7 to make sure we include the last - * incomplete byte (integer division rounds down). + * pglz uses one control bit per byte, so if the entire desired prefix is + * represented as literal bytes, we'll need (rawsize * 9) bits. We care + * about bytes though, so be sure to round up not down. * - * XXX Use int64 to prevent overflow during calculation. + * Use int64 here to prevent overflow during calculation. + */ + compressed_size = ((int64) rawsize * 9 + 7) / 8; + + /* + * The above fails to account for a corner case: we could have compressed + * data that starts with N-1 or N-2 literal bytes and then has a match tag + * of 2 or 3 bytes. It's therefore possible that we need to fetch 1 or 2 + * more bytes in order to have the whole match tag. (Match tags earlier + * in the compressed data don't cause a problem, since they should + * represent more decompressed bytes than they occupy themselves.) */ - compressed_size = (int32) ((int64) rawsize * 9 + 7) / 8; + compressed_size += 2; /* * Maximum compressed size can't be larger than total compressed size. + * (This also ensures that our result fits in int32.) */ compressed_size = Min(compressed_size, total_compressed_size); - return compressed_size; + return (int32) compressed_size; } diff --git a/src/common/restricted_token.c b/src/common/restricted_token.c index d8d3aeffcdc2..dcc88a75c59d 100644 --- a/src/common/restricted_token.c +++ b/src/common/restricted_token.c @@ -66,7 +66,7 @@ CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo) return 0; } - _CreateRestrictedToken = (__CreateRestrictedToken) GetProcAddress(Advapi32Handle, "CreateRestrictedToken"); + _CreateRestrictedToken = (__CreateRestrictedToken) (pg_funcptr_t) GetProcAddress(Advapi32Handle, "CreateRestrictedToken"); if (_CreateRestrictedToken == NULL) { diff --git a/src/common/saslprep.c b/src/common/saslprep.c index 2dedf6b0fb68..d60452f75f28 100644 --- a/src/common/saslprep.c +++ b/src/common/saslprep.c @@ -29,12 +29,6 @@ #include "common/unicode_norm.h" #include "mb/pg_wchar.h" -/* - * Limit on how large password's we will try to process. A password - * larger than this will be treated the same as out-of-memory. - */ -#define MAX_PASSWORD_LENGTH 1024 - /* * In backend, we will use palloc/pfree. In frontend, use malloc, and * return SASLPREP_OOM on out-of-memory. @@ -1078,18 +1072,6 @@ pg_saslprep(const char *input, char **output) /* Ensure we return *output as NULL on failure */ *output = NULL; - /* Check that the password isn't stupendously long */ - if (strlen(input) > MAX_PASSWORD_LENGTH) - { -#ifndef FRONTEND - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("password too long"))); -#else - return SASLPREP_OOM; -#endif - } - /* * Quick check if the input is pure ASCII. An ASCII string requires no * further processing. diff --git a/src/port/sprompt.c b/src/common/sprompt.c similarity index 81% rename from src/port/sprompt.c rename to src/common/sprompt.c index 6d8a8b2609ff..0ec75da5bfe6 100644 --- a/src/port/sprompt.c +++ b/src/common/sprompt.c @@ -8,12 +8,15 @@ * * * IDENTIFICATION - * src/port/sprompt.c + * src/common/sprompt.c * *------------------------------------------------------------------------- */ #include "c.h" +#include "common/fe_memutils.h" +#include "common/string.h" + #ifdef HAVE_TERMIOS_H #include #endif @@ -26,20 +29,17 @@ * passwords interactively. Reads from /dev/tty or stdin/stderr. * * prompt: The prompt to print, or NULL if none (automatically localized) - * destination: buffer in which to store result - * destlen: allocated length of destination * echo: Set to false if you want to hide what is entered (for passwords) * - * The input (without trailing newline) is returned in the destination buffer, - * with a '\0' appended. + * The input (without trailing newline) is returned as a malloc'd string. + * Caller is responsible for freeing it when done. */ -void -simple_prompt(const char *prompt, char *destination, size_t destlen, bool echo) +char * +simple_prompt(const char *prompt, bool echo) { - int length; + char *result; FILE *termin, *termout; - #if defined(HAVE_TERMIOS_H) struct termios t_orig, t; @@ -126,29 +126,14 @@ simple_prompt(const char *prompt, char *destination, size_t destlen, bool echo) fflush(termout); } - if (fgets(destination, destlen, termin) == NULL) - destination[0] = '\0'; + result = pg_get_line(termin); - length = strlen(destination); - if (length > 0 && destination[length - 1] != '\n') - { - /* eat rest of the line */ - char buf[128]; - int buflen; - - do - { - if (fgets(buf, sizeof(buf), termin) == NULL) - break; - buflen = strlen(buf); - } while (buflen > 0 && buf[buflen - 1] != '\n'); - } + /* If we failed to read anything, just return an empty string */ + if (result == NULL) + result = pg_strdup(""); /* strip trailing newline, including \r in case we're on Windows */ - while (length > 0 && - (destination[length - 1] == '\n' || - destination[length - 1] == '\r')) - destination[--length] = '\0'; + (void) pg_strip_crlf(result); if (!echo) { @@ -169,4 +154,6 @@ simple_prompt(const char *prompt, char *destination, size_t destlen, bool echo) fclose(termin); fclose(termout); } + + return result; } diff --git a/src/common/unicode/Makefile b/src/common/unicode/Makefile index 93a9d1615f1c..eb14add28ad6 100644 --- a/src/common/unicode/Makefile +++ b/src/common/unicode/Makefile @@ -18,7 +18,7 @@ LIBS += $(PTHREAD_LIBS) # By default, do nothing. all: -update-unicode: unicode_norm_table.h unicode_combining_table.h unicode_normprops_table.h +update-unicode: unicode_norm_table.h unicode_combining_table.h unicode_normprops_table.h unicode_norm_hashfunc.h mv $^ ../../../src/include/common/ $(MAKE) normalization-check @@ -30,6 +30,8 @@ UnicodeData.txt DerivedNormalizationProps.txt CompositionExclusions.txt Normaliz # Generation of conversion tables used for string normalization with # UTF-8 strings. +unicode_norm_hashfunc.h: unicode_norm_table.h + unicode_norm_table.h: generate-unicode_norm_table.pl UnicodeData.txt CompositionExclusions.txt $(PERL) generate-unicode_norm_table.pl diff --git a/src/common/unicode/generate-unicode_norm_table.pl b/src/common/unicode/generate-unicode_norm_table.pl index 7ce15e1a0395..e4d3ccc2346a 100644 --- a/src/common/unicode/generate-unicode_norm_table.pl +++ b/src/common/unicode/generate-unicode_norm_table.pl @@ -1,16 +1,22 @@ #!/usr/bin/perl # -# Generate a composition table, using Unicode data files as input +# Generate a composition table and its lookup utilities, using Unicode data +# files as input. # # Input: UnicodeData.txt and CompositionExclusions.txt -# Output: unicode_norm_table.h +# Output: unicode_norm_table.h and unicode_norm_hashfunc.h # # Copyright (c) 2000-2020, PostgreSQL Global Development Group use strict; use warnings; -my $output_file = "unicode_norm_table.h"; +use FindBin; +use lib "$FindBin::RealBin/../../tools/"; +use PerfectHash; + +my $output_table_file = "unicode_norm_table.h"; +my $output_func_file = "unicode_norm_hashfunc.h"; my $FH; @@ -64,11 +70,13 @@ my $num_characters = scalar @characters; -# Start writing out the output file -open my $OUTPUT, '>', $output_file - or die "Could not open output file $output_file: $!\n"; +# Start writing out the output files +open my $OT, '>', $output_table_file + or die "Could not open output file $output_table_file: $!\n"; +open my $OF, '>', $output_func_file + or die "Could not open output file $output_func_file: $!\n"; -print $OUTPUT <{code}; foreach my $char (@characters) @@ -121,6 +174,9 @@ my $class = $char->{class}; my $decomp = $char->{decomp}; + # Save the code point bytes as a string in network order. + push @dec_cp_packed, pack('N', hex($char->{code})); + # The character decomposition mapping field in UnicodeData.txt is a list # of unicode codepoints, separated by space. But it can be prefixed with # so-called compatibility formatting tag, like "", or "". @@ -163,7 +219,7 @@ { foreach my $lcode (@composition_exclusion_codes) { - if ($lcode eq $char->{code}) + if ($lcode eq $code) { $flags .= " | DECOMP_NO_COMPOSE"; $comment = "in exclusion list"; @@ -171,11 +227,26 @@ } } } + + # Save info for recomposeable codepoints. + # Note that this MUST match the macro DECOMPOSITION_NO_COMPOSE in C + # above! See also the inverse lookup in recompose_code() found in + # src/common/unicode_norm.c. + if (!($flags =~ /DECOMP_COMPAT/ || $flags =~ /DECOMP_NO_COMPOSE/)) + { + push @rec_info, + { + code => $code, + main_index => $main_index, + first => $first_decomp, + second => $decomp_elts[0] + }; + } } if ($decomp_size == 0) { - print $OUTPUT "\t{0x$code, $class, 0$flags, 0}"; + print $OT "\t{0x$code, $class, 0$flags, 0}"; } elsif ($decomp_size == 1 && length($first_decomp) <= 4) { @@ -183,12 +254,11 @@ # The decomposition consists of a single codepoint, and it fits # in a uint16, so we can store it "inline" in the main table. $flags .= " | DECOMP_INLINE"; - print $OUTPUT "\t{0x$code, $class, 1$flags, 0x$first_decomp}"; + print $OT "\t{0x$code, $class, 1$flags, 0x$first_decomp}"; } else { - print $OUTPUT - "\t{0x$code, $class, $decomp_size$flags, $decomp_index}"; + print $OT "\t{0x$code, $class, $decomp_size$flags, $decomp_index}"; # Now save the decompositions into a dedicated area that will # be written afterwards. First build the entry dedicated to @@ -205,25 +275,17 @@ } # Print a comma after all items except the last one. - print $OUTPUT "," unless ($code eq $last_code); - if ($comment ne "") - { + print $OT "," unless ($code eq $last_code); - # If the line is wide already, indent the comment with one tab, - # otherwise with two. This is to make the output match the way - # pgindent would mangle it. (This is quite hacky. To do this - # properly, we should actually track how long the line is so far, - # but this works for now.) - print $OUTPUT "\t" if ($decomp_index < 10); + print $OT "\t/* $comment */" if ($comment ne ""); + print $OT "\n"; - print $OUTPUT "\t/* $comment */" if ($comment ne ""); - } - print $OUTPUT "\n"; + $main_index++; } -print $OUTPUT "\n};\n\n"; +print $OT "\n};\n\n"; # Print the array of decomposed codes. -print $OUTPUT < 4); +print $OF "/* Perfect hash function for decomposition */\n"; +print $OF "static $dec_func\n"; + +# Emit the structure that wraps the hash lookup information into +# one variable. +print $OF <{first}) << 32) | hex($rec->{second}); + + # We are only interested in the lowest code point that decomposes + # to the given code pair. + next if $seenit{$hashkey}; + + # Save the hash key bytes in network order + push @rec_cp_packed, pack('Q>', $hashkey); + + # Append inverse lookup element + $recomp_string .= ",\n" if !$firstentry; + $recomp_string .= sprintf "\t/* U+%s+%s -> U+%s */ %s", + $rec->{first}, + $rec->{second}, + $rec->{code}, + $rec->{main_index}; + + $seenit{$hashkey} = 1; + $firstentry = 0; +} + +# Emit the inverse lookup array containing indexes into UnicodeDecompMain. +my $num_recomps = scalar @rec_cp_packed; +print $OF < 8); +print $OF "/* Perfect hash function for recomposition */\n"; +print $OF "static $rec_func\n"; + +# Emit the structure that wraps the hash lookup information into +# one variable. +print $OF <{first}); + my $b1 = hex($b->{first}); + + my $a2 = hex($a->{second}); + my $b2 = hex($b->{second}); + + # First sort by the first code point + return -1 if $a1 < $b1; + return 1 if $a1 > $b1; + + # Then sort by the second code point + return -1 if $a2 < $b2; + return 1 if $a2 > $b2; + + # Finally sort by the code point that decomposes into first and + # second ones. + my $acode = hex($a->{code}); + my $bcode = hex($b->{code}); + + return -1 if $acode < $bcode; + return -1 if $acode > $bcode; + + die "found duplicate entries of recomposeable code pairs"; +} diff --git a/src/common/unicode/generate-unicode_normprops_table.pl b/src/common/unicode/generate-unicode_normprops_table.pl index e8e5097c094b..d652b95965dc 100644 --- a/src/common/unicode/generate-unicode_normprops_table.pl +++ b/src/common/unicode/generate-unicode_normprops_table.pl @@ -9,6 +9,10 @@ use strict; use warnings; +use FindBin; +use lib "$FindBin::RealBin/../../tools/"; +use PerfectHash; + my %data; print @@ -18,13 +22,25 @@ #include "common/unicode_norm.h" /* - * We use a bit field here to save space. + * Normalization quick check entry for codepoint. We use a bit field + * here to save space. */ typedef struct { unsigned int codepoint:21; signed int quickcheck:4; /* really UnicodeNormalizationQC */ -} pg_unicode_normprops; +} pg_unicode_normprops; + +/* Typedef for hash function on quick check table */ +typedef int (*qc_hash_func) (const void *key); + +/* Information for quick check lookup with perfect hash function */ +typedef struct +{ + const pg_unicode_normprops *normprops; + qc_hash_func hash; + int num_normprops; +} pg_unicode_norminfo; EOS foreach my $line () @@ -66,6 +82,7 @@ "static const pg_unicode_normprops UnicodeNormProps_${prop}[] = {\n"; my %subdata = %{ $data{$prop} }; + my @cp_packed; foreach my $cp (sort { $a <=> $b } keys %subdata) { my $qc; @@ -82,7 +99,27 @@ die; } printf "\t{0x%04X, %s},\n", $cp, $qc; + + # Save the bytes as a string in network order. + push @cp_packed, pack('N', $cp); } print "};\n"; + + # Emit the definition of the perfect hash function. + my $funcname = $prop . '_hash_func'; + my $f = PerfectHash::generate_hash_function(\@cp_packed, $funcname, + fixed_key_length => 4); + printf "\n/* Perfect hash function for %s */", $prop; + print "\nstatic $f\n"; + + # Emit the structure that wraps the hash lookup information into + # one variable. + printf "/* Hash lookup information for %s */", $prop; + printf "\nstatic const pg_unicode_norminfo "; + printf "UnicodeNormInfo_%s = {\n", $prop; + printf "\tUnicodeNormProps_%s,\n", $prop; + printf "\t%s,\n", $funcname; + printf "\t%d\n", scalar @cp_packed; + printf "};\n"; } diff --git a/src/common/unicode_norm.c b/src/common/unicode_norm.c index ab5ce5934569..abb83cbf985e 100644 --- a/src/common/unicode_norm.c +++ b/src/common/unicode_norm.c @@ -19,10 +19,13 @@ #endif #include "common/unicode_norm.h" -#include "common/unicode_norm_table.h" #ifndef FRONTEND +#include "common/unicode_norm_hashfunc.h" #include "common/unicode_normprops_table.h" +#else +#include "common/unicode_norm_table.h" #endif +#include "port/pg_bswap.h" #ifndef FRONTEND #define ALLOC(size) palloc(size) @@ -43,6 +46,7 @@ #define NCOUNT VCOUNT * TCOUNT #define SCOUNT LCOUNT * NCOUNT +#ifdef FRONTEND /* comparison routine for bsearch() of decomposition lookup table. */ static int conv_compare(const void *p1, const void *p2) @@ -55,19 +59,53 @@ conv_compare(const void *p1, const void *p2) return (v1 > v2) ? 1 : ((v1 == v2) ? 0 : -1); } +#endif + /* + * get_code_entry + * * Get the entry corresponding to code in the decomposition lookup table. + * The backend version of this code uses a perfect hash function for the + * lookup, while the frontend version uses a binary search. */ -static pg_unicode_decomposition * +static const pg_unicode_decomposition * get_code_entry(pg_wchar code) { +#ifndef FRONTEND + int h; + uint32 hashkey; + pg_unicode_decompinfo decompinfo = UnicodeDecompInfo; + + /* + * Compute the hash function. The hash key is the codepoint with the bytes + * in network order. + */ + hashkey = pg_hton32(code); + h = decompinfo.hash(&hashkey); + + /* An out-of-range result implies no match */ + if (h < 0 || h >= decompinfo.num_decomps) + return NULL; + + /* + * Since it's a perfect hash, we need only match to the specific codepoint + * it identifies. + */ + if (code != decompinfo.decomps[h].codepoint) + return NULL; + + /* Success! */ + return &decompinfo.decomps[h]; +#else return bsearch(&(code), UnicodeDecompMain, lengthof(UnicodeDecompMain), sizeof(pg_unicode_decomposition), conv_compare); +#endif } + /* * Given a decomposition entry looked up earlier, get the decomposed * characters. @@ -76,7 +114,7 @@ get_code_entry(pg_wchar code) * is only valid until next call to this function! */ static const pg_wchar * -get_code_decomposition(pg_unicode_decomposition *entry, int *dec_size) +get_code_decomposition(const pg_unicode_decomposition *entry, int *dec_size) { static pg_wchar x; @@ -103,7 +141,7 @@ get_code_decomposition(pg_unicode_decomposition *entry, int *dec_size) static int get_decomposed_size(pg_wchar code, bool compat) { - pg_unicode_decomposition *entry; + const pg_unicode_decomposition *entry; int size = 0; int i; const uint32 *decomp; @@ -190,17 +228,51 @@ recompose_code(uint32 start, uint32 code, uint32 *result) } else { - int i; + const pg_unicode_decomposition *entry; /* * Do an inverse lookup of the decomposition tables to see if anything * matches. The comparison just needs to be a perfect match on the * sub-table of size two, because the start character has already been - * recomposed partially. + * recomposed partially. This lookup uses a perfect hash function for + * the backend code. + */ +#ifndef FRONTEND + + int h, + inv_lookup_index; + uint64 hashkey; + pg_unicode_recompinfo recompinfo = UnicodeRecompInfo; + + /* + * Compute the hash function. The hash key is formed by concatenating + * bytes of the two codepoints in network order. See also + * src/common/unicode/generate-unicode_norm_table.pl. */ + hashkey = pg_hton64(((uint64) start << 32) | (uint64) code); + h = recompinfo.hash(&hashkey); + + /* An out-of-range result implies no match */ + if (h < 0 || h >= recompinfo.num_recomps) + return false; + + inv_lookup_index = recompinfo.inverse_lookup[h]; + entry = &UnicodeDecompMain[inv_lookup_index]; + + if (start == UnicodeDecomp_codepoints[entry->dec_index] && + code == UnicodeDecomp_codepoints[entry->dec_index + 1]) + { + *result = entry->codepoint; + return true; + } + +#else + + int i; + for (i = 0; i < lengthof(UnicodeDecompMain); i++) { - const pg_unicode_decomposition *entry = &UnicodeDecompMain[i]; + entry = &UnicodeDecompMain[i]; if (DECOMPOSITION_SIZE(entry) != 2) continue; @@ -215,6 +287,7 @@ recompose_code(uint32 start, uint32 code, uint32 *result) return true; } } +#endif /* !FRONTEND */ } return false; @@ -230,7 +303,7 @@ recompose_code(uint32 start, uint32 code, uint32 *result) static void decompose_code(pg_wchar code, bool compat, pg_wchar **result, int *current) { - pg_unicode_decomposition *entry; + const pg_unicode_decomposition *entry; int i; const uint32 *decomp; int dec_size; @@ -357,8 +430,8 @@ unicode_normalize(UnicodeNormalizationForm form, const pg_wchar *input) pg_wchar prev = decomp_chars[count - 1]; pg_wchar next = decomp_chars[count]; pg_wchar tmp; - pg_unicode_decomposition *prevEntry = get_code_entry(prev); - pg_unicode_decomposition *nextEntry = get_code_entry(next); + const pg_unicode_decomposition *prevEntry = get_code_entry(prev); + const pg_unicode_decomposition *nextEntry = get_code_entry(next); /* * If no entries are found, the character used is either an Hangul @@ -416,7 +489,7 @@ unicode_normalize(UnicodeNormalizationForm form, const pg_wchar *input) for (count = 1; count < decomp_size; count++) { pg_wchar ch = decomp_chars[count]; - pg_unicode_decomposition *ch_entry = get_code_entry(ch); + const pg_unicode_decomposition *ch_entry = get_code_entry(ch); int ch_class = (ch_entry == NULL) ? 0 : ch_entry->comb_class; pg_wchar composite; @@ -457,7 +530,7 @@ unicode_normalize(UnicodeNormalizationForm form, const pg_wchar *input) static uint8 get_canonical_class(pg_wchar ch) { - pg_unicode_decomposition *entry = get_code_entry(ch); + const pg_unicode_decomposition *entry = get_code_entry(ch); if (!entry) return 0; @@ -465,15 +538,32 @@ get_canonical_class(pg_wchar ch) return entry->comb_class; } -static int -qc_compare(const void *p1, const void *p2) +static const pg_unicode_normprops * +qc_hash_lookup(pg_wchar ch, const pg_unicode_norminfo *norminfo) { - uint32 v1, - v2; + int h; + uint32 hashkey; + + /* + * Compute the hash function. The hash key is the codepoint with the bytes + * in network order. + */ + hashkey = pg_hton32(ch); + h = norminfo->hash(&hashkey); + + /* An out-of-range result implies no match */ + if (h < 0 || h >= norminfo->num_normprops) + return NULL; + + /* + * Since it's a perfect hash, we need only match to the specific codepoint + * it identifies. + */ + if (ch != norminfo->normprops[h].codepoint) + return NULL; - v1 = ((const pg_unicode_normprops *) p1)->codepoint; - v2 = ((const pg_unicode_normprops *) p2)->codepoint; - return (v1 - v2); + /* Success! */ + return &norminfo->normprops[h]; } /* @@ -482,26 +572,15 @@ qc_compare(const void *p1, const void *p2) static UnicodeNormalizationQC qc_is_allowed(UnicodeNormalizationForm form, pg_wchar ch) { - pg_unicode_normprops key; - pg_unicode_normprops *found = NULL; - - key.codepoint = ch; + const pg_unicode_normprops *found = NULL; switch (form) { case UNICODE_NFC: - found = bsearch(&key, - UnicodeNormProps_NFC_QC, - lengthof(UnicodeNormProps_NFC_QC), - sizeof(pg_unicode_normprops), - qc_compare); + found = qc_hash_lookup(ch, &UnicodeNormInfo_NFC_QC); break; case UNICODE_NFKC: - found = bsearch(&key, - UnicodeNormProps_NFKC_QC, - lengthof(UnicodeNormProps_NFKC_QC), - sizeof(pg_unicode_normprops), - qc_compare); + found = qc_hash_lookup(ch, &UnicodeNormInfo_NFKC_QC); break; default: Assert(false); diff --git a/src/fe_utils/archive.c b/src/fe_utils/archive.c index c4cb21319851..252dc0fb6a5d 100644 --- a/src/fe_utils/archive.c +++ b/src/fe_utils/archive.c @@ -50,7 +50,7 @@ RestoreArchivedFile(const char *path, const char *xlogfname, xlogfname, NULL); if (xlogRestoreCmd == NULL) { - pg_log_fatal("could not use restore_command with %%r alias"); + pg_log_fatal("cannot use restore_command with %%r placeholder"); exit(1); } @@ -71,9 +71,9 @@ RestoreArchivedFile(const char *path, const char *xlogfname, { if (expectedSize > 0 && stat_buf.st_size != expectedSize) { - pg_log_fatal("unexpected file size for \"%s\": %lu instead of %lu", - xlogfname, (unsigned long) stat_buf.st_size, - (unsigned long) expectedSize); + pg_log_fatal("unexpected file size for \"%s\": %lld instead of %lld", + xlogfname, (long long int) stat_buf.st_size, + (long long int) expectedSize); exit(1); } else @@ -109,7 +109,7 @@ RestoreArchivedFile(const char *path, const char *xlogfname, */ if (wait_result_is_any_signal(rc, true)) { - pg_log_fatal("restore_command failed due to the signal: %s", + pg_log_fatal("restore_command failed: %s", wait_result_to_str(rc)); exit(1); } diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c index 508f537c0c7a..d542792230f8 100644 --- a/src/fe_utils/print.c +++ b/src/fe_utils/print.c @@ -3495,7 +3495,7 @@ column_type_alignment(Oid ftype) case XIDOID: case XID8OID: case CIDOID: - case CASHOID: + case MONEYOID: align = 'r'; break; default: diff --git a/src/include/access/clog.h b/src/include/access/clog.h index e3fb4f09fd28..1d531a3f84d4 100644 --- a/src/include/access/clog.h +++ b/src/include/access/clog.h @@ -12,6 +12,7 @@ #define CLOG_H #include "access/xlogreader.h" +#include "storage/sync.h" #include "lib/stringinfo.h" /* @@ -54,6 +55,8 @@ extern bool CLOGScanForPrevStatus( XidStatus *status); extern bool CLOGTransactionIsOld(TransactionId xid); +extern int clogsyncfiletag(const FileTag *ftag, char *path); + /* XLOG stuff */ #define CLOG_ZEROPAGE 0x00 #define CLOG_TRUNCATE 0x10 diff --git a/src/include/access/commit_ts.h b/src/include/access/commit_ts.h index 2740c02a84fa..2d1724952257 100644 --- a/src/include/access/commit_ts.h +++ b/src/include/access/commit_ts.h @@ -14,6 +14,7 @@ #include "access/xlog.h" #include "datatype/timestamp.h" #include "replication/origin.h" +#include "storage/sync.h" #include "utils/guc.h" @@ -45,6 +46,8 @@ extern void SetCommitTsLimit(TransactionId oldestXact, TransactionId newestXact); extern void AdvanceOldestCommitTsXid(TransactionId oldestXact); +extern int committssyncfiletag(const FileTag *ftag, char *path); + /* XLOG stuff */ #define COMMIT_TS_ZEROPAGE 0x00 #define COMMIT_TS_TRUNCATE 0x10 diff --git a/src/include/access/distributedlog.h b/src/include/access/distributedlog.h index cbd25344f8fe..68f0225dd509 100644 --- a/src/include/access/distributedlog.h +++ b/src/include/access/distributedlog.h @@ -30,6 +30,7 @@ #define DISTRIBUTEDLOG_H #include "access/xlog.h" +#include "storage/sync.h" /* * The full binary representation of the distributed transaction id. @@ -61,7 +62,6 @@ extern void DistributedLog_BootStrap(void); extern void DistributedLog_Startup( TransactionId oldestActiveXid, TransactionId nextXid); -extern void DistributedLog_Shutdown(void); extern void DistributedLog_CheckPoint(void); extern void DistributedLog_Extend(TransactionId newestXid); extern bool DistributedLog_GetLowWaterXid( @@ -80,4 +80,6 @@ extern void DistributedLog_GetDistributedXid( TransactionId localXid, DistributedTransactionId *distribXid); +extern int DistributedLog_syncfiletag(const FileTag *ftag, char *path); + #endif /* DISTRIBUTEDLOG_H */ diff --git a/src/include/access/genam.h b/src/include/access/genam.h index 739972e7b6a9..0e15d74ac666 100644 --- a/src/include/access/genam.h +++ b/src/include/access/genam.h @@ -38,8 +38,8 @@ typedef struct IndexBuildResult * * num_heap_tuples is accurate only when estimated_count is false; * otherwise it's just an estimate (currently, the estimate is the - * prior value of the relation's pg_class.reltuples field). It will - * always just be an estimate during ambulkdelete. + * prior value of the relation's pg_class.reltuples field, so it could + * even be -1). It will always just be an estimate during ambulkdelete. */ typedef struct IndexVacuumInfo { diff --git a/src/include/access/gist.h b/src/include/access/gist.h index 4994351697c3..4f6dae9a76b0 100644 --- a/src/include/access/gist.h +++ b/src/include/access/gist.h @@ -37,7 +37,8 @@ #define GIST_DISTANCE_PROC 8 #define GIST_FETCH_PROC 9 #define GIST_OPTIONS_PROC 10 -#define GISTNProcs 10 +#define GIST_SORTSUPPORT_PROC 11 +#define GISTNProcs 11 /* * Page opaque data in a GiST index page. diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h index f190729d1a20..44d3a71c895c 100644 --- a/src/include/access/gist_private.h +++ b/src/include/access/gist_private.h @@ -501,12 +501,15 @@ extern IndexTuple gistgetadjusted(Relation r, GISTSTATE *giststate); extern IndexTuple gistFormTuple(GISTSTATE *giststate, Relation r, Datum *attdata, bool *isnull, bool isleaf); +extern void gistCompressValues(GISTSTATE *giststate, Relation r, + Datum *attdata, bool *isnull, bool isleaf, Datum *compatt); extern OffsetNumber gistchoose(Relation r, Page p, IndexTuple it, GISTSTATE *giststate); extern void GISTInitBuffer(Buffer b, uint32 f); +extern void gistinitpage(Page page, uint32 f); extern void gistdentryinit(GISTSTATE *giststate, int nkey, GISTENTRY *e, Datum k, Relation r, Page pg, OffsetNumber o, bool l, bool isNull); diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 6fa582367b69..b7c073034221 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -179,7 +179,8 @@ extern int heap_page_prune(Relation relation, Buffer buffer, struct GlobalVisState *vistest, TransactionId limited_oldest_xmin, TimestampTz limited_oldest_ts, - bool report_stats, TransactionId *latestRemovedXid); + bool report_stats, TransactionId *latestRemovedXid, + OffsetNumber *off_loc); extern void heap_page_prune_execute(Buffer buffer, OffsetNumber *redirected, int nredirected, OffsetNumber *nowdead, int ndead, diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h index 1187c8e1cdb6..491ca70bf48d 100644 --- a/src/include/access/heapam_xlog.h +++ b/src/include/access/heapam_xlog.h @@ -137,8 +137,6 @@ typedef struct xl_heap_truncate * or updated tuple in WAL; we can save a few bytes by reconstructing the * fields that are available elsewhere in the WAL record, or perhaps just * plain needn't be reconstructed. These are the fields we must store. - * NOTE: t_hoff could be recomputed, but we may as well store it because - * it will come for free due to alignment considerations. */ typedef struct xl_heap_header { diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h index 6d729008c600..9a303809019a 100644 --- a/src/include/access/multixact.h +++ b/src/include/access/multixact.h @@ -13,6 +13,7 @@ #include "access/xlogreader.h" #include "lib/stringinfo.h" +#include "storage/sync.h" /* @@ -108,6 +109,7 @@ extern MultiXactId MultiXactIdCreateFromMembers(int nmembers, MultiXactMember *members); extern MultiXactId ReadNextMultiXactId(void); +extern void ReadMultiXactIdRange(MultiXactId *oldest, MultiXactId *next); extern bool MultiXactIdIsRunning(MultiXactId multi, bool isLockOnly); extern void MultiXactIdSetOldestMember(void); extern int GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **xids, @@ -116,6 +118,9 @@ extern bool MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2); extern bool MultiXactIdPrecedesOrEquals(MultiXactId multi1, MultiXactId multi2); +extern int multixactoffsetssyncfiletag(const FileTag *ftag, char *path); +extern int multixactmemberssyncfiletag(const FileTag *ftag, char *path); + extern void AtEOXact_MultiXact(void); extern void AtPrepare_MultiXact(void); extern void PostPrepare_MultiXact(TransactionId xid); diff --git a/src/include/access/slru.h b/src/include/access/slru.h index 2ff7a1e9a28d..69d44e4804f4 100644 --- a/src/include/access/slru.h +++ b/src/include/access/slru.h @@ -15,6 +15,7 @@ #include "access/xlogdefs.h" #include "storage/lwlock.h" +#include "storage/sync.h" /* @@ -111,10 +112,10 @@ typedef struct SlruCtlData SlruShared shared; /* - * This flag tells whether to fsync writes (true for pg_xact and multixact - * stuff, false for pg_subtrans and pg_notify). + * Which sync handler function to use when handing sync requests over to + * the checkpointer. SYNC_HANDLER_NONE to disable fsync (eg pg_notify). */ - bool do_fsync; + SyncRequestHandler sync_handler; /* * Decide which of two page numbers is "older" for truncation purposes. We @@ -135,14 +136,15 @@ typedef SlruCtlData *SlruCtl; extern Size SimpleLruShmemSize(int nslots, int nlsns); extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns, - LWLock *ctllock, const char *subdir, int tranche_id); + LWLock *ctllock, const char *subdir, int tranche_id, + SyncRequestHandler sync_handler); extern int SimpleLruZeroPage(SlruCtl ctl, int pageno); extern int SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok, TransactionId xid); extern int SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno, TransactionId xid); extern void SimpleLruWritePage(SlruCtl ctl, int slotno); -extern void SimpleLruFlush(SlruCtl ctl, bool allow_redirtied); +extern void SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied); extern void SimpleLruTruncate(SlruCtl ctl, int cutoffPage); extern void SimpleLruTruncateWithLock(SlruCtl ctl, int cutoffPage); extern bool SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno); @@ -152,6 +154,8 @@ typedef bool (*SlruScanCallback) (SlruCtl ctl, char *filename, int segpage, extern bool SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data); extern void SlruDeleteSegment(SlruCtl ctl, int segno); +extern int SlruSyncFileTag(SlruCtl ctl, const FileTag *ftag, char *path); + /* SlruScanDirectory public callbacks */ extern bool SlruScanDirCbReportPresence(SlruCtl ctl, char *filename, int segpage, void *data); diff --git a/src/include/access/xact.h b/src/include/access/xact.h index e2f3b61e173a..5d8a3a295078 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -76,7 +76,8 @@ typedef enum SYNCHRONOUS_COMMIT_REMOTE_WRITE, /* wait for local flush and remote * write */ SYNCHRONOUS_COMMIT_REMOTE_FLUSH, /* wait for local and remote flush */ - SYNCHRONOUS_COMMIT_REMOTE_APPLY /* wait for local flush and remote apply */ + SYNCHRONOUS_COMMIT_REMOTE_APPLY /* wait for local and remote flush + and remote apply */ } SyncCommitLevel; /* Define the default setting for synchronous_commit */ diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index b543a1916185..b629a412dc71 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -55,6 +55,8 @@ extern bool XLogCheckBufferNeedsBackup(Buffer buffer); extern XLogRecPtr log_newpage(RelFileNode *rnode, ForkNumber forkNum, BlockNumber blk, char *page, bool page_std); +extern void log_newpages(RelFileNode *rnode, ForkNumber forkNum, int num_pages, + BlockNumber *blknos, char **pages, bool page_std); extern XLogRecPtr log_newpage_buffer(Buffer buffer, bool page_std); extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index 8f3ca7816814..a3733f49bed8 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -17,7 +17,7 @@ * XLogBeginRead() or XLogFindNextRecord(), and call XLogReadRecord() * until it returns NULL. * - * Callers supply a page_read callback if they want to to call + * Callers supply a page_read callback if they want to call * XLogReadRecord or XLogFindNextRecord; it can be passed in as NULL * otherwise. The WALRead function can be used as a helper to write * page_read callbacks, but it is not mandatory; callers that use it, diff --git a/src/include/c.h b/src/include/c.h index f618686c8ac3..6c38ff159fef 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -100,6 +100,7 @@ * * GCC: https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html * GCC: https://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html + * Clang: https://clang.llvm.org/docs/AttributeReference.html * Sunpro: https://docs.oracle.com/cd/E18659_01/html/821-1384/gjzke.html * XLC: https://www.ibm.com/support/knowledgecenter/SSGH2K_13.1.2/com.ibm.xlc131.aix.doc/language_ref/function_attributes.html * XLC: https://www.ibm.com/support/knowledgecenter/SSGH2K_13.1.2/com.ibm.xlc131.aix.doc/language_ref/type_attrib.html @@ -1156,7 +1157,8 @@ typedef union PGAlignedXLogBlock * access to the original string and translated string, and for cases where * immediate translation is not possible, like when initializing global * variables. - * http://www.gnu.org/software/autoconf/manual/gettext/Special-cases.html + * + * https://www.gnu.org/software/gettext/manual/html_node/Special-cases.html */ #define gettext_noop(x) (x) diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index add1503e3859..7c3d7c8999ca 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -56,6 +56,6 @@ */ /* 3yyymmddN */ -#define CATALOG_VERSION_NO 302605041 +#define CATALOG_VERSION_NO 302605211 #endif diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 4f240fedd9d9..87811244bc00 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -165,7 +165,8 @@ extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender, Node *expr, Oid relId, DependencyType behavior, DependencyType self_behavior, - bool reverse_self); + bool reverse_self, + bool record_version); extern ObjectClass getObjectClass(const ObjectAddress *object); @@ -185,16 +186,30 @@ extern void sort_object_addresses(ObjectAddresses *addrs); extern void free_object_addresses(ObjectAddresses *addrs); +typedef bool(*VisitDependenciesOfCB) (const ObjectAddress *otherObject, + const char *version, + char **new_version, + void *data); + +extern void visitDependenciesOf(const ObjectAddress *object, + VisitDependenciesOfCB callback, + void *data); + /* in pg_depend.c */ extern void recordDependencyOn(const ObjectAddress *depender, const ObjectAddress *referenced, DependencyType behavior); +extern void recordDependencyOnCollations(ObjectAddress *myself, + List *collations, + bool record_version); + extern void recordMultipleDependencies(const ObjectAddress *depender, const ObjectAddress *referenced, int nreferenced, - DependencyType behavior); + DependencyType behavior, + bool record_version); extern void recordDependencyOnCurrentExtension(const ObjectAddress *object, bool isReplace); @@ -215,10 +230,9 @@ extern long changeDependencyFor(Oid classId, Oid objectId, Oid refClassId, Oid oldRefObjectId, Oid newRefObjectId); -extern long changeDependenciesOf(Oid classId, Oid oldObjectId, +long changeDependenciesOf(Oid classId, Oid oldObjectId, Oid newObjectId); - -extern long changeDependenciesOn(Oid refClassId, Oid oldRefObjectId, +long changeDependenciesOn(Oid refClassId, Oid oldRefObjectId, Oid newRefObjectId); extern Oid getExtensionOfObject(Oid classId, Oid objectId); diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index 63d0bfa06b55..97f390fa6372 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -160,6 +160,7 @@ extern void RemoveAttributeById(Oid relid, AttrNumber attnum); extern void RemoveAttrDefault(Oid relid, AttrNumber attnum, DropBehavior behavior, bool complain, bool internal); extern void RemoveAttrDefaultById(Oid attrdefId); +extern void CopyStatistics(Oid fromrelid, Oid torelid); extern void RemoveStatistics(Oid relid, AttrNumber attnum); extern const FormData_pg_attribute *SystemAttributeDefinition(AttrNumber attno); diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index c0210a0b1fd0..ff947c8ebc91 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -130,6 +130,9 @@ extern void FormIndexDatum(IndexInfo *indexInfo, extern Oid setNewRelfilenodeToOid(Relation relation, TransactionId freezeXid, Oid newrelfilenode); +extern void index_check_collation_versions(Oid relid); +extern void index_update_collation_versions(Oid relid, Oid coll); + extern void index_build(Relation heapRelation, Relation indexRelation, IndexInfo *indexInfo, diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 7a486c2a263a..246a3eb87548 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -31,6 +31,12 @@ */ typedef struct ResultRelInfo *CatalogIndexState; +/* + * Cap the maximum amount of bytes allocated for multi-inserts with system + * catalogs, limiting the number of slots used. + */ +#define MAX_CATALOG_MULTI_INSERT_BYTES 65535 + /* * indexing.c prototypes */ diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat index c606948dfa6f..cad81556f809 100644 --- a/src/include/catalog/pg_am.dat +++ b/src/include/catalog/pg_am.dat @@ -19,7 +19,7 @@ { oid => '6131', oid_symbol => 'AO_ROW_TABLE_AM_OID', descr => 'row-oriented append-optimized table access method', amname => 'ao_row', amhandler => 'ao_row_tableam_handler', amtype => 't' }, -{ oid => '3435', oid_symbol => 'AO_COLUMN_TABLE_AM_OID', +{ oid => '8435', oid_symbol => 'AO_COLUMN_TABLE_AM_OID', descr => 'column-oriented append-optimized table access method', amname => 'ao_column', amhandler => 'ao_column_tableam_handler', amtype => 't' }, diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index e04b27d7cf1c..72cf2b17042d 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1100,10 +1100,6 @@ amopstrategy => '11', amopopr => '|>>(box,box)', amopmethod => 'gist' }, { amopfamily => 'gist/box_ops', amoplefttype => 'box', amoprighttype => 'box', amopstrategy => '12', amopopr => '|&>(box,box)', amopmethod => 'gist' }, -{ amopfamily => 'gist/box_ops', amoplefttype => 'box', amoprighttype => 'box', - amopstrategy => '13', amopopr => '~(box,box)', amopmethod => 'gist' }, -{ amopfamily => 'gist/box_ops', amoplefttype => 'box', amoprighttype => 'box', - amopstrategy => '14', amopopr => '@(box,box)', amopmethod => 'gist' }, { amopfamily => 'gist/box_ops', amoplefttype => 'box', amoprighttype => 'point', amopstrategy => '15', amoppurpose => 'o', amopopr => '<->(box,point)', amopmethod => 'gist', amopsortfamily => 'btree/float_ops' }, @@ -1175,12 +1171,6 @@ { amopfamily => 'gist/poly_ops', amoplefttype => 'polygon', amoprighttype => 'polygon', amopstrategy => '12', amopopr => '|&>(polygon,polygon)', amopmethod => 'gist' }, -{ amopfamily => 'gist/poly_ops', amoplefttype => 'polygon', - amoprighttype => 'polygon', amopstrategy => '13', - amopopr => '~(polygon,polygon)', amopmethod => 'gist' }, -{ amopfamily => 'gist/poly_ops', amoplefttype => 'polygon', - amoprighttype => 'polygon', amopstrategy => '14', - amopopr => '@(polygon,polygon)', amopmethod => 'gist' }, { amopfamily => 'gist/poly_ops', amoplefttype => 'polygon', amoprighttype => 'point', amopstrategy => '15', amoppurpose => 'o', amopopr => '<->(polygon,point)', amopmethod => 'gist', @@ -1223,12 +1213,6 @@ { amopfamily => 'gist/circle_ops', amoplefttype => 'circle', amoprighttype => 'circle', amopstrategy => '12', amopopr => '|&>(circle,circle)', amopmethod => 'gist' }, -{ amopfamily => 'gist/circle_ops', amoplefttype => 'circle', - amoprighttype => 'circle', amopstrategy => '13', - amopopr => '~(circle,circle)', amopmethod => 'gist' }, -{ amopfamily => 'gist/circle_ops', amoplefttype => 'circle', - amoprighttype => 'circle', amopstrategy => '14', - amopopr => '@(circle,circle)', amopmethod => 'gist' }, { amopfamily => 'gist/circle_ops', amoplefttype => 'circle', amoprighttype => 'point', amopstrategy => '15', amoppurpose => 'o', amopopr => '<->(circle,point)', amopmethod => 'gist', @@ -2454,8 +2438,6 @@ amoprighttype => 'box', amopstrategy => '12', amopopr => '|&>(box,box)', amopmethod => 'brin' }, -# we could, but choose not to, supply entries for strategies 13 and 14 - { amopfamily => 'brin/box_inclusion_ops', amoplefttype => 'box', amoprighttype => 'point', amopstrategy => '7', amopopr => '@>(box,point)', amopmethod => 'brin' }, diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat index 92af01e88011..3da7b3ba4a9e 100644 --- a/src/include/catalog/pg_amproc.dat +++ b/src/include/catalog/pg_amproc.dat @@ -484,6 +484,8 @@ amproc => 'gist_point_distance' }, { amprocfamily => 'gist/point_ops', amproclefttype => 'point', amprocrighttype => 'point', amprocnum => '9', amproc => 'gist_point_fetch' }, +{ amprocfamily => 'gist/point_ops', amproclefttype => 'point', + amprocrighttype => 'point', amprocnum => '11', amproc => 'gist_point_sortsupport' }, { amprocfamily => 'gist/box_ops', amproclefttype => 'box', amprocrighttype => 'box', amprocnum => '1', amproc => 'gist_box_consistent' }, { amprocfamily => 'gist/box_ops', amproclefttype => 'box', diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index fb1748a5bf5d..2f45bab571ff 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -62,8 +62,8 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat /* # of blocks (not always up-to-date) */ int32 relpages BKI_DEFAULT(0); - /* # of tuples (not always up-to-date) */ - float4 reltuples BKI_DEFAULT(0); + /* # of tuples (not always up-to-date; -1 means "unknown") */ + float4 reltuples BKI_DEFAULT(-1); /* # of all-visible blocks (not always up-to-date) */ int32 relallvisible BKI_DEFAULT(0); diff --git a/src/include/catalog/pg_collation.dat b/src/include/catalog/pg_collation.dat index ba1b3e201b67..45301ccdd7d1 100644 --- a/src/include/catalog/pg_collation.dat +++ b/src/include/catalog/pg_collation.dat @@ -15,17 +15,16 @@ { oid => '100', oid_symbol => 'DEFAULT_COLLATION_OID', descr => 'database\'s default collation', collname => 'default', collnamespace => 'PGNSP', collowner => 'PGUID', - collprovider => 'd', collencoding => '-1', collcollate => '', collctype => '', - collversion => '_null_' }, + collprovider => 'd', collencoding => '-1', collcollate => '', collctype => '' }, { oid => '950', oid_symbol => 'C_COLLATION_OID', descr => 'standard C collation', collname => 'C', collnamespace => 'PGNSP', collowner => 'PGUID', collprovider => 'c', collencoding => '-1', collcollate => 'C', - collctype => 'C', collversion => '_null_' }, + collctype => 'C' }, { oid => '951', oid_symbol => 'POSIX_COLLATION_OID', descr => 'standard POSIX collation', collname => 'POSIX', collnamespace => 'PGNSP', collowner => 'PGUID', collprovider => 'c', collencoding => '-1', collcollate => 'POSIX', - collctype => 'POSIX', collversion => '_null_' }, + collctype => 'POSIX' }, ] diff --git a/src/include/catalog/pg_collation.h b/src/include/catalog/pg_collation.h index 27618b324d3f..e7e958b80898 100644 --- a/src/include/catalog/pg_collation.h +++ b/src/include/catalog/pg_collation.h @@ -37,10 +37,6 @@ CATALOG(pg_collation,3456,CollationRelationId) int32 collencoding; /* encoding for this collation; -1 = "all" */ NameData collcollate; /* LC_COLLATE setting */ NameData collctype; /* LC_CTYPE setting */ -#ifdef CATALOG_VARLEN /* variable-length fields start here */ - text collversion; /* provider-dependent version of collation - * data */ -#endif } FormData_pg_collation; /* ---------------- @@ -65,7 +61,6 @@ extern Oid CollationCreate(const char *collname, Oid collnamespace, bool collisdeterministic, int32 collencoding, const char *collcollate, const char *collctype, - const char *collversion, bool if_not_exists, bool quiet); diff --git a/src/include/catalog/pg_depend.h b/src/include/catalog/pg_depend.h index c690f2d46a8c..25051b08a902 100644 --- a/src/include/catalog/pg_depend.h +++ b/src/include/catalog/pg_depend.h @@ -61,6 +61,9 @@ CATALOG(pg_depend,2608,DependRelationId) * field. See DependencyType in catalog/dependency.h. */ char deptype; /* see codes in dependency.h */ +#ifdef CATALOG_VARLEN + text refobjversion; /* version of referenced object */ +#endif } FormData_pg_depend; /* GPDB added foreign key definitions for gpcheckcat. */ diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index d9fbb2e33543..fb032d46aea2 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -223,12 +223,6 @@ oprname => '>=', oprleft => 'xid8', oprright => 'xid8', oprresult => 'bool', oprcom => '<=(xid8,xid8)', oprnegate => '<(xid8,xid8)', oprcode => 'xid8ge', oprrest => 'scalargesel', oprjoin => 'scalargejoinsel' }, -{ oid => '388', descr => 'factorial', - oprname => '!', oprkind => 'r', oprleft => 'int8', oprright => '0', - oprresult => 'numeric', oprcode => 'numeric_fac' }, -{ oid => '389', descr => 'deprecated, use ! instead', - oprname => '!!', oprkind => 'l', oprleft => '0', oprright => 'int8', - oprresult => 'numeric', oprcode => 'numeric_fac' }, { oid => '385', descr => 'equal', oid_symbol => 'CidEqualOperator', oprname => '=', oprcanhash => 't', oprleft => 'cid', oprright => 'cid', @@ -2814,71 +2808,6 @@ oprname => '||', oprleft => 'anynonarray', oprright => 'text', oprresult => 'text', oprcode => 'anytextcat' }, -# obsolete names for contains/contained-by operators; remove these someday -{ oid => '2860', descr => 'deprecated, use <@ instead', - oprname => '@', oprleft => 'polygon', oprright => 'polygon', - oprresult => 'bool', oprcom => '~(polygon,polygon)', - oprcode => 'poly_contained', oprrest => 'contsel', oprjoin => 'contjoinsel' }, -{ oid => '2861', descr => 'deprecated, use @> instead', - oprname => '~', oprleft => 'polygon', oprright => 'polygon', - oprresult => 'bool', oprcom => '@(polygon,polygon)', - oprcode => 'poly_contain', oprrest => 'contsel', oprjoin => 'contjoinsel' }, -{ oid => '2862', descr => 'deprecated, use <@ instead', - oprname => '@', oprleft => 'box', oprright => 'box', oprresult => 'bool', - oprcom => '~(box,box)', oprcode => 'box_contained', oprrest => 'contsel', - oprjoin => 'contjoinsel' }, -{ oid => '2863', descr => 'deprecated, use @> instead', - oprname => '~', oprleft => 'box', oprright => 'box', oprresult => 'bool', - oprcom => '@(box,box)', oprcode => 'box_contain', oprrest => 'contsel', - oprjoin => 'contjoinsel' }, -{ oid => '2864', descr => 'deprecated, use <@ instead', - oprname => '@', oprleft => 'circle', oprright => 'circle', - oprresult => 'bool', oprcom => '~(circle,circle)', - oprcode => 'circle_contained', oprrest => 'contsel', - oprjoin => 'contjoinsel' }, -{ oid => '2865', descr => 'deprecated, use @> instead', - oprname => '~', oprleft => 'circle', oprright => 'circle', - oprresult => 'bool', oprcom => '@(circle,circle)', - oprcode => 'circle_contain', oprrest => 'contsel', oprjoin => 'contjoinsel' }, -{ oid => '2866', descr => 'deprecated, use <@ instead', - oprname => '@', oprleft => 'point', oprright => 'box', oprresult => 'bool', - oprcode => 'on_pb' }, -{ oid => '2867', descr => 'deprecated, use <@ instead', - oprname => '@', oprleft => 'point', oprright => 'path', oprresult => 'bool', - oprcom => '~(path,point)', oprcode => 'on_ppath' }, -{ oid => '2868', descr => 'deprecated, use @> instead', - oprname => '~', oprleft => 'path', oprright => 'point', oprresult => 'bool', - oprcom => '@(point,path)', oprcode => 'path_contain_pt' }, -{ oid => '2869', descr => 'deprecated, use <@ instead', - oprname => '@', oprleft => 'point', oprright => 'polygon', - oprresult => 'bool', oprcom => '~(polygon,point)', - oprcode => 'pt_contained_poly' }, -{ oid => '2870', descr => 'deprecated, use @> instead', - oprname => '~', oprleft => 'polygon', oprright => 'point', - oprresult => 'bool', oprcom => '@(point,polygon)', - oprcode => 'poly_contain_pt' }, -{ oid => '2871', descr => 'deprecated, use <@ instead', - oprname => '@', oprleft => 'point', oprright => 'circle', oprresult => 'bool', - oprcom => '~(circle,point)', oprcode => 'pt_contained_circle' }, -{ oid => '2872', descr => 'deprecated, use @> instead', - oprname => '~', oprleft => 'circle', oprright => 'point', oprresult => 'bool', - oprcom => '@(point,circle)', oprcode => 'circle_contain_pt' }, -{ oid => '2873', descr => 'deprecated, use <@ instead', - oprname => '@', oprleft => 'point', oprright => 'line', oprresult => 'bool', - oprcode => 'on_pl' }, -{ oid => '2874', descr => 'deprecated, use <@ instead', - oprname => '@', oprleft => 'point', oprright => 'lseg', oprresult => 'bool', - oprcode => 'on_ps' }, -{ oid => '2875', descr => 'deprecated, use <@ instead', - oprname => '@', oprleft => 'lseg', oprright => 'line', oprresult => 'bool', - oprcode => 'on_sl' }, -{ oid => '2876', descr => 'deprecated, use <@ instead', - oprname => '@', oprleft => 'lseg', oprright => 'box', oprresult => 'bool', - oprcode => 'on_sb' }, -{ oid => '2877', descr => 'deprecated, use @> instead', - oprname => '~', oprleft => '_aclitem', oprright => 'aclitem', - oprresult => 'bool', oprcode => 'aclcontains' }, - # uuid operators { oid => '2972', descr => 'equal', oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'uuid', diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index b8422bc113b9..bbebecedd7d7 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -41,7 +41,7 @@ CATALOG(pg_operator,2617,OperatorRelationId) /* operator owner */ Oid oprowner BKI_DEFAULT(PGUID); - /* 'l', 'r', or 'b' */ + /* 'l' for prefix or 'b' for infix */ char oprkind BKI_DEFAULT(b); /* can be used in merge join? */ @@ -50,10 +50,10 @@ CATALOG(pg_operator,2617,OperatorRelationId) /* can be used in hash join? */ bool oprcanhash BKI_DEFAULT(f); - /* left arg type, or 0 if 'l' oprkind */ + /* left arg type, or 0 if prefix operator */ Oid oprleft BKI_LOOKUP(pg_type); - /* right arg type, or 0 if 'r' oprkind */ + /* right arg type */ Oid oprright BKI_LOOKUP(pg_type); /* result datatype */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4b7d3ae9175f..121ba59b80c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -327,9 +327,6 @@ { oid => '110', descr => 'I/O', proname => 'unknownout', prorettype => 'cstring', proargtypes => 'unknown', prosrc => 'unknownout' }, -{ oid => '111', - proname => 'numeric_fac', prorettype => 'numeric', proargtypes => 'int8', - prosrc => 'numeric_fac' }, { oid => '115', proname => 'box_above_eq', prorettype => 'bool', proargtypes => 'box box', @@ -904,19 +901,16 @@ prosrc => 'ftoi4' }, # Table access method handlers -{ oid => '3', oid_symbol => 'HEAP_TABLE_AM_HANDLER_OID', - descr => 'row-oriented heap table access method handler', +{ oid => '3', descr => 'row-oriented heap table access method handler', proname => 'heap_tableam_handler', provolatile => 'v', prorettype => 'table_am_handler', proargtypes => 'internal', prosrc => 'heap_tableam_handler' }, -{ oid => '4198', oid_symbol => 'AO_ROW_TABLE_AM_HANDLER_OID', - descr => 'row-oriented append-optimized table access method handler', +{ oid => '4198', descr => 'row-oriented append-optimized table access method handler', proname => 'ao_row_tableam_handler', provolatile => 'v', prorettype => 'table_am_handler', proargtypes => 'internal', prosrc => 'ao_row_tableam_handler' }, -{ oid => '4199', oid_symbol => 'AO_COLUMN_TABLE_AM_HANDLER_OID', - descr => 'column-oriented append-optimized table access method handler', +{ oid => '4199', descr => 'column-oriented append-optimized table access method handler', proname => 'ao_column_tableam_handler', provolatile => 'v', prorettype => 'table_am_handler', proargtypes => 'internal', prosrc => 'ao_column_tableam_handler' }, @@ -1573,16 +1567,24 @@ { oid => '383', proname => 'array_cat', proisstrict => 'f', prorettype => 'anyarray', proargtypes => 'anyarray anyarray', prosrc => 'array_cat' }, -{ oid => '394', descr => 'split delimited text into text[]', +{ oid => '394', descr => 'split delimited text', proname => 'string_to_array', proisstrict => 'f', prorettype => '_text', proargtypes => 'text text', prosrc => 'text_to_array' }, +{ oid => '376', descr => 'split delimited text, with null string', + proname => 'string_to_array', proisstrict => 'f', prorettype => '_text', + proargtypes => 'text text text', prosrc => 'text_to_array_null' }, +{ oid => '8432', descr => 'split delimited text', + proname => 'string_to_table', proisstrict => 'f', prorows => '1000', + proretset => 't', prorettype => 'text', proargtypes => 'text text', + prosrc => 'text_to_table' }, +{ oid => '8433', descr => 'split delimited text, with null string', + proname => 'string_to_table', proisstrict => 'f', prorows => '1000', + proretset => 't', prorettype => 'text', proargtypes => 'text text text', + prosrc => 'text_to_table_null' }, { oid => '395', descr => 'concatenate array elements, using delimiter, into text', proname => 'array_to_string', provolatile => 's', prorettype => 'text', proargtypes => 'anyarray text', prosrc => 'array_to_text' }, -{ oid => '376', descr => 'split delimited text into text[], with null string', - proname => 'string_to_array', proisstrict => 'f', prorettype => '_text', - proargtypes => 'text text text', prosrc => 'text_to_array_null' }, { oid => '384', descr => 'concatenate array elements, using delimiter and null string, into text', proname => 'array_to_string', proisstrict => 'f', provolatile => 's', @@ -3606,7 +3608,7 @@ prosrc => 'regexp_matches' }, { oid => '2088', descr => 'split string by field_sep and return field_num', proname => 'split_part', prorettype => 'text', - proargtypes => 'text text int4', prosrc => 'split_text' }, + proargtypes => 'text text int4', prosrc => 'split_part' }, { oid => '2765', descr => 'split string by pattern', proname => 'regexp_split_to_table', prorows => '1000', proretset => 't', prorettype => 'text', proargtypes => 'text text', @@ -3738,10 +3740,11 @@ prosrc => 'pg_get_function_arg_default' }, { oid => '1686', descr => 'list of SQL keywords', - proname => 'pg_get_keywords', procost => '10', prorows => '400', + proname => 'pg_get_keywords', procost => '10', prorows => '500', proretset => 't', provolatile => 's', prorettype => 'record', - proargtypes => '', proallargtypes => '{text,char,text}', - proargmodes => '{o,o,o}', proargnames => '{word,catcode,catdesc}', + proargtypes => '', proallargtypes => '{text,char,bool,text,text}', + proargmodes => '{o,o,o,o,o}', + proargnames => '{word,catcode,barelabel,catdesc,baredesc}', prosrc => 'pg_get_keywords' }, { oid => '2289', descr => 'convert generic options array to name/value table', @@ -5331,6 +5334,14 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,status,receive_start_lsn,receive_start_tli,written_lsn,flushed_lsn,received_tli,last_msg_send_time,last_msg_receipt_time,latest_end_lsn,latest_end_time,slot_name,sender_host,sender_port,conninfo}', prosrc => 'pg_stat_get_wal_receiver' }, +{ oid => '8595', descr => 'statistics: information about replication slots', + proname => 'pg_stat_get_replication_slots', prorows => '10', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => '', + proallargtypes => '{text,int8,int8,int8,int8,int8,int8,timestamptz}', + proargmodes => '{o,o,o,o,o,o,o,o}', + proargnames => '{slot_name,spill_txns,spill_count,spill_bytes,stream_txns,stream_count,stream_bytes,stats_reset}', + prosrc => 'pg_stat_get_replication_slots' }, { oid => '6118', descr => 'statistics: information about subscription', proname => 'pg_stat_get_subscription', proisstrict => 'f', provolatile => 's', proparallel => 'r', prorettype => 'record', proargtypes => 'oid', @@ -5555,6 +5566,14 @@ proname => 'pg_stat_get_buf_alloc', provolatile => 's', proparallel => 'r', prorettype => 'int8', proargtypes => '', prosrc => 'pg_stat_get_buf_alloc' }, +{ oid => '1136', descr => 'statistics: information about WAL activity', + proname => 'pg_stat_get_wal', proisstrict => 'f', provolatile => 's', + proparallel => 'r', prorettype => 'record', proargtypes => '', + proallargtypes => '{int8,timestamptz}', + proargmodes => '{o,o}', + proargnames => '{wal_buffers_full,stats_reset}', + prosrc => 'pg_stat_get_wal' }, + { oid => '2306', descr => 'statistics: information about SLRU caches', proname => 'pg_stat_get_slru', prorows => '100', proisstrict => 'f', proretset => 't', provolatile => 's', proparallel => 'r', @@ -5672,6 +5691,10 @@ descr => 'statistics: reset collected statistics for a single SLRU', proname => 'pg_stat_reset_slru', proisstrict => 'f', provolatile => 'v', prorettype => 'void', proargtypes => 'text', prosrc => 'pg_stat_reset_slru' }, +{ oid => '8596', + descr => 'statistics: reset collected statistics for a single replication slot', + proname => 'pg_stat_reset_replication_slot', proisstrict => 'f', provolatile => 'v', + prorettype => 'void', proargtypes => 'text', prosrc => 'pg_stat_reset_replication_slot' }, { oid => '3163', descr => 'current trigger depth', proname => 'pg_trigger_depth', provolatile => 's', proparallel => 'r', @@ -6509,9 +6532,7 @@ proargtypes => 'pg_lsn', prosrc => 'aggregate_dummy' }, # count has two forms: count(any) and count(*) -{ oid => '2147', - oid_symbol => 'COUNT_ANY_OID', - descr => 'number of input rows for which the input expression is not null', +{ oid => '2147', descr => 'number of input rows for which the input expression is not null', proname => 'count', prokind => 'a', proisstrict => 'f', prorettype => 'int8', proargtypes => 'any', prosrc => 'aggregate_dummy' }, { oid => '2803', descr => 'number of input rows', @@ -8147,6 +8168,9 @@ proname => 'gist_poly_distance', prorettype => 'float8', proargtypes => 'internal polygon int2 oid internal', prosrc => 'gist_poly_distance' }, +{ oid => '3435', descr => 'sort support', + proname => 'gist_point_sortsupport', prorettype => 'void', + proargtypes => 'internal', prosrc => 'gist_point_sortsupport' }, # GIN array support { oid => '2743', descr => 'GIN array support', @@ -11361,7 +11385,7 @@ prosrc => 'gp_percentile_disc_transition' }, { oid => 7194, descr => 'unordered percentile discrete aggregate', proname => 'gp_percentile_disc', prokind => 'a', proisstrict => 'f', prorettype => 'anyelement', proargtypes => 'anyelement float8 int8 int8', prosrc => 'aggregate_dummy' }, -{ oid => 7050, oid_symbol => 'BITMAP_INDEXAM_HANDLER_OID', descr => 'bitmap(internal)', +{ oid => 7050, descr => 'bitmap(internal)', proname => 'bmhandler', provolatile => 'v', prorettype => 'index_am_handler', proargtypes => 'internal', prosrc => 'bmhandler' }, @@ -11859,16 +11883,16 @@ { oid => 6126, descr => 'aggregate final function', proname => 'percentile_cont_timestamptz_multi_final', proisstrict => 'f', prorettype => '_timestamptz', proargtypes => 'internal _float8', prosrc => 'percentile_cont_timestamptz_multi_final' }, -{ oid => 6127, oid_symbol => 'MEDIAN_FLOAT8_OID', descr => 'median', +{ oid => 6127, descr => 'median', proname => 'median', prokind => 'a', proisstrict => 'f', prorettype => 'float8', proargtypes => 'float8 float8', prosrc => 'aggregate_dummy' }, -{ oid => 6128, oid_symbol => 'MEDIAN_INTERVAL_OID', descr => 'median', +{ oid => 6128, descr => 'median', proname => 'median', prokind => 'a', proisstrict => 'f', prorettype => 'interval', proargtypes => 'float8 interval', prosrc => 'aggregate_dummy' }, -{ oid => 6129, oid_symbol => 'MEDIAN_TIMESTAMP_OID', descr => 'median', +{ oid => 6129, descr => 'median', proname => 'median', prokind => 'a', proisstrict => 'f', prorettype => 'timestamp', proargtypes => 'float8 timestamp', prosrc => 'aggregate_dummy' }, -{ oid => 6130, oid_symbol => 'MEDIAN_TIMESTAMPTZ_OID', descr => 'median', +{ oid => 6130, descr => 'median', proname => 'median', prokind => 'a', proisstrict => 'f', prorettype => 'timestamptz', proargtypes => 'float8 timestamptz', prosrc => 'aggregate_dummy' }, { oid => 8066, descr => 'itemwise add two integer arrays', diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 543be728f687..24df4e58e046 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -93,7 +93,7 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce * proargtypes */ - /* parameter types (excludes OUT params) */ + /* parameter types (excludes OUT params of functions) */ oidvector proargtypes BKI_LOOKUP(pg_type) BKI_FORCE_NOT_NULL; #ifdef CATALOG_VARLEN @@ -248,10 +248,10 @@ extern bool function_parse_error_transpose(const char *prosrc); extern List *oid_array_to_list(Datum datum); -#define IS_MEDIAN_OID(x) ((x) == MEDIAN_FLOAT8_OID || \ - (x) == MEDIAN_INTERVAL_OID || \ - (x) == MEDIAN_TIMESTAMP_OID || \ - (x) == MEDIAN_TIMESTAMPTZ_OID) +#define IS_MEDIAN_OID(x) ((x) == F_MEDIAN_FLOAT8_FLOAT8 || \ + (x) == F_MEDIAN_FLOAT8_INTERVAL || \ + (x) == F_MEDIAN_FLOAT8_TIMESTAMP || \ + (x) == F_MEDIAN_FLOAT8_TIMESTAMPTZ) #endif /* PG_PROC_H */ diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h index 9795c35000d8..9ebec7bf0bff 100644 --- a/src/include/catalog/pg_subscription.h +++ b/src/include/catalog/pg_subscription.h @@ -51,6 +51,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW bool subbinary; /* True if the subscription wants the * publisher to send data in binary */ + bool substream; /* Stream in-progress transactions. */ + #ifdef CATALOG_VARLEN /* variable-length fields start here */ /* Connection string to the publisher */ text subconninfo BKI_FORCE_NOT_NULL; @@ -78,6 +80,7 @@ typedef struct Subscription bool enabled; /* Indicates if the subscription is enabled */ bool binary; /* Indicates if the subscription wants data in * binary format */ + bool stream; /* Allow streaming in-progress transactions. */ char *conninfo; /* Connection string to the publisher */ char *slotname; /* Name of the replication slot */ char *synccommit; /* Synchronous commit setting for worker */ diff --git a/src/include/catalog/pg_subscription_rel.h b/src/include/catalog/pg_subscription_rel.h index f384f4e7fa65..ff5c8d7ff91c 100644 --- a/src/include/catalog/pg_subscription_rel.h +++ b/src/include/catalog/pg_subscription_rel.h @@ -80,8 +80,7 @@ extern void AddSubscriptionRelState(Oid subid, Oid relid, char state, XLogRecPtr sublsn); extern void UpdateSubscriptionRelState(Oid subid, Oid relid, char state, XLogRecPtr sublsn); -extern char GetSubscriptionRelState(Oid subid, Oid relid, - XLogRecPtr *sublsn, bool missing_ok); +extern char GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn); extern void RemoveSubscriptionRel(Oid subid, Oid relid); extern List *GetSubscriptionRelations(Oid subid); diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index c52fd419b1ff..48765a88bb3f 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -15,14 +15,10 @@ # For types used in the system catalogs, make sure the values here match # TypInfo[] in bootstrap.c. -# OID symbol macro names for pg_type OIDs are generated by genbki.pl -# according to the following rule, so you don't need to specify them -# here: +# OID symbol macro names for pg_type OIDs are not specified here because +# they are generated by genbki.pl according to the following rule: # foo_bar -> FOO_BAROID # _foo_bar -> FOO_BARARRAYOID -# -# The only oid_symbol entries in this file are for names that don't match -# this rule, and are grandfathered in. # To autogenerate an array type, add 'array_type_oid => 'nnnn' to the element # type, which will instruct genbki.pl to generate a BKI entry for it. @@ -144,35 +140,30 @@ typname => 'xml', typlen => '-1', typbyval => 'f', typcategory => 'U', typinput => 'xml_in', typoutput => 'xml_out', typreceive => 'xml_recv', typsend => 'xml_send', typalign => 'i', typstorage => 'x' }, -{ oid => '194', oid_symbol => 'PGNODETREEOID', - descr => 'string representing an internal node tree', +{ oid => '194', descr => 'string representing an internal node tree', typname => 'pg_node_tree', typlen => '-1', typbyval => 'f', typcategory => 'S', typinput => 'pg_node_tree_in', typoutput => 'pg_node_tree_out', typreceive => 'pg_node_tree_recv', typsend => 'pg_node_tree_send', typalign => 'i', typstorage => 'x', typcollation => 'default' }, -{ oid => '3361', oid_symbol => 'PGNDISTINCTOID', - descr => 'multivariate ndistinct coefficients', +{ oid => '3361', descr => 'multivariate ndistinct coefficients', typname => 'pg_ndistinct', typlen => '-1', typbyval => 'f', typcategory => 'S', typinput => 'pg_ndistinct_in', typoutput => 'pg_ndistinct_out', typreceive => 'pg_ndistinct_recv', typsend => 'pg_ndistinct_send', typalign => 'i', typstorage => 'x', typcollation => 'default' }, -{ oid => '3402', oid_symbol => 'PGDEPENDENCIESOID', - descr => 'multivariate dependencies', +{ oid => '3402', descr => 'multivariate dependencies', typname => 'pg_dependencies', typlen => '-1', typbyval => 'f', typcategory => 'S', typinput => 'pg_dependencies_in', typoutput => 'pg_dependencies_out', typreceive => 'pg_dependencies_recv', typsend => 'pg_dependencies_send', typalign => 'i', typstorage => 'x', typcollation => 'default' }, -{ oid => '5017', oid_symbol => 'PGMCVLISTOID', - descr => 'multivariate MCV list', +{ oid => '5017', descr => 'multivariate MCV list', typname => 'pg_mcv_list', typlen => '-1', typbyval => 'f', typcategory => 'S', typinput => 'pg_mcv_list_in', typoutput => 'pg_mcv_list_out', typreceive => 'pg_mcv_list_recv', typsend => 'pg_mcv_list_send', typalign => 'i', typstorage => 'x', typcollation => 'default' }, -{ oid => '32', oid_symbol => 'PGDDLCOMMANDOID', - descr => 'internal type for passing CollectedCommand', +{ oid => '32', descr => 'internal type for passing CollectedCommand', typname => 'pg_ddl_command', typlen => 'SIZEOF_POINTER', typbyval => 't', typtype => 'p', typcategory => 'P', typinput => 'pg_ddl_command_in', typoutput => 'pg_ddl_command_out', typreceive => 'pg_ddl_command_recv', @@ -237,7 +228,7 @@ typname => 'circle', typlen => '24', typbyval => 'f', typcategory => 'G', typinput => 'circle_in', typoutput => 'circle_out', typreceive => 'circle_recv', typsend => 'circle_send', typalign => 'd' }, -{ oid => '790', oid_symbol => 'CASHOID', array_type_oid => '791', +{ oid => '790', array_type_oid => '791', descr => 'monetary amounts, $d,ddd.cc', typname => 'money', typlen => '8', typbyval => 'FLOAT8PASSBYVAL', typcategory => 'N', typinput => 'cash_in', typoutput => 'cash_out', @@ -409,8 +400,7 @@ typsend => 'uuid_send', typalign => 'c' }, # pg_lsn -{ oid => '3220', oid_symbol => 'LSNOID', array_type_oid => '3221', - descr => 'PostgreSQL LSN datatype', +{ oid => '3220', array_type_oid => '3221', descr => 'PostgreSQL LSN datatype', typname => 'pg_lsn', typlen => '8', typbyval => 'FLOAT8PASSBYVAL', typcategory => 'U', typinput => 'pg_lsn_in', typoutput => 'pg_lsn_out', typreceive => 'pg_lsn_recv', typsend => 'pg_lsn_send', typalign => 'd' }, @@ -542,7 +532,7 @@ typname => 'trigger', typlen => '4', typbyval => 't', typtype => 'p', typcategory => 'P', typinput => 'trigger_in', typoutput => 'trigger_out', typreceive => '-', typsend => '-', typalign => 'i' }, -{ oid => '3838', oid_symbol => 'EVTTRIGGEROID', +{ oid => '3838', descr => 'pseudo-type for the result of an event trigger function', typname => 'event_trigger', typlen => '4', typbyval => 't', typtype => 'p', typcategory => 'P', typinput => 'event_trigger_in', @@ -630,13 +620,11 @@ typoutput => 'anycompatiblerange_out', typreceive => '-', typsend => '-', typalign => 'd', typstorage => 'x' }, -{ oid => '7198', oid_symbol => 'COMPLEXOID', array_type_oid => '7199', - descr => 'double-precision floating point complex number, 16-byte storage', +{ oid => '7198', array_type_oid => '7199', descr => 'double-precision floating point complex number, 16-byte storage', typname => 'complex', typlen => '16', typbyval => 'f', typcategory => 'N', typdelim => '\054', typinput => 'complex_in', typoutput => 'complex_out', typreceive => 'complex_recv', typsend => 'complex_send', typalign => 'd' }, -{ oid => '7053', oid_symbol => 'ANYTABLEOID', - descr => 'Represents a generic TABLE value expression', +{ oid => '7053', descr => 'Represents a generic TABLE value expression', typname => 'anytable', typlen => '-1', typbyval => 'f', typtype => 'p', typcategory => 'P', typdelim => '\054', typinput => 'anytable_in', typoutput => 'anytable_out', typreceive => '-', typsend => '-', diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 3408b85fd3e1..98bc19c00360 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -328,6 +328,13 @@ typedef FormData_pg_type *Form_pg_type; #define TypeSupportsDescribe(typid) \ ((typid) == RECORDOID) +/* + * Backwards compatibility for ancient random spellings of pg_type OID macros. + * Don't use these names in new code. + */ +#define CASHOID MONEYOID +#define LSNOID PG_LSNOID + #endif /* EXPOSE_TO_CLIENT_CODE */ @@ -377,6 +384,8 @@ extern void GenerateTypeDependencies(HeapTuple typeTuple, bool isDependentType, bool rebuild); +extern List *GetTypeCollations(Oid typeObjectid); + extern void RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace); diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h index 84fcd796729a..bcf08ea30c4e 100644 --- a/src/include/catalog/toasting.h +++ b/src/include/catalog/toasting.h @@ -50,9 +50,9 @@ extern void BootstrapToastTable(char *relName, /* normal catalogs */ DECLARE_TOAST(pg_aggregate, 4159, 4160); DECLARE_TOAST(pg_attrdef, 2830, 2831); -DECLARE_TOAST(pg_collation, 4161, 4162); DECLARE_TOAST(pg_constraint, 2832, 2833); DECLARE_TOAST(pg_default_acl, 4143, 4144); +DECLARE_TOAST(pg_depend, 8888, 8889); DECLARE_TOAST(pg_description, 2834, 2835); DECLARE_TOAST(pg_event_trigger, 4145, 4146); DECLARE_TOAST(pg_extension, 4147, 4148); diff --git a/src/include/commands/collationcmds.h b/src/include/commands/collationcmds.h index 373b85374c42..3e1c16ac7f05 100644 --- a/src/include/commands/collationcmds.h +++ b/src/include/commands/collationcmds.h @@ -20,6 +20,5 @@ extern ObjectAddress DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_exists); extern void IsThereCollationInNamespace(const char *collname, Oid nspOid); -extern ObjectAddress AlterCollation(AlterCollationStmt *stmt); #endif /* COLLATIONCMDS_H */ diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 9e9b39713cf6..ab9efde87b24 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -40,7 +40,7 @@ extern ObjectAddress DefineIndex(Oid relationId, extern void ReindexIndex(ReindexStmt *stmt, bool isTopLevel); extern Oid ReindexTable(ReindexStmt *stmt, bool isTopLevel); extern void ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind, - int options, bool concurrent); + int options); extern char *makeObjectName(const char *name1, const char *name2, const char *label); extern char *ChooseRelationName(const char *name1, const char *name2, diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 6ca102f01844..e895c943b152 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -49,7 +49,7 @@ typedef struct TriggerData * The state for capturing old and new tuples into transition tables for a * single ModifyTable node (or other operation source, e.g. copy.c). * - * This is per-caller to avoid conflicts in setting tcs_map or + * This is per-caller to avoid conflicts in setting * tcs_original_insert_tuple. Note, however, that the pointed-to * private data may be shared across multiple callers. */ @@ -68,14 +68,6 @@ typedef struct TransitionCaptureState bool tcs_update_new_table; bool tcs_insert_new_table; - /* - * For UPDATE and DELETE, AfterTriggerSaveEvent may need to convert the - * new and old tuples from a child table's format to the format of the - * relation named in a query so that it is compatible with the transition - * tuplestores. The caller must store the conversion map here if so. - */ - TupleConversionMap *tcs_map; - /* * For INSERT and COPY, it would be wasteful to convert tuples from child * format to parent format after they have already been converted in the diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h index a7add75efa1d..fef846485f83 100644 --- a/src/include/common/file_utils.h +++ b/src/include/common/file_utils.h @@ -1,6 +1,4 @@ /*------------------------------------------------------------------------- - * - * File-processing utility routines for frontend code * * Assorted utility functions to work on files. * @@ -15,10 +13,28 @@ #ifndef FILE_UTILS_H #define FILE_UTILS_H +#include + +typedef enum PGFileType +{ + PGFILETYPE_ERROR, + PGFILETYPE_UNKNOWN, + PGFILETYPE_REG, + PGFILETYPE_DIR, + PGFILETYPE_LNK +} PGFileType; + +#ifdef FRONTEND extern int fsync_fname(const char *fname, bool isdir); extern void fsync_pgdata(const char *pg_data, int serverVersion); extern void fsync_dir_recurse(const char *dir); extern int durable_rename(const char *oldfile, const char *newfile); extern int fsync_parent_path(const char *fname); +#endif + +extern PGFileType get_dirent_type(const char *path, + const struct dirent *de, + bool look_through_symlinks, + int elevel); #endif /* FILE_UTILS_H */ diff --git a/src/include/common/keywords.h b/src/include/common/keywords.h index 257c050903e3..c9f9a9f991a7 100644 --- a/src/include/common/keywords.h +++ b/src/include/common/keywords.h @@ -25,9 +25,11 @@ #ifndef FRONTEND extern PGDLLIMPORT const ScanKeywordList ScanKeywords; extern PGDLLIMPORT const uint8 ScanKeywordCategories[]; +extern PGDLLIMPORT const bool ScanKeywordBareLabel[]; #else extern const ScanKeywordList ScanKeywords; extern const uint8 ScanKeywordCategories[]; +extern const bool ScanKeywordBareLabel[]; #endif #endif /* KEYWORDS_H */ diff --git a/src/include/common/logging.h b/src/include/common/logging.h index 028149c7a152..3205b8fef9b7 100644 --- a/src/include/common/logging.h +++ b/src/include/common/logging.h @@ -66,6 +66,7 @@ extern enum pg_log_level __pg_log_level; void pg_logging_init(const char *argv0); void pg_logging_config(int new_flags); void pg_logging_set_level(enum pg_log_level new_level); +void pg_logging_increase_verbosity(void); void pg_logging_set_pre_callback(void (*cb) (void)); void pg_logging_set_locus_callback(void (*cb) (const char **filename, uint64 *lineno)); diff --git a/src/include/common/string.h b/src/include/common/string.h index c7f52c3b0b04..6a4baa6f3590 100644 --- a/src/include/common/string.h +++ b/src/include/common/string.h @@ -12,6 +12,7 @@ struct StringInfoData; /* avoid including stringinfo.h here */ +/* functions in src/common/string.c */ extern bool pg_str_endswith(const char *str, const char *end); extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base); @@ -19,7 +20,11 @@ extern void pg_clean_ascii(char *str); extern int pg_strip_crlf(char *str); /* functions in src/common/pg_get_line.c */ +extern char *pg_get_line(FILE *stream); extern bool pg_get_line_buf(FILE *stream, struct StringInfoData *buf); extern bool pg_get_line_append(FILE *stream, struct StringInfoData *buf); +/* functions in src/common/sprompt.c */ +extern char *simple_prompt(const char *prompt, bool echo); + #endif /* COMMON_STRING_H */ diff --git a/src/include/common/unicode_norm_hashfunc.h b/src/include/common/unicode_norm_hashfunc.h new file mode 100644 index 000000000000..e6acb2a8d0f7 --- /dev/null +++ b/src/include/common/unicode_norm_hashfunc.h @@ -0,0 +1,2932 @@ +/*------------------------------------------------------------------------- + * + * unicode_norm_hashfunc.h + * Perfect hash functions used for Unicode normalization + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/common/unicode_norm_hashfunc.h + * + *------------------------------------------------------------------------- + */ + +/* + * File auto-generated by src/common/unicode/generate-unicode_norm_table.pl, + * do not edit. There is deliberately not an #ifndef PG_UNICODE_NORM_HASHFUNC_H + * here. + */ + +#include "common/unicode_norm_table.h" + +/* Typedef for perfect hash functions */ +typedef int (*cp_hash_func) (const void *key); + +/* Information for lookups with perfect hash functions */ +typedef struct +{ + const pg_unicode_decomposition *decomps; + cp_hash_func hash; + int num_decomps; +} pg_unicode_decompinfo; + +typedef struct +{ + const uint16 *inverse_lookup; + cp_hash_func hash; + int num_recomps; +} pg_unicode_recompinfo; + +/* Perfect hash function for decomposition */ +static int +Decomp_hash_func(const void *key) +{ + static const int16 h[13209] = { + 0, 1515, 4744, 4745, 0, 0, 0, 0, + 0, 0, 0, 0, 3890, 3890, 0, 0, + 3891, 3891, -2046, 2800, 3890, 3890, 3890, -4396, + 4361, 4362, -4441, -4441, -4396, 1773, 1773, 1773, + 4372, 4373, -4438, -4438, -4393, -4393, 2619, 17, + -4347, -4393, -4393, -4393, -4393, -4393, 2619, 2619, + 1560, 4346, 4347, 4348, 1917, 1873, 1874, 1875, + -7856, 4358, 17619, 2622, 2622, 2622, 6357, 6358, + 6359, 6360, 6361, 6362, 6363, 2622, -4390, -4390, + 4414, -5356, -5356, 4374, 4375, -5356, -5356, -6335, + -3020, 2511, -5356, -5356, -3583, -3583, -3583, -3583, + -995, 0, 0, -9799, -9754, 2874, 2875, 2876, + 2877, 2878, -9830, -3591, -9756, -9756, -2744, -5346, + -9710, -9756, 342, -5346, -9756, -5346, -2743, -449, + 348, 2894, 2895, -2853, 2897, 2898, 2899, 2900, + 2901, 2902, 2903, 2904, 2905, 2906, 2907, 2908, + 2909, 2910, 2911, 2912, 2913, 2914, 2915, 2916, + 2917, 2918, 2919, 2920, 2921, 2922, 2923, 2924, + 2925, 2926, 2927, 2928, 2929, 2930, 2931, 2932, + 2933, 2934, 32767, 32767, 32767, 32767, 32767, 32767, + -8721, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 1, 32767, 48, 32767, 32767, 32767, 32767, 49, + 32767, 32767, -8687, -8687, -6255, -6210, 32767, 32767, + -8689, -8689, -21949,32767, -18635,-15320,-15320,32767, + -12006,-8691, -8691, -8691, -8691, -8691, 32767, 66, + -8737, -8737, -8692, -8692, -8692, -8692, 73, 74, + 32767, -8738, -8693, -8693, -8693, -8693, -8693, 32767, + 32767, -8695, -8695, -8695, -8695, -8695, 32767, 32767, + 40, 41, -2390, -2434, 44, 45, 32767, 46, + 13307, 9993, 9994, 6680, 6681, 3367, 3368, 54, + 0, 55, 56, 57, -8699, -8699, 105, 32767, + 32767, 61, 62, 63, -8701, -8701, 32767, 111, + 32767, 67, 68, 69, 70, 1890, 3687, -1272, + 3690, 75, 76, 77, 78, 79, 80, 81, + 82, 32767, 32767, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 32767, + 32767, 103, 104, 105, 106, 107, 108, 109, + -8660, -8660, 32767, -8661, -8661, -8661, -8661, -8661, + -8661, 32767, 73, 74, 75, 76, -2355, -2399, + 79, 80, 32767, 32767, 13341, 10027, 10028, 6714, + 6715, 3401, 3402, 32767, 32767, 88, 89, 90, + -8666, -8666, 138, 32767, 32767, 94, 95, 96, + -8668, -8668, 144, 145, 101, -2553, -2553, -2553, + -2553, -4983, -2553, -2553, 154, -2553, 156, 32767, + 32767, 6114, 158, -3153, -3152, -3151, -12891,-6888, + -931, -3149, 166, -3148, -4728, 169, -3147, -3146, + -3145, -3144, -3143, -3142, -3141, -2543, -3139, -3138, + 180, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 3314, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 0, 3660, 3661, 2131, 2132, 2133, 2134, 2135, + 2136, 2137, 2138, 2139, 2140, 2141, 2142, 2143, + 2144, 2145, -5472, -5472, -3612, -3612, -3612, -3612, + -3612, 2652, -3612, -3612, -3612, -3612, -3612, -3612, + -3612, -3612, 3693, -3613, -7015, -7015, 1742, 1743, + -7060, -7060, -7015, -846, -846, -846, 1753, 1754, + -7057, -7057, -7012, -7012, 0, -2602, -6966, -7012, + -7012, -7012, -7012, -7012, 0, 0, 1725, 1726, + 1727, 1728, -703, -747, -746, 0, 1735, 1736, + 14997, 0, 0, 0, 3735, 3736, 3737, 3738, + 3739, 3740, 3741, 0, -7012, -7012, 1792, 1793, + 1749, 1750, 1751, -7980, -7980, -8959, -5644, -113, + -7980, -113, -2382, -6116, -6116, -6116, -6116, -6116, + -6116, -6116, -2374, 4639, 4640, -4163, 5608, 5609, + -4120, -4120, 5612, 5613, 6593, 3279, -2251, 5617, + 5618, 3846, 3847, 3848, 3849, 1262, 1262, 10066, + 10067, 10023, 3855, 3856, 3857, 1259, 1259, 10071, + 3861, 10027, 10028, 3017, 5620, 9985, 10032, -65, + 5624, 10035, 5626, 3024, 731, -65, 1298, 12530, + 3727, 3727, 3772, 3772, 3772, 13504, 13505, 14485, + 11171, 5641, 13509, 5643, 7913, 11648, 11649, 11650, + 11651, 11652, 11653, 11654, 7913, 901, 901, 9705, + -65, -65, 9665, 9666, -65, -65, -1044, 2271, + 7802, -65, -65, 1708, 1708, 1708, 1708, 4296, + 4297, -4506, -4506, -4461, 1708, 1708, 1708, 4307, + 4308, -4503, 1708, -4457, -4457, 2555, -47, -4411, + -4457, 5641, -47, -4457, -47, 2556, 4850, 5647, + 4285, -6946, 1858, 1859, 1815, 1816, 1817, -7914, + -7914, -8893, -5578, -47, -7914, -47, -2316, -6050, + -6050, -6050, -6050, -6050, -6050, -6050, -2308, 4705, + 4706, -4097, 5674, 5675, -4054, -4054, 5678, 5679, + 6659, 3345, -2185, 5683, 5684, 3912, 3913, 3914, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, -3083, -3083, 232, 287, 233, 233, + 233, 8990, 8991, 32767, 32767, 3668, 32767, 3667, + 3667, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 208, 208, 208, 208, 208, 208, + 32767, 32767, 206, 206, 206, 206, 206, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 304, 305, -1274, 307, 308, + 309, 6753, -1374, 10488, 4486, -1470, 4488, 316, + 4489, -5607, 4490, 4491, 4492, 322, 760, 324, + 325, 326, 166, 763, 329, -2553, 765, 332, + 333, 334, 335, 772, 337, 6310, 339, 340, + 341, 342, 343, 344, 345, 346, -2542, -2542, + -2542, 350, 351, 352, 353, 354, 355, 356, + 357, 358, 359, 360, 361, 362, -6008, 364, + 365, 366, 367, 368, 369, 370, 254, 372, + 373, 374, 375, 376, 377, 378, 379, 380, + 381, 382, 32767, 383, 384, -3606, -3605, -3604, + -3603, 389, -3600, -3599, -3598, 2340, -1238, -3595, + -3594, -3593, 4694, -4062, -4062, 4742, 4743, 4699, + -1469, -1468, -1467, -4065, -4065, 4747, -1463, 4703, + 4704, -2307, 296, 32767, 0, 32767, 32767, 4708, + -1376, -1376, -1376, 32767, 32767, -1246, 506, 506, + 0, -1559, 32767, 32767, 32767, 32767, 32767, 305, + 419, 308, 2578, 6313, 6314, 424, 32767, -6030, + 32767, 426, 427, 428, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 0, 32767, 0, + 32767, 0, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 0, 32767, 429, -5407, 431, + -5406, 433, -3601, 435, 32767, -3751, 32767, 32767, + 32767, 32767, -3755, 32767, 32767, 32767, 32767, 0, + 32767, 32767, 32767, 32767, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 436, -11425,-5422, + 535, -5422, 535, -5422, 4675, -5421, -5421, -5421, + -5421, -5421, 4681, 0, 0, 0, 4682, 4683, + 4684, 4685, 4686, 4687, 0, 0, 32767, 32767, + 0, 0, -5684, 0, 4688, 4689, 4690, 4691, + 4692, 4693, 4694, 4695, -1257, -1257, 4696, -5441, + -5441, 4699, 4700, 4701, -5443, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 454, 0, 32767, 456, + 32767, 32767, 0, 457, 32767, 32767, 32767, 0, + 458, 459, 460, 32767, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 4703, 4704, 4705, 4706, 32767, + 32767, 0, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 4655, 4656, 4657, 4658, + 4659, 4712, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 462, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 463, 464, 32767, 465, + 32767, 32767, 32767, 466, 32767, 32767, 32767, 32767, + 467, 468, 469, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 3011, 3011, 3011, + 3011, 3011, 3011, 3011, 32767, 32767, 32767, 32767, + 32767, 32767, 470, 471, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 472, + 473, 474, 475, 476, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 4713, 4714, 4715, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 477, 478, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 479, 480, 481, 482, + 32767, 32767, 483, 484, 32767, 32767, 485, 486, + 487, 488, 489, 490, 32767, 32767, 491, 492, + 493, 494, 495, 496, 32767, 32767, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 665, -255, 667, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 693, 694, 695, 696, + 697, 698, 699, 700, 701, 702, 703, 704, + 705, 706, 707, 708, 709, 710, 711, 712, + 7183, 714, -1580, 716, 2547, 718, 7194, 720, + 2553, 722, 723, 7204, 725, 726, 727, 728, + 729, 730, 731, 732, 733, 734, 735, 736, + 0, 0, 8114, 8159, 745, -1535, 747, 748, + 8161, -5019, -5019, -5019, -5019, 1938, 0, 0, + 0, 0, 0, 0, 767, 768, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 32767, 32767, 32767, 32767, 32767, 0, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, -2875, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, -2884, -2884, + -2884, -2884, -2884, -2884, -2884, -2884, -2884, -2884, + -2884, -2884, -4271, -2884, -2884, -2884, -2884, -2884, + -2884, -2884, -2884, -2884, -2884, -2884, -2884, -2884, + -2884, -2884, -2884, -2884, -2884, -2884, -2884, -2884, + -2884, -2884, -2884, -2884, -2884, -2884, -2884, -2884, + -2884, -2884, -2884, 32767, -2885, 32767, -2886, -2886, + 32767, -2887, -2887, 32767, -2888, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 563, 564, + 565, 566, 567, 568, 569, 570, 571, 572, + 573, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 574, 575, 576, 577, 578, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, -294, -294, -294, -3047, 583, 584, 585, + -4462, -4418, -4418, -4418, -4418, -4418, -4462, -4462, + -4462, 595, 596, 597, 598, 599, 32767, 32767, + 32767, 32767, -4471, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 4716, 4717, 4718, 4719, + 4720, 4721, 4722, 4723, 4724, 4725, 4726, 4727, + 4728, 4729, 4730, 4731, 4732, 4733, 4734, 4735, + 3826, 4737, 4738, 4739, 4740, 4741, 4742, 3832, + 4744, 3833, 3120, 3121, 3835, 3835, 3124, 3836, + 3836, 4753, 4754, 4755, 4756, 4757, 4758, 4759, + 4760, 4761, 4762, 4763, 4764, 4765, 4766, 4767, + 4768, 4769, 4770, 4771, 4772, 4773, 4774, 4775, + 4776, 4777, 4778, 4779, 4780, 4781, 6619, 6620, + 6621, 11272, 6623, 6624, 4788, 4789, 4790, 3874, + 4761, 3874, 4794, 3874, 4796, 4797, 4798, 3874, + 4800, 32767, 0, 4802, 4803, 4804, 4805, 4806, + 4807, 4808, 4809, 4810, 4811, 4812, 4813, 4814, + 4815, 4816, 4817, 4818, 4819, 4820, 4821, 4822, + 4823, 4824, 4825, 4826, 4827, 4828, 11299, 4830, + 2536, 4832, 6663, 4834, 11310, 4836, 6669, 4838, + 4839, 11320, 4841, 4842, 4843, 4844, 4845, 4846, + 4847, 4848, 4849, 4850, 4851, 4852, 1188, 4854, + 4855, 4856, 4857, 2577, 4859, 4860, 12273, -907, + -907, -907, -907, -907, -907, 4868, 4869, 4870, + 4871, 32767, 4872, 4873, 32767, 32767, 4874, 32767, + 627, 4875, 4876, 32767, 32767, 4877, 4878, 4879, + 6722, 32767, 4881, 4882, 4883, 6730, 6731, 7446, + 6733, 4888, 7449, 7449, 4891, 4892, 32767, 4893, + 32767, 4894, 4895, 4896, 4897, 4898, 4899, 3512, + 3513, 3514, 3515, 3516, 4904, 3518, 3519, 3520, + 3521, 3522, 3523, 3524, 3525, 3526, 3527, 3528, + 3529, 3530, 3531, 3532, 3533, 3534, 3535, 3536, + 3537, 3538, 4926, 6797, 4928, 6800, 4930, 4931, + 4932, 4933, 4934, 4935, 6813, 4937, 4938, 6816, + 6817, 4941, 4942, 4943, 0, 4945, 6821, 0, + 0, 4949, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, -127, -127, -127, + 7285, -127, -127, 0, -128, -128, -128, -128, + 0, 32767, -130, 4971, -129, 5613, 5614, 5615, + 4976, 5618, 32767, 5619, 5620, 5621, 4981, 5624, + 4983, 4984, 32767, 5630, 5631, -1986, -1986, -126, + -126, 5078, 4992, 5037, 5038, 5039, 5040, 5041, + 5086, 5087, 5088, 5089, -2322, 5091, 5092, 5093, + 5094, 5095, 5096, 5097, 5098, 5099, 5100, 0, + 5101, -640, -640, -640, 0, -641, -641, -641, + -641, -641, 0, -642, 0, 0, 32767, -645, + -645, 6973, 6974, 5115, 5116, -87, 0, -44, + -44, -44, -44, -44, -88, -88, -88, -88, + 7324, -88, -88, -88, -88, -88, -88, -88, + -88, -88, -88, -88, -88, 5654, 5655, 5656, + 5657, 5658, 5659, 5660, 5661, 5662, 5663, 5664, + 5665, 5666, 5667, 5668, 5669, -1948, -1948, -88, + -88, 5116, 5117, 5074, 5075, 5076, 5077, 5078, + 5123, 5124, 5125, 5126, -2285, 5128, 5129, 5130, + 5131, 5132, 5133, 5134, 5135, 5136, 5137, 5138, + 5139, -602, -602, -602, -602, -602, -602, -602, + -602, -602, -602, -602, -602, -602, -602, -602, + -602, 7016, 7017, 5158, 5159, -44, -44, 0, + 0, 0, 0, 0, -44, -44, -44, -44, + 7368, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, 5698, 5699, 5700, + 5701, 5702, 5703, 5704, 5705, 5706, 5707, 5708, + 5709, 5710, 5711, 5712, 5713, -1904, -1904, -44, + -44, 5160, 5161, 5118, 5119, 5120, 5121, 5122, + 5167, 5168, 5169, 5170, -2241, 5172, 5173, 5174, + 5175, 5176, 5177, 5178, 5179, 5180, 5181, 5182, + 5183, -558, -558, -558, -558, -558, -558, -558, + -558, -558, -558, -558, -558, -558, -558, -558, + -558, 7060, 7061, 5202, 5203, 0, 0, 44, + 44, 44, 44, 44, 0, 0, 0, 0, + 7412, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 5742, 5743, 5744, + 5745, 5746, 5747, 5748, 5749, 5750, 5751, 5752, + 5753, 5754, 5755, 5756, 5757, -1860, -1860, 0, + 0, 0, 0, 0, 6264, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, -3402, + -3402, 5355, 5356, -3447, -3447, -3402, -3402, -3402, + -3402, 5363, 5364, -3447, -3447, -3402, -3402, -3402, + -3358, -3358, -3404, -3404, -3404, -3404, -3404, -3404, + -3404, 5331, 5332, 5333, 5334, 2903, 2859, 5337, + 5338, 5339, 5340, 18601, 15287, 15288, 11974, 11975, + 8661, 8662, 5348, 5349, 5350, 5351, 5352, -3404, + -3404, 5400, 5401, 5357, 5358, 5359, 5360, -3404, + -3404, 5408, 5409, 5365, 5366, 5367, 5324, 5325, + 5372, 5373, 5374, 5375, 5376, 5377, 5378, -3356, + -3356, -3356, -3356, -924, -879, -3356, -3356, -3356, + -3356, -16616,-13301,-13301,-9986, -9986, -6671, -6671, + -3356, -3356, -3356, -3356, -3356, 5401, 5402, -3401, + -3401, -3356, -3356, -3356, -3356, 5409, 5410, -3401, + -3401, -3356, -3356, -3356, -3312, -3312, -3358, -3358, + -3358, -3358, -3358, -3358, -3358, 5377, 5378, 5379, + 5380, 2949, 2905, 5383, 5384, 5385, 5386, 18647, + 15333, 15334, 12020, 12021, 8707, 8708, 5394, 5395, + 5396, 5397, 5398, -3358, -3358, 5446, 5447, 5403, + 5404, 5405, 5406, -3358, -3358, 5454, 5455, 5411, + 5412, 5413, 5414, 5415, 5416, 5417, 5418, 5419, + 5420, 5421, 5422, -3312, -3312, -3312, -3312, -880, + -835, -3312, -3312, -3312, -3312, -16572,-13257,-13257, + -9942, -9942, -6627, -6627, -3312, -3312, -3312, -3312, + -3312, 5445, 5446, -3357, -3357, -3312, -3312, -3312, + -3312, 5453, 5454, -3357, -3357, -3312, -3312, -3312, + -3312, -3312, -3312, -3312, -3312, -3312, -3312, -3312, + -3312, 5423, 5424, 5425, 5426, 2995, 2951, 5429, + 5430, 5431, 5432, 18693, 15379, 15380, 12066, 12067, + 8753, 8754, 5440, 5441, 5442, 5443, 5444, -3312, + -3312, 5492, 5493, 5449, 5450, 5451, 5452, -3312, + -3312, 5500, 5501, 5457, 2803, 2803, 2803, 2803, + 373, 2803, 2803, 5510, 2803, 5512, 11470, 5514, + 11472, 5516, 2205, 2206, 2207, -7533, -1530, 4427, + 2209, 5524, 2210, 630, 5527, 2211, 2212, 2213, + 2214, 2215, 2216, 2217, 2815, 2219, 2220, 5538, + 2221, 5540, 2222, 5542, 5543, 2223, -3312, -3312, + -3312, 5548, 5549, -3312, -3312, 2803, 2803, 2803, + 5555, 5556, 5557, 2803, 2803, 2803, 2803, 2803, + 2803, 2803, 2803, 2803, 2803, 2803, 2803, 2803, + 9050, 9051, 2803, 2803, 2803, 2803, 2803, 2803, + 2803, 2803, 2803, 2803, 2803, 2803, 4318, 7547, + 7548, 2803, 2803, 2803, 2803, 2803, 2803, 2803, + 2803, 6693, 6693, 2803, 2803, 6694, 6694, 757, + 5603, 6693, 6693, 6693, -1593, 7164, 7165, -1638, + -1638, -1593, 4576, 4576, 4576, 7175, 7176, -1635, + -1635, -1590, -1590, 5422, 2820, -1544, -1590, -1590, + -1590, -1590, -1590, 5422, 5422, 4363, 7149, 7150, + 7151, 4720, 4676, 4677, 4678, -5053, 7161, 20422, + 5425, 5425, 5425, 9160, 9161, 9162, 9163, 9164, + 9165, 9166, 5425, -1587, -1587, 7217, -2553, -2553, + 7177, 7178, -2553, 32767, 32767, -219, 5312, -2555, + -2555, -782, -782, -782, -782, 1806, 2801, 2801, + -6998, -6953, 5675, 5676, 5677, 5678, 5679, -7029, + -790, -6955, -6955, 57, -2545, -6909, -6955, 3143, + -2545, -6955, -2545, 58, 2352, 3149, 5695, 5696, + -52, 5698, 5699, 5700, 5701, 5702, 5703, 5704, + 5705, 5706, 5707, 5708, 5709, 5710, 5711, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, -1838, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 6927, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, -973, 32767, 32767, + 32767, 32767, 0, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 4567, 4568, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, -437, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, -448, 32767, 32767, -450, -450, + -450, 0, 32767, 32767, 32767, -2166, 32767, 32767, + 32767, 32767, 32767, 32767, 0, 0, 32767, -464, + -464, 32767, 0, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, -514, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 5757, 5758, 5759, 0, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, -4186, -4186, -12097,-4186, 32767, + -4187, -4187, -8787, 32767, 0, 0, 5952, 0, + 0, -4183, -4183, -4183, 0, -2386, -4182, 778, + -4183, -5935, 32767, 32767, -4690, -6249, -4184, -4184, + -4184, 32767, 32767, -4186, -4186, -77, 32767, -77, + 32767, -4188, 0, -4189, 32767, 0, 0, 0, + 0, 32767, 0, 0, 0, 32767, 0, 0, + 0, 0, 0, 0, 0, 32767, 0, 0, + 0, 0, 0, 0, 32767, 32767, 32767, 32767, + 0, 0, 0, 0, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -5937, -2358, 0, 0, 0, + -8286, 471, 472, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 1747, 32767, -2126, 32767, 32767, 1748, + 1749, 1750, 1751, 1752, 1753, 8224, 1755, -539, + 1757, 781, 32767, 32767, 32767, -1991, -2035, 32767, + 32767, 782, -3784, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 837, 32767, 32767, 32767, 32767, 32767, -4008, + -4008, -4008, 2949, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 0, -797, 1806, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 4605, 4606, + 32767, 32767, 0, 455, 32767, 0, 32767, 32767, + 32767, 0, 32767, 32767, 32767, 32767, 0, 0, + 0, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, -4244, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 784, 32767, 32767, 2950, 2951, 32767, 32767, 32767, + 32767, 32767, 32767, 786, 787, 32767, 1252, 1253, + 32767, 790, 32767, 0, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 0, 32767, 0, 32767, 32767, + 32767, 0, 32767, 32767, 32767, 32767, 0, 0, + 0, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 0, 0, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 0, 0, 0, + 0, 0, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, -200, -200, -200, + -200, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + -5932, -5932, 32767, 32767, 2952, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, -5387, + -5387, -5387, -5387, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 0, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 0, 0, 0, 32767, 32767, + 0, 0, 32767, 32767, 0, 0, 0, 0, + 0, 0, 32767, 32767, 0, 0, 0, 0, + 0, 0, 32767, 32767, 497, 498, 499, 500, + 501, 502, 503, 504, 505, 506, 507, 508, + 32767, 32767, -156, 765, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, -861, + 32767, 6106, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 2953, 2954, 32767, 797, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 2955, 32767, 32767, 32767, -8929, + 32767, -8885, -8885, -8885, 32767, 32767, 32767, 32767, + 32767, 32767, -749, 7119, 7120, 32767, 32767, 32767, + 32767, 2760, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 0, 0, 0, 32767, 32767, 32767, 32767, + 32767, -1181, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, -5587, 0, 7596, + 7597, 0, 0, 0, 0, 0, 0, 32767, + 32767, 32767, 32767, 32767, 32767, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, -714, 0, + 0, -713, -712, 0, -711, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1859, + 0, 3247, 32767, 32767, 0, 3247, 0, 3248, + 0, 3249, 0, 3250, 0, 3251, 0, 3252, + 808, 3252, 0, 3253, 0, 3254, 0, 0, + 3256, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, 0, 0, 0, + 0, 32767, 32767, 32767, 32767, 0, 0, 6824, + 32767, 0, 32767, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 4207, 4208, 0, 0, 0, 0, 0, 1896, + 0, 0, 1898, 1898, 1898, 1898, 0, 0, + 0, 1901, 1901, 0, 0, 0, 0, 0, + 0, -1319, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 7618, 7619, 7620, + 3, 3, 1863, 1863, 7067, 7068, 7025, 7026, + 7027, 7028, 7029, 7074, 7075, 7076, 7077, -334, + 7079, 7080, 7081, 7082, 7083, 7084, 7085, 7086, + 7087, 7088, 7089, 7090, 1349, 1349, 1349, 1349, + 1349, 1349, 1349, 1349, 1349, 1349, 1349, 1349, + 1349, 1349, 1349, 1349, 8967, 8968, 7109, 7110, + 1907, 1907, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 2976, 2977, 2978, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 0, 0, 0, 820, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 821, + 2381, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 2005, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 823, 32767, 824, 32767, + 825, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 826, 32767, 32767, 32767, 32767, 32767, + 32767, 4575, 4576, 4577, 4578, 4579, 4580, 4581, + 4582, 4583, 4584, 4585, 32767, 32767, 829, 32767, + 32767, 32767, 32767, 830, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 6253, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 6253, -3848, 834, 835, 836, -3845, -3845, -3845, + -3845, -3845, -3845, 843, 844, -4280, 32767, 845, + 846, 6531, 848, -3839, 32767, -3840, -3840, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 1946, 32767, + 32767, 32767, -3849, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 853, 32767, 32767, 32767, + 32767, 854, 32767, 32767, 32767, 32767, 855, 32767, + 32767, 32767, 32767, 856, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 857, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, -3799, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 8266, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 859, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 860, + 32767, 861, -5065, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 10746, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 4526, + 32767, 4573, 4574, 4575, 32767, 32767, -2436, -1376, + 32767, 32767, 32767, 32767, 32767, -1689, -1689, 4349, + -4171, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 4588, 32767, + 4589, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 4590, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 4591, 4592, 32767, + 32767, 32767, 32767, 32767, 32767, 2933, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 864, 32767, 32767, 32767, + 0, 32767, 0, 32767, 32767, -2977, 335, 335, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 2992, 2993, 2994, 2995, + 32767, 32767, 32767, 4596, 2550, 32767, 32767, 32767, + -1188, 4769, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 4600, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 0, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 2997, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 4601, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 2013, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, -11287,32767, 32767, 32767, 32767, + 32767, 32767, 32767, -4664, 32767, 32767, -4711, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, -4718, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 4049, + 32767, 32767, 32767, 4050, 4051, 4052, 17313, 32767, + 32767, 32767, 10684, 7370, 7371, 4057, 4058, 4059, + 4060, 4061, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 4603, 8793, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 1283, 4897, 4898, 4899, 12175, 4901, 4902, 32767, + 4903, 4904, 4905, 4906, 4907, 10276, -1469, 1282, + 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, + 1282, 32767, 32767, 4920, 4921, 4063, -2051, -2050, + 4925, 4926, 32767, 7332, 7333, 32767, 7334, 7335, + 7336, 7337, 5045, 32767, 32767, 32767, -2049, -2048, + 32767, -8294, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 0, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1132, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 20166, 16852, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 6908, 6909, 6910, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + -4510, -4510, -4510, -4510, -4510, -4510, -4510, 0, + 0, 0, 0, 0, 0, -1831, -1831, -1831, + -15091,-11776,-11776,-8461, 0, 0, 0, -1834, + -1834, -1834, -1834, -1834, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, 32767, 32767, 32767, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -1819, -3615, 1345, -3616, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 32767, 32767, 0, + 0, 0, 0, 0, 0, 0, 8770, 8771, + 8772, 8773, 8774, 8775, 8776, 8777, 8778, 8779, + 45, 45, 45, 45, 2477, 2522, 45, 45, + 45, 45, -13215,-9900, -9900, -6585, -6585, -3270, + -3270, 45, 45, 45, 45, 45, 8802, 8803, + 0, 0, 45, 45, 45, 45, 8810, 8811, + 0, 0, 45, 2700, 2701, 2702, 2703, 5134, + 2705, 2706, 0, 2708, 0, -5957, 0, -5957, + 0, 3312, 3312, 3312, 13053, 7051, 1095, 3314, + 0, 3315, 4896, 0, 3317, 3317, 3317, 3317, + 3317, 3317, 3317, 2720, 3317, 3317, 0, 3318, + 0, 3319, 0, 0, 3321, 8857, 8858, 8859, + 0, 0, 8862, 8863, 2749, 2750, 2751, 0, + 0, 0, 2755, 2756, 2757, 2758, 2759, 2760, + 2761, 2762, 2763, 2764, 2765, 2766, 2767, -3479, + -3479, 2770, 2771, 2772, 2773, 2774, 2775, 2776, + 2777, 2778, 2779, 2780, 2781, 1267, -1961, -1961, + 2785, 2786, 2787, 2788, 2789, 2790, 2791, 2792, + -1097, -1096, 2795, 2796, -1094, -1093, 4845, 0, + -1089, -1088, -1087, 7200, -1556, -1556, 7248, 7249, + 7205, 1037, 1038, 1039, -1559, -1559, 7253, 7254, + 7210, 7211, 200, 2803, 7168, 7215, 7216, 7217, + 7218, 7219, 208, 209, 1269, -1516, -1516, -1516, + 916, 961, 961, 961, 10693, -1520, -14780,218, + 219, 220, -3514, -3514, -3514, -3514, -3514, -3514, + -3514, 228, 7241, 7242, -1561, 8210, 8211, -1518, + -1518, 8214, 8215, 9195, 5881, 351, 8219, 8220, + 6448, 6449, 6450, 6451, 3864, 2870, 2871, 12671, + 12627, 0, 0, 0, 0, 0, 12709, 6471, + 12637, 12638, 5627, 8230, 12595, 12642, 2545, 8234, + 12645, 8236, 5634, 3341, 2545, 0, 0, 5749, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 0, 0, 0, 0, 11602, + 0, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 0, 0, 1466, + 0, 0, 32767, 32767, 32767, 32767, 32767, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5760, 0, 0, 0, 0, 0, 32767, + 0, 32767, 0, 0, 32767, 0, 0, 32767, + 0, 3507, 3508, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1644, 1645, 1646, 1647, -5764, 1649, 1650, 1651, + 1652, 1653, 1654, 1655, 1656, 1657, 1658, 1659, + 1660, -4081, -4081, -4081, -4081, -4081, -4081, -4081, + -4081, -4081, -4081, -4081, -4081, -4081, -4081, -4081, + -4081, 3537, 3538, 1679, 3582, 3583, 3584, -3482, + -3482, -3482, -3482, -3482, -3526, -3526, -3526, -3526, + 3886, -3526, -3526, -3526, -3526, 3599, 3600, 3601, + 3602, 3603, 3604, 3605, 3606, 3607, 3608, 3609, + 3610, 3611, 3612, 3613, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 0, 0, 0, + -7275, 0, 0, -7234, 0, 0, 0, 0, + 0, -5368, 6378, 3628, 3629, 3630, 3631, 3632, + 3633, 3634, 3635, 3636, 3637, 3638, 3639, 0, + 0, 859, 6974, 6974, 0, 0, 3647, -2405, + -2405, 3650, -2405, -2405, -2405, -2405, -112, -2405, + -3201, 3658, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 0, 32767, 32767, 32767, + 32767, 5280, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 4637, 4638, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 4014, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 802, 32767, 32767, + 32767, 32767, 803, -1055, 805, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 4639, 32767, + 32767, 32767, 806, -2445, 0, -2443, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 810, 32767, 32767, + 32767, 32767, 811, 812, 813, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, -6211, -6211, -6211, -6211, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, -6271, -6271, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 935, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, -10300,32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 0, 0, 32767, 32767, 4640, 4641, 32767, + 32767, 32767, 32767, 32767, 4624, 32767, 32767, 32767, + -4233, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 1859, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 872, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, -4568, -1253, 32767, + -3590, 32767, 32767, 32767, -1820, -1820, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 0, 0, 0, 0, 0, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 873, 874, 875, 3629, 0, 0, + 0, 5048, 5005, 5006, 5007, 5008, 5009, 5054, + 5055, 5056, 0, 0, 0, 0, 0, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, -4118, + 32767, 32767, 32767, 32767, -4122, -4122, -4122, -4122, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, -4193, + 32767, -4194, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, -4209, 32767, 32767, -4211, -4211, -4211, + -4211, -4211, -4211, -4211, 32767, 32767, -4213, -10683, + -4213, -1918, -4213, -6043, 32767, 32767, -4215, -6047, + 32767, -4216, -10696,-4216, -4216, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 4646, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 876, 877, 0, 32767, 0, 32767, 0, + 32767, 0, 32767, 0, 32767, 32767, 32767, 0, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 1844, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -2899, 0, 32767, + 0, 32767, 0, 32767, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 836, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 32767, 0, 0, 0, 879, + 880, 881, 882, 883, 884, 885, 886, 0, + 0, 887, 0, 920, 0, 922, 923, 924, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 5431, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 0, 0, + 0, 32767, 3639, 889, 890, 891, 892, 893, + 894, 895, 896, 897, 898, 899, 900, -2739, + 927, -1881, 4234, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, -459, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, -458, + -457, 904, 32767, 905, 32767, 906, 32767, 907, + 32767, 908, 32767, 32767, 32767, 909, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 910, + 0, 0, 0, 0, 0, 0, 911, 0, + 912, 1626, 1626, 913, 914, 1626, 915, 916, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -1837, -1837, -1837, + -6487, -1837, -1837, 0, 0, 0, 917, 31, + 919, 0, 921, 0, 0, 0, 925, 0, + 32767, 4801, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -6470, 0, 2295, + 0, -1830, 0, -6475, 0, -1832, 0, 0, + -6480, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3665, 0, 0, + 0, 0, 2281, 0, 0, -7412, 5769, 5770, + 5771, 5772, 5773, 5774, 0, 0, 0, 0, + 32767, 0, 0, 32767, 32767, 0, 32767, 32767, + 0, 0, 32767, 32767, 0, 0, 0, -1842, + 32767, 0, 0, 0, -1846, -1846, -2560, -1846, + 0, -2560, -2559, 0, 0, 32767, 0, 32767, + 0, 0, 0, 0, 0, 0, 1388, 0, + 1387, 1387, 1387, 0, 1387, 1387, 1387, 1387, + 1387, 1387, 1387, 1387, 1387, 1387, 1387, 1387, + 1387, 1387, 1387, 1387, 1387, 1387, 1387, 1387, + 1387, 0, -1870, 0, -1871, 0, 0, 0, + 0, 0, 0, -1877, 0, 0, -1877, -1877, + 0, 0, 0, 4944, 0, -1875, 4947, 4948, + 0, 4950, 4951, 4952, 4953, 4954, 4955, 4956, + 4957, 4958, 4959, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, + 32767, 32767, 0, 0, 0, 0, 32767, 32767, + 32767, 0, 0, 931, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 4650, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 5375, + 5376, 5377, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 13180, 0, 0, + 0, 0, 0, 0, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, -4011, 933, -4011, 32767, + 935, 936, -4012, 938, 939, 940, 941, 942, + 943, 944, 945, 946, 947, 32767, 1075, 1076, + 1077, -6334, 1079, 1080, 954, 32767, 32767, 32767, + 32767, 955, 32767, 32767, 32767, 32767, 32767, 32767, + -4659, 32767, 32767, 32767, -4662, -4662, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 0, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 959, 960, 961, 32767, 962, 963, 964, + 965, 966, 967, 968, 969, 970, 971, 972, + 32767, 973, 974, 975, 976, 977, 978, 979, + 980, 981, 982, 983, 984, 985, 986, 987, + 988, 989, 990, 32767, 991, 992, 993, 994, + 995, 996, 997, 998, 999, 1000, 1001, 1002, + 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, + 1011, 1012, 1013, 1014, 1015, 1016, 1017, -362, + -362, 32767, 32767, 32767, 32767, -410, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 1019, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 164, 1021, -3551, -3551, 1024, 1025, 1026, 1027, + 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, + 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, + 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, + 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, + 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, + 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, + 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, + 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, + 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, + 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, + 1108, 1109, 1110, 1111, 1112, 1113, 1114, 32767, + 1115, 1116, 1117, 1118, 1119, 32767, 1120, 1121, + 1122, 1123, 1124, 1125, 1126, 1127, 1128, 1129, + 1130, 1131, 0, 1133, 1134, 1135, 1136, 1137, + 1138, 1139, 1140, 1141, 1142, 1143, 1144, 1145, + 1146, 1147, 1148, 1149, 1150, 1151, 1152, 1153, + 1154, 1155, 1156, 1157, 1158, 1159, 1160, 1161, + 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, + 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1177, + 1178, 1179, 1180, 1181, 1182, 1183, 1184, 1185, + 1186, 1187, 1188, 1189, 1190, 1191, 1192, 1193, + 1194, 1195, 1196, 1197, 1198, 1199, 1200, 1201, + 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, + -18956,-15641,1212, 1213, 1214, 1215, 1216, 1217, + 1218, 1219, 1220, 1221, 1222, 1223, 1224, 1225, + -5682, -5682, -5682, 1229, 1230, 1231, 1232, 1233, + 1234, 1235, 1236, 1237, 1238, 1239, 5750, 5751, + 5752, 5753, 5754, 5755, 5756, 1247, 1248, 1249, + 1250, 1251, 1252, 3084, 3085, 3086, 16347, 13033, + 13034, 9720, 1260, 1261, 1262, 3097, 3098, 3099, + 3100, 3101, 1268, 1269, 1270, 1271, 1272, 1273, + 1274, 1275, 32767, 32767, 32767, 32767, 1276, 1277, + 1278, 1279, 1280, 1281, 1282, 1283, 1284, 1285, + 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, + 1294, 1295, 1296, 1297, 1298, 1299, 1300, 1301, + 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, + 1310, 1311, 1312, 1313, 1314, 1315, 1316, 1317, + 1318, 1319, 1320, 1321, 1322, 1323, 1324, 1325, + 1326, 1327, 1328, 1329, 1330, 1331, 1332, 1333, + 1334, 1335, 1336, 1337, 1338, 1339, 1340, 1341, + 1342, 3162, 4959, 0, 4962, 1347, 1348, 1349, + 1350, 1351, 1352, 1353, 1354, 1355, 1356, 1357, + 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1365, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 7481, + 7482, 7483, 7484, 5053, 5009, 7487, 7488, 7489, + 7490, 20751, 17437, 17438, 14124, 14125, 10811, 10812, + 7498, 7499, 7500, 7501, 7502, 32767, 32767, 7548, + 7549, 7505, 7506, 7507, 7508, 32767, 32767, 7554, + 7555, 7511, 4857, 4857, 4857, 4857, 2427, 4857, + 4857, 7564, 4857, 7566, 13524, 7568, 13526, 7570, + 4259, 4260, 4261, -5479, 524, 6481, 4263, 7578, + 4264, 2684, 1421, -7842, -4527, -4527, -1212, -1212, + -1212, -1212, -1212, 7545, 7546, 0, 0, -1214, + -1214, -1214, -1214, 7551, 7552, 32767, 1610, -1216, + 1439, 1440, 1441, 1442, 3873, 1444, 1445, 32767, + 1446, 32767, -7220, 32767, -7221, 0, 2047, 2047, + 2047, 11788, 5786, -170, 2049, -1265, 2050, 3631, + -1265, 2052, 2052, 2052, 2052, 2052, 2052, 2052, + 1455, 2052, 2052, -1265, 2053, -1265, 2054, -1265, + -1265, 2056, 7592, 7593, 7594, 32767, 32767, 7595, + 7596, 1482, 1483, 1484, -1267, -1267, -1267, 1488, + 1489, 1490, 1491, 1492, 1493, 1494, 1495, 1496, + 1497, 1498, 1499, 1500, -4746, -4746, 1503, 1504, + 1505, 1506, 1507, 1508, 1509, 1510, 1511, 1512, + 1513, 1514, 0, -3228, -3228, 1518, 1519, 1520, + 1521, 1522, 1523, 1524, 1525, -2364, -2363, 1528, + 1529, -2361, -2360, 3578, 0, -2357, -2356, -2355, + 5932, -2824, -2824, 5980, 5981, 5937, -231, -230, + -229, -2827, -2827, 5985, -225, 5941, 5942, -1069, + 1534, 5899, 5946, 5947, 5948, 5949, 5950, -1061, + -1060, 0, -2785, 0, -355, -355, -310, -310, + -310, 9422, -2791, 32767, -1054, -1053, -1052, -4786, + -4786, -4786, -4786, -4786, -4786, -4786, -1044, 5969, + 5970, -2833, 6938, 6939, -2790, -2790, 6942, 0, + 32767, 4607, -923, 6945, 32767, 5173, 5174, 5175, + 5176, 2589, 1595, 1596, 11396, 11352, 32767, 32767, + 6126, 2812, 2813, 2814, 2815, 2816, -5940, -5940, + 1607, 1608, 2823, 32767, 32767, 1516, 0, -8581, + 0, 0, 728, 1525, 163, -11068,0, -2262, + -2306, -2305, 32767, 32767, 0, 0, 1580, 0, + 0, 0, -6443, 1685, -10176,-4173, 1784, -4173, + 0, -4172, 5925, -4171, -4171, -4171, 0, -437, + 0, 0, 0, 161, -435, 0, 2883, -434, + 0, 0, 0, 0, -436, 0, -5972, 0, + 0, 0, 0, 0, 0, 0, 0, 2889, + 2890, 2891, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 6371, + 0, 0, 0, 0, 0, 0, 0, 117, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 32767, 0, 0, 3991, 3991, + 3991, 3991, 0, 3990, 3990, 3990, -1947, 1632, + 3990, 3990, 3990, -4296, 4461, 4462, -4341, -4341, + -4296, 1873, 1873, 1873, 4472, 4473, -4338, 1873, + -4292, -4292, 2720, 118, -4246, -4292, -4292, 117, + -4293, -4293, 2719, 2719, 1660, 4446, 1662, 2018, + 2019, 1975, 1976, 1977, -7754, -7754, -8733, -5418, + 113, 0, 112, -2157, -5891, -5891, 0, -5892, + 6455, -5893, 0, 0, 0, 32767, 32767, 32767, + 5826, 32767, 32767, 32767, 32767, 6806, 32767, -2039, + 32767, 5829, 32767, 5830, 5831, 5832, 32767, 5833, + 5834, 32767, 5835, 32767, 32767, -3520, 0, 5837, + 0, 5838, 0, 4035, 0, 5840, 32767, 10251, + 154, 1671, 10253, 1673, 1674, 947, 151, 1514, + 12746, 1679, 3942, 3987, 3987, 3987, 13719, 13720, + 14700, 103, 5855, 13723, 5857, 8127, 0, 11862, + 5860, -96, 5862, 1690, 5863, -4233, 5864, 5865, + 5866, 5867, 5868, 5869, 5870, 5871, 5872, 5873, + 32767, 5874, 5875, 5876, 5877, 5878, 5879, 5880, + 5881, 5882, 5883, 13795, 5885, 5886, 5887, 5888, + 10489, 5890, 1703, 1704, -4247, 1706, 1707, 5891, + 5892, 5893, 1711, 4098, 5895, 5896, 5897, 7650, + 32767, 5899, 6406, 7966, 5902, 5903, 5904, 5905, + 5906, 5907, 5908, 1800, 5910, 1801, 5912, 5913, + 5914, 5915, 32767, 1727, 1728, 1729, 1730, 32767, + 1731, 1732, 1733, 32767, 1734, 1735, 1736, 1737, + 1738, 1739, 1740, 32767, 1741, 1742, 1743, 1744, + 1745, 1746, 32767, 32767, 32767, 32767, 1747, 1748, + 1749, 1750, 1751, 32767, 32767, 32767, 32767, 32767, + 32767, 1752, 1753, 1754, 1755, 1756, 1757, 1758, + 1759, 1760, 1761, 1762, 1763, 1764, 1765, 1766, + 1767, 1768, 1769, 1770, 1771, 1772, 1773, 1774, + 1775, 1776, 1777, 1778, 1779, 1780, 1781, 1782, + 1783, 1784, 1785, 1786, 1787, 1788, 1789, 1790, + 1791, 7729, 4151, 1794, 1795, 1796, 10083, 1327, + 1327, 10131, 10132, 10088, 3920, 3921, 3922, 1324, + 1324, 10136, 3926, 10092, 10093, 3082, 5685, 10050, + 10097, 0, 5689, 10100, 5691, 3089, 796, 0, + 1363, 12595, 3792, 3792, 3837, 3837, 3837, 13569, + 13570, 14550, 11236, 5706, 13574, 5708, 7978, 11713, + 11714, 11715, 11716, 11717, 11718, 11719, 7978, 966, + 966, 9770, 0, 0, 9730, 9731, 0, 0, + -979, 2336, 7867, 0, 0, 32767, 0, 0, + 0, 32767, 0, 0, 32767, 0, 32767, 32767, + 9356, 32767, 0, 32767, 0, 32767, 1804, 2602, + 0, -4364, -4410, 5688, 0, -4410, 0, 2603, + 4897, 5694, 4332, -6899, 1905, 1906, 1862, 1863, + 1864, -7867, -7867, -8846, -5531, 0, -7867, 0, + -2269, -6003, -6003, 0, 5957, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, -7911, 0, + 0, 0, 0, -4600, 0, 0, 4156, 32767, + 32767, 0, 0, 0, 0, 0, 1796, 0, + 0, 0, -1752, 0, 0, -506, -2065, 0, + 0, 0, 0, 0, 0, 0, 4109, 0, + 4110, 0, 0, 0, 0, 0, 4111, 17372, + 0, 14058, 10744, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -4650, 0, 0, 4161, 32767, + 32767, 4117, 32767, 4118, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, -7946, 32767, -4632, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, -4642, + -4642, 4123, 4124, -4687, 0, 0, -4644, -4644, + 0, 0, -4646, -4646, 32767, 32767, 32767, 32767, + 32767, 32767, 4084, 4085, 32767, 32767, 1609, 4087, + 32767, 32767, 4088, 17349, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 10092, 4136, + 10094, 4138, 10096, 0, 10097, 10098, 10099, 10100, + 10101, 0, 32767, 32767, 32767, 0, 0, 0, + 0, 0, 0, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 0, 0, 0, 0, 0, + 0, 0, 0, 32767, 32767, 0, 10138, 10139, + 0, 0, 0, 10145, 32767, 32767, 32767, 32767, + 32767, 32767, -1425, 8316, 2314, -3642, 32767, 0, + 32767, 32767, 32767, 32767, -1426, -1426, -1426, -1426, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 0, 0, 0, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 52, 52, 52, 52, 52, + 0, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 1849, 1850, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 100, 101, 102, 103, 104, 105, 106, 107, + 108, -5633, -5633, -5633, -5633, -5633, -5633, -5633, + -5633, -5633, -5633, -5633, -5633, -5633, -5633, -5633, + -5633, 1985, 1986, 127, 2030, 2031, 2032, -5034, + 32767, 32767, 32767, 32767, 32767, 0, 32767, 32767, + 32767, 5916, 5917, 5918, 5919, 5920, 5921, 5922, + 5923, 5924, 8824, 5926, 32767, 32767, 0, 32767, + 0, 5927, 5928, 5929, 5930, 5931, 5932, 5933, + 5934, 5935, 5936, 5937, 5938, 5939, 5940, 5105, + 5942, 5943, 5944, 5945, 5946, 5947, 5948, 5949, + 5950, 5951, 5952, 5953, 5954, 5955, 5956, 5957, + 32767, 5958, 5959, 5960, 5082, 5082, 5082, 5082, + 5082, 5082, 5082, 5082, 5969, 5970, 5084, 5972, + 5053, 5974, 5053, 5053, 5053, 5978, 5979, 5980, + 5981, 5982, 5983, 5984, 5985, 5986, 5987, 5988, + 5989, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 2552, 32767, 32767, 32767, + 32767, 32767, 32767, 5990, 5991, 5992, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 5993, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 6936, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 0, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 1851, 1852, 1853, 1854, + 1855, 1856, 1857, 1858, 1859, 1860, 1861, 1862, + 1863, 1864, 1200, 2121, 1200, 1868, 1869, 1870, + 1871, 1872, 1873, 1874, 1875, 1876, 1877, 1878, + 1879, 1880, 1188, 1188, 1188, 1188, 1188, 1188, + 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1188, + 1188, 1188, 1188, 1188, 1188, 1188, -5282, 1188, + 3483, 1188, -642, 1188, -5287, 1188, -644, 1188, + 1188, -5292, 1188, 1188, 1188, 1188, 1188, 1188, + 1188, 1188, 1188, 1188, 1188, 1188, 1925, 1926, + -6187, -6231, 1184, 3465, 1184, 1184, -6228, 6953, + 6954, 6955, 6956, 0, 1939, 1940, 1941, 1942, + 1943, 1944, 1178, 1178, 1947, 1948, 1949, 1950, + 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, + 1959, 1960, 1961, 1962, 1963, 1964, 1965, 1966, + 1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, + 1975, 1976, 1977, 1978, 1979, 1980, 1981, 1982, + 1983, 1984, 1985, 1986, 1987, 1988, 1989, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 0, 0, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 0, 0, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 709, 666, 667, 668, 32767, 669, + 714, 715, 716, 717, -6694, 719, 720, 721, + 32767, 722, 723, 724, 32767, 725, 726, 727, + 728, -5013, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 6052, 0, 0, 6055, + 0, 0, 0, 0, 2293, 0, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 1244, 1245, 1246, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, -4660, + -4660, -4660, -4660, 4097, 4098, -4705, -4705, -4660, + -4660, -4660, -4660, 4105, 4106, -4705, 32767, -4661, + -4661, -4661, -4617, -4617, -4663, -4663, -4663, -4663, + -4663, -4663, -4663, 4072, 4073, 4074, 4075, 1644, + 1600, 4078, 4079, 4080, 4081, 17342, 14028, 14029, + 10715, 10716, 7402, 7403, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 0, 0, + 0, 32767, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 32767, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 32767, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1380, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 856, 0, 4573, + 4574, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, 0, 0, 0, + 0, 0, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 5204, 5161, 5162, 5163, 5164, 5165, 5210, 5211, + 5212, 5213, -2198, 5215, 5216, 5217, 5218, 5219, + 5220, 5221, 5222, 5223, 5224, 5225, 5226, -515, + -515, -515, -515, -515, -515, -515, -515, -515, + -515, -515, -515, -515, -515, -515, -515, 7103, + 7104, 5245, 5246, 5247, 5248, 5249, -1014, 5251, + 5252, 5253, 5254, 5255, 5256, 5257, 5258, 5259, + 5260, 8663, 8664, -92, -92, 8712, 8713, 8669, + 8670, 8671, 8672, -92, -92, 8720, 8721, 8677, + 8678, 8679, 8636, 8637, 8684, 8685, 8686, 8687, + 8688, 8689, 8690, -44, -44, -44, -44, 2388, + 2433, -44, -44, -44, -44, -13304,-9989, -9989, + -6674, -6674, -3359, -3359, -44, -44, -44, -44, + -44, 8713, 8714, -89, -89, -44, -44, -44, + -44, 8721, 8722, -89, -89, -44, -44, -44, + 0, 0, -46, -46, -46, -46, -46, -46, + -46, 8689, 8690, 8691, 8692, 6261, 6217, 8695, + 8696, 8697, 8698, 21959, 18645, 18646, 15332, 15333, + 12019, 12020, 8706, 8707, 8708, 8709, 8710, -46, + -46, 8758, 8759, 8715, 8716, 8717, 8718, -46, + -46, 8766, 8767, 8723, 8724, 8725, 8726, 8727, + 8728, 8729, 8730, 8731, 8732, 8733, 8734, 0, + 0, 0, 0, 2432, 2477, 0, 0, 0, + 0, -13260,-9945, -9945, -6630, -6630, -3315, -3315, + 0, 0, 0, 0, 0, 8757, 8758, -45, + -45, 0, 0, 0, 0, 8765, 8766, -45, + -45, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 8735, 8736, 8737, + 8738, 6307, 6263, 8741, 8742, 8743, 8744, 22005, + 18691, 18692, 15378, 15379, 12065, 12066, 8752, 8753, + 8754, 8755, 8756, 0, 0, 8804, 8805, 8761, + 8762, 8763, 8764, 0, 0, 8812, 8813, 8769, + 6115, 6115, 6115, 6115, 3685, 6115, 6115, 8822, + 6115, 8824, 14782, 8826, 14784, 8828, 5517, 5518, + 5519, -4221, 1782, 7739, 5521, 8836, 5522, 3942, + 8839, 5523, 5524, 5525, 5526, 5527, 5528, 5529, + 6127, 5531, 5532, 8850, 5533, 8852, 5534, 8854, + 8855, 5535, 0, 0, 0, 8860, 8861, 0, + 0, 0, 13252, 9939, 9939, 6626, 6626, 3313, + 3313, 0, 0, 0, -9269, -3312, 0, 0, + 0, 9741, 32767, 32767, 0, 32767, 0, 32767, + 32767, 0, 0, 0, 0, 0, 0, 0, + -597, 0, 0, 32767, 0, 32767, 0, 32767, + 32767, 0, 0, 32767, 32767, 32767, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 32767, 32767, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, -1387, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 0, 0, 0, 0, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, -1773, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, + 0, 0, 0, 0, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, -4161, 1581, 1582, 32767, 32767, 1990, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 0, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 1539, 32767, 32767, 6150, 6151, 6152, 411, + 411, 411, 411, 411, 411, 411, 411, 411, + 411, 411, 411, 411, 411, 411, 411, 8029, + 8030, 6171, 6172, 969, 969, 1013, 1013, 1013, + 1013, 1013, 969, 969, 969, 969, 8381, 969, + 969, 969, 969, 969, 969, 969, 969, 969, + 969, 969, 969, 6711, 6712, 6713, 6714, 6715, + 6716, 6717, 6718, 6719, 6720, 6721, 6722, 6723, + 6724, 6725, 6726, -891, -891, 969, 969, 6173, + 6174, 6131, 6132, 6133, 6134, 6135, 6180, 6181, + 6182, 6183, -1228, 6185, 6186, 6187, 6188, 6189, + 6190, 6191, 6192, 6193, 6194, 6195, 6196, 455, + 455, 455, 455, 455, 455, 455, 455, 455, + 455, 455, 455, 455, 455, 455, 455, 8073, + 8074, 6215, 6216, 1013, 1013, 1057, 1057, 1057, + 1057, 1057, 1013, 1013, 1013, 1013, 8425, 1013, + 1013, 1013, 1013, 1013, 1013, 1013, 1013, 1013, + 1013, 1013, 1013, 6755, 6756, 6757, 6758, 6759, + 6760, 6761, 6762, 6763, 6764, 6765, 6766, 6767, + 6768, 6769, 6770, -847, -847, 1013, 1013, 6217, + 6218, 6175, 6176, 6177, 6178, 6179, 6224, 6225, + 6226, 6227, -1184, 6229, 6230, 6231, 6232, 6233, + 6234, 6235, 6236, 6237, 6238, 6239, 6240, 499, + 499, 499, 499, 499, 499, 499, 499, 499, + 499, 499, 499, 499, 499, 499, 499, 8117, + 8118, 6259, 6260, 6261, 6262, 6263, 0, 6265, + 6266, 6267, 6268, 6269, 6270, 6271, 6272, 6273, + 6274, 9677, 9678, 922, 922, 9726, 9727, 9683, + 9684, 9685, 9686, 922, 922, 9734, 9735, 9691, + 9692, 9693, 9650, 9651, 9698, 9699, 9700, 9701, + 9702, 9703, 9704, 970, 970, 970, 970, 3402, + 3447, 970, 970, 970, 970, -12290,-8975, -8975, + -5660, -5660, -2345, -2345, -2345, -2345, -2345, 6412, + 6413, -2390, -2390, -2345, -2345, -2345, -2345, 6420, + 6421, -2390, -2390, -2345, -2345, -2345, -2301, -2301, + -2347, -2347, -2347, -2347, -2347, -2347, -2347, 6388, + 6389, 6390, 6391, 3960, 3916, 6394, 6395, 6396, + 6397, 19658, 16344, 16345, 13031, 13032, 9718, 9719, + 6405, 6406, 6407, 6408, 6409, -2347, -2347, 6457, + 6458, 6414, 6415, 6416, 6417, -2347, -2347, 6465, + 6466, 6422, 6423, 6424, 6381, 6382, 6429, 6430, + 6431, 6432, 6433, 6434, 6435, -2299, -2299, -2299, + -2299, 133, 178, -2299, -2299, -2299, -2299, -15559, + -12244,-12244,-8929, -8929, -5614, -5614, -2299, -2299, + -2299, -2299, -2299, 6458, 6459, -2344, -2344, -2299, + -2299, -2299, -2299, 6466, 6467, -2344, -2344, -2299, + -2299, -2299, -2299, -2299, -2299, -2299, -2299, -2299, + -2299, -2299, -2299, 6436, 6437, 6438, 6439, 4008, + 3964, 6442, 6443, 6444, 6445, 19706, 16392, 16393, + 13079, 13080, 9766, 9767, 6453, 6454, 6455, 6456, + 6457, -2299, -2299, 6505, 6506, 6462, 6463, 6464, + 6465, -2299, -2299, 6513, 6514, 6470, 6471, 6472, + 6473, 6474, 6475, 6476, 6477, 6478, 6479, 6480, + 6481, -2253, -2253, -2253, -2253, 179, 224, -2253, + -2253, -2253, -2253, -15513,-12198,-12198,-8883, -8883, + -5568, -5568, -2253, -2253, -2253, -2253, -2253, 6504, + 6505, -2298, -2298, -2253, -2253, -2253, -2253, 6512, + 6513, -2298, -2298, -2253, 402, 403, 404, 405, + 2836, 407, 408, -2298, 410, -2298, -8255, -2298, + -8255, -2298, 1014, 1014, 1014, 10755, 4753, -1203, + 1016, -2298, 1017, 2598, -2298, 1019, 1019, 1019, + 1019, 1019, 1019, 1019, 422, 1019, 1019, -2298, + 1020, -2298, 1021, -2298, -2298, 1023, 6559, 6560, + 6561, -2298, -2298, 6564, 6565, 6566, -6685, -3371, + -3370, -56, -55, 3259, 3260, 3261, 12531, 6575, + 3264, 3265, 3266, -6474, -471, 5486, 3268, 6583, + 3269, 1689, 6586, 3270, 3271, 3272, 3273, 3274, + 3275, 3276, 3874, 3278, 3279, 6597, 3280, 6599, + 3281, 6601, 6602, 3282, 3283, 32767, 32767, 32767, + 3284, 3285, 3286, 3287, 3288, 3289, 3290, 3291, + 3292, 3293, 3294, 3295, 3296, 3297, 3298, 3299, + 3300, 3301, 3302, 3303, 3304, 3305, 3306, 3307, + 3308, 3309, 3310, 3311, 3312, 3313, 3314, 3315, + 3316, 3317, 3318, 3319, 3320, 3321, 3322, 3323, + 3324, 3325, 3326, 3327, 3328, 3329, 3330, 3331, + 3332, 3333, 3334, 3335, 3336, 3337, 3338, 3339, + 3340, 3341, 3342, 3343, 3344, 3345, 3346, 3347, + 3348, 3349, 3350, 3351, 32767, 32767, 3352, 3353, + 3354, 3355, 3356, 3357, 3358, 3359, 3360, 3361, + 3362, 3363, 3364, 3365, 3366, 3367, 3368, 3369, + 3370, 3371, 3372, 3373, 3374, 3375, 3376, 3377, + 3378, 3379, 3380, 3381, 3382, 3383, 3384, 3385, + 3386, 3387, 3388, 3389, 3390, 3391, 3392, 3393, + 3394, 3395, 3396, 3397, 3398, 3399, 3400, 3401, + 3402, 3403, 3404, 3405, 3406, 3407, 4795, 3409, + 3410, 3411, 3412, 3413, 3414, 3415, 3416, 3417, + 3418, 3419, 3420, 3421, 3422, 3423, 3424, 3425, + 3426, 3427, 3428, 3429, 3430, 3431, 3432, 3433, + 3434, 3435, 3436, 3437, 3438, 3439, 3440, 3441, + 3442, 3443, 3444, 3445, 3446, 3447, 3448, 3449, + 3450, 3451, 3452, 3453, 3454, 3455, 3456, 3457, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 3458, + 3459, 3460, 3461, 3462, -8139, 3464, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 3465, 3466, 2001, 3468, 3469, 32767, + 32767, 32767, 32767, 32767, 3470, 3471, 3472, 3473, + 3474, 3475, 3476, 3477, 3478, 3479, 3480, 3481, + 3482, 3483, 3484, 3485, 3486, 3487, 3488, 3489, + 3490, 3491, 3492, 3493, 3494, 3495, 32767, 3496, + 3497, 3498, 3499, 3500, 32767, 3501, 32767, 3502, + 3503, 32767, 3504, 3505, 32767, 3506, 0, 0, + 3509, 3510, 3511, 3512, 3513, 3514, 3515, 3516, + 3517, 3518, 3519, 3520, 3521, 3522, 3523, 3524, + 3525, 3526, 3527, 3528, 3529, 3530, 3531, 3532, + 3533, 3534, 3535, 3536, 3537, 3538, 3539, 3540, + 3541, 3542, 3543, 3544, 3545, 1902, 1902, 1902, + 1902, 9314, 1902, 1902, 1902, 1902, 1902, 1902, + 1902, 1902, 1902, 1902, 1902, 1902, 7644, 7645, + 7646, 7647, 7648, 7649, 7650, 7651, 7652, 7653, + 7654, 7655, 7656, 7657, 7658, 7659, 42, 42, + 1902, 0, 0, 0, 7067, 7068, 7069, 7070, + 7071, 7116, 7117, 7118, 7119, -292, 7121, 7122, + 7123, 7124, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3614, 3615, 3616, 10892, 3618, 3619, + 10854, 3621, 3622, 3623, 3624, 3625, 8994, -2751, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3640, 3641, 2783, -3331, + -3330, 3645, 3646, 0, 6053, 6054, 0, 6056, + 6057, 6058, 6059, 3767, 6061, 6858, 0, 0, + 3659, 0, 0, 1531, 1531, 1531, 1531, 1531, + 1531, 1531, 1531, 1531, 1531, 1531, 1531, 1531, + 1531, 1531, 9149, 9150, 7291, 7292, 7293, 7294, + 7295, 1032, 7297, 7298, 7299, 7300, 7301, 7302, + 7303, 7304, 0, 7307, 10710, 10711, 1955, 1955, + 10759, 10760, 10716, 4548, 4549, 4550, 1952, 1952, + 10764, 10765, 10721, 10722, 3711, 6314, 10679, 10726, + 10727, 10728, 10729, 10730, 3719, 3720, 1996, 1996, + 1996, 1996, 4428, 4473, 4473, 3728, 1994, 1994, + -11266,3732, 3733, 3734, 0, 0, 0, 0, + 0, 0, 0, 3742, 10755, 10756, 1953, 1953, + 1998, 1998, 1998, 11730, 11731, 12711, 9397, 3867, + 11735, 3869, 6139, 9874, 9875, 9876, 9877, 9878, + 9879, 9880, 6139, -873, -873, 7931, -1839, -1839, + 7891, 7892, -1839, -1839, -2818, 497, 6028, -1839, + -1839, -66, -66, -66, -66, 2522, 2523, -6280, + -6280, -6235, -66, -66, -66, 2533, 2534, -6277, + -66, -6231, -6231, 781, -1821, -6185, -6231, 3867, + -1821, -6231, -1821, 782, 3076, 3873, 2511, -8720, + 84, 85, 41, 42, 43, -9688, -9688, -10667, + -7352, -1821, -9688, -1821, -4090, -7824, -7824, -7824, + -7824, -7824, -7824, -7824, -4082, 2931, 2932, -5871, + 3900, 3901, -5828, -5828, 3904, 3905, 4885, 1571, + -3959, 3909, 3910, 2138, 2139, 2140, 2141, -446, + -446, 8358, 8359, 8315, 2147, 2148, 2149, -449, + -449, 8363, 2153, 8319, 8320, 1309, 3912, 8277, + 8324, -1773, 3916, 8327, 3918, 1316, -977, -1773, + -410, 10822, 2019, 2019, 2064, 2064, 2064, 11796, + 11797, 12777, 9463, 3933, 11801, 3935, 6205, 9940, + 9941, 9942, 9943, 9944, 9945, 9946, 6205, -807, + -807, 7997, -1773, -1773, 7957, 7958, -1773, -1773, + -2752, 563, 6094, -1773, -1773, 0, 0, 0, + 0, 2588, 2589, -6214, -6214, -6169, 0, 0, + 0, 2599, 2600, -6211, 0, -6165, -6165, 847, + -1755, -6119, -6165, 3933, -1755, -6165, -1755, 848, + 3142, 3939, 2577, -8654, 150, 151, 107, 108, + 109, -9622, -9622, -10601,-7286, -1755, -9622, -1755, + -4024, -7758, -7758, -7758, -7758, -7758, -7758, -7758, + -4016, 2997, 2998, -5805, 3966, 3967, -5762, -5762, + 3970, 3971, 4951, 1637, -3893, 3975, 3976, 2204, + 2205, 2206, 2207, -380, -380, 8424, 8425, 8381, + 2213, 2214, 2215, -383, -383, 8429, 2219, 8385, + 8386, 1375, 3978, 8343, 8390, -1707, 3982, 8393, + 3984, 1382, -911, -1707, -344, 10888, 2085, 2085, + 2130, 2130, 2130, 11862, 11863, 12843, 9529, 3999, + 11867, 4001, 6271, 10006, 10007, 4005, -1951, 4007, + 4008, 4009, 4010, 4011, 4012, 4013, 4014, 4015, + 4016, 4017, 4018, 4019, 4020, 4021, 4022, 4023, + 4024, 4025, 4026, 4027, 4028, 4029, 4030, 4031, + 11943, 4033, 4034, 4035, 4036, 8637, 4038, 4039, + -116, 32767, 32767, 4041, 4042, 4043, 4044, 4045, + 2250, 4047, 4048, 4049, 5802, 4051, 4052, 4559, + 6119, 4055, 4056, 4057, 4058, 4059, 4060, 4061, + -47, 4063, -46, 4065, 4066, 4067, 4068, 4069, + -41, -13301,4072, -9985, -6670, 4075, 4076, 4077, + 4078, 4079, 4080, 4081, 4082, 4083, 4084, 4085, + 4086, 4087, 4088, 4089, 4090, 8741, 4092, 4093, + -67, 32767, 32767, 32767, 32767, 32767, 2257, 32767, + 2258, 2259, 2260, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 2261, 32767, 2262, 32767, + 2263, 32767, 2264, 32767, 2265, 32767, 2266, 32767, + 2267, 8737, 8738, -26, -26, 8786, 4100, 4101, + 8746, 8747, 4104, 4105, 8752, 8753, 32767, 2274, + 32767, 2275, 32767, 32767, 32767, 32767, 32767, 32767, + 2276, 2277, 32767, 2278, 2279, 32767, 2280, 0, + 32767, 2282, 9695, 4109, -3486, -3486, 4112, 4113, + 4114, 4115, 4116, 4117, 32767, 32767, 32767, 32767, + 32767, 32767, 4118, 4119, 4120, 4121, 4122, 4123, + 4124, 4125, 4126, 4127, 4128, 4129, 4130, 4131, + 4132, 4133, 4134, 4849, 4136, 4137, 4851, 4851, + 4140, 4852, 4142, 4143, 4144, 4145, 4146, 4147, + 4148, 4149, 4150, 4151, 2293, 4153, 907, 32767, + 2295, 4155, 909, 4157, 910, 4159, 911, 4161, + 912, 4163, 913, 4165, 914, 32767, 915, 4168, + 916, 4170, 917, 4172, 4173, 918, 4175, 4176, + 4177, 4178, 4179, 4180, 4181, 4182, 4183, 4184, + 4185, 2309, 4186, 4187, 4188, 4189, 2312, 2313, + 32767, 2314, 4190, 4191, -2632, 2317, 4193, 32767, + 4194, 4195, 4196, 4197, 4198, 4199, 4200, 4201, + 4202, 4203, 4204, 4205, 4206, 0, 0, 4209, + 4210, 4211, 4212, 4213, 2318, 4215, 4216, 2319, + 2320, 2321, 2322, 4221, 4222, 4223, 2323, 2324, + 4226, 4227, 4228, 4229, 4230, 4231, 5551, 4233, + 4234, 4235, 4236, 4237, 4238, 4239, 4240, 4241, + 4242, 4243, 4244, 4245, 4246, 4247, 4248, 4249, + 4250, 4251, 4252, 4253, 4254, 4255, 4256, 4257, + 4258, 4259, 4260, 4261, 4262, 4263, 4264, 4265, + 4266, 4267, 4268, 4269, 4270, 4271, 4272, 4273, + 4274, 4275, -3342, -3342, -3342, 4276, 4277, 2418, + 2419, -2784, -2784, -2740, -2740, -2740, -2740, -2740, + -2784, -2784, -2784, -2784, 4628, -2784, -2784, -2784, + -2784, -2784, -2784, -2784, -2784, -2784, -2784, -2784, + -2784, 2958, 2959, 2960, 2961, 2962, 2963, 2964, + 2965, 2966, 2967, 2968, 2969, 2970, 2971, 2972, + 2973, -4644, -4644, -2784, -2784, 2420, 2421, 2378, + 2379, 2380, 2381, 2382, 2427, 2428, 2429, 2430, + -4981, 2432, 2433, 2434, 2435, 2436, 2437, 2438, + 2439, 2440, 2441, 2442, 2443, -3298, -3298, -3298, + -3298, -3298, -3298, -3298, -3298, -3298, -3298, -3298, + -3298, -3298, -3298, -3298, -3298, 4320, 4321, 2462, + 4365, 4366, 4367, -2699, -2699, -2699, -2699, -2699, + -2743, -2743, -2743, -2743, 4669, -2743, -2743, -2743, + -2743, 4382, 4383, 4384, 4385, 4386, 4387, 4388, + 4389, 4390, 4391, 4392, 4393, 4394, 4395, 4396, + 4397, 4398, 4399, 4400, 4401, 4402, 4403, 4404, + 4405, 4406, 4407, 4408, 4409, 4410, 4411, 4412, + 4413, 4414, 4415, 4416, 4417, 4418, 4419, 4420, + 4421, 4422, 4423, 4424, 4425, 4426, 4427, 4428, + 4429, 816, 816, 816, -6459, 816, 816, -6418, + 816, 816, 816, 816, 816, -4552, 7194, 4444, + 4445, 4446, 4447, 4448, 4449, 4450, 4451, 4452, + 4453, 4454, 4455, 816, 816, 1675, 7790, 7790, + 816, 816, 4463, -1589, -1589, 4466, -1589, -1589, + -1589, -1589, 704, -1589, -2385, 4474, 4475, 817, + 4477, 4478, 2948, 2949, 2950, 2951, 2952, 2953, + 2954, 2955, 2956, 2957, 2958, 2959, 2960, 2961, + 2962, -4655, -4655, -2795, -2795, -2795, -2795, -2795, + 3469, -2795, -2795, -2795, -2795, -2795, -2795, -2795, + -2795, 4510, -2796, -6198, -6198, 2559, 2560, -6243, + -6243, -6198, -6198, -6198, -6198, 2567, 2568, -6243, + -6243, -6198, -6198, -6198, -6154, -6154, -6200, -6200, + -6200, -6200, -6200, -6200, -6200, 2535, 2536, 2537, + 2538, 107, 63, 2541, 2542, 2543, 2544, 15805, + 12491, 12492, 32767, 4540, 4541, 4542, 4543, 4544, + 4545, 4546, 2548, -6208, -6208, 2596, 2597, 2553, + 2554, 2555, 2556, -6208, -6208, 2604, 2605, 2561, + 2562, 2563, 2520, 2521, 2568, 2569, 2570, 2571, + 2572, 2573, 2574, -6160, -6160, -6160, -6160, -3728, + -3683, -6160, -6160, -6160, -6160, -19420,-16105,-16105, + -12790,-12790,-9475, -9475, -6160, -6160, -6160, -6160, + -6160, 32767, 2597, -6206, -6206, -6161, -6161, -6161, + -6161, 2604, 2605, -6206, -6206, -6161, -6161, -6161, + -6161, -6161, -6161, -6161, -6161, -6161, -6161, -6161, + -6161, 2574, 2575, 2576, 2577, 146, 102, 2580, + 2581, 2582, 2583, 15844, 12530, 12531, 9217, 9218, + 5904, 5905, 2591, 2592, 2593, 2594, 2595, -6161, + -6161, 2643, 2644, 2600, 2601, 2602, 2603, -6161, + -6161, 2651, 2652, 2608, 2609, 2610, 2611, 2612, + 2613, 2614, 2615, 2616, 2617, 2618, 2619, -6115, + -6115, -6115, -6115, -3683, -3638, -6115, -6115, -6115, + -6115, -19375,-16060,-16060,-12745,-12745,-9430, -9430, + -6115, -6115, -6115, -6115, -6115, 2642, 2643, -6160, + -6160, -6115, -6115, -6115, -6115, 2650, 2651, -6160, + -6160, -6115, -3460, -3459, -3458, -3457, -1026, -3455, + -3454, -6160, -3452, -6160, -12117,-6160, -12117,-6160, + -2848, -2848, -2848, 6893, 891, -5065, -2846, -6160, + -2845, -1264, 0, 9264, 5950, 5951, 2637, 2638, + 2639, 2640, 2641, -6115, -6115, 2689, 2690, 2646, + 2647, 2648, 2649, -6115, -6115, 2697, 2698, 2654, + 0, 0, 0, 0, -2430, 0, 0, 2707, + 0, 2709, 8667, 2711, 8669, 2713, -598, -597, + -596, -10336,-4333, 1624, -594, 2721, -593, -2173, + 2724, -592, -591, -590, -589, -588, -587, -586, + 12, -584, -583, 2735, -582, 2737, -581, 2739, + 2740, -580, -6115, -6115, -6115, 2745, 2746, -6115, + -6115, 0, 0, 0, 2752, 2753, 2754, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 6247, 6248, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0 + }; + + const unsigned char *k = (const unsigned char *) key; + size_t keylen = 4; + uint32 a = 0; + uint32 b = 1; + + while (keylen--) + { + unsigned char c = *k++; + + a = a * 257 + c; + b = b * 8191 + c; + } + return h[a % 13209] + h[b % 13209]; +} + +/* Hash lookup information for decomposition */ +static const pg_unicode_decompinfo UnicodeDecompInfo = +{ + UnicodeDecompMain, + Decomp_hash_func, + 6604 +}; + +/* Inverse lookup array -- contains indexes into UnicodeDecompMain[] */ +static const uint16 RecompInverseLookup[941] = +{ + /* U+003C+0338 -> U+226E */ 1823, + /* U+003D+0338 -> U+2260 */ 1820, + /* U+003E+0338 -> U+226F */ 1824, + /* U+0041+0300 -> U+00C0 */ 14, + /* U+0041+0301 -> U+00C1 */ 15, + /* U+0041+0302 -> U+00C2 */ 16, + /* U+0041+0303 -> U+00C3 */ 17, + /* U+0041+0304 -> U+0100 */ 67, + /* U+0041+0306 -> U+0102 */ 69, + /* U+0041+0307 -> U+0226 */ 270, + /* U+0041+0308 -> U+00C4 */ 18, + /* U+0041+0309 -> U+1EA2 */ 1278, + /* U+0041+030A -> U+00C5 */ 19, + /* U+0041+030C -> U+01CD */ 194, + /* U+0041+030F -> U+0200 */ 240, + /* U+0041+0311 -> U+0202 */ 242, + /* U+0041+0323 -> U+1EA0 */ 1276, + /* U+0041+0325 -> U+1E00 */ 1120, + /* U+0041+0328 -> U+0104 */ 71, + /* U+0042+0307 -> U+1E02 */ 1122, + /* U+0042+0323 -> U+1E04 */ 1124, + /* U+0042+0331 -> U+1E06 */ 1126, + /* U+0043+0301 -> U+0106 */ 73, + /* U+0043+0302 -> U+0108 */ 75, + /* U+0043+0307 -> U+010A */ 77, + /* U+0043+030C -> U+010C */ 79, + /* U+0043+0327 -> U+00C7 */ 20, + /* U+0044+0307 -> U+1E0A */ 1130, + /* U+0044+030C -> U+010E */ 81, + /* U+0044+0323 -> U+1E0C */ 1132, + /* U+0044+0327 -> U+1E10 */ 1136, + /* U+0044+032D -> U+1E12 */ 1138, + /* U+0044+0331 -> U+1E0E */ 1134, + /* U+0045+0300 -> U+00C8 */ 21, + /* U+0045+0301 -> U+00C9 */ 22, + /* U+0045+0302 -> U+00CA */ 23, + /* U+0045+0303 -> U+1EBC */ 1304, + /* U+0045+0304 -> U+0112 */ 83, + /* U+0045+0306 -> U+0114 */ 85, + /* U+0045+0307 -> U+0116 */ 87, + /* U+0045+0308 -> U+00CB */ 24, + /* U+0045+0309 -> U+1EBA */ 1302, + /* U+0045+030C -> U+011A */ 91, + /* U+0045+030F -> U+0204 */ 244, + /* U+0045+0311 -> U+0206 */ 246, + /* U+0045+0323 -> U+1EB8 */ 1300, + /* U+0045+0327 -> U+0228 */ 272, + /* U+0045+0328 -> U+0118 */ 89, + /* U+0045+032D -> U+1E18 */ 1144, + /* U+0045+0330 -> U+1E1A */ 1146, + /* U+0046+0307 -> U+1E1E */ 1150, + /* U+0047+0301 -> U+01F4 */ 230, + /* U+0047+0302 -> U+011C */ 93, + /* U+0047+0304 -> U+1E20 */ 1152, + /* U+0047+0306 -> U+011E */ 95, + /* U+0047+0307 -> U+0120 */ 97, + /* U+0047+030C -> U+01E6 */ 216, + /* U+0047+0327 -> U+0122 */ 99, + /* U+0048+0302 -> U+0124 */ 101, + /* U+0048+0307 -> U+1E22 */ 1154, + /* U+0048+0308 -> U+1E26 */ 1158, + /* U+0048+030C -> U+021E */ 268, + /* U+0048+0323 -> U+1E24 */ 1156, + /* U+0048+0327 -> U+1E28 */ 1160, + /* U+0048+032E -> U+1E2A */ 1162, + /* U+0049+0300 -> U+00CC */ 25, + /* U+0049+0301 -> U+00CD */ 26, + /* U+0049+0302 -> U+00CE */ 27, + /* U+0049+0303 -> U+0128 */ 103, + /* U+0049+0304 -> U+012A */ 105, + /* U+0049+0306 -> U+012C */ 107, + /* U+0049+0307 -> U+0130 */ 111, + /* U+0049+0308 -> U+00CF */ 28, + /* U+0049+0309 -> U+1EC8 */ 1316, + /* U+0049+030C -> U+01CF */ 196, + /* U+0049+030F -> U+0208 */ 248, + /* U+0049+0311 -> U+020A */ 250, + /* U+0049+0323 -> U+1ECA */ 1318, + /* U+0049+0328 -> U+012E */ 109, + /* U+0049+0330 -> U+1E2C */ 1164, + /* U+004A+0302 -> U+0134 */ 114, + /* U+004B+0301 -> U+1E30 */ 1168, + /* U+004B+030C -> U+01E8 */ 218, + /* U+004B+0323 -> U+1E32 */ 1170, + /* U+004B+0327 -> U+0136 */ 116, + /* U+004B+0331 -> U+1E34 */ 1172, + /* U+004C+0301 -> U+0139 */ 118, + /* U+004C+030C -> U+013D */ 122, + /* U+004C+0323 -> U+1E36 */ 1174, + /* U+004C+0327 -> U+013B */ 120, + /* U+004C+032D -> U+1E3C */ 1180, + /* U+004C+0331 -> U+1E3A */ 1178, + /* U+004D+0301 -> U+1E3E */ 1182, + /* U+004D+0307 -> U+1E40 */ 1184, + /* U+004D+0323 -> U+1E42 */ 1186, + /* U+004E+0300 -> U+01F8 */ 232, + /* U+004E+0301 -> U+0143 */ 126, + /* U+004E+0303 -> U+00D1 */ 29, + /* U+004E+0307 -> U+1E44 */ 1188, + /* U+004E+030C -> U+0147 */ 130, + /* U+004E+0323 -> U+1E46 */ 1190, + /* U+004E+0327 -> U+0145 */ 128, + /* U+004E+032D -> U+1E4A */ 1194, + /* U+004E+0331 -> U+1E48 */ 1192, + /* U+004F+0300 -> U+00D2 */ 30, + /* U+004F+0301 -> U+00D3 */ 31, + /* U+004F+0302 -> U+00D4 */ 32, + /* U+004F+0303 -> U+00D5 */ 33, + /* U+004F+0304 -> U+014C */ 133, + /* U+004F+0306 -> U+014E */ 135, + /* U+004F+0307 -> U+022E */ 278, + /* U+004F+0308 -> U+00D6 */ 34, + /* U+004F+0309 -> U+1ECE */ 1322, + /* U+004F+030B -> U+0150 */ 137, + /* U+004F+030C -> U+01D1 */ 198, + /* U+004F+030F -> U+020C */ 252, + /* U+004F+0311 -> U+020E */ 254, + /* U+004F+031B -> U+01A0 */ 181, + /* U+004F+0323 -> U+1ECC */ 1320, + /* U+004F+0328 -> U+01EA */ 220, + /* U+0050+0301 -> U+1E54 */ 1204, + /* U+0050+0307 -> U+1E56 */ 1206, + /* U+0052+0301 -> U+0154 */ 139, + /* U+0052+0307 -> U+1E58 */ 1208, + /* U+0052+030C -> U+0158 */ 143, + /* U+0052+030F -> U+0210 */ 256, + /* U+0052+0311 -> U+0212 */ 258, + /* U+0052+0323 -> U+1E5A */ 1210, + /* U+0052+0327 -> U+0156 */ 141, + /* U+0052+0331 -> U+1E5E */ 1214, + /* U+0053+0301 -> U+015A */ 145, + /* U+0053+0302 -> U+015C */ 147, + /* U+0053+0307 -> U+1E60 */ 1216, + /* U+0053+030C -> U+0160 */ 151, + /* U+0053+0323 -> U+1E62 */ 1218, + /* U+0053+0326 -> U+0218 */ 264, + /* U+0053+0327 -> U+015E */ 149, + /* U+0054+0307 -> U+1E6A */ 1226, + /* U+0054+030C -> U+0164 */ 155, + /* U+0054+0323 -> U+1E6C */ 1228, + /* U+0054+0326 -> U+021A */ 266, + /* U+0054+0327 -> U+0162 */ 153, + /* U+0054+032D -> U+1E70 */ 1232, + /* U+0054+0331 -> U+1E6E */ 1230, + /* U+0055+0300 -> U+00D9 */ 35, + /* U+0055+0301 -> U+00DA */ 36, + /* U+0055+0302 -> U+00DB */ 37, + /* U+0055+0303 -> U+0168 */ 157, + /* U+0055+0304 -> U+016A */ 159, + /* U+0055+0306 -> U+016C */ 161, + /* U+0055+0308 -> U+00DC */ 38, + /* U+0055+0309 -> U+1EE6 */ 1346, + /* U+0055+030A -> U+016E */ 163, + /* U+0055+030B -> U+0170 */ 165, + /* U+0055+030C -> U+01D3 */ 200, + /* U+0055+030F -> U+0214 */ 260, + /* U+0055+0311 -> U+0216 */ 262, + /* U+0055+031B -> U+01AF */ 183, + /* U+0055+0323 -> U+1EE4 */ 1344, + /* U+0055+0324 -> U+1E72 */ 1234, + /* U+0055+0328 -> U+0172 */ 167, + /* U+0055+032D -> U+1E76 */ 1238, + /* U+0055+0330 -> U+1E74 */ 1236, + /* U+0056+0303 -> U+1E7C */ 1244, + /* U+0056+0323 -> U+1E7E */ 1246, + /* U+0057+0300 -> U+1E80 */ 1248, + /* U+0057+0301 -> U+1E82 */ 1250, + /* U+0057+0302 -> U+0174 */ 169, + /* U+0057+0307 -> U+1E86 */ 1254, + /* U+0057+0308 -> U+1E84 */ 1252, + /* U+0057+0323 -> U+1E88 */ 1256, + /* U+0058+0307 -> U+1E8A */ 1258, + /* U+0058+0308 -> U+1E8C */ 1260, + /* U+0059+0300 -> U+1EF2 */ 1358, + /* U+0059+0301 -> U+00DD */ 39, + /* U+0059+0302 -> U+0176 */ 171, + /* U+0059+0303 -> U+1EF8 */ 1364, + /* U+0059+0304 -> U+0232 */ 282, + /* U+0059+0307 -> U+1E8E */ 1262, + /* U+0059+0308 -> U+0178 */ 173, + /* U+0059+0309 -> U+1EF6 */ 1362, + /* U+0059+0323 -> U+1EF4 */ 1360, + /* U+005A+0301 -> U+0179 */ 174, + /* U+005A+0302 -> U+1E90 */ 1264, + /* U+005A+0307 -> U+017B */ 176, + /* U+005A+030C -> U+017D */ 178, + /* U+005A+0323 -> U+1E92 */ 1266, + /* U+005A+0331 -> U+1E94 */ 1268, + /* U+0061+0300 -> U+00E0 */ 40, + /* U+0061+0301 -> U+00E1 */ 41, + /* U+0061+0302 -> U+00E2 */ 42, + /* U+0061+0303 -> U+00E3 */ 43, + /* U+0061+0304 -> U+0101 */ 68, + /* U+0061+0306 -> U+0103 */ 70, + /* U+0061+0307 -> U+0227 */ 271, + /* U+0061+0308 -> U+00E4 */ 44, + /* U+0061+0309 -> U+1EA3 */ 1279, + /* U+0061+030A -> U+00E5 */ 45, + /* U+0061+030C -> U+01CE */ 195, + /* U+0061+030F -> U+0201 */ 241, + /* U+0061+0311 -> U+0203 */ 243, + /* U+0061+0323 -> U+1EA1 */ 1277, + /* U+0061+0325 -> U+1E01 */ 1121, + /* U+0061+0328 -> U+0105 */ 72, + /* U+0062+0307 -> U+1E03 */ 1123, + /* U+0062+0323 -> U+1E05 */ 1125, + /* U+0062+0331 -> U+1E07 */ 1127, + /* U+0063+0301 -> U+0107 */ 74, + /* U+0063+0302 -> U+0109 */ 76, + /* U+0063+0307 -> U+010B */ 78, + /* U+0063+030C -> U+010D */ 80, + /* U+0063+0327 -> U+00E7 */ 46, + /* U+0064+0307 -> U+1E0B */ 1131, + /* U+0064+030C -> U+010F */ 82, + /* U+0064+0323 -> U+1E0D */ 1133, + /* U+0064+0327 -> U+1E11 */ 1137, + /* U+0064+032D -> U+1E13 */ 1139, + /* U+0064+0331 -> U+1E0F */ 1135, + /* U+0065+0300 -> U+00E8 */ 47, + /* U+0065+0301 -> U+00E9 */ 48, + /* U+0065+0302 -> U+00EA */ 49, + /* U+0065+0303 -> U+1EBD */ 1305, + /* U+0065+0304 -> U+0113 */ 84, + /* U+0065+0306 -> U+0115 */ 86, + /* U+0065+0307 -> U+0117 */ 88, + /* U+0065+0308 -> U+00EB */ 50, + /* U+0065+0309 -> U+1EBB */ 1303, + /* U+0065+030C -> U+011B */ 92, + /* U+0065+030F -> U+0205 */ 245, + /* U+0065+0311 -> U+0207 */ 247, + /* U+0065+0323 -> U+1EB9 */ 1301, + /* U+0065+0327 -> U+0229 */ 273, + /* U+0065+0328 -> U+0119 */ 90, + /* U+0065+032D -> U+1E19 */ 1145, + /* U+0065+0330 -> U+1E1B */ 1147, + /* U+0066+0307 -> U+1E1F */ 1151, + /* U+0067+0301 -> U+01F5 */ 231, + /* U+0067+0302 -> U+011D */ 94, + /* U+0067+0304 -> U+1E21 */ 1153, + /* U+0067+0306 -> U+011F */ 96, + /* U+0067+0307 -> U+0121 */ 98, + /* U+0067+030C -> U+01E7 */ 217, + /* U+0067+0327 -> U+0123 */ 100, + /* U+0068+0302 -> U+0125 */ 102, + /* U+0068+0307 -> U+1E23 */ 1155, + /* U+0068+0308 -> U+1E27 */ 1159, + /* U+0068+030C -> U+021F */ 269, + /* U+0068+0323 -> U+1E25 */ 1157, + /* U+0068+0327 -> U+1E29 */ 1161, + /* U+0068+032E -> U+1E2B */ 1163, + /* U+0068+0331 -> U+1E96 */ 1270, + /* U+0069+0300 -> U+00EC */ 51, + /* U+0069+0301 -> U+00ED */ 52, + /* U+0069+0302 -> U+00EE */ 53, + /* U+0069+0303 -> U+0129 */ 104, + /* U+0069+0304 -> U+012B */ 106, + /* U+0069+0306 -> U+012D */ 108, + /* U+0069+0308 -> U+00EF */ 54, + /* U+0069+0309 -> U+1EC9 */ 1317, + /* U+0069+030C -> U+01D0 */ 197, + /* U+0069+030F -> U+0209 */ 249, + /* U+0069+0311 -> U+020B */ 251, + /* U+0069+0323 -> U+1ECB */ 1319, + /* U+0069+0328 -> U+012F */ 110, + /* U+0069+0330 -> U+1E2D */ 1165, + /* U+006A+0302 -> U+0135 */ 115, + /* U+006A+030C -> U+01F0 */ 226, + /* U+006B+0301 -> U+1E31 */ 1169, + /* U+006B+030C -> U+01E9 */ 219, + /* U+006B+0323 -> U+1E33 */ 1171, + /* U+006B+0327 -> U+0137 */ 117, + /* U+006B+0331 -> U+1E35 */ 1173, + /* U+006C+0301 -> U+013A */ 119, + /* U+006C+030C -> U+013E */ 123, + /* U+006C+0323 -> U+1E37 */ 1175, + /* U+006C+0327 -> U+013C */ 121, + /* U+006C+032D -> U+1E3D */ 1181, + /* U+006C+0331 -> U+1E3B */ 1179, + /* U+006D+0301 -> U+1E3F */ 1183, + /* U+006D+0307 -> U+1E41 */ 1185, + /* U+006D+0323 -> U+1E43 */ 1187, + /* U+006E+0300 -> U+01F9 */ 233, + /* U+006E+0301 -> U+0144 */ 127, + /* U+006E+0303 -> U+00F1 */ 55, + /* U+006E+0307 -> U+1E45 */ 1189, + /* U+006E+030C -> U+0148 */ 131, + /* U+006E+0323 -> U+1E47 */ 1191, + /* U+006E+0327 -> U+0146 */ 129, + /* U+006E+032D -> U+1E4B */ 1195, + /* U+006E+0331 -> U+1E49 */ 1193, + /* U+006F+0300 -> U+00F2 */ 56, + /* U+006F+0301 -> U+00F3 */ 57, + /* U+006F+0302 -> U+00F4 */ 58, + /* U+006F+0303 -> U+00F5 */ 59, + /* U+006F+0304 -> U+014D */ 134, + /* U+006F+0306 -> U+014F */ 136, + /* U+006F+0307 -> U+022F */ 279, + /* U+006F+0308 -> U+00F6 */ 60, + /* U+006F+0309 -> U+1ECF */ 1323, + /* U+006F+030B -> U+0151 */ 138, + /* U+006F+030C -> U+01D2 */ 199, + /* U+006F+030F -> U+020D */ 253, + /* U+006F+0311 -> U+020F */ 255, + /* U+006F+031B -> U+01A1 */ 182, + /* U+006F+0323 -> U+1ECD */ 1321, + /* U+006F+0328 -> U+01EB */ 221, + /* U+0070+0301 -> U+1E55 */ 1205, + /* U+0070+0307 -> U+1E57 */ 1207, + /* U+0072+0301 -> U+0155 */ 140, + /* U+0072+0307 -> U+1E59 */ 1209, + /* U+0072+030C -> U+0159 */ 144, + /* U+0072+030F -> U+0211 */ 257, + /* U+0072+0311 -> U+0213 */ 259, + /* U+0072+0323 -> U+1E5B */ 1211, + /* U+0072+0327 -> U+0157 */ 142, + /* U+0072+0331 -> U+1E5F */ 1215, + /* U+0073+0301 -> U+015B */ 146, + /* U+0073+0302 -> U+015D */ 148, + /* U+0073+0307 -> U+1E61 */ 1217, + /* U+0073+030C -> U+0161 */ 152, + /* U+0073+0323 -> U+1E63 */ 1219, + /* U+0073+0326 -> U+0219 */ 265, + /* U+0073+0327 -> U+015F */ 150, + /* U+0074+0307 -> U+1E6B */ 1227, + /* U+0074+0308 -> U+1E97 */ 1271, + /* U+0074+030C -> U+0165 */ 156, + /* U+0074+0323 -> U+1E6D */ 1229, + /* U+0074+0326 -> U+021B */ 267, + /* U+0074+0327 -> U+0163 */ 154, + /* U+0074+032D -> U+1E71 */ 1233, + /* U+0074+0331 -> U+1E6F */ 1231, + /* U+0075+0300 -> U+00F9 */ 61, + /* U+0075+0301 -> U+00FA */ 62, + /* U+0075+0302 -> U+00FB */ 63, + /* U+0075+0303 -> U+0169 */ 158, + /* U+0075+0304 -> U+016B */ 160, + /* U+0075+0306 -> U+016D */ 162, + /* U+0075+0308 -> U+00FC */ 64, + /* U+0075+0309 -> U+1EE7 */ 1347, + /* U+0075+030A -> U+016F */ 164, + /* U+0075+030B -> U+0171 */ 166, + /* U+0075+030C -> U+01D4 */ 201, + /* U+0075+030F -> U+0215 */ 261, + /* U+0075+0311 -> U+0217 */ 263, + /* U+0075+031B -> U+01B0 */ 184, + /* U+0075+0323 -> U+1EE5 */ 1345, + /* U+0075+0324 -> U+1E73 */ 1235, + /* U+0075+0328 -> U+0173 */ 168, + /* U+0075+032D -> U+1E77 */ 1239, + /* U+0075+0330 -> U+1E75 */ 1237, + /* U+0076+0303 -> U+1E7D */ 1245, + /* U+0076+0323 -> U+1E7F */ 1247, + /* U+0077+0300 -> U+1E81 */ 1249, + /* U+0077+0301 -> U+1E83 */ 1251, + /* U+0077+0302 -> U+0175 */ 170, + /* U+0077+0307 -> U+1E87 */ 1255, + /* U+0077+0308 -> U+1E85 */ 1253, + /* U+0077+030A -> U+1E98 */ 1272, + /* U+0077+0323 -> U+1E89 */ 1257, + /* U+0078+0307 -> U+1E8B */ 1259, + /* U+0078+0308 -> U+1E8D */ 1261, + /* U+0079+0300 -> U+1EF3 */ 1359, + /* U+0079+0301 -> U+00FD */ 65, + /* U+0079+0302 -> U+0177 */ 172, + /* U+0079+0303 -> U+1EF9 */ 1365, + /* U+0079+0304 -> U+0233 */ 283, + /* U+0079+0307 -> U+1E8F */ 1263, + /* U+0079+0308 -> U+00FF */ 66, + /* U+0079+0309 -> U+1EF7 */ 1363, + /* U+0079+030A -> U+1E99 */ 1273, + /* U+0079+0323 -> U+1EF5 */ 1361, + /* U+007A+0301 -> U+017A */ 175, + /* U+007A+0302 -> U+1E91 */ 1265, + /* U+007A+0307 -> U+017C */ 177, + /* U+007A+030C -> U+017E */ 179, + /* U+007A+0323 -> U+1E93 */ 1267, + /* U+007A+0331 -> U+1E95 */ 1269, + /* U+00A8+0300 -> U+1FED */ 1584, + /* U+00A8+0301 -> U+0385 */ 419, + /* U+00A8+0342 -> U+1FC1 */ 1544, + /* U+00C2+0300 -> U+1EA6 */ 1282, + /* U+00C2+0301 -> U+1EA4 */ 1280, + /* U+00C2+0303 -> U+1EAA */ 1286, + /* U+00C2+0309 -> U+1EA8 */ 1284, + /* U+00C4+0304 -> U+01DE */ 210, + /* U+00C5+0301 -> U+01FA */ 234, + /* U+00C6+0301 -> U+01FC */ 236, + /* U+00C6+0304 -> U+01E2 */ 214, + /* U+00C7+0301 -> U+1E08 */ 1128, + /* U+00CA+0300 -> U+1EC0 */ 1308, + /* U+00CA+0301 -> U+1EBE */ 1306, + /* U+00CA+0303 -> U+1EC4 */ 1312, + /* U+00CA+0309 -> U+1EC2 */ 1310, + /* U+00CF+0301 -> U+1E2E */ 1166, + /* U+00D4+0300 -> U+1ED2 */ 1326, + /* U+00D4+0301 -> U+1ED0 */ 1324, + /* U+00D4+0303 -> U+1ED6 */ 1330, + /* U+00D4+0309 -> U+1ED4 */ 1328, + /* U+00D5+0301 -> U+1E4C */ 1196, + /* U+00D5+0304 -> U+022C */ 276, + /* U+00D5+0308 -> U+1E4E */ 1198, + /* U+00D6+0304 -> U+022A */ 274, + /* U+00D8+0301 -> U+01FE */ 238, + /* U+00DC+0300 -> U+01DB */ 208, + /* U+00DC+0301 -> U+01D7 */ 204, + /* U+00DC+0304 -> U+01D5 */ 202, + /* U+00DC+030C -> U+01D9 */ 206, + /* U+00E2+0300 -> U+1EA7 */ 1283, + /* U+00E2+0301 -> U+1EA5 */ 1281, + /* U+00E2+0303 -> U+1EAB */ 1287, + /* U+00E2+0309 -> U+1EA9 */ 1285, + /* U+00E4+0304 -> U+01DF */ 211, + /* U+00E5+0301 -> U+01FB */ 235, + /* U+00E6+0301 -> U+01FD */ 237, + /* U+00E6+0304 -> U+01E3 */ 215, + /* U+00E7+0301 -> U+1E09 */ 1129, + /* U+00EA+0300 -> U+1EC1 */ 1309, + /* U+00EA+0301 -> U+1EBF */ 1307, + /* U+00EA+0303 -> U+1EC5 */ 1313, + /* U+00EA+0309 -> U+1EC3 */ 1311, + /* U+00EF+0301 -> U+1E2F */ 1167, + /* U+00F4+0300 -> U+1ED3 */ 1327, + /* U+00F4+0301 -> U+1ED1 */ 1325, + /* U+00F4+0303 -> U+1ED7 */ 1331, + /* U+00F4+0309 -> U+1ED5 */ 1329, + /* U+00F5+0301 -> U+1E4D */ 1197, + /* U+00F5+0304 -> U+022D */ 277, + /* U+00F5+0308 -> U+1E4F */ 1199, + /* U+00F6+0304 -> U+022B */ 275, + /* U+00F8+0301 -> U+01FF */ 239, + /* U+00FC+0300 -> U+01DC */ 209, + /* U+00FC+0301 -> U+01D8 */ 205, + /* U+00FC+0304 -> U+01D6 */ 203, + /* U+00FC+030C -> U+01DA */ 207, + /* U+0102+0300 -> U+1EB0 */ 1292, + /* U+0102+0301 -> U+1EAE */ 1290, + /* U+0102+0303 -> U+1EB4 */ 1296, + /* U+0102+0309 -> U+1EB2 */ 1294, + /* U+0103+0300 -> U+1EB1 */ 1293, + /* U+0103+0301 -> U+1EAF */ 1291, + /* U+0103+0303 -> U+1EB5 */ 1297, + /* U+0103+0309 -> U+1EB3 */ 1295, + /* U+0112+0300 -> U+1E14 */ 1140, + /* U+0112+0301 -> U+1E16 */ 1142, + /* U+0113+0300 -> U+1E15 */ 1141, + /* U+0113+0301 -> U+1E17 */ 1143, + /* U+014C+0300 -> U+1E50 */ 1200, + /* U+014C+0301 -> U+1E52 */ 1202, + /* U+014D+0300 -> U+1E51 */ 1201, + /* U+014D+0301 -> U+1E53 */ 1203, + /* U+015A+0307 -> U+1E64 */ 1220, + /* U+015B+0307 -> U+1E65 */ 1221, + /* U+0160+0307 -> U+1E66 */ 1222, + /* U+0161+0307 -> U+1E67 */ 1223, + /* U+0168+0301 -> U+1E78 */ 1240, + /* U+0169+0301 -> U+1E79 */ 1241, + /* U+016A+0308 -> U+1E7A */ 1242, + /* U+016B+0308 -> U+1E7B */ 1243, + /* U+017F+0307 -> U+1E9B */ 1275, + /* U+01A0+0300 -> U+1EDC */ 1336, + /* U+01A0+0301 -> U+1EDA */ 1334, + /* U+01A0+0303 -> U+1EE0 */ 1340, + /* U+01A0+0309 -> U+1EDE */ 1338, + /* U+01A0+0323 -> U+1EE2 */ 1342, + /* U+01A1+0300 -> U+1EDD */ 1337, + /* U+01A1+0301 -> U+1EDB */ 1335, + /* U+01A1+0303 -> U+1EE1 */ 1341, + /* U+01A1+0309 -> U+1EDF */ 1339, + /* U+01A1+0323 -> U+1EE3 */ 1343, + /* U+01AF+0300 -> U+1EEA */ 1350, + /* U+01AF+0301 -> U+1EE8 */ 1348, + /* U+01AF+0303 -> U+1EEE */ 1354, + /* U+01AF+0309 -> U+1EEC */ 1352, + /* U+01AF+0323 -> U+1EF0 */ 1356, + /* U+01B0+0300 -> U+1EEB */ 1351, + /* U+01B0+0301 -> U+1EE9 */ 1349, + /* U+01B0+0303 -> U+1EEF */ 1355, + /* U+01B0+0309 -> U+1EED */ 1353, + /* U+01B0+0323 -> U+1EF1 */ 1357, + /* U+01B7+030C -> U+01EE */ 224, + /* U+01EA+0304 -> U+01EC */ 222, + /* U+01EB+0304 -> U+01ED */ 223, + /* U+0226+0304 -> U+01E0 */ 212, + /* U+0227+0304 -> U+01E1 */ 213, + /* U+0228+0306 -> U+1E1C */ 1148, + /* U+0229+0306 -> U+1E1D */ 1149, + /* U+022E+0304 -> U+0230 */ 280, + /* U+022F+0304 -> U+0231 */ 281, + /* U+0292+030C -> U+01EF */ 225, + /* U+0391+0300 -> U+1FBA */ 1537, + /* U+0391+0301 -> U+0386 */ 420, + /* U+0391+0304 -> U+1FB9 */ 1536, + /* U+0391+0306 -> U+1FB8 */ 1535, + /* U+0391+0313 -> U+1F08 */ 1374, + /* U+0391+0314 -> U+1F09 */ 1375, + /* U+0391+0345 -> U+1FBC */ 1539, + /* U+0395+0300 -> U+1FC8 */ 1550, + /* U+0395+0301 -> U+0388 */ 422, + /* U+0395+0313 -> U+1F18 */ 1388, + /* U+0395+0314 -> U+1F19 */ 1389, + /* U+0397+0300 -> U+1FCA */ 1552, + /* U+0397+0301 -> U+0389 */ 423, + /* U+0397+0313 -> U+1F28 */ 1402, + /* U+0397+0314 -> U+1F29 */ 1403, + /* U+0397+0345 -> U+1FCC */ 1554, + /* U+0399+0300 -> U+1FDA */ 1566, + /* U+0399+0301 -> U+038A */ 424, + /* U+0399+0304 -> U+1FD9 */ 1565, + /* U+0399+0306 -> U+1FD8 */ 1564, + /* U+0399+0308 -> U+03AA */ 429, + /* U+0399+0313 -> U+1F38 */ 1418, + /* U+0399+0314 -> U+1F39 */ 1419, + /* U+039F+0300 -> U+1FF8 */ 1592, + /* U+039F+0301 -> U+038C */ 425, + /* U+039F+0313 -> U+1F48 */ 1432, + /* U+039F+0314 -> U+1F49 */ 1433, + /* U+03A1+0314 -> U+1FEC */ 1583, + /* U+03A5+0300 -> U+1FEA */ 1581, + /* U+03A5+0301 -> U+038E */ 426, + /* U+03A5+0304 -> U+1FE9 */ 1580, + /* U+03A5+0306 -> U+1FE8 */ 1579, + /* U+03A5+0308 -> U+03AB */ 430, + /* U+03A5+0314 -> U+1F59 */ 1446, + /* U+03A9+0300 -> U+1FFA */ 1594, + /* U+03A9+0301 -> U+038F */ 427, + /* U+03A9+0313 -> U+1F68 */ 1458, + /* U+03A9+0314 -> U+1F69 */ 1459, + /* U+03A9+0345 -> U+1FFC */ 1596, + /* U+03AC+0345 -> U+1FB4 */ 1532, + /* U+03AE+0345 -> U+1FC4 */ 1547, + /* U+03B1+0300 -> U+1F70 */ 1466, + /* U+03B1+0301 -> U+03AC */ 431, + /* U+03B1+0304 -> U+1FB1 */ 1529, + /* U+03B1+0306 -> U+1FB0 */ 1528, + /* U+03B1+0313 -> U+1F00 */ 1366, + /* U+03B1+0314 -> U+1F01 */ 1367, + /* U+03B1+0342 -> U+1FB6 */ 1533, + /* U+03B1+0345 -> U+1FB3 */ 1531, + /* U+03B5+0300 -> U+1F72 */ 1468, + /* U+03B5+0301 -> U+03AD */ 432, + /* U+03B5+0313 -> U+1F10 */ 1382, + /* U+03B5+0314 -> U+1F11 */ 1383, + /* U+03B7+0300 -> U+1F74 */ 1470, + /* U+03B7+0301 -> U+03AE */ 433, + /* U+03B7+0313 -> U+1F20 */ 1394, + /* U+03B7+0314 -> U+1F21 */ 1395, + /* U+03B7+0342 -> U+1FC6 */ 1548, + /* U+03B7+0345 -> U+1FC3 */ 1546, + /* U+03B9+0300 -> U+1F76 */ 1472, + /* U+03B9+0301 -> U+03AF */ 434, + /* U+03B9+0304 -> U+1FD1 */ 1559, + /* U+03B9+0306 -> U+1FD0 */ 1558, + /* U+03B9+0308 -> U+03CA */ 436, + /* U+03B9+0313 -> U+1F30 */ 1410, + /* U+03B9+0314 -> U+1F31 */ 1411, + /* U+03B9+0342 -> U+1FD6 */ 1562, + /* U+03BF+0300 -> U+1F78 */ 1474, + /* U+03BF+0301 -> U+03CC */ 438, + /* U+03BF+0313 -> U+1F40 */ 1426, + /* U+03BF+0314 -> U+1F41 */ 1427, + /* U+03C1+0313 -> U+1FE4 */ 1575, + /* U+03C1+0314 -> U+1FE5 */ 1576, + /* U+03C5+0300 -> U+1F7A */ 1476, + /* U+03C5+0301 -> U+03CD */ 439, + /* U+03C5+0304 -> U+1FE1 */ 1572, + /* U+03C5+0306 -> U+1FE0 */ 1571, + /* U+03C5+0308 -> U+03CB */ 437, + /* U+03C5+0313 -> U+1F50 */ 1438, + /* U+03C5+0314 -> U+1F51 */ 1439, + /* U+03C5+0342 -> U+1FE6 */ 1577, + /* U+03C9+0300 -> U+1F7C */ 1478, + /* U+03C9+0301 -> U+03CE */ 440, + /* U+03C9+0313 -> U+1F60 */ 1450, + /* U+03C9+0314 -> U+1F61 */ 1451, + /* U+03C9+0342 -> U+1FF6 */ 1590, + /* U+03C9+0345 -> U+1FF3 */ 1588, + /* U+03CA+0300 -> U+1FD2 */ 1560, + /* U+03CA+0301 -> U+0390 */ 428, + /* U+03CA+0342 -> U+1FD7 */ 1563, + /* U+03CB+0300 -> U+1FE2 */ 1573, + /* U+03CB+0301 -> U+03B0 */ 435, + /* U+03CB+0342 -> U+1FE7 */ 1578, + /* U+03CE+0345 -> U+1FF4 */ 1589, + /* U+03D2+0301 -> U+03D3 */ 444, + /* U+03D2+0308 -> U+03D4 */ 445, + /* U+0406+0308 -> U+0407 */ 457, + /* U+0410+0306 -> U+04D0 */ 479, + /* U+0410+0308 -> U+04D2 */ 481, + /* U+0413+0301 -> U+0403 */ 456, + /* U+0415+0300 -> U+0400 */ 454, + /* U+0415+0306 -> U+04D6 */ 483, + /* U+0415+0308 -> U+0401 */ 455, + /* U+0416+0306 -> U+04C1 */ 477, + /* U+0416+0308 -> U+04DC */ 487, + /* U+0417+0308 -> U+04DE */ 489, + /* U+0418+0300 -> U+040D */ 459, + /* U+0418+0304 -> U+04E2 */ 491, + /* U+0418+0306 -> U+0419 */ 461, + /* U+0418+0308 -> U+04E4 */ 493, + /* U+041A+0301 -> U+040C */ 458, + /* U+041E+0308 -> U+04E6 */ 495, + /* U+0423+0304 -> U+04EE */ 501, + /* U+0423+0306 -> U+040E */ 460, + /* U+0423+0308 -> U+04F0 */ 503, + /* U+0423+030B -> U+04F2 */ 505, + /* U+0427+0308 -> U+04F4 */ 507, + /* U+042B+0308 -> U+04F8 */ 509, + /* U+042D+0308 -> U+04EC */ 499, + /* U+0430+0306 -> U+04D1 */ 480, + /* U+0430+0308 -> U+04D3 */ 482, + /* U+0433+0301 -> U+0453 */ 465, + /* U+0435+0300 -> U+0450 */ 463, + /* U+0435+0306 -> U+04D7 */ 484, + /* U+0435+0308 -> U+0451 */ 464, + /* U+0436+0306 -> U+04C2 */ 478, + /* U+0436+0308 -> U+04DD */ 488, + /* U+0437+0308 -> U+04DF */ 490, + /* U+0438+0300 -> U+045D */ 468, + /* U+0438+0304 -> U+04E3 */ 492, + /* U+0438+0306 -> U+0439 */ 462, + /* U+0438+0308 -> U+04E5 */ 494, + /* U+043A+0301 -> U+045C */ 467, + /* U+043E+0308 -> U+04E7 */ 496, + /* U+0443+0304 -> U+04EF */ 502, + /* U+0443+0306 -> U+045E */ 469, + /* U+0443+0308 -> U+04F1 */ 504, + /* U+0443+030B -> U+04F3 */ 506, + /* U+0447+0308 -> U+04F5 */ 508, + /* U+044B+0308 -> U+04F9 */ 510, + /* U+044D+0308 -> U+04ED */ 500, + /* U+0456+0308 -> U+0457 */ 466, + /* U+0474+030F -> U+0476 */ 470, + /* U+0475+030F -> U+0477 */ 471, + /* U+04D8+0308 -> U+04DA */ 485, + /* U+04D9+0308 -> U+04DB */ 486, + /* U+04E8+0308 -> U+04EA */ 497, + /* U+04E9+0308 -> U+04EB */ 498, + /* U+0627+0653 -> U+0622 */ 574, + /* U+0627+0654 -> U+0623 */ 575, + /* U+0627+0655 -> U+0625 */ 577, + /* U+0648+0654 -> U+0624 */ 576, + /* U+064A+0654 -> U+0626 */ 578, + /* U+06C1+0654 -> U+06C2 */ 606, + /* U+06D2+0654 -> U+06D3 */ 607, + /* U+06D5+0654 -> U+06C0 */ 605, + /* U+0928+093C -> U+0929 */ 733, + /* U+0930+093C -> U+0931 */ 734, + /* U+0933+093C -> U+0934 */ 735, + /* U+09C7+09BE -> U+09CB */ 751, + /* U+09C7+09D7 -> U+09CC */ 752, + /* U+0B47+0B3E -> U+0B4B */ 770, + /* U+0B47+0B56 -> U+0B48 */ 769, + /* U+0B47+0B57 -> U+0B4C */ 771, + /* U+0B92+0BD7 -> U+0B94 */ 775, + /* U+0BC6+0BBE -> U+0BCA */ 776, + /* U+0BC6+0BD7 -> U+0BCC */ 778, + /* U+0BC7+0BBE -> U+0BCB */ 777, + /* U+0C46+0C56 -> U+0C48 */ 780, + /* U+0CBF+0CD5 -> U+0CC0 */ 785, + /* U+0CC6+0CC2 -> U+0CCA */ 788, + /* U+0CC6+0CD5 -> U+0CC7 */ 786, + /* U+0CC6+0CD6 -> U+0CC8 */ 787, + /* U+0CCA+0CD5 -> U+0CCB */ 789, + /* U+0D46+0D3E -> U+0D4A */ 793, + /* U+0D46+0D57 -> U+0D4C */ 795, + /* U+0D47+0D3E -> U+0D4B */ 794, + /* U+0DD9+0DCA -> U+0DDA */ 798, + /* U+0DD9+0DCF -> U+0DDC */ 799, + /* U+0DD9+0DDF -> U+0DDE */ 801, + /* U+0DDC+0DCA -> U+0DDD */ 800, + /* U+1025+102E -> U+1026 */ 859, + /* U+1B05+1B35 -> U+1B06 */ 904, + /* U+1B07+1B35 -> U+1B08 */ 905, + /* U+1B09+1B35 -> U+1B0A */ 906, + /* U+1B0B+1B35 -> U+1B0C */ 907, + /* U+1B0D+1B35 -> U+1B0E */ 908, + /* U+1B11+1B35 -> U+1B12 */ 909, + /* U+1B3A+1B35 -> U+1B3B */ 911, + /* U+1B3C+1B35 -> U+1B3D */ 912, + /* U+1B3E+1B35 -> U+1B40 */ 913, + /* U+1B3F+1B35 -> U+1B41 */ 914, + /* U+1B42+1B35 -> U+1B43 */ 915, + /* U+1E36+0304 -> U+1E38 */ 1176, + /* U+1E37+0304 -> U+1E39 */ 1177, + /* U+1E5A+0304 -> U+1E5C */ 1212, + /* U+1E5B+0304 -> U+1E5D */ 1213, + /* U+1E62+0307 -> U+1E68 */ 1224, + /* U+1E63+0307 -> U+1E69 */ 1225, + /* U+1EA0+0302 -> U+1EAC */ 1288, + /* U+1EA0+0306 -> U+1EB6 */ 1298, + /* U+1EA1+0302 -> U+1EAD */ 1289, + /* U+1EA1+0306 -> U+1EB7 */ 1299, + /* U+1EB8+0302 -> U+1EC6 */ 1314, + /* U+1EB9+0302 -> U+1EC7 */ 1315, + /* U+1ECC+0302 -> U+1ED8 */ 1332, + /* U+1ECD+0302 -> U+1ED9 */ 1333, + /* U+1F00+0300 -> U+1F02 */ 1368, + /* U+1F00+0301 -> U+1F04 */ 1370, + /* U+1F00+0342 -> U+1F06 */ 1372, + /* U+1F00+0345 -> U+1F80 */ 1480, + /* U+1F01+0300 -> U+1F03 */ 1369, + /* U+1F01+0301 -> U+1F05 */ 1371, + /* U+1F01+0342 -> U+1F07 */ 1373, + /* U+1F01+0345 -> U+1F81 */ 1481, + /* U+1F02+0345 -> U+1F82 */ 1482, + /* U+1F03+0345 -> U+1F83 */ 1483, + /* U+1F04+0345 -> U+1F84 */ 1484, + /* U+1F05+0345 -> U+1F85 */ 1485, + /* U+1F06+0345 -> U+1F86 */ 1486, + /* U+1F07+0345 -> U+1F87 */ 1487, + /* U+1F08+0300 -> U+1F0A */ 1376, + /* U+1F08+0301 -> U+1F0C */ 1378, + /* U+1F08+0342 -> U+1F0E */ 1380, + /* U+1F08+0345 -> U+1F88 */ 1488, + /* U+1F09+0300 -> U+1F0B */ 1377, + /* U+1F09+0301 -> U+1F0D */ 1379, + /* U+1F09+0342 -> U+1F0F */ 1381, + /* U+1F09+0345 -> U+1F89 */ 1489, + /* U+1F0A+0345 -> U+1F8A */ 1490, + /* U+1F0B+0345 -> U+1F8B */ 1491, + /* U+1F0C+0345 -> U+1F8C */ 1492, + /* U+1F0D+0345 -> U+1F8D */ 1493, + /* U+1F0E+0345 -> U+1F8E */ 1494, + /* U+1F0F+0345 -> U+1F8F */ 1495, + /* U+1F10+0300 -> U+1F12 */ 1384, + /* U+1F10+0301 -> U+1F14 */ 1386, + /* U+1F11+0300 -> U+1F13 */ 1385, + /* U+1F11+0301 -> U+1F15 */ 1387, + /* U+1F18+0300 -> U+1F1A */ 1390, + /* U+1F18+0301 -> U+1F1C */ 1392, + /* U+1F19+0300 -> U+1F1B */ 1391, + /* U+1F19+0301 -> U+1F1D */ 1393, + /* U+1F20+0300 -> U+1F22 */ 1396, + /* U+1F20+0301 -> U+1F24 */ 1398, + /* U+1F20+0342 -> U+1F26 */ 1400, + /* U+1F20+0345 -> U+1F90 */ 1496, + /* U+1F21+0300 -> U+1F23 */ 1397, + /* U+1F21+0301 -> U+1F25 */ 1399, + /* U+1F21+0342 -> U+1F27 */ 1401, + /* U+1F21+0345 -> U+1F91 */ 1497, + /* U+1F22+0345 -> U+1F92 */ 1498, + /* U+1F23+0345 -> U+1F93 */ 1499, + /* U+1F24+0345 -> U+1F94 */ 1500, + /* U+1F25+0345 -> U+1F95 */ 1501, + /* U+1F26+0345 -> U+1F96 */ 1502, + /* U+1F27+0345 -> U+1F97 */ 1503, + /* U+1F28+0300 -> U+1F2A */ 1404, + /* U+1F28+0301 -> U+1F2C */ 1406, + /* U+1F28+0342 -> U+1F2E */ 1408, + /* U+1F28+0345 -> U+1F98 */ 1504, + /* U+1F29+0300 -> U+1F2B */ 1405, + /* U+1F29+0301 -> U+1F2D */ 1407, + /* U+1F29+0342 -> U+1F2F */ 1409, + /* U+1F29+0345 -> U+1F99 */ 1505, + /* U+1F2A+0345 -> U+1F9A */ 1506, + /* U+1F2B+0345 -> U+1F9B */ 1507, + /* U+1F2C+0345 -> U+1F9C */ 1508, + /* U+1F2D+0345 -> U+1F9D */ 1509, + /* U+1F2E+0345 -> U+1F9E */ 1510, + /* U+1F2F+0345 -> U+1F9F */ 1511, + /* U+1F30+0300 -> U+1F32 */ 1412, + /* U+1F30+0301 -> U+1F34 */ 1414, + /* U+1F30+0342 -> U+1F36 */ 1416, + /* U+1F31+0300 -> U+1F33 */ 1413, + /* U+1F31+0301 -> U+1F35 */ 1415, + /* U+1F31+0342 -> U+1F37 */ 1417, + /* U+1F38+0300 -> U+1F3A */ 1420, + /* U+1F38+0301 -> U+1F3C */ 1422, + /* U+1F38+0342 -> U+1F3E */ 1424, + /* U+1F39+0300 -> U+1F3B */ 1421, + /* U+1F39+0301 -> U+1F3D */ 1423, + /* U+1F39+0342 -> U+1F3F */ 1425, + /* U+1F40+0300 -> U+1F42 */ 1428, + /* U+1F40+0301 -> U+1F44 */ 1430, + /* U+1F41+0300 -> U+1F43 */ 1429, + /* U+1F41+0301 -> U+1F45 */ 1431, + /* U+1F48+0300 -> U+1F4A */ 1434, + /* U+1F48+0301 -> U+1F4C */ 1436, + /* U+1F49+0300 -> U+1F4B */ 1435, + /* U+1F49+0301 -> U+1F4D */ 1437, + /* U+1F50+0300 -> U+1F52 */ 1440, + /* U+1F50+0301 -> U+1F54 */ 1442, + /* U+1F50+0342 -> U+1F56 */ 1444, + /* U+1F51+0300 -> U+1F53 */ 1441, + /* U+1F51+0301 -> U+1F55 */ 1443, + /* U+1F51+0342 -> U+1F57 */ 1445, + /* U+1F59+0300 -> U+1F5B */ 1447, + /* U+1F59+0301 -> U+1F5D */ 1448, + /* U+1F59+0342 -> U+1F5F */ 1449, + /* U+1F60+0300 -> U+1F62 */ 1452, + /* U+1F60+0301 -> U+1F64 */ 1454, + /* U+1F60+0342 -> U+1F66 */ 1456, + /* U+1F60+0345 -> U+1FA0 */ 1512, + /* U+1F61+0300 -> U+1F63 */ 1453, + /* U+1F61+0301 -> U+1F65 */ 1455, + /* U+1F61+0342 -> U+1F67 */ 1457, + /* U+1F61+0345 -> U+1FA1 */ 1513, + /* U+1F62+0345 -> U+1FA2 */ 1514, + /* U+1F63+0345 -> U+1FA3 */ 1515, + /* U+1F64+0345 -> U+1FA4 */ 1516, + /* U+1F65+0345 -> U+1FA5 */ 1517, + /* U+1F66+0345 -> U+1FA6 */ 1518, + /* U+1F67+0345 -> U+1FA7 */ 1519, + /* U+1F68+0300 -> U+1F6A */ 1460, + /* U+1F68+0301 -> U+1F6C */ 1462, + /* U+1F68+0342 -> U+1F6E */ 1464, + /* U+1F68+0345 -> U+1FA8 */ 1520, + /* U+1F69+0300 -> U+1F6B */ 1461, + /* U+1F69+0301 -> U+1F6D */ 1463, + /* U+1F69+0342 -> U+1F6F */ 1465, + /* U+1F69+0345 -> U+1FA9 */ 1521, + /* U+1F6A+0345 -> U+1FAA */ 1522, + /* U+1F6B+0345 -> U+1FAB */ 1523, + /* U+1F6C+0345 -> U+1FAC */ 1524, + /* U+1F6D+0345 -> U+1FAD */ 1525, + /* U+1F6E+0345 -> U+1FAE */ 1526, + /* U+1F6F+0345 -> U+1FAF */ 1527, + /* U+1F70+0345 -> U+1FB2 */ 1530, + /* U+1F74+0345 -> U+1FC2 */ 1545, + /* U+1F7C+0345 -> U+1FF2 */ 1587, + /* U+1FB6+0345 -> U+1FB7 */ 1534, + /* U+1FBF+0300 -> U+1FCD */ 1555, + /* U+1FBF+0301 -> U+1FCE */ 1556, + /* U+1FBF+0342 -> U+1FCF */ 1557, + /* U+1FC6+0345 -> U+1FC7 */ 1549, + /* U+1FF6+0345 -> U+1FF7 */ 1591, + /* U+1FFE+0300 -> U+1FDD */ 1568, + /* U+1FFE+0301 -> U+1FDE */ 1569, + /* U+1FFE+0342 -> U+1FDF */ 1570, + /* U+2190+0338 -> U+219A */ 1801, + /* U+2192+0338 -> U+219B */ 1802, + /* U+2194+0338 -> U+21AE */ 1803, + /* U+21D0+0338 -> U+21CD */ 1804, + /* U+21D2+0338 -> U+21CF */ 1806, + /* U+21D4+0338 -> U+21CE */ 1805, + /* U+2203+0338 -> U+2204 */ 1807, + /* U+2208+0338 -> U+2209 */ 1808, + /* U+220B+0338 -> U+220C */ 1809, + /* U+2223+0338 -> U+2224 */ 1810, + /* U+2225+0338 -> U+2226 */ 1811, + /* U+223C+0338 -> U+2241 */ 1816, + /* U+2243+0338 -> U+2244 */ 1817, + /* U+2245+0338 -> U+2247 */ 1818, + /* U+2248+0338 -> U+2249 */ 1819, + /* U+224D+0338 -> U+226D */ 1822, + /* U+2261+0338 -> U+2262 */ 1821, + /* U+2264+0338 -> U+2270 */ 1825, + /* U+2265+0338 -> U+2271 */ 1826, + /* U+2272+0338 -> U+2274 */ 1827, + /* U+2273+0338 -> U+2275 */ 1828, + /* U+2276+0338 -> U+2278 */ 1829, + /* U+2277+0338 -> U+2279 */ 1830, + /* U+227A+0338 -> U+2280 */ 1831, + /* U+227B+0338 -> U+2281 */ 1832, + /* U+227C+0338 -> U+22E0 */ 1841, + /* U+227D+0338 -> U+22E1 */ 1842, + /* U+2282+0338 -> U+2284 */ 1833, + /* U+2283+0338 -> U+2285 */ 1834, + /* U+2286+0338 -> U+2288 */ 1835, + /* U+2287+0338 -> U+2289 */ 1836, + /* U+2291+0338 -> U+22E2 */ 1843, + /* U+2292+0338 -> U+22E3 */ 1844, + /* U+22A2+0338 -> U+22AC */ 1837, + /* U+22A8+0338 -> U+22AD */ 1838, + /* U+22A9+0338 -> U+22AE */ 1839, + /* U+22AB+0338 -> U+22AF */ 1840, + /* U+22B2+0338 -> U+22EA */ 1845, + /* U+22B3+0338 -> U+22EB */ 1846, + /* U+22B4+0338 -> U+22EC */ 1847, + /* U+22B5+0338 -> U+22ED */ 1848, + /* U+3046+3099 -> U+3094 */ 2286, + /* U+304B+3099 -> U+304C */ 2261, + /* U+304D+3099 -> U+304E */ 2262, + /* U+304F+3099 -> U+3050 */ 2263, + /* U+3051+3099 -> U+3052 */ 2264, + /* U+3053+3099 -> U+3054 */ 2265, + /* U+3055+3099 -> U+3056 */ 2266, + /* U+3057+3099 -> U+3058 */ 2267, + /* U+3059+3099 -> U+305A */ 2268, + /* U+305B+3099 -> U+305C */ 2269, + /* U+305D+3099 -> U+305E */ 2270, + /* U+305F+3099 -> U+3060 */ 2271, + /* U+3061+3099 -> U+3062 */ 2272, + /* U+3064+3099 -> U+3065 */ 2273, + /* U+3066+3099 -> U+3067 */ 2274, + /* U+3068+3099 -> U+3069 */ 2275, + /* U+306F+3099 -> U+3070 */ 2276, + /* U+306F+309A -> U+3071 */ 2277, + /* U+3072+3099 -> U+3073 */ 2278, + /* U+3072+309A -> U+3074 */ 2279, + /* U+3075+3099 -> U+3076 */ 2280, + /* U+3075+309A -> U+3077 */ 2281, + /* U+3078+3099 -> U+3079 */ 2282, + /* U+3078+309A -> U+307A */ 2283, + /* U+307B+3099 -> U+307C */ 2284, + /* U+307B+309A -> U+307D */ 2285, + /* U+309D+3099 -> U+309E */ 2291, + /* U+30A6+3099 -> U+30F4 */ 2318, + /* U+30AB+3099 -> U+30AC */ 2293, + /* U+30AD+3099 -> U+30AE */ 2294, + /* U+30AF+3099 -> U+30B0 */ 2295, + /* U+30B1+3099 -> U+30B2 */ 2296, + /* U+30B3+3099 -> U+30B4 */ 2297, + /* U+30B5+3099 -> U+30B6 */ 2298, + /* U+30B7+3099 -> U+30B8 */ 2299, + /* U+30B9+3099 -> U+30BA */ 2300, + /* U+30BB+3099 -> U+30BC */ 2301, + /* U+30BD+3099 -> U+30BE */ 2302, + /* U+30BF+3099 -> U+30C0 */ 2303, + /* U+30C1+3099 -> U+30C2 */ 2304, + /* U+30C4+3099 -> U+30C5 */ 2305, + /* U+30C6+3099 -> U+30C7 */ 2306, + /* U+30C8+3099 -> U+30C9 */ 2307, + /* U+30CF+3099 -> U+30D0 */ 2308, + /* U+30CF+309A -> U+30D1 */ 2309, + /* U+30D2+3099 -> U+30D3 */ 2310, + /* U+30D2+309A -> U+30D4 */ 2311, + /* U+30D5+3099 -> U+30D6 */ 2312, + /* U+30D5+309A -> U+30D7 */ 2313, + /* U+30D8+3099 -> U+30D9 */ 2314, + /* U+30D8+309A -> U+30DA */ 2315, + /* U+30DB+3099 -> U+30DC */ 2316, + /* U+30DB+309A -> U+30DD */ 2317, + /* U+30EF+3099 -> U+30F7 */ 2319, + /* U+30F0+3099 -> U+30F8 */ 2320, + /* U+30F1+3099 -> U+30F9 */ 2321, + /* U+30F2+3099 -> U+30FA */ 2322, + /* U+30FD+3099 -> U+30FE */ 2323, + /* U+11099+110BA -> U+1109A */ 4588, + /* U+1109B+110BA -> U+1109C */ 4589, + /* U+110A5+110BA -> U+110AB */ 4590, + /* U+11131+11127 -> U+1112E */ 4596, + /* U+11132+11127 -> U+1112F */ 4597, + /* U+11347+1133E -> U+1134B */ 4609, + /* U+11347+11357 -> U+1134C */ 4610, + /* U+114B9+114B0 -> U+114BC */ 4628, + /* U+114B9+114BA -> U+114BB */ 4627, + /* U+114B9+114BD -> U+114BE */ 4629, + /* U+115B8+115AF -> U+115BA */ 4632, + /* U+115B9+115AF -> U+115BB */ 4633, + /* U+11935+11930 -> U+11938 */ 4642 +}; + +/* Perfect hash function for recomposition */ +static int +Recomp_hash_func(const void *key) +{ + static const int16 h[1883] = { + 772, 773, 621, 32767, 32767, 387, 653, 196, + 32767, 32767, 855, 463, -19, 651, 32767, 32767, + 32767, 364, 32767, 32767, -108, 32767, 32767, 32767, + 32767, 0, -568, 32767, 32767, 32767, 0, 0, + 0, -103, 364, 0, 210, 732, 0, 0, + -506, 0, 0, 0, 32767, 32767, 0, 32767, + 407, -140, 32767, 409, 32767, 772, 0, 86, + 842, 934, 32767, 32767, -499, -355, 32767, 32767, + 532, 138, 174, -243, 860, 1870, 742, 32767, + 32767, 339, 32767, 1290, 0, 32767, 32767, 0, + -449, -1386, 1633, 560, 561, 32767, 1219, 1004, + 139, -804, 32767, -179, 141, 579, 1586, 32767, + 32767, 32767, 142, 199, 32767, 32767, 143, 0, + 32767, 32767, 314, 896, 32767, 32767, 428, 129, + 286, -58, 0, 68, 32767, 0, 244, -566, + 32767, 32767, 32767, 246, 32767, 32767, 0, 32767, + 32767, 271, -108, 928, 32767, 715, 32767, 32767, + -211, -497, 32767, 0, 1055, 1339, 32767, 0, + 32767, 32767, -968, -144, 32767, 32767, 248, 32767, + -161, 32767, 32767, 282, 32767, -372, 0, 2, + -137, 1116, 32767, 687, 32767, 459, 913, 0, + 461, 879, -816, 443, 32767, 32767, 462, 1089, + 32767, 1054, 0, 314, 447, -26, 480, 32767, + 64, 0, 0, 112, 32767, 66, 0, 646, + 603, 22, -292, 0, 710, 475, 32767, 24, + -781, 32767, 32767, 32767, 281, 307, 32767, 1289, + 32767, 0, 1064, -149, 454, 118, 32767, 32767, + 0, 32767, -126, 0, 32767, 32767, 858, 32767, + 32767, 32767, 1029, 886, 665, 209, 0, 26, + 359, 0, 0, -108, -508, -603, 894, 906, + 32767, 32767, 14, 0, 0, 534, 984, 876, + 32767, -93, 110, -367, 167, 843, 32767, 32767, + -947, -290, 169, 0, 0, 32767, -42, 564, + 0, -927, 32767, 817, 32767, 32767, 32767, 110, + 0, 32767, 32767, -38, 32767, 32767, -101, 694, + -142, 190, 191, 1288, 32767, -687, 194, -579, + 534, -452, 0, -72, 536, 765, 823, 266, + -259, 684, 767, 32767, 654, 32767, 32767, 64, + 920, 32767, 32767, 32767, 0, 1653, 0, 0, + 32767, 32767, -452, -222, 855, 0, 32767, -1153, + 127, 490, 449, 863, 32767, -144, 32767, -379, + 545, 32767, 32767, 32767, 530, 32767, 32767, 1331, + 611, -612, 332, 545, -73, 0, 604, 201, + 32767, -279, 338, 836, 340, 408, 32767, -60, + -358, 32767, 343, 69, 707, 0, -129, 582, + 32767, 0, 32767, 96, 392, 490, 639, 157, + -4, 406, 32767, 32767, -571, 1077, 546, 32767, + 551, 0, 0, 0, 32767, 32767, 348, 32767, + 498, -181, 0, -433, 1057, 260, 0, 32767, + 32767, 397, 32767, 816, -130, 32767, 624, 0, + 0, 32767, 32767, 32767, 485, 0, 32767, 32767, + 32767, 32767, 32767, 0, 32767, 32767, 32767, 1222, + -230, 32767, 797, -538, 32767, 974, 32767, 32767, + 831, 70, -658, 145, 0, 147, 0, 32767, + 1295, 32767, 0, 0, 895, 0, 0, -385, + 491, -287, 32767, -587, 32767, 32767, 32767, 813, + -471, -13, 32767, 32767, 32767, 0, 203, 411, + 470, 0, -546, -179, 146, 0, 0, 32767, + -468, 32767, 0, 0, 32767, 32767, 32767, 211, + 32767, 32767, 0, 32767, 0, 52, 32767, 0, + 32767, 0, 692, 990, 32767, 32767, 32767, 56, + -507, 784, 951, 0, 32767, 0, 697, 32767, + 187, 0, 32767, 32767, 430, 1209, 682, 32767, + 130, 0, -25, 0, -1006, 0, 32767, 214, + 433, 22, 0, -1119, 32767, 285, 32767, 32767, + 32767, 216, 32767, 32767, 32767, 217, 527, 32767, + 32767, 32767, 829, 485, 419, 717, 620, 731, + 32767, 470, 0, -145, -620, 1162, -644, 848, + 287, -632, 32767, 32767, 32767, 32767, 381, 32767, + 510, 511, -554, -2, 32767, 0, 0, 698, + 32767, 32767, 436, 1154, 32767, 463, 32767, 32767, + 627, 517, 32767, 32767, 854, 579, 723, 396, + 110, -42, 354, 32767, 664, 32767, 32767, 0, + 0, 32767, 65, -163, 67, 140, 69, 341, + 70, 71, 402, 73, 623, 544, 624, 417, + -1375, 648, 32767, -26, 904, 0, 548, 0, + 0, 32767, 32767, 855, 32767, 488, -524, 599, + 130, 131, 32767, 32767, 542, -1110, -324, -462, + 32767, -405, -440, 0, 0, 629, 850, 0, + 741, 257, 258, 32767, 32767, 0, 32767, 923, + 0, 32767, 0, 32767, 1559, 32767, 32767, 32767, + 671, 32767, 134, 32767, 32767, -336, -104, 576, + 577, 829, 32767, 32767, 762, 902, 32767, 0, + 32767, 0, 1506, 887, 32767, 636, 601, 2465, + 426, 0, 236, 317, 427, 968, 32767, -975, + -559, -343, 341, 32767, 937, 241, 0, 32767, + 32767, 547, 32767, 32767, 32767, 32767, 32767, 789, + 0, 32767, 32767, 32767, 0, 0, 0, 32767, + -192, 859, 1185, 1153, 69, 32767, 32767, 32767, + -539, 32767, 32767, 0, 32767, 32767, 32767, 32767, + 640, 578, 32767, 32767, -766, 32767, 32767, 32767, + 32767, 1050, -572, 32767, 32767, 32767, 32767, 1268, + 32767, 32767, 32767, 754, 32767, 32767, 1640, 179, + 804, 32767, 32767, 32767, 32767, 0, 684, 943, + 1006, 32767, 32767, 652, 0, 32767, 1041, 32767, + 718, 791, 32767, 274, 697, 32767, 32767, 0, + 32767, 32767, 32767, 0, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 735, + 0, 32767, 32767, 32767, 275, 358, 688, 32767, + 32767, 32767, 548, -87, 770, 32767, -42, 0, + 551, 32767, 691, 222, 32767, 32767, 32767, 32767, + 0, 1273, 403, -121, 806, 553, 554, 163, + 32767, 32767, 892, 825, 32767, 32767, -490, 32767, + 32767, 32767, 32767, 32767, -109, 744, 910, 32767, + 91, 32767, 32767, 0, 0, 32767, 32767, 32767, + 1521, 50, 701, 32767, 32767, 32767, 32767, 164, + 658, 32767, 288, 0, 32767, 0, 51, 0, + 32767, 32767, 32767, 32767, 555, 1547, 32767, 32767, + 595, 585, 429, 32767, -80, 32767, 1258, 0, + 540, 486, -434, 865, 0, 192, 0, 884, + 0, 0, 0, 175, 555, 0, 32767, 32767, + 0, 32767, -566, 866, 591, 32767, 32767, 32767, + 32767, 32767, 496, 495, -215, 32767, 849, -772, + 32767, 32767, 502, 178, 483, 32767, 912, 793, + 794, 0, 32767, 32767, 32767, -556, 499, 838, + 32767, 32767, -506, 331, 0, 0, -1096, 512, + 880, 0, 774, -338, 649, 32767, 270, 32767, + 32767, -624, 328, 459, 32767, 32767, 32767, 32767, + 329, -201, -835, 813, -879, 560, 0, -212, + -114, 35, -494, 37, 523, 653, 751, -653, + -743, 32767, 1356, 818, 32767, 32767, 856, 0, + 44, 902, 0, 0, 0, 0, 32767, -26, + 526, 795, 456, 32767, 104, -209, -341, 133, + -372, 0, 45, 110, 111, 0, 511, 47, + 114, 32767, 32767, 93, 48, 116, -1031, -279, + 32767, 192, 0, 32767, 453, 415, 0, -190, + 32767, 471, 240, 175, 29, 665, 684, 0, + -11, -95, -344, 32767, 245, 148, 0, 530, + 0, 1185, -615, -712, 693, 784, 32767, 0, + -776, 32767, 32767, -813, 0, 0, 0, 207, + 208, 32767, 674, 32767, 742, -289, 249, 32767, + 520, 929, -50, 781, 0, -778, 32767, 0, + 302, 32767, 720, -465, 0, 32767, 32767, 32767, + 0, 0, 32767, 833, 328, 806, 32767, -403, + 0, 32767, -77, 32767, 0, 441, 930, 32767, + 643, 0, 32767, 1938, 0, 1334, 381, 32767, + 216, 32767, 32767, 0, 32767, 484, 383, 0, + 242, 395, 0, 32767, 32767, 32767, -781, 355, + 356, 32767, 292, 706, 32767, 32767, 32767, 32767, + 32767, -410, 32767, 32767, 782, 32767, 189, 32767, + 32767, 943, 0, -212, 407, 335, 0, 135, + 32767, 616, 0, -497, 0, -67, 853, 32767, + 700, 32767, 0, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 459, -48, 32767, 58, 0, + -856, 1017, 32767, 59, 916, -731, 32767, 940, + -855, 347, 650, 0, 678, 32767, 0, 32767, + 32767, 530, 32767, 0, -80, 32767, -730, 32767, + 1214, 799, 58, 651, 841, 0, 0, -589, + -1530, -478, 651, 652, 93, 576, -1215, 32767, + 125, 32767, 1279, 32767, 32767, 0, 32767, 0, + -367, 416, -1236, 32767, 418, 32767, 815, 558, + 559, 781, 419, 32767, 739, 32767, 0, 32767, + 128, 570, 1349, -298, -66, 0, 147, -488, + 32767, 590, 189, 274, 524, 32767, 1082, -209, + 32767, 423, 32767, 32767, 975, 573, 32767, 424, + 32767, 32767, 1241, 32767, 32767, 32767, 32767, 32767, + 612, 391, 32767, 0, -803, 1004, -561, 32767, + 32767, 735, 870, 32767, 0, 32767, 32767, -123, + 99, 210, 600, 1294, 109, 1053, 32767, 307, + 834, 32767, 0, 1651, 32767, 644, 32767, 32767, + 0, 32767, -801, 385, 379, 32767, -368, 32767, + 32767, 830, 0, 32767, 32767, 739, 371, 372, + -275, 32767, 32767, 331, -780, 32767, 0, 1229, + -1462, 913, 266, 827, 125, 32767, 32767, 32767, + 393, 32767, 631, -33, -883, -661, -204, 6, + -19, 257, 8, 9, 118, 519, 615, -541, + -893, 0, 32767, 0, 1156, 15, 900, 32767, + 32767, 32767, 32767, 32767, 32767, 1022, 376, 0, + 32767, 32767, -972, 676, 840, -661, 631, 58, + 0, 17, 32767, 0, -799, 82, 0, 32767, + 32767, 680, 32767, 905, 0, 0, 32767, 32767, + 0, 0, 32767, 0, 828, 386, 802, 0, + 146, 0, 148, 32767, -1146, 0, 150, 151, + -743, 153, 154, 32767, 32767, 442, 32767, 743, + 0, 0, 746, 0, 32767, 32767, 32767, 98, + 32767, 157, 0, 696, 0, 32767, 32767, -294, + 32767, 158, 159, 32767, 0, 32767, 160, 32767, + 933, 32767, 32767, -50, 759, 824, 162, 672, + 32767, 356, 0, 356, 32767, 32767, 0, 0, + 656, 692, 253, 254, -374, 102, 256, 32767, + 0, 0, 32767, 32767, 259, 32767, 63, 260, + 510, 261, 32767, 0, 32767, 1061, 32767, 521, + 32767, 32767, 32767, 32767, 32767, 32767, 316, 317, + 846, 0, 32767, -500, 318, 0, 32767, 32767, + 263, 0, 790, 872, 32767, 32767, 32767, 2171, + 264, 32767, 32767, 32767, 32767, 486, 334, 465, + 32767, 466, 32767, 444, 606, 32767, 0, 445, + 320, -317, 0, 520, 322, 718, 32767, 32767, + 32767, 0, 1013, 32767, 32767, 32767, 32767, 32767, + 32767, 611, 32767, 0, 0, 32767, 32767, -120, + 156, 613, 0, 0, 32767, -68, 32767, 622, + 32767, 32767, 32767, 32767, 32767, 455, 32767, 32767, + 32767, 403, 533, 0, -161, 405, 95, 96, + 32767, 97, 32767, 0, 29, 0, 32767, 32767, + 30, 32767, 99, 32767, 32767, 0, 161, 32767, + 97, 0, 32, 32767, 32767, 0, 0, 315, + 32767, 32767, 414, 966, 0, 585, 32767, 32767, + -616, -256, 171, 172, 666, 101, 562, 563, + 32767, 95, 0, 0, 1492, 390, -251, 103, + 32767, 0, 32767, 188, 1487, 32767, 0, 0, + 586, 668, -126, 0, 0, 32767, 32767, 204, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 656, 32767, 32767, + 599, 0, 222, 32767, 0, 1368, -412, 435, + 32767, 936, 32767, -17, 32767, 832, 32767, 437, + 0, -518, 787, 32767, 864, -449, 0, 636, + 713, 206, 592, 572, 0, 483, -139, 32767, + 32767, 180, 818, 32767, 32767, 1304, 0, 32767, + 274, 0, 0, 0, 0, 705, 32767, 32767, + 32767, 0, -272, 0, 502, 503, 319, 0, + 32767, 0, 13, 32767, 32767, 0, 32767, 270, + 737, 0, 32767, 32767, 32767, 901, 32767, 616, + 180, 32767, 721, 353, 32767, 0, 32767, 32767, + -199, 0, 280, 788, 32767, 940, 32767, 51, + 0, 400, 53, 0, 54, -637, 0, -453, + 0, 0, 0, 380, 0, 32767, 504, 0, + 2049, 0, -964, 32767, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 798, 32767, 32767, 32767, 0, + 538, 488, 0, 32767, -528, 57, 819, 32767, + 32767, 1244, 0, 488, 739, 908, 32767, 32767, + 0, 32767, 32767, 0, 55, 533, 0, 32767, + 814, 0, 32767, 458, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 776, 777, 920, 0, + 0, 755, 32767, 0, 32767, 32767, 0, 32767, + 55, -954, 0, 372, 166, 218, 165, 857, + 221, 675, 0, 223, 224, -155, 226, 32767, + 1851, 227, 32767, 32767, 1192, 0, 229, 0, + -72, 0, 865, 0, 0, -330, 0, 683, + 32767, -550, -196, 725, -573, 293, 102, 32767, + -589, 296, 297, 298, 231, -256, 300, 32767, + 32767, 301, 233, 868, 32767, 234, 0, 811, + 1187, 32767, 32767, 0, 32767, 518, 0, 361, + 362, 466, 0, 365, 32767, -179, 366, 367, + 874, 369, 305, 0, 32767, 0, 32767, 0, + 32767, 2000, 1215, 451, 652, 0, 0, 799, + 32767, 32767, 32767 + }; + + const unsigned char *k = (const unsigned char *) key; + size_t keylen = 8; + uint32 a = 0; + uint32 b = 0; + + while (keylen--) + { + unsigned char c = *k++; + + a = a * 257 + c; + b = b * 17 + c; + } + return h[a % 1883] + h[b % 1883]; +} + +/* Hash lookup information for recomposition */ +static const pg_unicode_recompinfo UnicodeRecompInfo = +{ + RecompInverseLookup, + Recomp_hash_func, + 941 +}; diff --git a/src/include/common/unicode_normprops_table.h b/src/include/common/unicode_normprops_table.h index 93a2e55b7583..8c310f10d79c 100644 --- a/src/include/common/unicode_normprops_table.h +++ b/src/include/common/unicode_normprops_table.h @@ -3,7 +3,8 @@ #include "common/unicode_norm.h" /* - * We use a bit field here to save space. + * Normalization quick check entry for codepoint. We use a bit field + * here to save space. */ typedef struct { @@ -11,6 +12,17 @@ typedef struct signed int quickcheck:4; /* really UnicodeNormalizationQC */ } pg_unicode_normprops; +/* Typedef for hash function on quick check table */ +typedef int (*qc_hash_func) (const void *key); + +/* Information for quick check lookup with perfect hash function */ +typedef struct +{ + const pg_unicode_normprops *normprops; + qc_hash_func hash; + int num_normprops; +} pg_unicode_norminfo; + static const pg_unicode_normprops UnicodeNormProps_NFC_QC[] = { {0x0300, UNICODE_NORM_QC_MAYBE}, {0x0301, UNICODE_NORM_QC_MAYBE}, @@ -1245,6 +1257,343 @@ static const pg_unicode_normprops UnicodeNormProps_NFC_QC[] = { {0x2FA1D, UNICODE_NORM_QC_NO}, }; +/* Perfect hash function for NFC_QC */ +static int +NFC_QC_hash_func(const void *key) +{ + static const int16 h[2463] = { + 0, -2717, 0, 221, 1293, 223, 1295, 225, + 226, 241, 0, 229, 230, 231, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + -386, 0, 0, 0, 0, 0, 0, 0, + -163, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + -246, -175, 1260, 0, 0, 0, -174, -173, + 0, -172, 0, 0, 0, 0, 0, 0, + 1049, 0, 300, 301, 1071, 0, 1071, 0, + 1071, 1071, 1057, 0, 0, 0, 0, 1061, + 0, -1053, 1664, 0, 2956, 0, 0, -13, + 0, 0, 0, 0, 2156, 0, 0, 0, + 0, 0, 0, 0, 71, 0, 1082, 0, + 1083, 1083, 0, 1084, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 359, 360, 361, + -1091, 363, -762, -130, -129, -128, -127, -126, + 137, -124, -708, -707, -706, -120, -185, -705, + -117, -184, -1307, -114, -113, -112, -111, 0, + 386, 387, 388, 389, -90, 391, 171, 172, + 394, -94, -183, 397, 398, 399, -98, -225, + 402, -1019, -636, -1019, -225, 407, 408, 409, + 410, 411, 674, 413, -171, -170, -169, 417, + 352, -168, 420, 353, -770, 423, 424, 425, + 426, 427, 428, 32767, 239, 239, 239, 239, + 239, 239, 239, 239, 239, 239, 239, 239, + 239, 239, 32767, 32767, 237, 32767, 236, 32767, + 32767, 234, 234, 234, 234, 617, 234, 234, + 234, -2483, 234, -1430, 1526, -1430, 1527, 47, + 48, 471, 230, 32767, 32767, 32767, 227, 227, + 227, 227, 227, 227, 227, 227, 227, 227, + 227, 227, 227, 227, 227, 227, 227, 227, + -159, 227, 227, 227, 227, 227, 227, 227, + 64, 227, 227, 227, 227, 227, 227, 227, + 227, 227, 227, 227, 227, 227, 227, 227, + 227, 227, 227, 227, 227, 227, 227, 227, + -19, 52, 1487, 227, 227, 227, 53, 54, + 227, 55, 227, 227, 227, 227, 227, 227, + 1276, 227, -989, 32767, 1296, 225, 1296, 225, + 1296, 1296, 1282, 225, 225, 225, 225, 1286, + 225, -828, 1889, 225, 3181, 225, 225, 212, + 225, 225, 225, 225, 2381, 225, 225, 225, + 225, 225, 225, 225, 296, 225, 1307, 225, + 1308, 1308, 225, 1309, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 584, 585, 586, + -866, 588, -537, 95, 96, 97, 98, 99, + 362, 101, -483, -482, -481, 105, 40, -480, + 108, 41, -1082, 111, 112, 113, 114, 225, + 611, 612, 613, 614, 135, 616, 396, 397, + 619, 131, 42, 622, 623, 624, 127, 0, + 627, -794, -411, -794, 0, 632, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + -272, 32767, 32767, 32767, 0, 32767, 32767, 32767, + 32767, 32767, -166, -165, 32767, 32767, 32767, 32767, + -164, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 397, 32767, 396, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 386, + 0, 386, 386, 386, 386, 386, 386, 386, + 223, 386, 386, 386, 32767, 385, 385, 385, + 385, 385, 32767, 384, 32767, 383, 383, 32767, + 382, 382, 32767, 381, 381, 381, 381, 381, + 135, 206, 1641, 381, 32767, 32767, 32767, 32767, + 32767, 32767, -160, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 1148, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, + 32767, 32767, 32767, 0, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, -257, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, -910, -910, 32767, 32767, + 0, 32767, 0, 32767, 0, 32767, 0, 32767, + 147, 32767, 0, 32767, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 143, 32767, 144, 32767, 145, + 32767, 146, 32767, 0, 32767, 148, 32767, 149, + 32767, 32767, 32767, -160, 32767, 32767, 32767, 32767, + 32767, 32767, 15, 32767, 32767, 0, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 145, 32767, 144, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, -148, 32767, 32767, 32767, 32767, + 32767, 32767, 2009, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 32767, 32767, 135, -918, 32767, + 151, 32767, 32767, 0, 1, 2, 3, 4, + 133, 5, 6, 7, 8, 9, 10, 11, + 32767, 32767, -1248, 32767, 13, 154, 188, 188, + 32767, 32767, 32767, 32767, 32767, 155, 16, 32767, + 32767, 32767, 32767, 32767, 32767, -1853, -1054, 18, + -1052, -1051, -1036, 22, 32767, 157, 32767, 28, + 23, 1077, 673, 25, -2930, 0, 32767, 32767, + 32767, 32767, 32767, 27, 32767, 155, 32767, 154, + 32767, 32767, -62, 28, -42, 30, -1051, 32, + -1050, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 34, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 129, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 672, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 0, 32767, + 32767, 32767, 32767, 32767, -156, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, -155, 32767, 32767, + 32767, 0, 0, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 73, 32767, 32767, 32767, 32767, 74, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 675, + 32767, 32767, 32767, 32767, 32767, 75, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 165, 32767, 32767, 32767, 166, 167, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 170, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 689, 690, 691, 692, 693, 694, 695, + 696, 697, 698, 699, 700, 701, 702, 703, + 704, 705, 706, 707, 708, 709, 710, 711, + 712, 713, 714, 715, 716, 717, 718, 719, + 720, 721, 722, -304, -303, -302, -301, -300, + -299, -298, -297, 930, -295, -294, -293, -292, + -291, -290, -289, -288, -287, -286, -285, -284, + -283, -282, -281, -280, -279, -278, -277, -276, + -275, 753, 754, 755, 646, 757, -712, -1765, + 952, -712, 2244, -712, 2245, 765, 766, 767, + 768, 125, 770, 771, 772, 773, 774, 775, + 603, 777, 778, 779, 780, 781, 782, 783, + 784, 2011, 786, 787, 788, 789, 790, 791, + 792, 793, 794, 795, 796, 797, 798, 799, + 800, 801, 802, 803, 804, 805, 806, 603, + 603, 809, 603, 811, 603, 603, 814, 815, + 816, 817, 435, 819, 820, 821, 3539, 823, + 603, -468, 603, -468, 603, 603, 589, 831, + 603, 603, 603, 835, 836, 837, 838, 839, + 840, 841, 842, 843, 844, 845, 846, 847, + 848, 849, 850, 851, 852, 1239, 854, 855, + 856, 857, 858, 859, 860, 1024, 862, 863, + 864, 865, 866, 867, 868, 869, 870, 871, + 872, 873, 874, 875, 876, 877, 878, 879, + 880, 881, 882, 883, 884, 1131, 1061, -373, + 888, 889, 890, 1065, 1065, 893, 1066, 895, + 896, 897, 898, 899, 900, -148, 902, 603, + 603, -166, 906, -164, 908, -162, -161, -146, + 912, 913, 914, 915, -145, 917, 1971, -745, + 920, -2035, 922, 923, 937, 925, 926, 927, + 928, -1227, 930, 931, 932, 933, 934, 935, + 936, 866, 938, -143, 940, -142, -141, 943, + -140, 32767, 945, 946, 947, 948, 949, 950, + 951, 952, 953, 954, 955, 956, 957, 958, + 959, 960, 961, -65, -64, -63, -62, -61, + -60, -59, -58, 1169, -56, -55, -54, -53, + -52, -51, -50, -49, -48, -47, -46, -45, + -44, -43, -42, -41, -40, -39, -38, -37, + -36, 992, 993, 994, 885, 996, -473, -1526, + 1191, -473, 2483, -473, 2484, 1004, 1005, 1006, + 1007, 364, 1009, 1010, 1011, 1012, 1013, 1014, + 842, 1016, 1017, 1018, 1019, 1020, 1021, 1022, + 1023, 2250, 1025, 1026, 1027, 1028, 1029, 1030, + 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, + 1039, 1040, 1041, 1042, 1043, 1044, 1045, 842, + 842, 1048, 842, 1050, 842, 842, 1053, 1054, + 1055, 1056, 674, 1058, 1059, 1060, 3778, 1062, + 842, -229, 842, -229, 842, 842, 828, 1070, + 842, 842, 842, 1074, 1075, 1076, 1077, 1078, + 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, + 1087, 1088, 1089, 1090, 1091, 1478, 1093, 1094, + 1095, 1096, 1097, 1098, 1099, 1263, 1101, 1102, + 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, + 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, + 1119, 1120, 1121, 1122, 1123, 1370, 1300, -134, + 1127, 1128, 1129, 1304, 1304, 1132, 1305, 1134, + 1135, 1136, 1137, 1138, 1139, 91, 1141, 842, + 842, 73, 1145, 75, 1147, 77, 78, 93, + 1151, 1152, 1153, 1154, 94, 1156, 2210, -506, + 1159, -1796, 1161, 1162, 1176, 1164, 1165, 1166, + 1167, -988, 1169, 1170, 1171, 1172, 1173, 1174, + 1175, 1105, 1177, 96, 1179, 97, 98, 1182, + 99, 1184, 1185, 1186, 1187, 1188, 1189, 1190, + 1191, 1192, 1193, 1194, 1195, 1196, 1197, 1198, + 1199, 1200, 0, 174, 175, 176, 177, 178, + 179, 180, 181, 1408, 183, 184, 185, 186, + 187, 188, 189, 190, 191, 192, 193, 194, + 195, 196, 197, 198, 199, 200, 201, 202, + 203, 0, 0, 206, 0, 208, 0, 0, + 211, 212, 213, 214, -168, 216, 217, 218, + 2936, 220, 0, -1071, 0, -1071, 0, 0, + -14, 228, 0, 0, 0, 232, 233, 234, + 235, 236, 237, 238, 239, 240, 241, 242, + 243, 244, 245, 246, 247, 248, 249, 636, + 251, 252, 253, 254, 255, 256, 257, 421, + 259, 260, 261, 262, 263, 264, 265, 266, + 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 528, + 458, -976, 285, 286, 287, 462, 462, 290, + 463, 292, 293, 294, 295, 296, 297, -751, + 299, 0, 0, -769, 303, -767, 305, -765, + -764, -749, 309, 310, 311, 312, -748, 314, + 1368, -1348, 317, -2638, 319, 320, 334, 322, + 323, 324, 325, -1830, 327, 328, 329, 330, + 331, 332, 333, 263, 335, -746, 337, -745, + -744, 340, -743, 342, 343, 344, 345, 346, + 347, 348, 349, 350, 351, 352, 353, 354, + 355, 356, 357, 358, 0, 0, 0, 1453, + 0, 1126, 495, 495, 495, 495, 495, 233, + 495, 1080, 1080, 1080, 495, 561, 1082, 495, + 563, 1687, 495, 495, 495, 495, 385, 0, + 0, 0, 0, 480, 0, 221, 221, 0, + 489, 579, 0, 0, 0, 498, 626, 0, + 1422, 1040, 1424, 631, 0, 0, 0, 0, + 0, -262, 0, 585, 585, 585, 0, 66, + 587, 0, 68, 1192, 0, 0, 0, 0, + 0, 0, 32767, 32767, 32767, 32767, 669, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 670, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 142, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 133, 134, 135, 136, + 137, 138, 139, 140, 141, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1027, 1027, 1027, + 1027, 1027, 1027, 1027, 1027, -199, 1027, 1027, + 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, + 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, + 1027, 1027, 1027, 0, 0, 0, 110, 0, + 1470, 2524, -192, 1473, -1482, 1475, -1481, 0, + 0, 0, 0, 644, 0, 0, 0, 0, + 0, 0, 173, 0, 0, 0, 0, 0, + 0, 0, 0, -1226, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 204, 205, 0, 207, 0, 209, 210, + 0, 0, 0, 0, 383, 0, 0 + }; + + const unsigned char *k = (const unsigned char *) key; + size_t keylen = 4; + uint32 a = 0; + uint32 b = 0; + + while (keylen--) + { + unsigned char c = *k++; + + a = a * 257 + c; + b = b * 17 + c; + } + return h[a % 2463] + h[b % 2463]; +} + +/* Hash lookup information for NFC_QC */ +static const pg_unicode_norminfo UnicodeNormInfo_NFC_QC = { + UnicodeNormProps_NFC_QC, + NFC_QC_hash_func, + 1231 +}; + static const pg_unicode_normprops UnicodeNormProps_NFKC_QC[] = { {0x00A0, UNICODE_NORM_QC_NO}, {0x00A8, UNICODE_NORM_QC_NO}, @@ -6165,3 +6514,1262 @@ static const pg_unicode_normprops UnicodeNormProps_NFKC_QC[] = { {0x2FA1C, UNICODE_NORM_QC_NO}, {0x2FA1D, UNICODE_NORM_QC_NO}, }; + +/* Perfect hash function for NFKC_QC */ +static int +NFKC_QC_hash_func(const void *key) +{ + static const int16 h[9837] = { + -2472, -2472, -2472, -2472, -2472, -2472, -2472, -2472, + -2472, -2472, -2472, -2472, -2472, -2472, -2472, -2472, + -2472, -2472, -2472, -2472, -2472, -2472, -2472, -2472, + -2472, -2472, -2472, -2472, -2472, 32767, 32767, 32767, + -2475, -2475, -2475, -2475, -2475, -2475, -2475, -2475, + -2475, -2475, -2475, -2475, -2475, -2475, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 865, 865, 865, 865, 865, 865, 865, + 865, 865, 865, 865, -2255, 32767, -5207, 32767, + -5207, 860, 860, 860, 860, 860, 860, 860, + 860, 860, 4250, 861, 861, 861, 3339, 3339, + 3339, 3339, 3339, 3339, 3339, 3339, 3339, 3339, + 3339, 3339, 3339, 3339, 3339, 3339, 3339, 3339, + 32767, 3338, 3338, 3338, 3338, 3338, 3338, 3338, + 3338, 3338, 3338, 3338, 3338, 3338, 3338, 3338, + 3338, 3338, 3338, 3338, 3338, 3338, 3338, 3338, + 3338, 3338, 3338, 3338, 3338, 3338, 3338, 3338, + 3338, 9, 10, 32767, 11, 12, 0, 32767, + 0, 2913, 2914, 2915, 2916, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 2917, 32767, 2918, -100, + 2919, 2920, 2921, 840, 840, 840, 2922, 0, + 0, 0, 0, 0, 2206, 0, 2923, 0, + 2924, 2925, 2926, 0, 0, 0, -2590, 0, + 0, 0, 0, 0, 0, 0, 2934, 0, + 2474, 2931, 2932, 0, 0, 0, 0, 0, + 14, 805, 0, 0, 2933, 0, 2934, 0, + 2935, 2936, 0, 0, 0, 16, 17, 0, + 0, 0, 0, 0, 0, 0, 0, 18, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, -790, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, -1675, 0, 0, 19, 0, -1679, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, -1694, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 29, 30, 31, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 724, 2668, 724, 4350, -2633, -2633, + 2533, 2534, 2535, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 2518, 2519, 2520, 1431, 45, 46, + 32767, 32767, 47, 48, 49, 50, 51, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, -3011, 53, -1125, -3010, -3010, + 32767, -3334, -1123, -3011, 60, 61, 62, 63, + 32767, 32767, 64, 32767, 65, 32767, 66, 67, + 32767, 32767, 32767, 32767, 32767, 32767, 2268, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 69, 70, + 71, 72, 73, 74, 32767, 32767, 32767, 32767, + 75, 76, 32767, 77, 281, 32767, 32767, 32767, + 32767, 32767, 32767, 811, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 1341, 1342, 1343, 1344, 1345, + 1346, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 86, + 32767, 32767, 32767, 32767, 32767, 4550, 32767, 32767, + 32767, 1135, 32767, 32767, 32767, 32767, 32767, 1130, + 3016, 32767, 3017, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 677, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 2858, 2859, 651, 2861, -438, + 2863, 2864, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, -5305, -5305, -5305, 32767, -5306, + -5306, 32767, 32767, 32767, 2871, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 3022, 3023, 680, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, -272, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 4308, 4309, 4310, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 4311, 4312, 4313, + 4314, 4315, 4316, 4317, 4318, 4319, 4320, 4321, + 4322, 4323, 4324, 4325, 4326, 4307, 4307, 4307, + 4307, 4307, 4307, 4307, 4307, 4307, 4336, 4337, + 4338, 4339, 4340, 4341, 4342, 4343, 4344, 4345, + 4346, 4347, 4348, 4349, 4350, 4351, 4352, 4353, + 4354, 32767, 32767, 32767, 32767, 4355, 4356, 4357, + 4358, 4359, 4360, 4361, 4362, 4363, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 4364, 4365, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2202, 0, 0, 0, 59, 0, + 0, 35, 0, 0, 0, 3549, 0, 0, + 0, 0, 0, 3394, 0, 0, 3399, 0, + 0, 0, 0, 0, 0, 0, 0, 2012, + 0, 0, 0, 0, 87, 2022, 0, 7490, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 2255, 0, 2256, 2256, 2256, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 32767, 0, 0, + 0, 0, 0, 0, -1759, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4767, 0, 0, 4772, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 32767, 5977, 0, + 892, 32767, 0, 32767, 32767, 0, 0, 32767, + 32767, 2344, 4834, 4835, 4836, 32767, 0, 4840, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 32767, 0, 32767, 0, 0, 0, + 0, 0, 0, 0, 32767, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 32767, 32767, 0, 32767, 0, 0, 0, 32767, + 32767, 32767, 32767, 3261, 3262, 32767, 3007, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 106, 107, 108, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 109, 110, 111, 112, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 0, 0, -2344, + -2344, 0, 32767, 0, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, -1642, 1469, -1641, 1469, -1640, 1469, + 1469, 1457, 1469, 1469, 1469, -4254, -4254, -4254, + -4254, -4254, -4254, -4254, -4254, -4254, -4254, -4254, + -4254, -4254, -4254, -4254, -4254, -4254, -3359, -4254, + -4254, -4254, -4254, -4254, -4254, -4254, -4254, -4254, + -4254, -4254, -4254, -4254, -4254, -4254, -4254, -4254, + -4254, -4254, -4254, -4254, -4254, -4254, -4254, -4254, + -4254, -4254, -4254, -4254, -4254, -4254, -4254, -4254, + -4254, -4254, -4254, -4254, -4254, -4254, -4254, -4103, + -1478, 0, -4254, -4254, -4254, -4254, -4254, -4254, + -4254, -4254, -4254, -2433, -4254, -4254, -4254, -3658, + -4254, -4254, -4254, -4254, -4254, -4254, -4254, -4254, + -4254, -4254, 0, -4253, -4253, -4253, -4253, -4253, + -4253, -4253, -4253, -4253, -678, -677, -676, -675, + -674, -673, -672, -4253, 314, -4253, -4253, -4253, + -4253, -4253, -4253, -4253, -4253, -4253, -4253, -4253, + -4253, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 1464, 1465, 1466, 1467, + 1468, 1469, 0, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, + 0, 0, 0, 0, 32767, 32767, 32767, 32767, + 32767, 0, 32767, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 827, 828, 829, -2469, -2469, -260, 0, + 0, 32767, 0, 32767, 0, 0, 32767, 0, + 0, 32767, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 3575, 3576, 3577, 3578, 3579, 3580, 3581, 0, + 4567, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 2201, 4411, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, -3338, 0, 0, 0, + 0, 0, 0, 0, -3337, 0, -3336, 0, + 0, 0, 0, -3335, 0, 0, -3334, -3333, + -3332, -3331, 0, 0, -3330, 0, 0, 32767, + 0, 0, 13, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 3073, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + -2556, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 3074, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 2355, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, -488, -488, -488, -302, -3067, -3067, + -3067, -3067, -488, -488, -488, -488, 2999, -488, + 2999, -488, -488, -488, -3067, -3067, -3067, -488, + -488, -3067, -3067, -3067, -488, -488, -488, 2463, + -488, -488, -488, -301, 2465, -488, 2466, 2467, + -3600, -493, -3599, -488, -3598, -488, -3597, -488, + -488, -500, -488, -488, -488, -488, -488, 2470, + 2471, 2472, -488, -488, -254, -488, -488, -488, + -488, -488, -104, -488, -488, -488, -102, -101, + -100, -99, -98, -97, -96, -95, -94, -93, + -92, -488, -488, -488, -488, -488, -488, -488, + -488, -488, -2194, -2194, -2194, -2194, -2194, -2194, + -2194, -2194, -2194, -2194, 5211, 3269, 5213, 3269, + 6895, -88, -88, 5078, 5079, 5080, 1773, -92, + -92, 1773, 1773, 1773, 1773, 1773, 1773, 5072, + 5073, 2865, 5075, 1776, 5077, 5078, 1778, 1778, + 6942, 6943, 1778, 1778, 1778, 5086, 6952, 6953, + 5089, 5090, 5091, 5092, 5093, 5094, 5095, 5096, + 4007, 5098, 2333, 2334, 2335, 2336, 2337, -3066, + -3066, -3066, 2341, -3066, -3066, 2344, 2345, 2346, + 5114, 317, 2349, 848, 849, 850, 2353, 852, + 853, 854, 855, 856, 857, 858, 859, 860, + 861, 692, 692, 692, 692, 692, 692, 692, + 692, 692, 692, 692, 692, 692, 692, 692, + 692, 692, 692, 692, 692, 692, 692, 692, + 692, 692, 692, 692, 692, 692, 692, 692, + 692, 692, 692, 692, 692, 692, 692, 692, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 3093, 3094, 3095, 3096, 3097, 3098, 3099, + 3100, 3101, 3102, 901, 3104, 3105, 3106, 3048, + 3108, 3109, 3075, 3111, 3112, 3113, -435, 3115, + 3116, 3117, 3118, 3119, -274, 3121, 3122, -276, + 3124, 3125, 3126, 3127, 3128, 3129, 3130, 3131, + 1120, 3133, 3134, 3135, 3136, 3050, 1116, 3139, + -4350, 3141, 3142, 3143, 3144, 3145, 3146, 3147, + 3148, 3149, 3150, 3151, 3152, 3153, 3154, 3155, + 3156, 902, 3158, 903, 904, 905, 3162, 3163, + 3164, 3165, 3166, 3167, 3168, 3169, 3170, 3171, + 3172, 3173, 3174, 3175, 3176, 3177, 32767, 3178, + 3179, 3180, 3181, 3182, 3183, 4943, 3185, 3186, + 3187, 3188, 3189, 3190, 3191, 3192, 3193, 3194, + 3195, 3196, 3197, 3198, 3199, 3200, 3201, 3202, + 3203, 3204, 3205, 3206, 3207, 3208, 3209, 3210, + 3211, 3212, 3213, 3214, 3215, 3216, 3217, 3218, + 3219, 3220, 3221, 3222, 3223, -1543, 3225, 3226, + -1545, 3228, 3229, 3230, 3231, 3232, 3233, 3234, + 3235, 3236, 3237, 3238, 3239, 3240, 3241, 3242, + 3243, 3244, 3245, 3246, 3247, 3248, -1251, -2728, + 3250, 32767, 32767, 3251, 906, 907, 3252, 3253, + 32767, 32767, 910, -1579, -1579, -1579, 32767, 3258, + -1581, 3260, 3261, 3262, 3263, 3264, 3265, 3266, + 3267, 3268, 3269, 32767, 3270, 32767, 3271, 3272, + 3273, 3274, 3275, 3276, 3277, 32767, 3278, 3279, + 3280, 3281, 3282, 3283, 3284, 3285, 3286, 3287, + 3288, 3289, 3290, 3291, 3292, 3293, 3294, 3295, + 3296, 3297, 3298, 3299, 3300, 3301, 3302, 3303, + 3304, 3305, 3306, 3307, 3308, 3309, 3310, 3311, + 3312, 3313, 3314, 3315, 3316, 3317, 3318, 3319, + 3320, 3321, 3322, 3323, 3324, 3325, 3326, 3327, + 3328, 3329, 3330, 3331, 3332, 3333, 3334, 3335, + 3336, 32767, 3337, 3338, 3339, 3340, 3341, 3342, + 0, 3343, 3344, 3345, 3346, 32767, 32767, 3347, + 3348, 3349, 3350, 3351, 3352, 3353, 3354, 32767, + 3355, 3356, 3357, 3358, 3359, 3360, 3361, 32767, + 3362, 3363, 3364, 3365, 3366, 3367, 3368, 3369, + 3370, 3371, 3372, 3373, 3374, 3375, 3376, 3377, + 3378, 3379, 3380, 3381, 3382, 3383, 3384, 3385, + 3386, 3387, 3388, 3389, 0, 3390, 3391, 3392, + 915, 916, 917, 918, 919, 920, 921, 922, + 923, 924, 925, 926, 927, 928, 929, 930, + 931, 932, 933, 934, 935, 936, 937, 938, + 939, 940, 941, 942, 943, 944, 945, 946, + 947, 948, 949, 950, 951, 952, 953, 954, + 955, 956, 957, 958, 959, 960, 961, 962, + 963, 964, 965, 966, 967, 968, 969, 970, + 971, 972, 973, 974, 975, 976, 3449, 3450, + 3451, 3452, 3453, 3454, 3455, 3456, 3457, 3458, + 3459, 3460, 3461, 3462, 3463, 3464, 3465, 3466, + 3467, 3468, 3469, 3470, 3471, 3472, 3473, 3474, + 3475, 3476, 3477, 3478, 3479, 3480, 3481, 3482, + 3483, 3484, 3485, 3486, 3487, 3488, 3489, 3490, + 3491, 3492, 3493, 3494, 3495, 3496, 3497, 3498, + 3499, 3500, 3501, 3502, 3503, 3504, 3505, 3506, + 3507, 3508, 3509, 3510, 3511, 3512, 3513, 3514, + 3515, 3516, 3517, 3518, 3519, 3520, 3521, 3522, + 3523, 3524, 3525, 3526, 3527, 3528, 3529, 3530, + 3531, 3532, 3533, 3534, 3535, 3536, 3537, 3538, + 3539, 3540, 3541, 3542, 3543, 3544, 3545, 3546, + 3547, 3548, 3549, 3550, 3551, 3552, 3553, 3554, + 3555, 3556, 3557, 3558, 3559, 3560, 3561, 3562, + 3563, 3564, 3565, 3566, 3567, 3568, 3569, 3570, + 3571, 3572, 3573, 3574, 3575, 3576, 3577, 6056, + 6057, 6058, 32767, 3581, 3582, 3583, 3584, 3585, + 4157, 4158, 4159, 3589, 4162, -4510, -1558, -1557, + -1556, -1742, -4507, -1553, -4506, -4506, 1562, -1544, + 1563, -1547, 1564, -1545, 1565, -1543, -1542, -1529, + -1540, -1539, -1538, -1537, -1536, -4493, -4493, -4493, + -1532, -1531, -1764, -1529, 3622, -1528, -1527, -1526, + -1909, -1524, -1523, -1522, -1907, -1907, -1907, -1907, + -1907, -1907, -1907, -1907, -1907, -1907, -1907, -1510, + -1509, 1071, 1072, 1073, 1074, 1075, 1076, 1077, + 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, + 1086, 1087, 1088, 1089, 1090, 3663, 3664, 3665, + 3666, 3667, 3668, 3669, 3670, 3671, 3672, 3673, + 3674, 1095, 1096, 1097, 1098, 1099, 1100, 1101, + 3682, 1103, 3684, 1105, 3686, 3687, 3688, 1109, + 1110, 1111, 3692, 1113, 1114, 1115, 1116, 1117, + 1118, 1119, 3700, 1121, 3702, 3703, 3704, 1125, + 1126, 1127, -1809, -1809, -1809, -1809, -1809, -1809, + 3720, 3721, 3722, 3717, 3718, 3719, 3720, 1140, + 1141, 1142, 1143, -1802, 1145, 1146, 1147, 1148, + 3730, -1797, 3732, 1152, 3734, 3735, 1155, 1156, + 3738, 3739, 3740, 3741, 3742, 3743, -1785, -1785, + -1785, -1779, -1324, 1168, 1169, 1170, 1171, 1172, + 3752, 3753, 1175, 1176, 1177, 992, 3758, 3759, + 3760, 3761, 1183, 1184, 1185, 1186, -2300, 1188, + -2298, 1190, 1191, 1192, 3772, 3773, 3774, 1196, + 1197, 3777, 3778, 3779, 1201, 1202, 1203, -1747, + 1205, 1206, 1207, 1021, -1744, 1210, -1743, -1743, + 4325, 1219, 4326, 1216, 4327, 1218, 4328, 1220, + 1221, 1234, 1223, 1224, 1225, 1226, 1227, -1730, + -1730, -1730, 1231, 1232, 999, 1234, 1235, 1236, + 1237, 1238, 855, 1240, 1241, 1242, 857, 857, + 857, 857, 857, 857, 857, 857, 857, 857, + 857, 1254, 1255, 1256, 1257, 1258, 1259, 1260, + 1261, 1262, 2969, 2970, 2971, 2972, 2973, 2974, + 2975, 2976, 2977, 2978, -4426, -2483, -4426, -2481, + -6106, 878, 879, -4286, -4286, -4286, -978, 888, + 889, -975, -974, -973, -972, -971, -970, -4268, + -4268, -2059, -4268, -968, -4268, -4268, -967, -966, + -6129, -6129, -963, -962, -961, -4268, -6133, -6133, + -4268, -4268, -4268, -4268, -4268, -4268, -4268, -4268, + -3178, -4268, -1502, -1502, -1502, -1502, -1502, 3902, + 3903, 3904, -1502, 3906, 3907, -1502, -1502, -1502, + -4269, 529, -1502, 0, 0, 0, -1502, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 170, 171, 172, 173, 174, 175, 176, + 177, 178, 179, 180, 181, 182, 183, 184, + 185, 186, 187, 188, 189, 190, 191, 192, + 193, 194, 195, 196, 197, 198, 199, 200, + 201, 202, 203, 204, 205, 206, 207, 208, + 209, 210, 211, 212, 213, 214, 215, 216, + 217, 218, 219, -3194, 221, 222, 223, 224, + -1657, 226, 227, -1657, 229, 230, -1655, 555, + -1655, 234, 235, 236, 732, 238, 239, 240, + 241, 242, 243, -1655, 245, 246, 247, 248, + -1655, 250, -1655, 252, -1655, -1655, -1655, -1655, + -1655, -1655, 259, -1655, -1655, -1655, -1655, 264, + -1655, 266, -1655, 268, -1655, -3620, 271, 272, + -1655, 274, 275, -1655, 277, -1655, -1655, 280, + -1655, 282, 5746, 5747, 5748, 5749, -1655, 288, + -1655, 290, -3335, 3649, 3650, -1515, -1515, -1515, + 1793, 3659, 3660, 1796, 1797, 1798, 1799, 1800, + 1801, -1497, -1497, 712, -1497, 1803, -1497, -1497, + 1804, 1805, -3358, -3358, 1808, 1809, 1810, -1497, + -3362, -3362, -1497, -1497, -1497, -1497, -1497, -1497, + -1497, -1497, -407, -1497, -1497, -1497, -1497, -1497, + -1497, 3667, 3668, -1497, -1497, -1497, 1811, 3677, + 3678, 32767, 1814, 32767, 1815, 32767, 32767, 1816, + 1817, 32767, 32767, 32767, 1818, 1819, 1820, 1821, + -3342, -3342, 1824, 1825, 1826, 1827, 1828, 1829, + 1830, 1831, 1832, 1833, 1834, 1835, 1836, 1837, + 1838, 1839, 1840, 1841, 1842, 1843, 1844, 1845, + 1846, 1847, 1848, 1849, 1850, 1851, 1852, 1853, + 1854, 1855, 1856, 1857, 1858, 1859, 1860, 1861, + 1862, 1863, 1864, 1865, 1866, 1867, 1868, 1869, + 1870, 1871, 1872, 1873, 1874, 1875, 1876, -1537, + 1878, 1879, 1880, 1881, 0, 1883, 1884, 0, + 529, 0, 0, 2210, 0, 1889, 1890, 1891, + 2387, 1893, 1894, 1895, 1896, 1897, 1898, 0, + 1900, 1901, 1902, 1903, 0, 1905, 0, 1907, + 0, 0, 0, 0, 0, 0, 1914, 0, + 0, 0, 0, 1919, 0, 1921, 0, 1923, + 0, -1965, 1926, 1927, 0, 1929, 1930, 0, + 1932, 0, 0, 1935, 0, 1937, 7401, 7402, + 7403, 7404, 0, 1943, 0, 1945, 1946, 0, + 1948, 0, 0, 1951, 1952, 1953, 1954, 0, + 1956, 1957, 1958, 1959, 1960, 1961, 1962, 0, + 1964, 1965, 1966, 1967, 0, 1969, 1970, 1971, + 1972, 0, 1974, 0, 1976, 1977, 1978, 1979, + 1980, 1981, 1982, 1983, 1984, 1985, 0, 1987, + 1988, 1989, 1990, 1991, 566, 566, 566, 5141, + 5142, 566, 566, 566, 566, 566, 566, 566, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 8673, 5722, 5722, 5722, 0, 8676, + 5723, 8677, 8678, 2611, 5718, 2612, 5723, 2613, + 5723, 2614, 5723, 5723, 5711, 5723, 5723, 5723, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 895, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 151, 2776, 4254, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1821, 0, + 0, 0, 596, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, -2856, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, -2901, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, -1025, 32767, 32767, 32767, + 32767, -2910, 32767, 32767, 32767, 32767, 157, 32767, + 32767, 32767, 32767, 158, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 2359, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 160, 32767, 161, 162, 163, 164, + 165, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 898, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 1428, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 1254, 32767, 32767, 32767, + 32767, 1250, 32767, 32767, 32767, 32767, 1246, 32767, + 32767, 32767, 32767, 1243, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 1231, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 1842, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 3177, 1235, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, -4323, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 0, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 0, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 174, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 1830, -112, 1832, -112, 3514, -3469, + -3469, 1697, 1698, 1699, -1608, -3473, -3473, -1608, + -1608, -1608, -1608, -1608, -1608, 1691, 1692, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, -1623, -1623, -1623, 3541, 3542, -1623, -1623, + -1623, -1623, -1623, -1623, -1623, -1623, -1623, -1623, + -1623, -1623, -1623, -1623, -1623, -1623, -1623, -1623, + -1623, -1623, -1623, -1623, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, -766, 2253, 2254, 2255, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 1531, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 0, 0, 32767, 0, 0, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, -173, -173, -173, -173, -173, + -173, -173, -173, -173, -173, -173, -173, 3241, + -173, -173, -173, -173, 1709, -173, -173, 1712, + -173, -173, 1713, -496, 1715, -173, -173, -173, + -668, -173, -173, -173, -173, -173, -173, 1726, + -173, -173, -173, -173, 1731, -173, 1733, -173, + 1735, 1736, 1737, 1738, 1739, 1740, -173, 1742, + 1743, 1744, 1745, -173, 1747, -173, 1749, -173, + 1751, 3717, -173, -173, 1755, -173, -173, 1758, + -173, 1760, 1761, -173, 1763, -173, -5636, -5636, + -5636, -5636, 1769, -173, 1771, -173, 3453, -3530, + -3530, 1636, 1637, 1638, -1669, -3534, -3534, -1669, + -1669, -1669, -1669, -1669, -1669, 1630, 1631, -577, + 1633, -1666, 1635, 1636, -1664, -1664, 3500, 3501, + -1664, -1664, -1664, 1644, 3510, 3511, 1647, 1648, + 1649, 1650, 1651, 1652, 1653, 1654, 565, 1656, + 1657, 1658, 1659, 1660, 1661, -3502, -3502, 1664, + 1665, 1666, 1667, 1668, 1669, 1670, 1671, 1672, + 1673, 1674, 1675, 1676, 1677, 1678, 1679, 1680, + 1681, 1682, 1683, 1684, 1685, 1686, 1687, 1688, + 1689, 1690, 1691, 1692, 1693, 1694, 1695, 1696, + 1697, 1698, 1699, 1700, 1701, 1702, 1703, 1704, + 1705, 1706, 1707, 1708, 1709, 1710, 1711, 1712, + 1713, 1714, 1715, 1716, -1697, 1718, 1719, 1720, + 1721, -160, 1723, 1724, -160, 1726, 1727, -158, + 2052, -158, 1731, 1732, 1733, 2229, 1735, 1736, + 1737, 1738, 1739, 1740, -158, 1742, 1743, 1744, + 1745, -158, 1747, -158, 1749, -158, -158, -158, + -158, -158, -158, 1756, -158, -158, -158, -158, + 1761, -158, 1763, -158, 1765, -158, -2123, 1768, + 1769, -158, 1771, 1772, -158, 1774, -158, -158, + 1777, -158, 1779, 7243, 7244, 7245, 7246, -158, + 1785, -158, 1787, -1838, 5146, 5147, -18, -18, + -18, 3290, 5156, 5157, 3293, 3294, 3295, 3296, + 3297, 3298, 0, 0, 2209, 0, 3300, 0, + 0, 3301, 3302, -1861, -1861, 3305, 3306, 3307, + 0, -1865, -1865, 0, 0, 0, 0, 0, + 0, 0, 0, 1090, 0, 0, 0, 0, + 0, 0, 5164, 5165, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3414, 0, 0, 0, 0, 1882, 0, + 0, 1885, 0, 0, 1886, -323, 1888, 0, + 0, 0, -495, 0, 0, 0, 0, 0, + 0, 1899, 0, 0, 0, 0, 1904, 0, + 1906, 0, 1908, 1909, 1910, 1911, 1912, 1913, + 0, 1915, 1916, 1917, 1918, 0, 1920, 0, + 1922, 0, 1924, 3890, 0, 0, 1928, 0, + 0, 1931, 0, 1933, 1934, 0, 1936, 0, + -5463, -5463, -5463, -5463, 1942, 0, 1944, 0, + 0, 1947, 0, 1949, 1950, 0, 0, 0, + 0, 1955, 0, 0, 0, 0, 0, 0, + 0, 1963, 0, 0, 0, 0, 1968, 0, + 0, 0, 0, 1973, 0, 1975, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1986, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 527, 527, 527, 527, 0, + 528, 528, 528, 528, 528, 528, 528, 528, + 528, 528, 528, 1998, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 1999, 2000, 2001, 2002, 2003, 32767, 32767, 32767, + 32767, 32767, 2004, 32767, 2005, 2006, 2007, 2008, + 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, + 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, + 2025, 2026, 1200, 1200, 32767, 4498, 4499, 2291, + 2032, 2033, 32767, 2034, 32767, 2035, 2036, 32767, + 2037, 2038, 32767, 2039, 2040, 2041, 2042, 2043, + 2044, 2045, 2046, 2047, 2048, 2049, 2050, 2051, + 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2059, + 2060, 2061, 2062, 2063, 2064, 2065, 2066, 2067, + 2068, -1506, -1506, -1506, -1506, -1506, -1506, -1506, + 2076, -2490, 2078, 2079, 2080, 2081, 2082, 2083, + 2084, 2085, 2086, 2087, 2088, 2089, 2090, 2091, + 2092, 2093, 2094, 2095, -105, -2314, 2098, 2099, + 2100, 2101, 2102, 2103, 2104, 2105, 2106, 2107, + 2108, 2109, 2110, 2111, 2112, 2113, 2114, 2115, + 2116, 2117, 2118, 2119, 2120, 5459, 2122, 2123, + 2124, 2125, 2126, 2127, 2128, 5466, 2130, 5467, + 2132, 2133, 2134, 2135, 5471, 2137, 2138, 5473, + 5473, 5473, 5473, 2143, 2144, 5475, 2146, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 2147, 2148, 2149, 2150, 2151, 2152, 2153, 2154, + 2155, 2156, 2157, 2158, 2159, 2160, 2161, 2162, + 2163, 2164, 2165, 2166, 2167, 2168, 2169, 2170, + 2171, 2172, 2173, 2174, 2175, 2176, 2177, 2178, + 2179, 2180, 2181, 2182, 2183, 2184, 2185, 2186, + 2187, 2188, 2189, 2190, 2191, 32767, -726, 2293, + -725, -725, -725, 1357, 1358, 1359, -722, 2201, + 2202, 2203, 2204, 2205, 0, 2207, -715, 2209, + -714, -714, -714, 2213, 2214, 2215, 4806, 2217, + 2218, 2219, 2220, 2221, 2222, 2223, -710, 2225, + -248, -704, -704, 2229, 2230, 2231, 2232, 2233, + 2220, 1430, 2236, 2237, -695, 2239, -694, 2241, + -693, -693, 2244, 2245, 2246, 2231, 2231, 2249, + 2250, 2251, 2252, 2253, 2254, 2255, 2256, 2239, + 2258, 2259, 2260, 2261, 2262, 2263, 2264, 2265, + 2266, 2267, 2268, 2269, 2270, 2271, 2272, 2273, + 2274, 2275, 2276, 2277, 2278, 2279, 2280, 2281, + 2282, 2283, 2284, 2285, 2286, 2287, 2288, 2289, + 2290, 2291, 2292, 2293, 3084, 2295, 2296, 2297, + 2298, 2299, 2300, 2301, 2302, 2303, 2304, 2305, + 2306, 2307, 3983, 2309, 2310, 2292, 2312, 3992, + 2314, 2315, 2316, 2317, 2318, 2319, 2320, 2321, + 2322, 2323, 2324, 2325, 2326, 2327, 2328, 4023, + 2330, 2331, 2332, 2333, 2334, 2335, 2336, 2337, + 2338, 2339, 2340, 2341, 2342, 2343, 2344, 2345, + 2346, 2347, 2348, 2349, 2350, 2351, 2352, 2353, + 2354, 2355, 2356, 2357, 2358, 2359, 2360, 2361, + 2362, 2363, 2364, 2365, 2366, 2367, 2368, 2369, + 2370, 2371, 2372, 2373, 2374, 2375, 2376, 2377, + 2378, 2379, 2360, 2360, 2360, 2360, 2360, 2360, + 2360, 2360, 2360, 2389, 2390, 2391, 2392, 2393, + 2394, 2395, 2396, 2397, 2398, 2399, 2400, 2401, + 2402, 2403, 2404, 2405, 2406, 2407, 2408, 2409, + 2410, 2411, 2412, 2413, 2414, 2415, 2416, 2417, + 2418, 2419, 2420, 2421, 2422, 2423, 2424, 2425, + 2426, 2427, 2428, 2429, 2430, 2431, 2432, 2433, + 2434, 2435, 2436, 2437, 2438, 2439, 2440, 2441, + 2442, 2443, 2444, 2445, 2446, 2447, 32767, 2448, + 2449, 2450, 2451, 2452, 2453, 2454, 2455, 2456, + 2457, 2458, 2459, 2460, 2461, 2462, 2463, 2464, + 2465, 2466, 2467, 2468, 2469, 2470, 2471, 2472, + 2473, 2474, 2475, 2476, 2477, 2478, 2479, 2480, + 2481, 2482, 2483, 2484, 2485, 2486, 2487, 2488, + 2489, 2490, 2491, 2492, 2493, 2494, 2495, 2496, + 2497, 2498, 2499, 2500, 2501, 2502, 2503, 2504, + 2505, 2506, 2507, 2508, 2509, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 2510, + 2511, 2512, 2513, 3266, 3266, 3266, 3266, 2518, + 3267, 3267, 3267, 2522, 3268, 3268, 3268, 3268, + 3268, 3268, 3268, 6682, 3268, 3268, 3268, 2534, + 5151, 3269, 2537, 2538, 3271, 3271, 5157, 2948, + 5159, 2544, 2545, 3273, 2778, 3273, 2549, 3274, + 2551, 3275, 2553, 5175, 2555, 3277, 3277, 3277, + 5181, 2560, 5184, 3278, 5186, 2564, 5189, 5190, + 5191, 5192, 3279, 5194, 5195, 2572, 5198, 32767, + 32767, 3278, 5200, 3278, 2577, 2578, 2579, 2580, + 5210, 3282, 3282, 5213, 3282, 2586, 2587, 2588, + 2589, 2590, 2591, -2175, -2175, -2175, 5230, 3288, + 5232, 3288, 6914, -69, -69, 5097, 5098, 5099, + 1792, -73, -73, 1792, 1792, 1792, 1792, 1792, + 1792, 5091, 5092, 2884, 5094, 1795, 5096, 5097, + 1797, 1797, 6961, 6962, 1797, 1797, 1797, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 2578, 2578, 2578, 2578, 2578, + 2578, 872, 872, 872, 872, 872, 872, 872, + 872, 872, 872, 8277, 6335, 8279, 6335, 9961, + 2978, 2978, 8144, 8145, 8146, 4839, 2974, 2974, + 4839, 4839, 4839, 4839, 4839, 4839, 8138, 8139, + 5931, 8141, 4842, 8143, 8144, 4844, 4844, 10008, + 10009, 4844, 4844, 4844, 8152, 10018, 10019, 8155, + 8156, 8157, 8158, 8159, 8160, 8161, 8162, 7073, + 8164, 5399, 5400, 5401, 5402, 5403, 0, 0, + 0, 5407, 0, 0, 5410, 5411, 5412, 8180, + 3383, 5415, 3914, 3915, 3916, 5419, 3918, 3919, + 3920, 3921, 3922, 3923, 3924, 3925, 3926, 3927, + 3758, 3758, 3758, 3758, 3758, 3758, 3758, 3758, + 3758, 3758, 3758, 3758, 3758, 3758, 3758, 3758, + 3758, 3758, 3758, 3758, 3758, 3758, 3758, 3758, + 3758, 3758, 3758, 3758, 3758, 3758, 3758, 3758, + 3758, 3758, 3758, 3758, 3758, 3758, 3758, 3758, + 3758, 3758, 3758, 3758, 3758, 3758, 3758, 3758, + 3758, 3758, 7172, 3758, 3758, 3758, 3758, 5640, + 3758, 3758, 5643, 3758, 3758, 5644, 3435, 5646, + 3758, 3758, 3758, 3263, 3758, 3758, 3758, 3758, + 3758, 3758, 5657, 3758, 3758, 3758, 3758, 5662, + 3758, 5664, 3758, 5666, 5667, 5668, 5669, 5670, + 5671, 3758, 5673, 5674, 5675, 5676, 3758, 5678, + 3758, 5680, 3758, 5682, 7648, 3758, 3758, 5686, + 3758, 3758, 5689, 3758, 5691, 5692, 3758, -1707, + -1707, -1707, -1707, -1707, -1707, 5698, 3756, 5700, + 3756, 7382, 399, 399, 5565, 5566, 5567, 2260, + 395, 395, 2260, 2260, 2260, 2260, 2260, 2260, + 5559, 5560, 3352, 5562, 2263, 5564, 5565, 2265, + 2265, 7429, 7430, 2265, 2265, 2265, 5573, 7439, + 7440, 5576, 5577, 5578, 5579, 5580, 5581, 5582, + 5583, 4494, 5585, 2820, 2821, 2822, 2823, 2824, + -2579, -2579, -2579, 2828, -2579, -2579, 2831, 2832, + 2833, 5601, 804, 2836, 1335, 1336, 1337, 2840, + 1339, 1340, 1341, 1342, 1343, 1344, 1345, 1346, + 1347, 1348, 1179, 1179, 1179, 1179, 1179, 1179, + 1179, 1179, 1179, 1179, 1179, 1179, 1179, 1179, + 1179, 1179, 1179, 1179, 1179, 1179, 1179, 1179, + 1179, 1179, 1179, 1179, 1179, 1179, 1179, 1179, + 1179, 1179, 1179, 1179, 1179, 1179, 1179, 1179, + 1179, 1179, 1179, 1179, 1179, 1179, 1179, 1179, + 1179, 1179, 1179, 1179, 4593, 1179, 1179, 1179, + 1179, 3061, 1179, 1179, 3064, 1179, 1179, 3065, + 856, 3067, 1179, 1179, 1179, 684, 1179, 1179, + 1179, 1179, 1179, 1179, 3078, 1179, 1179, 1179, + 1179, 3083, 1179, 3085, 1179, 3087, 3088, 3089, + 3090, 3091, 3092, 1179, 3094, 3095, 3096, 3097, + 1179, 3099, 1179, 3101, 1179, 3103, 5069, 1179, + 1179, 3107, 1179, 1179, 3110, 1179, 3112, 3113, + 1179, 3115, 1179, -4284, -4284, -4284, -4284, 3121, + 1179, 3123, 1179, 4805, -2178, -2178, 2988, 2989, + 2990, -317, -2182, -2182, -317, -317, -317, -317, + -317, -317, 2982, 2983, 775, 2985, -314, 2987, + 2988, -312, -312, 4852, 4853, -312, -312, -312, + 2996, 4862, 4863, 2999, 3000, 3001, 3002, 3003, + 3004, 3005, 3006, 1917, 3008, 3009, 3010, 3011, + 3012, 3013, -2150, -2150, 3016, 3017, 3018, 3019, + 3020, 3021, 3022, 3023, 3024, 3025, 3026, 3027, + 3028, 3029, 3030, 3031, 3032, 3033, 3034, 3035, + 32767, 32767, 32767, 3036, 3037, 3038, 3039, 3040, + 3041, 32767, 32767, 3042, 3043, 3044, 3045, 3046, + 3047, 32767, 32767, 3048, 3049, 3050, 3051, 3052, + 3053, 32767, 32767, 3054, 3055, 3056, 32767, 32767, + 32767, -357, 3058, 3059, 3060, 3061, 1180, 3063, + 0, 1179, 3065, 3066, 1181, 3391, 1181, 3070, + 0, 0, 0, 0, 32767, 0, 0, 32767, + 0, 32767, 0, 0, -4973, 32767, 32767, -7368, + -2202, -2201, -2200, -5507, -7372, -7372, -5507, -5507, + -5507, 32767, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 32767, 0, 0, 32767, 0, + -203, -2234, -732, -732, -732, -2234, -732, -732, + -2763, -1261, -1261, -1261, -2763, -1261, -1261, -1261, + -1261, -1261, -1261, -1261, -1261, -1261, -1261, -1091, + -1090, -1089, -1088, -1087, 32767, 32767, -1086, -1085, + -1084, -1083, -1082, -1081, -1080, -1079, -1078, -1077, + -1076, -1075, 32767, -1074, -1073, -1072, -1071, -1070, + -1069, -1068, -1067, -1066, -1065, -1064, -1063, -1062, + -1061, -1060, -1059, -1058, -1057, -1056, 32767, -1055, + -1054, -1053, -1052, 0, 32767, 32767, 32767, -1051, + -1050, -4463, 32767, -1048, 32767, -1047, -2928, -1045, + -1044, -2928, -1042, -1041, -2926, -716, -2926, -1037, + -1036, -1035, -539, -1033, -1032, -1031, -1030, -1029, + -1028, -2926, -1026, -1025, -1024, -1023, -2926, -1021, + -2926, -1019, -2926, -2926, -2926, -2926, -2926, -2926, + -1012, -2926, -2926, -2926, -2926, -1007, -2926, -1005, + -2926, -1003, -2926, -4891, -1000, -999, -2926, -997, + -996, -2926, -994, -2926, -2926, -991, 4475, 4476, + 4477, 4478, 4479, 4480, -2924, -981, -2924, -979, + -4604, 2380, 2381, -2784, -2784, -2784, 524, 2390, + 2391, 527, 528, 529, 530, 531, 532, -2766, + -2766, -557, -2766, 534, -2766, -2766, 535, 536, + -4627, -4627, 539, 540, 541, -2766, -4631, -4631, + -2766, -2766, -2766, -2766, -2766, -2766, -2766, -2766, + -1676, -2766, 0, 0, 0, 0, 0, 5404, + 5405, 5406, 0, 5408, 5409, 0, 0, 0, + -2767, 2031, 0, 1502, 1502, 1502, 0, 1502, + 1502, 1502, 1502, 1502, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 224, 225, 226, 32767, 227, 228, 229, + 230, 231, 232, 233, 234, 235, 236, 67, + 32767, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 32767, 65, 65, 65, 65, + 65, 65, 65, 65, 65, 65, 65, 65, + 65, 65, 65, 65, 65, 65, 65, 65, + 65, 65, 65, 65, 65, 65, 65, 65, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, -271, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 1940, 18, 1942, 3908, 18, 18, 1946, 18, + 18, 1949, 18, 1951, 1952, 18, 1954, 18, + -5445, -5445, -5445, -5445, 1960, 18, 1962, 18, + 3644, -3339, -3339, 1827, 1828, 1829, -1478, -3343, + -3343, -1478, -1478, -1478, -1478, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 0, 0, 0, + 0, 32767, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1340, 1341, + 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1349, + -2064, 1351, 1352, 1353, 1354, 32767, 1355, 1356, + 32767, 0, 32767, 32767, 1679, 32767, 1357, 1358, + 1359, 1855, 1361, 1362, 1363, 1364, 1365, 1366, + 32767, 1367, 1368, 1369, 1370, 32767, 1371, 32767, + 1372, 32767, 32767, 32767, 32767, 32767, 32767, 1373, + 32767, 32767, 32767, 32767, 1374, 32767, 1375, 32767, + 1376, 32767, -2513, 1378, 1379, 32767, 1380, 1381, + 32767, 1382, 32767, 32767, 1383, 32767, 1384, 32767, + 6848, 32767, 6849, 32767, 1387, 32767, 1388, 1389, + 32767, 1390, 32767, 32767, 1391, 1392, 1393, 1394, + 32767, 1395, 1396, 1397, 1398, 1399, 1400, 1401, + 32767, 1402, 1403, 1404, 1405, 32767, 1406, 1407, + 1408, 1409, 32767, 1410, 32767, 1411, 1412, 1413, + 1414, 1415, 1416, 1417, 1418, 1419, 1420, 32767, + 1421, 1422, 1423, 1424, 1425, 0, 0, 0, + 4575, 4576, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, -571, -571, + -571, 0, -572, 8101, 5150, 5150, 5150, 5337, + 8103, 5150, 8104, 8105, 2038, 5145, 2039, 5150, + 2040, 5150, 2041, 5150, 5150, 5138, 5150, 5150, + 5150, 5150, 5150, 8108, 8109, 8110, 5150, 5150, + 5384, 5150, 0, 5151, 5151, 5151, 5535, 5151, + 5151, 5151, 5537, 5538, 5539, 5540, 5541, 5542, + 5543, 5544, 5545, 5546, 5547, 5151, 5151, 2572, + 2572, 2572, 2572, 2572, 2572, 2572, 2572, 2572, + 2572, 2572, 2572, 2572, 2572, 2572, 2572, 2572, + 2572, 2572, 2572, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2580, + 2580, 2580, 2580, 2580, 2580, 2580, 0, 2580, + 0, 2580, 0, 0, 0, 2580, 2580, 2580, + 0, 2580, 2580, 2580, 2580, 2580, 2580, 2580, + 0, 2580, 0, 0, 0, 2580, 2580, 2580, + 5517, 5518, 5519, 5520, 5521, 5522, -6, -6, + -6, 0, 0, 0, 0, 2581, 2581, 2581, + 2581, 5527, 2581, 2581, 2581, 2581, 0, 5528, + 0, 2581, 0, 0, 2581, 2581, 0, 0, + 0, 0, 0, 0, 5529, 5530, 5531, 32767, + 32767, 2579, 2579, 2579, 2579, 2579, 0, 0, + 2579, 2579, 2579, 2765, 0, 0, 0, 0, + 2579, 2579, 2579, 2579, 6066, 2579, 6066, 2579, + 2579, 2579, 0, 0, 0, 2579, 2579, 0, + 0, 0, 2579, 2579, 2579, 5530, 2579, 2579, + 2579, 2766, 5532, 2579, 5533, 5534, -533, 2574, + -532, 2579, -531, 2579, -530, 2579, 2579, 2567, + 2579, 2579, 2579, 2579, 2579, 5537, 5538, 5539, + 2579, 2579, 2813, 2579, 2579, 2579, 2579, 2579, + 2963, 2579, 2579, 2579, 2965, 2966, 2967, 2968, + 2969, 2970, 2971, 2972, 2973, 2974, 2975, 2579, + 2579, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 32767, 32767, 32767, + 32767, 32767, 331, 32767, 332, -2580, -2580, -2580, + -2580, 0, 0, 0, 0, 0, 0, 0, + -2580, 0, -2580, 0, -2580, -2580, -2580, 0, + 0, 0, -2580, 0, 0, 0, 0, 0, + 0, 0, -2580, 0, -2580, -2580, -2580, 0, + 0, 0, 2937, 2938, 2939, 2940, 2941, 2942, + -2586, -2586, -2586, -2580, -2125, -2581, -2581, 0, + 0, 0, 0, 2946, 0, 0, 0, 0, + -2581, 2947, -2581, 0, -2581, -2581, 0, 0, + -2581, -2581, -2581, -2581, -2581, -2581, 2948, 2949, + 2950, 2945, 2491, 0, 0, 0, 0, 0, + -2579, -2579, 0, 0, 0, 186, -2579, -2579, + -2579, -2579, 0, 0, 0, 0, 3487, 0, + 3487, 0, 0, 0, -2579, -2579, -2579, 0, + 0, -2579, -2579, -2579, 0, 0, 0, 2951, + 0, 0, 0, 187, 2953, 0, 2954, 2955, + -3112, -5, -3111, 0, -3110, 0, -3109, 0, + 0, -12, 0, 0, 0, 0, 0, 2958, + 2959, 2960, 0, 0, 234, 0, 0, 0, + 0, 0, 384, 0, 0, 0, 386, 387, + 388, 389, 390, 391, 392, 393, 394, 395, + 396, 0, 0, 0, 0, 0, 0, 0, + 0, 0, -1706, -1706, -1706, 0, 0, 0, + 0, 385, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 397, + 398, 399, 400, 401, 402, 403, 404, 405, + 2112, 2113, 2114, 409, 410, 411, 412, 32767, + 413, 414, 415, 416, 417, 418, 419, 420, + 421, 422, 423, 424, 425, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + -1688, 32767, 32767, 32767, 32767, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 0, 0, 0, + 0, -752, -751, -750, -749, 0, -748, -747, + -746, 0, -745, -744, -743, -742, -741, -740, + -739, -4152, -737, -736, -735, 0, -2616, -733, + 0, 0, -732, -731, -2616, -406, -2616, 0, + 0, -727, -231, -725, 0, -724, 0, -723, + 0, -2621, 0, -721, -720, -719, -2622, 0, + -2623, -716, -2623, 0, -2624, -2624, -2624, -2624, + -710, -2624, -2624, 0, -2625, -706, -2625, -704, + -2625, -702, 0, 0, 0, 0, -2629, -700, + -699, -2629, -697, 0, 0, 0, 0, 0, + 0, 4767, 4768, 4769, -2635, -692, -2635, -690, + -4315, 2669, 2670, -2495, -2495, -2495, 813, 2679, + 2680, 816, 817, 818, 819, 820, 821, -2477, + -2477, -268, -2477, 823, -2477, -2477, 824, 825, + -4338, -4338, 828, 829, 830, -2477, -4342, -4342, + -2477, -2477, -2477, -2477, -2477, -2477, -2477, -2477, + -1387, 0, 0, 32767, 32767, 0, 0, 0, + 0, 0, -2486, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 1756, 1757, 1758, + 1759, -5645, -3702, -5645, -3700, -7325, -341, -340, + -5505, -5505, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 532, 533, + 32767, 534, 535, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, -781, 1084, 1084, 1084, 1084, + 1084, 1084, 4383, 4384, 2176, 4386, 1087, 4388, + 4389, 1089, 1089, 6253, 6254, 1089, 1089, 1089, + 4397, 6263, 6264, 4400, 4401, 4402, 4403, 4404, + 4405, 4406, 4407, 3318, 4409, 4410, 4411, 4412, + 4413, 4414, -749, -749, 4417, 4418, 4419, 4420, + 4421, 4422, 4423, 4424, 4425, 4426, 4427, 4428, + 4429, 4430, 4431, 4432, 4433, 4434, 4435, 4436, + 4437, 4438, 4439, 4440, 4441, 4442, 4443, 4444, + 4445, 4446, 4447, 4448, 4449, 4450, 4451, 4452, + 4453, 4454, 4455, 4456, 4457, 4458, 4459, 4460, + 4461, 4462, 4463, 4464, 4465, 4466, 4467, 4468, + 4469, 1056, 4471, 4472, 4473, 4474, 2593, 4476, + 4477, 2593, 4479, 4480, 2595, 4805, 2595, 4484, + 4485, 4486, 4982, 4488, 4489, 4490, 4491, 4492, + 4493, 2595, 4495, 4496, 4497, 4498, 2595, 4500, + 2595, 4502, 2595, 2595, 2595, 2595, 2595, 2595, + 4509, 2595, 2595, 2595, 2595, 4514, 2595, 4516, + 2595, 4518, 2595, 630, 4521, 4522, 2595, 4524, + 4525, 2595, 4527, 2595, 2595, 4530, 2595, 4532, + 9996, 9997, 9998, 9999, 2595, 4538, 2595, 4540, + 4541, 2595, 4543, 2595, 2595, 4546, 4547, 4548, + 4549, 2595, 4551, 4552, 4553, 4554, 4555, 4556, + 4557, 2595, 4559, 4560, 4561, 4562, 2595, 4564, + 4565, 4566, 4567, 2595, 4569, 2595, 4571, 4572, + 4573, 4574, 4575, 4576, 4577, 4578, 4579, 4580, + 2595, 4582, 4583, 4584, 4585, 4586, 4587, 4588, + 4589, 4590, 4591, 4592, 4593, 4594, 4595, 4596, + 4597, 4598, 4599, 4600, 4601, 4602, 4603, 4604, + 4605, 4606, 4607, 4608, 4609, 4610, 4611, 4612, + 4613, 4614, 4615, 4089, 4090, 4091, 4092, 4620, + 4093, 4094, 4095, 4096, 4097, 4098, 4099, 4100, + 4101, 4102, 4103, 4104, 2765, 2765, 2765, 2765, + 2765, 2765, 2765, 2765, 2765, 2765, 6179, 2765, + 2765, 2765, 2765, 4647, 2765, 2765, 4650, 4122, + 4652, 4653, 2444, 4655, 2767, 2767, 2767, 2272, + 2767, 2767, 2767, 2767, 2767, 2767, 4666, 2767, + 2767, 2767, 2767, 4671, 2767, 4673, 2767, 4675, + 4676, 4677, 4678, 4679, 4680, 2767, 4682, 4683, + 4684, 4685, 2767, 4687, 2767, 4689, 2767, 4691, + 6657, 2767, 2767, 4695, 2767, 2767, 4698, 2767, + 4700, 4701, 2767, 4703, 2767, -2696, -2696, -2696, + -2696, 4709, 2767, 4711, 2767, 2767, 4714, 2767, + 4716, 4717, 2767, 2767, 2767, 2767, 4722, 2767, + 2767, 2767, 2767, 2767, 2767, 2767, 4730, 2767, + 2767, 2767, 2767, 4735, 2767, 2767, 2767, 2767, + 4740, 2767, 4742, 2767, 2767, 2767, 2767, 2767, + 2767, 2767, 2767, 2767, 2767, 4753, 2767, 2767, + 2767, 2767, 2767, 4193, 4194, 4195, -379, -379, + 4198, 4199, 4200, 4201, 4202, 4203, 4204, 4771, + 4772, 4773, 4774, 4775, 4776, 4777, 4778, 4779, + 4780, -3892, -940, -939, -938, 4785, -3890, -936, + -3889, -3889, 2179, -927, 2180, -930, 2181, -928, + 2182, -926, -925, -912, -923, -922, -921, 4803, + 4804, 4805, 4806, 4807, 4808, 4809, 4810, 4811, + 4812, 4813, 4814, 4815, 4816, 4817, 4818, 4819, + 3925, 4821, 4822, 4823, 4824, 4825, 4826, 4827, + 4828, 4829, 4830, 4831, 4832, 4833, 4834, 4835, + 4836, 4837, 4838, 4839, 4840, 4841, 4842, 4843, + 4844, 4845, 4846, 4847, 4848, 4849, 4850, 4851, + 4852, 4853, 4854, 4855, 4856, 4857, 4858, 4859, + 4860, 4710, 2086, 609, 4864, 4865, 4866, 4867, + 4868, 4869, 4870, 4871, 4872, 3052, 4874, 4875, + 4876, 4281, 4878, 4879, 4880, 4881, 4882, 4883, + 4884, 4885, 4886, 4887, 634, 4888, 4889, 4890, + 4891, 4892, 4893, 4894, 4895, 4896, 1322, 1322, + 1322, 1322, 1322, 1322, 1322, 4904, 338, 4906, + 4907, 4908, 4909, 4910, 4911, 4912, 4913, 4914, + 4915, 4916, 4917, 665, 666, 667, 668, 669, + 670, 671, 672, 673, 674, 675, 676, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, + 0, 0, 0, 0, 0, 32767, 0, 0, + 0, 0, 32767, 32767, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, 0, 0, 0, + 0, 0, 0, 0, 32767, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 32767, 0, 0, 0, 2478, 32767, 2477, + 2477, 2477, 2477, 2477, 32767, 2476, 32767, 32767, + 32767, 2473, 2473, 2473, 2473, 2473, 2473, 2473, + 32767, 2472, 2472, 2472, 2472, 2472, 2472, 2472, + 2472, 2472, 2472, 2472, 2472, 2472, 2472, 2472, + 2472, 2472, 2472, 2472, 2472, 2472, 2472, 2472, + 2472, 2472, 2472, 2472, 2472, 2472, 2472, 2472, + 2472, 2472, 2472, 2472, 2472, 2472, 2472, 2472, + 2472, 2472, 2472, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, -2478, -2478, -2478, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }; + + const unsigned char *k = (const unsigned char *) key; + size_t keylen = 4; + uint32 a = 0; + uint32 b = 1; + + while (keylen--) + { + unsigned char c = *k++; + + a = a * 257 + c; + b = b * 8191 + c; + } + return h[a % 9837] + h[b % 9837]; +} + +/* Hash lookup information for NFKC_QC */ +static const pg_unicode_norminfo UnicodeNormInfo_NFKC_QC = { + UnicodeNormProps_NFKC_QC, + NFKC_QC_hash_func, + 4918 +}; diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 43c0e626d183..ffa1a5580c85 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -224,7 +224,6 @@ typedef enum ExprEvalOp EEOP_ROWIDEXPR, EEOP_WINDOW_FUNC, EEOP_SUBPLAN, - EEOP_ALTERNATIVE_SUBPLAN, /* aggregation related nodes */ EEOP_AGG_STRICT_DESERIALIZE, @@ -640,13 +639,6 @@ typedef struct ExprEvalStep SubPlanState *sstate; } subplan; - /* for EEOP_ALTERNATIVE_SUBPLAN */ - struct - { - /* out-of-line state, created by nodeSubplan.c */ - AlternativeSubPlanState *asstate; - } alternative_subplan; - /* for EEOP_AGG_*DESERIALIZE */ struct { @@ -788,8 +780,6 @@ extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op); extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op); extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext); -extern void ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, - ExprContext *econtext); extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op, diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h index 23c824de87db..dcf290c29bdb 100644 --- a/src/include/executor/execPartition.h +++ b/src/include/executor/execPartition.h @@ -22,33 +22,6 @@ typedef struct PartitionDispatchData *PartitionDispatch; typedef struct PartitionTupleRouting PartitionTupleRouting; -/* - * PartitionRoutingInfo - * - * Additional result relation information specific to routing tuples to a - * table partition. - */ -typedef struct PartitionRoutingInfo -{ - /* - * Map for converting tuples in root partitioned table format into - * partition format, or NULL if no conversion is required. - */ - TupleConversionMap *pi_RootToPartitionMap; - - /* - * Map for converting tuples in partition format into the root partitioned - * table format, or NULL if no conversion is required. - */ - TupleConversionMap *pi_PartitionToRootMap; - - /* - * Slot to store tuples in partition format, or NULL when no translation - * is required between root and partition. - */ - TupleTableSlot *pi_PartitionTupleSlot; -} PartitionRoutingInfo; - /* * PartitionedRelPruningData - Per-partitioned-table data for run-time pruning * of partitions. For a multilevel partitioned table, we have one of these diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 3fbef4ca0601..daadf189ab04 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -176,6 +176,9 @@ extern void ResetTupleHashTable(TupleHashTable hashtable); */ extern JunkFilter *ExecInitJunkFilter(List *targetList, TupleTableSlot *slot); +extern JunkFilter *ExecInitJunkFilterInsertion(List *targetList, + TupleDesc cleanTupType, + TupleTableSlot *slot); extern JunkFilter *ExecInitJunkFilterConversion(List *targetList, TupleDesc cleanTupType, TupleTableSlot *slot); @@ -211,7 +214,6 @@ extern void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation partition_root, int instrument_options); extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid); -extern void ExecCleanUpTriggerState(EState *estate); extern void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate); extern bool ExecPartitionCheck(ResultRelInfo *resultRelInfo, @@ -582,6 +584,8 @@ extern Relation ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags extern Relation ExecOpenScanExternalRelation(EState *estate, Index scanrelid); extern void ExecInitRangeTable(EState *estate, List *rangeTable); +extern void ExecCloseRangeTableRelations(EState *estate); +extern void ExecCloseResultRelations(EState *estate); static inline RangeTblEntry * exec_rt_fetch(Index rti, EState *estate) @@ -590,6 +594,8 @@ exec_rt_fetch(Index rti, EState *estate) } extern Relation ExecGetRangeTableRelation(EState *estate, Index rti); +extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo, + Index rti); extern void executor_errposition(EState *estate, int location); @@ -617,10 +623,14 @@ extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relIn */ extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative); extern void ExecCloseIndices(ResultRelInfo *resultRelInfo); -extern List *ExecInsertIndexTuples(TupleTableSlot *slot, EState *estate, bool noDupErr, +extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, EState *estate, + bool noDupErr, bool *specConflict, List *arbiterIndexes); -extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate, - ItemPointer conflictTid, List *arbiterIndexes); +extern bool ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + EState *estate, ItemPointer conflictTid, + List *arbiterIndexes); extern void check_exclusion_constraint(Relation heap, Relation index, IndexInfo *indexInfo, ItemPointer tupleid, @@ -636,10 +646,14 @@ extern bool RelationFindReplTupleByIndex(Relation rel, Oid idxoid, TupleTableSlot *outslot); extern bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, TupleTableSlot *searchslot, TupleTableSlot *outslot); -extern void ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot); -extern void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, + +extern void ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo, + EState *estate, TupleTableSlot *slot); +extern void ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo, + EState *estate, EPQState *epqstate, TupleTableSlot *searchslot, TupleTableSlot *slot); -extern void ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, +extern void ExecSimpleRelationDelete(ResultRelInfo *resultRelInfo, + EState *estate, EPQState *epqstate, TupleTableSlot *searchslot); extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd); diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h index 56e20706ea42..b675c50ceee8 100644 --- a/src/include/executor/functions.h +++ b/src/include/executor/functions.h @@ -29,9 +29,9 @@ extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTupl extern void sql_fn_parser_setup(struct ParseState *pstate, SQLFunctionParseInfoPtr pinfo); -extern void check_sql_fn_statements(List *queryTreeList); +extern void check_sql_fn_statements(List *queryTreeLists); -extern bool check_sql_fn_retval(List *queryTreeList, +extern bool check_sql_fn_retval(List *queryTreeLists, Oid rettype, TupleDesc rettupdesc, bool insertDroppedCols, List **resultTargetList); diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h index 4749cd774f2d..1259fdecbf52 100644 --- a/src/include/executor/nodeModifyTable.h +++ b/src/include/executor/nodeModifyTable.h @@ -15,7 +15,9 @@ #include "nodes/execnodes.h" -extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype); +extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo, + EState *estate, TupleTableSlot *slot, + CmdType cmdtype); extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags); extern void ExecEndModifyTable(ModifyTableState *node); diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h index f78928405a6c..373443707220 100644 --- a/src/include/executor/nodeSubplan.h +++ b/src/include/executor/nodeSubplan.h @@ -19,12 +19,8 @@ extern SubPlanState *ExecInitSubPlan(SubPlan *subplan, PlanState *parent); -extern AlternativeSubPlanState *ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent); - extern Datum ExecSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull); -extern Datum ExecAlternativeSubPlan(AlternativeSubPlanState *node, ExprContext *econtext, bool *isNull); - extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent); /* diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 269912ee0fca..e9a496d94011 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -276,6 +276,7 @@ extern struct varlena *pg_detoast_datum_packed(struct varlena *datum); #define PG_GETARG_POINTER(n) DatumGetPointer(PG_GETARG_DATUM(n)) #define PG_GETARG_CSTRING(n) DatumGetCString(PG_GETARG_DATUM(n)) #define PG_GETARG_NAME(n) DatumGetName(PG_GETARG_DATUM(n)) +#define PG_GETARG_TRANSACTIONID(n) DatumGetTransactionId(PG_GETARG_DATUM(n)) /* these macros hide the pass-by-reference-ness of the datatype: */ #define PG_GETARG_FLOAT4(n) DatumGetFloat4(PG_GETARG_DATUM(n)) #define PG_GETARG_FLOAT8(n) DatumGetFloat8(PG_GETARG_DATUM(n)) @@ -362,6 +363,7 @@ extern struct varlena *pg_detoast_datum_packed(struct varlena *datum); #define PG_RETURN_POINTER(x) return PointerGetDatum(x) #define PG_RETURN_CSTRING(x) return CStringGetDatum(x) #define PG_RETURN_NAME(x) return NameGetDatum(x) +#define PG_RETURN_TRANSACTIONID(x) return TransactionIdGetDatum(x) /* these macros hide the pass-by-reference-ness of the datatype: */ #define PG_RETURN_FLOAT4(x) return Float4GetDatum(x) #define PG_RETURN_FLOAT8(x) return Float8GetDatum(x) diff --git a/src/include/funcapi.h b/src/include/funcapi.h index 41e4af4e0b0c..c3cd87e01bfe 100644 --- a/src/include/funcapi.h +++ b/src/include/funcapi.h @@ -173,7 +173,8 @@ extern int get_func_arg_info(HeapTuple procTup, Oid **p_argtypes, char ***p_argnames, char **p_argmodes); -extern int get_func_input_arg_names(Datum proargnames, Datum proargmodes, +extern int get_func_input_arg_names(char prokind, + Datum proargnames, Datum proargmodes, char ***arg_names); extern int get_func_trftypes(HeapTuple procTup, Oid **p_trftypes); diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 33d6aaa65670..fb8d6df8dfae 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -42,6 +42,10 @@ typedef enum UserAuth #define USER_AUTH_LAST uaPeer /* Must be last value of this enum */ } UserAuth; +/* + * Data structures representing pg_hba.conf entries + */ + typedef enum IPCompareMethod { ipCmpMask, @@ -75,11 +79,12 @@ typedef struct HbaLine List *databases; List *roles; struct sockaddr_storage addr; + int addrlen; /* zero if we don't have a valid addr */ struct sockaddr_storage mask; + int masklen; /* zero if we don't have a valid mask */ IPCompareMethod ip_cmp_method; char *hostname; UserAuth auth_method; - char *usermap; char *pamservice; bool pam_use_hostname; diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 623f0bf953a1..55d8c52a3abb 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -36,7 +36,6 @@ #include "utils/tuplestore.h" struct PlanState; /* forward references in this file */ -struct PartitionRoutingInfo; struct ParallelHashJoinState; struct ExecRowMark; struct ExprState; @@ -502,19 +501,29 @@ typedef struct ResultRelInfo /* ON CONFLICT evaluation state */ OnConflictSetState *ri_onConflict; - /* partition check expression */ - List *ri_PartitionCheck; - - /* partition check expression state */ + /* partition check expression state (NULL if not set up yet) */ ExprState *ri_PartitionCheckExpr; - /* relation descriptor for root partitioned table */ + /* + * Information needed by tuple routing target relations + * + * PartitionRoot gives the target relation mentioned in the query. + * RootToPartitionMap and PartitionTupleSlot, initialized by + * ExecInitRoutingInfo, are non-NULL if partition has a different tuple + * format than the root table. + */ Relation ri_PartitionRoot; + TupleConversionMap *ri_RootToPartitionMap; + TupleTableSlot *ri_PartitionTupleSlot; - /* Additional information specific to partition tuple routing */ - struct PartitionRoutingInfo *ri_PartitionInfo; + /* + * Map to convert child result relation tuples to the format of the table + * actually mentioned in the query (called "root"). Set only if + * transition tuple capture or update partition row movement is active. + */ + TupleConversionMap *ri_ChildToRootMap; - /* For use by copy.c when performing multi-inserts */ + /* for use by copy.c when performing multi-inserts */ struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer; } ResultRelInfo; @@ -547,23 +556,18 @@ typedef struct EState CommandId es_output_cid; /* Info about target table(s) for insert/update/delete queries: */ - ResultRelInfo *es_result_relations; /* array of ResultRelInfos */ - int es_num_result_relations; /* length of array */ - ResultRelInfo *es_result_relation_info; /* currently active array elt */ + ResultRelInfo **es_result_relations; /* Array of per-range-table-entry + * ResultRelInfo pointers, or NULL + * if not a target table */ + List *es_opened_result_relations; /* List of non-NULL entries in + * es_result_relations in no + * specific order */ - /* - * Info about the partition root table(s) for insert/update/delete queries - * targeting partitioned tables. Only leaf partitions are mentioned in - * es_result_relations, but we need access to the roots for firing - * triggers and for runtime tuple routing. - */ - ResultRelInfo *es_root_result_relations; /* array of ResultRelInfos */ - int es_num_root_result_relations; /* length of the array */ PartitionDirectory es_partition_directory; /* for PartitionDesc lookup */ /* * The following list contains ResultRelInfos created by the tuple routing - * code for partitions that don't already have one. + * code for partitions that aren't found in the es_result_relations array. */ List *es_tuple_routing_result_relations; @@ -978,18 +982,6 @@ typedef struct SubPlanState Tuplestorestate *ts_state; } SubPlanState; -/* ---------------- - * AlternativeSubPlanState node - * ---------------- - */ -typedef struct AlternativeSubPlanState -{ - NodeTag type; - AlternativeSubPlan *subplan; /* expression plan node */ - List *subplans; /* SubPlanStates of alternative subplans */ - int active; /* list index of the one we're using */ -} AlternativeSubPlanState; - /* * DomainConstraintState - one item to check during CoerceToDomain * @@ -1285,8 +1277,13 @@ typedef struct ModifyTableState TupleTableSlot **mt_scans; /* input tuple corresponding to underlying * plans */ ResultRelInfo *resultRelInfo; /* per-subplan target relations */ - ResultRelInfo *rootResultRelInfo; /* root target relation (partitioned - * table root) */ + + /* + * Target relation mentioned in the original statement, used to fire + * statement-level triggers and as the root for tuple routing. + */ + ResultRelInfo *rootResultRelInfo; + List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */ EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */ bool fireBSTriggers; /* do we need to fire stmt triggers? */ @@ -1306,9 +1303,6 @@ typedef struct ModifyTableState /* controls transition table population for INSERT...ON CONFLICT UPDATE */ struct TransitionCaptureState *mt_oc_transition_capture; - - /* Per plan map for tuple conversion from child to root */ - TupleConversionMap **mt_per_subplan_tupconv_maps; } ModifyTableState; /* ---------------- @@ -2093,6 +2087,7 @@ typedef struct ForeignScanState ScanState ss; /* its first field is NodeTag */ ExprState *fdw_recheck_quals; /* original quals not in ss.ps.qual */ Size pscan_len; /* size of parallel coordination information */ + ResultRelInfo *resultRelInfo; /* result rel info, if UPDATE or DELETE */ /* use struct pointer to avoid including fdwapi.h here */ struct FdwRoutine *fdwroutine; void *fdw_state; /* foreign-data wrapper can keep state here */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 8c989371a8d9..26962e64d44a 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -277,7 +277,6 @@ typedef enum NodeTag T_WindowFuncExprState, T_SetExprState, T_SubPlanState, - T_AlternativeSubPlanState, T_DomainConstraintState, T_AggExprIdState, T_RowIdExprState, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index e04f61a5cc1b..7e1315c6f614 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -367,6 +367,7 @@ typedef struct CollateClause typedef enum RoleSpecType { ROLESPEC_CSTRING, /* role name is stored as a C string */ + ROLESPEC_CURRENT_ROLE, /* role spec is CURRENT_ROLE */ ROLESPEC_CURRENT_USER, /* role spec is CURRENT_USER */ ROLESPEC_SESSION_USER, /* role spec is SESSION_USER */ ROLESPEC_PUBLIC /* role name is "public" */ @@ -1026,12 +1027,16 @@ typedef struct PartitionCmd * * updatedCols is also used in some other places, for example, to determine * which triggers to fire and in FDWs to know which changed columns they - * need to ship off. Generated columns that are caused to be updated by an - * update to a base column are collected in extraUpdatedCols. This is not - * considered for permission checking, but it is useful in those places - * that want to know the full set of columns being updated as opposed to - * only the ones the user explicitly mentioned in the query. (There is - * currently no need for an extraInsertedCols, but it could exist.) + * need to ship off. + * + * Generated columns that are caused to be updated by an update to a base + * column are listed in extraUpdatedCols. This is not considered for + * permission checking, but it is useful in those places that want to know + * the full set of columns being updated as opposed to only the ones the + * user explicitly mentioned in the query. (There is currently no need for + * an extraInsertedCols, but it could exist.) Note that extraUpdatedCols + * is populated during query rewrite, NOT in the parser, since generated + * columns could be added after a rule has been parsed and stored. * * securityQuals is a list of security barrier quals (boolean expressions), * to be tested in the listed order before returning a row from the @@ -1953,6 +1958,7 @@ typedef enum AlterTableType AT_AddColumnRecurse, /* internal to commands/tablecmds.c */ AT_AddColumnToView, /* implicitly via CREATE OR REPLACE VIEW */ AT_ColumnDefault, /* alter column default */ + AT_CookedColumnDefault, /* add a pre-cooked column default */ AT_DropNotNull, /* alter column drop not null */ AT_SetNotNull, /* alter column set not null */ AT_DropExpression, /* alter column drop expression */ @@ -2017,6 +2023,7 @@ typedef enum AlterTableType AT_AddIdentity, /* ADD IDENTITY */ AT_SetIdentity, /* SET identity column options */ AT_DropIdentity, /* DROP IDENTITY */ + AT_AlterCollationRefreshVersion, /* ALTER COLLATION ... REFRESH VERSION */ AT_SetDistributedBy, /* SET DISTRIBUTED BY */ AT_ExpandTable, /* EXPAND DISTRIBUTED */ @@ -2046,6 +2053,7 @@ typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */ AlterTableType subtype; /* Type of table alteration to apply */ char *name; /* column, constraint, or trigger to act on, * or tablespace */ + List *object; /* collation to act on if it's a collation */ int16 num; /* attribute number for columns referenced by * number */ RoleSpec *newowner; @@ -2134,17 +2142,6 @@ typedef struct GpSplitPartitionCmd int location; } GpSplitPartitionCmd; -/* ---------------------- - * Alter Collation - * ---------------------- - */ -typedef struct AlterCollationStmt -{ - NodeTag type; - List *collname; -} AlterCollationStmt; - - /* ---------------------- * Alter Domain * @@ -3869,6 +3866,8 @@ typedef struct ConstraintsSetStmt /* Reindex options */ #define REINDEXOPT_VERBOSE (1 << 0) /* print progress info */ #define REINDEXOPT_REPORT_PROGRESS (1 << 1) /* report pgstat progress */ +#define REINDEXOPT_MISSING_OK (1 << 2) /* skip missing relations */ +#define REINDEXOPT_CONCURRENTLY (1 << 3) /* concurrent mode */ typedef enum ReindexObjectType { @@ -3887,7 +3886,6 @@ typedef struct ReindexStmt RangeVar *relation; /* Table or index to reindex */ const char *name; /* name of database to reindex */ int options; /* Reindex options flags */ - bool concurrent; /* reindex concurrently? */ Oid relid; /* oid of table or index, used by QE */ } ReindexStmt; diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index b1a4cb4a2e9a..a659b4b7c802 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -179,8 +179,6 @@ typedef struct PlannerGlobal List *resultRelations; /* "flat" list of integer RT indexes */ - List *rootResultRelations; /* "flat" list of integer RT indexes */ - List *appendRelations; /* "flat" list of AppendRelInfos */ List *relationOids; /* OIDs of relations the plan depends on */ @@ -429,6 +427,7 @@ struct PlannerInfo bool hasHavingQual; /* true if havingQual was non-null */ bool hasPseudoConstantQuals; /* true if any RestrictInfo has * pseudoconstant = true */ + bool hasAlternativeSubPlans; /* true if we've made any of those */ bool hasRecursion; /* true if planning a recursive WITH item */ /* These fields are used only when hasRecursion is true: */ @@ -773,9 +772,6 @@ static inline void planner_subplan_put_plan(struct PlannerInfo *root, SubPlan *s * part_rels - RelOptInfos for each partition * all_partrels - Relids set of all partition relids * partexprs, nullable_partexprs - Partition key expressions - * partitioned_child_rels - RT indexes of unpruned partitions of - * this relation that are partitioned tables - * themselves, in hierarchical order * * The partexprs and nullable_partexprs arrays each contain * part_scheme->partnatts elements. Each of the elements is a list of @@ -930,7 +926,6 @@ typedef struct RelOptInfo Relids all_partrels; /* Relids set of all partition relids */ List **partexprs; /* Non-nullable partition key expressions */ List **nullable_partexprs; /* Nullable partition key expressions */ - List *partitioned_child_rels; /* List of RT indexes */ /* * In a subquery, if this base relation contains quals that must @@ -1085,10 +1080,13 @@ typedef struct ForeignKeyOptInfo /* Derived info about whether FK's equality conditions match the query: */ int nmatched_ec; /* # of FK cols matched by ECs */ + int nconst_ec; /* # of these ECs that are ec_has_const */ int nmatched_rcols; /* # of FK cols matched by non-EC rinfos */ int nmatched_ri; /* total # of non-EC rinfos matched to FK */ /* Pointer to eclass matching each column's condition, if there is one */ struct EquivalenceClass *eclass[INDEX_MAX_KEYS]; + /* Pointer to eclass member for the referencing Var, if there is one */ + struct EquivalenceMember *fk_eclass_member[INDEX_MAX_KEYS]; /* List of non-EC RestrictInfos matching each column's condition */ List *rinfos[INDEX_MAX_KEYS]; } ForeignKeyOptInfo; @@ -1734,8 +1732,9 @@ typedef struct CustomPath typedef struct AppendPath { Path path; - /* RT indexes of non-leaf tables in a partition tree */ - List *partitioned_rels; + List *partitioned_rels; /* List of Relids containing RT indexes of + * non-leaf tables for each partition + * hierarchy whose paths are in 'subpaths' */ List *subpaths; /* list of component Paths */ /* Index of first partial path in subpaths; list_length(subpaths) if none */ int first_partial_path; @@ -1760,8 +1759,9 @@ extern bool is_dummy_rel(RelOptInfo *rel); typedef struct MergeAppendPath { Path path; - /* RT indexes of non-leaf tables in a partition tree */ - List *partitioned_rels; + List *partitioned_rels; /* List of Relids containing RT indexes of + * non-leaf tables for each partition + * hierarchy whose paths are in 'subpaths' */ List *subpaths; /* list of component Paths */ double limit_tuples; /* hard limit on output tuples, or -1 */ } MergeAppendPath; diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h index 9836e3f84113..f4072d7eec48 100644 --- a/src/include/nodes/pg_list.h +++ b/src/include/nodes/pg_list.h @@ -144,26 +144,6 @@ list_second_cell(const List *l) return NULL; } -/* Fetch address of list's third cell, if it has one, else NULL */ -static inline ListCell * -list_third_cell(const List *l) -{ - if (l && l->length >= 3) - return &l->elements[2]; - else - return NULL; -} - -/* Fetch address of list's fourth cell, if it has one, else NULL */ -static inline ListCell * -list_fourth_cell(const List *l) -{ - if (l && l->length >= 4) - return &l->elements[3]; - else - return NULL; -} - /* Fetch list's length */ static inline int list_length(const List *l) @@ -186,35 +166,34 @@ list_length(const List *l) * linitial() than lfirst(): given a List, lsecond() returns the data * in the second list cell. */ - #define lfirst(lc) ((lc)->ptr_value) #define lfirst_int(lc) ((lc)->int_value) #define lfirst_oid(lc) ((lc)->oid_value) #define lfirst_node(type,lc) castNode(type, lfirst(lc)) -#define linitial(l) lfirst(list_head(l)) -#define linitial_int(l) lfirst_int(list_head(l)) -#define linitial_oid(l) lfirst_oid(list_head(l)) +#define linitial(l) lfirst(list_nth_cell(l, 0)) +#define linitial_int(l) lfirst_int(list_nth_cell(l, 0)) +#define linitial_oid(l) lfirst_oid(list_nth_cell(l, 0)) #define linitial_node(type,l) castNode(type, linitial(l)) -#define lsecond(l) lfirst(list_second_cell(l)) -#define lsecond_int(l) lfirst_int(list_second_cell(l)) -#define lsecond_oid(l) lfirst_oid(list_second_cell(l)) +#define lsecond(l) lfirst(list_nth_cell(l, 1)) +#define lsecond_int(l) lfirst_int(list_nth_cell(l, 1)) +#define lsecond_oid(l) lfirst_oid(list_nth_cell(l, 1)) #define lsecond_node(type,l) castNode(type, lsecond(l)) -#define lthird(l) lfirst(list_third_cell(l)) -#define lthird_int(l) lfirst_int(list_third_cell(l)) -#define lthird_oid(l) lfirst_oid(list_third_cell(l)) +#define lthird(l) lfirst(list_nth_cell(l, 2)) +#define lthird_int(l) lfirst_int(list_nth_cell(l, 2)) +#define lthird_oid(l) lfirst_oid(list_nth_cell(l, 2)) #define lthird_node(type,l) castNode(type, lthird(l)) -#define lfourth(l) lfirst(list_fourth_cell(l)) -#define lfourth_int(l) lfirst_int(list_fourth_cell(l)) -#define lfourth_oid(l) lfirst_oid(list_fourth_cell(l)) +#define lfourth(l) lfirst(list_nth_cell(l, 3)) +#define lfourth_int(l) lfirst_int(list_nth_cell(l, 3)) +#define lfourth_oid(l) lfirst_oid(list_nth_cell(l, 3)) #define lfourth_node(type,l) castNode(type, lfourth(l)) -#define llast(l) lfirst(list_tail(l)) -#define llast_int(l) lfirst_int(list_tail(l)) -#define llast_oid(l) lfirst_oid(list_tail(l)) +#define llast(l) lfirst(list_last_cell(l)) +#define llast_int(l) lfirst_int(list_last_cell(l)) +#define llast_oid(l) lfirst_oid(list_last_cell(l)) #define llast_node(type,l) castNode(type, llast(l)) /* @@ -269,6 +248,16 @@ list_nth_cell(const List *list, int n) return &list->elements[n]; } +/* + * Return the last cell in a non-NIL List. + */ +static inline ListCell * +list_last_cell(const List *list) +{ + Assert(list != NIL); + return &list->elements[list->length - 1]; +} + /* * Return the pointer value contained in the n'th element of the * specified list. (List elements begin at 0.) @@ -380,6 +369,32 @@ lnext(const List *l, const ListCell *c) */ #define foreach_current_index(cell) (cell##__state.i) +/* + * for_each_from - + * Like foreach(), but start from the N'th (zero-based) list element, + * not necessarily the first one. + * + * It's okay for N to exceed the list length, but not for it to be negative. + * + * The caveats for foreach() apply equally here. + */ +#define for_each_from(cell, lst, N) \ + for (ForEachState cell##__state = for_each_from_setup(lst, N); \ + (cell##__state.l != NIL && \ + cell##__state.i < cell##__state.l->length) ? \ + (cell = &cell##__state.l->elements[cell##__state.i], true) : \ + (cell = NULL, false); \ + cell##__state.i++) + +static inline ForEachState +for_each_from_setup(const List *lst, int N) +{ + ForEachState r = {lst, N}; + + Assert(N >= 0); + return r; +} + /* * for_each_cell - * a convenience macro which loops through a list starting from a @@ -396,7 +411,7 @@ lnext(const List *l, const ListCell *c) cell##__state.i++) static inline ForEachState -for_each_cell_setup(List *lst, ListCell *initcell) +for_each_cell_setup(const List *lst, const ListCell *initcell) { ForEachState r = {lst, initcell ? list_cell_number(lst, initcell) : list_length(lst)}; @@ -453,8 +468,8 @@ for_each_cell_setup(List *lst, ListCell *initcell) cell1##__state.i1++, cell1##__state.i2++) static inline ForBothCellState -for_both_cell_setup(List *list1, ListCell *initcell1, - List *list2, ListCell *initcell2) +for_both_cell_setup(const List *list1, const ListCell *initcell1, + const List *list2, const ListCell *initcell2) { ForBothCellState r = {list1, list2, initcell1 ? list_cell_number(list1, initcell1) : list_length(list1), diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 0f5ef35cd79e..915e4838f4e7 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -108,12 +108,6 @@ typedef struct PlannedStmt /* rtable indexes of target relations for INSERT/UPDATE/DELETE */ List *resultRelations; /* integer list of RT indexes, or NIL */ - /* - * rtable indexes of partitioned table roots that are UPDATE/DELETE - * targets; needed for trigger firing. - */ - List *rootResultRelations; - List *appendRelations; /* list of AppendRelInfo nodes */ List *subplans; /* Plan trees for SubPlan expressions; note @@ -395,8 +389,6 @@ typedef struct ModifyTable Index rootRelation; /* Root RT index, if target is partitioned */ bool partColsUpdated; /* some part key in hierarchy updated */ List *resultRelations; /* integer list of RT indexes */ - int resultRelIndex; /* index of first resultRel in plan's list */ - int rootResultRelIndex; /* index of the partitioned table root */ List *plans; /* plan(s) producing source data */ List *withCheckOptionLists; /* per-target-table WCO lists */ List *returningLists; /* per-target-table RETURNING tlists */ @@ -946,12 +938,20 @@ typedef struct ExternalScanInfo * When the plan node represents a foreign join, scan.scanrelid is zero and * fs_relids must be consulted to identify the join relation. (fs_relids * is valid for simple scans as well, but will always match scan.scanrelid.) + * + * If the FDW's PlanDirectModify() callback decides to repurpose a ForeignScan + * node to perform the UPDATE or DELETE operation directly in the remote + * server, it sets 'operation' and 'resultRelation' to identify the operation + * type and target relation. Note that these fields are only set if the + * modification is performed *fully* remotely; otherwise, the modification is + * driven by a local ModifyTable node and 'operation' is left to CMD_SELECT. * ---------------- */ typedef struct ForeignScan { Scan scan; CmdType operation; /* SELECT/INSERT/UPDATE/DELETE */ + Index resultRelation; /* direct modification target's RT index */ Oid fs_server; /* OID of foreign server */ List *fdw_exprs; /* expressions that FDW may evaluate */ List *fdw_private; /* private data for FDW */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 62261fe1975a..69c88825b9ec 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -886,6 +886,9 @@ typedef struct SubPlan /* * AlternativeSubPlan - expression node for a choice among SubPlans * + * This is used only transiently during planning: by the time the plan + * reaches the executor, all AlternativeSubPlan nodes have been removed. + * * The subplans are given as a List so that the node definition need not * change if there's ever more than two alternatives. For the moment, * though, there are always exactly two; and the first one is the fast-start diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index dd1c5c41a21c..8c295520b6a6 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -16,6 +16,8 @@ #ifndef COST_H #define COST_H +#include + #include "nodes/pathnodes.h" #include "nodes/plannodes.h" @@ -33,6 +35,14 @@ #define DEFAULT_EFFECTIVE_CACHE_SIZE 524288 /* measured in pages */ +/* + * Maximum value for row estimates. We cap row estimates to this to help + * ensure that costs based on these estimates remain within the range of what + * double can represent. add_path() wouldn't act sanely given infinite or NaN + * cost values. + */ +#define MAXIMUM_ROWCOUNT 1e100 + typedef enum { CONSTRAINT_EXCLUSION_OFF, /* do not use c_e */ @@ -49,11 +59,18 @@ static inline double clamp_row_est(double nrows) { /* - * Force estimate to be at least one row, to make explain output look - * better and to avoid possible divide-by-zero when interpolating costs. + * Avoid infinite and NaN row estimates. Costs derived from such values + * are going to be useless. Also force the estimate to be at least one + * row, to make explain output look better and to avoid possible + * divide-by-zero when interpolating costs. * CDB: Don't round to integer. */ - return (nrows < 1.0) ? 1.0 : nrows; + if (nrows > MAXIMUM_ROWCOUNT || isnan(nrows)) + nrows = MAXIMUM_ROWCOUNT; + else if (nrows < 1.0) + nrows = 1.0; + + return nrows; } diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index d2acf248e460..8fd61b44203a 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -136,6 +136,7 @@ extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root, Relids rel, bool create_it); extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel); +extern Expr *find_em_expr_usable_for_sorting_rel(EquivalenceClass *ec, RelOptInfo *rel); extern void generate_base_implied_equalities(PlannerInfo *root); extern List *generate_join_implied_equalities(PlannerInfo *root, Relids join_relids, @@ -150,6 +151,8 @@ extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2); extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, ForeignKeyOptInfo *fkinfo, int colno); +extern RestrictInfo *find_derived_clause_for_ec_member(EquivalenceClass *ec, + EquivalenceMember *em); extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 599886363642..72642257c8ee 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -118,16 +118,16 @@ extern void create_lateral_join_info(PlannerInfo *root); extern List *deconstruct_jointree(PlannerInfo *root); extern void distribute_restrictinfo_to_rels(PlannerInfo *root, RestrictInfo *restrictinfo); -extern void process_implied_equality(PlannerInfo *root, - Oid opno, - Oid collation, - Expr *item1, - Expr *item2, - Relids qualscope, - Relids nullable_relids, - Index security_level, - bool below_outer_join, - bool both_const); +extern RestrictInfo *process_implied_equality(PlannerInfo *root, + Oid opno, + Oid collation, + Expr *item1, + Expr *item2, + Relids qualscope, + Relids nullable_relids, + Index security_level, + bool below_outer_join, + bool both_const); extern RestrictInfo *build_implied_join_equality(Oid opno, Oid collation, Expr *item1, diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index ed2e16a72c3e..157441061582 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -46,6 +46,4 @@ extern void applyLockingClause(Query *qry, Index rtindex, extern List *BuildOnConflictExcludedTargetlist(Relation targetrel, Index exclRelIndex); -extern void fill_extraUpdatedCols(RangeTblEntry *target_rte, TupleDesc tupdesc); - #endif /* ANALYZE_H */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 8c75f0189b57..b8d7d6ac7844 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -19,521 +19,521 @@ /* there is deliberately not an #ifndef KWLIST_H here */ /* - * List of keyword (name, token-value, category) entries. + * List of keyword (name, token-value, category, bare-label-status) entries. * * Note: gen_keywordlist.pl requires the entries to appear in ASCII order. */ -/* name, value, category */ -PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD) -PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD) -PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD) -PG_KEYWORD("active", ACTIVE, UNRESERVED_KEYWORD) -PG_KEYWORD("add", ADD_P, UNRESERVED_KEYWORD) -PG_KEYWORD("admin", ADMIN, UNRESERVED_KEYWORD) -PG_KEYWORD("after", AFTER, UNRESERVED_KEYWORD) -PG_KEYWORD("aggregate", AGGREGATE, UNRESERVED_KEYWORD) -PG_KEYWORD("all", ALL, RESERVED_KEYWORD) -PG_KEYWORD("also", ALSO, UNRESERVED_KEYWORD) -PG_KEYWORD("alter", ALTER, UNRESERVED_KEYWORD) -PG_KEYWORD("always", ALWAYS, UNRESERVED_KEYWORD) -PG_KEYWORD("analyse", ANALYSE, RESERVED_KEYWORD) /* British spelling */ -PG_KEYWORD("analyze", ANALYZE, RESERVED_KEYWORD) -PG_KEYWORD("and", AND, RESERVED_KEYWORD) -PG_KEYWORD("any", ANY, RESERVED_KEYWORD) -PG_KEYWORD("array", ARRAY, RESERVED_KEYWORD) -PG_KEYWORD("as", AS, RESERVED_KEYWORD) -PG_KEYWORD("asc", ASC, RESERVED_KEYWORD) -PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD) -PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD) -PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD) -PG_KEYWORD("at", AT, UNRESERVED_KEYWORD) -PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD) -PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD) -PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD) -PG_KEYWORD("before", BEFORE, UNRESERVED_KEYWORD) -PG_KEYWORD("begin", BEGIN_P, UNRESERVED_KEYWORD) -PG_KEYWORD("between", BETWEEN, COL_NAME_KEYWORD) -PG_KEYWORD("bigint", BIGINT, COL_NAME_KEYWORD) -PG_KEYWORD("binary", BINARY, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("bit", BIT, COL_NAME_KEYWORD) -PG_KEYWORD("boolean", BOOLEAN_P, COL_NAME_KEYWORD) -PG_KEYWORD("both", BOTH, RESERVED_KEYWORD) -PG_KEYWORD("by", BY, UNRESERVED_KEYWORD) -PG_KEYWORD("cache", CACHE, UNRESERVED_KEYWORD) -PG_KEYWORD("call", CALL, UNRESERVED_KEYWORD) -PG_KEYWORD("called", CALLED, UNRESERVED_KEYWORD) -PG_KEYWORD("cascade", CASCADE, UNRESERVED_KEYWORD) -PG_KEYWORD("cascaded", CASCADED, UNRESERVED_KEYWORD) -PG_KEYWORD("case", CASE, RESERVED_KEYWORD) -PG_KEYWORD("cast", CAST, RESERVED_KEYWORD) -PG_KEYWORD("catalog", CATALOG_P, UNRESERVED_KEYWORD) -PG_KEYWORD("chain", CHAIN, UNRESERVED_KEYWORD) -PG_KEYWORD("char", CHAR_P, COL_NAME_KEYWORD) -PG_KEYWORD("character", CHARACTER, COL_NAME_KEYWORD) -PG_KEYWORD("characteristics", CHARACTERISTICS, UNRESERVED_KEYWORD) -PG_KEYWORD("check", CHECK, RESERVED_KEYWORD) -PG_KEYWORD("checkpoint", CHECKPOINT, UNRESERVED_KEYWORD) -PG_KEYWORD("class", CLASS, UNRESERVED_KEYWORD) -PG_KEYWORD("close", CLOSE, UNRESERVED_KEYWORD) -PG_KEYWORD("cluster", CLUSTER, UNRESERVED_KEYWORD) -PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD) -PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD) -PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD) -PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD) -PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD) -PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD) -PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD) -PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD) -PG_KEYWORD("concurrency", CONCURRENCY, UNRESERVED_KEYWORD) -PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD) -PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD) -PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD) -PG_KEYWORD("constraint", CONSTRAINT, RESERVED_KEYWORD) -PG_KEYWORD("constraints", CONSTRAINTS, UNRESERVED_KEYWORD) -PG_KEYWORD("contains", CONTAINS, UNRESERVED_KEYWORD) -PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD) -PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD) -PG_KEYWORD("coordinator", COORDINATOR, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD) -PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD) -PG_KEYWORD("cpu_rate_limit", CPU_RATE_LIMIT, UNRESERVED_KEYWORD) -PG_KEYWORD("cpuset", CPUSET, UNRESERVED_KEYWORD) -PG_KEYWORD("create", CREATE, RESERVED_KEYWORD) -PG_KEYWORD("createexttable", CREATEEXTTABLE, UNRESERVED_KEYWORD) -PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("csv", CSV, UNRESERVED_KEYWORD) -PG_KEYWORD("cube", CUBE, UNRESERVED_KEYWORD) -PG_KEYWORD("current", CURRENT_P, UNRESERVED_KEYWORD) -PG_KEYWORD("current_catalog", CURRENT_CATALOG, RESERVED_KEYWORD) -PG_KEYWORD("current_date", CURRENT_DATE, RESERVED_KEYWORD) -PG_KEYWORD("current_role", CURRENT_ROLE, RESERVED_KEYWORD) -PG_KEYWORD("current_schema", CURRENT_SCHEMA, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD) -PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD) -PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD) -PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD) -PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD) -PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD) -PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD) -PG_KEYWORD("day", DAY_P, UNRESERVED_KEYWORD) -PG_KEYWORD("deallocate", DEALLOCATE, UNRESERVED_KEYWORD) -PG_KEYWORD("dec", DEC, COL_NAME_KEYWORD) -PG_KEYWORD("decimal", DECIMAL_P, COL_NAME_KEYWORD) -PG_KEYWORD("declare", DECLARE, UNRESERVED_KEYWORD) -PG_KEYWORD("decode", DECODE, RESERVED_KEYWORD) -PG_KEYWORD("default", DEFAULT, RESERVED_KEYWORD) -PG_KEYWORD("defaults", DEFAULTS, UNRESERVED_KEYWORD) -PG_KEYWORD("deferrable", DEFERRABLE, RESERVED_KEYWORD) -PG_KEYWORD("deferred", DEFERRED, UNRESERVED_KEYWORD) -PG_KEYWORD("definer", DEFINER, UNRESERVED_KEYWORD) -PG_KEYWORD("delete", DELETE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD) -PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD) -PG_KEYWORD("deny", DENY, UNRESERVED_KEYWORD) -PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD) -PG_KEYWORD("desc", DESC, RESERVED_KEYWORD) -PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD) -PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD) -PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD) -PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD) -PG_KEYWORD("distributed", DISTRIBUTED, RESERVED_KEYWORD) -PG_KEYWORD("do", DO, RESERVED_KEYWORD) -PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD) -PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD) -PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD) -PG_KEYWORD("dxl", DXL, UNRESERVED_KEYWORD) -PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD) -PG_KEYWORD("else", ELSE, RESERVED_KEYWORD) -PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD) -PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD) -PG_KEYWORD("end", END_P, RESERVED_KEYWORD) -PG_KEYWORD("endpoint", ENDPOINT, UNRESERVED_KEYWORD) -PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD) -PG_KEYWORD("errors", ERRORS, UNRESERVED_KEYWORD) -PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD) -PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD) -PG_KEYWORD("every", EVERY, UNRESERVED_KEYWORD) /* Reserved in standard */ -PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD) -PG_KEYWORD("exchange", EXCHANGE, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("exclude", EXCLUDE, RESERVED_KEYWORD) -PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD) -PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD) -PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD) -PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD) -PG_KEYWORD("expand", EXPAND, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("explain", EXPLAIN, UNRESERVED_KEYWORD) -PG_KEYWORD("expression", EXPRESSION, UNRESERVED_KEYWORD) -PG_KEYWORD("extension", EXTENSION, UNRESERVED_KEYWORD) -PG_KEYWORD("external", EXTERNAL, UNRESERVED_KEYWORD) -PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD) -PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD) -PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD) -PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD) -PG_KEYWORD("fields", FIELDS, UNRESERVED_KEYWORD) -PG_KEYWORD("fill", FILL, UNRESERVED_KEYWORD) -PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD) -PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD) -PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD) -PG_KEYWORD("following", FOLLOWING, RESERVED_KEYWORD) /* Unreserved in standard */ -PG_KEYWORD("for", FOR, RESERVED_KEYWORD) -PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD) -PG_KEYWORD("foreign", FOREIGN, RESERVED_KEYWORD) -PG_KEYWORD("format", FORMAT, UNRESERVED_KEYWORD) -PG_KEYWORD("forward", FORWARD, UNRESERVED_KEYWORD) -PG_KEYWORD("freeze", FREEZE, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("from", FROM, RESERVED_KEYWORD) -PG_KEYWORD("full", FULL, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("fullscan", FULLSCAN, UNRESERVED_KEYWORD) -PG_KEYWORD("function", FUNCTION, UNRESERVED_KEYWORD) -PG_KEYWORD("functions", FUNCTIONS, UNRESERVED_KEYWORD) -PG_KEYWORD("generated", GENERATED, UNRESERVED_KEYWORD) -PG_KEYWORD("global", GLOBAL, UNRESERVED_KEYWORD) -PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD) -PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD) -PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD) -PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD) -PG_KEYWORD("group_id", GROUP_ID, COL_NAME_KEYWORD) -PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD) -PG_KEYWORD("groups", GROUPS, UNRESERVED_KEYWORD) -PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD) -PG_KEYWORD("hash", HASH, UNRESERVED_KEYWORD) -PG_KEYWORD("having", HAVING, RESERVED_KEYWORD) -PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD) -PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD) -PG_KEYWORD("host", HOST, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("hour", HOUR_P, UNRESERVED_KEYWORD) -PG_KEYWORD("identity", IDENTITY_P, UNRESERVED_KEYWORD) -PG_KEYWORD("if", IF_P, UNRESERVED_KEYWORD) -PG_KEYWORD("ignore", IGNORE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("ilike", ILIKE, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("immediate", IMMEDIATE, UNRESERVED_KEYWORD) -PG_KEYWORD("immutable", IMMUTABLE, UNRESERVED_KEYWORD) -PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD) -PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD) -PG_KEYWORD("in", IN_P, RESERVED_KEYWORD) -PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD) -PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD) -PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD) -PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD) -PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD) -PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD) -PG_KEYWORD("inherits", INHERITS, UNRESERVED_KEYWORD) -PG_KEYWORD("initially", INITIALLY, RESERVED_KEYWORD) -PG_KEYWORD("initplan", INITPLAN, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("inline", INLINE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("inner", INNER_P, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("inout", INOUT, COL_NAME_KEYWORD) -PG_KEYWORD("input", INPUT_P, UNRESERVED_KEYWORD) -PG_KEYWORD("insensitive", INSENSITIVE, UNRESERVED_KEYWORD) -PG_KEYWORD("insert", INSERT, UNRESERVED_KEYWORD) -PG_KEYWORD("instead", INSTEAD, UNRESERVED_KEYWORD) -PG_KEYWORD("int", INT_P, COL_NAME_KEYWORD) -PG_KEYWORD("integer", INTEGER, COL_NAME_KEYWORD) -PG_KEYWORD("intersect", INTERSECT, RESERVED_KEYWORD) -PG_KEYWORD("interval", INTERVAL, COL_NAME_KEYWORD) -PG_KEYWORD("into", INTO, RESERVED_KEYWORD) -PG_KEYWORD("invoker", INVOKER, UNRESERVED_KEYWORD) -PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD) -PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD) -PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD) -PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD) -PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("last", LAST_P, UNRESERVED_KEYWORD) -PG_KEYWORD("lateral", LATERAL_P, RESERVED_KEYWORD) -PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD) -PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD) -PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD) -PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD) -PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD) -PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD) -PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD) -PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD) -PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD) /* Reserved in standard */ -PG_KEYWORD("localtime", LOCALTIME, RESERVED_KEYWORD) -PG_KEYWORD("localtimestamp", LOCALTIMESTAMP, RESERVED_KEYWORD) -PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD) -PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD) -PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD) -PG_KEYWORD("log", LOG_P, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD) -PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD) -PG_KEYWORD("master", MASTER, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD) -PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD) -PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD) -PG_KEYWORD("median", MEDIAN, COL_NAME_KEYWORD) -PG_KEYWORD("memory_limit", MEMORY_LIMIT, UNRESERVED_KEYWORD) -PG_KEYWORD("memory_shared_quota", MEMORY_SHARED_QUOTA, UNRESERVED_KEYWORD) -PG_KEYWORD("memory_spill_ratio", MEMORY_SPILL_RATIO, UNRESERVED_KEYWORD) -PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD) -PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD) -PG_KEYWORD("missing", MISSING, UNRESERVED_KEYWORD) -PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD) -PG_KEYWORD("modifies", MODIFIES, UNRESERVED_KEYWORD) -PG_KEYWORD("month", MONTH_P, UNRESERVED_KEYWORD) -PG_KEYWORD("move", MOVE, UNRESERVED_KEYWORD) -PG_KEYWORD("name", NAME_P, UNRESERVED_KEYWORD) -PG_KEYWORD("names", NAMES, UNRESERVED_KEYWORD) -PG_KEYWORD("national", NATIONAL, COL_NAME_KEYWORD) -PG_KEYWORD("natural", NATURAL, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("nchar", NCHAR, COL_NAME_KEYWORD) -PG_KEYWORD("new", NEW, UNRESERVED_KEYWORD) -PG_KEYWORD("newline", NEWLINE, UNRESERVED_KEYWORD) -PG_KEYWORD("next", NEXT, UNRESERVED_KEYWORD) -PG_KEYWORD("nfc", NFC, UNRESERVED_KEYWORD) -PG_KEYWORD("nfd", NFD, UNRESERVED_KEYWORD) -PG_KEYWORD("nfkc", NFKC, UNRESERVED_KEYWORD) -PG_KEYWORD("nfkd", NFKD, UNRESERVED_KEYWORD) -PG_KEYWORD("no", NO, UNRESERVED_KEYWORD) -PG_KEYWORD("nocreateexttable", NOCREATEEXTTABLE, UNRESERVED_KEYWORD) -PG_KEYWORD("none", NONE, COL_NAME_KEYWORD) -PG_KEYWORD("noovercommit", NOOVERCOMMIT, UNRESERVED_KEYWORD) -PG_KEYWORD("normalize", NORMALIZE, COL_NAME_KEYWORD) -PG_KEYWORD("normalized", NORMALIZED, UNRESERVED_KEYWORD) -PG_KEYWORD("not", NOT, RESERVED_KEYWORD) -PG_KEYWORD("nothing", NOTHING, UNRESERVED_KEYWORD) -PG_KEYWORD("notify", NOTIFY, UNRESERVED_KEYWORD) -PG_KEYWORD("notnull", NOTNULL, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("nowait", NOWAIT, UNRESERVED_KEYWORD) -PG_KEYWORD("null", NULL_P, RESERVED_KEYWORD) -PG_KEYWORD("nullif", NULLIF, COL_NAME_KEYWORD) -PG_KEYWORD("nulls", NULLS_P, UNRESERVED_KEYWORD) -PG_KEYWORD("numeric", NUMERIC, COL_NAME_KEYWORD) -PG_KEYWORD("object", OBJECT_P, UNRESERVED_KEYWORD) -PG_KEYWORD("of", OF, UNRESERVED_KEYWORD) -PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD) -PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD) -PG_KEYWORD("oids", OIDS, UNRESERVED_KEYWORD) -PG_KEYWORD("old", OLD, UNRESERVED_KEYWORD) -PG_KEYWORD("on", ON, RESERVED_KEYWORD) -PG_KEYWORD("only", ONLY, RESERVED_KEYWORD) -PG_KEYWORD("operator", OPERATOR, UNRESERVED_KEYWORD) -PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD) -PG_KEYWORD("options", OPTIONS, UNRESERVED_KEYWORD) -PG_KEYWORD("or", OR, RESERVED_KEYWORD) -PG_KEYWORD("order", ORDER, RESERVED_KEYWORD) -PG_KEYWORD("ordered", ORDERED, UNRESERVED_KEYWORD) -PG_KEYWORD("ordinality", ORDINALITY, UNRESERVED_KEYWORD) -PG_KEYWORD("others", OTHERS, UNRESERVED_KEYWORD) -PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD) -PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("over", OVER, UNRESERVED_KEYWORD) -PG_KEYWORD("overcommit", OVERCOMMIT, UNRESERVED_KEYWORD) -PG_KEYWORD("overlaps", OVERLAPS, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("overlay", OVERLAY, COL_NAME_KEYWORD) -PG_KEYWORD("overriding", OVERRIDING, UNRESERVED_KEYWORD) -PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD) -PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD) -PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD) -PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD) -PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD) -PG_KEYWORD("partition", PARTITION, RESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("partitions", PARTITIONS, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD) -PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD) -PG_KEYWORD("percent", PERCENT, UNRESERVED_KEYWORD) -PG_KEYWORD("persistently", PERSISTENTLY, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD) -PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD) -PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD) -PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD) -PG_KEYWORD("preceding", PRECEDING, RESERVED_KEYWORD) /* unreserved in standard */ -PG_KEYWORD("precision", PRECISION, COL_NAME_KEYWORD) -PG_KEYWORD("prepare", PREPARE, UNRESERVED_KEYWORD) -PG_KEYWORD("prepared", PREPARED, UNRESERVED_KEYWORD) -PG_KEYWORD("preserve", PRESERVE, UNRESERVED_KEYWORD) -PG_KEYWORD("primary", PRIMARY, RESERVED_KEYWORD) -PG_KEYWORD("prior", PRIOR, UNRESERVED_KEYWORD) -PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD) -PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD) -PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD) -PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD) -PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD) -PG_KEYWORD("protocol", PROTOCOL, UNRESERVED_KEYWORD) -PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD) -PG_KEYWORD("queue", QUEUE, UNRESERVED_KEYWORD) -PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD) -PG_KEYWORD("randomly", RANDOMLY, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD) -PG_KEYWORD("read", READ, UNRESERVED_KEYWORD) -PG_KEYWORD("readable", READABLE, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("reads", READS, UNRESERVED_KEYWORD) -PG_KEYWORD("real", REAL, COL_NAME_KEYWORD) -PG_KEYWORD("reassign", REASSIGN, UNRESERVED_KEYWORD) -PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD) -PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD) -PG_KEYWORD("ref", REF, UNRESERVED_KEYWORD) -PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD) -PG_KEYWORD("referencing", REFERENCING, UNRESERVED_KEYWORD) -PG_KEYWORD("refresh", REFRESH, UNRESERVED_KEYWORD) -PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD) -PG_KEYWORD("reject", REJECT_P, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD) -PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD) -PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD) -PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD) -PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD) -PG_KEYWORD("replicated", REPLICATED, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD) -PG_KEYWORD("resource", RESOURCE, UNRESERVED_KEYWORD) -PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD) -PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD) -PG_KEYWORD("retrieve", RETRIEVE, UNRESERVED_KEYWORD) -PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD) -PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD) -PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD) -PG_KEYWORD("right", RIGHT, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("role", ROLE, UNRESERVED_KEYWORD) -PG_KEYWORD("rollback", ROLLBACK, UNRESERVED_KEYWORD) -PG_KEYWORD("rollup", ROLLUP, UNRESERVED_KEYWORD) -PG_KEYWORD("rootpartition", ROOTPARTITION, UNRESERVED_KEYWORD) -PG_KEYWORD("routine", ROUTINE, UNRESERVED_KEYWORD) -PG_KEYWORD("routines", ROUTINES, UNRESERVED_KEYWORD) -PG_KEYWORD("row", ROW, COL_NAME_KEYWORD) -PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD) -PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD) -PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD) -PG_KEYWORD("scatter", SCATTER, RESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD) -PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD) -PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD) -PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD) -PG_KEYWORD("second", SECOND_P, UNRESERVED_KEYWORD) -PG_KEYWORD("security", SECURITY, UNRESERVED_KEYWORD) -PG_KEYWORD("segment", SEGMENT, UNRESERVED_KEYWORD) -PG_KEYWORD("segments", SEGMENTS, UNRESERVED_KEYWORD) -PG_KEYWORD("select", SELECT, RESERVED_KEYWORD) -PG_KEYWORD("sequence", SEQUENCE, UNRESERVED_KEYWORD) -PG_KEYWORD("sequences", SEQUENCES, UNRESERVED_KEYWORD) -PG_KEYWORD("serializable", SERIALIZABLE, UNRESERVED_KEYWORD) -PG_KEYWORD("server", SERVER, UNRESERVED_KEYWORD) -PG_KEYWORD("session", SESSION, UNRESERVED_KEYWORD) -PG_KEYWORD("session_user", SESSION_USER, RESERVED_KEYWORD) -PG_KEYWORD("set", SET, UNRESERVED_KEYWORD) -PG_KEYWORD("setof", SETOF, COL_NAME_KEYWORD) -PG_KEYWORD("sets", SETS, UNRESERVED_KEYWORD) -PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD) -PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD) -PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("simple", SIMPLE, UNRESERVED_KEYWORD) -PG_KEYWORD("skip", SKIP, UNRESERVED_KEYWORD) -PG_KEYWORD("smallint", SMALLINT, COL_NAME_KEYWORD) -PG_KEYWORD("snapshot", SNAPSHOT, UNRESERVED_KEYWORD) -PG_KEYWORD("some", SOME, RESERVED_KEYWORD) -PG_KEYWORD("split", SPLIT, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("sql", SQL_P, UNRESERVED_KEYWORD) -PG_KEYWORD("stable", STABLE, UNRESERVED_KEYWORD) -PG_KEYWORD("standalone", STANDALONE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("start", START, UNRESERVED_KEYWORD) -PG_KEYWORD("statement", STATEMENT, UNRESERVED_KEYWORD) -PG_KEYWORD("statistics", STATISTICS, UNRESERVED_KEYWORD) -PG_KEYWORD("stdin", STDIN, UNRESERVED_KEYWORD) -PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD) -PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD) -PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD) -PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD) -PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD) -PG_KEYWORD("subpartition", SUBPARTITION, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD) -PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD) -PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD) -PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD) -PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD) -PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD) -PG_KEYWORD("table", TABLE, RESERVED_KEYWORD) -PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD) -PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("tablespace", TABLESPACE, UNRESERVED_KEYWORD) -PG_KEYWORD("temp", TEMP, UNRESERVED_KEYWORD) -PG_KEYWORD("template", TEMPLATE, UNRESERVED_KEYWORD) -PG_KEYWORD("temporary", TEMPORARY, UNRESERVED_KEYWORD) -PG_KEYWORD("text", TEXT_P, UNRESERVED_KEYWORD) -PG_KEYWORD("then", THEN, RESERVED_KEYWORD) -PG_KEYWORD("threshold", THRESHOLD, UNRESERVED_KEYWORD) /* GPDB */ -PG_KEYWORD("ties", TIES, UNRESERVED_KEYWORD) -PG_KEYWORD("time", TIME, COL_NAME_KEYWORD) -PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD) -PG_KEYWORD("to", TO, RESERVED_KEYWORD) -PG_KEYWORD("trailing", TRAILING, RESERVED_KEYWORD) -PG_KEYWORD("transaction", TRANSACTION, UNRESERVED_KEYWORD) -PG_KEYWORD("transform", TRANSFORM, UNRESERVED_KEYWORD) -PG_KEYWORD("treat", TREAT, COL_NAME_KEYWORD) -PG_KEYWORD("trigger", TRIGGER, UNRESERVED_KEYWORD) -PG_KEYWORD("trim", TRIM, COL_NAME_KEYWORD) -PG_KEYWORD("true", TRUE_P, RESERVED_KEYWORD) -PG_KEYWORD("truncate", TRUNCATE, UNRESERVED_KEYWORD) -PG_KEYWORD("trusted", TRUSTED, UNRESERVED_KEYWORD) -PG_KEYWORD("type", TYPE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("types", TYPES_P, UNRESERVED_KEYWORD) -PG_KEYWORD("uescape", UESCAPE, UNRESERVED_KEYWORD) -PG_KEYWORD("unbounded", UNBOUNDED, RESERVED_KEYWORD) /* Unreserved in standard */ -PG_KEYWORD("uncommitted", UNCOMMITTED, UNRESERVED_KEYWORD) -PG_KEYWORD("unencrypted", UNENCRYPTED, UNRESERVED_KEYWORD) -PG_KEYWORD("union", UNION, RESERVED_KEYWORD) -PG_KEYWORD("unique", UNIQUE, RESERVED_KEYWORD) -PG_KEYWORD("unknown", UNKNOWN, UNRESERVED_KEYWORD) -PG_KEYWORD("unlisten", UNLISTEN, UNRESERVED_KEYWORD) -PG_KEYWORD("unlogged", UNLOGGED, UNRESERVED_KEYWORD) -PG_KEYWORD("until", UNTIL, UNRESERVED_KEYWORD) -PG_KEYWORD("update", UPDATE, UNRESERVED_KEYWORD) -PG_KEYWORD("user", USER, RESERVED_KEYWORD) -PG_KEYWORD("using", USING, RESERVED_KEYWORD) -PG_KEYWORD("vacuum", VACUUM, UNRESERVED_KEYWORD) -PG_KEYWORD("valid", VALID, UNRESERVED_KEYWORD) -PG_KEYWORD("validate", VALIDATE, UNRESERVED_KEYWORD) -PG_KEYWORD("validation", VALIDATION, UNRESERVED_KEYWORD) -PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD) -PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD) -PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD) -PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD) -PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD) -PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD) -PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD) -PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD) -PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD) -PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD) -PG_KEYWORD("web", WEB, UNRESERVED_KEYWORD) -PG_KEYWORD("when", WHEN, RESERVED_KEYWORD) -PG_KEYWORD("where", WHERE, RESERVED_KEYWORD) -PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD) -PG_KEYWORD("window", WINDOW, RESERVED_KEYWORD) -PG_KEYWORD("with", WITH, RESERVED_KEYWORD) -PG_KEYWORD("within", WITHIN, UNRESERVED_KEYWORD) -PG_KEYWORD("without", WITHOUT, UNRESERVED_KEYWORD) -PG_KEYWORD("work", WORK, UNRESERVED_KEYWORD) -PG_KEYWORD("wrapper", WRAPPER, UNRESERVED_KEYWORD) -PG_KEYWORD("writable", WRITABLE, UNRESERVED_KEYWORD) -PG_KEYWORD("write", WRITE, UNRESERVED_KEYWORD) -PG_KEYWORD("xml", XML_P, UNRESERVED_KEYWORD) -PG_KEYWORD("xmlattributes", XMLATTRIBUTES, COL_NAME_KEYWORD) -PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD) -PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD) -PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD) -PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD) -PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD) -PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD) -PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD) -PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD) -PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD) -PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD) -PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD) -PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD) -PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD) +/* name, value, category, is-bare-label */ +PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("active", ACTIVE, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("add", ADD_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("admin", ADMIN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("after", AFTER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("aggregate", AGGREGATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("all", ALL, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("also", ALSO, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("alter", ALTER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("always", ALWAYS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("analyse", ANALYSE, RESERVED_KEYWORD, BARE_LABEL) /* British spelling */ +PG_KEYWORD("analyze", ANALYZE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("and", AND, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("any", ANY, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("array", ARRAY, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("as", AS, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("asc", ASC, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("at", AT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("before", BEFORE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("begin", BEGIN_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("between", BETWEEN, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("bigint", BIGINT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("binary", BINARY, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("bit", BIT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("boolean", BOOLEAN_P, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("both", BOTH, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("by", BY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cache", CACHE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("call", CALL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("called", CALLED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cascade", CASCADE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cascaded", CASCADED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("case", CASE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cast", CAST, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("catalog", CATALOG_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("chain", CHAIN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("char", CHAR_P, COL_NAME_KEYWORD, AS_LABEL) +PG_KEYWORD("character", CHARACTER, COL_NAME_KEYWORD, AS_LABEL) +PG_KEYWORD("characteristics", CHARACTERISTICS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("check", CHECK, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("checkpoint", CHECKPOINT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("class", CLASS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("close", CLOSE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cluster", CLUSTER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("concurrency", CONCURRENCY, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("constraint", CONSTRAINT, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("constraints", CONSTRAINTS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("contains", CONTAINS, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("coordinator", COORDINATOR, UNRESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cpu_rate_limit", CPU_RATE_LIMIT, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("cpuset", CPUSET, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("create", CREATE, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("createexttable", CREATEEXTTABLE, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("csv", CSV, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cube", CUBE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current", CURRENT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_catalog", CURRENT_CATALOG, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_date", CURRENT_DATE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_role", CURRENT_ROLE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_schema", CURRENT_SCHEMA, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("day", DAY_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("deallocate", DEALLOCATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("dec", DEC, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("decimal", DECIMAL_P, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("declare", DECLARE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("decode", DECODE, RESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("default", DEFAULT, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("defaults", DEFAULTS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("deferrable", DEFERRABLE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("deferred", DEFERRED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("definer", DEFINER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("delete", DELETE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("deny", DENY, UNRESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("desc", DESC, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("distributed", DISTRIBUTED, RESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("dxl", DXL, UNRESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("end", END_P, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("endpoint", ENDPOINT, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("errors", ERRORS, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("every", EVERY, UNRESERVED_KEYWORD, AS_LABEL) /* Reserved in standard */ +PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("exchange", EXCHANGE, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("exclude", EXCLUDE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("expand", EXPAND, UNRESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("explain", EXPLAIN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("expression", EXPRESSION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("extension", EXTENSION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("external", EXTERNAL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("fields", FIELDS, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("fill", FILL, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("following", FOLLOWING, RESERVED_KEYWORD, BARE_LABEL) /* Unreserved in standard */ +PG_KEYWORD("for", FOR, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("foreign", FOREIGN, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("format", FORMAT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("forward", FORWARD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("freeze", FREEZE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("from", FROM, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("full", FULL, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("fullscan", FULLSCAN, UNRESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("function", FUNCTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("functions", FUNCTIONS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("generated", GENERATED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("global", GLOBAL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("group_id", GROUP_ID, COL_NAME_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("groups", GROUPS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("hash", HASH, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("having", HAVING, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("host", HOST, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("hour", HOUR_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("identity", IDENTITY_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("if", IF_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("ignore", IGNORE_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("ilike", ILIKE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("immediate", IMMEDIATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("immutable", IMMUTABLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("inherits", INHERITS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("initially", INITIALLY, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("initplan", INITPLAN, UNRESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("inline", INLINE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("inner", INNER_P, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("inout", INOUT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("input", INPUT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("insensitive", INSENSITIVE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("insert", INSERT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("instead", INSTEAD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("int", INT_P, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("integer", INTEGER, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("intersect", INTERSECT, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("interval", INTERVAL, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("into", INTO, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("invoker", INVOKER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL) +PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("last", LAST_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("lateral", LATERAL_P, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD, BARE_LABEL) /* Reserved in standard */ +PG_KEYWORD("localtime", LOCALTIME, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("localtimestamp", LOCALTIMESTAMP, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("log", LOG_P, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("master", MASTER, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("median", MEDIAN, COL_NAME_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("memory_limit", MEMORY_LIMIT, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("memory_shared_quota", MEMORY_SHARED_QUOTA, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("memory_spill_ratio", MEMORY_SPILL_RATIO, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("missing", MISSING, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("modifies", MODIFIES, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("month", MONTH_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("move", MOVE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("name", NAME_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("names", NAMES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("national", NATIONAL, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("natural", NATURAL, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("nchar", NCHAR, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("new", NEW, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("newline", NEWLINE, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("next", NEXT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nfc", NFC, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nfd", NFD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nfkc", NFKC, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nfkd", NFKD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("no", NO, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nocreateexttable", NOCREATEEXTTABLE, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("none", NONE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("noovercommit", NOOVERCOMMIT, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("normalize", NORMALIZE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("normalized", NORMALIZED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("not", NOT, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nothing", NOTHING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("notify", NOTIFY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("notnull", NOTNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL) +PG_KEYWORD("nowait", NOWAIT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("null", NULL_P, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nullif", NULLIF, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("nulls", NULLS_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("numeric", NUMERIC, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("object", OBJECT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("of", OF, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("oids", OIDS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("old", OLD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("on", ON, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("only", ONLY, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("operator", OPERATOR, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("options", OPTIONS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("or", OR, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("order", ORDER, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("ordered", ORDERED, UNRESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("ordinality", ORDINALITY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("others", OTHERS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("over", OVER, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("overcommit", OVERCOMMIT, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("overlaps", OVERLAPS, TYPE_FUNC_NAME_KEYWORD, AS_LABEL) +PG_KEYWORD("overlay", OVERLAY, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("overriding", OVERRIDING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("partition", PARTITION, RESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("partitions", PARTITIONS, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("percent", PERCENT, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("persistently", PERSISTENTLY, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("preceding", PRECEDING, RESERVED_KEYWORD, BARE_LABEL) /* unreserved in standard */ +PG_KEYWORD("precision", PRECISION, COL_NAME_KEYWORD, AS_LABEL) +PG_KEYWORD("prepare", PREPARE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("prepared", PREPARED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("preserve", PRESERVE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("primary", PRIMARY, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("prior", PRIOR, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("protocol", PROTOCOL, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("queue", QUEUE, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("randomly", RANDOMLY, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("read", READ, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("readable", READABLE, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("reads", READS, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("real", REAL, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("reassign", REASSIGN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("ref", REF, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("referencing", REFERENCING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("refresh", REFRESH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("reject", REJECT_P, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("replicated", REPLICATED, UNRESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("resource", RESOURCE, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("retrieve", RETRIEVE, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("right", RIGHT, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("role", ROLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("rollback", ROLLBACK, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("rollup", ROLLUP, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("rootpartition", ROOTPARTITION, UNRESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("routine", ROUTINE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("routines", ROUTINES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("row", ROW, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("scatter", SCATTER, RESERVED_KEYWORD, AS_LABEL) /* GPDB */ +PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("second", SECOND_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("security", SECURITY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("segment", SEGMENT, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("segments", SEGMENTS, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("select", SELECT, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("sequence", SEQUENCE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("sequences", SEQUENCES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("serializable", SERIALIZABLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("server", SERVER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("session", SESSION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("session_user", SESSION_USER, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("set", SET, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("setof", SETOF, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("sets", SETS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("simple", SIMPLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("skip", SKIP, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("smallint", SMALLINT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("snapshot", SNAPSHOT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("some", SOME, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("split", SPLIT, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("sql", SQL_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("stable", STABLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("standalone", STANDALONE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("start", START, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("statement", STATEMENT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("statistics", STATISTICS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("stdin", STDIN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("subpartition", SUBPARTITION, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("tablespace", TABLESPACE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("temp", TEMP, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("template", TEMPLATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("temporary", TEMPORARY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("text", TEXT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("then", THEN, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("threshold", THRESHOLD, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("ties", TIES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("time", TIME, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("to", TO, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("trailing", TRAILING, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("transaction", TRANSACTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("transform", TRANSFORM, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("treat", TREAT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("trigger", TRIGGER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("trim", TRIM, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("true", TRUE_P, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("truncate", TRUNCATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("trusted", TRUSTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("type", TYPE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("types", TYPES_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("uescape", UESCAPE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unbounded", UNBOUNDED, RESERVED_KEYWORD, BARE_LABEL) /* Unreserved in standard */ +PG_KEYWORD("uncommitted", UNCOMMITTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unencrypted", UNENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("union", UNION, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("unique", UNIQUE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unknown", UNKNOWN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unlisten", UNLISTEN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unlogged", UNLOGGED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("until", UNTIL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("update", UPDATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("user", USER, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("using", USING, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("vacuum", VACUUM, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("valid", VALID, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("validate", VALIDATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("validation", VALIDATION, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("web", WEB, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("window", WINDOW, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("with", WITH, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("within", WITHIN, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("without", WITHOUT, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("work", WORK, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("wrapper", WRAPPER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("writable", WRITABLE, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */ +PG_KEYWORD("write", WRITE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("xml", XML_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlattributes", XMLATTRIBUTES, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD, BARE_LABEL) diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index 913e32f01b30..1a9ab069d61c 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -79,6 +79,8 @@ extern void fixup_unknown_vars_in_exprlist(ParseState *pstate, List *exprlist); extern void fixup_unknown_vars_in_targetlist(ParseState *pstate, List *targetlist); +extern int32 select_common_typmod(ParseState *pstate, List *exprs, Oid common_type); + extern bool check_generic_type_consistency(const Oid *actual_arg_types, const Oid *declared_arg_types, int nargs); diff --git a/src/include/parser/parse_oper.h b/src/include/parser/parse_oper.h index bcd861e43ac3..09695a2765cf 100644 --- a/src/include/parser/parse_oper.h +++ b/src/include/parser/parse_oper.h @@ -31,8 +31,6 @@ extern Oid LookupOperWithArgs(ObjectWithArgs *oper, bool noError); /* NB: the selected operator may require coercion of the input types! */ extern Operator oper(ParseState *pstate, List *op, Oid arg1, Oid arg2, bool noError, int location); -extern Operator right_oper(ParseState *pstate, List *op, Oid arg, - bool noError, int location); extern Operator left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 638057d4c31d..0a6e8f84f7a9 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -34,6 +34,8 @@ extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt); extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent, PartitionBoundSpec *spec); +extern List *expandTableLikeClause(RangeVar *heapRel, + TableLikeClause *table_like_clause); extern IndexStmt *generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, const struct AttrMap *attmap, diff --git a/src/include/parser/scansup.h b/src/include/parser/scansup.h index 7a6ee529ae0c..5bc426660df6 100644 --- a/src/include/parser/scansup.h +++ b/src/include/parser/scansup.h @@ -1,8 +1,7 @@ /*------------------------------------------------------------------------- * * scansup.h - * scanner support routines. used by both the bootstrap lexer - * as well as the normal lexer + * scanner support routines used by the core lexer * * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -15,8 +14,6 @@ #ifndef SCANSUP_H #define SCANSUP_H -extern char *scanstr(const char *s); - extern char *downcase_truncate_identifier(const char *ident, int len, bool warn); diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h index dfc720720b93..192b0b1e2ad8 100644 --- a/src/include/partitioning/partbounds.h +++ b/src/include/partitioning/partbounds.h @@ -12,10 +12,9 @@ #define PARTBOUNDS_H #include "fmgr.h" -#include "nodes/parsenodes.h" -#include "nodes/pg_list.h" +#include "parser/parse_node.h" #include "partitioning/partdefs.h" -#include "utils/relcache.h" + struct RelOptInfo; /* avoid including pathnodes.h here */ @@ -98,7 +97,8 @@ extern PartitionBoundInfo partition_bounds_merge(int partnatts, List **inner_parts); extern bool partitions_are_ordered(PartitionBoundInfo boundinfo, int nparts); extern void check_new_partition_bound(char *relname, Relation parent, - PartitionBoundSpec *spec); + PartitionBoundSpec *spec, + ParseState *pstate); extern void check_default_partition_contents(Relation parent, Relation defaultRel, PartitionBoundSpec *new_spec); diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 7f36b43cfccf..6f5dc6caaabe 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -58,19 +58,22 @@ typedef enum StatMsgType PGSTAT_MTYPE_RESETSHAREDCOUNTER, PGSTAT_MTYPE_RESETSINGLECOUNTER, PGSTAT_MTYPE_RESETSLRUCOUNTER, + PGSTAT_MTYPE_RESETREPLSLOTCOUNTER, PGSTAT_MTYPE_AUTOVAC_START, PGSTAT_MTYPE_VACUUM, PGSTAT_MTYPE_ANALYZE, PGSTAT_MTYPE_ARCHIVER, PGSTAT_MTYPE_QUEUESTAT, /* GPDB */ PGSTAT_MTYPE_BGWRITER, + PGSTAT_MTYPE_WAL, PGSTAT_MTYPE_SLRU, PGSTAT_MTYPE_FUNCSTAT, PGSTAT_MTYPE_FUNCPURGE, PGSTAT_MTYPE_RECOVERYCONFLICT, PGSTAT_MTYPE_TEMPFILE, PGSTAT_MTYPE_DEADLOCK, - PGSTAT_MTYPE_CHECKSUMFAILURE + PGSTAT_MTYPE_CHECKSUMFAILURE, + PGSTAT_MTYPE_REPLSLOT, } StatMsgType; /* ---------- @@ -125,7 +128,8 @@ typedef struct PgStat_TableCounts typedef enum PgStat_Shared_Reset_Target { RESET_ARCHIVER, - RESET_BGWRITER + RESET_BGWRITER, + RESET_WAL } PgStat_Shared_Reset_Target; /* Possible object types for resetting single counters */ @@ -359,6 +363,18 @@ typedef struct PgStat_MsgResetslrucounter int m_index; } PgStat_MsgResetslrucounter; +/* ---------- + * PgStat_MsgResetreplslotcounter Sent by the backend to tell the collector + * to reset replication slot counter(s) + * ---------- + */ +typedef struct PgStat_MsgResetreplslotcounter +{ + PgStat_MsgHdr m_hdr; + char m_slotname[NAMEDATALEN]; + bool clearall; +} PgStat_MsgResetreplslotcounter; + /* ---------- * PgStat_MsgAutovacStart Sent by the autovacuum daemon to signal * that a database is going to be processed @@ -454,6 +470,16 @@ typedef struct PgStat_MsgBgWriter PgStat_Counter m_checkpoint_sync_time; } PgStat_MsgBgWriter; +/* ---------- + * PgStat_MsgWal Sent by backends and background processes to update WAL statistics. + * ---------- + */ +typedef struct PgStat_MsgWal +{ + PgStat_MsgHdr m_hdr; + PgStat_Counter m_wal_buffers_full; +} PgStat_MsgWal; + /* ---------- * PgStat_MsgSLRU Sent by a backend to update SLRU statistics. * ---------- @@ -471,6 +497,25 @@ typedef struct PgStat_MsgSLRU PgStat_Counter m_truncate; } PgStat_MsgSLRU; +/* ---------- + * PgStat_MsgReplSlot Sent by a backend or a wal sender to update replication + * slot statistics. + * ---------- + */ +typedef struct PgStat_MsgReplSlot +{ + PgStat_MsgHdr m_hdr; + char m_slotname[NAMEDATALEN]; + bool m_drop; + PgStat_Counter m_spill_txns; + PgStat_Counter m_spill_count; + PgStat_Counter m_spill_bytes; + PgStat_Counter m_stream_txns; + PgStat_Counter m_stream_count; + PgStat_Counter m_stream_bytes; +} PgStat_MsgReplSlot; + + /* ---------- * PgStat_MsgRecoveryConflict Sent by the backend upon recovery conflict * ---------- @@ -609,12 +654,14 @@ typedef union PgStat_Msg PgStat_MsgResetsharedcounter msg_resetsharedcounter; PgStat_MsgResetsinglecounter msg_resetsinglecounter; PgStat_MsgResetslrucounter msg_resetslrucounter; + PgStat_MsgResetreplslotcounter msg_resetreplslotcounter; PgStat_MsgAutovacStart msg_autovacuum_start; PgStat_MsgVacuum msg_vacuum; PgStat_MsgAnalyze msg_analyze; PgStat_MsgArchiver msg_archiver; PgStat_MsgQueuestat msg_queuestat; /* GPDB */ PgStat_MsgBgWriter msg_bgwriter; + PgStat_MsgWal msg_wal; PgStat_MsgSLRU msg_slru; PgStat_MsgFuncstat msg_funcstat; PgStat_MsgFuncpurge msg_funcpurge; @@ -622,6 +669,7 @@ typedef union PgStat_Msg PgStat_MsgDeadlock msg_deadlock; PgStat_MsgTempFile msg_tempfile; PgStat_MsgChecksumFailure msg_checksumfailure; + PgStat_MsgReplSlot msg_replslot; } PgStat_Msg; @@ -633,7 +681,7 @@ typedef union PgStat_Msg * ------------------------------------------------------------ */ -#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9D +#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9F /* ---------- * PgStat_StatDBEntry The collector's data per database @@ -806,6 +854,15 @@ typedef struct PgStat_GlobalStats TimestampTz stat_reset_timestamp; } PgStat_GlobalStats; +/* + * WAL statistics kept in the stats collector + */ +typedef struct PgStat_WalStats +{ + PgStat_Counter wal_buffers_full; + TimestampTz stat_reset_timestamp; +} PgStat_WalStats; + /* * SLRU statistics kept in the stats collector */ @@ -821,6 +878,20 @@ typedef struct PgStat_SLRUStats TimestampTz stat_reset_timestamp; } PgStat_SLRUStats; +/* + * Replication slot statistics kept in the stats collector + */ +typedef struct PgStat_ReplSlotStats +{ + char slotname[NAMEDATALEN]; + PgStat_Counter spill_txns; + PgStat_Counter spill_count; + PgStat_Counter spill_bytes; + PgStat_Counter stream_txns; + PgStat_Counter stream_count; + PgStat_Counter stream_bytes; + TimestampTz stat_reset_timestamp; +} PgStat_ReplSlotStats; /* ---------- * Backend states @@ -999,6 +1070,7 @@ typedef enum WAIT_EVENT_BASEBACKUP_READ = PG_WAIT_IO, WAIT_EVENT_BUFFILE_READ, WAIT_EVENT_BUFFILE_WRITE, + WAIT_EVENT_BUFFILE_TRUNCATE, WAIT_EVENT_CONTROL_FILE_READ, WAIT_EVENT_CONTROL_FILE_SYNC, WAIT_EVENT_CONTROL_FILE_SYNC_UPDATE, @@ -1064,7 +1136,11 @@ typedef enum WAIT_EVENT_WAL_READ, WAIT_EVENT_WAL_SYNC, WAIT_EVENT_WAL_SYNC_METHOD_ASSIGN, - WAIT_EVENT_WAL_WRITE + WAIT_EVENT_WAL_WRITE, + WAIT_EVENT_LOGICAL_CHANGES_READ, + WAIT_EVENT_LOGICAL_CHANGES_WRITE, + WAIT_EVENT_LOGICAL_SUBXACT_READ, + WAIT_EVENT_LOGICAL_SUBXACT_WRITE } WaitEventIO; /* ---------- @@ -1348,6 +1424,11 @@ extern bool pgstat_collect_queuelevel; */ extern PgStat_MsgBgWriter BgWriterStats; +/* + * WAL statistics counter is updated by backends and background processes + */ +extern PgStat_MsgWal WalStats; + /* * Updated by pgstat_count_buffer_*_time macros */ @@ -1388,6 +1469,7 @@ extern void pgstat_reset_counters(void); extern void pgstat_reset_shared_counters(const char *); extern void pgstat_reset_single_counter(Oid objectid, PgStat_Single_Reset_Type type); extern void pgstat_reset_slru_counter(const char *); +extern void pgstat_reset_replslot_counter(const char *name); extern void pgstat_report_autovac(Oid dboid); extern void pgstat_report_vacuum(Oid tableoid, bool shared, @@ -1400,6 +1482,9 @@ extern void pgstat_report_recovery_conflict(int reason); extern void pgstat_report_deadlock(void); extern void pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount); extern void pgstat_report_checksum_failure(void); +extern void pgstat_report_replslot(const char *slotname, int spilltxns, int spillcount, + int spillbytes, int streamtxns, int streamcount, int streambytes); +extern void pgstat_report_replslot_drop(const char *slotname); extern void pgstat_initialize(void); extern void pgstat_bestart(void); @@ -1642,6 +1727,7 @@ extern void pgstat_twophase_postabort(TransactionId xid, uint16 info, extern void pgstat_send_archiver(const char *xlog, bool failed); extern void pgstat_send_bgwriter(void); +extern void pgstat_send_wal(void); struct CdbDispatchResults; struct pg_result; @@ -1667,7 +1753,9 @@ extern PgStat_StatFuncEntry *pgstat_fetch_stat_funcentry(Oid funcid); extern int pgstat_fetch_stat_numbackends(void); extern PgStat_ArchiverStats *pgstat_fetch_stat_archiver(void); extern PgStat_GlobalStats *pgstat_fetch_global(void); +extern PgStat_WalStats *pgstat_fetch_stat_wal(void); extern PgStat_SLRUStats *pgstat_fetch_slru(void); +extern PgStat_ReplSlotStats *pgstat_fetch_replslot(int *nslots_p); extern void pgstat_count_slru_page_zeroed(int slru_idx); extern void pgstat_count_slru_page_hit(int slru_idx); diff --git a/src/include/port.h b/src/include/port.h index ce486c8a0d9d..39fbea0d8d1b 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -99,6 +99,27 @@ extern void pgfnames_cleanup(char **filenames); ) #endif +/* + * This macro provides a centralized list of all errnos that identify + * hard failure of a previously-established network connection. + * The macro is intended to be used in a switch statement, in the form + * "case ALL_CONNECTION_FAILURE_ERRNOS:". + * + * Note: this groups EPIPE and ECONNRESET, which we take to indicate a + * probable server crash, with other errors that indicate loss of network + * connectivity without proving much about the server's state. Places that + * are actually reporting errors typically single out EPIPE and ECONNRESET, + * while allowing the network failures to be reported generically. + */ +#define ALL_CONNECTION_FAILURE_ERRNOS \ + EPIPE: \ + case ECONNRESET: \ + case ECONNABORTED: \ + case EHOSTDOWN: \ + case EHOSTUNREACH: \ + case ENETDOWN: \ + case ENETRESET: \ + case ENETUNREACH /* Portable locale initialization (in exec.c) */ extern void set_pglocale_pgservice(const char *argv0, const char *app); @@ -215,10 +236,6 @@ extern char *pg_strerror_r(int errnum, char *buf, size_t buflen); /* Wrap strsignal(), or provide our own version if necessary */ extern const char *pg_strsignal(int signum); -/* Portable prompt handling */ -extern void simple_prompt(const char *prompt, char *destination, size_t destlen, - bool echo); - extern int pclose_check(FILE *stream); /* Global variable holding time zone information. */ diff --git a/src/include/port/atomics/arch-ppc.h b/src/include/port/atomics/arch-ppc.h index fdfe0d0cd5f4..a82ae38c1d6e 100644 --- a/src/include/port/atomics/arch-ppc.h +++ b/src/include/port/atomics/arch-ppc.h @@ -32,14 +32,14 @@ typedef struct pg_atomic_uint32 } pg_atomic_uint32; /* 64bit atomics are only supported in 64bit mode */ -#ifdef __64BIT__ +#if SIZEOF_VOID_P >= 8 #define PG_HAVE_ATOMIC_U64_SUPPORT typedef struct pg_atomic_uint64 { volatile uint64 value pg_attribute_aligned(8); } pg_atomic_uint64; -#endif /* __64BIT__ */ +#endif /* * This mimics gcc __atomic_compare_exchange_n(..., __ATOMIC_SEQ_CST), but @@ -72,14 +72,6 @@ typedef struct pg_atomic_uint64 * the __asm__. (That would remove the freedom to eliminate dead stores when * the caller ignores "expected", but few callers do.) * - * The cmpwi variant may be dead code. In gcc 7.2.0, - * __builtin_constant_p(*expected) always reports false. - * __atomic_compare_exchange_n() does use cmpwi when its second argument - * points to a constant. Hence, using this instead of - * __atomic_compare_exchange_n() nominally penalizes the generic.h - * pg_atomic_test_set_flag_impl(). Modern GCC will use the generic-gcc.h - * version, making the penalty theoretical only. - * * Recognizing constant "newval" would be superfluous, because there's no * immediate-operand version of stwcx. */ @@ -94,7 +86,8 @@ pg_atomic_compare_exchange_u32_impl(volatile pg_atomic_uint32 *ptr, #ifdef HAVE_I_CONSTRAINT__BUILTIN_CONSTANT_P if (__builtin_constant_p(*expected) && - *expected <= PG_INT16_MAX && *expected >= PG_INT16_MIN) + (int32) *expected <= PG_INT16_MAX && + (int32) *expected >= PG_INT16_MIN) __asm__ __volatile__( " sync \n" " lwarx %0,0,%5 \n" @@ -183,7 +176,8 @@ pg_atomic_compare_exchange_u64_impl(volatile pg_atomic_uint64 *ptr, /* Like u32, but s/lwarx/ldarx/; s/stwcx/stdcx/; s/cmpw/cmpd/ */ #ifdef HAVE_I_CONSTRAINT__BUILTIN_CONSTANT_P if (__builtin_constant_p(*expected) && - *expected <= PG_INT16_MAX && *expected >= PG_INT16_MIN) + (int64) *expected <= PG_INT16_MAX && + (int64) *expected >= PG_INT16_MIN) __asm__ __volatile__( " sync \n" " ldarx %0,0,%5 \n" diff --git a/src/include/port/atomics/generic-gcc.h b/src/include/port/atomics/generic-gcc.h index 2d84305f26bc..1a3dce34ed37 100644 --- a/src/include/port/atomics/generic-gcc.h +++ b/src/include/port/atomics/generic-gcc.h @@ -10,9 +10,9 @@ * * Documentation: * * Legacy __sync Built-in Functions for Atomic Memory Access - * http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/_005f_005fsync-Builtins.html + * https://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/_005f_005fsync-Builtins.html * * Built-in functions for memory model aware atomic operations - * http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/_005f_005fatomic-Builtins.html + * https://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/_005f_005fatomic-Builtins.html * * src/include/port/atomics/generic-gcc.h * diff --git a/src/include/port/win32_msvc/dirent.h b/src/include/port/win32_msvc/dirent.h index 93c5f52c680d..1098d7e299ac 100644 --- a/src/include/port/win32_msvc/dirent.h +++ b/src/include/port/win32_msvc/dirent.h @@ -11,6 +11,7 @@ struct dirent { long d_ino; unsigned short d_reclen; + unsigned char d_type; unsigned short d_namlen; char d_name[MAXPGPATH]; }; @@ -21,4 +22,14 @@ DIR *opendir(const char *); struct dirent *readdir(DIR *); int closedir(DIR *); +/* File types for 'd_type'. */ +#define DT_UNKNOWN 0 +#define DT_FIFO 1 +#define DT_CHR 2 +#define DT_DIR 4 +#define DT_BLK 6 +#define DT_REG 8 +#define DT_LNK 10 +#define DT_SOCK 12 +#define DT_WHT 14 #endif diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h index 8b6576b23dc8..59c7f35e3dfa 100644 --- a/src/include/port/win32_port.h +++ b/src/include/port/win32_port.h @@ -51,7 +51,13 @@ #include #include #undef near -#include /* needed before sys/stat hacking below */ + +/* needed before sys/stat hacking below: */ +#define fstat microsoft_native_fstat +#define stat microsoft_native_stat +#include +#undef fstat +#undef stat /* Must be here to avoid conflicting with prototype in windows.h */ #define mkdir(a,b) mkdir(a) @@ -240,20 +246,34 @@ typedef int pid_t; * Supplement to . * * We must pull in sys/stat.h before this part, else our overrides lose. - */ -#define lstat(path, sb) stat(path, sb) - -/* + * * stat() is not guaranteed to set the st_size field on win32, so we - * redefine it to our own implementation that is. + * redefine it to our own implementation. See src/port/win32stat.c. * - * Some frontends don't need the size from stat, so if UNSAFE_STAT_OK - * is defined we don't bother with this. + * The struct stat is 32 bit in MSVC, so we redefine it as a copy of + * struct __stat64. This also fixes the struct size for MINGW builds. */ -#ifndef UNSAFE_STAT_OK -extern int pgwin32_safestat(const char *path, struct stat *buf); -#define stat(a,b) pgwin32_safestat(a,b) -#endif +struct stat /* This should match struct __stat64 */ +{ + _dev_t st_dev; + _ino_t st_ino; + unsigned short st_mode; + short st_nlink; + short st_uid; + short st_gid; + _dev_t st_rdev; + __int64 st_size; + __time64_t st_atime; + __time64_t st_mtime; + __time64_t st_ctime; +}; + +extern int _pgfstat64(int fileno, struct stat *buf); +extern int _pgstat64(const char *name, struct stat *buf); + +#define fstat(fileno, sb) _pgfstat64(fileno, sb) +#define stat(path, sb) _pgstat64(path, sb) +#define lstat(path, sb) _pgstat64(path, sb) /* These macros are not provided by older MinGW, nor by MSVC */ #ifndef S_IRUSR @@ -349,8 +369,16 @@ extern int pgwin32_safestat(const char *path, struct stat *buf); #define EADDRINUSE WSAEADDRINUSE #undef EADDRNOTAVAIL #define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#undef EHOSTDOWN +#define EHOSTDOWN WSAEHOSTDOWN #undef EHOSTUNREACH #define EHOSTUNREACH WSAEHOSTUNREACH +#undef ENETDOWN +#define ENETDOWN WSAENETDOWN +#undef ENETRESET +#define ENETRESET WSAENETRESET +#undef ENETUNREACH +#define ENETUNREACH WSAENETUNREACH #undef ENOTCONN #define ENOTCONN WSAENOTCONN diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h index 45abc444b7a5..40bab7ee02df 100644 --- a/src/include/replication/logical.h +++ b/src/include/replication/logical.h @@ -122,5 +122,6 @@ extern void LogicalConfirmReceivedLocation(XLogRecPtr lsn); extern bool filter_by_origin_cb_wrapper(LogicalDecodingContext *ctx, RepOriginId origin_id); extern void ResetLogicalStreamingState(void); +extern void UpdateDecodingStats(LogicalDecodingContext *ctx); #endif diff --git a/src/include/replication/logicalproto.h b/src/include/replication/logicalproto.h index 60a76bc85cf9..cca13dae964c 100644 --- a/src/include/replication/logicalproto.h +++ b/src/include/replication/logicalproto.h @@ -19,13 +19,46 @@ /* * Protocol capabilities * - * LOGICALREP_PROTO_VERSION_NUM is our native protocol and the greatest version - * we can support. LOGICALREP_PROTO_MIN_VERSION_NUM is the oldest version we + * LOGICALREP_PROTO_VERSION_NUM is our native protocol. + * LOGICALREP_PROTO_MAX_VERSION_NUM is the greatest version we can support. + * LOGICALREP_PROTO_MIN_VERSION_NUM is the oldest version we * have backwards compatibility for. The client requests protocol version at * connect time. + * + * LOGICALREP_PROTO_STREAM_VERSION_NUM is the minimum protocol version with + * support for streaming large transactions. */ #define LOGICALREP_PROTO_MIN_VERSION_NUM 1 #define LOGICALREP_PROTO_VERSION_NUM 1 +#define LOGICALREP_PROTO_STREAM_VERSION_NUM 2 +#define LOGICALREP_PROTO_MAX_VERSION_NUM LOGICALREP_PROTO_STREAM_VERSION_NUM + +/* + * Logical message types + * + * Used by logical replication wire protocol. + * + * Note: though this is an enum, the values are used to identify message types + * in logical replication protocol, which uses a single byte to identify a + * message type. Hence the values should be single byte wide and preferrably + * human readable characters. + */ +typedef enum LogicalRepMsgType +{ + LOGICAL_REP_MSG_BEGIN = 'B', + LOGICAL_REP_MSG_COMMIT = 'C', + LOGICAL_REP_MSG_ORIGIN = 'O', + LOGICAL_REP_MSG_INSERT = 'I', + LOGICAL_REP_MSG_UPDATE = 'U', + LOGICAL_REP_MSG_DELETE = 'D', + LOGICAL_REP_MSG_TRUNCATE = 'T', + LOGICAL_REP_MSG_RELATION = 'R', + LOGICAL_REP_MSG_TYPE = 'Y', + LOGICAL_REP_MSG_STREAM_START = 'S', + LOGICAL_REP_MSG_STREAM_END = 'E', + LOGICAL_REP_MSG_STREAM_COMMIT = 'c', + LOGICAL_REP_MSG_STREAM_ABORT = 'A' +} LogicalRepMsgType; /* * This struct stores a tuple received via logical replication. @@ -98,25 +131,44 @@ extern void logicalrep_read_commit(StringInfo in, extern void logicalrep_write_origin(StringInfo out, const char *origin, XLogRecPtr origin_lsn); extern char *logicalrep_read_origin(StringInfo in, XLogRecPtr *origin_lsn); -extern void logicalrep_write_insert(StringInfo out, Relation rel, - HeapTuple newtuple, bool binary); +extern void logicalrep_write_insert(StringInfo out, TransactionId xid, + Relation rel, HeapTuple newtuple, + bool binary); extern LogicalRepRelId logicalrep_read_insert(StringInfo in, LogicalRepTupleData *newtup); -extern void logicalrep_write_update(StringInfo out, Relation rel, HeapTuple oldtuple, +extern void logicalrep_write_update(StringInfo out, TransactionId xid, + Relation rel, HeapTuple oldtuple, HeapTuple newtuple, bool binary); extern LogicalRepRelId logicalrep_read_update(StringInfo in, bool *has_oldtuple, LogicalRepTupleData *oldtup, LogicalRepTupleData *newtup); -extern void logicalrep_write_delete(StringInfo out, Relation rel, - HeapTuple oldtuple, bool binary); +extern void logicalrep_write_delete(StringInfo out, TransactionId xid, + Relation rel, HeapTuple oldtuple, + bool binary); extern LogicalRepRelId logicalrep_read_delete(StringInfo in, LogicalRepTupleData *oldtup); -extern void logicalrep_write_truncate(StringInfo out, int nrelids, Oid relids[], +extern void logicalrep_write_truncate(StringInfo out, TransactionId xid, + int nrelids, Oid relids[], bool cascade, bool restart_seqs); extern List *logicalrep_read_truncate(StringInfo in, bool *cascade, bool *restart_seqs); -extern void logicalrep_write_rel(StringInfo out, Relation rel); +extern void logicalrep_write_rel(StringInfo out, TransactionId xid, + Relation rel); extern LogicalRepRelation *logicalrep_read_rel(StringInfo in); -extern void logicalrep_write_typ(StringInfo out, Oid typoid); +extern void logicalrep_write_typ(StringInfo out, TransactionId xid, + Oid typoid); extern void logicalrep_read_typ(StringInfo out, LogicalRepTyp *ltyp); +extern void logicalrep_write_stream_start(StringInfo out, TransactionId xid, + bool first_segment); +extern TransactionId logicalrep_read_stream_start(StringInfo in, + bool *first_segment); +extern void logicalrep_write_stream_stop(StringInfo out); +extern void logicalrep_write_stream_commit(StringInfo out, ReorderBufferTXN *txn, + XLogRecPtr commit_lsn); +extern TransactionId logicalrep_read_stream_commit(StringInfo out, + LogicalRepCommitData *commit_data); +extern void logicalrep_write_stream_abort(StringInfo out, TransactionId xid, + TransactionId subxid); +extern void logicalrep_read_stream_abort(StringInfo in, TransactionId *xid, + TransactionId *subxid); #endif /* LOGICAL_PROTO_H */ diff --git a/src/include/replication/logicalrelation.h b/src/include/replication/logicalrelation.h index a6b44b12bd1f..62ddd3c7a2ae 100644 --- a/src/include/replication/logicalrelation.h +++ b/src/include/replication/logicalrelation.h @@ -19,9 +19,16 @@ typedef struct LogicalRepRelMapEntry { LogicalRepRelation remoterel; /* key is remoterel.remoteid */ - /* Mapping to local relation, filled as needed. */ + /* + * Validity flag -- when false, revalidate all derived info at next + * logicalrep_rel_open. (While the localrel is open, we assume our lock + * on that rel ensures the info remains good.) + */ + bool localrelvalid; + + /* Mapping to local relation. */ Oid localreloid; /* local relation id */ - Relation localrel; /* relcache entry */ + Relation localrel; /* relcache entry (NULL when closed) */ AttrMap *attrmap; /* map of local attributes to remote ones */ bool updatable; /* Can apply updates/deletes? */ diff --git a/src/include/replication/message.h b/src/include/replication/message.h index 937addde4858..e97891ebcafa 100644 --- a/src/include/replication/message.h +++ b/src/include/replication/message.h @@ -23,9 +23,8 @@ typedef struct xl_logical_message bool transactional; /* is message transactional? */ Size prefix_size; /* length of prefix */ Size message_size; /* size of the message */ - char message[FLEXIBLE_ARRAY_MEMBER]; /* message including the null - * terminated prefix of length - * prefix_size */ + /* payload, including null-terminated prefix of length prefix_size */ + char message[FLEXIBLE_ARRAY_MEMBER]; } xl_logical_message; #define SizeOfLogicalMessage (offsetof(xl_logical_message, message)) diff --git a/src/include/replication/reorderbuffer.h b/src/include/replication/reorderbuffer.h index 1ae17d5f11fd..dfdda938b2a9 100644 --- a/src/include/replication/reorderbuffer.h +++ b/src/include/replication/reorderbuffer.h @@ -57,6 +57,7 @@ enum ReorderBufferChangeType REORDER_BUFFER_CHANGE_UPDATE, REORDER_BUFFER_CHANGE_DELETE, REORDER_BUFFER_CHANGE_MESSAGE, + REORDER_BUFFER_CHANGE_INVALIDATION, REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT, REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID, REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID, @@ -149,6 +150,13 @@ typedef struct ReorderBufferChange CommandId cmax; CommandId combocid; } tuplecid; + + /* Invalidation. */ + struct + { + uint32 ninvalidations; /* Number of messages */ + SharedInvalidationMessage *invalidations; /* invalidation message */ + } inval; } data; /* @@ -162,9 +170,10 @@ typedef struct ReorderBufferChange #define RBTXN_HAS_CATALOG_CHANGES 0x0001 #define RBTXN_IS_SUBXACT 0x0002 #define RBTXN_IS_SERIALIZED 0x0004 -#define RBTXN_IS_STREAMED 0x0008 -#define RBTXN_HAS_TOAST_INSERT 0x0010 -#define RBTXN_HAS_SPEC_INSERT 0x0020 +#define RBTXN_IS_SERIALIZED_CLEAR 0x0008 +#define RBTXN_IS_STREAMED 0x0010 +#define RBTXN_HAS_TOAST_INSERT 0x0020 +#define RBTXN_HAS_SPEC_INSERT 0x0040 /* Does the transaction have catalog changes? */ #define rbtxn_has_catalog_changes(txn) \ @@ -184,6 +193,12 @@ typedef struct ReorderBufferChange ((txn)->txn_flags & RBTXN_IS_SERIALIZED) != 0 \ ) +/* Has this transaction ever been spilled to disk? */ +#define rbtxn_is_serialized_clear(txn) \ +( \ + ((txn)->txn_flags & RBTXN_IS_SERIALIZED_CLEAR) != 0 \ +) + /* This transaction's changes has toast insert, without main table insert. */ #define rbtxn_has_toast_insert(txn) \ ( \ @@ -306,8 +321,8 @@ typedef struct ReorderBufferTXN uint64 nentries_mem; /* - * List of ReorderBufferChange structs, including new Snapshots and new - * CommandIds + * List of ReorderBufferChange structs, including new Snapshots, new + * CommandIds and command invalidation messages. */ dlist_head changes; @@ -525,6 +540,22 @@ struct ReorderBuffer /* memory accounting */ Size size; + + /* + * Statistics about transactions spilled to disk. + * + * A single transaction may be spilled repeatedly, which is why we keep + * two different counters. For spilling, the transaction counter includes + * both toplevel transactions and subtransactions. + */ + int64 spillTxns; /* number of transactions spilled to disk */ + int64 spillCount; /* spill-to-disk invocation counter */ + int64 spillBytes; /* amount of data spilled to disk */ + + /* Statistics about transactions streamed to the decoding output plugin */ + int64 streamTxns; /* number of transactions streamed */ + int64 streamCount; /* streaming invocation counter */ + int64 streamBytes; /* amount of data streamed */ }; diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h index 5b4075a7aeb7..633f419df8ff 100644 --- a/src/include/replication/slot.h +++ b/src/include/replication/slot.h @@ -212,6 +212,7 @@ extern XLogRecPtr ReplicationSlotsComputeLogicalRestartLSN(void); extern bool ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive); extern void ReplicationSlotsDropDBSlots(Oid dboid); extern void InvalidateObsoleteReplicationSlots(XLogSegNo oldestSegno); +extern ReplicationSlot *SearchNamedReplicationSlot(const char *name); extern void StartupReplicationSlots(void); extern void CheckPointReplicationSlots(void); diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h index bd988e38d8a1..36a0e982ea18 100644 --- a/src/include/replication/walreceiver.h +++ b/src/include/replication/walreceiver.h @@ -178,6 +178,7 @@ typedef struct uint32 proto_version; /* Logical protocol version */ List *publication_names; /* String list of publications */ bool binary; /* Ask publisher to use binary */ + bool streaming; /* Streaming of large transactions */ } logical; } proto; } WalRcvStreamOptions; diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h index eb2e7b1768ac..a18211f4a23c 100644 --- a/src/include/rewrite/rewriteHandler.h +++ b/src/include/rewrite/rewriteHandler.h @@ -26,6 +26,9 @@ extern Node *build_column_default(Relation rel, int attrno); extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte, Relation target_relation); +extern void fill_extraUpdatedCols(RangeTblEntry *target_rte, + Relation target_relation); + extern Query *get_view_query(Relation view); extern const char *view_query_is_auto_updatable(Query *viewquery, bool check_cols); diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h index dc5e9911a8b3..9f980e0fca28 100644 --- a/src/include/storage/buf_internals.h +++ b/src/include/storage/buf_internals.h @@ -211,7 +211,7 @@ typedef struct BufferDesc * Note that local buffer descriptors aren't forced to be aligned - as there's * no concurrent access to those it's unlikely to be beneficial. * - * We use 64bit as the cache line size here, because that's the most common + * We use a 64-byte cache line size here, because that's the most common * size. Making it bigger would be a waste of memory. Even if running on a * platform with either 32 or 128 byte line sizes, it's good to align to * boundaries and avoid false sharing. diff --git a/src/include/storage/buffile.h b/src/include/storage/buffile.h index 0e1b9b83d569..cc20305bc576 100644 --- a/src/include/storage/buffile.h +++ b/src/include/storage/buffile.h @@ -55,8 +55,10 @@ extern long BufFileAppend(BufFile *target, BufFile *source); extern BufFile *BufFileCreateShared(SharedFileSet *fileset, const char *name, struct workfile_set *work_set); extern void BufFileExportShared(BufFile *file); -extern BufFile *BufFileOpenShared(SharedFileSet *fileset, const char *name); +extern BufFile *BufFileOpenShared(SharedFileSet *fileset, const char *name, + int mode); extern void BufFileDeleteShared(SharedFileSet *fileset, const char *name); +extern void BufFileTruncateShared(BufFile *file, int fileno, off_t offset); extern void *BufFileReadFromBuffer(BufFile *file, size_t size); diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h index 35dfb89e27a9..3fd65033a32b 100644 --- a/src/include/storage/bufpage.h +++ b/src/include/storage/bufpage.h @@ -443,26 +443,36 @@ do { \ * extern declarations * ---------------------------------------------------------------- */ + +/* flags for PageAddItemExtended() */ #define PAI_OVERWRITE (1 << 0) #define PAI_IS_HEAP (1 << 1) +/* flags for PageIsVerifiedExtended() */ +#define PIV_LOG_WARNING (1 << 0) +#define PIV_REPORT_STAT (1 << 1) + #define PageAddItem(page, item, size, offsetNumber, overwrite, is_heap) \ PageAddItemExtended(page, item, size, offsetNumber, \ ((overwrite) ? PAI_OVERWRITE : 0) | \ ((is_heap) ? PAI_IS_HEAP : 0)) +#define PageIsVerified(page, blkno) \ + PageIsVerifiedExtended(page, blkno, \ + PIV_LOG_WARNING | PIV_REPORT_STAT) + /* - * Check that BLCKSZ is a multiple of sizeof(size_t). In PageIsVerified(), - * it is much faster to check if a page is full of zeroes using the native - * word size. Note that this assertion is kept within a header to make - * sure that StaticAssertDecl() works across various combinations of - * platforms and compilers. + * Check that BLCKSZ is a multiple of sizeof(size_t). In + * PageIsVerifiedExtended(), it is much faster to check if a page is + * full of zeroes using the native word size. Note that this assertion + * is kept within a header to make sure that StaticAssertDecl() works + * across various combinations of platforms and compilers. */ StaticAssertDecl(BLCKSZ == ((BLCKSZ / sizeof(size_t)) * sizeof(size_t)), "BLCKSZ has to be a multiple of sizeof(size_t)"); extern void PageInit(Page page, Size pageSize, Size specialSize); -extern bool PageIsVerified(Page page, BlockNumber blkno); +extern bool PageIsVerifiedExtended(Page page, BlockNumber blkno, int flags); extern OffsetNumber PageAddItemExtended(Page page, Item item, Size size, OffsetNumber offsetNumber, int flags); extern Page PageGetTempPage(Page page); diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h index 8b7e8d54755b..6b618c19b836 100644 --- a/src/include/storage/fd.h +++ b/src/include/storage/fd.h @@ -98,7 +98,7 @@ extern int64 FileDiskSize(File file); /* Operations used for sharing named temporary files */ extern File PathNameCreateTemporaryFile(const char *name, bool error_on_failure); -extern File PathNameOpenTemporaryFile(const char *name); +extern File PathNameOpenTemporaryFile(const char *path, int mode); extern bool PathNameDeleteTemporaryFile(const char *name, bool error_on_failure); extern void PathNameCreateTemporaryDir(const char *base, const char *name); extern void PathNameDeleteTemporaryDir(const char *name); diff --git a/src/include/storage/ipc.h b/src/include/storage/ipc.h index 4ec7b1ad21ce..408dcaef7b69 100644 --- a/src/include/storage/ipc.h +++ b/src/include/storage/ipc.h @@ -73,6 +73,7 @@ extern void before_shmem_exit(pg_on_exit_callback function, Datum arg); extern void cancel_before_shmem_exit(pg_on_exit_callback function, Datum arg); extern void on_exit_reset(void); extern void proc_exit_prepare(int code); +extern void check_on_shmem_exit_lists_are_empty(void); /* ipci.c */ extern PGDLLIMPORT shmem_startup_hook_type shmem_startup_hook; diff --git a/src/include/storage/sharedfileset.h b/src/include/storage/sharedfileset.h index 2d6cf077e51d..d5edb600af96 100644 --- a/src/include/storage/sharedfileset.h +++ b/src/include/storage/sharedfileset.h @@ -37,9 +37,11 @@ typedef struct SharedFileSet extern void SharedFileSetInit(SharedFileSet *fileset, dsm_segment *seg); extern void SharedFileSetAttach(SharedFileSet *fileset, dsm_segment *seg); extern File SharedFileSetCreate(SharedFileSet *fileset, const char *name); -extern File SharedFileSetOpen(SharedFileSet *fileset, const char *name); +extern File SharedFileSetOpen(SharedFileSet *fileset, const char *name, + int mode); extern bool SharedFileSetDelete(SharedFileSet *fileset, const char *name, bool error_on_failure); extern void SharedFileSetDeleteAll(SharedFileSet *fileset); +extern void SharedFileSetUnregister(SharedFileSet *input_fileset); #endif diff --git a/src/include/storage/sync.h b/src/include/storage/sync.h index a6e92b49cf2b..7a904c5c4ef4 100644 --- a/src/include/storage/sync.h +++ b/src/include/storage/sync.h @@ -35,7 +35,13 @@ typedef enum SyncRequestType typedef enum SyncRequestHandler { SYNC_HANDLER_MD = 0, /* md smgr */ - SYNC_HANDLER_AO = 1 + SYNC_HANDLER_AO = 1, + SYNC_HANDLER_DISTRIBUTED_CLOG, /* GGDB */ + SYNC_HANDLER_CLOG, + SYNC_HANDLER_COMMIT_TS, + SYNC_HANDLER_MULTIXACT_OFFSET, + SYNC_HANDLER_MULTIXACT_MEMBER, + SYNC_HANDLER_NONE } SyncRequestHandler; /* diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h index 13c614bfc5fd..201bbaf59cf5 100644 --- a/src/include/tcop/cmdtaglist.h +++ b/src/include/tcop/cmdtaglist.h @@ -158,7 +158,6 @@ PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false) PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false) PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false) PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false) -PG_CMDTAG(CMDTAG_DROP_REPLICATION_SLOT, "DROP REPLICATION SLOT", false, false, false) PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false) PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false) PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false) diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h index 125b4d5c1eff..a32e192a0afa 100644 --- a/src/include/tcop/dest.h +++ b/src/include/tcop/dest.h @@ -139,6 +139,7 @@ extern void BeginCommand(CommandTag commandTag, CommandDest dest); extern DestReceiver *CreateDestReceiver(CommandDest dest); extern void EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_output); +extern void EndReplicationCommand(const char *commandTag); /* Additional functions that go with destination management, more or less. */ diff --git a/src/include/tsearch/ts_locale.h b/src/include/tsearch/ts_locale.h index cc4bd9ab20d4..f1669fda2111 100644 --- a/src/include/tsearch/ts_locale.h +++ b/src/include/tsearch/ts_locale.h @@ -15,6 +15,7 @@ #include #include +#include "lib/stringinfo.h" #include "mb/pg_wchar.h" #include "utils/pg_locale.h" @@ -33,7 +34,9 @@ typedef struct FILE *fp; const char *filename; int lineno; - char *curline; + StringInfoData buf; /* current input line, in UTF-8 */ + char *curline; /* current input line, in DB's encoding */ + /* curline may be NULL, or equal to buf.data, or a palloc'd string */ ErrorContextCallback cb; } tsearch_readline_state; @@ -57,6 +60,4 @@ extern bool tsearch_readline_begin(tsearch_readline_state *stp, extern char *tsearch_readline(tsearch_readline_state *stp); extern void tsearch_readline_end(tsearch_readline_state *stp); -extern char *t_readline(FILE *fp); - #endif /* __TSLOCALE_H__ */ diff --git a/src/include/utils/date.h b/src/include/utils/date.h index 6bfc986ce5f1..f5935ec778d1 100644 --- a/src/include/utils/date.h +++ b/src/include/utils/date.h @@ -88,6 +88,9 @@ extern int32 anytime_typmod_check(bool istz, int32 typmod); extern double date2timestamp_no_overflow(DateADT dateVal); extern Timestamp date2timestamp_opt_overflow(DateADT dateVal, int *overflow); extern TimestampTz date2timestamptz_opt_overflow(DateADT dateVal, int *overflow); +extern int32 date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2); +extern int32 date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2); + extern void EncodeSpecialDate(DateADT dt, char *str); extern DateADT GetSQLCurrentDate(void); extern TimeTzADT *GetSQLCurrentTime(int32 typmod); diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 86096524d5d0..10ef1c20c279 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -166,6 +166,7 @@ extern bool ParseConfigDirectory(const char *includedir, ConfigVariable **head_p, ConfigVariable **tail_p); extern void FreeConfigVariables(ConfigVariable *list); +extern char *DeescapeQuotedString(const char *s); /* * The possible values of an enum variable are specified by an array of diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h index f1deb9beab04..bebf89b3c451 100644 --- a/src/include/utils/hsearch.h +++ b/src/include/utils/hsearch.h @@ -68,7 +68,6 @@ typedef struct HASHCTL long ssize; /* segment size */ long dsize; /* (initial) directory size */ long max_dsize; /* limit to dsize if dir size is limited */ - long ffactor; /* fill factor */ Size keysize; /* hash key length in bytes */ Size entrysize; /* total user element size in bytes */ HashValueFunc hash; /* hash function */ @@ -83,7 +82,6 @@ typedef struct HASHCTL #define HASH_PARTITION 0x0001 /* Hashtable is used w/partitioned locking */ #define HASH_SEGMENT 0x0002 /* Set segment size */ #define HASH_DIRSIZE 0x0004 /* Set directory size (initial and max) */ -#define HASH_FFACTOR 0x0008 /* Set fill factor */ #define HASH_ELEM 0x0010 /* Set keysize and entrysize */ #define HASH_BLOBS 0x0020 /* Select support functions for binary keys */ #define HASH_FUNCTION 0x0040 /* Set user defined hash function */ diff --git a/src/include/utils/logtape.h b/src/include/utils/logtape.h index f4c66909ae56..831a17925fbe 100644 --- a/src/include/utils/logtape.h +++ b/src/include/utils/logtape.h @@ -55,7 +55,8 @@ typedef struct TapeShare */ extern char * LogicalTapeGetBufFilename(const LogicalTapeSet *lts); -extern LogicalTapeSet *LogicalTapeSetCreate(int ntapes, TapeShare *shared, +extern LogicalTapeSet *LogicalTapeSetCreate(int ntapes, bool preallocate, + TapeShare *shared, SharedFileSet *fileset, int worker); extern void LogicalTapeSetClose(LogicalTapeSet *lts); extern void LogicalTapeSetForgetFreeSpace(LogicalTapeSet *lts); diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h index f1cc537aec67..e1d084eb9b9b 100644 --- a/src/include/utils/numeric.h +++ b/src/include/utils/numeric.h @@ -69,6 +69,8 @@ int32 numeric_maximum_size(int32 typmod); extern char *numeric_out_sci(Numeric num, int scale); extern char *numeric_normalize(Numeric num); +extern Numeric int64_to_numeric(int64 val); + extern Numeric numeric_add_opt_error(Numeric num1, Numeric num2, bool *have_error); extern Numeric numeric_sub_opt_error(Numeric num1, Numeric num2, diff --git a/src/include/utils/old_snapshot.h b/src/include/utils/old_snapshot.h new file mode 100644 index 000000000000..e6da1833a634 --- /dev/null +++ b/src/include/utils/old_snapshot.h @@ -0,0 +1,75 @@ +/*------------------------------------------------------------------------- + * + * old_snapshot.h + * Data structures for 'snapshot too old' + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/include/utils/old_snapshot.h + * + *------------------------------------------------------------------------- + */ + +#ifndef OLD_SNAPSHOT_H +#define OLD_SNAPSHOT_H + +#include "datatype/timestamp.h" +#include "storage/s_lock.h" + +/* + * Structure for dealing with old_snapshot_threshold implementation. + */ +typedef struct OldSnapshotControlData +{ + /* + * Variables for old snapshot handling are shared among processes and are + * only allowed to move forward. + */ + slock_t mutex_current; /* protect current_timestamp */ + TimestampTz current_timestamp; /* latest snapshot timestamp */ + slock_t mutex_latest_xmin; /* protect latest_xmin and next_map_update */ + TransactionId latest_xmin; /* latest snapshot xmin */ + TimestampTz next_map_update; /* latest snapshot valid up to */ + slock_t mutex_threshold; /* protect threshold fields */ + TimestampTz threshold_timestamp; /* earlier snapshot is old */ + TransactionId threshold_xid; /* earlier xid may be gone */ + + /* + * Keep one xid per minute for old snapshot error handling. + * + * Use a circular buffer with a head offset, a count of entries currently + * used, and a timestamp corresponding to the xid at the head offset. A + * count_used value of zero means that there are no times stored; a + * count_used value of OLD_SNAPSHOT_TIME_MAP_ENTRIES means that the buffer + * is full and the head must be advanced to add new entries. Use + * timestamps aligned to minute boundaries, since that seems less + * surprising than aligning based on the first usage timestamp. The + * latest bucket is effectively stored within latest_xmin. The circular + * buffer is updated when we get a new xmin value that doesn't fall into + * the same interval. + * + * It is OK if the xid for a given time slot is from earlier than + * calculated by adding the number of minutes corresponding to the + * (possibly wrapped) distance from the head offset to the time of the + * head entry, since that just results in the vacuuming of old tuples + * being slightly less aggressive. It would not be OK for it to be off in + * the other direction, since it might result in vacuuming tuples that are + * still expected to be there. + * + * Use of an SLRU was considered but not chosen because it is more + * heavyweight than is needed for this, and would probably not be any less + * code to implement. + * + * Persistence is not needed. + */ + int head_offset; /* subscript of oldest tracked time */ + TimestampTz head_timestamp; /* time corresponding to head xid */ + int count_used; /* how many slots are in use */ + TransactionId xid_by_minute[FLEXIBLE_ARRAY_MEMBER]; +} OldSnapshotControlData; + +extern PGDLLIMPORT volatile OldSnapshotControlData *oldSnapshotControl; + +#endif diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h index 9cb7d91ddfb1..96da132c031b 100644 --- a/src/include/utils/pg_locale.h +++ b/src/include/utils/pg_locale.h @@ -103,7 +103,7 @@ typedef struct pg_locale_struct *pg_locale_t; extern pg_locale_t pg_newlocale_from_collation(Oid collid); -extern char *get_collation_actual_version(char collprovider, const char *collcollate); +extern char *get_collation_version_for_oid(Oid collid); #ifdef USE_ICU extern int32_t icu_to_uchar(UChar **buff_uchar, const char *buff, size_t nbytes); diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b3ffa593d841..ef236d0511f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -68,6 +68,7 @@ typedef struct RelationData bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and * rd_replidindex) */ bool rd_statvalid; /* is rd_statlist valid? */ + bool rd_version_checked; /* has version check been done yet? */ /*---------- * rd_createSubid is the ID of the highest subtransaction the rel has diff --git a/src/include/utils/sortsupport.h b/src/include/utils/sortsupport.h index 264aec820b1c..fb262c6e8d42 100644 --- a/src/include/utils/sortsupport.h +++ b/src/include/utils/sortsupport.h @@ -272,5 +272,6 @@ extern void PrepareSortSupportComparisonShim(Oid cmpFunc, SortSupport ssup); extern void PrepareSortSupportFromOrderingOp(Oid orderingOp, SortSupport ssup); extern void PrepareSortSupportFromIndexRel(Relation indexRel, int16 strategy, SortSupport ssup); +extern void PrepareSortSupportFromGistIndexRel(Relation indexRel, SortSupport ssup); #endif /* SORTSUPPORT_H */ diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h index 44e11d0ff13a..7e9c520f84e3 100644 --- a/src/include/utils/timestamp.h +++ b/src/include/utils/timestamp.h @@ -125,6 +125,8 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2); extern TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow); +extern int32 timestamp_cmp_timestamptz_internal(Timestamp timestampVal, + TimestampTz dt2); extern int isoweek2j(int year, int week); extern void isoweek2date(int woy, int *year, int *mon, int *mday); diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h index 0576c584ded9..3b11d2bb7906 100644 --- a/src/include/utils/tuplesort.h +++ b/src/include/utils/tuplesort.h @@ -223,6 +223,10 @@ extern Tuplesortstate *tuplesort_begin_index_hash(Relation heapRel, uint32 max_buckets, int workMem, SortCoordinate coordinate, bool randomAccess); +extern Tuplesortstate *tuplesort_begin_index_gist(Relation heapRel, + Relation indexRel, + int workMem, SortCoordinate coordinate, + bool randomAccess); extern Tuplesortstate *tuplesort_begin_datum(Oid datumType, Oid sortOperator, Oid sortCollation, bool nullsFirstFlag, diff --git a/src/interfaces/ecpg/Makefile b/src/interfaces/ecpg/Makefile index 41460a17c964..a8f91e3dc2b3 100644 --- a/src/interfaces/ecpg/Makefile +++ b/src/interfaces/ecpg/Makefile @@ -5,7 +5,7 @@ include $(top_builddir)/src/Makefile.global SUBDIRS = include pgtypeslib ecpglib compatlib preproc # Suppress parallel build of subdirectories to avoid a bug in GNU make 3.82, cf -# http://savannah.gnu.org/bugs/?30653 +# https://savannah.gnu.org/bugs/?30653 # https://bugzilla.redhat.com/show_bug.cgi?id=835424 # (There are some other parallelism bugs in the subdirectory makefiles # themselves, but there's little point in fixing them as long as we have diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c index 9d61ae72506f..930b6adbe4fa 100644 --- a/src/interfaces/ecpg/ecpglib/execute.c +++ b/src/interfaces/ecpg/ecpglib/execute.c @@ -230,7 +230,7 @@ ecpg_is_type_an_array(int type, const struct statement *stmt, const struct varia return ECPG_ARRAY_ERROR; if (!ecpg_type_infocache_push(&(stmt->connection->cache_head), CIRCLEOID, ECPG_ARRAY_NONE, stmt->lineno)) return ECPG_ARRAY_ERROR; - if (!ecpg_type_infocache_push(&(stmt->connection->cache_head), CASHOID, ECPG_ARRAY_NONE, stmt->lineno)) + if (!ecpg_type_infocache_push(&(stmt->connection->cache_head), MONEYOID, ECPG_ARRAY_NONE, stmt->lineno)) return ECPG_ARRAY_ERROR; if (!ecpg_type_infocache_push(&(stmt->connection->cache_head), INETOID, ECPG_ARRAY_NONE, stmt->lineno)) return ECPG_ARRAY_ERROR; diff --git a/src/interfaces/ecpg/ecpglib/misc.c b/src/interfaces/ecpg/ecpglib/misc.c index a07d0dfb9fc2..1feb5c03e174 100644 --- a/src/interfaces/ecpg/ecpglib/misc.c +++ b/src/interfaces/ecpg/ecpglib/misc.c @@ -477,8 +477,8 @@ win32_pthread_once(volatile pthread_once_t *once, void (*fn) (void)) pthread_mutex_lock(&win32_pthread_once_lock); if (!*once) { - *once = true; fn(); + *once = true; } pthread_mutex_unlock(&win32_pthread_once_lock); } diff --git a/src/interfaces/ecpg/preproc/keywords.c b/src/interfaces/ecpg/preproc/keywords.c index f82764aeb97d..f1640d0062b6 100644 --- a/src/interfaces/ecpg/preproc/keywords.c +++ b/src/interfaces/ecpg/preproc/keywords.c @@ -29,7 +29,7 @@ #include "preproc_extern.h" #include "preproc.h" -#define PG_KEYWORD(kwname, value, category) value, +#define PG_KEYWORD(kwname, value, category, collabel) value, const uint16 SQLScanKeywordTokens[] = { #include "parser/kwlist.h" diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l index 466bbac6a7b0..91d8b635787b 100644 --- a/src/interfaces/ecpg/preproc/pgc.l +++ b/src/interfaces/ecpg/preproc/pgc.l @@ -623,11 +623,8 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+ } } -{xqdouble} { addlitchar('\''); } -{xqcquote} { - addlitchar('\\'); - addlitchar('\''); - } +{xqdouble} { addlit(yytext, yyleng); } +{xqcquote} { addlit(yytext, yyleng); } {xqinside} { addlit(yytext, yyleng); } {xeinside} { addlit(yytext, yyleng); @@ -718,7 +715,14 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+ BEGIN(state_before_str_start); if (literallen == 0) mmerror(PARSE_ERROR, ET_ERROR, "zero-length delimited identifier"); - /* The backend will truncate the identifier here. We do not as it does not change the result. */ + /* + * The server will truncate the identifier here. We do + * not, as (1) it does not change the result; (2) we don't + * know what NAMEDATALEN the server might use; (3) this + * code path is also taken for literal query strings in + * PREPARE and EXECUTE IMMEDIATE, which can certainly be + * longer than NAMEDATALEN. + */ base_yylval.str = mm_strdup(literalbuf); return CSTRING; } @@ -736,7 +740,7 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+ return UIDENT; } {xddouble} { - addlitchar('"'); + addlit(yytext, yyleng); } {xdinside} { addlit(yytext, yyleng); diff --git a/src/interfaces/ecpg/test/expected/preproc-strings.c b/src/interfaces/ecpg/test/expected/preproc-strings.c index e695007b1336..1e50cd36c382 100644 --- a/src/interfaces/ecpg/test/expected/preproc-strings.c +++ b/src/interfaces/ecpg/test/expected/preproc-strings.c @@ -45,7 +45,7 @@ int main(void) #line 13 "strings.pgc" - { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select 'abcdef' , N'abcdef' as foo , E'abc\\bdef' as \"foo\" , U&'d\\0061t\\0061' as U&\"foo\" , U&'d!+000061t!+000061' UESCAPE '!' , $foo$abc$def$foo$", ECPGt_EOIT, + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select 'abc''d\\ef' , N'abc''d\\ef' as foo , E'abc''d\\\\ef' as \"foo\"\"bar\" , U&'d\\0061t\\0061' as U&\"foo\"\"bar\" , U&'d!+000061t!+000061' UESCAPE '!' , $foo$abc$def$foo$", ECPGt_EOIT, ECPGt_char,&(s1),(long)0,(long)1,(1)*sizeof(char), ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_char,&(s2),(long)0,(long)1,(1)*sizeof(char), diff --git a/src/interfaces/ecpg/test/expected/preproc-strings.stderr b/src/interfaces/ecpg/test/expected/preproc-strings.stderr index dbc9e5c0b8db..4c3a8eee5aad 100644 --- a/src/interfaces/ecpg/test/expected/preproc-strings.stderr +++ b/src/interfaces/ecpg/test/expected/preproc-strings.stderr @@ -8,7 +8,7 @@ [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_process_output on line 13: OK: SET [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_execute on line 15: query: select 'abcdef' , N'abcdef' as foo , E'abc\bdef' as "foo" , U&'d\0061t\0061' as U&"foo" , U&'d!+000061t!+000061' UESCAPE '!' , $foo$abc$def$foo$; with 0 parameter(s) on connection ecpg1_regression +[NO_PID]: ecpg_execute on line 15: query: select 'abc''d\ef' , N'abc''d\ef' as foo , E'abc''d\\ef' as "foo""bar" , U&'d\0061t\0061' as U&"foo""bar" , U&'d!+000061t!+000061' UESCAPE '!' , $foo$abc$def$foo$; with 0 parameter(s) on connection ecpg1_regression [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_execute on line 15: using PQexec [NO_PID]: sqlca: code: 0, state: 00000 @@ -16,15 +16,15 @@ [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_store_result on line 15: allocating memory for 1 tuples [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_get_data on line 15: RESULT: abcdef offset: -1; array: no +[NO_PID]: ecpg_get_data on line 15: RESULT: abc'd\ef offset: -1; array: no [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_store_result on line 15: allocating memory for 1 tuples [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_get_data on line 15: RESULT: abcdef offset: -1; array: no +[NO_PID]: ecpg_get_data on line 15: RESULT: abc'd\ef offset: -1; array: no [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_store_result on line 15: allocating memory for 1 tuples [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_get_data on line 15: RESULT: abcdef offset: -1; array: no +[NO_PID]: ecpg_get_data on line 15: RESULT: abc'd\ef offset: -1; array: no [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_store_result on line 15: allocating memory for 1 tuples [NO_PID]: sqlca: code: 0, state: 00000 diff --git a/src/interfaces/ecpg/test/expected/preproc-strings.stdout b/src/interfaces/ecpg/test/expected/preproc-strings.stdout index 730d72dd64e1..1456b152d78e 100644 --- a/src/interfaces/ecpg/test/expected/preproc-strings.stdout +++ b/src/interfaces/ecpg/test/expected/preproc-strings.stdout @@ -1 +1 @@ -abcdef abcdef abcdef data data abc$def +abc'd\ef abc'd\ef abc'd\ef data data abc$def diff --git a/src/interfaces/ecpg/test/expected/sql-execute.c b/src/interfaces/ecpg/test/expected/sql-execute.c index 735eb98ead36..1b65051b5a25 100644 --- a/src/interfaces/ecpg/test/expected/sql-execute.c +++ b/src/interfaces/ecpg/test/expected/sql-execute.c @@ -77,8 +77,8 @@ if (sqlca.sqlcode < 0) sqlprint();} #line 26 "execute.pgc" - sprintf(command, "insert into test (name, amount, letter) values ('db: ''r1''', 1, 'f')"); - { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_exec_immediate, command, ECPGt_EOIT, ECPGt_EORT); + /* test handling of embedded quotes in EXECUTE IMMEDIATE "literal" */ + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_exec_immediate, "insert into test (name, \042amount\042, letter) values ('db: ''r1''', 1, 'f')", ECPGt_EOIT, ECPGt_EORT); #line 29 "execute.pgc" if (sqlca.sqlcode < 0) sqlprint();} diff --git a/src/interfaces/ecpg/test/expected/sql-execute.stderr b/src/interfaces/ecpg/test/expected/sql-execute.stderr index 96b46bd15847..d8bc3c6524ab 100644 --- a/src/interfaces/ecpg/test/expected/sql-execute.stderr +++ b/src/interfaces/ecpg/test/expected/sql-execute.stderr @@ -10,7 +10,7 @@ [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ECPGtrans on line 26: action "commit"; connection "main" [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_execute on line 29: query: insert into test (name, amount, letter) values ('db: ''r1''', 1, 'f'); with 0 parameter(s) on connection main +[NO_PID]: ecpg_execute on line 29: query: insert into test (name, "amount", letter) values ('db: ''r1''', 1, 'f'); with 0 parameter(s) on connection main [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_execute on line 29: using PQexec [NO_PID]: sqlca: code: 0, state: 00000 diff --git a/src/interfaces/ecpg/test/expected/thread-thread.c b/src/interfaces/ecpg/test/expected/thread-thread.c index a7e401570a40..0e75c47fab26 100644 --- a/src/interfaces/ecpg/test/expected/thread-thread.c +++ b/src/interfaces/ecpg/test/expected/thread-thread.c @@ -99,7 +99,7 @@ int main() #ifndef WIN32 pthread_create(&threads[n], NULL, test_thread, (void *) (n + 1)); #else - threads[n] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)test_thread, (void *) (n + 1), 0, NULL); + threads[n] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) (void (*) (void)) test_thread, (void *) (n + 1), 0, NULL); #endif } diff --git a/src/interfaces/ecpg/test/expected/thread-thread_implicit.c b/src/interfaces/ecpg/test/expected/thread-thread_implicit.c index 6c7adb062c80..0df2794530c9 100644 --- a/src/interfaces/ecpg/test/expected/thread-thread_implicit.c +++ b/src/interfaces/ecpg/test/expected/thread-thread_implicit.c @@ -99,7 +99,7 @@ int main() #ifndef WIN32 pthread_create(&threads[n], NULL, test_thread, (void *) (n + 1)); #else - threads[n] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) test_thread, (void *) (n+1), 0, NULL); + threads[n] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) (void (*) (void)) test_thread, (void *) (n+1), 0, NULL); #endif } diff --git a/src/interfaces/ecpg/test/pg_regress_ecpg.c b/src/interfaces/ecpg/test/pg_regress_ecpg.c index d4e92bafb29b..6e1d25b1f4a3 100644 --- a/src/interfaces/ecpg/test/pg_regress_ecpg.c +++ b/src/interfaces/ecpg/test/pg_regress_ecpg.c @@ -22,6 +22,7 @@ #include "common/string.h" #include "lib/stringinfo.h" + static void ecpg_filter(const char *sourcefile, const char *outfile) { @@ -160,6 +161,7 @@ ecpg_start_test(const char *testname, unsetenv("PGAPPNAME"); free(appnameenv); + free(testname_dash.data); return pid; diff --git a/src/interfaces/ecpg/test/preproc/strings.pgc b/src/interfaces/ecpg/test/preproc/strings.pgc index f004ddf6dc1e..25157f136c2a 100644 --- a/src/interfaces/ecpg/test/preproc/strings.pgc +++ b/src/interfaces/ecpg/test/preproc/strings.pgc @@ -12,10 +12,10 @@ int main(void) exec sql set standard_conforming_strings to on; - exec sql select 'abcdef', - N'abcdef' AS foo, - E'abc\bdef' AS "foo", - U&'d\0061t\0061' AS U&"foo", + exec sql select 'abc''d\ef', + N'abc''d\ef' AS foo, + E'abc''d\\ef' AS "foo""bar", + U&'d\0061t\0061' AS U&"foo""bar", U&'d!+000061t!+000061' uescape '!', $foo$abc$def$foo$ into :s1, :s2, :s3, :s4, :s5, :s6; diff --git a/src/interfaces/ecpg/test/sql/dyntest.pgc b/src/interfaces/ecpg/test/sql/dyntest.pgc index 5f02fd5dd695..0222c8985154 100644 --- a/src/interfaces/ecpg/test/sql/dyntest.pgc +++ b/src/interfaces/ecpg/test/sql/dyntest.pgc @@ -51,7 +51,7 @@ main () exec sql create table dyntest (name char (14), d float8, i int, bignumber int8, b boolean, comment text, day date); - exec sql insert into dyntest values ('first entry', 14.7, 14, 123045607890, true, 'The world''''s most advanced open source database.', '1987-07-14'); + exec sql insert into dyntest values ('first entry', 14.7, 14, 123045607890, true, 'The world''s most advanced open source database.', '1987-07-14'); exec sql insert into dyntest values ('second entry', 1407.87, 1407, 987065403210, false, 'The elephant never forgets.', '1999-11-5'); exec sql prepare MYQUERY from :QUERY; diff --git a/src/interfaces/ecpg/test/sql/execute.pgc b/src/interfaces/ecpg/test/sql/execute.pgc index cc9814e9bea7..43171bb77c99 100644 --- a/src/interfaces/ecpg/test/sql/execute.pgc +++ b/src/interfaces/ecpg/test/sql/execute.pgc @@ -25,8 +25,8 @@ exec sql end declare section; exec sql create table test (name char(8), amount int, letter char(1)); exec sql commit; - sprintf(command, "insert into test (name, amount, letter) values ('db: ''r1''', 1, 'f')"); - exec sql execute immediate :command; + /* test handling of embedded quotes in EXECUTE IMMEDIATE "literal" */ + exec sql execute immediate "insert into test (name, \042amount\042, letter) values ('db: ''r1''', 1, 'f')"; sprintf(command, "insert into test (name, amount, letter) values ('db: ''r1''', 2, 't')"); exec sql execute immediate :command; diff --git a/src/interfaces/ecpg/test/thread/thread.pgc b/src/interfaces/ecpg/test/thread/thread.pgc index e149b91d976b..e7d8c00af6f4 100644 --- a/src/interfaces/ecpg/test/thread/thread.pgc +++ b/src/interfaces/ecpg/test/thread/thread.pgc @@ -68,7 +68,7 @@ int main() #ifndef WIN32 pthread_create(&threads[n], NULL, test_thread, (void *) (n + 1)); #else - threads[n] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)test_thread, (void *) (n + 1), 0, NULL); + threads[n] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) (void (*) (void)) test_thread, (void *) (n + 1), 0, NULL); #endif } diff --git a/src/interfaces/ecpg/test/thread/thread_implicit.pgc b/src/interfaces/ecpg/test/thread/thread_implicit.pgc index 3209da22bc54..b4cae7e1aecf 100644 --- a/src/interfaces/ecpg/test/thread/thread_implicit.pgc +++ b/src/interfaces/ecpg/test/thread/thread_implicit.pgc @@ -68,7 +68,7 @@ int main() #ifndef WIN32 pthread_create(&threads[n], NULL, test_thread, (void *) (n + 1)); #else - threads[n] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) test_thread, (void *) (n+1), 0, NULL); + threads[n] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) (void (*) (void)) test_thread, (void *) (n+1), 0, NULL); #endif } diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index 712342679821..c35cb2409f49 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -89,6 +89,8 @@ SHLIB_PREREQS = submake-libpgport SHLIB_EXPORTS = exports.txt +PKG_CONFIG_REQUIRES_PRIVATE = libssl libcrypto + all: all-lib # Shared library stuff diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 51d93a204cc6..f1b25ff26155 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -22,6 +22,7 @@ * which only defines FRONTEND besides including "c.h" */ #include "c.h" +#include "common/fe_memutils.h" #ifndef WIN32 #include @@ -40,6 +41,7 @@ #include "fe-auth.h" #include "libpq-fe.h" #include "libpq-int.h" +#include "lib/stringinfo.h" #include "mb/pg_wchar.h" #include "pg_config_paths.h" #include "port/pg_bswap.h" @@ -3953,23 +3955,30 @@ makeEmptyPGconn(void) #ifdef WIN32 /* - * Make sure socket support is up and running. + * Make sure socket support is up and running in this process. + * + * Note: the Windows documentation says that we should eventually do a + * matching WSACleanup() call, but experience suggests that that is at + * least as likely to cause problems as fix them. So we don't. */ - WSADATA wsaData; + static bool wsastartup_done = false; - if (WSAStartup(MAKEWORD(1, 1), &wsaData)) - return NULL; + if (!wsastartup_done) + { + WSADATA wsaData; + + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) + return NULL; + wsastartup_done = true; + } + + /* Forget any earlier error */ WSASetLastError(0); -#endif +#endif /* WIN32 */ conn = (PGconn *) malloc(sizeof(PGconn)); if (conn == NULL) - { -#ifdef WIN32 - WSACleanup(); -#endif return conn; - } /* Zero all pointers and booleans */ MemSet(conn, 0, sizeof(PGconn)); @@ -4174,10 +4183,6 @@ freePGconn(PGconn *conn) termPQExpBuffer(&conn->workBuffer); free(conn); - -#ifdef WIN32 - WSACleanup(); -#endif } /* @@ -5169,8 +5174,6 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options, #endif /* USE_LDAP */ -#define MAXBUFSIZE 256 - /* * parseServiceInfo: if a service name has been given, look it up and absorb * connection options from it into *options. @@ -5257,11 +5260,14 @@ parseServiceFile(const char *serviceFile, PQExpBuffer errorMessage, bool *group_found) { - int linenr = 0, + int result = 0, + linenr = 0, i; FILE *f; - char buf[MAXBUFSIZE], - *line; + char *line; + StringInfoData linebuf; + + *group_found = false; f = fopen(serviceFile, "r"); if (f == NULL) @@ -5271,26 +5277,18 @@ parseServiceFile(const char *serviceFile, return 1; } - while ((line = fgets(buf, sizeof(buf), f)) != NULL) - { - int len; + initStringInfo(&linebuf); + while (pg_get_line_buf(f, &linebuf)) + { linenr++; - if (strlen(line) >= sizeof(buf) - 1) - { - fclose(f); - printfPQExpBuffer(errorMessage, - libpq_gettext("line %d too long in service file \"%s\"\n"), - linenr, - serviceFile); - return 2; - } - /* ignore whitespace at end of line, especially the newline */ - len = strlen(line); - while (len > 0 && isspace((unsigned char) line[len - 1])) - line[--len] = '\0'; + while (linebuf.len > 0 && + isspace((unsigned char) linebuf.data[linebuf.len - 1])) + linebuf.data[--linebuf.len] = '\0'; + + line = linebuf.data; /* ignore leading whitespace too */ while (*line && isspace((unsigned char) line[0])) @@ -5305,9 +5303,8 @@ parseServiceFile(const char *serviceFile, { if (*group_found) { - /* group info already read */ - fclose(f); - return 0; + /* end of desired group reached; return success */ + goto exit; } if (strncmp(line + 1, service, strlen(service)) == 0 && @@ -5336,12 +5333,11 @@ parseServiceFile(const char *serviceFile, switch (rc) { case 0: - fclose(f); - return 0; + goto exit; case 1: case 3: - fclose(f); - return 3; + result = 3; + goto exit; case 2: continue; } @@ -5356,8 +5352,8 @@ parseServiceFile(const char *serviceFile, libpq_gettext("syntax error in service file \"%s\", line %d\n"), serviceFile, linenr); - fclose(f); - return 3; + result = 3; + goto exit; } *val++ = '\0'; @@ -5367,8 +5363,8 @@ parseServiceFile(const char *serviceFile, libpq_gettext("nested service specifications not supported in service file \"%s\", line %d\n"), serviceFile, linenr); - fclose(f); - return 3; + result = 3; + goto exit; } /* @@ -5386,8 +5382,8 @@ parseServiceFile(const char *serviceFile, { printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n")); - fclose(f); - return 3; + result = 3; + goto exit; } found_keyword = true; break; @@ -5400,16 +5396,18 @@ parseServiceFile(const char *serviceFile, libpq_gettext("syntax error in service file \"%s\", line %d\n"), serviceFile, linenr); - fclose(f); - return 3; + result = 3; + goto exit; } } } } +exit: fclose(f); + pfree(linebuf.data); - return 0; + return result; } @@ -7095,10 +7093,7 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname, { FILE *fp; struct stat stat_buf; - int line_number = 0; - -#define LINELEN NAMEDATALEN*5 - char buf[LINELEN]; + PQExpBufferData buf; if (dbname == NULL || dbname[0] == '\0') return NULL; @@ -7154,89 +7149,77 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname, if (fp == NULL) return NULL; + /* Use an expansible buffer to accommodate any reasonable line length */ + initPQExpBuffer(&buf); + while (!feof(fp) && !ferror(fp)) { - char *t = buf, - *ret, - *p1, - *p2; - int len; - int buflen; + /* Make sure there's a reasonable amount of room in the buffer */ + if (!enlargePQExpBuffer(&buf, 128)) + break; - if (fgets(buf, sizeof(buf), fp) == NULL) + /* Read some data, appending it to what we already have */ + if (fgets(buf.data + buf.len, buf.maxlen - buf.len, fp) == NULL) break; + buf.len += strlen(buf.data + buf.len); - line_number++; - buflen = strlen(buf); - if (buflen >= sizeof(buf) - 1 && buf[buflen - 1] != '\n') + /* If we don't yet have a whole line, loop around to read more */ + if (!(buf.len > 0 && buf.data[buf.len - 1] == '\n') && !feof(fp)) + continue; + + /* ignore comments */ + if (buf.data[0] != '#') { - char rest[LINELEN]; - int restlen; + char *t = buf.data; + int len; - /* - * Warn if this password setting line is too long, because it's - * unexpectedly truncated. - */ - if (buf[0] != '#') - fprintf(stderr, - libpq_gettext("WARNING: line %d too long in password file \"%s\"\n"), - line_number, pgpassfile); + /* strip trailing newline and carriage return */ + len = pg_strip_crlf(t); - /* eat rest of the line */ - while (!feof(fp) && !ferror(fp)) + if (len > 0 && + (t = pwdfMatchesString(t, hostname)) != NULL && + (t = pwdfMatchesString(t, port)) != NULL && + (t = pwdfMatchesString(t, dbname)) != NULL && + (t = pwdfMatchesString(t, username)) != NULL) { - if (fgets(rest, sizeof(rest), fp) == NULL) - break; - restlen = strlen(rest); - if (restlen < sizeof(rest) - 1 || rest[restlen - 1] == '\n') - break; - } - } - - /* ignore comments */ - if (buf[0] == '#') - continue; - - /* strip trailing newline and carriage return */ - len = pg_strip_crlf(buf); + /* Found a match. */ + char *ret, + *p1, + *p2; - if (len == 0) - continue; + ret = strdup(t); - if ((t = pwdfMatchesString(t, hostname)) == NULL || - (t = pwdfMatchesString(t, port)) == NULL || - (t = pwdfMatchesString(t, dbname)) == NULL || - (t = pwdfMatchesString(t, username)) == NULL) - continue; + fclose(fp); + explicit_bzero(buf.data, buf.maxlen); + termPQExpBuffer(&buf); - /* Found a match. */ - ret = strdup(t); - fclose(fp); + if (!ret) + { + /* Out of memory. XXX: an error message would be nice. */ + return NULL; + } - if (!ret) - { - /* Out of memory. XXX: an error message would be nice. */ - explicit_bzero(buf, sizeof(buf)); - return NULL; - } + /* De-escape password. */ + for (p1 = p2 = ret; *p1 != ':' && *p1 != '\0'; ++p1, ++p2) + { + if (*p1 == '\\' && p1[1] != '\0') + ++p1; + *p2 = *p1; + } + *p2 = '\0'; - /* De-escape password. */ - for (p1 = p2 = ret; *p1 != ':' && *p1 != '\0'; ++p1, ++p2) - { - if (*p1 == '\\' && p1[1] != '\0') - ++p1; - *p2 = *p1; + return ret; + } } - *p2 = '\0'; - return ret; + /* No match, reset buffer to prepare for next line. */ + buf.len = 0; } fclose(fp); - explicit_bzero(buf, sizeof(buf)); + explicit_bzero(buf.data, buf.maxlen); + termPQExpBuffer(&buf); return NULL; - -#undef LINELEN } @@ -7264,7 +7247,7 @@ pgpassfileWarning(PGconn *conn) } /* - * Check if the SSL procotol value given in input is valid or not. + * Check if the SSL protocol value given in input is valid or not. * This is used as a sanity check routine for the connection parameters * ssl_min_protocol_version and ssl_max_protocol_version. */ diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c index e10809ba0907..2e21c1c5ff0d 100644 --- a/src/interfaces/libpq/fe-misc.c +++ b/src/interfaces/libpq/fe-misc.c @@ -718,24 +718,29 @@ pqReadData(PGconn *conn) conn->inBufSize - conn->inEnd); if (nread < 0) { - if (SOCK_ERRNO == EINTR) - goto retry3; - /* Some systems return EAGAIN/EWOULDBLOCK for no data */ + switch (SOCK_ERRNO) + { + case EINTR: + goto retry3; + + /* Some systems return EAGAIN/EWOULDBLOCK for no data */ #ifdef EAGAIN - if (SOCK_ERRNO == EAGAIN) - return someread; + case EAGAIN: + return someread; #endif #if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) - if (SOCK_ERRNO == EWOULDBLOCK) - return someread; + case EWOULDBLOCK: + return someread; #endif - /* We might get ECONNRESET here if using TCP and backend died */ -#ifdef ECONNRESET - if (SOCK_ERRNO == ECONNRESET) - goto definitelyFailed; -#endif - /* pqsecure_read set the error message for us */ - return -1; + + /* We might get ECONNRESET etc here if connection failed */ + case ALL_CONNECTION_FAILURE_ERRNOS: + goto definitelyFailed; + + default: + /* pqsecure_read set the error message for us */ + return -1; + } } if (nread > 0) { @@ -808,24 +813,29 @@ pqReadData(PGconn *conn) conn->inBufSize - conn->inEnd); if (nread < 0) { - if (SOCK_ERRNO == EINTR) - goto retry4; - /* Some systems return EAGAIN/EWOULDBLOCK for no data */ + switch (SOCK_ERRNO) + { + case EINTR: + goto retry4; + + /* Some systems return EAGAIN/EWOULDBLOCK for no data */ #ifdef EAGAIN - if (SOCK_ERRNO == EAGAIN) - return 0; + case EAGAIN: + return 0; #endif #if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) - if (SOCK_ERRNO == EWOULDBLOCK) - return 0; + case EWOULDBLOCK: + return 0; #endif - /* We might get ECONNRESET here if using TCP and backend died */ -#ifdef ECONNRESET - if (SOCK_ERRNO == ECONNRESET) - goto definitelyFailed; -#endif - /* pqsecure_read set the error message for us */ - return -1; + + /* We might get ECONNRESET etc here if connection failed */ + case ALL_CONNECTION_FAILURE_ERRNOS: + goto definitelyFailed; + + default: + /* pqsecure_read set the error message for us */ + return -1; + } } if (nread > 0) { diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c index a4d326693905..a6c1f19d5653 100644 --- a/src/interfaces/libpq/fe-secure-gssapi.c +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -233,7 +233,7 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len) PqGSSSendConsumed += input.length; /* 4 network-order bytes of length, then payload */ - netlen = htonl(output.length); + netlen = pg_hton32(output.length); memcpy(PqGSSSendBuffer + PqGSSSendLength, &netlen, sizeof(uint32)); PqGSSSendLength += sizeof(uint32); @@ -353,7 +353,7 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len) } /* Decode the packet length and check for overlength packet */ - input.length = ntohl(*(uint32 *) PqGSSRecvBuffer); + input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer); if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) { @@ -596,7 +596,7 @@ pqsecure_open_gss(PGconn *conn) */ /* Get the length and check for over-length packet */ - input.length = ntohl(*(uint32 *) PqGSSRecvBuffer); + input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer); if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) { printfPQExpBuffer(&conn->errorMessage, @@ -695,7 +695,7 @@ pqsecure_open_gss(PGconn *conn) } /* Queue the token for writing */ - netlen = htonl(output.length); + netlen = pg_hton32(output.length); memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32)); PqGSSSendLength += sizeof(uint32); diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index f31ec933d892..89f214717aad 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -268,14 +268,13 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len) /* no error message, caller is expected to retry */ break; -#ifdef ECONNRESET + case EPIPE: case ECONNRESET: printfPQExpBuffer(&conn->errorMessage, libpq_gettext("server closed the connection unexpectedly\n" "\tThis probably means the server terminated abnormally\n" "\tbefore or while processing the request.\n")); break; -#endif default: printfPQExpBuffer(&conn->errorMessage, @@ -381,11 +380,9 @@ pqsecure_raw_write(PGconn *conn, const void *ptr, size_t len) /* Set flag for EPIPE */ REMEMBER_EPIPE(spinfo, true); -#ifdef ECONNRESET /* FALL THRU */ case ECONNRESET: -#endif printfPQExpBuffer(&conn->errorMessage, libpq_gettext("server closed the connection unexpectedly\n" "\tThis probably means the server terminated abnormally\n" diff --git a/src/interfaces/libpq/win32.h b/src/interfaces/libpq/win32.h index c42d7abfe30a..fcce1e0544a7 100644 --- a/src/interfaces/libpq/win32.h +++ b/src/interfaces/libpq/win32.h @@ -14,17 +14,6 @@ #define write(a,b,c) _write(a,b,c) #undef EAGAIN /* doesn't apply on sockets */ -#undef EINTR -#define EINTR WSAEINTR -#ifndef EWOULDBLOCK -#define EWOULDBLOCK WSAEWOULDBLOCK -#endif -#ifndef ECONNRESET -#define ECONNRESET WSAECONNRESET -#endif -#ifndef EINPROGRESS -#define EINPROGRESS WSAEINPROGRESS -#endif /* * support for handling Windows Socket errors diff --git a/src/pl/plperl/expected/plperl_call.out b/src/pl/plperl/expected/plperl_call.out index c55c59cbceb3..49e13e3fc3dc 100644 --- a/src/pl/plperl/expected/plperl_call.out +++ b/src/pl/plperl/expected/plperl_call.out @@ -48,6 +48,24 @@ CALL test_proc6(2, 3, 4); 6 | 8 (1 row) +-- OUT parameters +CREATE PROCEDURE test_proc9(IN a int, OUT b int) +LANGUAGE plperl +AS $$ +my ($a, $b) = @_; +elog(NOTICE, "a: $a, b: $b"); +return { b => $a * 2 }; +$$; +DO $$ +DECLARE _a int; _b int; +BEGIN + _a := 10; _b := 30; + CALL test_proc9(_a, _b); + RAISE NOTICE '_a: %, _b: %', _a, _b; +END +$$; +NOTICE: a: 10, b: +NOTICE: _a: 10, _b: 20 DROP PROCEDURE test_proc1; DROP PROCEDURE test_proc2; DROP PROCEDURE test_proc3; diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 5fdf303fe6b3..7844c500eee8 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -2002,7 +2002,7 @@ plperl_validator(PG_FUNCTION_ARGS) { if (proc->prorettype == TRIGGEROID) is_trigger = true; - else if (proc->prorettype == EVTTRIGGEROID) + else if (proc->prorettype == EVENT_TRIGGEROID) is_event_trigger = true; else if (proc->prorettype != RECORDOID && proc->prorettype != VOIDOID) @@ -2838,7 +2838,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger) rettype == RECORDOID) /* okay */ ; else if (rettype == TRIGGEROID || - rettype == EVTTRIGGEROID) + rettype == EVENT_TRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions can only be called " diff --git a/src/pl/plperl/plperl.h b/src/pl/plperl/plperl.h index 1f73c64089f2..bc9edc8599be 100644 --- a/src/pl/plperl/plperl.h +++ b/src/pl/plperl/plperl.h @@ -99,6 +99,7 @@ #undef bind #undef connect #undef fopen +#undef fstat #undef kill #undef listen #undef lstat diff --git a/src/pl/plperl/plperl_opmask.pl b/src/pl/plperl/plperl_opmask.pl index 3b33112ff945..ee18e915289b 100644 --- a/src/pl/plperl/plperl_opmask.pl +++ b/src/pl/plperl/plperl_opmask.pl @@ -52,7 +52,7 @@ printf $fh qq{ opmask[OP_%-12s] = 0;\t/* %s */ \\\n}, uc($opname), opdesc($opname); } -printf $fh " /* end */ \n"; +printf $fh " /* end */\n"; close $fh or die "Error closing $plperl_opmask_tmp: $!"; diff --git a/src/pl/plperl/sql/plperl_call.sql b/src/pl/plperl/sql/plperl_call.sql index 2cf5461fefde..bbea85fc9f50 100644 --- a/src/pl/plperl/sql/plperl_call.sql +++ b/src/pl/plperl/sql/plperl_call.sql @@ -51,6 +51,26 @@ $$; CALL test_proc6(2, 3, 4); +-- OUT parameters + +CREATE PROCEDURE test_proc9(IN a int, OUT b int) +LANGUAGE plperl +AS $$ +my ($a, $b) = @_; +elog(NOTICE, "a: $a, b: $b"); +return { b => $a * 2 }; +$$; + +DO $$ +DECLARE _a int; _b int; +BEGIN + _a := 10; _b := 30; + CALL test_proc9(_a, _b); + RAISE NOTICE '_a: %, _b: %', _a, _b; +END +$$; + + DROP PROCEDURE test_proc1; DROP PROCEDURE test_proc2; DROP PROCEDURE test_proc3; diff --git a/src/pl/plpgsql/src/expected/plpgsql_call.out b/src/pl/plpgsql/src/expected/plpgsql_call.out index e86132f20b23..cc89fc339400 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_call.out +++ b/src/pl/plpgsql/src/expected/plpgsql_call.out @@ -264,6 +264,25 @@ END $$; ERROR: procedure parameter "c" is an output parameter but corresponding argument is not writable CONTEXT: PL/pgSQL function inline_code_block line 5 at CALL +-- OUT parameters +CREATE PROCEDURE test_proc9(IN a int, OUT b int) +LANGUAGE plpgsql +AS $$ +BEGIN + RAISE NOTICE 'a: %, b: %', a, b; + b := a * 2; +END; +$$; +DO $$ +DECLARE _a int; _b int; +BEGIN + _a := 10; _b := 30; + CALL test_proc9(_a, _b); + RAISE NOTICE '_a: %, _b: %', _a, _b; +END +$$; +NOTICE: a: 10, b: +NOTICE: _a: 10, _b: 20 -- transition variable assignment TRUNCATE test1; CREATE FUNCTION triggerfunc1() RETURNS trigger diff --git a/src/pl/plpgsql/src/generate-plerrcodes.pl b/src/pl/plpgsql/src/generate-plerrcodes.pl index f74dd0ef03f8..a50de66ef813 100644 --- a/src/pl/plpgsql/src/generate-plerrcodes.pl +++ b/src/pl/plpgsql/src/generate-plerrcodes.pl @@ -3,8 +3,8 @@ # Generate the plerrcodes.h header from errcodes.txt # Copyright (c) 2000-2020, PostgreSQL Global Development Group -use warnings; use strict; +use warnings; print "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n"; @@ -34,7 +34,7 @@ # Skip lines without PL/pgSQL condition names next unless defined($condition_name); - print "{\n\t\"$condition_name\", $errcode_macro\n},\n\n"; + print "\n{\n\t\"$condition_name\", $errcode_macro\n},\n"; } close $errcodes; diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 1be8e70401d2..6df8e14629d4 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -456,33 +456,15 @@ do_compile(FunctionCallInfo fcinfo, } /* Remember arguments in appropriate arrays */ - switch (argmode) - { - /* input modes */ - case PROARGMODE_IN: - case PROARGMODE_VARIADIC: - in_arg_varnos[num_in_args++] = argvariable->dno; - break; - - /* output modes */ - case PROARGMODE_OUT: - case PROARGMODE_TABLE: - out_arg_variables[num_out_args++] = argvariable; - break; - - /* both */ - case PROARGMODE_INOUT: - in_arg_varnos[num_in_args++] = argvariable->dno; - out_arg_variables[num_out_args++] = argvariable; - break; - - default: - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("plpgsql functions do not support argmode '%c'", - argmode))); - break; - } + if (argmode == PROARGMODE_IN || + argmode == PROARGMODE_INOUT || + (argmode == PROARGMODE_OUT && function->fn_prokind == PROKIND_PROCEDURE) || + argmode == PROARGMODE_VARIADIC) + in_arg_varnos[num_in_args++] = argvariable->dno; + if (argmode == PROARGMODE_OUT || + argmode == PROARGMODE_INOUT || + argmode == PROARGMODE_TABLE) + out_arg_variables[num_out_args++] = argvariable; /* Add to namespace under the $n name */ add_parameter_name(argitemtype, argvariable->dno, buf); @@ -569,7 +551,7 @@ do_compile(FunctionCallInfo fcinfo, if (rettypeid == VOIDOID || rettypeid == RECORDOID) /* okay */ ; - else if (rettypeid == TRIGGEROID || rettypeid == EVTTRIGGEROID) + else if (rettypeid == TRIGGEROID || rettypeid == EVENT_TRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions can only be called as triggers"))); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 3b4b3fa35ed7..450ae36cc806 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -2146,40 +2146,60 @@ exec_stmt_perform(PLpgSQL_execstate *estate, PLpgSQL_stmt_perform *stmt) /* * exec_stmt_call + * + * NOTE: this is used for both CALL and DO statements. */ static int exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) { PLpgSQL_expr *expr = stmt->expr; + SPIPlanPtr orig_plan = expr->plan; + bool local_plan; + PLpgSQL_variable *volatile cur_target = stmt->target; volatile LocalTransactionId before_lxid; LocalTransactionId after_lxid; volatile bool pushed_active_snap = false; volatile int rc; + /* + * If not in atomic context, we make a local plan that we'll just use for + * this invocation, and will free at the end. Otherwise, transaction ends + * would cause errors about plancache leaks. + * + * XXX This would be fixable with some plancache/resowner surgery + * elsewhere, but for now we'll just work around this here. + */ + local_plan = !estate->atomic; + /* PG_TRY to ensure we clear the plan link, if needed, on failure */ PG_TRY(); { SPIPlanPtr plan = expr->plan; ParamListInfo paramLI; - if (plan == NULL) + /* + * Make a plan if we don't have one, or if we need a local one. Note + * that we'll overwrite expr->plan either way; the PG_TRY block will + * ensure we undo that on the way out, if the plan is local. + */ + if (plan == NULL || local_plan) { + /* Don't let SPI save the plan if it's going to be local */ + exec_prepare_plan(estate, expr, 0, !local_plan); + plan = expr->plan; /* - * Don't save the plan if not in atomic context. Otherwise, - * transaction ends would cause errors about plancache leaks. - * - * XXX This would be fixable with some plancache/resowner surgery - * elsewhere, but for now we'll just work around this here. + * A CALL or DO can never be a simple expression. (If it could + * be, we'd have to worry about saving/restoring the previous + * values of the related expr fields, not just expr->plan.) */ - exec_prepare_plan(estate, expr, 0, estate->atomic); + Assert(!expr->expr_simple_expr); /* * The procedure call could end transactions, which would upset * the snapshot management in SPI_execute*, so don't let it do it. * Instead, we set the snapshots ourselves below. */ - plan = expr->plan; plan->no_snapshots = true; /* @@ -2187,14 +2207,21 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) * case the procedure's argument list has changed. */ stmt->target = NULL; + cur_target = NULL; } /* * We construct a DTYPE_ROW datum representing the plpgsql variables * associated with the procedure's output arguments. Then we can use * exec_move_row() to do the assignments. + * + * If we're using a local plan, also make a local target; otherwise, + * since the above code will force a new plan each time through, we'd + * repeatedly leak the memory for the target. (Note: we also leak the + * target when a plan change is forced, but that isn't so likely to + * cause excessive memory leaks.) */ - if (stmt->is_call && stmt->target == NULL) + if (stmt->is_call && cur_target == NULL) { Node *node; FuncExpr *funcexpr; @@ -2209,6 +2236,9 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) int i; ListCell *lc; + /* Use eval_mcontext for any cruft accumulated here */ + oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); + /* * Get the parsed CallStmt, and look up the called procedure */ @@ -2240,9 +2270,11 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) ReleaseSysCache(func_tuple); /* - * Begin constructing row Datum + * Begin constructing row Datum; keep it in fn_cxt if it's to be + * long-lived. */ - oldcontext = MemoryContextSwitchTo(estate->func->fn_cxt); + if (!local_plan) + MemoryContextSwitchTo(estate->func->fn_cxt); row = (PLpgSQL_row *) palloc0(sizeof(PLpgSQL_row)); row->dtype = PLPGSQL_DTYPE_ROW; @@ -2250,7 +2282,8 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) row->lineno = -1; row->varnos = (int *) palloc(sizeof(int) * list_length(funcargs)); - MemoryContextSwitchTo(oldcontext); + if (!local_plan) + MemoryContextSwitchTo(get_eval_mcontext(estate)); /* * Examine procedure's argument list. Each output arg position @@ -2294,7 +2327,13 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) row->nfields = nfields; - stmt->target = (PLpgSQL_variable *) row; + cur_target = (PLpgSQL_variable *) row; + + /* We can save and re-use the target datum, if it's not local */ + if (!local_plan) + stmt->target = cur_target; + + MemoryContextSwitchTo(oldcontext); } paramLI = setup_param_list(estate, expr); @@ -2317,17 +2356,27 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) PG_CATCH(); { /* - * If we aren't saving the plan, unset the pointer. Note that it - * could have been unset already, in case of a recursive call. + * If we are using a local plan, restore the old plan link. */ - if (expr->plan && !expr->plan->saved) - expr->plan = NULL; + if (local_plan) + expr->plan = orig_plan; PG_RE_THROW(); } PG_END_TRY(); - if (expr->plan && !expr->plan->saved) - expr->plan = NULL; + /* + * If we are using a local plan, restore the old plan link; then free the + * local plan to avoid memory leaks. (Note that the error exit path above + * just clears the link without risking calling SPI_freeplan; we expect + * that xact cleanup will take care of the mess in that case.) + */ + if (local_plan) + { + SPIPlanPtr plan = expr->plan; + + expr->plan = orig_plan; + SPI_freeplan(plan); + } if (rc < 0) elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s", @@ -2364,10 +2413,10 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) { SPITupleTable *tuptab = SPI_tuptable; - if (!stmt->target) + if (!cur_target) elog(ERROR, "DO statement returned a row"); - exec_move_row(estate, stmt->target, tuptab->vals[0], tuptab->tupdesc); + exec_move_row(estate, cur_target, tuptab->vals[0], tuptab->tupdesc); } else if (SPI_processed > 1) elog(ERROR, "procedure call returned more than one row"); diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index 5a7e1a44442b..8227bf0449fc 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -107,7 +107,7 @@ static void check_labels(const char *start_label, const char *end_label, int end_location); static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor, - int until, const char *expected); + int until); static List *read_raise_options(void); static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); @@ -1414,8 +1414,7 @@ for_control : for_variable K_IN /* collect cursor's parameters if any */ new->argquery = read_cursor_args(cursor, - K_LOOP, - "LOOP"); + K_LOOP); /* create loop's private RECORD variable */ new->var = (PLpgSQL_variable *) @@ -2129,7 +2128,7 @@ stmt_open : K_OPEN cursor_variable else { /* predefined cursor query, so read args */ - new->argquery = read_cursor_args($2, ';', ";"); + new->argquery = read_cursor_args($2, ';'); } $$ = (PLpgSQL_stmt *)new; @@ -3773,7 +3772,7 @@ check_labels(const char *start_label, const char *end_label, int end_location) * parens). */ static PLpgSQL_expr * -read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected) +read_cursor_args(PLpgSQL_var *cursor, int until) { PLpgSQL_expr *expr; PLpgSQL_row *row; diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index b81ef194a80d..39c7988a1624 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -452,7 +452,7 @@ plpgsql_validator(PG_FUNCTION_ARGS) { if (proc->prorettype == TRIGGEROID) is_dml_trigger = true; - else if (proc->prorettype == EVTTRIGGEROID) + else if (proc->prorettype == EVENT_TRIGGEROID) is_event_trigger = true; else if (proc->prorettype != RECORDOID && proc->prorettype != VOIDOID && diff --git a/src/pl/plpgsql/src/sql/plpgsql_call.sql b/src/pl/plpgsql/src/sql/plpgsql_call.sql index 4702bd14d12e..d506809ddbf6 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_call.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_call.sql @@ -237,6 +237,27 @@ END $$; +-- OUT parameters + +CREATE PROCEDURE test_proc9(IN a int, OUT b int) +LANGUAGE plpgsql +AS $$ +BEGIN + RAISE NOTICE 'a: %, b: %', a, b; + b := a * 2; +END; +$$; + +DO $$ +DECLARE _a int; _b int; +BEGIN + _a := 10; _b := 30; + CALL test_proc9(_a, _b); + RAISE NOTICE '_a: %, _b: %', _a, _b; +END +$$; + + -- transition variable assignment TRUNCATE test1; diff --git a/src/pl/plpython/expected/plpython_call.out b/src/pl/plpython/expected/plpython_call.out index 07ae04e98ba2..c3f3c8e95e57 100644 --- a/src/pl/plpython/expected/plpython_call.out +++ b/src/pl/plpython/expected/plpython_call.out @@ -52,6 +52,23 @@ CALL test_proc6(2, 3, 4); 6 | 8 (1 row) +-- OUT parameters +CREATE PROCEDURE test_proc9(IN a int, OUT b int) +LANGUAGE plpythonu +AS $$ +plpy.notice("a: %s, b: %s" % (a, b)) +return (a * 2,) +$$; +DO $$ +DECLARE _a int; _b int; +BEGIN + _a := 10; _b := 30; + CALL test_proc9(_a, _b); + RAISE NOTICE '_a: %, _b: %', _a, _b; +END +$$; +NOTICE: a: 10, b: None +NOTICE: _a: 10, _b: 20 DROP PROCEDURE test_proc1; DROP PROCEDURE test_proc2; DROP PROCEDURE test_proc3; diff --git a/src/pl/plpython/generate-spiexceptions.pl b/src/pl/plpython/generate-spiexceptions.pl index d7afb8516ded..14967ba3eef6 100644 --- a/src/pl/plpython/generate-spiexceptions.pl +++ b/src/pl/plpython/generate-spiexceptions.pl @@ -3,8 +3,8 @@ # Generate the spiexceptions.h header from errcodes.txt # Copyright (c) 2000-2020, PostgreSQL Global Development Group -use warnings; use strict; +use warnings; print "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n"; @@ -37,8 +37,8 @@ # Change some_error_condition to SomeErrorCondition $condition_name =~ s/([a-z])([^_]*)(?:_|$)/\u$1$2/g; - print "{ \"spiexceptions.$condition_name\", " - . "\"$condition_name\", $errcode_macro },\n"; + print "\n{\n\t\"spiexceptions.$condition_name\", " + . "\"$condition_name\", $errcode_macro\n},\n"; } close $errcodes; diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c index 24d921549a22..a6049f74c229 100644 --- a/src/pl/plpython/plpy_elog.c +++ b/src/pl/plpython/plpy_elog.c @@ -226,7 +226,7 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, else if (strcmp(e_module_s, "builtins") == 0 || strcmp(e_module_s, "__main__") == 0 || strcmp(e_module_s, "exceptions") == 0) - appendStringInfo(&xstr, "%s", e_type_s); + appendStringInfoString(&xstr, e_type_s); else appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s); appendStringInfo(&xstr, ": %s", vstr); diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c index 9e1583961115..1f05c633ef2b 100644 --- a/src/pl/plpython/plpy_procedure.c +++ b/src/pl/plpython/plpy_procedure.c @@ -220,7 +220,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) if (rettype == VOIDOID || rettype == RECORDOID) /* okay */ ; - else if (rettype == TRIGGEROID || rettype == EVTTRIGGEROID) + else if (rettype == TRIGGEROID || rettype == EVENT_TRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions can only be called as triggers"))); @@ -273,7 +273,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) /* proc->nargs was initialized to 0 above */ for (i = 0; i < total; i++) { - if (modes[i] != PROARGMODE_OUT && + if ((modes[i] != PROARGMODE_OUT || proc->is_procedure) && modes[i] != PROARGMODE_TABLE) (proc->nargs)++; } @@ -289,7 +289,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) Form_pg_type argTypeStruct; if (modes && - (modes[i] == PROARGMODE_OUT || + ((modes[i] == PROARGMODE_OUT && !proc->is_procedure) || modes[i] == PROARGMODE_TABLE)) continue; /* skip OUT arguments */ diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c index d84b9bec680b..b4aeb7fd5959 100644 --- a/src/pl/plpython/plpy_typeio.c +++ b/src/pl/plpython/plpy_typeio.c @@ -679,7 +679,7 @@ PLyList_FromArray(PLyDatumToOb *arg, Datum d) /* Array dimensions and left bounds */ ndim = ARR_NDIM(array); dims = ARR_DIMS(array); - Assert(ndim < MAXDIM); + Assert(ndim <= MAXDIM); /* * We iterate the SQL array in the physical order it's stored in the diff --git a/src/pl/plpython/sql/plpython_call.sql b/src/pl/plpython/sql/plpython_call.sql index 2f792f92bd78..46e89b1a9e1c 100644 --- a/src/pl/plpython/sql/plpython_call.sql +++ b/src/pl/plpython/sql/plpython_call.sql @@ -54,6 +54,25 @@ $$; CALL test_proc6(2, 3, 4); +-- OUT parameters + +CREATE PROCEDURE test_proc9(IN a int, OUT b int) +LANGUAGE plpythonu +AS $$ +plpy.notice("a: %s, b: %s" % (a, b)) +return (a * 2,) +$$; + +DO $$ +DECLARE _a int; _b int; +BEGIN + _a := 10; _b := 30; + CALL test_proc9(_a, _b); + RAISE NOTICE '_a: %, _b: %', _a, _b; +END +$$; + + DROP PROCEDURE test_proc1; DROP PROCEDURE test_proc2; DROP PROCEDURE test_proc3; diff --git a/src/pl/tcl/expected/pltcl_call.out b/src/pl/tcl/expected/pltcl_call.out index d290c8fbd05c..f0eb356cf23a 100644 --- a/src/pl/tcl/expected/pltcl_call.out +++ b/src/pl/tcl/expected/pltcl_call.out @@ -49,6 +49,23 @@ CALL test_proc6(2, 3, 4); 6 | 8 (1 row) +-- OUT parameters +CREATE PROCEDURE test_proc9(IN a int, OUT b int) +LANGUAGE pltcl +AS $$ +elog NOTICE "a: $1, b: $2" +return [list b [expr {$1 * 2}]] +$$; +DO $$ +DECLARE _a int; _b int; +BEGIN + _a := 10; _b := 30; + CALL test_proc9(_a, _b); + RAISE NOTICE '_a: %, _b: %', _a, _b; +END +$$; +NOTICE: a: 10, b: +NOTICE: _a: 10, _b: 20 DROP PROCEDURE test_proc1; DROP PROCEDURE test_proc2; DROP PROCEDURE test_proc3; diff --git a/src/pl/tcl/generate-pltclerrcodes.pl b/src/pl/tcl/generate-pltclerrcodes.pl index 59ea2baab7ed..bb9eb8a824dc 100644 --- a/src/pl/tcl/generate-pltclerrcodes.pl +++ b/src/pl/tcl/generate-pltclerrcodes.pl @@ -3,8 +3,8 @@ # Generate the pltclerrcodes.h header from errcodes.txt # Copyright (c) 2000-2020, PostgreSQL Global Development Group -use warnings; use strict; +use warnings; print "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n"; @@ -34,7 +34,7 @@ # Skip lines without PL/pgSQL condition names next unless defined($condition_name); - print "{\n\t\"$condition_name\", $errcode_macro\n},\n\n"; + print "\n{\n\t\"$condition_name\", $errcode_macro\n},\n"; } close $errcodes; diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index f4eabc8f39c0..a3a2dc8e89fb 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -1532,7 +1532,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, rettype == RECORDOID) /* okay */ ; else if (rettype == TRIGGEROID || - rettype == EVTTRIGGEROID) + rettype == EVENT_TRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions can only be called as triggers"))); diff --git a/src/pl/tcl/sql/pltcl_call.sql b/src/pl/tcl/sql/pltcl_call.sql index 95791d08beea..963277e1fb87 100644 --- a/src/pl/tcl/sql/pltcl_call.sql +++ b/src/pl/tcl/sql/pltcl_call.sql @@ -52,6 +52,25 @@ $$; CALL test_proc6(2, 3, 4); +-- OUT parameters + +CREATE PROCEDURE test_proc9(IN a int, OUT b int) +LANGUAGE pltcl +AS $$ +elog NOTICE "a: $1, b: $2" +return [list b [expr {$1 * 2}]] +$$; + +DO $$ +DECLARE _a int; _b int; +BEGIN + _a := 10; _b := 30; + CALL test_proc9(_a, _b); + RAISE NOTICE '_a: %, _b: %', _a, _b; +END +$$; + + DROP PROCEDURE test_proc1; DROP PROCEDURE test_proc2; DROP PROCEDURE test_proc3; diff --git a/src/port/Makefile b/src/port/Makefile index 0d0bfc4da51a..b9a554eb2a56 100644 --- a/src/port/Makefile +++ b/src/port/Makefile @@ -35,6 +35,8 @@ include $(top_builddir)/src/Makefile.global override CPPFLAGS := -I$(top_builddir)/src/port -DFRONTEND $(CPPFLAGS) -fPIC LIBS += $(PTHREAD_LIBS) +# If you add objects here, see also src/tools/msvc/Mkvcbuild.pm + OBJS = \ $(LIBOBJS) \ $(PG_CRC32C_OBJS) \ @@ -55,7 +57,6 @@ OBJS = \ qsort_arg.o \ quotes.o \ snprintf.o \ - sprompt.o \ strerror.o \ tar.o \ thread.o diff --git a/src/port/dirent.c b/src/port/dirent.c index b264484fca24..70a444f9a53f 100644 --- a/src/port/dirent.c +++ b/src/port/dirent.c @@ -69,6 +69,7 @@ opendir(const char *dirname) d->handle = INVALID_HANDLE_VALUE; d->ret.d_ino = 0; /* no inodes on win32 */ d->ret.d_reclen = 0; /* not used on win32 */ + d->ret.d_type = DT_UNKNOWN; return d; } @@ -105,6 +106,15 @@ readdir(DIR *d) } strcpy(d->ret.d_name, fd.cFileName); /* Both strings are MAX_PATH long */ d->ret.d_namlen = strlen(d->ret.d_name); + /* The only identified types are: directory, regular file or symbolic link */ + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + d->ret.d_type = DT_DIR; + /* For reparse points dwReserved0 field will contain the ReparseTag */ + else if ((fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && + (fd.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT)) + d->ret.d_type = DT_LNK; + else + d->ret.d_type = DT_REG; return &d->ret; } diff --git a/src/port/dirmod.c b/src/port/dirmod.c index 016a3e60db60..8979f100803b 100644 --- a/src/port/dirmod.c +++ b/src/port/dirmod.c @@ -353,65 +353,3 @@ pgwin32_is_junction(const char *path) return ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT); } #endif /* defined(WIN32) && !defined(__CYGWIN__) */ - - -#if defined(WIN32) && !defined(__CYGWIN__) - -#undef stat - -/* - * The stat() function in win32 is not guaranteed to update the st_size - * field when run. So we define our own version that uses the Win32 API - * to update this field. - */ -int -pgwin32_safestat(const char *path, struct stat *buf) -{ - int r; - WIN32_FILE_ATTRIBUTE_DATA attr; - - r = stat(path, buf); - if (r < 0) - { - if (GetLastError() == ERROR_DELETE_PENDING) - { - /* - * File has been deleted, but is not gone from the filesystem yet. - * This can happen when some process with FILE_SHARE_DELETE has it - * open and it will be fully removed once that handle is closed. - * Meanwhile, we can't open it, so indicate that the file just - * doesn't exist. - */ - errno = ENOENT; - return -1; - } - - return r; - } - - // MPP-24774: just return if path refer to a windows named pipe file. - // no need to get size of a windows named pipe file - if (strlen(path) >2) - { - if (path[0] == '\\' && path[1] == '\\') - { - return r; - } - } - - if (!GetFileAttributesEx(path, GetFileExInfoStandard, &attr)) - { - _dosmaperr(GetLastError()); - return -1; - } - - /* - * XXX no support for large files here, but we don't do that in general on - * Win32 yet. - */ - buf->st_size = attr.nFileSizeLow; - - return 0; -} - -#endif diff --git a/src/port/getaddrinfo.c b/src/port/getaddrinfo.c index 3b51eea4815e..495ad343f392 100644 --- a/src/port/getaddrinfo.c +++ b/src/port/getaddrinfo.c @@ -79,12 +79,12 @@ haveNativeWindowsIPv6routines(void) { /* We found a dll, so now get the addresses of the routines */ - getaddrinfo_ptr = (getaddrinfo_ptr_t) GetProcAddress(hLibrary, - "getaddrinfo"); - freeaddrinfo_ptr = (freeaddrinfo_ptr_t) GetProcAddress(hLibrary, - "freeaddrinfo"); - getnameinfo_ptr = (getnameinfo_ptr_t) GetProcAddress(hLibrary, - "getnameinfo"); + getaddrinfo_ptr = (getaddrinfo_ptr_t) (pg_funcptr_t) GetProcAddress(hLibrary, + "getaddrinfo"); + freeaddrinfo_ptr = (freeaddrinfo_ptr_t) (pg_funcptr_t) GetProcAddress(hLibrary, + "freeaddrinfo"); + getnameinfo_ptr = (getnameinfo_ptr_t) (pg_funcptr_t) GetProcAddress(hLibrary, + "getnameinfo"); /* * If any one of the routines is missing, let's play it safe and diff --git a/src/port/strerror.c b/src/port/strerror.c index 375edb0f5abd..127bc5ef1fee 100644 --- a/src/port/strerror.c +++ b/src/port/strerror.c @@ -118,14 +118,10 @@ get_errno_symbol(int errnum) return "E2BIG"; case EACCES: return "EACCES"; -#ifdef EADDRINUSE case EADDRINUSE: return "EADDRINUSE"; -#endif -#ifdef EADDRNOTAVAIL case EADDRNOTAVAIL: return "EADDRNOTAVAIL"; -#endif case EAFNOSUPPORT: return "EAFNOSUPPORT"; #ifdef EAGAIN @@ -146,16 +142,12 @@ get_errno_symbol(int errnum) return "EBUSY"; case ECHILD: return "ECHILD"; -#ifdef ECONNABORTED case ECONNABORTED: return "ECONNABORTED"; -#endif case ECONNREFUSED: return "ECONNREFUSED"; -#ifdef ECONNRESET case ECONNRESET: return "ECONNRESET"; -#endif case EDEADLK: return "EDEADLK"; case EDOM: @@ -166,10 +158,10 @@ get_errno_symbol(int errnum) return "EFAULT"; case EFBIG: return "EFBIG"; -#ifdef EHOSTUNREACH + case EHOSTDOWN: + return "EHOSTDOWN"; case EHOSTUNREACH: return "EHOSTUNREACH"; -#endif case EIDRM: return "EIDRM"; case EINPROGRESS: @@ -180,10 +172,8 @@ get_errno_symbol(int errnum) return "EINVAL"; case EIO: return "EIO"; -#ifdef EISCONN case EISCONN: return "EISCONN"; -#endif case EISDIR: return "EISDIR"; #ifdef ELOOP @@ -198,6 +188,12 @@ get_errno_symbol(int errnum) return "EMSGSIZE"; case ENAMETOOLONG: return "ENAMETOOLONG"; + case ENETDOWN: + return "ENETDOWN"; + case ENETRESET: + return "ENETRESET"; + case ENETUNREACH: + return "ENETUNREACH"; case ENFILE: return "ENFILE"; case ENOBUFS: @@ -214,20 +210,16 @@ get_errno_symbol(int errnum) return "ENOSPC"; case ENOSYS: return "ENOSYS"; -#ifdef ENOTCONN case ENOTCONN: return "ENOTCONN"; -#endif case ENOTDIR: return "ENOTDIR"; #if defined(ENOTEMPTY) && (ENOTEMPTY != EEXIST) /* same code on AIX */ case ENOTEMPTY: return "ENOTEMPTY"; #endif -#ifdef ENOTSOCK case ENOTSOCK: return "ENOTSOCK"; -#endif #ifdef ENOTSUP case ENOTSUP: return "ENOTSUP"; diff --git a/src/port/thread.c b/src/port/thread.c index f9172eafe751..4be2068f2729 100644 --- a/src/port/thread.c +++ b/src/port/thread.c @@ -47,9 +47,6 @@ * use non-*_r functions if they are thread-safe * * One thread-safe solution for gethostbyname() might be to use getaddrinfo(). - * - * Run src/test/thread to test if your operating system has thread-safe - * non-*_r functions. */ diff --git a/src/port/win32env.c b/src/port/win32env.c index 2021f3d5aa6e..177488cc67ea 100644 --- a/src/port/win32env.c +++ b/src/port/win32env.c @@ -95,7 +95,7 @@ pgwin32_putenv(const char *envval) { PUTENVPROC putenvFunc; - putenvFunc = (PUTENVPROC) GetProcAddress(hmodule, "_putenv"); + putenvFunc = (PUTENVPROC) (pg_funcptr_t) GetProcAddress(hmodule, "_putenv"); if (putenvFunc) putenvFunc(envval); FreeLibrary(hmodule); diff --git a/src/port/win32stat.c b/src/port/win32stat.c new file mode 100644 index 000000000000..4351aa4d08f2 --- /dev/null +++ b/src/port/win32stat.c @@ -0,0 +1,307 @@ +/*------------------------------------------------------------------------- + * + * win32stat.c + * Replacements for functions using GetFileInformationByHandle + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/port/win32stat.c + * + *------------------------------------------------------------------------- + */ + +#ifdef WIN32 + +#include "c.h" +#include + +/* + * In order to support MinGW and MSVC2013 we use NtQueryInformationFile as an + * alternative for GetFileInformationByHandleEx. It is loaded from the ntdll + * library. + */ +#if _WIN32_WINNT < 0x0600 +#include + +#if !defined(__MINGW32__) && !defined(__MINGW64__) +/* MinGW includes this in , but it is missing in MSVC */ +typedef struct _FILE_STANDARD_INFORMATION +{ + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG NumberOfLinks; + BOOLEAN DeletePending; + BOOLEAN Directory; +} FILE_STANDARD_INFORMATION; +#define FileStandardInformation 5 +#endif /* !defined(__MINGW32__) && + * !defined(__MINGW64__) */ + +typedef NTSTATUS (NTAPI * PFN_NTQUERYINFORMATIONFILE) + (IN HANDLE FileHandle, + OUT PIO_STATUS_BLOCK IoStatusBlock, + OUT PVOID FileInformation, + IN ULONG Length, + IN FILE_INFORMATION_CLASS FileInformationClass); + +static PFN_NTQUERYINFORMATIONFILE _NtQueryInformationFile = NULL; + +static HMODULE ntdll = NULL; + +/* + * Load DLL file just once regardless of how many functions we load/call in it. + */ +static void +LoadNtdll(void) +{ + if (ntdll != NULL) + return; + ntdll = LoadLibraryEx("ntdll.dll", NULL, 0); +} + +#endif /* _WIN32_WINNT < 0x0600 */ + + +/* + * Convert a FILETIME struct into a 64 bit time_t. + */ +static __time64_t +filetime_to_time(const FILETIME *ft) +{ + ULARGE_INTEGER unified_ft = {0}; + static const uint64 EpochShift = UINT64CONST(116444736000000000); + + unified_ft.LowPart = ft->dwLowDateTime; + unified_ft.HighPart = ft->dwHighDateTime; + + if (unified_ft.QuadPart < EpochShift) + return -1; + + unified_ft.QuadPart -= EpochShift; + unified_ft.QuadPart /= 10 * 1000 * 1000; + + return unified_ft.QuadPart; +} + +/* + * Convert WIN32 file attributes to a Unix-style mode. + * + * Only owner permissions are set. + */ +static unsigned short +fileattr_to_unixmode(int attr) +{ + unsigned short uxmode = 0; + + uxmode |= (unsigned short) ((attr & FILE_ATTRIBUTE_DIRECTORY) ? + (_S_IFDIR) : (_S_IFREG)); + + uxmode |= (unsigned short) ((attr & FILE_ATTRIBUTE_READONLY) ? + (_S_IREAD) : (_S_IREAD | _S_IWRITE)); + + /* there is no need to simulate _S_IEXEC using CMD's PATHEXT extensions */ + uxmode |= _S_IEXEC; + + return uxmode; +} + +/* + * Convert WIN32 file information (from a HANDLE) to a struct stat. + */ +static int +fileinfo_to_stat(HANDLE hFile, struct stat *buf) +{ + BY_HANDLE_FILE_INFORMATION fiData; + + memset(buf, 0, sizeof(*buf)); + + /* + * GetFileInformationByHandle minimum supported version: Windows XP and + * Windows Server 2003, so it exists everywhere we care about. + */ + if (!GetFileInformationByHandle(hFile, &fiData)) + { + _dosmaperr(GetLastError()); + return -1; + } + + if (fiData.ftLastWriteTime.dwLowDateTime || + fiData.ftLastWriteTime.dwHighDateTime) + buf->st_mtime = filetime_to_time(&fiData.ftLastWriteTime); + + if (fiData.ftLastAccessTime.dwLowDateTime || + fiData.ftLastAccessTime.dwHighDateTime) + buf->st_atime = filetime_to_time(&fiData.ftLastAccessTime); + else + buf->st_atime = buf->st_mtime; + + if (fiData.ftCreationTime.dwLowDateTime || + fiData.ftCreationTime.dwHighDateTime) + buf->st_ctime = filetime_to_time(&fiData.ftCreationTime); + else + buf->st_ctime = buf->st_mtime; + + buf->st_mode = fileattr_to_unixmode(fiData.dwFileAttributes); + buf->st_nlink = fiData.nNumberOfLinks; + + buf->st_size = ((((uint64) fiData.nFileSizeHigh) << 32) | + fiData.nFileSizeLow); + + return 0; +} + +/* + * Windows implementation of stat(). + * + * This currently also implements lstat(), though perhaps that should change. + */ +int +_pgstat64(const char *name, struct stat *buf) +{ + /* + * We must use a handle so lstat() returns the information of the target + * file. To have a reliable test for ERROR_DELETE_PENDING, we use + * NtQueryInformationFile from Windows 2000 or + * GetFileInformationByHandleEx from Server 2008 / Vista. + */ + SECURITY_ATTRIBUTES sa; + HANDLE hFile; + int ret; +#if _WIN32_WINNT < 0x0600 + IO_STATUS_BLOCK ioStatus; + FILE_STANDARD_INFORMATION standardInfo; +#else + FILE_STANDARD_INFO standardInfo; +#endif + + if (name == NULL || buf == NULL) + { + errno = EINVAL; + return -1; + } + + /* fast not-exists check */ + if (GetFileAttributes(name) == INVALID_FILE_ATTRIBUTES) + { + _dosmaperr(GetLastError()); + return -1; + } + + /* get a file handle as lightweight as we can */ + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + hFile = CreateFile(name, + GENERIC_READ, + (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), + &sa, + OPEN_EXISTING, + (FILE_FLAG_NO_BUFFERING | FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OVERLAPPED), + NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + DWORD err = GetLastError(); + + CloseHandle(hFile); + _dosmaperr(err); + return -1; + } + + memset(&standardInfo, 0, sizeof(standardInfo)); + +#if _WIN32_WINNT < 0x0600 + if (_NtQueryInformationFile == NULL) + { + /* First time through: load ntdll.dll and find NtQueryInformationFile */ + LoadNtdll(); + if (ntdll == NULL) + { + DWORD err = GetLastError(); + + CloseHandle(hFile); + _dosmaperr(err); + return -1; + } + + _NtQueryInformationFile = (PFN_NTQUERYINFORMATIONFILE) (pg_funcptr_t) + GetProcAddress(ntdll, "NtQueryInformationFile"); + if (_NtQueryInformationFile == NULL) + { + DWORD err = GetLastError(); + + CloseHandle(hFile); + _dosmaperr(err); + return -1; + } + } + + if (!NT_SUCCESS(_NtQueryInformationFile(hFile, &ioStatus, &standardInfo, + sizeof(standardInfo), + FileStandardInformation))) + { + DWORD err = GetLastError(); + + CloseHandle(hFile); + _dosmaperr(err); + return -1; + } +#else + if (!GetFileInformationByHandleEx(hFile, FileStandardInfo, &standardInfo, + sizeof(standardInfo))) + { + DWORD err = GetLastError(); + + CloseHandle(hFile); + _dosmaperr(err); + return -1; + } +#endif /* _WIN32_WINNT < 0x0600 */ + + if (standardInfo.DeletePending) + { + /* + * File has been deleted, but is not gone from the filesystem yet. + * This can happen when some process with FILE_SHARE_DELETE has it + * open, and it will be fully removed once that handle is closed. + * Meanwhile, we can't open it, so indicate that the file just doesn't + * exist. + */ + CloseHandle(hFile); + errno = ENOENT; + return -1; + } + + /* At last we can invoke fileinfo_to_stat */ + ret = fileinfo_to_stat(hFile, buf); + + CloseHandle(hFile); + return ret; +} + +/* + * Windows implementation of fstat(). + */ +int +_pgfstat64(int fileno, struct stat *buf) +{ + HANDLE hFile = (HANDLE) _get_osfhandle(fileno); + + if (hFile == INVALID_HANDLE_VALUE || buf == NULL) + { + errno = EINVAL; + return -1; + } + + /* + * Since we already have a file handle there is no need to check for + * ERROR_DELETE_PENDING. + */ + + return fileinfo_to_stat(hFile, buf); +} + +#endif /* WIN32 */ diff --git a/src/template/netbsd b/src/template/netbsd index d97f995c748d..aaa560cd92c9 100644 --- a/src/template/netbsd +++ b/src/template/netbsd @@ -1,5 +1,4 @@ # src/template/netbsd -# tools/thread/thread_test must be run # Extra CFLAGS for code that will go into a shared library CFLAGS_SL="-fPIC -DPIC" diff --git a/src/test/Makefile b/src/test/Makefile index 1ac0142b97aa..b6201b677781 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -14,7 +14,8 @@ include $(top_builddir)/src/Makefile.global # GPDB_12_MERGE_FEATURE_NOT_SUPPORTED Since logical replication doesn't work for GPDB, # removed "subscription" test from below list. -SUBDIRS = perl regress isolation modules authentication recovery +SUBDIRS = perl regress isolation modules authentication recovery \ + locale SUBDIRS += fsync walrep heap_checksum isolation2 fdw @@ -41,7 +42,7 @@ endif # clean" etc to recurse into them. (We must filter out those that we # have conditionally included into SUBDIRS above, else there will be # make confusion.) -ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos ldap locale thread ssl) +ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos ldap locale ssl) # We want to recurse to all subdirs for all standard targets, except that # installcheck and install should not recurse into the subdirectory "modules". diff --git a/src/test/README b/src/test/README index b5ccfc0cf673..afdc76765190 100644 --- a/src/test/README +++ b/src/test/README @@ -9,7 +9,7 @@ Not all these tests get run by "make check". Check src/test/Makefile to see which tests get run automatically. authentication/ - Tests for authentication + Tests for authentication (but see also below) examples/ Demonstration programs for libpq that double as regression tests via @@ -18,6 +18,12 @@ examples/ isolation/ Tests for concurrent behavior at the SQL level +kerberos/ + Tests for Kerberos/GSSAPI authentication and encryption + +ldap/ + Tests for LDAP-based authentication + locale/ Sanity checks for locale data, encodings, etc @@ -42,6 +48,3 @@ ssl/ subscription/ Tests for logical replication - -thread/ - A thread-safety-testing utility used by configure diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl index 1305de0051a6..36a616d7c7a7 100644 --- a/src/test/authentication/t/001_password.pl +++ b/src/test/authentication/t/001_password.pl @@ -17,7 +17,7 @@ } else { - plan tests => 10; + plan tests => 13; } @@ -29,7 +29,8 @@ sub reset_pg_hba my $hba_method = shift; unlink($node->data_dir . '/pg_hba.conf'); - $node->append_conf('pg_hba.conf', "local all all $hba_method"); + # just for testing purposes, use a continuation line + $node->append_conf('pg_hba.conf', "local all all\\\n $hba_method"); $node->reload; return; } @@ -45,7 +46,9 @@ sub test_role $status_string = 'success' if ($expected_res eq 0); - my $res = $node->psql('postgres', undef, extra_params => [ '-U', $role ]); + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my $res = $node->psql('postgres', undef, extra_params => [ '-U', $role, '-w' ]); is($res, $expected_res, "authentication $status_string for method $method, role $role"); return; @@ -96,3 +99,27 @@ sub test_role reset_pg_hba($node, 'scram-sha-256'); $ENV{"PGCHANNELBINDING"} = 'require'; test_role($node, 'scram_role', 'scram-sha-256', 2); + +# Test .pgpass processing; but use a temp file, don't overwrite the real one! +my $pgpassfile = "${TestLib::tmp_check}/pgpass"; + +delete $ENV{"PGPASSWORD"}; +delete $ENV{"PGCHANNELBINDING"}; +$ENV{"PGPASSFILE"} = $pgpassfile; + +unlink($pgpassfile); +append_to_file($pgpassfile, qq! +# This very long comment is just here to exercise handling of long lines in the file. This very long comment is just here to exercise handling of long lines in the file. This very long comment is just here to exercise handling of long lines in the file. This very long comment is just here to exercise handling of long lines in the file. This very long comment is just here to exercise handling of long lines in the file. +*:*:postgres:scram_role:pass:this is not part of the password. +!); +chmod 0600, $pgpassfile or die; + +reset_pg_hba($node, 'password'); +test_role($node, 'scram_role', 'password from pgpass', 0); +test_role($node, 'md5_role', 'password from pgpass', 2); + +append_to_file($pgpassfile, qq! +*:*:*:md5_role:p\\ass +!); + +test_role($node, 'md5_role', 'password from pgpass', 0); diff --git a/src/test/isolation/Makefile b/src/test/isolation/Makefile index eb6a5b9b274a..53e240734f16 100644 --- a/src/test/isolation/Makefile +++ b/src/test/isolation/Makefile @@ -33,12 +33,16 @@ atmsort.pm: explain.pm: rm -f $@ && $(LN_S) $(top_builddir)/src/test/regress/explain.pm -# Though we don't install these binaries, build them during installation -# (including temp-install). Otherwise, "make -j check-world" and "make -j -# installcheck-world" would spawn multiple, concurrent builds in this -# directory. Later builds would overwrite files while earlier builds are -# reading them, causing occasional failures. -install: | all +install: all installdirs + $(INSTALL_PROGRAM) pg_isolation_regress$(X) '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_isolation_regress$(X)' + $(INSTALL_PROGRAM) isolationtester$(X) '$(DESTDIR)$(pgxsdir)/$(subdir)/isolationtester$(X)' + +installdirs: + $(MKDIR_P) '$(DESTDIR)$(pgxsdir)/$(subdir)' + +uninstall: + rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_isolation_regress$(X)' + rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/isolationtester$(X)' submake-regress: $(MAKE) -C $(top_builddir)/src/test/regress pg_regress.o diff --git a/src/test/isolation/expected/horizons.out b/src/test/isolation/expected/horizons.out new file mode 100644 index 000000000000..113faac716d2 --- /dev/null +++ b/src/test/isolation/expected/horizons.out @@ -0,0 +1,283 @@ +Parsed test spec with 2 sessions + +starting permutation: pruner_create_perm ll_start pruner_query_plan pruner_query pruner_query pruner_delete pruner_query pruner_query ll_commit pruner_drop +step pruner_create_perm: + CREATE TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); + INSERT INTO horizons_tst(data) VALUES(1),(2); + +step ll_start: + BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; + SELECT 1; + +?column? + +1 +step pruner_query_plan: + EXPLAIN (COSTS OFF) SELECT * FROM horizons_tst ORDER BY data; + +QUERY PLAN + +Index Only Scan using horizons_tst_data_key on horizons_tst +Optimizer: Postgres query optimizer +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_delete: + DELETE FROM horizons_tst; + +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step ll_commit: COMMIT; +step pruner_drop: + DROP TABLE horizons_tst; + + +starting permutation: pruner_create_temp ll_start pruner_query_plan pruner_query pruner_query pruner_delete pruner_query pruner_query ll_commit pruner_drop +step pruner_create_temp: + CREATE TEMPORARY TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); + INSERT INTO horizons_tst(data) VALUES(1),(2); + +step ll_start: + BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; + SELECT 1; + +?column? + +1 +step pruner_query_plan: + EXPLAIN (COSTS OFF) SELECT * FROM horizons_tst ORDER BY data; + +QUERY PLAN + +Index Only Scan using horizons_tst_data_key on horizons_tst +Optimizer: Postgres query optimizer +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_delete: + DELETE FROM horizons_tst; + +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +0 +step ll_commit: COMMIT; +step pruner_drop: + DROP TABLE horizons_tst; + + +starting permutation: pruner_create_temp ll_start pruner_query pruner_query pruner_begin pruner_delete pruner_query pruner_query ll_commit pruner_commit pruner_drop +step pruner_create_temp: + CREATE TEMPORARY TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); + INSERT INTO horizons_tst(data) VALUES(1),(2); + +step ll_start: + BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; + SELECT 1; + +?column? + +1 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_begin: BEGIN; +step pruner_delete: + DELETE FROM horizons_tst; + +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step ll_commit: COMMIT; +step pruner_commit: COMMIT; +step pruner_drop: + DROP TABLE horizons_tst; + + +starting permutation: pruner_create_perm ll_start pruner_query pruner_query pruner_delete pruner_vacuum pruner_query pruner_query ll_commit pruner_drop +step pruner_create_perm: + CREATE TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); + INSERT INTO horizons_tst(data) VALUES(1),(2); + +step ll_start: + BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; + SELECT 1; + +?column? + +1 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_delete: + DELETE FROM horizons_tst; + +step pruner_vacuum: + VACUUM horizons_tst; + +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step ll_commit: COMMIT; +step pruner_drop: + DROP TABLE horizons_tst; + + +starting permutation: pruner_create_temp ll_start pruner_query pruner_query pruner_delete pruner_vacuum pruner_query pruner_query ll_commit pruner_drop +step pruner_create_temp: + CREATE TEMPORARY TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); + INSERT INTO horizons_tst(data) VALUES(1),(2); + +step ll_start: + BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; + SELECT 1; + +?column? + +1 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +2 +step pruner_delete: + DELETE FROM horizons_tst; + +step pruner_vacuum: + VACUUM horizons_tst; + +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +0 +step pruner_query: + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; + +?column? + +0 +step ll_commit: COMMIT; +step pruner_drop: + DROP TABLE horizons_tst; + diff --git a/src/test/isolation/expected/partition-concurrent-attach.out b/src/test/isolation/expected/partition-concurrent-attach.out new file mode 100644 index 000000000000..17fac3998982 --- /dev/null +++ b/src/test/isolation/expected/partition-concurrent-attach.out @@ -0,0 +1,49 @@ +Parsed test spec with 2 sessions + +starting permutation: s1b s1a s2b s2i s1c s2c s2s +step s1b: begin; +step s1a: alter table tpart attach partition tpart_2 for values from (100) to (200); +step s2b: begin; +step s2i: insert into tpart values (110,'xxx'), (120, 'yyy'), (150, 'zzz'); +step s1c: commit; +step s2i: <... completed> +error in steps s1c s2i: ERROR: new row for relation "tpart_default" violates partition constraint +step s2c: commit; +step s2s: select tableoid::regclass, * from tpart; +tableoid i j + +tpart_2 110 xxx +tpart_2 120 yyy +tpart_2 150 zzz + +starting permutation: s1b s1a s2b s2i2 s1c s2c s2s +step s1b: begin; +step s1a: alter table tpart attach partition tpart_2 for values from (100) to (200); +step s2b: begin; +step s2i2: insert into tpart_default (i, j) values (110, 'xxx'), (120, 'yyy'), (150, 'zzz'); +step s1c: commit; +step s2i2: <... completed> +error in steps s1c s2i2: ERROR: new row for relation "tpart_default" violates partition constraint +step s2c: commit; +step s2s: select tableoid::regclass, * from tpart; +tableoid i j + +tpart_2 110 xxx +tpart_2 120 yyy +tpart_2 150 zzz + +starting permutation: s1b s2b s2i s1a s2c s1c s2s +step s1b: begin; +step s2b: begin; +step s2i: insert into tpart values (110,'xxx'), (120, 'yyy'), (150, 'zzz'); +step s1a: alter table tpart attach partition tpart_2 for values from (100) to (200); +step s2c: commit; +step s1a: <... completed> +error in steps s2c s1a: ERROR: updated partition constraint for default partition "tpart_default_default" would be violated by some row +step s1c: commit; +step s2s: select tableoid::regclass, * from tpart; +tableoid i j + +tpart_default_default110 xxx +tpart_default_default120 yyy +tpart_default_default150 zzz diff --git a/src/test/isolation/expected/reindex-schema.out b/src/test/isolation/expected/reindex-schema.out new file mode 100644 index 000000000000..0884e7541236 --- /dev/null +++ b/src/test/isolation/expected/reindex-schema.out @@ -0,0 +1,17 @@ +Parsed test spec with 3 sessions + +starting permutation: begin1 lock1 reindex2 drop3 end1 +step begin1: BEGIN; +step lock1: LOCK reindex_schema.tab_locked IN SHARE UPDATE EXCLUSIVE MODE; +step reindex2: REINDEX SCHEMA reindex_schema; +step drop3: DROP TABLE reindex_schema.tab_dropped; +step end1: COMMIT; +step reindex2: <... completed> + +starting permutation: begin1 lock1 reindex_conc2 drop3 end1 +step begin1: BEGIN; +step lock1: LOCK reindex_schema.tab_locked IN SHARE UPDATE EXCLUSIVE MODE; +step reindex_conc2: REINDEX SCHEMA CONCURRENTLY reindex_schema; +step drop3: DROP TABLE reindex_schema.tab_dropped; +step end1: COMMIT; +step reindex_conc2: <... completed> diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index bf2be46fbeeb..a829dec09d88 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -102,6 +102,7 @@ test: delete-abort-savept #test: lock-committed-keyupdate test: update-locked-tuple #test: reindex-concurrently +#test: reindex-schema #test: propagate-lock-delete #test: tuplelock-conflict #test: tuplelock-update @@ -132,9 +133,11 @@ test: timeouts test: vacuum-concurrent-drop test: vacuum-conflict test: vacuum-skip-locked +test: horizons #test: predicate-hash #test: predicate-gist #test: predicate-gin +#test: partition-concurrent-attach test: partition-drop-index-locking diff --git a/src/test/isolation/specs/horizons.spec b/src/test/isolation/specs/horizons.spec new file mode 100644 index 000000000000..f74035c42f4a --- /dev/null +++ b/src/test/isolation/specs/horizons.spec @@ -0,0 +1,169 @@ +# Test that pruning and vacuuming pay attention to concurrent sessions +# in the right way. For normal relations that means that rows cannot +# be pruned away if there's an older snapshot, in contrast to that +# temporary tables should nearly always be prunable. +# +# NB: Think hard before adding a test showing that rows in permanent +# tables get pruned - it's quite likely that it'd be racy, e.g. due to +# an autovacuum worker holding a snapshot. + +setup { + CREATE OR REPLACE FUNCTION explain_json(p_query text) + RETURNS json + LANGUAGE plpgsql AS $$ + DECLARE + v_ret json; + BEGIN + EXECUTE p_query INTO STRICT v_ret; + RETURN v_ret; + END;$$; +} + +teardown { + DROP FUNCTION explain_json(text); +} + +session "lifeline" + +# Start a transaction, force a snapshot to be held +step "ll_start" +{ + BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; + SELECT 1; +} + +step "ll_commit" { COMMIT; } + + +session "pruner" + +setup +{ + SET enable_seqscan = false; + SET enable_indexscan = false; + SET enable_bitmapscan = false; +} + +step "pruner_create_temp" +{ + CREATE TEMPORARY TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); + INSERT INTO horizons_tst(data) VALUES(1),(2); +} + +step "pruner_create_perm" +{ + CREATE TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); + INSERT INTO horizons_tst(data) VALUES(1),(2); +} + +# Temp tables cannot be dropped in the teardown, so just always do so +# as part of the permutation +step "pruner_drop" +{ + DROP TABLE horizons_tst; +} + +step "pruner_delete" +{ + DELETE FROM horizons_tst; +} + +step "pruner_begin" { BEGIN; } +step "pruner_commit" { COMMIT; } + +step "pruner_vacuum" +{ + VACUUM horizons_tst; +} + +# Show the heap fetches of an ordered index-only-scan (other plans +# have been forbidden above) - that tells us how many non-killed leaf +# entries there are. +step "pruner_query" +{ + SELECT explain_json($$ + EXPLAIN (FORMAT json, BUFFERS, ANALYZE) + SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; +} + +# Verify that the query plan still is an IOS +step "pruner_query_plan" +{ + EXPLAIN (COSTS OFF) SELECT * FROM horizons_tst ORDER BY data; +} + + +# Show that with a permanent relation deleted rows cannot be pruned +# away if there's a concurrent session still seeing the rows. +permutation + "pruner_create_perm" + "ll_start" + "pruner_query_plan" + # Run query that could do pruning twice, first has chance to prune, + # second would not perform heap fetches if first query did. + "pruner_query" + "pruner_query" + "pruner_delete" + "pruner_query" + "pruner_query" + "ll_commit" + "pruner_drop" + +# Show that with a temporary relation deleted rows can be pruned away, +# even if there's a concurrent session with a snapshot from before the +# deletion. That's safe because the session with the older snapshot +# cannot access the temporary table. +permutation + "pruner_create_temp" + "ll_start" + "pruner_query_plan" + "pruner_query" + "pruner_query" + "pruner_delete" + "pruner_query" + "pruner_query" + "ll_commit" + "pruner_drop" + +# Verify that pruning in temporary relations doesn't remove rows still +# visible in the current session +permutation + "pruner_create_temp" + "ll_start" + "pruner_query" + "pruner_query" + "pruner_begin" + "pruner_delete" + "pruner_query" + "pruner_query" + "ll_commit" + "pruner_commit" + "pruner_drop" + +# Show that vacuum cannot remove deleted rows still visible to another +# session's snapshot, when accessing a permanent table. +permutation + "pruner_create_perm" + "ll_start" + "pruner_query" + "pruner_query" + "pruner_delete" + "pruner_vacuum" + "pruner_query" + "pruner_query" + "ll_commit" + "pruner_drop" + +# Show that vacuum can remove deleted rows still visible to another +# session's snapshot, when accessing a temporary table. +permutation + "pruner_create_temp" + "ll_start" + "pruner_query" + "pruner_query" + "pruner_delete" + "pruner_vacuum" + "pruner_query" + "pruner_query" + "ll_commit" + "pruner_drop" diff --git a/src/test/isolation/specs/partition-concurrent-attach.spec b/src/test/isolation/specs/partition-concurrent-attach.spec new file mode 100644 index 000000000000..48c3f83e0c8b --- /dev/null +++ b/src/test/isolation/specs/partition-concurrent-attach.spec @@ -0,0 +1,43 @@ +# Verify that default partition constraint is enforced correctly +# in light of partitions being added concurrently to its parent +setup { + drop table if exists tpart; + create table tpart(i int, j text) partition by range(i); + create table tpart_1(like tpart); + create table tpart_2(like tpart); + create table tpart_default (a int, j text, i int) partition by list (j); + create table tpart_default_default (a int, i int, b int, j text); + alter table tpart_default_default drop b; + alter table tpart_default attach partition tpart_default_default default; + alter table tpart_default drop a; + alter table tpart attach partition tpart_default default; + alter table tpart attach partition tpart_1 for values from(0) to (100); + insert into tpart_2 values (110,'xxx'), (120, 'yyy'), (150, 'zzz'); +} + +session "s1" +step "s1b" { begin; } +step "s1a" { alter table tpart attach partition tpart_2 for values from (100) to (200); } +step "s1c" { commit; } + +session "s2" +step "s2b" { begin; } +step "s2i" { insert into tpart values (110,'xxx'), (120, 'yyy'), (150, 'zzz'); } +step "s2i2" { insert into tpart_default (i, j) values (110, 'xxx'), (120, 'yyy'), (150, 'zzz'); } +step "s2c" { commit; } +step "s2s" { select tableoid::regclass, * from tpart; } + +teardown { drop table tpart; } + +# insert into tpart by s2 which routes to tpart_default due to not seeing +# concurrently added tpart_2 should fail, because the partition constraint +# of tpart_default would have changed due to tpart_2 having been added +permutation "s1b" "s1a" "s2b" "s2i" "s1c" "s2c" "s2s" + +# similar to above, but now insert into sub-partitioned tpart_default +permutation "s1b" "s1a" "s2b" "s2i2" "s1c" "s2c" "s2s" + +# reverse: now the insert into tpart_default by s2 occurs first followed by +# attach in s1, which should fail when it scans the leaf default partition +# find the violating rows +permutation "s1b" "s2b" "s2i" "s1a" "s2c" "s1c" "s2s" diff --git a/src/test/isolation/specs/reindex-schema.spec b/src/test/isolation/specs/reindex-schema.spec new file mode 100644 index 000000000000..feff8a5ec05d --- /dev/null +++ b/src/test/isolation/specs/reindex-schema.spec @@ -0,0 +1,32 @@ +# REINDEX with schemas +# +# Check that concurrent drop of relations while doing a REINDEX +# SCHEMA allows the command to work. + +setup +{ + CREATE SCHEMA reindex_schema; + CREATE TABLE reindex_schema.tab_locked (a int PRIMARY KEY); + CREATE TABLE reindex_schema.tab_dropped (a int PRIMARY KEY); +} + +teardown +{ + DROP SCHEMA reindex_schema CASCADE; +} + +session "s1" +step "begin1" { BEGIN; } +step "lock1" { LOCK reindex_schema.tab_locked IN SHARE UPDATE EXCLUSIVE MODE; } +step "end1" { COMMIT; } + +session "s2" +step "reindex2" { REINDEX SCHEMA reindex_schema; } +step "reindex_conc2" { REINDEX SCHEMA CONCURRENTLY reindex_schema; } + +session "s3" +step "drop3" { DROP TABLE reindex_schema.tab_dropped; } + +# The table can be dropped while reindex is waiting. +permutation "begin1" "lock1" "reindex2" "drop3" "end1" +permutation "begin1" "lock1" "reindex_conc2" "drop3" "end1" diff --git a/src/test/isolation2/expected/lockmodes.out b/src/test/isolation2/expected/lockmodes.out index 8e64c82a51a9..0517dd9af559 100644 --- a/src/test/isolation2/expected/lockmodes.out +++ b/src/test/isolation2/expected/lockmodes.out @@ -1040,13 +1040,12 @@ BEGIN DELETE 10 -- on QD, there's a lock on the root and the target partition 1: select * from show_locks_lockmodes; - locktype | mode | granted | relation -----------+-----------------+---------+--------------------------------- - relation | AccessShareLock | t | t_lockmods_part_tbl_dml - relation | ExclusiveLock | t | t_lockmods_part_tbl_dml - relation | ExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_2 - relation | ExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_1 -(4 rows) + locktype | mode | granted | relation +----------+---------------+---------+--------------------------------- + relation | ExclusiveLock | t | t_lockmods_part_tbl_dml + relation | ExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_2 + relation | ExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_1 +(3 rows) 1: ROLLBACK; ROLLBACK @@ -1094,13 +1093,12 @@ BEGIN UPDATE 1 -- on QD, there's a lock on the root and the target partition 1: select * from show_locks_lockmodes; - locktype | mode | granted | relation -----------+-----------------+---------+--------------------------------- - relation | AccessShareLock | t | t_lockmods_part_tbl_dml - relation | ExclusiveLock | t | t_lockmods_part_tbl_dml - relation | ExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_2 - relation | ExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_1 -(4 rows) + locktype | mode | granted | relation +----------+---------------+---------+--------------------------------- + relation | ExclusiveLock | t | t_lockmods_part_tbl_dml + relation | ExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_2 + relation | ExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_1 +(3 rows) 1: ROLLBACK; ROLLBACK 1q: ... @@ -1111,11 +1109,10 @@ BEGIN DELETE 10 -- since the delete operation is on leaf part, there will be a lock on QD 1: select * from show_locks_lockmodes; - locktype | mode | granted | relation -----------+-----------------+---------+--------------------------------- - relation | AccessShareLock | t | t_lockmods_part_tbl_dml - relation | ExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_1 -(2 rows) + locktype | mode | granted | relation +----------+---------------+---------+--------------------------------- + relation | ExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_1 +(1 row) 1: ROLLBACK; ROLLBACK 1q: ... @@ -2148,9 +2145,8 @@ DELETE 10 ----------+------------------+---------+--------------------------------- relation | RowExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_2 relation | RowExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_1 - relation | AccessShareLock | t | t_lockmods_part_tbl_dml relation | RowExclusiveLock | t | t_lockmods_part_tbl_dml -(4 rows) +(3 rows) 1: ROLLBACK; ROLLBACK 1q: ... @@ -2165,9 +2161,8 @@ UPDATE 1 ----------+------------------+---------+--------------------------------- relation | RowExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_2 relation | RowExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_1 - relation | AccessShareLock | t | t_lockmods_part_tbl_dml relation | RowExclusiveLock | t | t_lockmods_part_tbl_dml -(4 rows) +(3 rows) 1: ROLLBACK; ROLLBACK 1q: ... @@ -2180,9 +2175,8 @@ DELETE 10 1: select * from show_locks_lockmodes; locktype | mode | granted | relation ----------+------------------+---------+--------------------------------- - relation | AccessShareLock | t | t_lockmods_part_tbl_dml relation | RowExclusiveLock | t | t_lockmods_part_tbl_dml_1_prt_1 -(2 rows) +(1 row) 1: ROLLBACK; ROLLBACK 1q: ... diff --git a/src/test/isolation2/input/ao_upgrade.source b/src/test/isolation2/input/ao_upgrade.source index 30e065501006..7c4ab8535a82 100644 --- a/src/test/isolation2/input/ao_upgrade.source +++ b/src/test/isolation2/input/ao_upgrade.source @@ -72,13 +72,13 @@ SET enable_seqscan TO off; -- Ensure we're using a bitmap scan for our tests. Upgrade note to developers: -- the only thing that this test needs to verify is that a fetch-based scan is -- in use. Other diffs are fine. -EXPLAIN SELECT n FROM ao_upgrade_test WHERE n = (9 !); -EXPLAIN SELECT n FROM aocs_upgrade_test WHERE n = (9 !); -EXPLAIN SELECT n FROM aocs_rle_upgrade_test WHERE n = (9 !); +EXPLAIN SELECT n FROM ao_upgrade_test WHERE n = factorial(9); +EXPLAIN SELECT n FROM aocs_upgrade_test WHERE n = factorial(9); +EXPLAIN SELECT n FROM aocs_rle_upgrade_test WHERE n = factorial(9); -SELECT n FROM ao_upgrade_test WHERE n = (9 !); -SELECT n FROM aocs_upgrade_test WHERE n = (9 !); -SELECT n FROM aocs_rle_upgrade_test WHERE n = (9 !); +SELECT n FROM ao_upgrade_test WHERE n = factorial(9); +SELECT n FROM aocs_upgrade_test WHERE n = factorial(9); +SELECT n FROM aocs_rle_upgrade_test WHERE n = factorial(9); RESET enable_seqscan; diff --git a/src/test/isolation2/input/resgroup/resgroup_cpuset.source b/src/test/isolation2/input/resgroup/resgroup_cpuset.source index 64dde1ebc27c..3c4894325440 100644 --- a/src/test/isolation2/input/resgroup/resgroup_cpuset.source +++ b/src/test/isolation2/input/resgroup/resgroup_cpuset.source @@ -170,11 +170,11 @@ CREATE VIEW busy AS bigtable t3, bigtable t4, bigtable t5 - WHERE 0 = (t1.c1 % 2 + 10000)! - AND 0 = (t2.c1 % 2 + 10000)! - AND 0 = (t3.c1 % 2 + 10000)! - AND 0 = (t4.c1 % 2 + 10000)! - AND 0 = (t5.c1 % 2 + 10000)! + WHERE 0 = factorial(t1.c1 % 2 + 10000) + AND 0 = factorial(t2.c1 % 2 + 10000) + AND 0 = factorial(t3.c1 % 2 + 10000) + AND 0 = factorial(t4.c1 % 2 + 10000) + AND 0 = factorial(t5.c1 % 2 + 10000) ; CREATE VIEW cancel_all AS diff --git a/src/test/isolation2/output/ao_upgrade.source b/src/test/isolation2/output/ao_upgrade.source index 3044d3a9e072..8cb1cfafd731 100644 --- a/src/test/isolation2/output/ao_upgrade.source +++ b/src/test/isolation2/output/ao_upgrade.source @@ -149,7 +149,7 @@ SET -- Ensure we're using a bitmap scan for our tests. Upgrade note to developers: -- the only thing that this test needs to verify is that a fetch-based scan is -- in use. Other diffs are fine. -EXPLAIN SELECT n FROM ao_upgrade_test WHERE n = (9 !); +EXPLAIN SELECT n FROM ao_upgrade_test WHERE n = factorial(9); QUERY PLAN ------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (cost=1000.36..1100.37 rows=1 width=9) @@ -160,7 +160,7 @@ EXPLAIN SELECT n FROM ao_upgrade_test WHERE n = (9 !); Settings: enable_seqscan=off Optimizer status: Postgres query optimizer (7 rows) -EXPLAIN SELECT n FROM aocs_upgrade_test WHERE n = (9 !); +EXPLAIN SELECT n FROM aocs_upgrade_test WHERE n = factorial(9); QUERY PLAN ------------------------------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (cost=1000.36..1100.37 rows=1 width=9) @@ -171,7 +171,7 @@ EXPLAIN SELECT n FROM aocs_upgrade_test WHERE n = (9 !); Settings: enable_seqscan=off Optimizer status: Postgres query optimizer (7 rows) -EXPLAIN SELECT n FROM aocs_rle_upgrade_test WHERE n = (9 !); +EXPLAIN SELECT n FROM aocs_rle_upgrade_test WHERE n = factorial(9); QUERY PLAN ---------------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (cost=1000.36..1100.37 rows=1 width=9) @@ -183,17 +183,17 @@ EXPLAIN SELECT n FROM aocs_rle_upgrade_test WHERE n = (9 !); Optimizer status: Postgres query optimizer (7 rows) -SELECT n FROM ao_upgrade_test WHERE n = (9 !); +SELECT n FROM ao_upgrade_test WHERE n = factorial(9); n -------- 362880 (1 row) -SELECT n FROM aocs_upgrade_test WHERE n = (9 !); +SELECT n FROM aocs_upgrade_test WHERE n = factorial(9); n -------- 362880 (1 row) -SELECT n FROM aocs_rle_upgrade_test WHERE n = (9 !); +SELECT n FROM aocs_rle_upgrade_test WHERE n = factorial(9); n -------- 362880 diff --git a/src/test/isolation2/output/ao_upgrade_optimizer.source b/src/test/isolation2/output/ao_upgrade_optimizer.source index 3044d3a9e072..8cb1cfafd731 100644 --- a/src/test/isolation2/output/ao_upgrade_optimizer.source +++ b/src/test/isolation2/output/ao_upgrade_optimizer.source @@ -149,7 +149,7 @@ SET -- Ensure we're using a bitmap scan for our tests. Upgrade note to developers: -- the only thing that this test needs to verify is that a fetch-based scan is -- in use. Other diffs are fine. -EXPLAIN SELECT n FROM ao_upgrade_test WHERE n = (9 !); +EXPLAIN SELECT n FROM ao_upgrade_test WHERE n = factorial(9); QUERY PLAN ------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (cost=1000.36..1100.37 rows=1 width=9) @@ -160,7 +160,7 @@ EXPLAIN SELECT n FROM ao_upgrade_test WHERE n = (9 !); Settings: enable_seqscan=off Optimizer status: Postgres query optimizer (7 rows) -EXPLAIN SELECT n FROM aocs_upgrade_test WHERE n = (9 !); +EXPLAIN SELECT n FROM aocs_upgrade_test WHERE n = factorial(9); QUERY PLAN ------------------------------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (cost=1000.36..1100.37 rows=1 width=9) @@ -171,7 +171,7 @@ EXPLAIN SELECT n FROM aocs_upgrade_test WHERE n = (9 !); Settings: enable_seqscan=off Optimizer status: Postgres query optimizer (7 rows) -EXPLAIN SELECT n FROM aocs_rle_upgrade_test WHERE n = (9 !); +EXPLAIN SELECT n FROM aocs_rle_upgrade_test WHERE n = factorial(9); QUERY PLAN ---------------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (cost=1000.36..1100.37 rows=1 width=9) @@ -183,17 +183,17 @@ EXPLAIN SELECT n FROM aocs_rle_upgrade_test WHERE n = (9 !); Optimizer status: Postgres query optimizer (7 rows) -SELECT n FROM ao_upgrade_test WHERE n = (9 !); +SELECT n FROM ao_upgrade_test WHERE n = factorial(9); n -------- 362880 (1 row) -SELECT n FROM aocs_upgrade_test WHERE n = (9 !); +SELECT n FROM aocs_upgrade_test WHERE n = factorial(9); n -------- 362880 (1 row) -SELECT n FROM aocs_rle_upgrade_test WHERE n = (9 !); +SELECT n FROM aocs_rle_upgrade_test WHERE n = factorial(9); n -------- 362880 diff --git a/src/test/isolation2/output/autovacuum-analyze.source b/src/test/isolation2/output/autovacuum-analyze.source index ecd90669e916..382b443944cd 100644 --- a/src/test/isolation2/output/autovacuum-analyze.source +++ b/src/test/isolation2/output/autovacuum-analyze.source @@ -243,12 +243,12 @@ select relpages, reltuples from pg_class where oid = 'rankpart_1_prt_5'::regclas select relpages, reltuples from pg_class where oid = 'rankpart_1_prt_6'::regclass; relpages | reltuples ----------+----------- - 0 | 0 + 0 | -1 (1 row) select relpages, reltuples from pg_class where oid = 'rankpart'::regclass; relpages | reltuples ----------+----------- - 0 | 0 + 0 | -1 (1 row) @@ -362,7 +362,7 @@ SELECT count(*) FROM pg_statistic where starelid = 'anaabort'::regclass; select relpages, reltuples from pg_class where oid = 'anaabort'::regclass; relpages | reltuples ----------+----------- - 0 | 0 + 0 | -1 (1 row) 1: END; diff --git a/src/test/isolation2/output/resgroup/resgroup_cpuset.source b/src/test/isolation2/output/resgroup/resgroup_cpuset.source index 45798b865d88..c0e7b7383757 100644 --- a/src/test/isolation2/output/resgroup/resgroup_cpuset.source +++ b/src/test/isolation2/output/resgroup/resgroup_cpuset.source @@ -39,7 +39,7 @@ CREATE CREATE TABLE bigtable AS SELECT i AS c1, 'abc' AS c2 FROM generate_series(1,50000) i; CREATE 50000 -CREATE VIEW busy AS SELECT count(*) FROM bigtable t1, bigtable t2, bigtable t3, bigtable t4, bigtable t5 WHERE 0 = (t1.c1 % 2 + 10000)! AND 0 = (t2.c1 % 2 + 10000)! AND 0 = (t3.c1 % 2 + 10000)! AND 0 = (t4.c1 % 2 + 10000)! AND 0 = (t5.c1 % 2 + 10000)! ; +CREATE VIEW busy AS SELECT count(*) FROM bigtable t1, bigtable t2, bigtable t3, bigtable t4, bigtable t5 WHERE 0 = factorial(t1.c1 % 2 + 10000) AND 0 = factorial(t2.c1 % 2 + 10000) AND 0 = factorial(t3.c1 % 2 + 10000) AND 0 = factorial(t4.c1 % 2 + 10000) AND 0 = factorial(t5.c1 % 2 + 10000) ; CREATE CREATE VIEW cancel_all AS SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE query LIKE 'SELECT * FROM busy%'; diff --git a/src/test/isolation2/workfile_mgr_test.c b/src/test/isolation2/workfile_mgr_test.c index fe64e27cc5e7..3e9cb01abf1a 100644 --- a/src/test/isolation2/workfile_mgr_test.c +++ b/src/test/isolation2/workfile_mgr_test.c @@ -524,7 +524,7 @@ logicaltape_test(void) int test_tape = 5; int test_entry = 45000; - LogicalTapeSet *tape_set = LogicalTapeSetCreate(max_tapes, NULL, NULL, -1); + LogicalTapeSet *tape_set = LogicalTapeSetCreate(max_tapes, false, NULL, NULL, -1); int work_tape = 0; long blocknum = 0; diff --git a/src/test/locale/.gitignore b/src/test/locale/.gitignore index 620d3df42542..64e1bf2a8037 100644 --- a/src/test/locale/.gitignore +++ b/src/test/locale/.gitignore @@ -1 +1,2 @@ /test-ctype +/tmp_check/ diff --git a/src/test/locale/Makefile b/src/test/locale/Makefile index 22a45b65f2c7..73495cf16b44 100644 --- a/src/test/locale/Makefile +++ b/src/test/locale/Makefile @@ -4,6 +4,7 @@ subdir = src/test/locale top_builddir = ../../.. include $(top_builddir)/src/Makefile.global +export with_icu PROGS = test-ctype DIRS = de_DE.ISO8859-1 gr_GR.ISO8859-7 koi8-r koi8-to-win1251 @@ -19,3 +20,9 @@ clean distclean maintainer-clean: # These behave like installcheck targets. check-%: all @$(MAKE) -C `echo $@ | sed 's/^check-//'` test + +check: + $(prove_check) + +installcheck: + $(prove_installcheck) diff --git a/src/test/locale/t/001_index.pl b/src/test/locale/t/001_index.pl new file mode 100644 index 000000000000..a67f78cb719c --- /dev/null +++ b/src/test/locale/t/001_index.pl @@ -0,0 +1,67 @@ +use strict; +use warnings; + +use Config; +use PostgresNode; +use TestLib; +use Test::More; + +if ($ENV{with_icu} eq 'yes') +{ + plan tests => 10; +} +else +{ + plan skip_all => 'ICU not supported by this build'; +} + +#### Set up the server + +note "setting up data directory"; +my $node = get_new_node('main'); +$node->init(extra => [ '--encoding=UTF8' ]); + +$ENV{PGHOST} = $node->host; +$ENV{PGPORT} = $node->port; +$node->start; + +sub test_index +{ + my ($err_like, $err_comm) = @_; + my ($ret, $out, $err) = $node->psql('postgres', "SELECT * FROM icu1"); + is($ret, 0, 'SELECT should succeed.'); + like($err, $err_like, $err_comm); +} + +$node->safe_psql('postgres', 'CREATE TABLE icu1(val text);'); +$node->safe_psql('postgres', 'CREATE INDEX icu1_fr ON icu1 (val COLLATE "fr-x-icu");'); + +test_index(qr/^$/, 'No warning should be raised'); + +# Simulate different collation version +$node->safe_psql('postgres', + "UPDATE pg_depend SET refobjversion = 'not_a_version'" + . " WHERE refobjversion IS NOT NULL" + . " AND objid::regclass::text = 'icu1_fr';"); + +test_index(qr/index "icu1_fr" depends on collation "fr-x-icu" version "not_a_version", but the current version is/, + 'Different collation version warning should be raised.'); + +$node->safe_psql('postgres', 'ALTER INDEX icu1_fr ALTER COLLATION "fr-x-icu" REFRESH VERSION;'); + +test_index(qr/^$/, 'No warning should be raised'); + +# Simulate different collation version +$node->safe_psql('postgres', + "UPDATE pg_depend SET refobjversion = 'not_a_version'" + . " WHERE refobjversion IS NOT NULL" + . " AND objid::regclass::text = 'icu1_fr';"); + +test_index(qr/index "icu1_fr" depends on collation "fr-x-icu" version "not_a_version", but the current version is/, + 'Different collation version warning should be raised.'); + +$node->safe_psql('postgres', 'REINDEX TABLE icu1;'); + +test_index(qr/^$/, 'No warning should be raised'); + +$node->stop; diff --git a/src/test/modules/dummy_index_am/dummy_index_am.c b/src/test/modules/dummy_index_am/dummy_index_am.c index e97a32d5be2a..8f4cdab1b334 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -153,7 +153,7 @@ dibuild(Relation heap, Relation index, IndexInfo *indexInfo) } /* - * Build an empty index for the initialiation fork. + * Build an empty index for the initialization fork. */ static void dibuildempty(Relation index) diff --git a/src/test/modules/test_ddl_deparse/expected/create_table.out b/src/test/modules/test_ddl_deparse/expected/create_table.out index c7c9bf8971f3..0f2a2c164eb5 100644 --- a/src/test/modules/test_ddl_deparse/expected/create_table.out +++ b/src/test/modules/test_ddl_deparse/expected/create_table.out @@ -135,6 +135,8 @@ CREATE TABLE like_fkey_table ( INCLUDING STORAGE ); NOTICE: DDL test: type simple, tag CREATE TABLE +NOTICE: DDL test: type alter table, tag ALTER TABLE +NOTICE: subcommand: ALTER COLUMN SET DEFAULT (precooked) NOTICE: DDL test: type simple, tag CREATE INDEX NOTICE: DDL test: type simple, tag CREATE INDEX -- Volatile table types diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index b7bdb88ce7f7..def4e39f19de 100644 --- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -111,6 +111,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS) case AT_ColumnDefault: strtype = "ALTER COLUMN SET DEFAULT"; break; + case AT_CookedColumnDefault: + strtype = "ALTER COLUMN SET DEFAULT (precooked)"; + break; case AT_DropNotNull: strtype = "DROP NOT NULL"; break; diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile index 7428f15ae368..d2014cbb294c 100644 --- a/src/test/modules/test_extensions/Makefile +++ b/src/test/modules/test_extensions/Makefile @@ -5,13 +5,14 @@ PGFILEDESC = "test_extensions - regression testing for EXTENSION support" EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \ test_ext7 test_ext8 test_ext_cine test_ext_cor \ - test_ext_cyclic1 test_ext_cyclic2 + test_ext_cyclic1 test_ext_cyclic2 test_ext_evttrig DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \ test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \ test_ext7--1.0.sql test_ext7--1.0--2.0.sql test_ext8--1.0.sql \ test_ext_cine--1.0.sql test_ext_cine--1.0--1.1.sql \ test_ext_cor--1.0.sql \ - test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql + test_ext_cyclic1--1.0.sql test_ext_cyclic2--1.0.sql \ + test_ext_evttrig--1.0.sql test_ext_evttrig--1.0--2.0.sql REGRESS = test_extensions test_extdepend diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out index 1e9164006204..3168ce6fb1cb 100644 --- a/src/test/modules/test_extensions/expected/test_extensions.out +++ b/src/test/modules/test_extensions/expected/test_extensions.out @@ -154,6 +154,13 @@ DROP TABLE test_ext4_tab; DROP FUNCTION create_extension_with_temp_schema(); RESET client_min_messages; \unset SHOW_CONTEXT + +-- Test case of an event trigger run in an extension upgrade script. +-- See: https://postgr.es/m/20200902193715.6e0269d4@firost +CREATE EXTENSION test_ext_evttrig; +ALTER EXTENSION test_ext_evttrig UPDATE TO '2.0'; +DROP EXTENSION test_ext_evttrig; + -- It's generally bad style to use CREATE OR REPLACE unnecessarily. -- Test what happens if an extension does it anyway. -- Replacing a shell type or operator is sort of like CREATE OR REPLACE; @@ -306,4 +313,3 @@ Objects in extension "test_ext_cine" table ext_cine_tab2 table ext_cine_tab3 (9 rows) - diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql index b3d45793ca3b..41b6cddf0b55 100644 --- a/src/test/modules/test_extensions/sql/test_extensions.sql +++ b/src/test/modules/test_extensions/sql/test_extensions.sql @@ -94,6 +94,12 @@ DROP FUNCTION create_extension_with_temp_schema(); RESET client_min_messages; \unset SHOW_CONTEXT +-- Test case of an event trigger run in an extension upgrade script. +-- See: https://postgr.es/m/20200902193715.6e0269d4@firost +CREATE EXTENSION test_ext_evttrig; +ALTER EXTENSION test_ext_evttrig UPDATE TO '2.0'; +DROP EXTENSION test_ext_evttrig; + -- It's generally bad style to use CREATE OR REPLACE unnecessarily. -- Test what happens if an extension does it anyway. -- Replacing a shell type or operator is sort of like CREATE OR REPLACE; diff --git a/src/test/modules/test_extensions/test_ext_evttrig--1.0--2.0.sql b/src/test/modules/test_extensions/test_ext_evttrig--1.0--2.0.sql new file mode 100644 index 000000000000..fdd2f3542e6c --- /dev/null +++ b/src/test/modules/test_extensions/test_ext_evttrig--1.0--2.0.sql @@ -0,0 +1,7 @@ +/* src/test/modules/test_extensions/test_event_trigger--1.0--2.0.sql */ +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION test_event_trigger UPDATE TO '2.0'" to load this file. \quit + +-- Test extension upgrade with event trigger. +ALTER EVENT TRIGGER table_rewrite_trg DISABLE; +ALTER TABLE t DROP COLUMN id; diff --git a/src/test/modules/test_extensions/test_ext_evttrig--1.0.sql b/src/test/modules/test_extensions/test_ext_evttrig--1.0.sql new file mode 100644 index 000000000000..0071712cb88b --- /dev/null +++ b/src/test/modules/test_extensions/test_ext_evttrig--1.0.sql @@ -0,0 +1,16 @@ +/* src/test/modules/test_extensions/test_event_trigger--1.0.sql */ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_event_trigger" to load this file. \quit + +-- Base table with event trigger, used in a regression test involving +-- extension upgrades. +CREATE TABLE t (id text); +CREATE OR REPLACE FUNCTION _evt_table_rewrite_fnct() +RETURNS EVENT_TRIGGER LANGUAGE plpgsql AS +$$ + BEGIN + END; +$$; +CREATE EVENT TRIGGER table_rewrite_trg + ON table_rewrite + EXECUTE PROCEDURE _evt_table_rewrite_fnct(); diff --git a/src/test/modules/test_extensions/test_ext_evttrig.control b/src/test/modules/test_extensions/test_ext_evttrig.control new file mode 100644 index 000000000000..915fae61666a --- /dev/null +++ b/src/test/modules/test_extensions/test_ext_evttrig.control @@ -0,0 +1,3 @@ +comment = 'Test extension - event trigger' +default_version = '1.0' +relocatable = true diff --git a/src/test/modules/test_misc/t/001_constraint_validation.pl b/src/test/modules/test_misc/t/001_constraint_validation.pl index 22497f22b01c..c9453f9063e6 100644 --- a/src/test/modules/test_misc/t/001_constraint_validation.pl +++ b/src/test/modules/test_misc/t/001_constraint_validation.pl @@ -56,7 +56,7 @@ sub is_table_verified $output = run_sql_command('alter table atacc1 alter test_a set not null;'); ok(!is_table_verified($output), 'with constraint will not scan table'); ok( $output =~ - m/existing constraints on column "atacc1"."test_a" are sufficient to prove that it does not contain nulls/, + m/existing constraints on column "atacc1.test_a" are sufficient to prove that it does not contain nulls/, 'test_a proved by constraints'); run_sql_command('alter table atacc1 alter test_a drop not null;'); @@ -68,7 +68,7 @@ sub is_table_verified ok(is_table_verified($output), 'table was scanned'); # we may miss debug message for test_a constraint because we need verify table due test_b ok( !( $output =~ - m/existing constraints on column "atacc1"."test_b" are sufficient to prove that it does not contain nulls/ + m/existing constraints on column "atacc1.test_b" are sufficient to prove that it does not contain nulls/ ), 'test_b not proved by wrong constraints'); run_sql_command( @@ -84,10 +84,10 @@ sub is_table_verified ); ok(!is_table_verified($output), 'table was not scanned for both columns'); ok( $output =~ - m/existing constraints on column "atacc1"."test_a" are sufficient to prove that it does not contain nulls/, + m/existing constraints on column "atacc1.test_a" are sufficient to prove that it does not contain nulls/, 'test_a proved by constraints'); ok( $output =~ - m/existing constraints on column "atacc1"."test_b" are sufficient to prove that it does not contain nulls/, + m/existing constraints on column "atacc1.test_b" are sufficient to prove that it does not contain nulls/, 'test_b proved by constraints'); run_sql_command('drop table atacc1;'); diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out index 4ee4486daa89..6b1c72f66c01 100644 --- a/src/test/modules/unsafe_tests/expected/rolenames.out +++ b/src/test/modules/unsafe_tests/expected/rolenames.out @@ -1,19 +1,21 @@ -CREATE OR REPLACE FUNCTION chkrolattr() +CREATE FUNCTION chkrolattr() RETURNS TABLE ("role" name, rolekeyword text, canlogin bool, replication bool) AS $$ SELECT r.rolname, v.keyword, r.rolcanlogin, r.rolreplication FROM pg_roles r - JOIN (VALUES(CURRENT_USER, 'current_user'), + JOIN (VALUES(CURRENT_ROLE, 'current_role'), + (CURRENT_USER, 'current_user'), (SESSION_USER, 'session_user'), + ('current_role', '-'), ('current_user', '-'), ('session_user', '-'), ('Public', '-'), ('None', '-')) AS v(uname, keyword) ON (r.rolname = v.uname) - ORDER BY 1; + ORDER BY 1, 2; $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION chksetconfig() +CREATE FUNCTION chksetconfig() RETURNS TABLE (db name, "role" name, rolkeyword text, setconfig text[]) AS $$ SELECT COALESCE(d.datname, 'ALL'), COALESCE(r.rolname, 'ALL'), @@ -21,21 +23,22 @@ SELECT COALESCE(d.datname, 'ALL'), COALESCE(r.rolname, 'ALL'), FROM pg_db_role_setting s LEFT JOIN pg_roles r ON (r.oid = s.setrole) LEFT JOIN pg_database d ON (d.oid = s.setdatabase) - LEFT JOIN (VALUES(CURRENT_USER, 'current_user'), - (SESSION_USER, 'session_user')) + LEFT JOIN (VALUES(CURRENT_ROLE, 'current_role'), + (CURRENT_USER, 'current_user'), + (SESSION_USER, 'session_user')) AS v(uname, keyword) ON (r.rolname = v.uname) WHERE (r.rolname) IN ('Public', 'current_user', 'regress_testrol1', 'regress_testrol2') -ORDER BY 1, 2; +ORDER BY 1, 2, 3; $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION chkumapping() +CREATE FUNCTION chkumapping() RETURNS TABLE (umname name, umserver name, umoptions text[]) AS $$ SELECT r.rolname, s.srvname, m.umoptions FROM pg_user_mapping m LEFT JOIN pg_roles r ON (r.oid = m.umuser) JOIN pg_foreign_server s ON (s.oid = m.umserver) - ORDER BY 2; + ORDER BY 2, 1; $$ LANGUAGE SQL; -- -- We test creation and use of these role names to ensure that the server @@ -46,6 +49,7 @@ $$ LANGUAGE SQL; SET client_min_messages = ERROR; CREATE ROLE "Public"; CREATE ROLE "None"; +CREATE ROLE "current_role"; CREATE ROLE "current_user"; CREATE ROLE "session_user"; CREATE ROLE "user"; @@ -55,7 +59,7 @@ ERROR: CURRENT_USER cannot be used as a role name here LINE 1: CREATE ROLE current_user; ^ CREATE ROLE current_role; -- error -ERROR: syntax error at or near "current_role" +ERROR: CURRENT_ROLE cannot be used as a role name here LINE 1: CREATE ROLE current_role; ^ CREATE ROLE session_user; -- error @@ -112,23 +116,56 @@ SELECT * FROM chkrolattr(); ------------------+--------------+----------+------------- None | - | f | f Public | - | f | f + current_role | - | f | f current_user | - | f | f regress_testrol1 | session_user | t | f + regress_testrol2 | current_role | f | f regress_testrol2 | current_user | f | f session_user | - | f | f -(6 rows) +(8 rows) + +ALTER ROLE CURRENT_ROLE WITH REPLICATION; +SELECT * FROM chkrolattr(); + role | rolekeyword | canlogin | replication +------------------+--------------+----------+------------- + None | - | f | f + Public | - | f | f + current_role | - | f | f + current_user | - | f | f + regress_testrol1 | session_user | t | f + regress_testrol2 | current_role | f | t + regress_testrol2 | current_user | f | t + session_user | - | f | f +(8 rows) +ALTER ROLE "current_role" WITH REPLICATION; +SELECT * FROM chkrolattr(); + role | rolekeyword | canlogin | replication +------------------+--------------+----------+------------- + None | - | f | f + Public | - | f | f + current_role | - | f | t + current_user | - | f | f + regress_testrol1 | session_user | t | f + regress_testrol2 | current_role | f | t + regress_testrol2 | current_user | f | t + session_user | - | f | f +(8 rows) + +ALTER ROLE CURRENT_ROLE WITH NOREPLICATION; ALTER ROLE CURRENT_USER WITH REPLICATION; SELECT * FROM chkrolattr(); role | rolekeyword | canlogin | replication ------------------+--------------+----------+------------- None | - | f | f Public | - | f | f + current_role | - | f | t current_user | - | f | f regress_testrol1 | session_user | t | f + regress_testrol2 | current_role | f | t regress_testrol2 | current_user | f | t session_user | - | f | f -(6 rows) +(8 rows) ALTER ROLE "current_user" WITH REPLICATION; SELECT * FROM chkrolattr(); @@ -136,11 +173,13 @@ SELECT * FROM chkrolattr(); ------------------+--------------+----------+------------- None | - | f | f Public | - | f | f + current_role | - | f | t current_user | - | f | t regress_testrol1 | session_user | t | f + regress_testrol2 | current_role | f | t regress_testrol2 | current_user | f | t session_user | - | f | f -(6 rows) +(8 rows) ALTER ROLE SESSION_USER WITH REPLICATION; SELECT * FROM chkrolattr(); @@ -148,11 +187,13 @@ SELECT * FROM chkrolattr(); ------------------+--------------+----------+------------- None | - | f | f Public | - | f | f + current_role | - | f | t current_user | - | f | t regress_testrol1 | session_user | t | t + regress_testrol2 | current_role | f | t regress_testrol2 | current_user | f | t session_user | - | f | f -(6 rows) +(8 rows) ALTER ROLE "session_user" WITH REPLICATION; SELECT * FROM chkrolattr(); @@ -160,11 +201,13 @@ SELECT * FROM chkrolattr(); ------------------+--------------+----------+------------- None | - | f | f Public | - | f | f + current_role | - | f | t current_user | - | f | t regress_testrol1 | session_user | t | t + regress_testrol2 | current_role | f | t regress_testrol2 | current_user | f | t session_user | - | f | t -(6 rows) +(8 rows) ALTER USER "Public" WITH REPLICATION; ALTER USER "None" WITH REPLICATION; @@ -173,11 +216,13 @@ SELECT * FROM chkrolattr(); ------------------+--------------+----------+------------- None | - | f | t Public | - | f | t + current_role | - | f | t current_user | - | f | t regress_testrol1 | session_user | t | t + regress_testrol2 | current_role | f | t regress_testrol2 | current_user | f | t session_user | - | f | t -(6 rows) +(8 rows) ALTER USER regress_testrol1 WITH NOREPLICATION; ALTER USER regress_testrol2 WITH NOREPLICATION; @@ -186,21 +231,19 @@ SELECT * FROM chkrolattr(); ------------------+--------------+----------+------------- None | - | f | t Public | - | f | t + current_role | - | f | t current_user | - | f | t regress_testrol1 | session_user | t | f + regress_testrol2 | current_role | f | f regress_testrol2 | current_user | f | f session_user | - | f | t -(6 rows) +(8 rows) ROLLBACK; ALTER ROLE USER WITH LOGIN; -- error ERROR: syntax error at or near "USER" LINE 1: ALTER ROLE USER WITH LOGIN; ^ -ALTER ROLE CURRENT_ROLE WITH LOGIN; --error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: ALTER ROLE CURRENT_ROLE WITH LOGIN; - ^ ALTER ROLE ALL WITH REPLICATION; -- error ERROR: syntax error at or near "WITH" LINE 1: ALTER ROLE ALL WITH REPLICATION; @@ -228,23 +271,56 @@ SELECT * FROM chkrolattr(); ------------------+--------------+----------+------------- None | - | f | f Public | - | f | f + current_role | - | f | f current_user | - | f | f regress_testrol1 | session_user | t | f + regress_testrol2 | current_role | f | f regress_testrol2 | current_user | f | f session_user | - | f | f -(6 rows) +(8 rows) + +ALTER USER CURRENT_ROLE WITH REPLICATION; +SELECT * FROM chkrolattr(); + role | rolekeyword | canlogin | replication +------------------+--------------+----------+------------- + None | - | f | f + Public | - | f | f + current_role | - | f | f + current_user | - | f | f + regress_testrol1 | session_user | t | f + regress_testrol2 | current_role | f | t + regress_testrol2 | current_user | f | t + session_user | - | f | f +(8 rows) + +ALTER USER "current_role" WITH REPLICATION; +SELECT * FROM chkrolattr(); + role | rolekeyword | canlogin | replication +------------------+--------------+----------+------------- + None | - | f | f + Public | - | f | f + current_role | - | f | t + current_user | - | f | f + regress_testrol1 | session_user | t | f + regress_testrol2 | current_role | f | t + regress_testrol2 | current_user | f | t + session_user | - | f | f +(8 rows) +ALTER USER CURRENT_ROLE WITH NOREPLICATION; ALTER USER CURRENT_USER WITH REPLICATION; SELECT * FROM chkrolattr(); role | rolekeyword | canlogin | replication ------------------+--------------+----------+------------- None | - | f | f Public | - | f | f + current_role | - | f | t current_user | - | f | f regress_testrol1 | session_user | t | f + regress_testrol2 | current_role | f | t regress_testrol2 | current_user | f | t session_user | - | f | f -(6 rows) +(8 rows) ALTER USER "current_user" WITH REPLICATION; SELECT * FROM chkrolattr(); @@ -252,11 +328,13 @@ SELECT * FROM chkrolattr(); ------------------+--------------+----------+------------- None | - | f | f Public | - | f | f + current_role | - | f | t current_user | - | f | t regress_testrol1 | session_user | t | f + regress_testrol2 | current_role | f | t regress_testrol2 | current_user | f | t session_user | - | f | f -(6 rows) +(8 rows) ALTER USER SESSION_USER WITH REPLICATION; SELECT * FROM chkrolattr(); @@ -264,11 +342,13 @@ SELECT * FROM chkrolattr(); ------------------+--------------+----------+------------- None | - | f | f Public | - | f | f + current_role | - | f | t current_user | - | f | t regress_testrol1 | session_user | t | t + regress_testrol2 | current_role | f | t regress_testrol2 | current_user | f | t session_user | - | f | f -(6 rows) +(8 rows) ALTER USER "session_user" WITH REPLICATION; SELECT * FROM chkrolattr(); @@ -276,11 +356,13 @@ SELECT * FROM chkrolattr(); ------------------+--------------+----------+------------- None | - | f | f Public | - | f | f + current_role | - | f | t current_user | - | f | t regress_testrol1 | session_user | t | t + regress_testrol2 | current_role | f | t regress_testrol2 | current_user | f | t session_user | - | f | t -(6 rows) +(8 rows) ALTER USER "Public" WITH REPLICATION; ALTER USER "None" WITH REPLICATION; @@ -289,11 +371,13 @@ SELECT * FROM chkrolattr(); ------------------+--------------+----------+------------- None | - | f | t Public | - | f | t + current_role | - | f | t current_user | - | f | t regress_testrol1 | session_user | t | t + regress_testrol2 | current_role | f | t regress_testrol2 | current_user | f | t session_user | - | f | t -(6 rows) +(8 rows) ALTER USER regress_testrol1 WITH NOREPLICATION; ALTER USER regress_testrol2 WITH NOREPLICATION; @@ -302,21 +386,19 @@ SELECT * FROM chkrolattr(); ------------------+--------------+----------+------------- None | - | f | t Public | - | f | t + current_role | - | f | t current_user | - | f | t regress_testrol1 | session_user | t | f + regress_testrol2 | current_role | f | f regress_testrol2 | current_user | f | f session_user | - | f | t -(6 rows) +(8 rows) ROLLBACK; ALTER USER USER WITH LOGIN; -- error ERROR: syntax error at or near "USER" LINE 1: ALTER USER USER WITH LOGIN; ^ -ALTER USER CURRENT_ROLE WITH LOGIN; -- error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: ALTER USER CURRENT_ROLE WITH LOGIN; - ^ ALTER USER ALL WITH REPLICATION; -- error ERROR: syntax error at or near "WITH" LINE 1: ALTER USER ALL WITH REPLICATION; @@ -343,6 +425,7 @@ SELECT * FROM chksetconfig(); ----+------+------------+----------- (0 rows) +ALTER ROLE CURRENT_ROLE SET application_name to 'BAZ'; ALTER ROLE CURRENT_USER SET application_name to 'FOO'; ALTER ROLE SESSION_USER SET application_name to 'BAR'; ALTER ROLE "current_user" SET application_name to 'FOOFOO'; @@ -354,8 +437,9 @@ SELECT * FROM chksetconfig(); ALL | Public | - | {application_name=BARBAR} ALL | current_user | - | {application_name=FOOFOO} ALL | regress_testrol1 | session_user | {application_name=BAR} + ALL | regress_testrol2 | current_role | {application_name=FOO} ALL | regress_testrol2 | current_user | {application_name=FOO} -(4 rows) +(5 rows) ALTER ROLE regress_testrol1 SET application_name to 'SLAM'; SELECT * FROM chksetconfig(); @@ -364,9 +448,11 @@ SELECT * FROM chksetconfig(); ALL | Public | - | {application_name=BARBAR} ALL | current_user | - | {application_name=FOOFOO} ALL | regress_testrol1 | session_user | {application_name=SLAM} + ALL | regress_testrol2 | current_role | {application_name=FOO} ALL | regress_testrol2 | current_user | {application_name=FOO} -(4 rows) +(5 rows) +ALTER ROLE CURRENT_ROLE RESET application_name; ALTER ROLE CURRENT_USER RESET application_name; ALTER ROLE SESSION_USER RESET application_name; ALTER ROLE "current_user" RESET application_name; @@ -377,10 +463,6 @@ SELECT * FROM chksetconfig(); ----+------+------------+----------- (0 rows) -ALTER ROLE CURRENT_ROLE SET application_name to 'BAZ'; -- error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: ALTER ROLE CURRENT_ROLE SET application_name to 'BAZ'; - ^ ALTER ROLE USER SET application_name to 'BOOM'; -- error ERROR: syntax error at or near "USER" LINE 1: ALTER ROLE USER SET application_name to 'BOOM'; @@ -395,6 +477,7 @@ SELECT * FROM chksetconfig(); ----+------+------------+----------- (0 rows) +ALTER USER CURRENT_ROLE SET application_name to 'BAZ'; ALTER USER CURRENT_USER SET application_name to 'FOO'; ALTER USER SESSION_USER SET application_name to 'BAR'; ALTER USER "current_user" SET application_name to 'FOOFOO'; @@ -406,8 +489,9 @@ SELECT * FROM chksetconfig(); ALL | Public | - | {application_name=BARBAR} ALL | current_user | - | {application_name=FOOFOO} ALL | regress_testrol1 | session_user | {application_name=BAR} + ALL | regress_testrol2 | current_role | {application_name=FOO} ALL | regress_testrol2 | current_user | {application_name=FOO} -(4 rows) +(5 rows) ALTER USER regress_testrol1 SET application_name to 'SLAM'; SELECT * FROM chksetconfig(); @@ -416,9 +500,11 @@ SELECT * FROM chksetconfig(); ALL | Public | - | {application_name=BARBAR} ALL | current_user | - | {application_name=FOOFOO} ALL | regress_testrol1 | session_user | {application_name=SLAM} + ALL | regress_testrol2 | current_role | {application_name=FOO} ALL | regress_testrol2 | current_user | {application_name=FOO} -(4 rows) +(5 rows) +ALTER USER CURRENT_ROLE RESET application_name; ALTER USER CURRENT_USER RESET application_name; ALTER USER SESSION_USER RESET application_name; ALTER USER "current_user" RESET application_name; @@ -429,10 +515,6 @@ SELECT * FROM chksetconfig(); ----+------+------------+----------- (0 rows) -ALTER USER CURRENT_ROLE SET application_name to 'BAZ'; -- error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: ALTER USER CURRENT_ROLE SET application_name to 'BAZ'; - ^ ALTER USER USER SET application_name to 'BOOM'; -- error ERROR: syntax error at or near "USER" LINE 1: ALTER USER USER SET application_name to 'BOOM'; @@ -448,26 +530,23 @@ ERROR: role "nonexistent" does not exist -- CREATE SCHEMA CREATE SCHEMA newschema1 AUTHORIZATION CURRENT_USER; CREATE SCHEMA newschema2 AUTHORIZATION "current_user"; -CREATE SCHEMA newschema3 AUTHORIZATION SESSION_USER; -CREATE SCHEMA newschema4 AUTHORIZATION regress_testrolx; -CREATE SCHEMA newschema5 AUTHORIZATION "Public"; -CREATE SCHEMA newschema6 AUTHORIZATION USER; -- error +CREATE SCHEMA newschema3 AUTHORIZATION CURRENT_ROLE; +CREATE SCHEMA newschema4 AUTHORIZATION SESSION_USER; +CREATE SCHEMA newschema5 AUTHORIZATION regress_testrolx; +CREATE SCHEMA newschema6 AUTHORIZATION "Public"; +CREATE SCHEMA newschemax AUTHORIZATION USER; -- error ERROR: syntax error at or near "USER" -LINE 1: CREATE SCHEMA newschema6 AUTHORIZATION USER; +LINE 1: CREATE SCHEMA newschemax AUTHORIZATION USER; ^ -CREATE SCHEMA newschema6 AUTHORIZATION CURRENT_ROLE; -- error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: CREATE SCHEMA newschema6 AUTHORIZATION CURRENT_ROLE; - ^ -CREATE SCHEMA newschema6 AUTHORIZATION PUBLIC; -- error +CREATE SCHEMA newschemax AUTHORIZATION PUBLIC; -- error ERROR: role "public" does not exist -CREATE SCHEMA newschema6 AUTHORIZATION "public"; -- error +CREATE SCHEMA newschemax AUTHORIZATION "public"; -- error ERROR: role "public" does not exist -CREATE SCHEMA newschema6 AUTHORIZATION NONE; -- error +CREATE SCHEMA newschemax AUTHORIZATION NONE; -- error ERROR: role name "none" is reserved -LINE 1: CREATE SCHEMA newschema6 AUTHORIZATION NONE; +LINE 1: CREATE SCHEMA newschemax AUTHORIZATION NONE; ^ -CREATE SCHEMA newschema6 AUTHORIZATION nonexistent; -- error +CREATE SCHEMA newschemax AUTHORIZATION nonexistent; -- error ERROR: role "nonexistent" does not exist SELECT n.nspname, r.rolname FROM pg_namespace n JOIN pg_roles r ON (r.oid = n.nspowner) @@ -476,38 +555,37 @@ SELECT n.nspname, r.rolname FROM pg_namespace n ------------+------------------ newschema1 | regress_testrol2 newschema2 | current_user - newschema3 | regress_testrol1 - newschema4 | regress_testrolx - newschema5 | Public -(5 rows) + newschema3 | regress_testrol2 + newschema4 | regress_testrol1 + newschema5 | regress_testrolx + newschema6 | Public +(6 rows) CREATE SCHEMA IF NOT EXISTS newschema1 AUTHORIZATION CURRENT_USER; NOTICE: schema "newschema1" already exists, skipping CREATE SCHEMA IF NOT EXISTS newschema2 AUTHORIZATION "current_user"; NOTICE: schema "newschema2" already exists, skipping -CREATE SCHEMA IF NOT EXISTS newschema3 AUTHORIZATION SESSION_USER; +CREATE SCHEMA IF NOT EXISTS newschema3 AUTHORIZATION CURRENT_ROLE; NOTICE: schema "newschema3" already exists, skipping -CREATE SCHEMA IF NOT EXISTS newschema4 AUTHORIZATION regress_testrolx; +CREATE SCHEMA IF NOT EXISTS newschema4 AUTHORIZATION SESSION_USER; NOTICE: schema "newschema4" already exists, skipping -CREATE SCHEMA IF NOT EXISTS newschema5 AUTHORIZATION "Public"; +CREATE SCHEMA IF NOT EXISTS newschema5 AUTHORIZATION regress_testrolx; NOTICE: schema "newschema5" already exists, skipping -CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION USER; -- error +CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION "Public"; +NOTICE: schema "newschema6" already exists, skipping +CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION USER; -- error ERROR: syntax error at or near "USER" -LINE 1: CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION USER; - ^ -CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION CURRENT_ROLE; -- error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: ...ATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION CURRENT_RO... +LINE 1: CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION USER; ^ -CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION PUBLIC; -- error +CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION PUBLIC; -- error ERROR: role "public" does not exist -CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION "public"; -- error +CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION "public"; -- error ERROR: role "public" does not exist -CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION NONE; -- error +CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION NONE; -- error ERROR: role name "none" is reserved -LINE 1: CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION NONE; +LINE 1: CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION NONE; ^ -CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION nonexistent; -- error +CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION nonexistent; -- error ERROR: role "nonexistent" does not exist SELECT n.nspname, r.rolname FROM pg_namespace n JOIN pg_roles r ON (r.oid = n.nspowner) @@ -516,10 +594,11 @@ SELECT n.nspname, r.rolname FROM pg_namespace n ------------+------------------ newschema1 | regress_testrol2 newschema2 | current_user - newschema3 | regress_testrol1 - newschema4 | regress_testrolx - newschema5 | Public -(5 rows) + newschema3 | regress_testrol2 + newschema4 | regress_testrol1 + newschema5 | regress_testrolx + newschema6 | Public +(6 rows) -- ALTER TABLE OWNER TO \c - @@ -530,27 +609,25 @@ CREATE TABLE testtab3 (a int); CREATE TABLE testtab4 (a int); CREATE TABLE testtab5 (a int); CREATE TABLE testtab6 (a int); +CREATE TABLE testtab7 (a int); \c - SET SESSION AUTHORIZATION regress_testrol1; SET ROLE regress_testrol2; ALTER TABLE testtab1 OWNER TO CURRENT_USER; ALTER TABLE testtab2 OWNER TO "current_user"; -ALTER TABLE testtab3 OWNER TO SESSION_USER; -ALTER TABLE testtab4 OWNER TO regress_testrolx; -ALTER TABLE testtab5 OWNER TO "Public"; -ALTER TABLE testtab6 OWNER TO CURRENT_ROLE; -- error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: ALTER TABLE testtab6 OWNER TO CURRENT_ROLE; - ^ -ALTER TABLE testtab6 OWNER TO USER; --error +ALTER TABLE testtab3 OWNER TO CURRENT_ROLE; +ALTER TABLE testtab4 OWNER TO SESSION_USER; +ALTER TABLE testtab5 OWNER TO regress_testrolx; +ALTER TABLE testtab6 OWNER TO "Public"; +ALTER TABLE testtab7 OWNER TO USER; --error ERROR: syntax error at or near "USER" -LINE 1: ALTER TABLE testtab6 OWNER TO USER; +LINE 1: ALTER TABLE testtab7 OWNER TO USER; ^ -ALTER TABLE testtab6 OWNER TO PUBLIC; -- error +ALTER TABLE testtab7 OWNER TO PUBLIC; -- error ERROR: role "public" does not exist -ALTER TABLE testtab6 OWNER TO "public"; -- error +ALTER TABLE testtab7 OWNER TO "public"; -- error ERROR: role "public" does not exist -ALTER TABLE testtab6 OWNER TO nonexistent; -- error +ALTER TABLE testtab7 OWNER TO nonexistent; -- error ERROR: role "nonexistent" does not exist SELECT c.relname, r.rolname FROM pg_class c JOIN pg_roles r ON (r.oid = c.relowner) @@ -560,11 +637,12 @@ SELECT c.relname, r.rolname ----------+------------------ testtab1 | regress_testrol2 testtab2 | current_user - testtab3 | regress_testrol1 - testtab4 | regress_testrolx - testtab5 | Public - testtab6 | regress_testrol0 -(6 rows) + testtab3 | regress_testrol2 + testtab4 | regress_testrol1 + testtab5 | regress_testrolx + testtab6 | Public + testtab7 | regress_testrol0 +(7 rows) -- ALTER TABLE, VIEW, MATERIALIZED VIEW, FOREIGN TABLE, SEQUENCE are -- changed their owner in the same way. @@ -580,27 +658,25 @@ CREATE AGGREGATE testagg6(int2) (SFUNC = int2_sum, STYPE = int8); CREATE AGGREGATE testagg7(int2) (SFUNC = int2_sum, STYPE = int8); CREATE AGGREGATE testagg8(int2) (SFUNC = int2_sum, STYPE = int8); CREATE AGGREGATE testagg9(int2) (SFUNC = int2_sum, STYPE = int8); +CREATE AGGREGATE testagga(int2) (SFUNC = int2_sum, STYPE = int8); \c - SET SESSION AUTHORIZATION regress_testrol1; SET ROLE regress_testrol2; ALTER AGGREGATE testagg1(int2) OWNER TO CURRENT_USER; ALTER AGGREGATE testagg2(int2) OWNER TO "current_user"; -ALTER AGGREGATE testagg3(int2) OWNER TO SESSION_USER; -ALTER AGGREGATE testagg4(int2) OWNER TO regress_testrolx; -ALTER AGGREGATE testagg5(int2) OWNER TO "Public"; -ALTER AGGREGATE testagg5(int2) OWNER TO CURRENT_ROLE; -- error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: ALTER AGGREGATE testagg5(int2) OWNER TO CURRENT_ROLE; - ^ -ALTER AGGREGATE testagg5(int2) OWNER TO USER; -- error +ALTER AGGREGATE testagg3(int2) OWNER TO CURRENT_ROLE; +ALTER AGGREGATE testagg4(int2) OWNER TO SESSION_USER; +ALTER AGGREGATE testagg5(int2) OWNER TO regress_testrolx; +ALTER AGGREGATE testagg6(int2) OWNER TO "Public"; +ALTER AGGREGATE testagg6(int2) OWNER TO USER; -- error ERROR: syntax error at or near "USER" -LINE 1: ALTER AGGREGATE testagg5(int2) OWNER TO USER; +LINE 1: ALTER AGGREGATE testagg6(int2) OWNER TO USER; ^ -ALTER AGGREGATE testagg5(int2) OWNER TO PUBLIC; -- error +ALTER AGGREGATE testagg6(int2) OWNER TO PUBLIC; -- error ERROR: role "public" does not exist -ALTER AGGREGATE testagg5(int2) OWNER TO "public"; -- error +ALTER AGGREGATE testagg6(int2) OWNER TO "public"; -- error ERROR: role "public" does not exist -ALTER AGGREGATE testagg5(int2) OWNER TO nonexistent; -- error +ALTER AGGREGATE testagg6(int2) OWNER TO nonexistent; -- error ERROR: role "nonexistent" does not exist SELECT p.proname, r.rolname FROM pg_proc p JOIN pg_roles r ON (r.oid = p.proowner) @@ -610,14 +686,15 @@ SELECT p.proname, r.rolname ----------+------------------ testagg1 | regress_testrol2 testagg2 | current_user - testagg3 | regress_testrol1 - testagg4 | regress_testrolx - testagg5 | Public - testagg6 | regress_testrol0 + testagg3 | regress_testrol2 + testagg4 | regress_testrol1 + testagg5 | regress_testrolx + testagg6 | Public testagg7 | regress_testrol0 testagg8 | regress_testrol0 testagg9 | regress_testrol0 -(9 rows) + testagga | regress_testrol0 +(10 rows) -- CREATE USER MAPPING CREATE FOREIGN DATA WRAPPER test_wrapper; @@ -630,58 +707,52 @@ CREATE SERVER sv6 FOREIGN DATA WRAPPER test_wrapper; CREATE SERVER sv7 FOREIGN DATA WRAPPER test_wrapper; CREATE SERVER sv8 FOREIGN DATA WRAPPER test_wrapper; CREATE SERVER sv9 FOREIGN DATA WRAPPER test_wrapper; +CREATE SERVER sv10 FOREIGN DATA WRAPPER test_wrapper; CREATE USER MAPPING FOR CURRENT_USER SERVER sv1 OPTIONS (user 'CURRENT_USER'); CREATE USER MAPPING FOR "current_user" SERVER sv2 OPTIONS (user '"current_user"'); -CREATE USER MAPPING FOR USER SERVER sv3 OPTIONS (user 'USER'); -CREATE USER MAPPING FOR "user" SERVER sv4 OPTIONS (user '"USER"'); -CREATE USER MAPPING FOR SESSION_USER SERVER sv5 OPTIONS (user 'SESSION_USER'); -CREATE USER MAPPING FOR PUBLIC SERVER sv6 OPTIONS (user 'PUBLIC'); -CREATE USER MAPPING FOR "Public" SERVER sv7 OPTIONS (user '"Public"'); -CREATE USER MAPPING FOR regress_testrolx SERVER sv8 OPTIONS (user 'regress_testrolx'); -CREATE USER MAPPING FOR CURRENT_ROLE SERVER sv9 - OPTIONS (user 'CURRENT_ROLE'); -- error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: CREATE USER MAPPING FOR CURRENT_ROLE SERVER sv9 - ^ -CREATE USER MAPPING FOR nonexistent SERVER sv9 - OPTIONS (user 'nonexistent'); -- error; +CREATE USER MAPPING FOR CURRENT_ROLE SERVER sv3 OPTIONS (user 'CURRENT_ROLE'); +CREATE USER MAPPING FOR USER SERVER sv4 OPTIONS (user 'USER'); +CREATE USER MAPPING FOR "user" SERVER sv5 OPTIONS (user '"USER"'); +CREATE USER MAPPING FOR SESSION_USER SERVER sv6 OPTIONS (user 'SESSION_USER'); +CREATE USER MAPPING FOR PUBLIC SERVER sv7 OPTIONS (user 'PUBLIC'); +CREATE USER MAPPING FOR "Public" SERVER sv8 OPTIONS (user '"Public"'); +CREATE USER MAPPING FOR regress_testrolx SERVER sv9 OPTIONS (user 'regress_testrolx'); +CREATE USER MAPPING FOR nonexistent SERVER sv10 OPTIONS (user 'nonexistent'); -- error; ERROR: role "nonexistent" does not exist SELECT * FROM chkumapping(); umname | umserver | umoptions ------------------+----------+--------------------------- regress_testrol2 | sv1 | {user=CURRENT_USER} current_user | sv2 | {"user=\"current_user\""} - regress_testrol2 | sv3 | {user=USER} - user | sv4 | {"user=\"USER\""} - regress_testrol1 | sv5 | {user=SESSION_USER} - | sv6 | {user=PUBLIC} - Public | sv7 | {"user=\"Public\""} - regress_testrolx | sv8 | {user=regress_testrolx} -(8 rows) + regress_testrol2 | sv3 | {user=CURRENT_ROLE} + regress_testrol2 | sv4 | {user=USER} + user | sv5 | {"user=\"USER\""} + regress_testrol1 | sv6 | {user=SESSION_USER} + | sv7 | {user=PUBLIC} + Public | sv8 | {"user=\"Public\""} + regress_testrolx | sv9 | {user=regress_testrolx} +(9 rows) -- ALTER USER MAPPING ALTER USER MAPPING FOR CURRENT_USER SERVER sv1 OPTIONS (SET user 'CURRENT_USER_alt'); ALTER USER MAPPING FOR "current_user" SERVER sv2 OPTIONS (SET user '"current_user"_alt'); -ALTER USER MAPPING FOR USER SERVER sv3 +ALTER USER MAPPING FOR CURRENT_ROLE SERVER sv3 + OPTIONS (SET user 'CURRENT_ROLE_alt'); +ALTER USER MAPPING FOR USER SERVER sv4 OPTIONS (SET user 'USER_alt'); -ALTER USER MAPPING FOR "user" SERVER sv4 +ALTER USER MAPPING FOR "user" SERVER sv5 OPTIONS (SET user '"user"_alt'); -ALTER USER MAPPING FOR SESSION_USER SERVER sv5 +ALTER USER MAPPING FOR SESSION_USER SERVER sv6 OPTIONS (SET user 'SESSION_USER_alt'); -ALTER USER MAPPING FOR PUBLIC SERVER sv6 +ALTER USER MAPPING FOR PUBLIC SERVER sv7 OPTIONS (SET user 'public_alt'); -ALTER USER MAPPING FOR "Public" SERVER sv7 +ALTER USER MAPPING FOR "Public" SERVER sv8 OPTIONS (SET user '"Public"_alt'); -ALTER USER MAPPING FOR regress_testrolx SERVER sv8 +ALTER USER MAPPING FOR regress_testrolx SERVER sv9 OPTIONS (SET user 'regress_testrolx_alt'); -ALTER USER MAPPING FOR CURRENT_ROLE SERVER sv9 - OPTIONS (SET user 'CURRENT_ROLE_alt'); -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: ALTER USER MAPPING FOR CURRENT_ROLE SERVER sv9 - ^ -ALTER USER MAPPING FOR nonexistent SERVER sv9 +ALTER USER MAPPING FOR nonexistent SERVER sv10 OPTIONS (SET user 'nonexistent_alt'); -- error ERROR: role "nonexistent" does not exist SELECT * FROM chkumapping(); @@ -689,28 +760,26 @@ SELECT * FROM chkumapping(); ------------------+----------+------------------------------- regress_testrol2 | sv1 | {user=CURRENT_USER_alt} current_user | sv2 | {"user=\"current_user\"_alt"} - regress_testrol2 | sv3 | {user=USER_alt} - user | sv4 | {"user=\"user\"_alt"} - regress_testrol1 | sv5 | {user=SESSION_USER_alt} - | sv6 | {user=public_alt} - Public | sv7 | {"user=\"Public\"_alt"} - regress_testrolx | sv8 | {user=regress_testrolx_alt} -(8 rows) + regress_testrol2 | sv3 | {user=CURRENT_ROLE_alt} + regress_testrol2 | sv4 | {user=USER_alt} + user | sv5 | {"user=\"user\"_alt"} + regress_testrol1 | sv6 | {user=SESSION_USER_alt} + | sv7 | {user=public_alt} + Public | sv8 | {"user=\"Public\"_alt"} + regress_testrolx | sv9 | {user=regress_testrolx_alt} +(9 rows) -- DROP USER MAPPING DROP USER MAPPING FOR CURRENT_USER SERVER sv1; DROP USER MAPPING FOR "current_user" SERVER sv2; -DROP USER MAPPING FOR USER SERVER sv3; -DROP USER MAPPING FOR "user" SERVER sv4; -DROP USER MAPPING FOR SESSION_USER SERVER sv5; -DROP USER MAPPING FOR PUBLIC SERVER sv6; -DROP USER MAPPING FOR "Public" SERVER sv7; -DROP USER MAPPING FOR regress_testrolx SERVER sv8; -DROP USER MAPPING FOR CURRENT_ROLE SERVER sv9; -- error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: DROP USER MAPPING FOR CURRENT_ROLE SERVER sv9; - ^ -DROP USER MAPPING FOR nonexistent SERVER sv; -- error +DROP USER MAPPING FOR CURRENT_ROLE SERVER sv3; +DROP USER MAPPING FOR USER SERVER sv4; +DROP USER MAPPING FOR "user" SERVER sv5; +DROP USER MAPPING FOR SESSION_USER SERVER sv6; +DROP USER MAPPING FOR PUBLIC SERVER sv7; +DROP USER MAPPING FOR "Public" SERVER sv8; +DROP USER MAPPING FOR regress_testrolx SERVER sv9; +DROP USER MAPPING FOR nonexistent SERVER sv10; -- error ERROR: role "nonexistent" does not exist SELECT * FROM chkumapping(); umname | umserver | umoptions @@ -719,24 +788,26 @@ SELECT * FROM chkumapping(); CREATE USER MAPPING FOR CURRENT_USER SERVER sv1 OPTIONS (user 'CURRENT_USER'); CREATE USER MAPPING FOR "current_user" SERVER sv2 OPTIONS (user '"current_user"'); -CREATE USER MAPPING FOR USER SERVER sv3 OPTIONS (user 'USER'); -CREATE USER MAPPING FOR "user" SERVER sv4 OPTIONS (user '"USER"'); -CREATE USER MAPPING FOR SESSION_USER SERVER sv5 OPTIONS (user 'SESSION_USER'); -CREATE USER MAPPING FOR PUBLIC SERVER sv6 OPTIONS (user 'PUBLIC'); -CREATE USER MAPPING FOR "Public" SERVER sv7 OPTIONS (user '"Public"'); -CREATE USER MAPPING FOR regress_testrolx SERVER sv8 OPTIONS (user 'regress_testrolx'); +CREATE USER MAPPING FOR CURRENT_ROLE SERVER sv3 OPTIONS (user 'CURRENT_ROLE'); +CREATE USER MAPPING FOR USER SERVER sv4 OPTIONS (user 'USER'); +CREATE USER MAPPING FOR "user" SERVER sv5 OPTIONS (user '"USER"'); +CREATE USER MAPPING FOR SESSION_USER SERVER sv6 OPTIONS (user 'SESSION_USER'); +CREATE USER MAPPING FOR PUBLIC SERVER sv7 OPTIONS (user 'PUBLIC'); +CREATE USER MAPPING FOR "Public" SERVER sv8 OPTIONS (user '"Public"'); +CREATE USER MAPPING FOR regress_testrolx SERVER sv9 OPTIONS (user 'regress_testrolx'); SELECT * FROM chkumapping(); umname | umserver | umoptions ------------------+----------+--------------------------- regress_testrol2 | sv1 | {user=CURRENT_USER} current_user | sv2 | {"user=\"current_user\""} - regress_testrol2 | sv3 | {user=USER} - user | sv4 | {"user=\"USER\""} - regress_testrol1 | sv5 | {user=SESSION_USER} - | sv6 | {user=PUBLIC} - Public | sv7 | {"user=\"Public\""} - regress_testrolx | sv8 | {user=regress_testrolx} -(8 rows) + regress_testrol2 | sv3 | {user=CURRENT_ROLE} + regress_testrol2 | sv4 | {user=USER} + user | sv5 | {"user=\"USER\""} + regress_testrol1 | sv6 | {user=SESSION_USER} + | sv7 | {user=PUBLIC} + Public | sv8 | {"user=\"Public\""} + regress_testrolx | sv9 | {user=regress_testrolx} +(9 rows) -- DROP USER MAPPING IF EXISTS DROP USER MAPPING IF EXISTS FOR CURRENT_USER SERVER sv1; @@ -744,82 +815,92 @@ SELECT * FROM chkumapping(); umname | umserver | umoptions ------------------+----------+--------------------------- current_user | sv2 | {"user=\"current_user\""} - regress_testrol2 | sv3 | {user=USER} - user | sv4 | {"user=\"USER\""} - regress_testrol1 | sv5 | {user=SESSION_USER} - | sv6 | {user=PUBLIC} - Public | sv7 | {"user=\"Public\""} - regress_testrolx | sv8 | {user=regress_testrolx} -(7 rows) + regress_testrol2 | sv3 | {user=CURRENT_ROLE} + regress_testrol2 | sv4 | {user=USER} + user | sv5 | {"user=\"USER\""} + regress_testrol1 | sv6 | {user=SESSION_USER} + | sv7 | {user=PUBLIC} + Public | sv8 | {"user=\"Public\""} + regress_testrolx | sv9 | {user=regress_testrolx} +(8 rows) DROP USER MAPPING IF EXISTS FOR "current_user" SERVER sv2; SELECT * FROM chkumapping(); umname | umserver | umoptions ------------------+----------+------------------------- - regress_testrol2 | sv3 | {user=USER} - user | sv4 | {"user=\"USER\""} - regress_testrol1 | sv5 | {user=SESSION_USER} - | sv6 | {user=PUBLIC} - Public | sv7 | {"user=\"Public\""} - regress_testrolx | sv8 | {user=regress_testrolx} + regress_testrol2 | sv3 | {user=CURRENT_ROLE} + regress_testrol2 | sv4 | {user=USER} + user | sv5 | {"user=\"USER\""} + regress_testrol1 | sv6 | {user=SESSION_USER} + | sv7 | {user=PUBLIC} + Public | sv8 | {"user=\"Public\""} + regress_testrolx | sv9 | {user=regress_testrolx} +(7 rows) + +DROP USER MAPPING IF EXISTS FOR CURRENT_USER SERVER sv3; +SELECT * FROM chkumapping(); + umname | umserver | umoptions +------------------+----------+------------------------- + regress_testrol2 | sv4 | {user=USER} + user | sv5 | {"user=\"USER\""} + regress_testrol1 | sv6 | {user=SESSION_USER} + | sv7 | {user=PUBLIC} + Public | sv8 | {"user=\"Public\""} + regress_testrolx | sv9 | {user=regress_testrolx} (6 rows) -DROP USER MAPPING IF EXISTS FOR USER SERVER sv3; +DROP USER MAPPING IF EXISTS FOR USER SERVER sv4; SELECT * FROM chkumapping(); umname | umserver | umoptions ------------------+----------+------------------------- - user | sv4 | {"user=\"USER\""} - regress_testrol1 | sv5 | {user=SESSION_USER} - | sv6 | {user=PUBLIC} - Public | sv7 | {"user=\"Public\""} - regress_testrolx | sv8 | {user=regress_testrolx} + user | sv5 | {"user=\"USER\""} + regress_testrol1 | sv6 | {user=SESSION_USER} + | sv7 | {user=PUBLIC} + Public | sv8 | {"user=\"Public\""} + regress_testrolx | sv9 | {user=regress_testrolx} (5 rows) -DROP USER MAPPING IF EXISTS FOR "user" SERVER sv4; +DROP USER MAPPING IF EXISTS FOR "user" SERVER sv5; SELECT * FROM chkumapping(); umname | umserver | umoptions ------------------+----------+------------------------- - regress_testrol1 | sv5 | {user=SESSION_USER} - | sv6 | {user=PUBLIC} - Public | sv7 | {"user=\"Public\""} - regress_testrolx | sv8 | {user=regress_testrolx} + regress_testrol1 | sv6 | {user=SESSION_USER} + | sv7 | {user=PUBLIC} + Public | sv8 | {"user=\"Public\""} + regress_testrolx | sv9 | {user=regress_testrolx} (4 rows) -DROP USER MAPPING IF EXISTS FOR SESSION_USER SERVER sv5; +DROP USER MAPPING IF EXISTS FOR SESSION_USER SERVER sv6; SELECT * FROM chkumapping(); umname | umserver | umoptions ------------------+----------+------------------------- - | sv6 | {user=PUBLIC} - Public | sv7 | {"user=\"Public\""} - regress_testrolx | sv8 | {user=regress_testrolx} + | sv7 | {user=PUBLIC} + Public | sv8 | {"user=\"Public\""} + regress_testrolx | sv9 | {user=regress_testrolx} (3 rows) -DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER sv6; +DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER sv7; SELECT * FROM chkumapping(); umname | umserver | umoptions ------------------+----------+------------------------- - Public | sv7 | {"user=\"Public\""} - regress_testrolx | sv8 | {user=regress_testrolx} + Public | sv8 | {"user=\"Public\""} + regress_testrolx | sv9 | {user=regress_testrolx} (2 rows) -DROP USER MAPPING IF EXISTS FOR "Public" SERVER sv7; +DROP USER MAPPING IF EXISTS FOR "Public" SERVER sv8; SELECT * FROM chkumapping(); umname | umserver | umoptions ------------------+----------+------------------------- - regress_testrolx | sv8 | {user=regress_testrolx} + regress_testrolx | sv9 | {user=regress_testrolx} (1 row) -DROP USER MAPPING IF EXISTS FOR regress_testrolx SERVER sv8; +DROP USER MAPPING IF EXISTS FOR regress_testrolx SERVER sv9; SELECT * FROM chkumapping(); umname | umserver | umoptions --------+----------+----------- (0 rows) -DROP USER MAPPING IF EXISTS FOR CURRENT_ROLE SERVER sv9; --error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: DROP USER MAPPING IF EXISTS FOR CURRENT_ROLE SERVER sv9; - ^ -DROP USER MAPPING IF EXISTS FOR nonexistent SERVER sv9; -- error +DROP USER MAPPING IF EXISTS FOR nonexistent SERVER sv10; -- error NOTICE: role "nonexistent" does not exist, skipping -- GRANT/REVOKE GRANT regress_testrol0 TO pg_signal_backend; -- success @@ -841,7 +922,8 @@ SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_'; testagg7 | testagg8 | testagg9 | -(9 rows) + testagga | +(10 rows) REVOKE ALL PRIVILEGES ON FUNCTION testagg1(int2) FROM PUBLIC; REVOKE ALL PRIVILEGES ON FUNCTION testagg2(int2) FROM PUBLIC; @@ -854,108 +936,106 @@ REVOKE ALL PRIVILEGES ON FUNCTION testagg8(int2) FROM PUBLIC; GRANT ALL PRIVILEGES ON FUNCTION testagg1(int2) TO PUBLIC; GRANT ALL PRIVILEGES ON FUNCTION testagg2(int2) TO CURRENT_USER; GRANT ALL PRIVILEGES ON FUNCTION testagg3(int2) TO "current_user"; -GRANT ALL PRIVILEGES ON FUNCTION testagg4(int2) TO SESSION_USER; -GRANT ALL PRIVILEGES ON FUNCTION testagg5(int2) TO "Public"; -GRANT ALL PRIVILEGES ON FUNCTION testagg6(int2) TO regress_testrolx; -GRANT ALL PRIVILEGES ON FUNCTION testagg7(int2) TO "public"; -GRANT ALL PRIVILEGES ON FUNCTION testagg8(int2) +GRANT ALL PRIVILEGES ON FUNCTION testagg4(int2) TO CURRENT_ROLE; +GRANT ALL PRIVILEGES ON FUNCTION testagg5(int2) TO SESSION_USER; +GRANT ALL PRIVILEGES ON FUNCTION testagg6(int2) TO "Public"; +GRANT ALL PRIVILEGES ON FUNCTION testagg7(int2) TO regress_testrolx; +GRANT ALL PRIVILEGES ON FUNCTION testagg8(int2) TO "public"; +GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO current_user, public, regress_testrolx; SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_'; proname | proacl ----------+----------------------------------------------------------------------------------------------------------------------------------- testagg1 | {regress_testrol2=X/regress_testrol2,=X/regress_testrol2} testagg2 | {current_user=X/current_user,regress_testrol2=X/current_user} - testagg3 | {regress_testrol1=X/regress_testrol1,current_user=X/regress_testrol1} - testagg4 | {regress_testrolx=X/regress_testrolx,regress_testrol1=X/regress_testrolx} - testagg5 | {Public=X/Public} - testagg6 | {regress_testrol0=X/regress_testrol0,regress_testrolx=X/regress_testrol0} - testagg7 | {regress_testrol0=X/regress_testrol0,=X/regress_testrol0} - testagg8 | {regress_testrol0=X/regress_testrol0,regress_testrol2=X/regress_testrol0,=X/regress_testrol0,regress_testrolx=X/regress_testrol0} - testagg9 | -(9 rows) + testagg3 | {regress_testrol2=X/regress_testrol2,current_user=X/regress_testrol2} + testagg4 | {regress_testrol1=X/regress_testrol1,regress_testrol2=X/regress_testrol1} + testagg5 | {regress_testrolx=X/regress_testrolx,regress_testrol1=X/regress_testrolx} + testagg6 | {Public=X/Public} + testagg7 | {regress_testrol0=X/regress_testrol0,regress_testrolx=X/regress_testrol0} + testagg8 | {regress_testrol0=X/regress_testrol0,=X/regress_testrol0} + testagg9 | {=X/regress_testrol0,regress_testrol0=X/regress_testrol0,regress_testrol2=X/regress_testrol0,regress_testrolx=X/regress_testrol0} + testagga | +(10 rows) -GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO CURRENT_ROLE; --error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: ...RANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO CURRENT_RO... - ^ -GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO USER; --error +GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO USER; --error ERROR: syntax error at or near "USER" -LINE 1: GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO USER; +LINE 1: GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO USER; ^ -GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO NONE; --error +GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO NONE; --error ERROR: role name "none" is reserved -LINE 1: GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO NONE; +LINE 1: GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO NONE; ^ -GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO "none"; --error +GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO "none"; --error ERROR: role name "none" is reserved -LINE 1: GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO "none"; +LINE 1: GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO "none"; ^ SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_'; proname | proacl ----------+----------------------------------------------------------------------------------------------------------------------------------- testagg1 | {regress_testrol2=X/regress_testrol2,=X/regress_testrol2} testagg2 | {current_user=X/current_user,regress_testrol2=X/current_user} - testagg3 | {regress_testrol1=X/regress_testrol1,current_user=X/regress_testrol1} - testagg4 | {regress_testrolx=X/regress_testrolx,regress_testrol1=X/regress_testrolx} - testagg5 | {Public=X/Public} - testagg6 | {regress_testrol0=X/regress_testrol0,regress_testrolx=X/regress_testrol0} - testagg7 | {regress_testrol0=X/regress_testrol0,=X/regress_testrol0} - testagg8 | {regress_testrol0=X/regress_testrol0,regress_testrol2=X/regress_testrol0,=X/regress_testrol0,regress_testrolx=X/regress_testrol0} - testagg9 | -(9 rows) + testagg3 | {regress_testrol2=X/regress_testrol2,current_user=X/regress_testrol2} + testagg4 | {regress_testrol1=X/regress_testrol1,regress_testrol2=X/regress_testrol1} + testagg5 | {regress_testrolx=X/regress_testrolx,regress_testrol1=X/regress_testrolx} + testagg6 | {Public=X/Public} + testagg7 | {regress_testrol0=X/regress_testrol0,regress_testrolx=X/regress_testrol0} + testagg8 | {regress_testrol0=X/regress_testrol0,=X/regress_testrol0} + testagg9 | {=X/regress_testrol0,regress_testrol0=X/regress_testrol0,regress_testrol2=X/regress_testrol0,regress_testrolx=X/regress_testrol0} + testagga | +(10 rows) REVOKE ALL PRIVILEGES ON FUNCTION testagg1(int2) FROM PUBLIC; REVOKE ALL PRIVILEGES ON FUNCTION testagg2(int2) FROM CURRENT_USER; REVOKE ALL PRIVILEGES ON FUNCTION testagg3(int2) FROM "current_user"; -REVOKE ALL PRIVILEGES ON FUNCTION testagg4(int2) FROM SESSION_USER; -REVOKE ALL PRIVILEGES ON FUNCTION testagg5(int2) FROM "Public"; -REVOKE ALL PRIVILEGES ON FUNCTION testagg6(int2) FROM regress_testrolx; -REVOKE ALL PRIVILEGES ON FUNCTION testagg7(int2) FROM "public"; -REVOKE ALL PRIVILEGES ON FUNCTION testagg8(int2) +REVOKE ALL PRIVILEGES ON FUNCTION testagg4(int2) FROM CURRENT_ROLE; +REVOKE ALL PRIVILEGES ON FUNCTION testagg5(int2) FROM SESSION_USER; +REVOKE ALL PRIVILEGES ON FUNCTION testagg6(int2) FROM "Public"; +REVOKE ALL PRIVILEGES ON FUNCTION testagg7(int2) FROM regress_testrolx; +REVOKE ALL PRIVILEGES ON FUNCTION testagg8(int2) FROM "public"; +REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM current_user, public, regress_testrolx; SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_'; proname | proacl ----------+--------------------------------------- testagg1 | {regress_testrol2=X/regress_testrol2} testagg2 | {current_user=X/current_user} - testagg3 | {regress_testrol1=X/regress_testrol1} - testagg4 | {regress_testrolx=X/regress_testrolx} - testagg5 | {} - testagg6 | {regress_testrol0=X/regress_testrol0} + testagg3 | {regress_testrol2=X/regress_testrol2} + testagg4 | {regress_testrol1=X/regress_testrol1} + testagg5 | {regress_testrolx=X/regress_testrolx} + testagg6 | {} testagg7 | {regress_testrol0=X/regress_testrol0} testagg8 | {regress_testrol0=X/regress_testrol0} - testagg9 | -(9 rows) + testagg9 | {regress_testrol0=X/regress_testrol0} + testagga | +(10 rows) -REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM CURRENT_ROLE; --error -ERROR: syntax error at or near "CURRENT_ROLE" -LINE 1: ...KE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM CURRENT_RO... - ^ -REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM USER; --error +REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM USER; --error ERROR: syntax error at or near "USER" -LINE 1: REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM USER; +LINE 1: REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM USER; ^ -REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM NONE; --error +REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM NONE; --error ERROR: role name "none" is reserved -LINE 1: REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM NONE; +LINE 1: REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM NONE; ^ -REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM "none"; --error +REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM "none"; --error ERROR: role name "none" is reserved -LINE 1: ...EVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM "none"; +LINE 1: ...EVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM "none"; ^ SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_'; proname | proacl ----------+--------------------------------------- testagg1 | {regress_testrol2=X/regress_testrol2} testagg2 | {current_user=X/current_user} - testagg3 | {regress_testrol1=X/regress_testrol1} - testagg4 | {regress_testrolx=X/regress_testrolx} - testagg5 | {} - testagg6 | {regress_testrol0=X/regress_testrol0} + testagg3 | {regress_testrol2=X/regress_testrol2} + testagg4 | {regress_testrol1=X/regress_testrol1} + testagg5 | {regress_testrolx=X/regress_testrolx} + testagg6 | {} testagg7 | {regress_testrol0=X/regress_testrol0} testagg8 | {regress_testrol0=X/regress_testrol0} - testagg9 | -(9 rows) + testagg9 | {regress_testrol0=X/regress_testrol0} + testagga | +(10 rows) -- DEFAULT MONITORING ROLES CREATE ROLE regress_role_haspriv; @@ -1006,7 +1086,7 @@ REVOKE pg_read_all_settings FROM regress_role_haspriv; -- clean up \c DROP SCHEMA test_roles_schema; -DROP OWNED BY regress_testrol0, "Public", "current_user", regress_testrol1, regress_testrol2, regress_testrolx CASCADE; +DROP OWNED BY regress_testrol0, "Public", "current_role", "current_user", regress_testrol1, regress_testrol2, regress_testrolx CASCADE; DROP ROLE regress_testrol0, regress_testrol1, regress_testrol2, regress_testrolx; -DROP ROLE "Public", "None", "current_user", "session_user", "user"; +DROP ROLE "Public", "None", "current_role", "current_user", "session_user", "user"; DROP ROLE regress_role_haspriv, regress_role_nopriv; diff --git a/src/test/modules/unsafe_tests/sql/rolenames.sql b/src/test/modules/unsafe_tests/sql/rolenames.sql index c3013c146498..adac36536db4 100644 --- a/src/test/modules/unsafe_tests/sql/rolenames.sql +++ b/src/test/modules/unsafe_tests/sql/rolenames.sql @@ -1,20 +1,22 @@ -CREATE OR REPLACE FUNCTION chkrolattr() +CREATE FUNCTION chkrolattr() RETURNS TABLE ("role" name, rolekeyword text, canlogin bool, replication bool) AS $$ SELECT r.rolname, v.keyword, r.rolcanlogin, r.rolreplication FROM pg_roles r - JOIN (VALUES(CURRENT_USER, 'current_user'), + JOIN (VALUES(CURRENT_ROLE, 'current_role'), + (CURRENT_USER, 'current_user'), (SESSION_USER, 'session_user'), + ('current_role', '-'), ('current_user', '-'), ('session_user', '-'), ('Public', '-'), ('None', '-')) AS v(uname, keyword) ON (r.rolname = v.uname) - ORDER BY 1; + ORDER BY 1, 2; $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION chksetconfig() +CREATE FUNCTION chksetconfig() RETURNS TABLE (db name, "role" name, rolkeyword text, setconfig text[]) AS $$ SELECT COALESCE(d.datname, 'ALL'), COALESCE(r.rolname, 'ALL'), @@ -22,22 +24,23 @@ SELECT COALESCE(d.datname, 'ALL'), COALESCE(r.rolname, 'ALL'), FROM pg_db_role_setting s LEFT JOIN pg_roles r ON (r.oid = s.setrole) LEFT JOIN pg_database d ON (d.oid = s.setdatabase) - LEFT JOIN (VALUES(CURRENT_USER, 'current_user'), - (SESSION_USER, 'session_user')) + LEFT JOIN (VALUES(CURRENT_ROLE, 'current_role'), + (CURRENT_USER, 'current_user'), + (SESSION_USER, 'session_user')) AS v(uname, keyword) ON (r.rolname = v.uname) WHERE (r.rolname) IN ('Public', 'current_user', 'regress_testrol1', 'regress_testrol2') -ORDER BY 1, 2; +ORDER BY 1, 2, 3; $$ LANGUAGE SQL; -CREATE OR REPLACE FUNCTION chkumapping() +CREATE FUNCTION chkumapping() RETURNS TABLE (umname name, umserver name, umoptions text[]) AS $$ SELECT r.rolname, s.srvname, m.umoptions FROM pg_user_mapping m LEFT JOIN pg_roles r ON (r.oid = m.umuser) JOIN pg_foreign_server s ON (s.oid = m.umserver) - ORDER BY 2; + ORDER BY 2, 1; $$ LANGUAGE SQL; -- @@ -50,6 +53,7 @@ SET client_min_messages = ERROR; CREATE ROLE "Public"; CREATE ROLE "None"; +CREATE ROLE "current_role"; CREATE ROLE "current_user"; CREATE ROLE "session_user"; CREATE ROLE "user"; @@ -84,6 +88,11 @@ SET ROLE regress_testrol2; -- ALTER ROLE BEGIN; SELECT * FROM chkrolattr(); +ALTER ROLE CURRENT_ROLE WITH REPLICATION; +SELECT * FROM chkrolattr(); +ALTER ROLE "current_role" WITH REPLICATION; +SELECT * FROM chkrolattr(); +ALTER ROLE CURRENT_ROLE WITH NOREPLICATION; ALTER ROLE CURRENT_USER WITH REPLICATION; SELECT * FROM chkrolattr(); ALTER ROLE "current_user" WITH REPLICATION; @@ -101,7 +110,6 @@ SELECT * FROM chkrolattr(); ROLLBACK; ALTER ROLE USER WITH LOGIN; -- error -ALTER ROLE CURRENT_ROLE WITH LOGIN; --error ALTER ROLE ALL WITH REPLICATION; -- error ALTER ROLE SESSION_ROLE WITH NOREPLICATION; -- error ALTER ROLE PUBLIC WITH NOREPLICATION; -- error @@ -113,6 +121,11 @@ ALTER ROLE nonexistent WITH NOREPLICATION; -- error -- ALTER USER BEGIN; SELECT * FROM chkrolattr(); +ALTER USER CURRENT_ROLE WITH REPLICATION; +SELECT * FROM chkrolattr(); +ALTER USER "current_role" WITH REPLICATION; +SELECT * FROM chkrolattr(); +ALTER USER CURRENT_ROLE WITH NOREPLICATION; ALTER USER CURRENT_USER WITH REPLICATION; SELECT * FROM chkrolattr(); ALTER USER "current_user" WITH REPLICATION; @@ -130,7 +143,6 @@ SELECT * FROM chkrolattr(); ROLLBACK; ALTER USER USER WITH LOGIN; -- error -ALTER USER CURRENT_ROLE WITH LOGIN; -- error ALTER USER ALL WITH REPLICATION; -- error ALTER USER SESSION_ROLE WITH NOREPLICATION; -- error ALTER USER PUBLIC WITH NOREPLICATION; -- error @@ -141,6 +153,7 @@ ALTER USER nonexistent WITH NOREPLICATION; -- error -- ALTER ROLE SET/RESET SELECT * FROM chksetconfig(); +ALTER ROLE CURRENT_ROLE SET application_name to 'BAZ'; ALTER ROLE CURRENT_USER SET application_name to 'FOO'; ALTER ROLE SESSION_USER SET application_name to 'BAR'; ALTER ROLE "current_user" SET application_name to 'FOOFOO'; @@ -149,6 +162,7 @@ ALTER ROLE ALL SET application_name to 'SLAP'; SELECT * FROM chksetconfig(); ALTER ROLE regress_testrol1 SET application_name to 'SLAM'; SELECT * FROM chksetconfig(); +ALTER ROLE CURRENT_ROLE RESET application_name; ALTER ROLE CURRENT_USER RESET application_name; ALTER ROLE SESSION_USER RESET application_name; ALTER ROLE "current_user" RESET application_name; @@ -157,13 +171,13 @@ ALTER ROLE ALL RESET application_name; SELECT * FROM chksetconfig(); -ALTER ROLE CURRENT_ROLE SET application_name to 'BAZ'; -- error ALTER ROLE USER SET application_name to 'BOOM'; -- error ALTER ROLE PUBLIC SET application_name to 'BOMB'; -- error ALTER ROLE nonexistent SET application_name to 'BOMB'; -- error -- ALTER USER SET/RESET SELECT * FROM chksetconfig(); +ALTER USER CURRENT_ROLE SET application_name to 'BAZ'; ALTER USER CURRENT_USER SET application_name to 'FOO'; ALTER USER SESSION_USER SET application_name to 'BAR'; ALTER USER "current_user" SET application_name to 'FOOFOO'; @@ -172,6 +186,7 @@ ALTER USER ALL SET application_name to 'SLAP'; SELECT * FROM chksetconfig(); ALTER USER regress_testrol1 SET application_name to 'SLAM'; SELECT * FROM chksetconfig(); +ALTER USER CURRENT_ROLE RESET application_name; ALTER USER CURRENT_USER RESET application_name; ALTER USER SESSION_USER RESET application_name; ALTER USER "current_user" RESET application_name; @@ -180,7 +195,6 @@ ALTER USER ALL RESET application_name; SELECT * FROM chksetconfig(); -ALTER USER CURRENT_ROLE SET application_name to 'BAZ'; -- error ALTER USER USER SET application_name to 'BOOM'; -- error ALTER USER PUBLIC SET application_name to 'BOMB'; -- error ALTER USER NONE SET application_name to 'BOMB'; -- error @@ -189,16 +203,16 @@ ALTER USER nonexistent SET application_name to 'BOMB'; -- error -- CREATE SCHEMA CREATE SCHEMA newschema1 AUTHORIZATION CURRENT_USER; CREATE SCHEMA newschema2 AUTHORIZATION "current_user"; -CREATE SCHEMA newschema3 AUTHORIZATION SESSION_USER; -CREATE SCHEMA newschema4 AUTHORIZATION regress_testrolx; -CREATE SCHEMA newschema5 AUTHORIZATION "Public"; +CREATE SCHEMA newschema3 AUTHORIZATION CURRENT_ROLE; +CREATE SCHEMA newschema4 AUTHORIZATION SESSION_USER; +CREATE SCHEMA newschema5 AUTHORIZATION regress_testrolx; +CREATE SCHEMA newschema6 AUTHORIZATION "Public"; -CREATE SCHEMA newschema6 AUTHORIZATION USER; -- error -CREATE SCHEMA newschema6 AUTHORIZATION CURRENT_ROLE; -- error -CREATE SCHEMA newschema6 AUTHORIZATION PUBLIC; -- error -CREATE SCHEMA newschema6 AUTHORIZATION "public"; -- error -CREATE SCHEMA newschema6 AUTHORIZATION NONE; -- error -CREATE SCHEMA newschema6 AUTHORIZATION nonexistent; -- error +CREATE SCHEMA newschemax AUTHORIZATION USER; -- error +CREATE SCHEMA newschemax AUTHORIZATION PUBLIC; -- error +CREATE SCHEMA newschemax AUTHORIZATION "public"; -- error +CREATE SCHEMA newschemax AUTHORIZATION NONE; -- error +CREATE SCHEMA newschemax AUTHORIZATION nonexistent; -- error SELECT n.nspname, r.rolname FROM pg_namespace n JOIN pg_roles r ON (r.oid = n.nspowner) @@ -206,16 +220,16 @@ SELECT n.nspname, r.rolname FROM pg_namespace n CREATE SCHEMA IF NOT EXISTS newschema1 AUTHORIZATION CURRENT_USER; CREATE SCHEMA IF NOT EXISTS newschema2 AUTHORIZATION "current_user"; -CREATE SCHEMA IF NOT EXISTS newschema3 AUTHORIZATION SESSION_USER; -CREATE SCHEMA IF NOT EXISTS newschema4 AUTHORIZATION regress_testrolx; -CREATE SCHEMA IF NOT EXISTS newschema5 AUTHORIZATION "Public"; +CREATE SCHEMA IF NOT EXISTS newschema3 AUTHORIZATION CURRENT_ROLE; +CREATE SCHEMA IF NOT EXISTS newschema4 AUTHORIZATION SESSION_USER; +CREATE SCHEMA IF NOT EXISTS newschema5 AUTHORIZATION regress_testrolx; +CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION "Public"; -CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION USER; -- error -CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION CURRENT_ROLE; -- error -CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION PUBLIC; -- error -CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION "public"; -- error -CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION NONE; -- error -CREATE SCHEMA IF NOT EXISTS newschema6 AUTHORIZATION nonexistent; -- error +CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION USER; -- error +CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION PUBLIC; -- error +CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION "public"; -- error +CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION NONE; -- error +CREATE SCHEMA IF NOT EXISTS newschemax AUTHORIZATION nonexistent; -- error SELECT n.nspname, r.rolname FROM pg_namespace n JOIN pg_roles r ON (r.oid = n.nspowner) @@ -230,6 +244,7 @@ CREATE TABLE testtab3 (a int); CREATE TABLE testtab4 (a int); CREATE TABLE testtab5 (a int); CREATE TABLE testtab6 (a int); +CREATE TABLE testtab7 (a int); \c - SET SESSION AUTHORIZATION regress_testrol1; @@ -237,15 +252,15 @@ SET ROLE regress_testrol2; ALTER TABLE testtab1 OWNER TO CURRENT_USER; ALTER TABLE testtab2 OWNER TO "current_user"; -ALTER TABLE testtab3 OWNER TO SESSION_USER; -ALTER TABLE testtab4 OWNER TO regress_testrolx; -ALTER TABLE testtab5 OWNER TO "Public"; +ALTER TABLE testtab3 OWNER TO CURRENT_ROLE; +ALTER TABLE testtab4 OWNER TO SESSION_USER; +ALTER TABLE testtab5 OWNER TO regress_testrolx; +ALTER TABLE testtab6 OWNER TO "Public"; -ALTER TABLE testtab6 OWNER TO CURRENT_ROLE; -- error -ALTER TABLE testtab6 OWNER TO USER; --error -ALTER TABLE testtab6 OWNER TO PUBLIC; -- error -ALTER TABLE testtab6 OWNER TO "public"; -- error -ALTER TABLE testtab6 OWNER TO nonexistent; -- error +ALTER TABLE testtab7 OWNER TO USER; --error +ALTER TABLE testtab7 OWNER TO PUBLIC; -- error +ALTER TABLE testtab7 OWNER TO "public"; -- error +ALTER TABLE testtab7 OWNER TO nonexistent; -- error SELECT c.relname, r.rolname FROM pg_class c JOIN pg_roles r ON (r.oid = c.relowner) @@ -267,6 +282,7 @@ CREATE AGGREGATE testagg6(int2) (SFUNC = int2_sum, STYPE = int8); CREATE AGGREGATE testagg7(int2) (SFUNC = int2_sum, STYPE = int8); CREATE AGGREGATE testagg8(int2) (SFUNC = int2_sum, STYPE = int8); CREATE AGGREGATE testagg9(int2) (SFUNC = int2_sum, STYPE = int8); +CREATE AGGREGATE testagga(int2) (SFUNC = int2_sum, STYPE = int8); \c - SET SESSION AUTHORIZATION regress_testrol1; @@ -274,15 +290,15 @@ SET ROLE regress_testrol2; ALTER AGGREGATE testagg1(int2) OWNER TO CURRENT_USER; ALTER AGGREGATE testagg2(int2) OWNER TO "current_user"; -ALTER AGGREGATE testagg3(int2) OWNER TO SESSION_USER; -ALTER AGGREGATE testagg4(int2) OWNER TO regress_testrolx; -ALTER AGGREGATE testagg5(int2) OWNER TO "Public"; +ALTER AGGREGATE testagg3(int2) OWNER TO CURRENT_ROLE; +ALTER AGGREGATE testagg4(int2) OWNER TO SESSION_USER; +ALTER AGGREGATE testagg5(int2) OWNER TO regress_testrolx; +ALTER AGGREGATE testagg6(int2) OWNER TO "Public"; -ALTER AGGREGATE testagg5(int2) OWNER TO CURRENT_ROLE; -- error -ALTER AGGREGATE testagg5(int2) OWNER TO USER; -- error -ALTER AGGREGATE testagg5(int2) OWNER TO PUBLIC; -- error -ALTER AGGREGATE testagg5(int2) OWNER TO "public"; -- error -ALTER AGGREGATE testagg5(int2) OWNER TO nonexistent; -- error +ALTER AGGREGATE testagg6(int2) OWNER TO USER; -- error +ALTER AGGREGATE testagg6(int2) OWNER TO PUBLIC; -- error +ALTER AGGREGATE testagg6(int2) OWNER TO "public"; -- error +ALTER AGGREGATE testagg6(int2) OWNER TO nonexistent; -- error SELECT p.proname, r.rolname FROM pg_proc p JOIN pg_roles r ON (r.oid = p.proowner) @@ -300,20 +316,19 @@ CREATE SERVER sv6 FOREIGN DATA WRAPPER test_wrapper; CREATE SERVER sv7 FOREIGN DATA WRAPPER test_wrapper; CREATE SERVER sv8 FOREIGN DATA WRAPPER test_wrapper; CREATE SERVER sv9 FOREIGN DATA WRAPPER test_wrapper; +CREATE SERVER sv10 FOREIGN DATA WRAPPER test_wrapper; CREATE USER MAPPING FOR CURRENT_USER SERVER sv1 OPTIONS (user 'CURRENT_USER'); CREATE USER MAPPING FOR "current_user" SERVER sv2 OPTIONS (user '"current_user"'); -CREATE USER MAPPING FOR USER SERVER sv3 OPTIONS (user 'USER'); -CREATE USER MAPPING FOR "user" SERVER sv4 OPTIONS (user '"USER"'); -CREATE USER MAPPING FOR SESSION_USER SERVER sv5 OPTIONS (user 'SESSION_USER'); -CREATE USER MAPPING FOR PUBLIC SERVER sv6 OPTIONS (user 'PUBLIC'); -CREATE USER MAPPING FOR "Public" SERVER sv7 OPTIONS (user '"Public"'); -CREATE USER MAPPING FOR regress_testrolx SERVER sv8 OPTIONS (user 'regress_testrolx'); - -CREATE USER MAPPING FOR CURRENT_ROLE SERVER sv9 - OPTIONS (user 'CURRENT_ROLE'); -- error -CREATE USER MAPPING FOR nonexistent SERVER sv9 - OPTIONS (user 'nonexistent'); -- error; +CREATE USER MAPPING FOR CURRENT_ROLE SERVER sv3 OPTIONS (user 'CURRENT_ROLE'); +CREATE USER MAPPING FOR USER SERVER sv4 OPTIONS (user 'USER'); +CREATE USER MAPPING FOR "user" SERVER sv5 OPTIONS (user '"USER"'); +CREATE USER MAPPING FOR SESSION_USER SERVER sv6 OPTIONS (user 'SESSION_USER'); +CREATE USER MAPPING FOR PUBLIC SERVER sv7 OPTIONS (user 'PUBLIC'); +CREATE USER MAPPING FOR "Public" SERVER sv8 OPTIONS (user '"Public"'); +CREATE USER MAPPING FOR regress_testrolx SERVER sv9 OPTIONS (user 'regress_testrolx'); + +CREATE USER MAPPING FOR nonexistent SERVER sv10 OPTIONS (user 'nonexistent'); -- error; SELECT * FROM chkumapping(); @@ -322,22 +337,22 @@ ALTER USER MAPPING FOR CURRENT_USER SERVER sv1 OPTIONS (SET user 'CURRENT_USER_alt'); ALTER USER MAPPING FOR "current_user" SERVER sv2 OPTIONS (SET user '"current_user"_alt'); -ALTER USER MAPPING FOR USER SERVER sv3 +ALTER USER MAPPING FOR CURRENT_ROLE SERVER sv3 + OPTIONS (SET user 'CURRENT_ROLE_alt'); +ALTER USER MAPPING FOR USER SERVER sv4 OPTIONS (SET user 'USER_alt'); -ALTER USER MAPPING FOR "user" SERVER sv4 +ALTER USER MAPPING FOR "user" SERVER sv5 OPTIONS (SET user '"user"_alt'); -ALTER USER MAPPING FOR SESSION_USER SERVER sv5 +ALTER USER MAPPING FOR SESSION_USER SERVER sv6 OPTIONS (SET user 'SESSION_USER_alt'); -ALTER USER MAPPING FOR PUBLIC SERVER sv6 +ALTER USER MAPPING FOR PUBLIC SERVER sv7 OPTIONS (SET user 'public_alt'); -ALTER USER MAPPING FOR "Public" SERVER sv7 +ALTER USER MAPPING FOR "Public" SERVER sv8 OPTIONS (SET user '"Public"_alt'); -ALTER USER MAPPING FOR regress_testrolx SERVER sv8 +ALTER USER MAPPING FOR regress_testrolx SERVER sv9 OPTIONS (SET user 'regress_testrolx_alt'); -ALTER USER MAPPING FOR CURRENT_ROLE SERVER sv9 - OPTIONS (SET user 'CURRENT_ROLE_alt'); -ALTER USER MAPPING FOR nonexistent SERVER sv9 +ALTER USER MAPPING FOR nonexistent SERVER sv10 OPTIONS (SET user 'nonexistent_alt'); -- error SELECT * FROM chkumapping(); @@ -345,25 +360,26 @@ SELECT * FROM chkumapping(); -- DROP USER MAPPING DROP USER MAPPING FOR CURRENT_USER SERVER sv1; DROP USER MAPPING FOR "current_user" SERVER sv2; -DROP USER MAPPING FOR USER SERVER sv3; -DROP USER MAPPING FOR "user" SERVER sv4; -DROP USER MAPPING FOR SESSION_USER SERVER sv5; -DROP USER MAPPING FOR PUBLIC SERVER sv6; -DROP USER MAPPING FOR "Public" SERVER sv7; -DROP USER MAPPING FOR regress_testrolx SERVER sv8; - -DROP USER MAPPING FOR CURRENT_ROLE SERVER sv9; -- error -DROP USER MAPPING FOR nonexistent SERVER sv; -- error +DROP USER MAPPING FOR CURRENT_ROLE SERVER sv3; +DROP USER MAPPING FOR USER SERVER sv4; +DROP USER MAPPING FOR "user" SERVER sv5; +DROP USER MAPPING FOR SESSION_USER SERVER sv6; +DROP USER MAPPING FOR PUBLIC SERVER sv7; +DROP USER MAPPING FOR "Public" SERVER sv8; +DROP USER MAPPING FOR regress_testrolx SERVER sv9; + +DROP USER MAPPING FOR nonexistent SERVER sv10; -- error SELECT * FROM chkumapping(); CREATE USER MAPPING FOR CURRENT_USER SERVER sv1 OPTIONS (user 'CURRENT_USER'); CREATE USER MAPPING FOR "current_user" SERVER sv2 OPTIONS (user '"current_user"'); -CREATE USER MAPPING FOR USER SERVER sv3 OPTIONS (user 'USER'); -CREATE USER MAPPING FOR "user" SERVER sv4 OPTIONS (user '"USER"'); -CREATE USER MAPPING FOR SESSION_USER SERVER sv5 OPTIONS (user 'SESSION_USER'); -CREATE USER MAPPING FOR PUBLIC SERVER sv6 OPTIONS (user 'PUBLIC'); -CREATE USER MAPPING FOR "Public" SERVER sv7 OPTIONS (user '"Public"'); -CREATE USER MAPPING FOR regress_testrolx SERVER sv8 OPTIONS (user 'regress_testrolx'); +CREATE USER MAPPING FOR CURRENT_ROLE SERVER sv3 OPTIONS (user 'CURRENT_ROLE'); +CREATE USER MAPPING FOR USER SERVER sv4 OPTIONS (user 'USER'); +CREATE USER MAPPING FOR "user" SERVER sv5 OPTIONS (user '"USER"'); +CREATE USER MAPPING FOR SESSION_USER SERVER sv6 OPTIONS (user 'SESSION_USER'); +CREATE USER MAPPING FOR PUBLIC SERVER sv7 OPTIONS (user 'PUBLIC'); +CREATE USER MAPPING FOR "Public" SERVER sv8 OPTIONS (user '"Public"'); +CREATE USER MAPPING FOR regress_testrolx SERVER sv9 OPTIONS (user 'regress_testrolx'); SELECT * FROM chkumapping(); -- DROP USER MAPPING IF EXISTS @@ -371,21 +387,22 @@ DROP USER MAPPING IF EXISTS FOR CURRENT_USER SERVER sv1; SELECT * FROM chkumapping(); DROP USER MAPPING IF EXISTS FOR "current_user" SERVER sv2; SELECT * FROM chkumapping(); -DROP USER MAPPING IF EXISTS FOR USER SERVER sv3; +DROP USER MAPPING IF EXISTS FOR CURRENT_USER SERVER sv3; +SELECT * FROM chkumapping(); +DROP USER MAPPING IF EXISTS FOR USER SERVER sv4; SELECT * FROM chkumapping(); -DROP USER MAPPING IF EXISTS FOR "user" SERVER sv4; +DROP USER MAPPING IF EXISTS FOR "user" SERVER sv5; SELECT * FROM chkumapping(); -DROP USER MAPPING IF EXISTS FOR SESSION_USER SERVER sv5; +DROP USER MAPPING IF EXISTS FOR SESSION_USER SERVER sv6; SELECT * FROM chkumapping(); -DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER sv6; +DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER sv7; SELECT * FROM chkumapping(); -DROP USER MAPPING IF EXISTS FOR "Public" SERVER sv7; +DROP USER MAPPING IF EXISTS FOR "Public" SERVER sv8; SELECT * FROM chkumapping(); -DROP USER MAPPING IF EXISTS FOR regress_testrolx SERVER sv8; +DROP USER MAPPING IF EXISTS FOR regress_testrolx SERVER sv9; SELECT * FROM chkumapping(); -DROP USER MAPPING IF EXISTS FOR CURRENT_ROLE SERVER sv9; --error -DROP USER MAPPING IF EXISTS FOR nonexistent SERVER sv9; -- error +DROP USER MAPPING IF EXISTS FOR nonexistent SERVER sv10; -- error -- GRANT/REVOKE GRANT regress_testrol0 TO pg_signal_backend; -- success @@ -410,38 +427,38 @@ REVOKE ALL PRIVILEGES ON FUNCTION testagg8(int2) FROM PUBLIC; GRANT ALL PRIVILEGES ON FUNCTION testagg1(int2) TO PUBLIC; GRANT ALL PRIVILEGES ON FUNCTION testagg2(int2) TO CURRENT_USER; GRANT ALL PRIVILEGES ON FUNCTION testagg3(int2) TO "current_user"; -GRANT ALL PRIVILEGES ON FUNCTION testagg4(int2) TO SESSION_USER; -GRANT ALL PRIVILEGES ON FUNCTION testagg5(int2) TO "Public"; -GRANT ALL PRIVILEGES ON FUNCTION testagg6(int2) TO regress_testrolx; -GRANT ALL PRIVILEGES ON FUNCTION testagg7(int2) TO "public"; -GRANT ALL PRIVILEGES ON FUNCTION testagg8(int2) +GRANT ALL PRIVILEGES ON FUNCTION testagg4(int2) TO CURRENT_ROLE; +GRANT ALL PRIVILEGES ON FUNCTION testagg5(int2) TO SESSION_USER; +GRANT ALL PRIVILEGES ON FUNCTION testagg6(int2) TO "Public"; +GRANT ALL PRIVILEGES ON FUNCTION testagg7(int2) TO regress_testrolx; +GRANT ALL PRIVILEGES ON FUNCTION testagg8(int2) TO "public"; +GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO current_user, public, regress_testrolx; SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_'; -GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO CURRENT_ROLE; --error -GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO USER; --error -GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO NONE; --error -GRANT ALL PRIVILEGES ON FUNCTION testagg9(int2) TO "none"; --error +GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO USER; --error +GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO NONE; --error +GRANT ALL PRIVILEGES ON FUNCTION testagga(int2) TO "none"; --error SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_'; REVOKE ALL PRIVILEGES ON FUNCTION testagg1(int2) FROM PUBLIC; REVOKE ALL PRIVILEGES ON FUNCTION testagg2(int2) FROM CURRENT_USER; REVOKE ALL PRIVILEGES ON FUNCTION testagg3(int2) FROM "current_user"; -REVOKE ALL PRIVILEGES ON FUNCTION testagg4(int2) FROM SESSION_USER; -REVOKE ALL PRIVILEGES ON FUNCTION testagg5(int2) FROM "Public"; -REVOKE ALL PRIVILEGES ON FUNCTION testagg6(int2) FROM regress_testrolx; -REVOKE ALL PRIVILEGES ON FUNCTION testagg7(int2) FROM "public"; -REVOKE ALL PRIVILEGES ON FUNCTION testagg8(int2) +REVOKE ALL PRIVILEGES ON FUNCTION testagg4(int2) FROM CURRENT_ROLE; +REVOKE ALL PRIVILEGES ON FUNCTION testagg5(int2) FROM SESSION_USER; +REVOKE ALL PRIVILEGES ON FUNCTION testagg6(int2) FROM "Public"; +REVOKE ALL PRIVILEGES ON FUNCTION testagg7(int2) FROM regress_testrolx; +REVOKE ALL PRIVILEGES ON FUNCTION testagg8(int2) FROM "public"; +REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM current_user, public, regress_testrolx; SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_'; -REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM CURRENT_ROLE; --error -REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM USER; --error -REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM NONE; --error -REVOKE ALL PRIVILEGES ON FUNCTION testagg9(int2) FROM "none"; --error +REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM USER; --error +REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM NONE; --error +REVOKE ALL PRIVILEGES ON FUNCTION testagga(int2) FROM "none"; --error SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_'; @@ -481,7 +498,7 @@ REVOKE pg_read_all_settings FROM regress_role_haspriv; \c DROP SCHEMA test_roles_schema; -DROP OWNED BY regress_testrol0, "Public", "current_user", regress_testrol1, regress_testrol2, regress_testrolx CASCADE; +DROP OWNED BY regress_testrol0, "Public", "current_role", "current_user", regress_testrol1, regress_testrol2, regress_testrolx CASCADE; DROP ROLE regress_testrol0, regress_testrol1, regress_testrol2, regress_testrolx; -DROP ROLE "Public", "None", "current_user", "session_user", "user"; +DROP ROLE "Public", "None", "current_role", "current_user", "session_user", "user"; DROP ROLE regress_role_haspriv, regress_role_nopriv; diff --git a/src/test/modules/worker_spi/worker_spi.c b/src/test/modules/worker_spi/worker_spi.c index 68b97977863e..a8270956a4b9 100644 --- a/src/test/modules/worker_spi/worker_spi.c +++ b/src/test/modules/worker_spi/worker_spi.c @@ -119,6 +119,7 @@ initialize_worker_spi(worktable *table) appendStringInfo(&buf, "select count(*) from pg_namespace where nspname = '%s'", table->schema); + debug_query_string = buf.data; ret = SPI_execute(buf.data, true, 0); if (ret != SPI_OK_SELECT) elog(FATAL, "SPI_execute failed: error code %d", ret); @@ -134,6 +135,7 @@ initialize_worker_spi(worktable *table) if (ntup == 0) { + debug_query_string = NULL; resetStringInfo(&buf); appendStringInfo(&buf, "CREATE SCHEMA \"%s\" " @@ -147,15 +149,19 @@ initialize_worker_spi(worktable *table) /* set statement start time */ SetCurrentStatementStartTimestamp(); + debug_query_string = buf.data; ret = SPI_execute(buf.data, false, 0); if (ret != SPI_OK_UTILITY) elog(FATAL, "failed to create my schema"); + + debug_query_string = NULL; /* rest is not statement-specific */ } SPI_finish(); PopActiveSnapshot(); CommitTransactionCommand(); + debug_query_string = NULL; pgstat_report_activity(STATE_IDLE, NULL); } @@ -262,6 +268,7 @@ worker_spi_main(Datum main_arg) StartTransactionCommand(); SPI_connect(); PushActiveSnapshot(GetTransactionSnapshot()); + debug_query_string = buf.data; pgstat_report_activity(STATE_RUNNING, buf.data); /* We can now execute queries via SPI */ @@ -291,6 +298,7 @@ worker_spi_main(Datum main_arg) SPI_finish(); PopActiveSnapshot(); CommitTransactionCommand(); + debug_query_string = NULL; pgstat_report_stat(false); pgstat_report_activity(STATE_IDLE, NULL); } diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm index 37d99c116070..7caf59b5ac17 100644 --- a/src/test/perl/PostgresNode.pm +++ b/src/test/perl/PostgresNode.pm @@ -510,15 +510,17 @@ sub init { print $conf "wal_level = replica\n"; } - print $conf "max_wal_senders = 5\n"; - print $conf "max_replication_slots = 5\n"; - # PG sets this to 128MB but that makes checkpoint too frequent for GPDB. - # 512MB corresponds to the ratio of GPDB seg size (64) over PG seg size (16). - print $conf "max_wal_size = 512MB\n"; - print $conf "shared_buffers = 1MB\n"; + print $conf "max_wal_senders = 10\n"; + print $conf "max_replication_slots = 10\n"; print $conf "wal_log_hints = on\n"; print $conf "hot_standby = on\n"; + # conservative settings to ensure we can run multiple postmasters: + print $conf "shared_buffers = 1MB\n"; print $conf "max_connections = 20\n"; + # limit disk space consumption, too: + # PG sets this to 128MB but that makes checkpoint too frequent for GPDB. + # 512MB corresponds to the ratio of GPDB seg size (64) over PG seg size (16). + print $conf "max_wal_size = 512MB\n"; } else { @@ -609,8 +611,10 @@ sub backup my $name = $self->name; print "# Taking pg_basebackup $backup_name from node \"$name\"\n"; - TestLib::system_or_bail('pg_basebackup', '-D', $backup_path, '-h', - $self->host, '-p', $self->port, '--no-sync', '--target-gp-dbid', 99); + TestLib::system_or_bail( + 'pg_basebackup', '-D', $backup_path, '-h', + $self->host, '-p', $self->port, '--checkpoint', + 'fast', '--no-sync', '--target-gp-dbid', 99); print "# Backup finished\n"; return; } diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm index cbe87f868431..1baf6bd00173 100644 --- a/src/test/perl/TestLib.pm +++ b/src/test/perl/TestLib.pm @@ -43,6 +43,7 @@ package TestLib; use strict; use warnings; +use Carp; use Config; use Cwd; use Exporter 'import'; @@ -421,7 +422,7 @@ sub slurp_dir { my ($dir) = @_; opendir(my $dh, $dir) - or die "could not opendir \"$dir\": $!"; + or croak "could not opendir \"$dir\": $!"; my @direntries = readdir $dh; closedir $dh; return @direntries; @@ -443,19 +444,19 @@ sub slurp_file if ($Config{osname} ne 'MSWin32') { open(my $in, '<', $filename) - or die "could not read \"$filename\": $!"; + or croak "could not read \"$filename\": $!"; $contents = <$in>; close $in; } else { my $fHandle = createFile($filename, "r", "rwd") - or die "could not open \"$filename\": $^E"; + or croak "could not open \"$filename\": $^E"; OsFHandleOpen(my $fh = IO::Handle->new(), $fHandle, 'r') - or die "could not read \"$filename\": $^E\n"; + or croak "could not read \"$filename\": $^E\n"; $contents = <$fh>; CloseHandle($fHandle) - or die "could not close \"$filename\": $^E\n"; + or croak "could not close \"$filename\": $^E\n"; } $contents =~ s/\r\n/\n/g if $Config{osname} eq 'msys'; return $contents; @@ -474,7 +475,7 @@ sub append_to_file { my ($filename, $str) = @_; open my $fh, ">>", $filename - or die "could not write \"$filename\": $!"; + or croak "could not write \"$filename\": $!"; print $fh $str; close $fh; return; diff --git a/src/test/recovery/t/020_archive_status.pl b/src/test/recovery/t/020_archive_status.pl index 2760160c5da1..4ba49e8d453f 100644 --- a/src/test/recovery/t/020_archive_status.pl +++ b/src/test/recovery/t/020_archive_status.pl @@ -65,7 +65,7 @@ FROM pg_stat_archiver }), "0|$segment_name_1", - 'pg_stat_archiver failed to archive $segment_name_1'); + "pg_stat_archiver failed to archive $segment_name_1"); # Crash the cluster for the next test in charge of checking that non-archived # WAL segments are not removed. diff --git a/src/test/recovery/t/021_row_visibility.pl b/src/test/recovery/t/021_row_visibility.pl new file mode 100644 index 000000000000..6d5faa059cf2 --- /dev/null +++ b/src/test/recovery/t/021_row_visibility.pl @@ -0,0 +1,201 @@ +# Checks that snapshots on standbys behave in a minimally reasonable +# way. +use strict; +use warnings; + +use PostgresNode; +use TestLib; +use Test::More tests => 10; +use Config; + +# Initialize primary node +my $node_primary = get_new_node('primary'); +$node_primary->init(allows_streaming => 1); +$node_primary->append_conf('postgresql.conf', 'max_prepared_transactions=10'); +$node_primary->start; + +# Initialize with empty test table +$node_primary->safe_psql('postgres', + 'CREATE TABLE public.test_visibility (data text not null)'); + +# Take backup +my $backup_name = 'my_backup'; +$node_primary->backup($backup_name); + +# Create streaming standby from backup +my $node_standby = get_new_node('standby'); +$node_standby->init_from_backup($node_primary, $backup_name, + has_streaming => 1); +$node_standby->append_conf('postgresql.conf', 'max_prepared_transactions=10'); +$node_standby->start; + +# To avoid hanging while expecting some specific input from a psql +# instance being driven by us, add a timeout high enough that it +# should never trigger even on very slow machines, unless something +# is really wrong. +my $psql_timeout = IPC::Run::timer(30); + +# One psql to primary and standby each, for all queries. That allows +# to check uncommitted changes being replicated and such. +my %psql_primary = (stdin => '', stdout => '', stderr => ''); +$psql_primary{run} = + IPC::Run::start( + ['psql', '-XA', '-f', '-', '-d', $node_primary->connstr('postgres')], + '<', \$psql_primary{stdin}, + '>', \$psql_primary{stdout}, + '2>', \$psql_primary{stderr}, + $psql_timeout); + +my %psql_standby = ('stdin' => '', 'stdout' => '', 'stderr' => ''); +$psql_standby{run} = + IPC::Run::start( + ['psql', '-XA', '-f', '-', '-d', $node_standby->connstr('postgres')], + '<', \$psql_standby{stdin}, + '>', \$psql_standby{stdout}, + '2>', \$psql_standby{stderr}, + $psql_timeout); + +# +# 1. Check initial data is the same +# +ok(send_query_and_wait(\%psql_standby, + q/SELECT * FROM test_visibility ORDER BY data;/, + qr/^\(0 rows\)$/m), + 'data not visible'); + +# +# 2. Check if an INSERT is replayed and visible +# +$node_primary->psql('postgres', "INSERT INTO test_visibility VALUES ('first insert')"); +$node_primary->wait_for_catchup($node_standby, 'replay', + $node_primary->lsn('insert')); + +ok(send_query_and_wait(\%psql_standby, + q[SELECT * FROM test_visibility ORDER BY data;], + qr/first insert.*\n\(1 row\)/m), + 'insert visible'); + +# +# 3. Verify that uncommitted changes aren't visible. +# +ok(send_query_and_wait(\%psql_primary, + q[ +BEGIN; +UPDATE test_visibility SET data = 'first update' RETURNING data; + ], + qr/^UPDATE 1$/m), + 'UPDATE'); + +$node_primary->psql('postgres', "SELECT txid_current();"); # ensure WAL flush +$node_primary->wait_for_catchup($node_standby, 'replay', + $node_primary->lsn('insert')); + +ok(send_query_and_wait(\%psql_standby, + q[SELECT * FROM test_visibility ORDER BY data;], + qr/first insert.*\n\(1 row\)/m), + 'uncommitted update invisible'); + +# +# 4. That a commit turns 3. visible +# +ok(send_query_and_wait(\%psql_primary, + q[COMMIT;], + qr/^COMMIT$/m), + 'COMMIT'); + +$node_primary->wait_for_catchup($node_standby, 'replay', + $node_primary->lsn('insert')); + +ok(send_query_and_wait(\%psql_standby, + q[SELECT * FROM test_visibility ORDER BY data;], + qr/first update\n\(1 row\)$/m), + 'committed update visible'); + +# +# 5. Check that changes in prepared xacts is invisible +# + +# GPDB: PREPARE TRANSACTION is not supported on GPDB, skip +SKIP: { + skip "PREPARE TRANSACTION not implemented on GPDB", 4; +ok(send_query_and_wait(\%psql_primary, q[ +DELETE from test_visibility; -- delete old data, so we start with clean slate +BEGIN; +INSERT INTO test_visibility VALUES('inserted in prepared will_commit'); +PREPARE TRANSACTION 'will_commit';], + qr/^PREPARE TRANSACTION$/m), + 'prepared will_commit'); + +ok(send_query_and_wait(\%psql_primary, q[ +BEGIN; +INSERT INTO test_visibility VALUES('inserted in prepared will_abort'); +PREPARE TRANSACTION 'will_abort'; + ], + qr/^PREPARE TRANSACTION$/m), + 'prepared will_abort'); + +$node_primary->wait_for_catchup($node_standby, 'replay', + $node_primary->lsn('insert')); + +ok(send_query_and_wait(\%psql_standby, + q[SELECT * FROM test_visibility ORDER BY data;], + qr/^\(0 rows\)$/m), + 'uncommitted prepared invisible'); + +# For some variation, finish prepared xacts via separate connections +$node_primary->safe_psql('postgres', + "COMMIT PREPARED 'will_commit';"); +$node_primary->safe_psql('postgres', + "ROLLBACK PREPARED 'will_abort';"); +$node_primary->wait_for_catchup($node_standby, 'replay', + $node_primary->lsn('insert')); + +ok(send_query_and_wait(\%psql_standby, + q[SELECT * FROM test_visibility ORDER BY data;], + qr/will_commit.*\n\(1 row\)$/m), + 'finished prepared visible'); +} # end SKIP + +$node_primary->stop; +$node_standby->stop; + +# Send query, wait until string matches +sub send_query_and_wait +{ + my ($psql, $query, $untl) = @_; + my $ret; + + # send query + $$psql{stdin} .= $query; + $$psql{stdin} .= "\n"; + + # wait for query results + $$psql{run}->pump_nb(); + while (1) + { + # See PostgresNode.pm's psql() + $$psql{stdout} =~ s/\r\n/\n/g if $Config{osname} eq 'msys'; + + last if $$psql{stdout} =~ /$untl/; + + if ($psql_timeout->is_expired) + { + BAIL_OUT("aborting wait: program timed out\n". + "stream contents: >>$$psql{stdout}<<\n". + "pattern searched for: $untl\n"); + return 0; + } + if (not $$psql{run}->pumpable()) + { + BAIL_OUT("aborting wait: program died\n". + "stream contents: >>$$psql{stdout}<<\n". + "pattern searched for: $untl\n"); + return 0; + } + $$psql{run}->pump(); + } + + $$psql{stdout} = ''; + + return 1; +} diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out index 244407b5e5e3..8953c5c7d714 100644 --- a/src/test/regress/expected/alter_generic.out +++ b/src/test/regress/expected/alter_generic.out @@ -506,10 +506,10 @@ CREATE OPERATOR FAMILY alt_opf19 USING btree; ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 test_opclass_options_func(internal, text[], bool); ERROR: function test_opclass_options_func(internal, text[], boolean) does not exist ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 (int4) btint42cmp(int4, int2); -ERROR: invalid opclass options parsing function -HINT: Valid signature of opclass options parsing function is '(internal) RETURNS void'. +ERROR: invalid operator class options parsing function +HINT: Valid signature of operator class options parsing function is (internal) RETURNS void. ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 (int4, int2) btint42cmp(int4, int2); -ERROR: left and right associated data types for opclass options parsing functions must match +ERROR: left and right associated data types for operator class options parsing functions must match ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 (int4) test_opclass_options_func(internal); -- Ok ALTER OPERATOR FAMILY alt_opf19 USING btree DROP FUNCTION 5 (int4, int4); DROP OPERATOR FAMILY alt_opf19 USING btree; diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index d522e5a0da6f..e3bae95d1252 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -3810,6 +3810,42 @@ Indexes: "ataddindex_expr_excl" EXCLUDE USING btree ((f1 ~~ 'a'::text) WITH =) Distributed Replicated +DROP TABLE ataddindex; +CREATE TABLE ataddindex(id int, ref_id int); +ALTER TABLE ataddindex + ADD PRIMARY KEY (id), + ADD FOREIGN KEY (ref_id) REFERENCES ataddindex; +\d ataddindex + Table "public.ataddindex" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + id | integer | | not null | + ref_id | integer | | | +Indexes: + "ataddindex_pkey" PRIMARY KEY, btree (id) +Foreign-key constraints: + "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id) +Referenced by: + TABLE "ataddindex" CONSTRAINT "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id) + +DROP TABLE ataddindex; +CREATE TABLE ataddindex(id int, ref_id int); +ALTER TABLE ataddindex + ADD UNIQUE (id), + ADD FOREIGN KEY (ref_id) REFERENCES ataddindex (id); +\d ataddindex + Table "public.ataddindex" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + id | integer | | | + ref_id | integer | | | +Indexes: + "ataddindex_id_key" UNIQUE CONSTRAINT, btree (id) +Foreign-key constraints: + "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id) +Referenced by: + TABLE "ataddindex" CONSTRAINT "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id) + DROP TABLE ataddindex; -- unsupported constraint types for partitioned tables CREATE TABLE partitioned ( @@ -3966,6 +4002,8 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS); ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); ERROR: partition "fail_part" would overlap partition "part_1" +LINE 1: ...LE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); + ^ DROP TABLE fail_part; -- check that an existing table can be attached as a default partition CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS); @@ -3975,6 +4013,8 @@ ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT; CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS); ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT; ERROR: partition "fail_def_part" conflicts with existing default partition "def_part" +LINE 1: ...ER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT; + ^ -- check validation when attaching list partitions CREATE TABLE list_parted2 ( a int, @@ -4044,6 +4084,8 @@ CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT; CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS); ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT; ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1" +LINE 1: ...LTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT; + ^ -- Overlapping partitions cannot be attached, hence, following should give error INSERT INTO partr_def1 VALUES (2, 10); CREATE TABLE part3 (LIKE range_parted); @@ -4166,8 +4208,12 @@ CREATE TABLE hpart_1 PARTITION OF hash_parted FOR VALUES WITH (MODULUS 4, REMAIN CREATE TABLE fail_part (LIKE hpart_1); ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 8, REMAINDER 4); ERROR: partition "fail_part" would overlap partition "hpart_1" +LINE 1: ...hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODU... + ^ ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODULUS 8, REMAINDER 0); ERROR: partition "fail_part" would overlap partition "hpart_1" +LINE 1: ...hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (MODU... + ^ DROP TABLE fail_part; -- check validation when attaching hash partitions -- check that violating rows are correctly reported diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out index 3fa14b96f63a..4ccea49ce576 100644 --- a/src/test/regress/expected/analyze.out +++ b/src/test/regress/expected/analyze.out @@ -30,20 +30,20 @@ analyze p3_sales; select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 1 p3_sales_1_prt_2_2_prt_2_3_prt_usa | 2 | 1 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 1 p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 1 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 1 p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 1 p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 1 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 1 (15 rows) @@ -82,21 +82,21 @@ WARNING: skipping "p3_sales_1_prt_2" --- cannot analyze a mid-level partition. select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | -1 | 0 (15 rows) select * from pg_stats where tablename like 'p3_sales%' order by tablename, attname; @@ -128,21 +128,21 @@ analyze p3_sales_1_prt_2_2_prt_2_3_prt_usa; select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | -1 | 0 p3_sales_1_prt_2_2_prt_2_3_prt_usa | 2 | 1 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | -1 | 0 (15 rows) select * from pg_stats where tablename like 'p3_sales%' order by tablename, attname; @@ -179,20 +179,20 @@ analyze; select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 1 p3_sales_1_prt_2_2_prt_2_3_prt_usa | 2 | 1 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 1 p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 1 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 1 p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 1 p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 1 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 1 (15 rows) @@ -230,20 +230,20 @@ vacuum analyze; select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 1 p3_sales_1_prt_2_2_prt_2_3_prt_usa | 2 | 1 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 1 p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 1 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 1 p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 1 p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 1 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 1 (15 rows) @@ -287,21 +287,21 @@ analyze rootpartition p3_sales; select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | -1 | 0 (15 rows) select * from pg_stats where tablename like 'p3_sales%' order by tablename, attname; @@ -339,21 +339,21 @@ WARNING: skipping "p3_sales_1_prt_2" --- cannot analyze a non-root partition us select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | -1 | 0 (15 rows) select * from pg_stats where tablename like 'p3_sales%' order by tablename, attname; @@ -386,21 +386,21 @@ WARNING: skipping "p3_sales_1_prt_2_2_prt_2_3_prt_usa" --- cannot analyze a non select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | -1 | 0 (15 rows) select * from pg_stats where tablename like 'p3_sales%' order by tablename, attname; @@ -432,20 +432,20 @@ analyze p3_sales; select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 1 p3_sales_1_prt_2_2_prt_2_3_prt_usa | 2 | 1 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 1 p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 1 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 1 p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 1 p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 1 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 1 (15 rows) @@ -488,21 +488,21 @@ analyze rootpartition p3_sales; select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | -1 | 0 (15 rows) select * from pg_stats where tablename like 'p3_sales%' order by tablename, attname; @@ -539,20 +539,20 @@ analyze p3_sales; select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 1 p3_sales_1_prt_2_2_prt_2_3_prt_usa | 2 | 1 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 1 p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 1 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 1 p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 1 p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 1 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 1 (15 rows) @@ -605,21 +605,21 @@ analyze rootpartition p3_sales; select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | -1 | 0 (15 rows) select * from pg_stats where tablename like 'p3_sales%' order by tablename, attname; @@ -656,21 +656,21 @@ analyze rootpartition p3_sales; select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | -1 | 0 (15 rows) select * from pg_stats where tablename like 'p3_sales%' order by tablename, attname; @@ -707,20 +707,20 @@ analyze p3_sales; select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 1 p3_sales_1_prt_2_2_prt_2_3_prt_usa | 2 | 1 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 1 p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 1 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 1 p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 1 p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 1 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 1 (15 rows) @@ -768,21 +768,21 @@ analyze rootpartition p3_sales; select relname, reltuples, relpages from pg_class where relname like 'p3_sales%' order by relname; relname | reltuples | relpages -----------------------------------------------------------------+-----------+---------- - p3_sales | 0 | 0 - p3_sales_1_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2 | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2 | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months | 0 | 0 - p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | 0 | 0 + p3_sales | -1 | 0 + p3_sales_1_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2 | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_2_2_prt_other_months_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2 | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_2_3_prt_usa | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_m_3_prt_other_regions | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months | -1 | 0 + p3_sales_1_prt_outlying_years_2_prt_other_months_3_prt_usa | -1 | 0 (15 rows) select * from pg_stats where tablename like 'p3_sales%' order by tablename, attname; diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 0e660899a9d8..3977c8e89e19 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -1775,6 +1775,114 @@ select string_to_array('1,2,3,4,*,6', ',', '*'); {1,2,3,4,NULL,6} (1 row) +select v, v is null as "is null" from string_to_table('1|2|3', '|') g(v); + v | is null +---+--------- + 1 | f + 2 | f + 3 | f +(3 rows) + +select v, v is null as "is null" from string_to_table('1|2|3|', '|') g(v); + v | is null +---+--------- + 1 | f + 2 | f + 3 | f + | f +(4 rows) + +select v, v is null as "is null" from string_to_table('1||2|3||', '||') g(v); + v | is null +-----+--------- + 1 | f + 2|3 | f + | f +(3 rows) + +select v, v is null as "is null" from string_to_table('1|2|3', '') g(v); + v | is null +-------+--------- + 1|2|3 | f +(1 row) + +select v, v is null as "is null" from string_to_table('', '|') g(v); + v | is null +---+--------- +(0 rows) + +select v, v is null as "is null" from string_to_table('1|2|3', NULL) g(v); + v | is null +---+--------- + 1 | f + | | f + 2 | f + | | f + 3 | f +(5 rows) + +select v, v is null as "is null" from string_to_table(NULL, '|') g(v); + v | is null +---+--------- +(0 rows) + +select v, v is null as "is null" from string_to_table('abc', '') g(v); + v | is null +-----+--------- + abc | f +(1 row) + +select v, v is null as "is null" from string_to_table('abc', '', 'abc') g(v); + v | is null +---+--------- + | t +(1 row) + +select v, v is null as "is null" from string_to_table('abc', ',') g(v); + v | is null +-----+--------- + abc | f +(1 row) + +select v, v is null as "is null" from string_to_table('abc', ',', 'abc') g(v); + v | is null +---+--------- + | t +(1 row) + +select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',') g(v); + v | is null +---+--------- + 1 | f + 2 | f + 3 | f + 4 | f + | f + 6 | f +(6 rows) + +select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',', '') g(v); + v | is null +---+--------- + 1 | f + 2 | f + 3 | f + 4 | f + | t + 6 | f +(6 rows) + +select v, v is null as "is null" from string_to_table('1,2,3,4,*,6', ',', '*') g(v); + v | is null +---+--------- + 1 | f + 2 | f + 3 | f + 4 | f + | t + 6 | f +(6 rows) + select array_to_string(NULL::int4[], ',') IS NULL; ?column? ---------- diff --git a/src/test/regress/expected/autostats.out b/src/test/regress/expected/autostats.out index c3e997c12096..ada8f96aa4a3 100644 --- a/src/test/regress/expected/autostats.out +++ b/src/test/regress/expected/autostats.out @@ -30,7 +30,7 @@ select relname, reltuples from pg_class where relname='autostats_test'; LOG: statement: select relname, reltuples from pg_class where relname='autostats_test'; relname | reltuples ----------------+----------- - autostats_test | 0 + autostats_test | -1 (1 row) -- Try it with gp_autostats_allow_nonowner GUC enabled, but as a non-owner @@ -48,7 +48,7 @@ select relname, reltuples from pg_class where relname='autostats_test'; LOG: statement: select relname, reltuples from pg_class where relname='autostats_test'; relname | reltuples ----------------+----------- - autostats_test | 0 + autostats_test | -1 (1 row) reset role; @@ -60,7 +60,7 @@ set role=autostats_nonowner; LOG: statement: set role=autostats_nonowner; insert into autostats_test select generate_series(11, 20); LOG: statement: insert into autostats_test select generate_series(11, 20); -LOG: In mode on_change, command INSERT on (dboid,tableoid)=(XXXXX,XXXXX) modifying 10 tuples caused Auto-ANALYZE. +LOG: In mode on_change, command INSERT on (dboid,tableoid)=(72126,72259) modifying 10 tuples caused Auto-ANALYZE. select relname, reltuples from pg_class where relname='autostats_test'; LOG: statement: select relname, reltuples from pg_class where relname='autostats_test'; relname | reltuples diff --git a/src/test/regress/expected/bfv_joins.out b/src/test/regress/expected/bfv_joins.out index a83b45fe7f49..4791a1c0a866 100644 --- a/src/test/regress/expected/bfv_joins.out +++ b/src/test/regress/expected/bfv_joins.out @@ -3529,6 +3529,63 @@ explain select * from o1 left join o2 on a1 = a2 left join o3 on a2 is not disti Optimizer: Postgres query optimizer (18 rows) +-- +-- Test case for Hash Join rescan after squelched without hashtable built +-- See https://github.com/greenplum-db/gpdb/pull/15590 +-- +--- Lateral Join +set from_collapse_limit = 1; +set join_collapse_limit = 1; +select 1 from pg_namespace join lateral + (select * from aclexplode(nspacl) x join pg_authid on x.grantee = pg_authid.oid where rolname = current_user) z on true limit 1; + ?column? +---------- + 1 +(1 row) + +reset from_collapse_limit; +reset join_collapse_limit; +--- NestLoop index join +create table l_table (a int, b int) distributed replicated; +create index l_table_idx on l_table(a); +create table r_table1 (ra1 int, rb1 int) distributed replicated; +create table r_table2 (ra2 int, rb2 int) distributed replicated; +insert into l_table select i % 10 , i from generate_series(1, 10000) i; +insert into r_table1 select i, i from generate_series(1, 1000) i; +insert into r_table2 values(11, 11), (1, 1) ; +analyze l_table; +analyze r_table1; +analyze r_table2; +set optimizer to off; +set enable_nestloop to on; +set enable_bitmapscan to off; +explain select * from r_table2 where ra2 in ( select a from l_table join r_table1 on b = rb1); + QUERY PLAN +------------------------------------------------------------------------------------------------- + Gather Motion 1:1 (slice1; segments: 1) (cost=64.56..64.56 rows=10 width=8) + -> Nested Loop Semi Join (cost=24.66..64.56 rows=10 width=8) + -> Seq Scan on r_table2 (cost=0.00..1.02 rows=2 width=8) + -> Hash Join (cost=24.66..62.75 rows=100 width=4) + Hash Cond: (l_table.b = r_table1.rb1) + -> Index Scan using l_table_idx on l_table (cost=0.16..25.62 rows=1000 width=8) + Index Cond: (a = r_table2.ra2) + -> Hash (cost=12.00..12.00 rows=1000 width=4) + -> Seq Scan on r_table1 (cost=0.00..12.00 rows=1000 width=4) + Optimizer: Postgres query optimizer +(10 rows) + +select * from r_table2 where ra2 in ( select a from l_table join r_table1 on b = rb1); + ra2 | rb2 +-----+----- + 1 | 1 +(1 row) + +reset optimizer; +reset enable_nestloop; +reset enable_bitmapscan; +drop table l_table; +drop table r_table1; +drop table r_table2; -- Clean up. None of the objects we create are very interesting to keep around. reset search_path; set client_min_messages='warning'; diff --git a/src/test/regress/expected/bfv_joins_optimizer.out b/src/test/regress/expected/bfv_joins_optimizer.out index ff2db765d455..be9db88f5350 100644 --- a/src/test/regress/expected/bfv_joins_optimizer.out +++ b/src/test/regress/expected/bfv_joins_optimizer.out @@ -3522,6 +3522,63 @@ explain select * from o1 left join o2 on a1 = a2 left join o3 on a2 is not disti Optimizer: Pivotal Optimizer (GPORCA) (13 rows) +-- +-- Test case for Hash Join rescan after squelched without hashtable built +-- See https://github.com/greenplum-db/gpdb/pull/15590 +-- +--- Lateral Join +set from_collapse_limit = 1; +set join_collapse_limit = 1; +select 1 from pg_namespace join lateral + (select * from aclexplode(nspacl) x join pg_authid on x.grantee = pg_authid.oid where rolname = current_user) z on true limit 1; + ?column? +---------- + 1 +(1 row) + +reset from_collapse_limit; +reset join_collapse_limit; +--- NestLoop index join +create table l_table (a int, b int) distributed replicated; +create index l_table_idx on l_table(a); +create table r_table1 (ra1 int, rb1 int) distributed replicated; +create table r_table2 (ra2 int, rb2 int) distributed replicated; +insert into l_table select i % 10 , i from generate_series(1, 10000) i; +insert into r_table1 select i, i from generate_series(1, 1000) i; +insert into r_table2 values(11, 11), (1, 1) ; +analyze l_table; +analyze r_table1; +analyze r_table2; +set optimizer to off; +set enable_nestloop to on; +set enable_bitmapscan to off; +explain select * from r_table2 where ra2 in ( select a from l_table join r_table1 on b = rb1); + QUERY PLAN +------------------------------------------------------------------------------------------------- + Gather Motion 1:1 (slice1; segments: 1) (cost=64.56..64.56 rows=10 width=8) + -> Nested Loop Semi Join (cost=24.66..64.56 rows=10 width=8) + -> Seq Scan on r_table2 (cost=0.00..1.02 rows=2 width=8) + -> Hash Join (cost=24.66..62.75 rows=100 width=4) + Hash Cond: (l_table.b = r_table1.rb1) + -> Index Scan using l_table_idx on l_table (cost=0.16..25.62 rows=1000 width=8) + Index Cond: (a = r_table2.ra2) + -> Hash (cost=12.00..12.00 rows=1000 width=4) + -> Seq Scan on r_table1 (cost=0.00..12.00 rows=1000 width=4) + Optimizer: Postgres query optimizer +(10 rows) + +select * from r_table2 where ra2 in ( select a from l_table join r_table1 on b = rb1); + ra2 | rb2 +-----+----- + 1 | 1 +(1 row) + +reset optimizer; +reset enable_nestloop; +reset enable_bitmapscan; +drop table l_table; +drop table r_table1; +drop table r_table2; -- Clean up. None of the objects we create are very interesting to keep around. reset search_path; set client_min_messages='warning'; diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out index eae1d52b3558..48ada51edae8 100644 --- a/src/test/regress/expected/btree_index.out +++ b/src/test/regress/expected/btree_index.out @@ -377,7 +377,7 @@ INSERT INTO btree_stats_tbl VALUES (1, 'aa', 1001, 101), (2, 'bb', 1002, 102); SELECT reltuples FROM pg_class WHERE relname='btree_stats_tbl'; reltuples ----------- - 0 + -1 (1 row) -- inspect the state of the stats on segments diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out index 2b86ce90286d..16b4d9e2cdc6 100644 --- a/src/test/regress/expected/collate.icu.utf8.out +++ b/src/test/regress/expected/collate.icu.utf8.out @@ -1082,9 +1082,6 @@ SELECT collname FROM pg_collation WHERE collname LIKE 'test%'; DROP SCHEMA test_schema; DROP ROLE regress_test_role; --- ALTER -ALTER COLLATION "en-x-icu" REFRESH VERSION; -NOTICE: version has not changed -- dependencies CREATE COLLATION test0 FROM "C"; CREATE TABLE collate_dep_test1 (a int, b text COLLATE test0); @@ -1900,6 +1897,207 @@ SELECT (SELECT count(*) FROM test33_0) <> (SELECT count(*) FROM test33_1); t (1 row) +-- collation versioning support +CREATE TYPE t_en_fr AS (fr text COLLATE "fr-x-icu", en text COLLATE "en-x-icu"); +CREATE DOMAIN d_en_fr AS t_en_fr; +CREATE DOMAIN d_es AS text COLLATE "es-x-icu"; +CREATE TYPE t_en_fr_ga AS (en_fr t_en_fr, ga text COLLATE "ga-x-icu"); +CREATE DOMAIN d_en_fr_ga AS t_en_fr_ga; +CREATE TYPE t_custom AS (meh text, meh2 text); +CREATE DOMAIN d_custom AS t_custom; +CREATE COLLATION custom ( + LOCALE = 'fr-x-icu', PROVIDER = 'icu' +); +CREATE TYPE myrange AS range (subtype = text); +CREATE TYPE myrange_en_fr_ga AS range(subtype = t_en_fr_ga); +CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); +CREATE TABLE collate_test ( + id integer, + val text COLLATE "fr-x-icu", + t_en_fr t_en_fr, + d_en_fr d_en_fr, + d_es d_es, + t_en_fr_ga t_en_fr_ga, + d_en_fr_ga d_en_fr_ga, + d_en_fr_ga_arr d_en_fr_ga[], + myrange myrange, + myrange_en_fr_ga myrange_en_fr_ga, + mood mood +); +CREATE INDEX icuidx00_val ON collate_test(val); +-- shouldn't get duplicated dependencies +CREATE INDEX icuidx00_val_val ON collate_test(val, val); +-- shouldn't track version +CREATE INDEX icuidx00_val_pattern ON collate_test(val text_pattern_ops); +-- should have single dependency, no version +CREATE INDEX icuidx00_val_pattern_val_pattern ON collate_test(val text_pattern_ops, val text_pattern_ops); +-- should have single dependency, with version +CREATE INDEX icuidx00_val_pattern_val ON collate_test(val text_pattern_ops, val); +-- should have single dependency, with version +CREATE INDEX icuidx00_val_val_pattern ON collate_test(val, val text_pattern_ops); +-- two rows expected, only one a version, because we don't try to merge these yet +CREATE INDEX icuidx00_val_pattern_where ON collate_test(val text_pattern_ops) WHERE val >= val; +-- two rows expected with version, because we don't try to merge these yet +CREATE INDEX icuidx00_val_where ON collate_test(val) WHERE val >= val; +-- two rows expected with version (expression walker + attribute) +CREATE INDEX icuidx00_val_pattern_expr ON collate_test(val varchar_pattern_ops, (val || val)); +-- two rows expected, one with a version (expression walker + attribute) +CREATE INDEX icuidx00_val_pattern_expr_pattern ON collate_test(val varchar_pattern_ops, (val || val) text_pattern_ops); +-- should have single dependency, with version tracked +CREATE INDEX icuidx01_t_en_fr__d_es ON collate_test (t_en_fr, d_es); +CREATE INDEX icuidx02_d_en_fr ON collate_test (d_en_fr); +CREATE INDEX icuidx03_t_en_fr_ga ON collate_test (t_en_fr_ga); +CREATE INDEX icuidx04_d_en_fr_ga ON collate_test (d_en_fr_ga); +CREATE INDEX icuidx05_d_en_fr_ga_arr ON collate_test (d_en_fr_ga_arr); +CREATE INDEX icuidx06_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga).en_fr.fr = 'foo'; +CREATE INDEX icuidx07_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga).ga = 'foo'; +CREATE INDEX icuidx08_d_en_fr_ga ON collate_test(id) WHERE (t_en_fr_ga) = ('foo', 'bar', 'baz'); +CREATE INDEX icuidx09_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga) = ('foo', 'bar', 'baz'); +CREATE INDEX icuidx10_d_en_fr_ga_es ON collate_test(id) WHERE (d_en_fr_ga) = ('foo', 'bar', 'baz' COLLATE "es-x-icu"); +CREATE INDEX icuidx11_d_es ON collate_test(id) WHERE (d_es) = ('foo'); +CREATE INDEX icuidx12_custom ON collate_test(id) WHERE ('foo', 'bar')::d_custom = ('foo', 'bar' COLLATE custom)::d_custom; +CREATE INDEX icuidx13_custom ON collate_test(id) WHERE ('foo' COLLATE custom, 'bar')::d_custom = ('foo', 'bar')::d_custom; +CREATE INDEX icuidx14_myrange ON collate_test(myrange); +CREATE INDEX icuidx15_myrange_en_fr_ga ON collate_test USING gist (myrange_en_fr_ga); +CREATE INDEX icuidx16_mood ON collate_test(id) WHERE mood > 'ok' COLLATE "fr-x-icu"; +CREATE TABLE collate_part(id integer, val text COLLATE "en-x-icu") PARTITION BY range(id); +CREATE TABLE collate_part_0 PARTITION OF collate_part FOR VALUES FROM (0) TO (1); +CREATE TABLE collate_part_1 PARTITION OF collate_part FOR VALUES FROM (1) TO (1000000); +CREATE INDEX icuidx17_part ON collate_part_1 (val); +SELECT objid::regclass::text collate "C", refobjid::regcollation::text collate "C", +CASE +WHEN refobjid = 'default'::regcollation THEN 'XXX' -- depends on libc version support +WHEN refobjversion IS NULL THEN 'version not tracked' +WHEN refobjversion = pg_collation_actual_version(refobjid) THEN 'up to date' +ELSE 'out of date' +END AS version +FROM pg_depend d +LEFT JOIN pg_class c ON c.oid = d.objid +WHERE refclassid = 'pg_collation'::regclass +AND coalesce(relkind, 'i') = 'i' +AND relname LIKE 'icuidx%' +ORDER BY 1, 2, 3; + objid | refobjid | version +-----------------------------------+------------+--------------------- + icuidx00_val | "fr-x-icu" | up to date + icuidx00_val_pattern | "fr-x-icu" | version not tracked + icuidx00_val_pattern_expr | "fr-x-icu" | up to date + icuidx00_val_pattern_expr | "fr-x-icu" | up to date + icuidx00_val_pattern_expr_pattern | "fr-x-icu" | up to date + icuidx00_val_pattern_expr_pattern | "fr-x-icu" | version not tracked + icuidx00_val_pattern_val | "fr-x-icu" | up to date + icuidx00_val_pattern_val_pattern | "fr-x-icu" | version not tracked + icuidx00_val_pattern_where | "fr-x-icu" | up to date + icuidx00_val_pattern_where | "fr-x-icu" | version not tracked + icuidx00_val_val | "fr-x-icu" | up to date + icuidx00_val_val_pattern | "fr-x-icu" | up to date + icuidx00_val_where | "fr-x-icu" | up to date + icuidx00_val_where | "fr-x-icu" | up to date + icuidx01_t_en_fr__d_es | "en-x-icu" | up to date + icuidx01_t_en_fr__d_es | "es-x-icu" | up to date + icuidx01_t_en_fr__d_es | "fr-x-icu" | up to date + icuidx02_d_en_fr | "en-x-icu" | up to date + icuidx02_d_en_fr | "fr-x-icu" | up to date + icuidx03_t_en_fr_ga | "en-x-icu" | up to date + icuidx03_t_en_fr_ga | "fr-x-icu" | up to date + icuidx03_t_en_fr_ga | "ga-x-icu" | up to date + icuidx04_d_en_fr_ga | "en-x-icu" | up to date + icuidx04_d_en_fr_ga | "fr-x-icu" | up to date + icuidx04_d_en_fr_ga | "ga-x-icu" | up to date + icuidx05_d_en_fr_ga_arr | "en-x-icu" | up to date + icuidx05_d_en_fr_ga_arr | "fr-x-icu" | up to date + icuidx05_d_en_fr_ga_arr | "ga-x-icu" | up to date + icuidx06_d_en_fr_ga | "default" | XXX + icuidx06_d_en_fr_ga | "en-x-icu" | up to date + icuidx06_d_en_fr_ga | "fr-x-icu" | up to date + icuidx06_d_en_fr_ga | "ga-x-icu" | up to date + icuidx07_d_en_fr_ga | "default" | XXX + icuidx07_d_en_fr_ga | "en-x-icu" | up to date + icuidx07_d_en_fr_ga | "fr-x-icu" | up to date + icuidx07_d_en_fr_ga | "ga-x-icu" | up to date + icuidx08_d_en_fr_ga | "en-x-icu" | up to date + icuidx08_d_en_fr_ga | "fr-x-icu" | up to date + icuidx08_d_en_fr_ga | "ga-x-icu" | up to date + icuidx09_d_en_fr_ga | "en-x-icu" | up to date + icuidx09_d_en_fr_ga | "fr-x-icu" | up to date + icuidx09_d_en_fr_ga | "ga-x-icu" | up to date + icuidx10_d_en_fr_ga_es | "en-x-icu" | up to date + icuidx10_d_en_fr_ga_es | "es-x-icu" | up to date + icuidx10_d_en_fr_ga_es | "fr-x-icu" | up to date + icuidx10_d_en_fr_ga_es | "ga-x-icu" | up to date + icuidx11_d_es | "default" | XXX + icuidx11_d_es | "es-x-icu" | up to date + icuidx12_custom | "default" | XXX + icuidx12_custom | custom | up to date + icuidx13_custom | "default" | XXX + icuidx13_custom | custom | up to date + icuidx14_myrange | "default" | XXX + icuidx15_myrange_en_fr_ga | "en-x-icu" | up to date + icuidx15_myrange_en_fr_ga | "fr-x-icu" | up to date + icuidx15_myrange_en_fr_ga | "ga-x-icu" | up to date + icuidx16_mood | "fr-x-icu" | up to date + icuidx17_part | "en-x-icu" | up to date +(58 rows) + +-- Validate that REINDEX will update the stored version. +UPDATE pg_depend SET refobjversion = 'not a version' +WHERE refclassid = 'pg_collation'::regclass +AND objid::regclass::text LIKE 'icuidx%' +AND refobjversion IS NOT NULL; +REINDEX TABLE collate_test; +REINDEX TABLE collate_part_0; +REINDEX TABLE collate_part_1; +SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version'; + objid +------- +(0 rows) + +-- Validate that REINDEX CONCURRENTLY will update the stored version. +UPDATE pg_depend SET refobjversion = 'not a version' +WHERE refclassid = 'pg_collation'::regclass +AND objid::regclass::text LIKE 'icuidx%' +AND refobjversion IS NOT NULL; +REINDEX TABLE CONCURRENTLY collate_test; +REINDEX TABLE CONCURRENTLY collate_part_0; +REINDEX INDEX CONCURRENTLY icuidx17_part; +SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version'; + objid +------- +(0 rows) + +-- Validate that VACUUM FULL will update the stored version. +UPDATE pg_depend SET refobjversion = 'not a version' +WHERE refclassid = 'pg_collation'::regclass +AND objid::regclass::text LIKE 'icuidx%' +AND refobjversion IS NOT NULL; +VACUUM FULL collate_test; +VACUUM FULL collate_part_0; +VACUUM FULL collate_part_1; +SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version'; + objid +------- +(0 rows) + +-- Test ALTER INDEX name ALTER COLLATION name REFRESH VERSION +UPDATE pg_depend SET refobjversion = 'not a version' +WHERE refclassid = 'pg_collation'::regclass +AND objid::regclass::text = 'icuidx17_part' +AND refobjversion IS NOT NULL; +SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version'; + objid +--------------- + icuidx17_part +(1 row) + +ALTER INDEX icuidx17_part ALTER COLLATION "en-x-icu" REFRESH VERSION; +SELECT objid::regclass, refobjversion = 'not a version' AS ver FROM pg_depend +WHERE refclassid = 'pg_collation'::regclass +AND objid::regclass::text = 'icuidx17_part'; + objid | ver +---------------+----- + icuidx17_part | f +(1 row) + -- cleanup RESET search_path; SET client_min_messages TO warning; diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out index f06ae543e497..580b00eea79b 100644 --- a/src/test/regress/expected/collate.linux.utf8.out +++ b/src/test/regress/expected/collate.linux.utf8.out @@ -1093,9 +1093,6 @@ SELECT collname FROM pg_collation WHERE collname LIKE 'test%'; DROP SCHEMA test_schema; DROP ROLE regress_test_role; --- ALTER -ALTER COLLATION "en_US" REFRESH VERSION; -NOTICE: version has not changed -- dependencies CREATE COLLATION test0 FROM "C"; CREATE TABLE collate_dep_test1 (a int, b text COLLATE test0); diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out index 1fe4f4d0aedb..bab1b462e344 100644 --- a/src/test/regress/expected/copy2.out +++ b/src/test/regress/expected/copy2.out @@ -40,6 +40,53 @@ PL/pgSQL function fn_x_after() line 3 at SQL statement -- non-existent column in column list: should fail COPY x (xyz) from stdin; ERROR: column "xyz" of relation "x" does not exist +-- redundant options +COPY x from stdin (format CSV, FORMAT CSV); +ERROR: conflicting or redundant options +LINE 1: COPY x from stdin (format CSV, FORMAT CSV); + ^ +COPY x from stdin (freeze off, freeze on); +ERROR: conflicting or redundant options +LINE 1: COPY x from stdin (freeze off, freeze on); + ^ +COPY x from stdin (delimiter ',', delimiter ','); +ERROR: conflicting or redundant options +LINE 1: COPY x from stdin (delimiter ',', delimiter ','); + ^ +COPY x from stdin (null ' ', null ' '); +ERROR: conflicting or redundant options +LINE 1: COPY x from stdin (null ' ', null ' '); + ^ +COPY x from stdin (header off, header on); +ERROR: conflicting or redundant options +LINE 1: COPY x from stdin (header off, header on); + ^ +COPY x from stdin (quote ':', quote ':'); +ERROR: conflicting or redundant options +LINE 1: COPY x from stdin (quote ':', quote ':'); + ^ +COPY x from stdin (escape ':', escape ':'); +ERROR: conflicting or redundant options +LINE 1: COPY x from stdin (escape ':', escape ':'); + ^ +COPY x from stdin (force_quote (a), force_quote *); +ERROR: conflicting or redundant options +LINE 1: COPY x from stdin (force_quote (a), force_quote *); + ^ +COPY x from stdin (force_not_null (a), force_not_null (b)); +ERROR: conflicting or redundant options +LINE 1: COPY x from stdin (force_not_null (a), force_not_null (b)); + ^ +COPY x from stdin (force_null (a), force_null (b)); +ERROR: conflicting or redundant options +COPY x from stdin (convert_selectively (a), convert_selectively (b)); +ERROR: conflicting or redundant options +LINE 1: COPY x from stdin (convert_selectively (a), convert_selectiv... + ^ +COPY x from stdin (encoding 'sql_ascii', encoding 'sql_ascii'); +ERROR: conflicting or redundant options +LINE 1: COPY x from stdin (encoding 'sql_ascii', encoding 'sql_ascii... + ^ -- too many columns in column list: should fail COPY x (a, b, c, d, e, d, c) from stdin; ERROR: column "d" specified more than once diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out index 9b0ae1778852..bc1f1e3d66a0 100644 --- a/src/test/regress/expected/create_am.out +++ b/src/test/regress/expected/create_am.out @@ -27,8 +27,6 @@ CREATE OPERATOR CLASS box_ops DEFAULT OPERATOR 10 <<|, OPERATOR 11 |>>, OPERATOR 12 |&>, - OPERATOR 13 ~, - OPERATOR 14 @, FUNCTION 1 gist_box_consistent(internal, box, smallint, oid, internal), FUNCTION 2 gist_box_union(internal, internal), -- don't need compress, decompress, or fetch functions @@ -46,7 +44,7 @@ SET enable_indexscan = ON; SET enable_bitmapscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; QUERY PLAN ---------------------------------------------------------------------- @@ -55,12 +53,12 @@ SELECT * FROM fast_emp4000 -> Sort Sort Key: ((home_base[0])[0]) -> Index Only Scan using grect2ind2 on fast_emp4000 - Index Cond: (home_base @ '(2000,1000),(200,200)'::box) + Index Cond: (home_base <@ '(2000,1000),(200,200)'::box) Optimizer: Postgres query optimizer (7 rows) SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; home_base ----------------------- @@ -111,8 +109,13 @@ ERROR: cannot drop access method gist2 because other objects depend on it DETAIL: index grect2ind2 depends on operator class box_ops for access method gist2 HINT: Use DROP ... CASCADE to drop the dependent objects too. -- Drop access method cascade +-- To prevent a (rare) deadlock against autovacuum, +-- we must lock the table that owns the index that will be dropped +BEGIN; +LOCK TABLE fast_emp4000; DROP ACCESS METHOD gist2 CASCADE; NOTICE: drop cascades to index grect2ind2 +COMMIT; -- -- Test table access methods -- diff --git a/src/test/regress/expected/create_am_optimizer.out b/src/test/regress/expected/create_am_optimizer.out index a94e800b55c0..c9cd2435095d 100644 --- a/src/test/regress/expected/create_am_optimizer.out +++ b/src/test/regress/expected/create_am_optimizer.out @@ -27,8 +27,6 @@ CREATE OPERATOR CLASS box_ops DEFAULT OPERATOR 10 <<|, OPERATOR 11 |>>, OPERATOR 12 |&>, - OPERATOR 13 ~, - OPERATOR 14 @, FUNCTION 1 gist_box_consistent(internal, box, smallint, oid, internal), FUNCTION 2 gist_box_union(internal, internal), -- don't need compress, decompress, or fetch functions @@ -46,7 +44,7 @@ SET enable_indexscan = ON; SET enable_bitmapscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; QUERY PLAN ------------------------------------------------------------------------ @@ -56,12 +54,12 @@ SELECT * FROM fast_emp4000 -> Sort Sort Key: ((home_base[0])[0]) -> Seq Scan on fast_emp4000 - Filter: (home_base @ '(2000,1000),(200,200)'::box) + Filter: (home_base <@ '(2000,1000),(200,200)'::box) Optimizer: Pivotal Optimizer (GPORCA) version 3.83.0 (8 rows) SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; home_base ----------------------- @@ -110,8 +108,13 @@ ERROR: cannot drop access method gist2 because other objects depend on it DETAIL: index grect2ind2 depends on operator class box_ops for access method gist2 HINT: Use DROP ... CASCADE to drop the dependent objects too. -- Drop access method cascade +-- To prevent a (rare) deadlock against autovacuum, +-- we must lock the table that owns the index that will be dropped +BEGIN; +LOCK TABLE fast_emp4000; DROP ACCESS METHOD gist2 CASCADE; NOTICE: drop cascades to index grect2ind2 +COMMIT; -- -- Test table access methods -- diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out index ba260df99607..ce508ae1dcc9 100644 --- a/src/test/regress/expected/create_function_3.out +++ b/src/test/regress/expected/create_function_3.out @@ -17,7 +17,7 @@ SET search_path TO temp_func_test, public; CREATE FUNCTION functest_A_1(text, date) RETURNS bool LANGUAGE 'sql' AS 'SELECT $1 = ''abcd'' AND $2 > ''2001-01-01'''; CREATE FUNCTION functest_A_2(text[]) RETURNS int LANGUAGE 'sql' - AS 'SELECT $1[0]::int'; + AS 'SELECT $1[1]::int'; CREATE FUNCTION functest_A_3() RETURNS bool LANGUAGE 'sql' AS 'SELECT false'; SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc @@ -31,6 +31,24 @@ SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc functest_a_3 | boolean | {} (3 rows) +SELECT functest_A_1('abcd', '2020-01-01'); + functest_a_1 +-------------- + t +(1 row) + +SELECT functest_A_2(ARRAY['1', '2', '3']); + functest_a_2 +-------------- + 1 +(1 row) + +SELECT functest_A_3(); + functest_a_3 +-------------- + f +(1 row) + -- -- IMMUTABLE | STABLE | VOLATILE -- diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index 3ae87bb35f6c..e67e836d5128 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -78,7 +78,7 @@ SET enable_seqscan = ON; SET enable_indexscan = OFF; SET enable_bitmapscan = OFF; SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; home_base ----------------------- @@ -98,7 +98,7 @@ SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; 278 (1 row) -SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon +SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; f1 --------------------- @@ -258,7 +258,7 @@ SET enable_indexscan = ON; SET enable_bitmapscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; QUERY PLAN ---------------------------------------------------------------------- @@ -267,12 +267,12 @@ SELECT * FROM fast_emp4000 -> Sort Sort Key: ((home_base[0])[0]) -> Index Only Scan using grect2ind on fast_emp4000 - Index Cond: (home_base @ '(2000,1000),(200,200)'::box) + Index Cond: (home_base <@ '(2000,1000),(200,200)'::box) Optimizer: Postgres query optimizer (7 rows) SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; home_base ----------------------- @@ -317,7 +317,7 @@ SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; (1 row) EXPLAIN (COSTS OFF) -SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon +SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; QUERY PLAN ----------------------------------------------------------------- @@ -326,11 +326,11 @@ SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon -> Sort Sort Key: ((poly_center(f1))[0]) -> Index Scan using gpolygonind on polygon_tbl - Index Cond: (f1 ~ '((1,1),(2,2),(2,1))'::polygon) + Index Cond: (f1 @> '((1,1),(2,2),(2,1))'::polygon) Optimizer: Postgres query optimizer (7 rows) -SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon +SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; f1 --------------------- @@ -2234,15 +2234,16 @@ WHERE classid = 'pg_class'::regclass AND obj | objref | deptype ------------------------------------------+------------------------------------------------------------+--------- index concur_reindex_ind1 | constraint concur_reindex_ind1 on table concur_reindex_tab | i + index concur_reindex_ind2 | collation "default" | n index concur_reindex_ind2 | column c2 of table concur_reindex_tab | a index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a - index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a + index concur_reindex_ind4 | collation "default" | n index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a index concur_reindex_ind4 | column c2 of table concur_reindex_tab | a materialized view concur_reindex_matview | schema public | n table concur_reindex_tab | schema public | n -(9 rows) +(10 rows) REINDEX INDEX CONCURRENTLY concur_reindex_ind1; ERROR: REINDEX CONCURRENTLY is not supported @@ -2265,15 +2266,16 @@ WHERE classid = 'pg_class'::regclass AND obj | objref | deptype ------------------------------------------+------------------------------------------------------------+--------- index concur_reindex_ind1 | constraint concur_reindex_ind1 on table concur_reindex_tab | i + index concur_reindex_ind2 | collation "default" | n index concur_reindex_ind2 | column c2 of table concur_reindex_tab | a index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a - index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a + index concur_reindex_ind4 | collation "default" | n index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a index concur_reindex_ind4 | column c2 of table concur_reindex_tab | a materialized view concur_reindex_matview | schema public | n table concur_reindex_tab | schema public | n -(9 rows) +(10 rows) -- Check that comments are preserved CREATE TABLE testcomment (i int); @@ -2726,6 +2728,17 @@ CREATE UNIQUE INDEX concur_exprs_index_pred ON concur_exprs_tab (c1) CREATE UNIQUE INDEX concur_exprs_index_pred_2 ON concur_exprs_tab (c1, (1 / c1)) WHERE ('-H') >= (c2::TEXT) COLLATE "C"; +ANALYZE concur_exprs_tab; +SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN ( + 'concur_exprs_index_expr'::regclass, + 'concur_exprs_index_pred'::regclass, + 'concur_exprs_index_pred_2'::regclass) + GROUP BY starelid ORDER BY starelid::regclass::text; + starelid | count +-------------------------+------- + concur_exprs_index_expr | 1 +(1 row) + SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass); pg_get_indexdef ------------------------------------------------------------------------------------------------------------------- @@ -2784,6 +2797,17 @@ SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass); CREATE UNIQUE INDEX concur_exprs_index_pred_2 ON public.concur_exprs_tab USING btree (c1, ((1 / c1))) WHERE ('-H'::text >= (c2 COLLATE "C")) (1 row) +-- Statistics should remain intact. +SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN ( + 'concur_exprs_index_expr'::regclass, + 'concur_exprs_index_pred'::regclass, + 'concur_exprs_index_pred_2'::regclass) + GROUP BY starelid ORDER BY starelid::regclass::text; + starelid | count +-------------------------+------- + concur_exprs_index_expr | 1 +(1 row) + DROP TABLE concur_exprs_tab; -- Temporary tables and on-commit actions, where CONCURRENTLY is ignored. -- ON COMMIT PRESERVE ROWS, the default. diff --git a/src/test/regress/expected/create_index_optimizer.out b/src/test/regress/expected/create_index_optimizer.out index 14a722f51c45..f904250f66e2 100644 --- a/src/test/regress/expected/create_index_optimizer.out +++ b/src/test/regress/expected/create_index_optimizer.out @@ -78,7 +78,7 @@ SET enable_seqscan = ON; SET enable_indexscan = OFF; SET enable_bitmapscan = OFF; SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; home_base ----------------------- @@ -98,7 +98,7 @@ SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; 278 (1 row) -SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon +SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; f1 --------------------- @@ -258,7 +258,7 @@ SET enable_indexscan = ON; SET enable_bitmapscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; QUERY PLAN ---------------------------------------------------------------------------- @@ -268,13 +268,13 @@ SELECT * FROM fast_emp4000 -> Sort Sort Key: ((home_base[0])[0]) -> Index Scan using grect2ind on fast_emp4000 - Index Cond: (home_base @ '(2000,1000),(200,200)'::box) - Filter: (home_base @ '(2000,1000),(200,200)'::box) + Index Cond: (home_base <@ '(2000,1000),(200,200)'::box) + Filter: (home_base <@ '(2000,1000),(200,200)'::box) Optimizer: Pivotal Optimizer (GPORCA) version 3.83.0 (9 rows) SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; home_base ----------------------- @@ -320,7 +320,7 @@ SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; (1 row) EXPLAIN (COSTS OFF) -SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon +SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; QUERY PLAN ----------------------------------------------------------------------- @@ -330,12 +330,12 @@ SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon -> Sort Sort Key: ((poly_center(f1))[0]) -> Index Scan using gpolygonind on polygon_tbl - Index Cond: (f1 ~ '((1,1),(2,2),(2,1))'::polygon) - Filter: (f1 ~ '((1,1),(2,2),(2,1))'::polygon) + Index Cond: (f1 @> '((1,1),(2,2),(2,1))'::polygon) + Filter: (f1 @> '((1,1),(2,2),(2,1))'::polygon) Optimizer: Pivotal Optimizer (GPORCA) version 3.83.0 (9 rows) -SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon +SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; f1 --------------------- @@ -2231,15 +2231,16 @@ WHERE classid = 'pg_class'::regclass AND obj | objref | deptype ------------------------------------------+------------------------------------------------------------+--------- index concur_reindex_ind1 | constraint concur_reindex_ind1 on table concur_reindex_tab | i + index concur_reindex_ind2 | collation "default" | n index concur_reindex_ind2 | column c2 of table concur_reindex_tab | a index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a - index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a + index concur_reindex_ind4 | collation "default" | n index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a index concur_reindex_ind4 | column c2 of table concur_reindex_tab | a materialized view concur_reindex_matview | schema public | n table concur_reindex_tab | schema public | n -(9 rows) +(10 rows) REINDEX INDEX CONCURRENTLY concur_reindex_ind1; ERROR: REINDEX CONCURRENTLY is not supported @@ -2262,15 +2263,16 @@ WHERE classid = 'pg_class'::regclass AND obj | objref | deptype ------------------------------------------+------------------------------------------------------------+--------- index concur_reindex_ind1 | constraint concur_reindex_ind1 on table concur_reindex_tab | i + index concur_reindex_ind2 | collation "default" | n index concur_reindex_ind2 | column c2 of table concur_reindex_tab | a index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a - index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a + index concur_reindex_ind4 | collation "default" | n index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a index concur_reindex_ind4 | column c2 of table concur_reindex_tab | a materialized view concur_reindex_matview | schema public | n table concur_reindex_tab | schema public | n -(9 rows) +(10 rows) -- Check that comments are preserved CREATE TABLE testcomment (i int); @@ -2734,6 +2736,17 @@ CREATE UNIQUE INDEX concur_exprs_index_pred ON concur_exprs_tab (c1) CREATE UNIQUE INDEX concur_exprs_index_pred_2 ON concur_exprs_tab (c1, (1 / c1)) WHERE ('-H') >= (c2::TEXT) COLLATE "C"; +ANALYZE concur_exprs_tab; +SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN ( + 'concur_exprs_index_expr'::regclass, + 'concur_exprs_index_pred'::regclass, + 'concur_exprs_index_pred_2'::regclass) + GROUP BY starelid ORDER BY starelid::regclass::text; + starelid | count +-------------------------+------- + concur_exprs_index_expr | 1 +(1 row) + SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass); pg_get_indexdef ------------------------------------------------------------------------------------------------------------------- @@ -2792,6 +2805,17 @@ SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass); CREATE UNIQUE INDEX concur_exprs_index_pred_2 ON public.concur_exprs_tab USING btree (c1, ((1 / c1))) WHERE ('-H'::text >= (c2 COLLATE "C")) (1 row) +-- Statistics should remain intact. +SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN ( + 'concur_exprs_index_expr'::regclass, + 'concur_exprs_index_pred'::regclass, + 'concur_exprs_index_pred_2'::regclass) + GROUP BY starelid ORDER BY starelid::regclass::text; + starelid | count +-------------------------+------- + concur_exprs_index_expr | 1 +(1 row) + DROP TABLE concur_exprs_tab; -- Temporary tables and on-commit actions, where CONCURRENTLY is ignored. -- ON COMMIT PRESERVE ROWS, the default. diff --git a/src/test/regress/expected/create_operator.out b/src/test/regress/expected/create_operator.out index 0246eea7d7c6..0852dc18396e 100644 --- a/src/test/regress/expected/create_operator.out +++ b/src/test/regress/expected/create_operator.out @@ -15,17 +15,15 @@ CREATE OPERATOR <% ( negator = >=% ); CREATE OPERATOR @#@ ( - rightarg = int8, -- left unary - procedure = numeric_fac -); -CREATE OPERATOR #@# ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, -- prefix + procedure = factorial ); CREATE OPERATOR #%# ( - leftarg = int8, -- right unary - procedure = numeric_fac + leftarg = int8, -- fail, postfix is no longer supported + procedure = factorial ); +ERROR: operator right argument type must be specified +DETAIL: Postfix operators are not supported. -- Test operator created above SELECT point '(1,2)' <% widget '(0,0,3)' AS t, point '(1,2)' <% widget '(0,0,1)' AS f; @@ -35,12 +33,23 @@ SELECT point '(1,2)' <% widget '(0,0,3)' AS t, (1 row) -- Test comments -COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad right unary'; -ERROR: operator does not exist: integer ###### --- => is disallowed now +COMMENT ON OPERATOR ###### (NONE, int4) IS 'bad prefix'; +ERROR: operator does not exist: ###### integer +COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad postfix'; +ERROR: postfix operators are not supported +COMMENT ON OPERATOR ###### (int4, int8) IS 'bad infix'; +ERROR: operator does not exist: integer ###### bigint +-- Check that DROP on a nonexistent op behaves sanely, too +DROP OPERATOR ###### (NONE, int4); +ERROR: operator does not exist: ###### integer +DROP OPERATOR ###### (int4, NONE); +ERROR: postfix operators are not supported +DROP OPERATOR ###### (int4, int8); +ERROR: operator does not exist: integer ###### bigint +-- => is disallowed as an operator name now CREATE OPERATOR => ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, + procedure = factorial ); ERROR: syntax error at or near "=>" LINE 1: CREATE OPERATOR => ( @@ -49,15 +58,20 @@ LINE 1: CREATE OPERATOR => ( -- (=> is tested elsewhere) -- this is legal because ! is not allowed in sql ops CREATE OPERATOR !=- ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, + procedure = factorial ); -SELECT 2 !=-; +SELECT !=- 10; ?column? ---------- - 2 + 3628800 (1 row) +-- postfix operators don't work anymore +SELECT 10 !=-; +ERROR: syntax error at or near ";" +LINE 1: SELECT 10 !=-; + ^ -- make sure lexer returns != as <> even in edge cases SELECT 2 !=/**/ 1, 2 !=/**/ 2; ?column? | ?column? @@ -128,8 +142,8 @@ REVOKE USAGE ON SCHEMA schema_op1 FROM regress_rol_op1; NOTICE: no privileges could be revoked SET ROLE regress_rol_op1; CREATE OPERATOR schema_op1.#*# ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, + procedure = factorial ); ERROR: permission denied for schema schema_op1 ROLLBACK; @@ -137,7 +151,7 @@ ROLLBACK; BEGIN TRANSACTION; CREATE OPERATOR #*# ( leftarg = SETOF int8, - procedure = numeric_fac + procedure = factorial ); ERROR: SETOF type not allowed for operator argument ROLLBACK; @@ -145,7 +159,7 @@ ROLLBACK; BEGIN TRANSACTION; CREATE OPERATOR #*# ( rightarg = SETOF int8, - procedure = numeric_fac + procedure = factorial ); ERROR: SETOF type not allowed for operator argument ROLLBACK; @@ -168,19 +182,19 @@ CREATE OPERATOR === ( ROLLBACK; -- Should fail. Invalid attribute CREATE OPERATOR #@%# ( - leftarg = int8, -- right unary - procedure = numeric_fac, + rightarg = int8, + procedure = factorial, invalid_att = int8 ); WARNING: operator attribute "invalid_att" not recognized --- Should fail. At least leftarg or rightarg should be mandatorily specified +-- Should fail. At least rightarg should be mandatorily specified CREATE OPERATOR #@%# ( - procedure = numeric_fac + procedure = factorial ); -ERROR: at least one of leftarg or rightarg must be specified +ERROR: operator argument types must be specified -- Should fail. Procedure should be mandatorily specified CREATE OPERATOR #@%# ( - leftarg = int8 + rightarg = int8 ); ERROR: operator function must be specified -- Should fail. CREATE OPERATOR requires USAGE on TYPE diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out index 211a42cefa03..3838fa2324da 100644 --- a/src/test/regress/expected/create_procedure.out +++ b/src/test/regress/expected/create_procedure.out @@ -146,6 +146,19 @@ AS $$ SELECT a = b; $$; CALL ptest7(least('a', 'b'), 'a'); +-- OUT parameters +CREATE PROCEDURE ptest9(OUT a int) +LANGUAGE SQL +AS $$ +INSERT INTO cp_test VALUES (1, 'a'); +SELECT 1; +$$; +CALL ptest9(NULL); + a +--- + 1 +(1 row) + -- various error cases CALL version(); -- error: not a procedure ERROR: version() is not a procedure @@ -165,9 +178,6 @@ CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT INTO cp_test VALUES ( ERROR: invalid attribute in procedure definition LINE 1: CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT I... ^ -CREATE PROCEDURE ptestx(OUT a int) LANGUAGE SQL AS $$ INSERT INTO cp_test VALUES (1, 'a') $$; -ERROR: procedures cannot have OUT arguments -HINT: INOUT arguments are permitted. ALTER PROCEDURE ptest1(text) STRICT; ERROR: invalid attribute in procedure definition LINE 1: ALTER PROCEDURE ptest1(text) STRICT; diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 60af8c3be909..9821cbb424e7 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -656,8 +656,6 @@ CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (genera ERROR: set-returning functions are not allowed in partition bound LINE 1: ...expr_fail PARTITION OF list_parted FOR VALUES IN (generate_s... ^ -CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN ('1' collate "POSIX"); -ERROR: collations are not supported by type integer CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN ((1+1) collate "POSIX"); ERROR: collations are not supported by type integer LINE 1: ...ail PARTITION OF list_parted FOR VALUES IN ((1+1) collate "P... @@ -681,6 +679,8 @@ LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES WITH (MODU... CREATE TABLE part_default PARTITION OF list_parted DEFAULT; CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT; ERROR: partition "fail_default_part" conflicts with existing default partition "part_default" +LINE 1: ...TE TABLE fail_default_part PARTITION OF list_parted DEFAULT; + ^ -- specified literal can't be cast to the partition column data type CREATE TABLE bools ( a bool @@ -706,6 +706,8 @@ CREATE TABLE bigintp_10 PARTITION OF bigintp FOR VALUES IN (10); -- fails due to overlap: CREATE TABLE bigintp_10_2 PARTITION OF bigintp FOR VALUES IN ('10'); ERROR: partition "bigintp_10_2" would overlap partition "bigintp_10" +LINE 1: ...ABLE bigintp_10_2 PARTITION OF bigintp FOR VALUES IN ('10'); + ^ DROP TABLE bigintp; CREATE TABLE range_parted ( a date @@ -827,8 +829,12 @@ CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b'); CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT; CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null); ERROR: partition "fail_part" would overlap partition "part_null_z" +LINE 1: ...LE fail_part PARTITION OF list_parted2 FOR VALUES IN (null); + ^ CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c'); ERROR: partition "fail_part" would overlap partition "part_ab" +LINE 1: ...ail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c'); + ^ -- check default partition overlap INSERT INTO list_parted2 VALUES('X'); CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y'); @@ -839,28 +845,46 @@ CREATE TABLE range_parted2 ( -- trying to create range partition with empty range CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0); ERROR: empty range bound specified for partition "fail_part" +LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0); + ^ DETAIL: Specified lower bound (1) is greater than or equal to upper bound (0). -- note that the range '[1, 1)' has no elements CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1); ERROR: empty range bound specified for partition "fail_part" +LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1); + ^ DETAIL: Specified lower bound (1) is greater than or equal to upper bound (1). CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (minvalue) TO (1); CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (minvalue) TO (2); ERROR: partition "fail_part" would overlap partition "part0" +LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (minvalue) ... + ^ CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10); +CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (-1) TO (1); +ERROR: partition "fail_part" would overlap partition "part0" +LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (-1) TO (1)... + ^ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (maxvalue); ERROR: partition "fail_part" would overlap partition "part1" +LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (max... + ^ CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30); CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40); CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30); ERROR: partition "fail_part" would overlap partition "part2" +LINE 1: ...art PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30); + ^ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50); ERROR: partition "fail_part" would overlap partition "part2" +LINE 1: ...art PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50); + ^ -- Create a default partition for range partitioned table CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT; -- More than one default partition is not allowed, so this should give error CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT; ERROR: partition "fail_default_part" conflicts with existing default partition "range2_default" +LINE 1: ... TABLE fail_default_part PARTITION OF range_parted2 DEFAULT; + ^ -- Check if the range for default partitions overlap INSERT INTO range_parted2 VALUES (85); CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90); @@ -874,17 +898,23 @@ CREATE TABLE range_parted3 ( CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, minvalue) TO (0, maxvalue); CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, minvalue) TO (0, 1); ERROR: partition "fail_part" would overlap partition "part00" +LINE 1: ..._part PARTITION OF range_parted3 FOR VALUES FROM (0, minvalu... + ^ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, 1); CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10); CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue); CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20); ERROR: partition "fail_part" would overlap partition "part12" +LINE 1: ...rt PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1,... + ^ CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT; -- cannot create a partition that says column b is allowed to range -- from -infinity to +infinity, while there exist partitions that have -- more specific ranges CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue); ERROR: partition "fail_part" would overlap partition "part10" +LINE 1: ..._part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalu... + ^ -- check for partition bound overlap and other invalid specifications for the hash partition CREATE TABLE hash_parted2 ( a varchar @@ -896,6 +926,8 @@ CREATE TABLE h2part_4 PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 8, REMA -- overlap with part_4 CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 2, REMAINDER 1); ERROR: partition "fail_part" would overlap partition "h2part_4" +LINE 1: ...LE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (MODU... + ^ -- modulus must be greater than zero CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 0, REMAINDER 1); ERROR: modulus for hash partition must be a positive integer @@ -990,6 +1022,13 @@ DETAIL: Failing row contains (1, null). Partition of: parted_notnull_inh_test FOR VALUES IN (1) drop table parted_notnull_inh_test; +-- check that collations are assigned in partition bound expressions +create table parted_boolean_col (a bool, b text) partition by list(a); +create table parted_boolean_less partition of parted_boolean_col + for values in ('foo' < 'bar'); +create table parted_boolean_greater partition of parted_boolean_col + for values in ('foo' > 'bar'); +drop table parted_boolean_col; -- check for a conflicting COLLATE clause create table parted_collate_must_match (a text collate "C", b text collate "C") partition by range (a); @@ -1000,26 +1039,15 @@ create table parted_collate_must_match1 partition of parted_collate_must_match create table parted_collate_must_match2 partition of parted_collate_must_match (b collate "POSIX") for values from ('m') to ('z'); drop table parted_collate_must_match; --- check that specifying incompatible collations for partition bound --- expressions fails promptly +-- check that non-matching collations for partition bound +-- expressions are coerced to the right collation create table test_part_coll_posix (a text) partition by range (a collate "POSIX"); --- fail +-- ok, collation is implicitly coerced create table test_part_coll partition of test_part_coll_posix for values from ('a' collate "C") to ('g'); -ERROR: collation of partition bound value for column "a" does not match partition key collation "POSIX" -LINE 1: ...artition of test_part_coll_posix for values from ('a' collat... - ^ --- ok -create table test_part_coll partition of test_part_coll_posix for values from ('a' collate "POSIX") to ('g'); -- ok create table test_part_coll2 partition of test_part_coll_posix for values from ('g') to ('m'); --- using a cast expression uses the target type's default collation --- fail +-- ok, collation is implicitly coerced create table test_part_coll_cast partition of test_part_coll_posix for values from (name 'm' collate "C") to ('s'); -ERROR: collation of partition bound value for column "a" does not match partition key collation "POSIX" -LINE 1: ...ion of test_part_coll_posix for values from (name 'm' collat... - ^ --- ok -create table test_part_coll_cast partition of test_part_coll_posix for values from (name 'm' collate "POSIX") to ('s'); -- ok; partition collation silently overrides the default collation of type 'name' create table test_part_coll_cast2 partition of test_part_coll_posix for values from (name 's') to ('z'); drop table test_part_coll_posix; diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out index f6722461305b..7c53f2511355 100644 --- a/src/test/regress/expected/create_table_like.out +++ b/src/test/regress/expected/create_table_like.out @@ -160,7 +160,9 @@ SELECT * FROM test_like_gen_3; DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3; -- also test generated column with a "forward" reference (bug #16342) -CREATE TABLE test_like_4 (b int DEFAULT 42, c int GENERATED ALWAYS AS (a * 2) STORED, a int); +CREATE TABLE test_like_4 (b int DEFAULT 42, + c int GENERATED ALWAYS AS (a * 2) STORED, + a int CHECK (a > 0)); \d test_like_4 Table "public.test_like_4" Column | Type | Collation | Nullable | Default @@ -168,6 +170,8 @@ CREATE TABLE test_like_4 (b int DEFAULT 42, c int GENERATED ALWAYS AS (a * 2) ST b | integer | | | 42 c | integer | | | generated always as (a * 2) stored a | integer | | | +Check constraints: + "test_like_4_a_check" CHECK (a > 0) CREATE TABLE test_like_4a (LIKE test_like_4); CREATE TABLE test_like_4b (LIKE test_like_4 INCLUDING DEFAULTS); @@ -233,7 +237,32 @@ SELECT a, b, c FROM test_like_4d; 11 | 42 | 22 (1 row) +-- Test renumbering of Vars when combining LIKE with inheritance +CREATE TABLE test_like_5 (x point, y point, z point); +CREATE TABLE test_like_5x (p int CHECK (p > 0), + q int GENERATED ALWAYS AS (p * 2) STORED); +CREATE TABLE test_like_5c (LIKE test_like_4 INCLUDING ALL) + INHERITS (test_like_5, test_like_5x); +\d test_like_5c + Table "public.test_like_5c" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + x | point | | | + y | point | | | + z | point | | | + p | integer | | | + q | integer | | | generated always as (p * 2) stored + b | integer | | | 42 + c | integer | | | generated always as (a * 2) stored + a | integer | | | +Check constraints: + "test_like_4_a_check" CHECK (a > 0) + "test_like_5x_p_check" CHECK (p > 0) +Inherits: test_like_5, + test_like_5x + DROP TABLE test_like_4, test_like_4a, test_like_4b, test_like_4c, test_like_4d; +DROP TABLE test_like_5, test_like_5x, test_like_5c; CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */ INSERT INTO inhg VALUES (5, 10); INSERT INTO inhg VALUES (20, 10); -- should fail @@ -242,7 +271,8 @@ DETAIL: Key (xx)=(10) already exists. DROP TABLE inhg; /* Multiple primary keys creation should fail */ CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, PRIMARY KEY(x)); /* fails */ -ERROR: multiple primary keys for table "inhg" are not allowed +ERROR: UNIQUE or PRIMARY KEY definitions are incompatible with each other +HINT: When there are multiple PRIMARY KEY / UNIQUE constraints, they must have at least one column in common. CREATE TABLE inhz (xx text DEFAULT 'text', yy int UNIQUE); CREATE UNIQUE INDEX inhz_xx_idx on inhz (xx) WHERE xx <> 'test'; ERROR: UNIQUE index must contain all columns in the table's distribution key @@ -277,9 +307,10 @@ ALTER TABLE ctlt1 ALTER COLUMN a SET STORAGE MAIN; CREATE TABLE ctlt2 (c text); ALTER TABLE ctlt2 ALTER COLUMN c SET STORAGE EXTERNAL; COMMENT ON COLUMN ctlt2.c IS 'C'; -CREATE TABLE ctlt3 (a text CHECK (length(a) < 5), c text); +CREATE TABLE ctlt3 (a text CHECK (length(a) < 5), c text CHECK (length(c) < 7)); ALTER TABLE ctlt3 ALTER COLUMN c SET STORAGE EXTERNAL; ALTER TABLE ctlt3 ALTER COLUMN a SET STORAGE MAIN; +CREATE INDEX ctlt3_fnidx ON ctlt3 ((a || c)); COMMENT ON COLUMN ctlt3.a IS 'A3'; COMMENT ON COLUMN ctlt3.c IS 'C'; COMMENT ON CONSTRAINT ctlt3_a_check ON ctlt3 IS 't3_a_check'; @@ -338,11 +369,12 @@ NOTICE: merging multiple inherited definitions of column "a" Check constraints: "ctlt1_a_check" CHECK (length(a) > 2) "ctlt3_a_check" CHECK (length(a) < 5) + "ctlt3_c_check" CHECK (length(c) < 7) Inherits: ctlt1, ctlt3 Distributed by: (a) -CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1); +CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1); NOTICE: merging column "a" with inherited definition \d+ ctlt13_like Table "public.ctlt13_like" @@ -351,9 +383,12 @@ NOTICE: merging column "a" with inherited definition a | text | | not null | | main | | A3 b | text | | | | extended | | c | text | | | | external | | C +Indexes: + "ctlt13_like_expr_idx" btree ((a || c)) Check constraints: "ctlt1_a_check" CHECK (length(a) > 2) "ctlt3_a_check" CHECK (length(a) < 5) + "ctlt3_c_check" CHECK (length(c) < 7) Inherits: ctlt1 Distributed by: (a) @@ -401,6 +436,24 @@ CREATE TABLE inh_error2 (LIKE ctlt4 INCLUDING STORAGE) INHERITS (ctlt1); NOTICE: merging column "a" with inherited definition ERROR: column "a" has a storage parameter conflict DETAIL: MAIN versus EXTENDED +-- Check that LIKE isn't confused by a system catalog of the same name +CREATE TABLE pg_attrdef (LIKE ctlt1 INCLUDING ALL); +\d+ public.pg_attrdef + Table "public.pg_attrdef" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+------+-----------+----------+---------+----------+--------------+------------- + a | text | | not null | | main | | A + b | text | | | | extended | | B +Indexes: + "pg_attrdef_pkey" PRIMARY KEY, btree (a) + "pg_attrdef_b_idx" btree (b) + "pg_attrdef_expr_idx" btree ((a || b)) +Check constraints: + "ctlt1_a_check" CHECK (length(a) > 2) +Statistics objects: + "public"."pg_attrdef_a_b_stat" (ndistinct, dependencies, mcv) ON a, b FROM public.pg_attrdef + +DROP TABLE public.pg_attrdef; DROP TABLE ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctlb, ctla, ctlt1, ctlt2, ctlt3, ctlt4 CASCADE; NOTICE: drop cascades to table inhe -- LIKE must respect NO INHERIT property of constraints diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out index 4044d174be7d..28660564b5b6 100644 --- a/src/test/regress/expected/date.out +++ b/src/test/regress/expected/date.out @@ -21,9 +21,10 @@ INSERT INTO DATE_TBL VALUES ('2000-04-03'); INSERT INTO DATE_TBL VALUES ('2038-04-08'); INSERT INTO DATE_TBL VALUES ('2039-04-09'); INSERT INTO DATE_TBL VALUES ('2040-04-10'); -SELECT f1 AS "Fifteen" FROM DATE_TBL; - Fifteen ------------- +INSERT INTO DATE_TBL VALUES ('2040-04-10 BC'); +SELECT f1 FROM DATE_TBL; + f1 +--------------- 04-09-1957 06-13-1957 02-28-1996 @@ -39,11 +40,12 @@ SELECT f1 AS "Fifteen" FROM DATE_TBL; 04-08-2038 04-09-2039 04-10-2040 -(15 rows) + 04-10-2040 BC +(16 rows) -SELECT f1 AS "Nine" FROM DATE_TBL WHERE f1 < '2000-01-01'; - Nine ------------- +SELECT f1 FROM DATE_TBL WHERE f1 < '2000-01-01'; + f1 +--------------- 04-09-1957 06-13-1957 02-28-1996 @@ -53,11 +55,12 @@ SELECT f1 AS "Nine" FROM DATE_TBL WHERE f1 < '2000-01-01'; 02-28-1997 03-01-1997 03-02-1997 -(9 rows) + 04-10-2040 BC +(10 rows) -SELECT f1 AS "Three" FROM DATE_TBL +SELECT f1 FROM DATE_TBL WHERE f1 BETWEEN '2000-01-01' AND '2001-01-01'; - Three + f1 ------------ 04-01-2000 04-02-2000 @@ -905,7 +908,8 @@ SELECT f1 - date '2000-01-01' AS "Days From 2K" FROM DATE_TBL; 13977 14343 14710 -(15 rows) + -1475115 +(16 rows) SELECT f1 - date 'epoch' AS "Days From Epoch" FROM DATE_TBL; Days From Epoch @@ -925,7 +929,8 @@ SELECT f1 - date 'epoch' AS "Days From Epoch" FROM DATE_TBL; 24934 25300 25667 -(15 rows) + -1464158 +(16 rows) SELECT date 'yesterday' - date 'today' AS "One day"; One day @@ -965,6 +970,43 @@ SELECT date 'tomorrow' - date 'yesterday' AS "Two days"; -- -- test extract! +-- +SELECT f1 as "date", + date_part('year', f1) AS year, + date_part('month', f1) AS month, + date_part('day', f1) AS day, + date_part('quarter', f1) AS quarter, + date_part('decade', f1) AS decade, + date_part('century', f1) AS century, + date_part('millennium', f1) AS millennium, + date_part('isoyear', f1) AS isoyear, + date_part('week', f1) AS week, + date_part('dow', f1) AS dow, + date_part('isodow', f1) AS isodow, + date_part('doy', f1) AS doy, + date_part('julian', f1) AS julian, + date_part('epoch', f1) AS epoch + FROM date_tbl; + date | year | month | day | quarter | decade | century | millennium | isoyear | week | dow | isodow | doy | julian | epoch +---------------+-------+-------+-----+---------+--------+---------+------------+---------+------+-----+--------+-----+---------+--------------- + 04-09-1957 | 1957 | 4 | 9 | 2 | 195 | 20 | 2 | 1957 | 15 | 2 | 2 | 99 | 2435938 | -401760000 + 06-13-1957 | 1957 | 6 | 13 | 2 | 195 | 20 | 2 | 1957 | 24 | 4 | 4 | 164 | 2436003 | -396144000 + 02-28-1996 | 1996 | 2 | 28 | 1 | 199 | 20 | 2 | 1996 | 9 | 3 | 3 | 59 | 2450142 | 825465600 + 02-29-1996 | 1996 | 2 | 29 | 1 | 199 | 20 | 2 | 1996 | 9 | 4 | 4 | 60 | 2450143 | 825552000 + 03-01-1996 | 1996 | 3 | 1 | 1 | 199 | 20 | 2 | 1996 | 9 | 5 | 5 | 61 | 2450144 | 825638400 + 03-02-1996 | 1996 | 3 | 2 | 1 | 199 | 20 | 2 | 1996 | 9 | 6 | 6 | 62 | 2450145 | 825724800 + 02-28-1997 | 1997 | 2 | 28 | 1 | 199 | 20 | 2 | 1997 | 9 | 5 | 5 | 59 | 2450508 | 857088000 + 03-01-1997 | 1997 | 3 | 1 | 1 | 199 | 20 | 2 | 1997 | 9 | 6 | 6 | 60 | 2450509 | 857174400 + 03-02-1997 | 1997 | 3 | 2 | 1 | 199 | 20 | 2 | 1997 | 9 | 0 | 7 | 61 | 2450510 | 857260800 + 04-01-2000 | 2000 | 4 | 1 | 2 | 200 | 20 | 2 | 2000 | 13 | 6 | 6 | 92 | 2451636 | 954547200 + 04-02-2000 | 2000 | 4 | 2 | 2 | 200 | 20 | 2 | 2000 | 13 | 0 | 7 | 93 | 2451637 | 954633600 + 04-03-2000 | 2000 | 4 | 3 | 2 | 200 | 20 | 2 | 2000 | 14 | 1 | 1 | 94 | 2451638 | 954720000 + 04-08-2038 | 2038 | 4 | 8 | 2 | 203 | 21 | 3 | 2038 | 14 | 4 | 4 | 98 | 2465522 | 2154297600 + 04-09-2039 | 2039 | 4 | 9 | 2 | 203 | 21 | 3 | 2039 | 14 | 6 | 6 | 99 | 2465888 | 2185920000 + 04-10-2040 | 2040 | 4 | 10 | 2 | 204 | 21 | 3 | 2040 | 15 | 2 | 2 | 101 | 2466255 | 2217628800 + 04-10-2040 BC | -2040 | 4 | 10 | 2 | -204 | -21 | -3 | -2040 | 15 | 1 | 1 | 100 | 976430 | -126503251200 +(16 rows) + -- -- epoch -- @@ -1156,6 +1198,132 @@ SELECT EXTRACT(CENTURY FROM TIMESTAMP '1970-03-20 04:30:00.00000'); -- 20 20 (1 row) +-- +-- all possible fields +-- +SELECT EXTRACT(MICROSECONDS FROM DATE '2020-08-11'); + date_part +----------- + 0 +(1 row) + +SELECT EXTRACT(MILLISECONDS FROM DATE '2020-08-11'); + date_part +----------- + 0 +(1 row) + +SELECT EXTRACT(SECOND FROM DATE '2020-08-11'); + date_part +----------- + 0 +(1 row) + +SELECT EXTRACT(MINUTE FROM DATE '2020-08-11'); + date_part +----------- + 0 +(1 row) + +SELECT EXTRACT(HOUR FROM DATE '2020-08-11'); + date_part +----------- + 0 +(1 row) + +SELECT EXTRACT(DAY FROM DATE '2020-08-11'); + date_part +----------- + 11 +(1 row) + +SELECT EXTRACT(MONTH FROM DATE '2020-08-11'); + date_part +----------- + 8 +(1 row) + +SELECT EXTRACT(YEAR FROM DATE '2020-08-11'); + date_part +----------- + 2020 +(1 row) + +SELECT EXTRACT(DECADE FROM DATE '2020-08-11'); + date_part +----------- + 202 +(1 row) + +SELECT EXTRACT(CENTURY FROM DATE '2020-08-11'); + date_part +----------- + 21 +(1 row) + +SELECT EXTRACT(MILLENNIUM FROM DATE '2020-08-11'); + date_part +----------- + 3 +(1 row) + +SELECT EXTRACT(ISOYEAR FROM DATE '2020-08-11'); + date_part +----------- + 2020 +(1 row) + +SELECT EXTRACT(QUARTER FROM DATE '2020-08-11'); + date_part +----------- + 3 +(1 row) + +SELECT EXTRACT(WEEK FROM DATE '2020-08-11'); + date_part +----------- + 33 +(1 row) + +SELECT EXTRACT(DOW FROM DATE '2020-08-11'); + date_part +----------- + 2 +(1 row) + +SELECT EXTRACT(ISODOW FROM DATE '2020-08-11'); + date_part +----------- + 2 +(1 row) + +SELECT EXTRACT(DOY FROM DATE '2020-08-11'); + date_part +----------- + 224 +(1 row) + +SELECT EXTRACT(TIMEZONE FROM DATE '2020-08-11'); +ERROR: timestamp units "timezone" not supported +CONTEXT: SQL function "date_part" statement 1 +SELECT EXTRACT(TIMEZONE_M FROM DATE '2020-08-11'); +ERROR: timestamp units "timezone_m" not supported +CONTEXT: SQL function "date_part" statement 1 +SELECT EXTRACT(TIMEZONE_H FROM DATE '2020-08-11'); +ERROR: timestamp units "timezone_h" not supported +CONTEXT: SQL function "date_part" statement 1 +SELECT EXTRACT(EPOCH FROM DATE '2020-08-11'); + date_part +------------ + 1597104000 +(1 row) + +SELECT EXTRACT(JULIAN FROM DATE '2020-08-11'); + date_part +----------- + 2459073 +(1 row) + -- -- test trunc function! -- @@ -1484,6 +1652,8 @@ select make_time(8, 20, 0.0); (1 row) -- should fail +select make_date(0, 7, 15); +ERROR: date field value out of range: 0-07-15 select make_date(2013, 2, 30); ERROR: date field value out of range: 2013-02-30 select make_date(2013, 13, 1); diff --git a/src/test/regress/expected/eagerfree.out b/src/test/regress/expected/eagerfree.out index fcef2871f9a2..0c60c1373f13 100644 --- a/src/test/regress/expected/eagerfree.out +++ b/src/test/regress/expected/eagerfree.out @@ -1885,19 +1885,15 @@ explain analyze select *, exists(select 1 from pg_class where oid = c.oid) as du QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------- Seq Scan on pg_class c (cost=10000000000.00..10003029725.25 rows=15124 width=206) (actual time=24.734..41.952 rows=6569 loops=1) - SubPlan 1 - -> Index Only Scan using pg_class_oid_index on pg_class (cost=0.29..200.30 rows=1 width=0) (never executed) - Index Cond: (oid = c.oid) - Heap Fetches: 0 SubPlan 2 - -> Index Only Scan using pg_class_oid_index on pg_class pg_class_1 (cost=0.29..21127.15 rows=15124 width=4) (actual time=0.075..18.903 rows=6569 loops=1) + -> Index Only Scan using pg_class_oid_index on pg_class (cost=0.29..21127.15 rows=15124 width=4) (actual time=0.075..18.903 rows=6569 loops=1) Heap Fetches: 20272 Planning time: 5.919 ms (slice0) Executor memory: 937K bytes. Memory used: 128000kB Optimizer: Postgres query optimizer Execution time: 43.318 ms -(13 rows) +(9 rows) -- BitmapScan -- start_ignore diff --git a/src/test/regress/expected/eagerfree_optimizer.out b/src/test/regress/expected/eagerfree_optimizer.out index bbbe29224bbf..0cd21248e712 100644 --- a/src/test/regress/expected/eagerfree_optimizer.out +++ b/src/test/regress/expected/eagerfree_optimizer.out @@ -1899,19 +1899,15 @@ explain analyze select *, exists(select 1 from pg_class where oid = c.oid) as du QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------- Seq Scan on pg_class c (cost=10000000000.00..10003029725.25 rows=15124 width=206) (actual time=24.734..41.952 rows=6569 loops=1) - SubPlan 1 - -> Index Only Scan using pg_class_oid_index on pg_class (cost=0.29..200.30 rows=1 width=0) (never executed) - Index Cond: (oid = c.oid) - Heap Fetches: 0 SubPlan 2 - -> Index Only Scan using pg_class_oid_index on pg_class pg_class_1 (cost=0.29..21127.15 rows=15124 width=4) (actual time=0.075..18.903 rows=6569 loops=1) + -> Index Only Scan using pg_class_oid_index on pg_class (cost=0.29..21127.15 rows=15124 width=4) (actual time=0.075..18.903 rows=6569 loops=1) Heap Fetches: 20272 Planning time: 5.919 ms (slice0) Executor memory: 937K bytes. Memory used: 128000kB Optimizer: Postgres query optimizer Execution time: 43.318 ms -(13 rows) +(9 rows) -- BitmapScan -- start_ignore diff --git a/src/test/regress/expected/enum.out b/src/test/regress/expected/enum.out index 31309fde55b6..686ab49742d8 100644 --- a/src/test/regress/expected/enum.out +++ b/src/test/regress/expected/enum.out @@ -92,7 +92,7 @@ ORDER BY enumlabel::planets; ALTER TYPE planets ADD VALUE 'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto'; ERROR: invalid enum label "plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto" -DETAIL: Labels must be 63 characters or less. +DETAIL: Labels must be 63 bytes or less. ALTER TYPE planets ADD VALUE 'pluto' AFTER 'zeus'; ERROR: "zeus" is not an existing enum label -- if not exists tests diff --git a/src/test/regress/expected/errors.out b/src/test/regress/expected/errors.out index 8122e0de3b4d..a55912b38aa0 100644 --- a/src/test/regress/expected/errors.out +++ b/src/test/regress/expected/errors.out @@ -452,28 +452,3 @@ NULL); ERROR: syntax error at or near "NUL" LINE 16: ...L, id2 TEXT NOT NULL PRIMARY KEY, id3 INTEGER NOT NUL, id4 I... ^ --- Check that stack depth detection mechanism works and --- max_stack_depth is not set too high. The full error report is not --- very stable, so show only SQLSTATE and primary error message. -create function infinite_recurse() returns int as -'select infinite_recurse()' language sql CONTAINS SQL; -\set VERBOSITY sqlstate --- start_matchsubs --- # mpp-2756 --- m/(ERROR|WARNING|CONTEXT|NOTICE):.*stack depth limit exceeded\s+at\s+character/ --- s/\s+at\s+character.*// --- m/ERROR:.*GPDB exception. Aborting Pivotal Optimizer \(GPORCA\).*/ --- s/ERROR:.*GPDB exception. Aborting Pivotal Optimizer \(GPORCA\).*// --- end_matchsubs --- start_ignore -select infinite_recurse(); -ERROR: 54001 --- end_ignore -\echo :LAST_ERROR_MESSAGE -stack depth limit exceeded -select 1; -- test that this works - ?column? ----------- - 1 -(1 row) - diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out index a485f83ce106..4576a1c5418b 100644 --- a/src/test/regress/expected/explain.out +++ b/src/test/regress/expected/explain.out @@ -23,6 +23,9 @@ begin -- Ignore text-mode buffers output because it varies depending -- on the system state CONTINUE WHEN (ln ~ ' +Buffers: .*'); + -- Ignore text-mode "Planning:" line because whether it's output + -- varies depending on the system state + CONTINUE WHEN (ln = 'Planning:'); return next ln; end loop; end; @@ -159,7 +162,6 @@ select explain_filter('explain (analyze, buffers, format json) select * from int }, + "Optimizer": "Postgres query optimizer",+ "Planning": { + - "Planning Time": N.N, + "Shared Hit Blocks": N, + "Shared Read Blocks": N, + "Shared Dirtied Blocks": N, + @@ -171,6 +173,7 @@ select explain_filter('explain (analyze, buffers, format json) select * from int "Temp Read Blocks": N, + "Temp Written Blocks": N + }, + + "Planning Time": N.N, + "Triggers": [ + ], + "Slice statistics": [ + @@ -259,7 +262,6 @@ select explain_filter('explain (analyze, buffers, format xml) select * from int8 + Postgres query optimizer + + - N.N + N + N + N + @@ -271,6 +273,7 @@ select explain_filter('explain (analyze, buffers, format xml) select * from int8 N + N + + + N.N + + + + @@ -353,7 +356,6 @@ select explain_filter('explain (analyze, buffers, format yaml) select * from int Temp Written Blocks: N + Optimizer: "Postgres query optimizer"+ Planning: + - Planning Time: N.N + Shared Hit Blocks: N + Shared Read Blocks: N + Shared Dirtied Blocks: N + @@ -364,6 +366,7 @@ select explain_filter('explain (analyze, buffers, format yaml) select * from int Local Written Blocks: N + Temp Read Blocks: N + Temp Written Blocks: N + + Planning Time: N.N + Triggers: + Slice statistics: + - Slice: N + @@ -378,6 +381,85 @@ select explain_filter('explain (analyze, buffers, format yaml) select * from int Execution Time: N.N (1 row) +select explain_filter('explain (buffers, format text) select * from int8_tbl i8'); + explain_filter +-------------------------------------------------------------------------- + Gather Motion N:N (slice1; segments: N) (cost=N.N..N.N rows=N width=N) + -> Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) + Optimizer: Postgres query optimizer +(3 rows) + +select explain_filter('explain (buffers, format json) select * from int8_tbl i8'); + explain_filter +---------------------------------------------- + [ + + { + + "Plan": { + + "Node Type": "Gather Motion", + + "Senders": N, + + "Receivers": N, + + "Slice": N, + + "Segments": N, + + "Gang Type": "primary reader", + + "Parallel Aware": false, + + "Startup Cost": N.N, + + "Total Cost": N.N, + + "Plan Rows": N, + + "Plan Width": N, + + "Shared Hit Blocks": N, + + "Shared Read Blocks": N, + + "Shared Dirtied Blocks": N, + + "Shared Written Blocks": N, + + "Local Hit Blocks": N, + + "Local Read Blocks": N, + + "Local Dirtied Blocks": N, + + "Local Written Blocks": N, + + "Temp Read Blocks": N, + + "Temp Written Blocks": N, + + "Plans": [ + + { + + "Node Type": "Seq Scan", + + "Parent Relationship": "Outer", + + "Slice": N, + + "Segments": N, + + "Gang Type": "primary reader", + + "Parallel Aware": false, + + "Relation Name": "int8_tbl", + + "Alias": "i8", + + "Startup Cost": N.N, + + "Total Cost": N.N, + + "Plan Rows": N, + + "Plan Width": N, + + "Shared Hit Blocks": N, + + "Shared Read Blocks": N, + + "Shared Dirtied Blocks": N, + + "Shared Written Blocks": N, + + "Local Hit Blocks": N, + + "Local Read Blocks": N, + + "Local Dirtied Blocks": N, + + "Local Written Blocks": N, + + "Temp Read Blocks": N, + + "Temp Written Blocks": N + + } + + ] + + }, + + "Optimizer": "Postgres query optimizer",+ + "Planning": { + + "Shared Hit Blocks": N, + + "Shared Read Blocks": N, + + "Shared Dirtied Blocks": N, + + "Shared Written Blocks": N, + + "Local Hit Blocks": N, + + "Local Read Blocks": N, + + "Local Dirtied Blocks": N, + + "Local Written Blocks": N, + + "Temp Read Blocks": N, + + "Temp Written Blocks": N + + } + + } + + ] +(1 row) + -- SETTINGS option -- We have to ignore other settings that might be imposed by the environment, -- so printing the whole Settings field unfortunately won't do. @@ -594,7 +676,6 @@ select jsonb_pretty( "Shared Written Blocks": 0 + }, + "Planning": { + - "Planning Time": 0.0, + "Local Hit Blocks": 0, + "Temp Read Blocks": 0, + "Local Read Blocks": 0, + @@ -615,6 +696,7 @@ select jsonb_pretty( "Triggers": [ + ], + "Optimizer": "Postgres query optimizer", + + "Planning Time": 0.0, + "Execution Time": 0.0, + "Slice statistics": [ + { + diff --git a/src/test/regress/expected/explain_format.out b/src/test/regress/expected/explain_format.out index d8d68ede7649..662e8c7d8948 100644 --- a/src/test/regress/expected/explain_format.out +++ b/src/test/regress/expected/explain_format.out @@ -525,8 +525,7 @@ explain_filter Actual Rows: N Actual Loops: N Optimizer: "Postgres query optimizer" - Planning: - Planning Time: N.N + Planning Time: N.N Triggers: Slice statistics: - Slice: N @@ -655,7 +654,7 @@ Execution Time: N.N ms -- Check explain analyze json format output with jit enable select explain_filter_to_json('EXPLAIN (ANALYZE, FORMAT json) SELECT * FROM jit_explain_output LIMIT 10;'); explain_filter_to_json -[{"JIT": {"slice": {"slice": 0, "Timing": {"avg": 0.0, "max": 0.0, "segid": 0, "nworker": 0}, "Functions": {"avg": 0.0, "max": 0.0, "segid": 0, "nworker": 0}}, "Options": {"Inlining": false, "Deforming": true, "Expressions": true, "Optimization": false}}, "Plan": {"Plans": [{"Plans": [{"Plans": [{"Alias": "jit_explain_output", "Slice": 0, "Segments": 0, "Gang Type": "primary reader", "Node Type": "Seq Scan", "Plan Rows": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Relation Name": "jit_explain_output", "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0, "Parent Relationship": "Outer"}], "Slice": 0, "Segments": 0, "Gang Type": "primary reader", "Node Type": "Limit", "Plan Rows": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0, "Parent Relationship": "Outer"}], "Slice": 0, "Senders": 0, "Segments": 0, "Gang Type": "primary reader", "Node Type": "Gather Motion", "Plan Rows": 0, "Receivers": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0, "Parent Relationship": "Outer"}], "Slice": 0, "Segments": 0, "Gang Type": "unallocated", "Node Type": "Limit", "Plan Rows": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0}, "Planning": {"Planning Time": 0.0}, "Triggers": [], "Optimizer": "Postgres query optimizer", "Execution Time": 0.0, "Slice statistics": [{"Slice": 0, "Executor Memory": 0}, {"Slice": 0, "Executor Memory": {"Average": 0, "Workers": 0, "Maximum Memory Used": 0}}], "Statement statistics": {"Memory used": 0}}] +[{"JIT": {"slice": {"slice": 0, "Timing": {"avg": 0.0, "max": 0.0, "segid": 0, "nworker": 0}, "Functions": {"avg": 0.0, "max": 0.0, "segid": 0, "nworker": 0}}, "Options": {"Inlining": false, "Deforming": true, "Expressions": true, "Optimization": false}}, "Plan": {"Plans": [{"Plans": [{"Plans": [{"Alias": "jit_explain_output", "Slice": 0, "Segments": 0, "Gang Type": "primary reader", "Node Type": "Seq Scan", "Plan Rows": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Relation Name": "jit_explain_output", "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0, "Parent Relationship": "Outer"}], "Slice": 0, "Segments": 0, "Gang Type": "primary reader", "Node Type": "Limit", "Plan Rows": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0, "Parent Relationship": "Outer"}], "Slice": 0, "Senders": 0, "Segments": 0, "Gang Type": "primary reader", "Node Type": "Gather Motion", "Plan Rows": 0, "Receivers": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0, "Parent Relationship": "Outer"}], "Slice": 0, "Segments": 0, "Gang Type": "unallocated", "Node Type": "Limit", "Plan Rows": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0}, "Triggers": [], "Optimizer": "Postgres query optimizer", "Planning Time": 0.0, "Execution Time": 0.0, "Slice statistics": [{"Slice": 0, "Executor Memory": 0}, {"Slice": 0, "Executor Memory": {"Average": 0, "Workers": 0, "Maximum Memory Used": 0}}], "Statement statistics": {"Memory used": 0}}] (1 row) RESET jit; RESET jit_above_cost; diff --git a/src/test/regress/expected/explain_format_optimizer.out b/src/test/regress/expected/explain_format_optimizer.out index e1e403f8161f..25c57eca7a02 100644 --- a/src/test/regress/expected/explain_format_optimizer.out +++ b/src/test/regress/expected/explain_format_optimizer.out @@ -475,8 +475,7 @@ explain_filter Index Cond: "(id = boxes.apple_id)" Rows Removed by Index Recheck: N Optimizer: "Pivotal Optimizer (GPORCA)" - Planning: - Planning Time: N.N + Planning Time: N.N Triggers: Slice statistics: - Slice: N @@ -600,7 +599,7 @@ Execution Time: N.N ms -- Check explain analyze json format output with jit enable select explain_filter_to_json('EXPLAIN (ANALYZE, FORMAT json) SELECT * FROM jit_explain_output LIMIT 10;'); explain_filter_to_json -[{"JIT": {"slice": {"slice": 0, "Timing": 0.0, "functions": 0.0}, "Options": {"Inlining": false, "Deforming": true, "Expressions": true, "Optimization": false}}, "Plan": {"Plans": [{"Plans": [{"Alias": "jit_explain_output", "Slice": 0, "Segments": 0, "Gang Type": "primary reader", "Node Type": "Seq Scan", "Plan Rows": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Relation Name": "jit_explain_output", "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0, "Parent Relationship": "Outer"}], "Slice": 0, "Senders": 0, "Segments": 0, "Gang Type": "primary reader", "Node Type": "Gather Motion", "Plan Rows": 0, "Receivers": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0, "Parent Relationship": "Outer"}], "Slice": 0, "Segments": 0, "Gang Type": "unallocated", "Node Type": "Limit", "Plan Rows": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0}, "Planning": {"Planning Time": 0.0}, "Triggers": [], "Optimizer": "Pivotal Optimizer (GPORCA)", "Execution Time": 0.0, "Slice statistics": [{"Slice": 0, "Executor Memory": 0}, {"Slice": 0, "Executor Memory": {"Average": 0, "Workers": 0, "Maximum Memory Used": 0}}], "Statement statistics": {"Memory used": 0}}] +[{"JIT": {"slice": {"slice": 0, "Timing": 0.0, "functions": 0.0}, "Options": {"Inlining": false, "Deforming": true, "Expressions": true, "Optimization": false}}, "Plan": {"Plans": [{"Plans": [{"Alias": "jit_explain_output", "Slice": 0, "Segments": 0, "Gang Type": "primary reader", "Node Type": "Seq Scan", "Plan Rows": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Relation Name": "jit_explain_output", "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0, "Parent Relationship": "Outer"}], "Slice": 0, "Senders": 0, "Segments": 0, "Gang Type": "primary reader", "Node Type": "Gather Motion", "Plan Rows": 0, "Receivers": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0, "Parent Relationship": "Outer"}], "Slice": 0, "Segments": 0, "Gang Type": "unallocated", "Node Type": "Limit", "Plan Rows": 0, "Plan Width": 0, "Total Cost": 0.0, "Actual Rows": 0, "Actual Loops": 0, "Startup Cost": 0.0, "Parallel Aware": false, "Actual Total Time": 0.0, "Actual Startup Time": 0.0}, "Triggers": [], "Optimizer": "Pivotal Optimizer (GPORCA)", "Planning Time": 0.0, "Execution Time": 0.0, "Slice statistics": [{"Slice": 0, "Executor Memory": 0}, {"Slice": 0, "Executor Memory": {"Average": 0, "Workers": 0, "Maximum Memory Used": 0}}], "Statement statistics": {"Memory used": 0}}] (1 row) RESET jit; RESET jit_above_cost; diff --git a/src/test/regress/expected/explain_optimizer.out b/src/test/regress/expected/explain_optimizer.out index b159720312b3..e8e2c61bb1c6 100644 --- a/src/test/regress/expected/explain_optimizer.out +++ b/src/test/regress/expected/explain_optimizer.out @@ -23,6 +23,9 @@ begin -- Ignore text-mode buffers output because it varies depending -- on the system state CONTINUE WHEN (ln ~ ' +Buffers: .*'); + -- Ignore text-mode "Planning:" line because whether it's output + -- varies depending on the system state + CONTINUE WHEN (ln = 'Planning:'); return next ln; end loop; end; @@ -158,7 +161,6 @@ select explain_filter('explain (analyze, buffers, format json) select * from int }, + "Optimizer": "Pivotal Optimizer (GPORCA)",+ "Planning": { + - "Planning Time": N.N, + "Shared Hit Blocks": N, + "Shared Read Blocks": N, + "Shared Dirtied Blocks": N, + @@ -170,6 +172,7 @@ select explain_filter('explain (analyze, buffers, format json) select * from int "Temp Read Blocks": N, + "Temp Written Blocks": N + }, + + "Planning Time": N.N, + "Triggers": [ + ], + "Slice statistics": [ + @@ -258,7 +261,6 @@ select explain_filter('explain (analyze, buffers, format xml) select * from int8 + Pivotal Optimizer (GPORCA) + + - N.N + N + N + N + @@ -270,6 +272,7 @@ select explain_filter('explain (analyze, buffers, format xml) select * from int8 N + N + + + N.N + + + + @@ -352,7 +355,6 @@ select explain_filter('explain (analyze, buffers, format yaml) select * from int Temp Written Blocks: N + Optimizer: "Pivotal Optimizer (GPORCA)"+ Planning: + - Planning Time: N.N + Shared Hit Blocks: N + Shared Read Blocks: N + Shared Dirtied Blocks: N + @@ -363,6 +365,7 @@ select explain_filter('explain (analyze, buffers, format yaml) select * from int Local Written Blocks: N + Temp Read Blocks: N + Temp Written Blocks: N + + Planning Time: N.N + Triggers: + Slice statistics: + - Slice: N + @@ -377,6 +380,85 @@ select explain_filter('explain (analyze, buffers, format yaml) select * from int Execution Time: N.N (1 row) +select explain_filter('explain (buffers, format text) select * from int8_tbl i8'); + explain_filter +-------------------------------------------------------------------------- + Gather Motion N:N (slice1; segments: N) (cost=N.N..N.N rows=N width=N) + -> Seq Scan on int8_tbl (cost=N.N..N.N rows=N width=N) + Optimizer: Pivotal Optimizer (GPORCA) +(3 rows) + +select explain_filter('explain (buffers, format json) select * from int8_tbl i8'); + explain_filter +------------------------------------------------ + [ + + { + + "Plan": { + + "Node Type": "Gather Motion", + + "Senders": N, + + "Receivers": N, + + "Slice": N, + + "Segments": N, + + "Gang Type": "primary reader", + + "Parallel Aware": false, + + "Startup Cost": N.N, + + "Total Cost": N.N, + + "Plan Rows": N, + + "Plan Width": N, + + "Shared Hit Blocks": N, + + "Shared Read Blocks": N, + + "Shared Dirtied Blocks": N, + + "Shared Written Blocks": N, + + "Local Hit Blocks": N, + + "Local Read Blocks": N, + + "Local Dirtied Blocks": N, + + "Local Written Blocks": N, + + "Temp Read Blocks": N, + + "Temp Written Blocks": N, + + "Plans": [ + + { + + "Node Type": "Seq Scan", + + "Parent Relationship": "Outer", + + "Slice": N, + + "Segments": N, + + "Gang Type": "primary reader", + + "Parallel Aware": false, + + "Relation Name": "int8_tbl", + + "Alias": "int8_tbl", + + "Startup Cost": N.N, + + "Total Cost": N.N, + + "Plan Rows": N, + + "Plan Width": N, + + "Shared Hit Blocks": N, + + "Shared Read Blocks": N, + + "Shared Dirtied Blocks": N, + + "Shared Written Blocks": N, + + "Local Hit Blocks": N, + + "Local Read Blocks": N, + + "Local Dirtied Blocks": N, + + "Local Written Blocks": N, + + "Temp Read Blocks": N, + + "Temp Written Blocks": N + + } + + ] + + }, + + "Optimizer": "Pivotal Optimizer (GPORCA)",+ + "Planning": { + + "Shared Hit Blocks": N, + + "Shared Read Blocks": N, + + "Shared Dirtied Blocks": N, + + "Shared Written Blocks": N, + + "Local Hit Blocks": N, + + "Local Read Blocks": N, + + "Local Dirtied Blocks": N, + + "Local Written Blocks": N, + + "Temp Read Blocks": N, + + "Temp Written Blocks": N + + } + + } + + ] +(1 row) + -- SETTINGS option -- We have to ignore other settings that might be imposed by the environment, -- so printing the whole Settings field unfortunately won't do. @@ -593,7 +675,6 @@ select jsonb_pretty( "Shared Written Blocks": 0 + }, + "Planning": { + - "Planning Time": 0.0, + "Local Hit Blocks": 0, + "Temp Read Blocks": 0, + "Local Read Blocks": 0, + @@ -613,6 +694,7 @@ select jsonb_pretty( "Triggers": [ + ], + "Optimizer": "Pivotal Optimizer (GPORCA)", + + "Planning Time": 0.0, + "Execution Time": 0.0, + "Slice statistics": [ + { + diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out index 1361d6ef35ca..e603b416ea9a 100644 --- a/src/test/regress/expected/expressions.out +++ b/src/test/regress/expected/expressions.out @@ -127,7 +127,7 @@ select count(*) from date_tbl where f1 not between '1997-01-01' and '1998-01-01'; count ------- - 12 + 13 (1 row) explain (costs off) @@ -167,6 +167,6 @@ select count(*) from date_tbl where f1 not between symmetric '1997-01-01' and '1998-01-01'; count ------- - 12 + 13 (1 row) diff --git a/src/test/regress/expected/expressions_optimizer.out b/src/test/regress/expected/expressions_optimizer.out index 191b107e15a7..8e390e019078 100644 --- a/src/test/regress/expected/expressions_optimizer.out +++ b/src/test/regress/expected/expressions_optimizer.out @@ -125,7 +125,7 @@ select count(*) from date_tbl where f1 not between '1997-01-01' and '1998-01-01'; count ------- - 12 + 13 (1 row) explain (costs off) @@ -163,6 +163,6 @@ select count(*) from date_tbl where f1 not between symmetric '1997-01-01' and '1998-01-01'; count ------- - 12 + 13 (1 row) diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out index 13f83d9da973..0dc2092b00e5 100644 --- a/src/test/regress/expected/generated.out +++ b/src/test/regress/expected/generated.out @@ -820,13 +820,14 @@ CREATE TABLE gtest30 ( b int GENERATED ALWAYS AS (a * 2) STORED ); CREATE TABLE gtest30_1 () INHERITS (gtest30); -ALTER TABLE ONLY gtest30 ALTER COLUMN b DROP EXPRESSION; +ALTER TABLE ONLY gtest30 ALTER COLUMN b DROP EXPRESSION; -- error +ERROR: ALTER TABLE / DROP EXPRESSION must be applied to child tables too \d gtest30 - Table "public.gtest30" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- + Table "public.gtest30" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ a | integer | | | - b | integer | | | + b | integer | | | generated always as (a * 2) stored Number of child tables: 1 (Use \d+ to list them.) \d gtest30_1 diff --git a/src/test/regress/expected/generated_optimizer.out b/src/test/regress/expected/generated_optimizer.out index db7bea1dca15..bc053ffa3246 100644 --- a/src/test/regress/expected/generated_optimizer.out +++ b/src/test/regress/expected/generated_optimizer.out @@ -1121,7 +1121,8 @@ CREATE TABLE gtest30 ( b int GENERATED ALWAYS AS (a * 2) STORED ); CREATE TABLE gtest30_1 () INHERITS (gtest30); -ALTER TABLE ONLY gtest30 ALTER COLUMN b DROP EXPRESSION; +ALTER TABLE ONLY gtest30 ALTER COLUMN b DROP EXPRESSION; -- error +ERROR: ALTER TABLE / DROP EXPRESSION must be applied to child tables too \d gtest30 INFO: GPORCA failed to produce a plan, falling back to planner DETAIL: Feature not supported: Non-default collation @@ -1141,11 +1142,11 @@ INFO: GPORCA failed to produce a plan, falling back to planner DETAIL: Feature not supported: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to planner DETAIL: Feature not supported: Non-default collation - Table "public.gtest30" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- + Table "public.gtest30" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ a | integer | | | - b | integer | | | + b | integer | | | generated always as (a * 2) stored Number of child tables: 1 (Use \d+ to list them.) Distributed by: (a) diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index b550f3f35891..ad8bf9925e95 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -219,6 +219,103 @@ from i @> '{1}' and j @> '{10}' | 2 | 0 | t (10 rows) +reset enable_seqscan; +reset enable_bitmapscan; +-- re-purpose t_gin_test_tbl to test scans involving posting trees +insert into t_gin_test_tbl select array[1, g, g/10], array[2, g, g/10] + from generate_series(1, 20000) g; +select gin_clean_pending_list('t_gin_test_tbl_i_j_idx') is not null; + ?column? +---------- + t + t + t +(3 rows) + +analyze t_gin_test_tbl; +set enable_seqscan = off; +set enable_bitmapscan = on; +explain (costs off) +select count(*) from t_gin_test_tbl where j @> array[50]; + QUERY PLAN +--------------------------------------------------------------------- + Finalize Aggregate + -> Gather Motion 3:1 (slice1; segments: 3) + -> Partial Aggregate + -> Bitmap Heap Scan on t_gin_test_tbl + Recheck Cond: (j @> '{50}'::integer[]) + -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx + Index Cond: (j @> '{50}'::integer[]) + Optimizer: Postgres query optimizer +(8 rows) + +select count(*) from t_gin_test_tbl where j @> array[50]; + count +------- + 11 +(1 row) + +explain (costs off) +select count(*) from t_gin_test_tbl where j @> array[2]; + QUERY PLAN +--------------------------------------------------------------------- + Finalize Aggregate + -> Gather Motion 3:1 (slice1; segments: 3) + -> Partial Aggregate + -> Bitmap Heap Scan on t_gin_test_tbl + Recheck Cond: (j @> '{2}'::integer[]) + -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx + Index Cond: (j @> '{2}'::integer[]) + Optimizer: Postgres query optimizer +(8 rows) + +select count(*) from t_gin_test_tbl where j @> array[2]; + count +------- + 20000 +(1 row) + +explain (costs off) +select count(*) from t_gin_test_tbl where j @> '{}'::int[]; + QUERY PLAN +---------------------------------------------------------------------------------- + Finalize Aggregate + -> Gather Motion 3:1 (slice1; segments: 3) + -> Partial Aggregate + -> Bitmap Heap Scan on t_gin_test_tbl + Recheck Cond: (j OPERATOR(pg_catalog.@>) '{}'::anyarray) + -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx + Index Cond: (j OPERATOR(pg_catalog.@>) '{}'::anyarray) + Optimizer: Postgres query optimizer +(8 rows) + +select count(*) from t_gin_test_tbl where j @> '{}'::int[]; + count +------- + 20006 +(1 row) + +-- test vacuuming of posting trees +delete from t_gin_test_tbl where j @> array[2]; +vacuum t_gin_test_tbl; +select count(*) from t_gin_test_tbl where j @> array[50]; + count +------- + 0 +(1 row) + +select count(*) from t_gin_test_tbl where j @> array[2]; + count +------- + 0 +(1 row) + +select count(*) from t_gin_test_tbl where j @> '{}'::int[]; + count +------- + 6 +(1 row) + reset enable_seqscan; reset enable_bitmapscan; drop table t_gin_test_tbl; diff --git a/src/test/regress/expected/gin_optimizer.out b/src/test/regress/expected/gin_optimizer.out index 3c15bac8c6cf..fdadca31f092 100644 --- a/src/test/regress/expected/gin_optimizer.out +++ b/src/test/regress/expected/gin_optimizer.out @@ -213,6 +213,97 @@ from i @> '{1}' and j @> '{10}' | 2 | 0 | t (10 rows) +reset enable_seqscan; +reset enable_bitmapscan; +-- re-purpose t_gin_test_tbl to test scans involving posting trees +insert into t_gin_test_tbl select array[1, g, g/10], array[2, g, g/10] + from generate_series(1, 20000) g; +select gin_clean_pending_list('t_gin_test_tbl_i_j_idx') is not null; + ?column? +---------- + t + t + t +(3 rows) + +analyze t_gin_test_tbl; +set enable_seqscan = off; +set enable_bitmapscan = on; +explain (costs off) +select count(*) from t_gin_test_tbl where j @> array[50]; + QUERY PLAN +------------------------------------------------------ + Finalize Aggregate + -> Gather Motion 3:1 (slice1; segments: 3) + -> Partial Aggregate + -> Seq Scan on t_gin_test_tbl + Filter: (j @> '{50}'::integer[]) + Optimizer: Pivotal Optimizer (GPORCA) +(6 rows) + +select count(*) from t_gin_test_tbl where j @> array[50]; + count +------- + 11 +(1 row) + +explain (costs off) +select count(*) from t_gin_test_tbl where j @> array[2]; + QUERY PLAN +----------------------------------------------------- + Finalize Aggregate + -> Gather Motion 3:1 (slice1; segments: 3) + -> Partial Aggregate + -> Seq Scan on t_gin_test_tbl + Filter: (j @> '{2}'::integer[]) + Optimizer: Pivotal Optimizer (GPORCA) +(6 rows) + +select count(*) from t_gin_test_tbl where j @> array[2]; + count +------- + 20000 +(1 row) + +explain (costs off) +select count(*) from t_gin_test_tbl where j @> '{}'::int[]; + QUERY PLAN +------------------------------------------------------------------------ + Finalize Aggregate + -> Gather Motion 3:1 (slice1; segments: 3) + -> Partial Aggregate + -> Seq Scan on t_gin_test_tbl + Filter: (j OPERATOR(pg_catalog.@>) '{}'::anyarray) + Optimizer: Pivotal Optimizer (GPORCA) +(6 rows) + +select count(*) from t_gin_test_tbl where j @> '{}'::int[]; + count +------- + 20006 +(1 row) + +-- test vacuuming of posting trees +delete from t_gin_test_tbl where j @> array[2]; +vacuum t_gin_test_tbl; +select count(*) from t_gin_test_tbl where j @> array[50]; + count +------- + 0 +(1 row) + +select count(*) from t_gin_test_tbl where j @> array[2]; + count +------- + 0 +(1 row) + +select count(*) from t_gin_test_tbl where j @> '{}'::int[]; + count +------- + 6 +(1 row) + reset enable_seqscan; reset enable_bitmapscan; drop table t_gin_test_tbl; diff --git a/src/test/regress/expected/gporca.out b/src/test/regress/expected/gporca.out index a69ce1614503..d722f1964c70 100644 --- a/src/test/regress/expected/gporca.out +++ b/src/test/regress/expected/gporca.out @@ -14373,7 +14373,7 @@ from (select (min(i) over (order by j)) as i, j from window_agg_test) tt where t.j = tt.j; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------- - Update on window_agg_test t (cost=3699.81..185385.16 rows=2471070 width=50) + Update on window_agg_test t (cost=3699.81..185385.16 rows=0 width=0) -> Explicit Redistribute Motion 3:3 (slice1; segments: 3) (cost=3699.81..185385.16 rows=2471070 width=50) -> Hash Join (cost=3699.81..135963.76 rows=2471070 width=50) Hash Cond: (tt.j = t.j) diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out index 169244862f8e..0e6f7b242292 100644 --- a/src/test/regress/expected/groupingsets.out +++ b/src/test/regress/expected/groupingsets.out @@ -504,6 +504,52 @@ select x, not x as not_x, q2 from | | 4567890123456789 (5 rows) +-- check qual push-down rules for a subquery with grouping sets +explain (verbose, costs off) +select * from ( + select 1 as x, q1, sum(q2) + from int8_tbl i1 + group by grouping sets(1, 2) +) ss +where x = 1 and q1 = 123; + QUERY PLAN +------------------------------------------------------------------------------------------ + Subquery Scan on ss + Output: ss.x, ss.q1, ss.sum + Filter: ((ss.x = 1) AND (ss.q1 = 123)) + -> Gather Motion 3:1 (slice1; segments: 3) + Output: 1, i1.q1, (sum(i1.q2)) + Merge Key: i1.q1 + -> Finalize GroupAggregate + Output: (1), i1.q1, sum(i1.q2) + Group Key: 1, i1.q1, (GROUPINGSET_ID()) + -> Sort + Output: 1, i1.q1, (PARTIAL sum(i1.q2)), (GROUPINGSET_ID()) + Sort Key: i1.q1, (GROUPINGSET_ID()) + -> Redistribute Motion 3:3 (slice2; segments: 3) + Output: 1, i1.q1, (PARTIAL sum(i1.q2)), (GROUPINGSET_ID()) + Hash Key: 1, i1.q1, (GROUPINGSET_ID()) + -> Partial GroupAggregate + Output: (1), i1.q1, PARTIAL sum(i1.q2), GROUPINGSET_ID() + Group Key: 1 + Sort Key: i1.q1 + Group Key: i1.q1 + -> Seq Scan on public.int8_tbl i1 + Output: 1, i1.q1, i1.q2 + Optimizer: Postgres query optimizer + Settings: enable_hashagg = 'off', optimizer = 'off' +(24 rows) + +select * from ( + select 1 as x, q1, sum(q2) + from int8_tbl i1 + group by grouping sets(1, 2) +) ss +where x = 1 and q1 = 123; + x | q1 | sum +---+----+----- +(0 rows) + -- simple rescan tests select a, b, sum(v.x) from (values (1),(2)) v(x), gstest_data(v.x) @@ -1742,6 +1788,7 @@ select array(select row(v.a,s1.*) from (select two,four, count(*) from onek grou set enable_indexscan = false; set work_mem = '64kB'; WARNING: "work_mem": setting is deprecated, and may be removed in a future release. +set hash_mem_multiplier = 2; explain (costs off) select unique1, count(two), count(four), count(ten), @@ -1790,6 +1837,7 @@ explain (costs off) Optimizer: Postgres query optimizer (12 rows) +reset hash_mem_multiplier; set work_mem = '384kB'; WARNING: "work_mem": setting is deprecated, and may be removed in a future release. explain (costs off) diff --git a/src/test/regress/expected/groupingsets_optimizer.out b/src/test/regress/expected/groupingsets_optimizer.out index d8edd5da00a7..44b1cff9a817 100644 --- a/src/test/regress/expected/groupingsets_optimizer.out +++ b/src/test/regress/expected/groupingsets_optimizer.out @@ -541,6 +541,33 @@ select x, not x as not_x, q2 from | | 4567890123456789 (5 rows) +-- check qual push-down rules for a subquery with grouping sets +explain (verbose, costs off) +select * from ( + select 1 as x, q1, sum(q2) + from int8_tbl i1 + group by grouping sets(1, 2) +) ss +where x = 1 and q1 = 123; + QUERY PLAN +------------------------------------------------------ + Result + Output: NULL::integer, NULL::bigint, NULL::numeric + One-Time Filter: false + Optimizer: Pivotal Optimizer (GPORCA) + Settings: enable_hashagg = 'off' +(5 rows) + +select * from ( + select 1 as x, q1, sum(q2) + from int8_tbl i1 + group by grouping sets(1, 2) +) ss +where x = 1 and q1 = 123; + x | q1 | sum +---+----+----- +(0 rows) + -- simple rescan tests select a, b, sum(v.x) from (values (1),(2)) v(x), gstest_data(v.x) @@ -1936,6 +1963,7 @@ DETAIL: Feature not supported: ROW EXPRESSION set enable_indexscan = false; set work_mem = '64kB'; WARNING: "work_mem": setting is deprecated, and may be removed in a future release. +set hash_mem_multiplier = 2; explain (costs off) select unique1, count(two), count(four), count(ten), @@ -2047,6 +2075,7 @@ explain (costs off) Optimizer: Pivotal Optimizer (GPORCA) (39 rows) +reset hash_mem_multiplier; set work_mem = '384kB'; WARNING: "work_mem": setting is deprecated, and may be removed in a future release. explain (costs off) diff --git a/src/test/regress/expected/hash_func.out b/src/test/regress/expected/hash_func.out index da0948e95a93..e6e3410aaa18 100644 --- a/src/test/regress/expected/hash_func.out +++ b/src/test/regress/expected/hash_func.out @@ -16,8 +16,8 @@ WHERE hashint2(v)::bit(32) != hashint2extended(v, 0)::bit(32) (0 rows) SELECT v as value, hashint4(v)::bit(32) as standard, - hashint4extended(v, 0)::bit(32) as extended0, - hashint4extended(v, 1)::bit(32) as extended1 + hashint4extended(v, 0)::bit(32) as extended0, + hashint4extended(v, 1)::bit(32) as extended1 FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) WHERE hashint4(v)::bit(32) != hashint4extended(v, 0)::bit(32) OR hashint4(v)::bit(32) = hashint4extended(v, 1)::bit(32); @@ -26,8 +26,8 @@ WHERE hashint4(v)::bit(32) != hashint4extended(v, 0)::bit(32) (0 rows) SELECT v as value, hashint8(v)::bit(32) as standard, - hashint8extended(v, 0)::bit(32) as extended0, - hashint8extended(v, 1)::bit(32) as extended1 + hashint8extended(v, 0)::bit(32) as extended0, + hashint8extended(v, 1)::bit(32) as extended1 FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) WHERE hashint8(v)::bit(32) != hashint8extended(v, 0)::bit(32) OR hashint8(v)::bit(32) = hashint8extended(v, 1)::bit(32); @@ -36,8 +36,8 @@ WHERE hashint8(v)::bit(32) != hashint8extended(v, 0)::bit(32) (0 rows) SELECT v as value, hashfloat4(v)::bit(32) as standard, - hashfloat4extended(v, 0)::bit(32) as extended0, - hashfloat4extended(v, 1)::bit(32) as extended1 + hashfloat4extended(v, 0)::bit(32) as extended0, + hashfloat4extended(v, 1)::bit(32) as extended1 FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) WHERE hashfloat4(v)::bit(32) != hashfloat4extended(v, 0)::bit(32) OR hashfloat4(v)::bit(32) = hashfloat4extended(v, 1)::bit(32); @@ -46,8 +46,8 @@ WHERE hashfloat4(v)::bit(32) != hashfloat4extended(v, 0)::bit(32) (0 rows) SELECT v as value, hashfloat8(v)::bit(32) as standard, - hashfloat8extended(v, 0)::bit(32) as extended0, - hashfloat8extended(v, 1)::bit(32) as extended1 + hashfloat8extended(v, 0)::bit(32) as extended0, + hashfloat8extended(v, 1)::bit(32) as extended1 FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) WHERE hashfloat8(v)::bit(32) != hashfloat8extended(v, 0)::bit(32) OR hashfloat8(v)::bit(32) = hashfloat8extended(v, 1)::bit(32); @@ -56,8 +56,8 @@ WHERE hashfloat8(v)::bit(32) != hashfloat8extended(v, 0)::bit(32) (0 rows) SELECT v as value, hashoid(v)::bit(32) as standard, - hashoidextended(v, 0)::bit(32) as extended0, - hashoidextended(v, 1)::bit(32) as extended1 + hashoidextended(v, 0)::bit(32) as extended0, + hashoidextended(v, 1)::bit(32) as extended1 FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) WHERE hashoid(v)::bit(32) != hashoidextended(v, 0)::bit(32) OR hashoid(v)::bit(32) = hashoidextended(v, 1)::bit(32); @@ -66,8 +66,8 @@ WHERE hashoid(v)::bit(32) != hashoidextended(v, 0)::bit(32) (0 rows) SELECT v as value, hashchar(v)::bit(32) as standard, - hashcharextended(v, 0)::bit(32) as extended0, - hashcharextended(v, 1)::bit(32) as extended1 + hashcharextended(v, 0)::bit(32) as extended0, + hashcharextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::"char"), ('1'), ('x'), ('X'), ('p'), ('N')) x(v) WHERE hashchar(v)::bit(32) != hashcharextended(v, 0)::bit(32) OR hashchar(v)::bit(32) = hashcharextended(v, 1)::bit(32); @@ -76,10 +76,10 @@ WHERE hashchar(v)::bit(32) != hashcharextended(v, 0)::bit(32) (0 rows) SELECT v as value, hashname(v)::bit(32) as standard, - hashnameextended(v, 0)::bit(32) as extended0, - hashnameextended(v, 1)::bit(32) as extended1 + hashnameextended(v, 0)::bit(32) as extended0, + hashnameextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL), ('PostgreSQL'), ('eIpUEtqmY89'), ('AXKEJBTK'), - ('muop28x03'), ('yi3nm0d73')) x(v) + ('muop28x03'), ('yi3nm0d73')) x(v) WHERE hashname(v)::bit(32) != hashnameextended(v, 0)::bit(32) OR hashname(v)::bit(32) = hashnameextended(v, 1)::bit(32); value | standard | extended0 | extended1 @@ -87,10 +87,10 @@ WHERE hashname(v)::bit(32) != hashnameextended(v, 0)::bit(32) (0 rows) SELECT v as value, hashtext(v)::bit(32) as standard, - hashtextextended(v, 0)::bit(32) as extended0, - hashtextextended(v, 1)::bit(32) as extended1 + hashtextextended(v, 0)::bit(32) as extended0, + hashtextextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL), ('PostgreSQL'), ('eIpUEtqmY89'), ('AXKEJBTK'), - ('muop28x03'), ('yi3nm0d73')) x(v) + ('muop28x03'), ('yi3nm0d73')) x(v) WHERE hashtext(v)::bit(32) != hashtextextended(v, 0)::bit(32) OR hashtext(v)::bit(32) = hashtextextended(v, 1)::bit(32); value | standard | extended0 | extended1 @@ -98,8 +98,8 @@ WHERE hashtext(v)::bit(32) != hashtextextended(v, 0)::bit(32) (0 rows) SELECT v as value, hashoidvector(v)::bit(32) as standard, - hashoidvectorextended(v, 0)::bit(32) as extended0, - hashoidvectorextended(v, 1)::bit(32) as extended1 + hashoidvectorextended(v, 0)::bit(32) as extended0, + hashoidvectorextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::oidvector), ('0 1 2 3 4'), ('17 18 19 20'), ('42 43 42 45'), ('550273 550273 570274'), ('207112489 207112499 21512 2155 372325 1363252')) x(v) @@ -110,8 +110,8 @@ WHERE hashoidvector(v)::bit(32) != hashoidvectorextended(v, 0)::bit(32) (0 rows) SELECT v as value, hash_aclitem(v)::bit(32) as standard, - hash_aclitem_extended(v, 0)::bit(32) as extended0, - hash_aclitem_extended(v, 1)::bit(32) as extended1 + hash_aclitem_extended(v, 0)::bit(32) as extended0, + hash_aclitem_extended(v, 1)::bit(32) as extended1 FROM (SELECT DISTINCT(relacl[1]) FROM pg_class LIMIT 10) x(v) WHERE hash_aclitem(v)::bit(32) != hash_aclitem_extended(v, 0)::bit(32) OR hash_aclitem(v)::bit(32) = hash_aclitem_extended(v, 1)::bit(32); @@ -120,10 +120,10 @@ WHERE hash_aclitem(v)::bit(32) != hash_aclitem_extended(v, 0)::bit(32) (0 rows) SELECT v as value, hashmacaddr(v)::bit(32) as standard, - hashmacaddrextended(v, 0)::bit(32) as extended0, - hashmacaddrextended(v, 1)::bit(32) as extended1 + hashmacaddrextended(v, 0)::bit(32) as extended0, + hashmacaddrextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::macaddr), ('08:00:2b:01:02:04'), ('08:00:2b:01:02:04'), - ('e2:7f:51:3e:70:49'), ('d6:a9:4a:78:1c:d5'), + ('e2:7f:51:3e:70:49'), ('d6:a9:4a:78:1c:d5'), ('ea:29:b1:5e:1f:a5')) x(v) WHERE hashmacaddr(v)::bit(32) != hashmacaddrextended(v, 0)::bit(32) OR hashmacaddr(v)::bit(32) = hashmacaddrextended(v, 1)::bit(32); @@ -132,10 +132,10 @@ WHERE hashmacaddr(v)::bit(32) != hashmacaddrextended(v, 0)::bit(32) (0 rows) SELECT v as value, hashinet(v)::bit(32) as standard, - hashinetextended(v, 0)::bit(32) as extended0, - hashinetextended(v, 1)::bit(32) as extended1 + hashinetextended(v, 0)::bit(32) as extended0, + hashinetextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::inet), ('192.168.100.128/25'), ('192.168.100.0/8'), - ('172.168.10.126/16'), ('172.18.103.126/24'), ('192.188.13.16/32')) x(v) + ('172.168.10.126/16'), ('172.18.103.126/24'), ('192.188.13.16/32')) x(v) WHERE hashinet(v)::bit(32) != hashinetextended(v, 0)::bit(32) OR hashinet(v)::bit(32) = hashinetextended(v, 1)::bit(32); value | standard | extended0 | extended1 @@ -143,8 +143,8 @@ WHERE hashinet(v)::bit(32) != hashinetextended(v, 0)::bit(32) (0 rows) SELECT v as value, hash_numeric(v)::bit(32) as standard, - hash_numeric_extended(v, 0)::bit(32) as extended0, - hash_numeric_extended(v, 1)::bit(32) as extended1 + hash_numeric_extended(v, 0)::bit(32) as extended0, + hash_numeric_extended(v, 1)::bit(32) as extended1 FROM (VALUES (0), (1.149484958), (17.149484958), (42.149484958), (149484958.550273), (2071124898672)) x(v) WHERE hash_numeric(v)::bit(32) != hash_numeric_extended(v, 0)::bit(32) @@ -154,8 +154,8 @@ WHERE hash_numeric(v)::bit(32) != hash_numeric_extended(v, 0)::bit(32) (0 rows) SELECT v as value, hashmacaddr8(v)::bit(32) as standard, - hashmacaddr8extended(v, 0)::bit(32) as extended0, - hashmacaddr8extended(v, 1)::bit(32) as extended1 + hashmacaddr8extended(v, 0)::bit(32) as extended0, + hashmacaddr8extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::macaddr8), ('08:00:2b:01:02:04:36:49'), ('08:00:2b:01:02:04:f0:e8'), ('e2:7f:51:3e:70:49:16:29'), ('d6:a9:4a:78:1c:d5:47:32'), ('ea:29:b1:5e:1f:a5')) x(v) @@ -166,8 +166,8 @@ WHERE hashmacaddr8(v)::bit(32) != hashmacaddr8extended(v, 0)::bit(32) (0 rows) SELECT v as value, hash_array(v)::bit(32) as standard, - hash_array_extended(v, 0)::bit(32) as extended0, - hash_array_extended(v, 1)::bit(32) as extended1 + hash_array_extended(v, 0)::bit(32) as extended0, + hash_array_extended(v, 1)::bit(32) as extended1 FROM (VALUES ('{0}'::int4[]), ('{0,1,2,3,4}'), ('{17,18,19,20}'), ('{42,34,65,98}'), ('{550273,590027, 870273}'), ('{207112489, 807112489}')) x(v) @@ -178,10 +178,10 @@ WHERE hash_array(v)::bit(32) != hash_array_extended(v, 0)::bit(32) (0 rows) SELECT v as value, hashbpchar(v)::bit(32) as standard, - hashbpcharextended(v, 0)::bit(32) as extended0, - hashbpcharextended(v, 1)::bit(32) as extended1 + hashbpcharextended(v, 0)::bit(32) as extended0, + hashbpcharextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL), ('PostgreSQL'), ('eIpUEtqmY89'), ('AXKEJBTK'), - ('muop28x03'), ('yi3nm0d73')) x(v) + ('muop28x03'), ('yi3nm0d73')) x(v) WHERE hashbpchar(v)::bit(32) != hashbpcharextended(v, 0)::bit(32) OR hashbpchar(v)::bit(32) = hashbpcharextended(v, 1)::bit(32); value | standard | extended0 | extended1 @@ -189,8 +189,8 @@ WHERE hashbpchar(v)::bit(32) != hashbpcharextended(v, 0)::bit(32) (0 rows) SELECT v as value, time_hash(v)::bit(32) as standard, - time_hash_extended(v, 0)::bit(32) as extended0, - time_hash_extended(v, 1)::bit(32) as extended1 + time_hash_extended(v, 0)::bit(32) as extended0, + time_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::time), ('11:09:59'), ('1:09:59'), ('11:59:59'), ('7:9:59'), ('5:15:59')) x(v) WHERE time_hash(v)::bit(32) != time_hash_extended(v, 0)::bit(32) @@ -200,10 +200,10 @@ WHERE time_hash(v)::bit(32) != time_hash_extended(v, 0)::bit(32) (0 rows) SELECT v as value, timetz_hash(v)::bit(32) as standard, - timetz_hash_extended(v, 0)::bit(32) as extended0, - timetz_hash_extended(v, 1)::bit(32) as extended1 + timetz_hash_extended(v, 0)::bit(32) as extended0, + timetz_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::timetz), ('00:11:52.518762-07'), ('00:11:52.51762-08'), - ('00:11:52.62-01'), ('00:11:52.62+01'), ('11:59:59+04')) x(v) + ('00:11:52.62-01'), ('00:11:52.62+01'), ('11:59:59+04')) x(v) WHERE timetz_hash(v)::bit(32) != timetz_hash_extended(v, 0)::bit(32) OR timetz_hash(v)::bit(32) = timetz_hash_extended(v, 1)::bit(32); value | standard | extended0 | extended1 @@ -211,12 +211,12 @@ WHERE timetz_hash(v)::bit(32) != timetz_hash_extended(v, 0)::bit(32) (0 rows) SELECT v as value, interval_hash(v)::bit(32) as standard, - interval_hash_extended(v, 0)::bit(32) as extended0, - interval_hash_extended(v, 1)::bit(32) as extended1 + interval_hash_extended(v, 0)::bit(32) as extended0, + interval_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::interval), ('5 month 7 day 46 minutes'), ('1 year 7 day 46 minutes'), - ('1 year 7 month 20 day 46 minutes'), ('5 month'), - ('17 year 11 month 7 day 9 hours 46 minutes 5 seconds')) x(v) + ('1 year 7 month 20 day 46 minutes'), ('5 month'), + ('17 year 11 month 7 day 9 hours 46 minutes 5 seconds')) x(v) WHERE interval_hash(v)::bit(32) != interval_hash_extended(v, 0)::bit(32) OR interval_hash(v)::bit(32) = interval_hash_extended(v, 1)::bit(32); value | standard | extended0 | extended1 @@ -224,11 +224,11 @@ WHERE interval_hash(v)::bit(32) != interval_hash_extended(v, 0)::bit(32) (0 rows) SELECT v as value, timestamp_hash(v)::bit(32) as standard, - timestamp_hash_extended(v, 0)::bit(32) as extended0, - timestamp_hash_extended(v, 1)::bit(32) as extended1 + timestamp_hash_extended(v, 0)::bit(32) as extended0, + timestamp_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::timestamp), ('2017-08-22 00:09:59.518762'), ('2015-08-20 00:11:52.51762-08'), - ('2017-05-22 00:11:52.62-01'), + ('2017-05-22 00:11:52.62-01'), ('2013-08-22 00:11:52.62+01'), ('2013-08-22 11:59:59+04')) x(v) WHERE timestamp_hash(v)::bit(32) != timestamp_hash_extended(v, 0)::bit(32) OR timestamp_hash(v)::bit(32) = timestamp_hash_extended(v, 1)::bit(32); @@ -237,12 +237,12 @@ WHERE timestamp_hash(v)::bit(32) != timestamp_hash_extended(v, 0)::bit(32) (0 rows) SELECT v as value, uuid_hash(v)::bit(32) as standard, - uuid_hash_extended(v, 0)::bit(32) as extended0, - uuid_hash_extended(v, 1)::bit(32) as extended1 + uuid_hash_extended(v, 0)::bit(32) as extended0, + uuid_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::uuid), ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'), - ('5a9ba4ac-8d6f-11e7-bb31-be2e44b06b34'), + ('5a9ba4ac-8d6f-11e7-bb31-be2e44b06b34'), ('99c6705c-d939-461c-a3c9-1690ad64ed7b'), - ('7deed3ca-8d6f-11e7-bb31-be2e44b06b34'), + ('7deed3ca-8d6f-11e7-bb31-be2e44b06b34'), ('9ad46d4f-6f2a-4edd-aadb-745993928e1e')) x(v) WHERE uuid_hash(v)::bit(32) != uuid_hash_extended(v, 0)::bit(32) OR uuid_hash(v)::bit(32) = uuid_hash_extended(v, 1)::bit(32); @@ -251,10 +251,10 @@ WHERE uuid_hash(v)::bit(32) != uuid_hash_extended(v, 0)::bit(32) (0 rows) SELECT v as value, pg_lsn_hash(v)::bit(32) as standard, - pg_lsn_hash_extended(v, 0)::bit(32) as extended0, - pg_lsn_hash_extended(v, 1)::bit(32) as extended1 + pg_lsn_hash_extended(v, 0)::bit(32) as extended0, + pg_lsn_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::pg_lsn), ('16/B374D84'), ('30/B374D84'), - ('255/B374D84'), ('25/B379D90'), ('900/F37FD90')) x(v) + ('255/B374D84'), ('25/B379D90'), ('900/F37FD90')) x(v) WHERE pg_lsn_hash(v)::bit(32) != pg_lsn_hash_extended(v, 0)::bit(32) OR pg_lsn_hash(v)::bit(32) = pg_lsn_hash_extended(v, 1)::bit(32); value | standard | extended0 | extended1 @@ -263,8 +263,8 @@ WHERE pg_lsn_hash(v)::bit(32) != pg_lsn_hash_extended(v, 0)::bit(32) CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); SELECT v as value, hashenum(v)::bit(32) as standard, - hashenumextended(v, 0)::bit(32) as extended0, - hashenumextended(v, 1)::bit(32) as extended1 + hashenumextended(v, 0)::bit(32) as extended0, + hashenumextended(v, 1)::bit(32) as extended1 FROM (VALUES ('sad'::mood), ('ok'), ('happy')) x(v) WHERE hashenum(v)::bit(32) != hashenumextended(v, 0)::bit(32) OR hashenum(v)::bit(32) = hashenumextended(v, 1)::bit(32); @@ -274,12 +274,12 @@ WHERE hashenum(v)::bit(32) != hashenumextended(v, 0)::bit(32) DROP TYPE mood; SELECT v as value, jsonb_hash(v)::bit(32) as standard, - jsonb_hash_extended(v, 0)::bit(32) as extended0, - jsonb_hash_extended(v, 1)::bit(32) as extended1 + jsonb_hash_extended(v, 0)::bit(32) as extended0, + jsonb_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::jsonb), - ('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'), - ('{"foo": [true, "bar"], "tags": {"e": 1, "f": null}}'), - ('{"g": {"h": "value"}}')) x(v) + ('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'), + ('{"foo": [true, "bar"], "tags": {"e": 1, "f": null}}'), + ('{"g": {"h": "value"}}')) x(v) WHERE jsonb_hash(v)::bit(32) != jsonb_hash_extended(v, 0)::bit(32) OR jsonb_hash(v)::bit(32) = jsonb_hash_extended(v, 1)::bit(32); value | standard | extended0 | extended1 @@ -287,11 +287,11 @@ WHERE jsonb_hash(v)::bit(32) != jsonb_hash_extended(v, 0)::bit(32) (0 rows) SELECT v as value, hash_range(v)::bit(32) as standard, - hash_range_extended(v, 0)::bit(32) as extended0, - hash_range_extended(v, 1)::bit(32) as extended1 + hash_range_extended(v, 0)::bit(32) as extended0, + hash_range_extended(v, 1)::bit(32) as extended1 FROM (VALUES (int4range(10, 20)), (int4range(23, 43)), - (int4range(5675, 550273)), - (int4range(550274, 1550274)), (int4range(1550275, 208112489))) x(v) + (int4range(5675, 550273)), + (int4range(550274, 1550274)), (int4range(1550275, 208112489))) x(v) WHERE hash_range(v)::bit(32) != hash_range_extended(v, 0)::bit(32) OR hash_range(v)::bit(32) = hash_range_extended(v, 1)::bit(32); value | standard | extended0 | extended1 diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out index d70efb3280df..14b57e319daf 100644 --- a/src/test/regress/expected/horology.out +++ b/src/test/regress/expected/horology.out @@ -188,6 +188,12 @@ reset datestyle; -- -- SET DateStyle = 'Postgres, MDY'; +SHOW TimeZone; -- Many of these tests depend on the prevailing setting + TimeZone +---------- + PST8PDT +(1 row) + -- -- Test various input formats -- @@ -2263,6 +2269,72 @@ SELECT '' AS "16", f1 AS "timestamp", date(f1) AS date DROP TABLE TEMP_TIMESTAMP; -- +-- Comparisons between datetime types, especially overflow cases +--- +SELECT '2202020-10-05'::date::timestamp; -- fail +ERROR: date out of range for timestamp +SELECT '2202020-10-05'::date > '2020-10-05'::timestamp as t; + t +--- + t +(1 row) + +SELECT '2020-10-05'::timestamp > '2202020-10-05'::date as f; + f +--- + f +(1 row) + +SELECT '2202020-10-05'::date::timestamptz; -- fail +ERROR: date out of range for timestamp +SELECT '2202020-10-05'::date > '2020-10-05'::timestamptz as t; + t +--- + t +(1 row) + +SELECT '2020-10-05'::timestamptz > '2202020-10-05'::date as f; + f +--- + f +(1 row) + +-- This conversion may work depending on timezone +SELECT '4714-11-24 BC'::date::timestamptz; + timestamptz +--------------------------------- + Mon Nov 24 00:00:00 4714 PST BC +(1 row) + +SET TimeZone = 'UTC-2'; +SELECT '4714-11-24 BC'::date::timestamptz; -- fail +ERROR: date out of range for timestamp +SELECT '4714-11-24 BC'::date < '2020-10-05'::timestamptz as t; + t +--- + t +(1 row) + +SELECT '2020-10-05'::timestamptz >= '4714-11-24 BC'::date as t; + t +--- + t +(1 row) + +SELECT '4714-11-24 BC'::timestamp < '2020-10-05'::timestamptz as t; + t +--- + t +(1 row) + +SELECT '2020-10-05'::timestamptz >= '4714-11-24 BC'::timestamp as t; + t +--- + t +(1 row) + +RESET TimeZone; +-- -- Formats -- SET DateStyle TO 'US,Postgres'; @@ -3103,6 +3175,45 @@ SELECT to_date('2458872', 'J'); 01-23-2020 (1 row) +-- +-- Check handling of BC dates +-- +SELECT to_date('44-02-01 BC','YYYY-MM-DD BC'); + to_date +--------------- + 02-01-0044 BC +(1 row) + +SELECT to_date('-44-02-01','YYYY-MM-DD'); + to_date +--------------- + 02-01-0044 BC +(1 row) + +SELECT to_date('-44-02-01 BC','YYYY-MM-DD BC'); + to_date +------------ + 02-01-0044 +(1 row) + +SELECT to_timestamp('44-02-01 11:12:13 BC','YYYY-MM-DD HH24:MI:SS BC'); + to_timestamp +--------------------------------- + Fri Feb 01 11:12:13 0044 PST BC +(1 row) + +SELECT to_timestamp('-44-02-01 11:12:13','YYYY-MM-DD HH24:MI:SS'); + to_timestamp +--------------------------------- + Fri Feb 01 11:12:13 0044 PST BC +(1 row) + +SELECT to_timestamp('-44-02-01 11:12:13 BC','YYYY-MM-DD HH24:MI:SS BC'); + to_timestamp +------------------------------ + Mon Feb 01 11:12:13 0044 PST +(1 row) + -- -- Check handling of multiple spaces in format and/or input -- @@ -3375,6 +3486,12 @@ SELECT to_date('2016 366', 'YYYY DDD'); -- ok SELECT to_date('2016 367', 'YYYY DDD'); ERROR: date/time field value out of range: "2016 367" +SELECT to_date('0000-02-01','YYYY-MM-DD'); -- allowed, though it shouldn't be + to_date +--------------- + 02-01-0001 BC +(1 row) + -- -- Check behavior with SQL-style fixed-GMT-offset time zone (cf bug #8572) -- diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out index 56b0463743d9..55bb0df90d18 100644 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -335,7 +335,7 @@ SELECT * FROM itest6; 102 | (3 rows) -SELECT table_name, column_name, is_identity, identity_generation FROM information_schema.columns WHERE table_name = 'itest6'; +SELECT table_name, column_name, is_identity, identity_generation FROM information_schema.columns WHERE table_name = 'itest6' ORDER BY 1, 2; table_name | column_name | is_identity | identity_generation ------------+-------------+-------------+--------------------- itest6 | a | YES | BY DEFAULT diff --git a/src/test/regress/expected/incremental_analyze.out b/src/test/regress/expected/incremental_analyze.out index 43f7c3c6ae9c..8e1e2be2e213 100644 --- a/src/test/regress/expected/incremental_analyze.out +++ b/src/test/regress/expected/incremental_analyze.out @@ -1893,7 +1893,7 @@ SELECT tablename, attname, null_frac, n_distinct, most_common_vals, most_common_ SELECT relname, relpages, reltuples FROM pg_class WHERE relname LIKE 'incr_analyze_test%' ORDER BY relname; relname | relpages | reltuples ---------------------------+----------+----------- - incr_analyze_test | 0 | 0 + incr_analyze_test | 0 | -1 incr_analyze_test_1_prt_1 | 1 | 1 incr_analyze_test_1_prt_2 | 2 | 1000 incr_analyze_test_1_prt_3 | 1 | 0 diff --git a/src/test/regress/expected/incremental_analyze_optimizer.out b/src/test/regress/expected/incremental_analyze_optimizer.out index 96c59715a298..cf8c89578016 100644 --- a/src/test/regress/expected/incremental_analyze_optimizer.out +++ b/src/test/regress/expected/incremental_analyze_optimizer.out @@ -1902,7 +1902,7 @@ SELECT tablename, attname, null_frac, n_distinct, most_common_vals, most_common_ SELECT relname, relpages, reltuples FROM pg_class WHERE relname LIKE 'incr_analyze_test%' ORDER BY relname; relname | relpages | reltuples ---------------------------+----------+----------- - incr_analyze_test | 0 | 0 + incr_analyze_test | 0 | -1 incr_analyze_test_1_prt_1 | 1 | 1 incr_analyze_test_1_prt_2 | 2 | 1000 incr_analyze_test_1_prt_3 | 1 | 0 diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out index e376ea127617..7cf2eee7c14c 100644 --- a/src/test/regress/expected/incremental_sort.out +++ b/src/test/regress/expected/incremental_sort.out @@ -1469,3 +1469,101 @@ explain (costs off) select * from t union select * from t order by 1,3; (13 rows) drop table t; +-- Sort pushdown can't go below where expressions are part of the rel target. +-- In particular this is interesting for volatile expressions which have to +-- go above joins since otherwise we'll incorrectly use expression evaluations +-- across multiple rows. +set enable_hashagg=off; +set enable_seqscan=off; +set enable_incremental_sort = off; +set parallel_tuple_cost=0; +set parallel_setup_cost=0; +set min_parallel_table_scan_size = 0; +set min_parallel_index_scan_size = 0; +-- Parallel sort below join. +explain (costs off) select distinct sub.unique1, stringu1 +from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub; + QUERY PLAN +-------------------------------------------------------------------------- + Unique + -> Nested Loop + -> Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: tenk1.unique1, tenk1.stringu1 + -> Parallel Index Scan using tenk1_unique1 on tenk1 + -> Function Scan on generate_series +(8 rows) + +explain (costs off) select sub.unique1, stringu1 +from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub +order by 1, 2; + QUERY PLAN +-------------------------------------------------------------------- + Nested Loop + -> Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: tenk1.unique1, tenk1.stringu1 + -> Parallel Index Scan using tenk1_unique1 on tenk1 + -> Function Scan on generate_series +(7 rows) + +-- Parallel sort but with expression that can be safely generated at the base rel. +explain (costs off) select distinct sub.unique1, md5(stringu1) +from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub; + QUERY PLAN +---------------------------------------------------------------------------------------- + Unique + -> Nested Loop + -> Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: tenk1.unique1, (md5((tenk1.stringu1)::text)) COLLATE "C" + -> Parallel Index Scan using tenk1_unique1 on tenk1 + -> Function Scan on generate_series +(8 rows) + +explain (costs off) select sub.unique1, md5(stringu1) +from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub +order by 1, 2; + QUERY PLAN +---------------------------------------------------------------------------------- + Nested Loop + -> Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: tenk1.unique1, (md5((tenk1.stringu1)::text)) COLLATE "C" + -> Parallel Index Scan using tenk1_unique1 on tenk1 + -> Function Scan on generate_series +(7 rows) + +-- Parallel sort but with expression not available until the upper rel. +explain (costs off) select distinct sub.unique1, stringu1 || random()::text +from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Unique + -> Sort + Sort Key: tenk1.unique1, (((tenk1.stringu1)::text || (random())::text)) COLLATE "C" + -> Gather + Workers Planned: 2 + -> Nested Loop + -> Parallel Index Scan using tenk1_unique1 on tenk1 + -> Function Scan on generate_series +(8 rows) + +explain (costs off) select sub.unique1, stringu1 || random()::text +from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub +order by 1, 2; + QUERY PLAN +--------------------------------------------------------------------------------------- + Sort + Sort Key: tenk1.unique1, (((tenk1.stringu1)::text || (random())::text)) COLLATE "C" + -> Gather + Workers Planned: 2 + -> Nested Loop + -> Parallel Index Scan using tenk1_unique1 on tenk1 + -> Function Scan on generate_series +(7 rows) + diff --git a/src/test/regress/expected/index_constraint_naming_partition.out b/src/test/regress/expected/index_constraint_naming_partition.out index a934d8c406ca..0ca5ad08dc9b 100755 --- a/src/test/regress/expected/index_constraint_naming_partition.out +++ b/src/test/regress/expected/index_constraint_naming_partition.out @@ -45,7 +45,8 @@ NOTICE: function dependencies() does not exist, skipping CREATE FUNCTION dependencies() RETURNS TABLE( depname NAME, classtype "char", depnsoid OID, refname NAME, refclasstype "char", refnsoid OID, classid REGCLASS, objid OID, objsubid INTEGER, - refclassid REGCLASS, refobjid OID, refobjsubid OID, deptype "char" ) + refclassid REGCLASS, refobjid OID, refobjsubid OID, + deptype "char", refobjversion "text" ) LANGUAGE SQL STABLE STRICT AS $fn$ WITH RECURSIVE w AS ( @@ -55,7 +56,8 @@ WITH RECURSIVE refclassid::regclass, refobjid, refobjsubid, - deptype + deptype, + refobjversion FROM pg_depend d WHERE classid IN ('pg_constraint'::regclass, 'pg_class'::regclass) AND (objid > 16384 OR refobjid > 16384) diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out index 92ad6751c52e..73731c17d072 100644 --- a/src/test/regress/expected/indexing.out +++ b/src/test/regress/expected/indexing.out @@ -180,6 +180,8 @@ create table idxpart1 partition of idxpart for values from (0) to (10); drop index idxpart1_a_idx; -- no way ERROR: cannot drop index idxpart1_a_idx because index idxpart_a_idx requires it HINT: You can drop index idxpart_a_idx instead. +drop index concurrently idxpart_a_idx; -- unsupported +ERROR: cannot drop partitioned index "idxpart_a_idx" concurrently drop index idxpart_a_idx; -- both indexes go away select relname, relkind from pg_class where relname like 'idxpart%' order by relname; @@ -200,6 +202,24 @@ select relname, relkind from pg_class (2 rows) drop table idxpart; +-- DROP behavior with temporary partitioned indexes +create temp table idxpart_temp (a int) partition by range (a); +create index on idxpart_temp(a); +create temp table idxpart1_temp partition of idxpart_temp + for values from (0) to (10); +drop index idxpart1_temp_a_idx; -- error +ERROR: cannot drop index idxpart1_temp_a_idx because index idxpart_temp_a_idx requires it +HINT: You can drop index idxpart_temp_a_idx instead. +-- non-concurrent drop is enforced here, so it is a valid case. +drop index concurrently idxpart_temp_a_idx; +select relname, relkind from pg_class + where relname like 'idxpart_temp%' order by relname; + relname | relkind +--------------+--------- + idxpart_temp | p +(1 row) + +drop table idxpart_temp; -- ALTER INDEX .. ATTACH, error cases create table idxpart (a int, b int) partition by range (a, b); create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10); @@ -904,16 +924,16 @@ Indexes: drop table idxpart; -- Failing to use the full partition key is not allowed create table idxpart (a int unique, b int) partition by range (a, b); -ERROR: insufficient columns in UNIQUE constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: UNIQUE constraint on table "idxpart" lacks column "b" which is part of the partition key. create table idxpart (a int, b int unique) partition by range (a, b); -ERROR: insufficient columns in UNIQUE constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: UNIQUE constraint on table "idxpart" lacks column "a" which is part of the partition key. create table idxpart (a int primary key, b int) partition by range (b, a); -ERROR: insufficient columns in PRIMARY KEY constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: PRIMARY KEY constraint on table "idxpart" lacks column "b" which is part of the partition key. create table idxpart (a int, b int primary key) partition by range (b, a); -ERROR: insufficient columns in PRIMARY KEY constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: PRIMARY KEY constraint on table "idxpart" lacks column "a" which is part of the partition key. -- OK if you use them in some other order create table idxpart (a int, b int, c text, primary key (a, b, c)) partition by range (b, c, a); @@ -933,7 +953,7 @@ DETAIL: UNIQUE constraints cannot be used when partition keys include expressio -- use ALTER TABLE to add a primary key create table idxpart (a int, b int, c text) partition by range (a, b); alter table idxpart add primary key (a); -- not an incomplete one though -ERROR: insufficient columns in PRIMARY KEY constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: PRIMARY KEY constraint on table "idxpart" lacks column "b" which is part of the partition key. alter table idxpart add primary key (a, b); -- this works \d idxpart @@ -964,7 +984,7 @@ drop table idxpart; -- use ALTER TABLE to add a unique constraint create table idxpart (a int, b int) partition by range (a, b); alter table idxpart add unique (a); -- not an incomplete one though -ERROR: insufficient columns in UNIQUE constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: UNIQUE constraint on table "idxpart" lacks column "b" which is part of the partition key. alter table idxpart add unique (b, a); -- this works \d idxpart @@ -1015,7 +1035,7 @@ drop table idxpart; create table idxpart (a int, b int, primary key (a)) partition by range (a); create table idxpart2 partition of idxpart for values from (0) to (1000) partition by range (b); -- fail -ERROR: insufficient columns in PRIMARY KEY constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: PRIMARY KEY constraint on table "idxpart2" lacks column "b" which is part of the partition key. drop table idxpart; -- Ditto for the ATTACH PARTITION case @@ -1024,7 +1044,7 @@ create table idxpart1 (a int not null, b int, unique (a, b)) partition by range (a, b); alter table idxpart1 set distributed by (a); -- GPDB: distribution key must match parent alter table idxpart attach partition idxpart1 for values from (1) to (1000); -ERROR: insufficient columns in UNIQUE constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: UNIQUE constraint on table "idxpart1" lacks column "b" which is part of the partition key. DROP TABLE idxpart, idxpart1; -- Multi-layer partitioning works correctly in this case: @@ -1291,7 +1311,7 @@ DETAIL: Distribution key column "a" is not included in the constraint. -- in upstream. Run another test to exercise the same check as in upstream. create table covidxpart_x (a int, b int) partition by list (a) distributed by (b); create unique index on covidxpart_x (b) include (a); -- should fail -ERROR: insufficient columns in UNIQUE constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: UNIQUE constraint on table "covidxpart_x" lacks column "a" which is part of the partition key. -- check that detaching a partition also detaches the primary key constraint create table parted_pk_detach_test (a int primary key) partition by list (a); diff --git a/src/test/regress/expected/infinite_recurse.out b/src/test/regress/expected/infinite_recurse.out new file mode 100644 index 000000000000..aa102fadd839 --- /dev/null +++ b/src/test/regress/expected/infinite_recurse.out @@ -0,0 +1,24 @@ +-- Check that stack depth detection mechanism works and +-- max_stack_depth is not set too high. +create function infinite_recurse() returns int as +'select infinite_recurse()' language sql; +-- Unfortunately, up till mid 2020 the Linux kernel had a bug in PPC64 +-- signal handling that would cause this test to crash if it happened +-- to receive an sinval catchup interrupt while the stack is deep: +-- https://bugzilla.kernel.org/show_bug.cgi?id=205183 +-- It is likely to be many years before that bug disappears from all +-- production kernels, so disable this test on such platforms. +-- (We still create the function, so as not to have a cross-platform +-- difference in the end state of the regression database.) +SELECT version() ~ 'powerpc64[^,]*-linux-gnu' + AS skip_test \gset +\if :skip_test +\quit +\endif +-- The full error report is not very stable, so we show only SQLSTATE +-- and primary error message. +\set VERBOSITY sqlstate +select infinite_recurse(); +ERROR: 54001 +\echo :LAST_ERROR_MESSAGE +stack depth limit exceeded diff --git a/src/test/regress/expected/infinite_recurse_1.out b/src/test/regress/expected/infinite_recurse_1.out new file mode 100644 index 000000000000..b2c99a0d0d41 --- /dev/null +++ b/src/test/regress/expected/infinite_recurse_1.out @@ -0,0 +1,16 @@ +-- Check that stack depth detection mechanism works and +-- max_stack_depth is not set too high. +create function infinite_recurse() returns int as +'select infinite_recurse()' language sql; +-- Unfortunately, up till mid 2020 the Linux kernel had a bug in PPC64 +-- signal handling that would cause this test to crash if it happened +-- to receive an sinval catchup interrupt while the stack is deep: +-- https://bugzilla.kernel.org/show_bug.cgi?id=205183 +-- It is likely to be many years before that bug disappears from all +-- production kernels, so disable this test on such platforms. +-- (We still create the function, so as not to have a cross-platform +-- difference in the end state of the regression database.) +SELECT version() ~ 'powerpc64[^,]*-linux-gnu' + AS skip_test \gset +\if :skip_test +\quit diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out index cbd3c85cd984..2c9780580510 100644 --- a/src/test/regress/expected/insert.out +++ b/src/test/regress/expected/insert.out @@ -832,9 +832,7 @@ drop role regress_coldesc_role; drop table inserttest3; drop table brtrigpartcon; drop function brtrigpartcon1trigf(); --- check that "do nothing" BR triggers work with tuple-routing (this checks --- that estate->es_result_relation_info is appropriately set/reset for each --- routed tuple) +-- check that "do nothing" BR triggers work with tuple-routing create table donothingbrtrig_test (a int, b text) partition by list (a); create table donothingbrtrig_test1 (b text, a int); alter table donothingbrtrig_test1 set distributed by (a); diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out index bbd11de089e6..477e4277f88f 100644 --- a/src/test/regress/expected/insert_conflict.out +++ b/src/test/regress/expected/insert_conflict.out @@ -50,7 +50,7 @@ explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on con Insert on insertconflicttest Conflict Resolution: UPDATE Conflict Arbiter Indexes: op_index_key, collation_index_key, both_index_key - Conflict Filter: (alternatives: SubPlan 1 or hashed SubPlan 2) + Conflict Filter: (SubPlan 1) -> Result SubPlan 1 -> Result @@ -58,11 +58,7 @@ explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on con -> Materialize -> Broadcast Motion 3:1 (slice1; segments: 3) -> Seq Scan on insertconflicttest ii - SubPlan 2 - -> Broadcast Motion 3:1 (slice2; segments: 3) - -> Seq Scan on insertconflicttest ii_1 - Optimizer: Postgres query optimizer -(15 rows) +(8 rows) -- Neither collation nor operator class specifications are required -- -- supplying them merely *limits* matches to indexes with matching opclasses diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 62d7edc50082..8965772ecac8 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -6294,6 +6294,62 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral drop table join_pt1; drop table join_ut1; -- +-- test estimation behavior with multi-column foreign key and constant qual +-- +begin; +-- GPDB: persuade the planner to choose same plan as in upstream. +set local enable_nestloop=on; +create table fkest (x integer, x10 integer, x10b integer, x100 integer); +insert into fkest select x, x/10, x/10, x/100 from generate_series(1,1000) x; +create unique index on fkest(x, x10, x100); +analyze fkest; +explain (costs off) +select * from fkest f1 + join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100) + join fkest f3 on f1.x = f3.x + where f1.x100 = 2; + QUERY PLAN +----------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Nested Loop + -> Hash Join + Hash Cond: ((f1.x = f2.x) AND (f1.x10 = f2.x10b)) + -> Seq Scan on fkest f1 + Filter: (x100 = 2) + -> Hash + -> Seq Scan on fkest f2 + Filter: (x100 = 2) + -> Index Scan using fkest_x_x10_x100_idx on fkest f3 + Index Cond: (x = f1.x) + Optimizer: Postgres query optimizer +(12 rows) + +alter table fkest add constraint fk + foreign key (x, x10b, x100) references fkest (x, x10, x100); +explain (costs off) +select * from fkest f1 + join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100) + join fkest f3 on f1.x = f3.x + where f1.x100 = 2; + QUERY PLAN +----------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Hash Join + Hash Cond: ((f1.x = f2.x) AND (f1.x10 = f2.x10b)) + -> Hash Join + Hash Cond: (f3.x = f1.x) + -> Seq Scan on fkest f3 + -> Hash + -> Seq Scan on fkest f1 + Filter: (x100 = 2) + -> Hash + -> Seq Scan on fkest f2 + Filter: (x100 = 2) + Optimizer: Postgres query optimizer +(13 rows) + +rollback; +-- -- test that foreign key join estimation performs sanely for outer joins -- begin; diff --git a/src/test/regress/expected/join_optimizer.out b/src/test/regress/expected/join_optimizer.out index daa6bde6f218..4742d8e266a3 100644 --- a/src/test/regress/expected/join_optimizer.out +++ b/src/test/regress/expected/join_optimizer.out @@ -6337,6 +6337,64 @@ ERROR: could not devise a query plan for the given query (pathnode.c:275) drop table join_pt1; drop table join_ut1; -- +-- test estimation behavior with multi-column foreign key and constant qual +-- +begin; +-- GPDB: persuade the planner to choose same plan as in upstream. +set local enable_nestloop=on; +create table fkest (x integer, x10 integer, x10b integer, x100 integer); +insert into fkest select x, x/10, x/10, x/100 from generate_series(1,1000) x; +create unique index on fkest(x, x10, x100); +analyze fkest; +explain (costs off) +select * from fkest f1 + join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100) + join fkest f3 on f1.x = f3.x + where f1.x100 = 2; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Hash Join + Hash Cond: ((fkest_1.x = fkest_2.x) AND (fkest_1.x10b = fkest_2.x10) AND (fkest_1.x100 = fkest_2.x100)) + -> Hash Join + Hash Cond: (fkest.x = fkest_1.x) + -> Seq Scan on fkest + -> Hash + -> Index Scan using fkest_x_x10_x100_idx on fkest fkest_1 + Index Cond: (x100 = 2) + -> Hash + -> Index Scan using fkest_x_x10_x100_idx on fkest fkest_2 + Index Cond: (x100 = 2) + Optimizer: Pivotal Optimizer (GPORCA) +(13 rows) + +alter table fkest add constraint fk + foreign key (x, x10b, x100) references fkest (x, x10, x100); +WARNING: referential integrity (FOREIGN KEY) constraints are not supported in Greenplum Database, will not be enforced +explain (costs off) +select * from fkest f1 + join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100) + join fkest f3 on f1.x = f3.x + where f1.x100 = 2; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Hash Join + Hash Cond: ((fkest_1.x = fkest_2.x) AND (fkest_1.x10b = fkest_2.x10) AND (fkest_1.x100 = fkest_2.x100)) + -> Hash Join + Hash Cond: (fkest.x = fkest_1.x) + -> Seq Scan on fkest + -> Hash + -> Index Scan using fkest_x_x10_x100_idx on fkest fkest_1 + Index Cond: (x100 = 2) + -> Hash + -> Index Scan using fkest_x_x10_x100_idx on fkest fkest_2 + Index Cond: (x100 = 2) + Optimizer: Pivotal Optimizer (GPORCA) +(13 rows) + +rollback; +-- -- test that foreign key join estimation performs sanely for outer joins -- begin; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index d4bc3cadbe4e..b198ee2ea7ac 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1722,6 +1722,16 @@ select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").t "time with time zone" (1 row) +select jsonb_path_query('"10-03-2017T12:34:56"', '$.datetime("dd-mm-yyyy\"T\"HH24:MI:SS")'); + jsonb_path_query +----------------------- + "2017-03-10T12:34:56" +(1 row) + +select jsonb_path_query('"10-03-2017t12:34:56"', '$.datetime("dd-mm-yyyy\"T\"HH24:MI:SS")'); +ERROR: unmatched format character "T" +select jsonb_path_query('"10-03-2017 12:34:56"', '$.datetime("dd-mm-yyyy\"T\"HH24:MI:SS")'); +ERROR: unmatched format character "T" set time zone '+00'; select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); jsonb_path_query @@ -1877,30 +1887,39 @@ select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()'); "2017-03-10T12:34:56" (1 row) -select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()'); +select jsonb_path_query('"2017-03-10 12:34:56+3"', '$.datetime().type()'); jsonb_path_query ---------------------------- "timestamp with time zone" (1 row) -select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()'); +select jsonb_path_query('"2017-03-10 12:34:56+3"', '$.datetime()'); jsonb_path_query ----------------------------- "2017-03-10T12:34:56+03:00" (1 row) -select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()'); +select jsonb_path_query('"2017-03-10 12:34:56+3:10"', '$.datetime().type()'); jsonb_path_query ---------------------------- "timestamp with time zone" (1 row) -select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()'); +select jsonb_path_query('"2017-03-10 12:34:56+3:10"', '$.datetime()'); jsonb_path_query ----------------------------- "2017-03-10T12:34:56+03:10" (1 row) +select jsonb_path_query('"2017-03-10T12:34:56+3:10"', '$.datetime()'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:56+03:10" +(1 row) + +select jsonb_path_query('"2017-03-10t12:34:56+3:10"', '$.datetime()'); +ERROR: datetime format is not recognized: "2017-03-10t12:34:56+3:10" +HINT: Use a datetime template argument to specify the input data format. select jsonb_path_query('"12:34:56"', '$.datetime().type()'); jsonb_path_query -------------------------- @@ -1913,25 +1932,25 @@ select jsonb_path_query('"12:34:56"', '$.datetime()'); "12:34:56" (1 row) -select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()'); +select jsonb_path_query('"12:34:56+3"', '$.datetime().type()'); jsonb_path_query ----------------------- "time with time zone" (1 row) -select jsonb_path_query('"12:34:56 +3"', '$.datetime()'); +select jsonb_path_query('"12:34:56+3"', '$.datetime()'); jsonb_path_query ------------------ "12:34:56+03:00" (1 row) -select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()'); +select jsonb_path_query('"12:34:56+3:10"', '$.datetime().type()'); jsonb_path_query ----------------------- "time with time zone" (1 row) -select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()'); +select jsonb_path_query('"12:34:56+3:10"', '$.datetime()'); jsonb_path_query ------------------ "12:34:56+03:10" @@ -1940,22 +1959,22 @@ select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()'); set time zone '+00'; -- date comparison select jsonb_path_query( - '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]', '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))'); -ERROR: cannot convert value from date to timestamptz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from date to timestamptz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query( - '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]', '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))'); -ERROR: cannot convert value from date to timestamptz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from date to timestamptz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query( - '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]', '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))'); -ERROR: cannot convert value from date to timestamptz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from date to timestamptz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query_tz( - '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]', '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))'); jsonb_path_query_tz ----------------------------- @@ -1965,7 +1984,7 @@ select jsonb_path_query_tz( (3 rows) select jsonb_path_query_tz( - '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]', '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))'); jsonb_path_query_tz ----------------------------- @@ -1977,7 +1996,7 @@ select jsonb_path_query_tz( (5 rows) select jsonb_path_query_tz( - '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]', '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))'); jsonb_path_query_tz ----------------------------- @@ -1987,22 +2006,22 @@ select jsonb_path_query_tz( -- time comparison select jsonb_path_query( - '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]', '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))'); -ERROR: cannot convert value from time to timetz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from time to timetz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query( - '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]', '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))'); -ERROR: cannot convert value from time to timetz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from time to timetz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query( - '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]', '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))'); -ERROR: cannot convert value from time to timetz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from time to timetz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query_tz( - '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]', '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))'); jsonb_path_query_tz --------------------- @@ -2011,7 +2030,7 @@ select jsonb_path_query_tz( (2 rows) select jsonb_path_query_tz( - '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]', '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))'); jsonb_path_query_tz --------------------- @@ -2021,7 +2040,7 @@ select jsonb_path_query_tz( (3 rows) select jsonb_path_query_tz( - '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]', '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))'); jsonb_path_query_tz --------------------- @@ -2032,22 +2051,22 @@ select jsonb_path_query_tz( -- timetz comparison select jsonb_path_query( - '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))'); -ERROR: cannot convert value from time to timetz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from time to timetz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query( - '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))'); -ERROR: cannot convert value from time to timetz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from time to timetz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query( - '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))'); -ERROR: cannot convert value from time to timetz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from time to timetz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query_tz( - '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))'); jsonb_path_query_tz --------------------- @@ -2055,7 +2074,7 @@ select jsonb_path_query_tz( (1 row) select jsonb_path_query_tz( - '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))'); jsonb_path_query_tz --------------------- @@ -2067,7 +2086,7 @@ select jsonb_path_query_tz( (5 rows) select jsonb_path_query_tz( - '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))'); jsonb_path_query_tz --------------------- @@ -2078,22 +2097,22 @@ select jsonb_path_query_tz( -- timestamp comparison select jsonb_path_query( - '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); -ERROR: cannot convert value from timestamp to timestamptz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from timestamp to timestamptz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query( - '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); -ERROR: cannot convert value from timestamp to timestamptz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from timestamp to timestamptz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query( - '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); -ERROR: cannot convert value from timestamp to timestamptz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from timestamp to timestamptz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query_tz( - '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); jsonb_path_query_tz ----------------------------- @@ -2102,7 +2121,7 @@ select jsonb_path_query_tz( (2 rows) select jsonb_path_query_tz( - '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); jsonb_path_query_tz ----------------------------- @@ -2114,7 +2133,7 @@ select jsonb_path_query_tz( (5 rows) select jsonb_path_query_tz( - '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); jsonb_path_query_tz ----------------------------- @@ -2125,22 +2144,22 @@ select jsonb_path_query_tz( -- timestamptz comparison select jsonb_path_query( - '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); -ERROR: cannot convert value from timestamp to timestamptz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from timestamp to timestamptz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query( - '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); -ERROR: cannot convert value from timestamp to timestamptz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from timestamp to timestamptz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query( - '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); -ERROR: cannot convert value from timestamp to timestamptz without timezone usage -HINT: Use *_tz() function for timezone support. +ERROR: cannot convert value from timestamp to timestamptz without time zone usage +HINT: Use *_tz() function for time zone support. select jsonb_path_query_tz( - '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); jsonb_path_query_tz ----------------------------- @@ -2149,7 +2168,7 @@ select jsonb_path_query_tz( (2 rows) select jsonb_path_query_tz( - '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); jsonb_path_query_tz ----------------------------- @@ -2162,7 +2181,7 @@ select jsonb_path_query_tz( (6 rows) select jsonb_path_query_tz( - '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); jsonb_path_query_tz ----------------------------- diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out index 3bbaaadc6793..1b8f7cde233b 100644 --- a/src/test/regress/expected/limit.out +++ b/src/test/regress/expected/limit.out @@ -447,7 +447,7 @@ SELECT thousand SELECT ''::text AS two, unique1, unique2, stringu1 FROM onek WHERE unique1 > 50 FETCH FIRST 2 ROW WITH TIES; -ERROR: WITH TIES options can not be specified without ORDER BY clause +ERROR: WITH TIES cannot be specified without ORDER BY clause -- test ruleutils CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST 5 ROWS WITH TIES OFFSET 10; @@ -481,7 +481,7 @@ View definition: CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST NULL ROWS WITH TIES; -- fails -ERROR: row count cannot be NULL in FETCH FIRST ... WITH TIES clause +ERROR: row count cannot be null in FETCH FIRST ... WITH TIES clause CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST (NULL+1) ROWS WITH TIES; \d+ limit_thousand_v_3 diff --git a/src/test/regress/expected/limit_optimizer.out b/src/test/regress/expected/limit_optimizer.out index 3ce09444e2dc..8dde8c811009 100644 --- a/src/test/regress/expected/limit_optimizer.out +++ b/src/test/regress/expected/limit_optimizer.out @@ -471,7 +471,7 @@ SELECT thousand SELECT ''::text AS two, unique1, unique2, stringu1 FROM onek WHERE unique1 > 50 FETCH FIRST 2 ROW WITH TIES; -ERROR: WITH TIES options can not be specified without ORDER BY clause +ERROR: WITH TIES cannot be specified without ORDER BY clause -- test ruleutils CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST 5 ROWS WITH TIES OFFSET 10; @@ -505,7 +505,7 @@ View definition: CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST NULL ROWS WITH TIES; -- fails -ERROR: row count cannot be NULL in FETCH FIRST ... WITH TIES clause +ERROR: row count cannot be null in FETCH FIRST ... WITH TIES clause CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST (NULL+1) ROWS WITH TIES; \d+ limit_thousand_v_3 diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out index 9f210b36cfa2..1d834cdf1b53 100644 --- a/src/test/regress/expected/lock.out +++ b/src/test/regress/expected/lock.out @@ -12,6 +12,9 @@ CREATE VIEW lock_view3 AS SELECT * from lock_view2; CREATE VIEW lock_view4 AS SELECT (select a from lock_tbl1a limit 1) from lock_tbl1; CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from lock_tbl1a); CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub; +CREATE MATERIALIZED VIEW lock_mv1 AS SELECT * FROM lock_view6; +CREATE INDEX lock_mvi1 ON lock_mv1 (a); +CREATE SEQUENCE lock_seq; CREATE ROLE regress_rol_lock1; ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1; GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1; @@ -205,9 +208,16 @@ BEGIN; LOCK TABLE ONLY lock_tbl1; ROLLBACK; RESET ROLE; +-- Lock other relations +BEGIN TRANSACTION; +LOCK TABLE lock_mv1; +LOCK TABLE lock_mvi1; +LOCK TABLE lock_seq; +ROLLBACK; -- -- Clean up -- +DROP MATERIALIZED VIEW lock_mv1; DROP VIEW lock_view7; DROP VIEW lock_view6; DROP VIEW lock_view5; @@ -219,6 +229,7 @@ DROP TABLE lock_tbl3; DROP TABLE lock_tbl2; DROP TABLE lock_tbl1; DROP TABLE lock_tbl1a; +DROP SEQUENCE lock_seq; DROP SCHEMA lock_schema1 CASCADE; DROP ROLE regress_rol_lock1; -- atomic ops tests diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out index 7725512dd177..6adf30cb2afa 100644 --- a/src/test/regress/expected/misc_sanity.out +++ b/src/test/regress/expected/misc_sanity.out @@ -18,8 +18,8 @@ WHERE refclassid = 0 OR refobjid = 0 OR deptype NOT IN ('a', 'e', 'i', 'n', 'p') OR (deptype != 'p' AND (classid = 0 OR objid = 0)) OR (deptype = 'p' AND (classid != 0 OR objid != 0 OR objsubid != 0)); - classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype ----------+-------+----------+------------+----------+-------------+--------- + classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype | refobjversion +---------+-------+----------+------------+----------+-------------+---------+--------------- (0 rows) -- **************** pg_shdepend **************** diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out index 0fa11f2e9612..00a1ad82f7c8 100644 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -1096,6 +1096,18 @@ SELECT AVG(val) FROM num_data; -13430913.592242320700 (1 row) +SELECT MAX(val) FROM num_data; + max +-------------------- + 7799461.4119000000 +(1 row) + +SELECT MIN(val) FROM num_data; + min +---------------------- + -83028485.0000000000 +(1 row) + SELECT STDDEV(val) FROM num_data; stddev ------------------------------- @@ -1304,10 +1316,8 @@ SELECT width_bucket('NaN', 3.0, 4.0, 888); ERROR: operand, lower bound, and upper bound cannot be NaN SELECT width_bucket(0::float8, 'NaN', 4.0::float8, 888); ERROR: operand, lower bound, and upper bound cannot be NaN -SELECT width_bucket('inf', 3.0, 4.0, 888); -ERROR: operand, lower bound, and upper bound cannot be infinity SELECT width_bucket(2.0, 3.0, '-inf', 888); -ERROR: operand, lower bound, and upper bound cannot be infinity +ERROR: lower and upper bounds must be finite SELECT width_bucket(0::float8, '-inf', 4.0::float8, 888); ERROR: lower and upper bounds must be finite -- normal operation @@ -1350,8 +1360,19 @@ SELECT 10.0000000000001 | 6 | 6 | 0 | 0 | 5 | 5 | 21 | 21 | 8 | 8 (19 rows) --- for float8 only, check positive and negative infinity: we require +-- Check positive and negative infinity: we require -- finite bucket bounds, but allow an infinite operand +SELECT width_bucket(0.0::numeric, 'Infinity'::numeric, 5, 10); -- error +ERROR: lower and upper bounds must be finite +SELECT width_bucket(0.0::numeric, 5, '-Infinity'::numeric, 20); -- error +ERROR: lower and upper bounds must be finite +SELECT width_bucket('Infinity'::numeric, 1, 10, 10), + width_bucket('-Infinity'::numeric, 1, 10, 10); + width_bucket | width_bucket +--------------+-------------- + 11 | 0 +(1 row) + SELECT width_bucket(0.0::float8, 'Infinity'::float8, 5, 10); -- error ERROR: lower and upper bounds must be finite SELECT width_bucket(0.0::float8, 5, '-Infinity'::float8, 20); -- error @@ -1364,6 +1385,46 @@ SELECT width_bucket('Infinity'::float8, 1, 10, 10), (1 row) DROP TABLE width_bucket_test; +-- Simple test for roundoff error when results should be exact +SELECT x, width_bucket(x::float8, 10, 100, 9) as flt, + width_bucket(x::numeric, 10, 100, 9) as num +FROM generate_series(0, 110, 10) x; + x | flt | num +-----+-----+----- + 0 | 0 | 0 + 10 | 1 | 1 + 20 | 2 | 2 + 30 | 3 | 3 + 40 | 4 | 4 + 50 | 5 | 5 + 60 | 6 | 6 + 70 | 7 | 7 + 80 | 8 | 8 + 90 | 9 | 9 + 100 | 10 | 10 + 110 | 10 | 10 +(12 rows) + +SELECT x, width_bucket(x::float8, 100, 10, 9) as flt, + width_bucket(x::numeric, 100, 10, 9) as num +FROM generate_series(0, 110, 10) x; + x | flt | num +-----+-----+----- + 0 | 10 | 10 + 10 | 10 | 10 + 20 | 9 | 9 + 30 | 8 | 8 + 40 | 7 | 7 + 50 | 6 | 6 + 60 | 5 | 5 + 70 | 4 | 4 + 80 | 3 | 3 + 90 | 2 | 2 + 100 | 1 | 1 + 110 | 0 | 0 +(12 rows) + +-- -- TO_CHAR() -- SELECT '' AS to_char_1, to_char(val, '9G999G999G999G999G999') @@ -2960,16 +3021,10 @@ ERROR: value overflows numeric format -- -- Tests for factorial -- -SELECT 4!; - ?column? ----------- - 24 -(1 row) - -SELECT !!3; - ?column? ----------- - 6 +SELECT factorial(4); + factorial +----------- + 24 (1 row) SELECT factorial(15); @@ -2978,16 +3033,14 @@ SELECT factorial(15); 1307674368000 (1 row) -SELECT 100000!; +SELECT factorial(100000); ERROR: value overflows numeric format -SELECT 0!; - ?column? ----------- - 1 +SELECT factorial(0); + factorial +----------- + 1 (1 row) -SELECT -4!; -ERROR: factorial of a negative number is undefined SELECT factorial(-4); ERROR: factorial of a negative number is undefined -- diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 3526bde80257..714d849d2e6b 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1079,7 +1079,7 @@ WHERE condefault AND -- Look for illegal values in pg_operator fields. SELECT p1.oid, p1.oprname FROM pg_operator as p1 -WHERE (p1.oprkind != 'b' AND p1.oprkind != 'l' AND p1.oprkind != 'r') OR +WHERE (p1.oprkind != 'b' AND p1.oprkind != 'l') OR p1.oprresult = 0 OR p1.oprcode = 0; oid | oprname -----+--------- @@ -1090,8 +1090,7 @@ SELECT p1.oid, p1.oprname FROM pg_operator as p1 WHERE (p1.oprleft = 0 and p1.oprkind != 'l') OR (p1.oprleft != 0 and p1.oprkind = 'l') OR - (p1.oprright = 0 and p1.oprkind != 'r') OR - (p1.oprright != 0 and p1.oprkind = 'r'); + p1.oprright = 0; oid | oprname -----+--------- (0 rows) @@ -1177,14 +1176,13 @@ ORDER BY 1, 2; ?-| | ?-| ?| | ?| ?|| | ?|| - @ | ~ @@ | @@ @@@ | @@@ | | | ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(30 rows) +(29 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -1300,18 +1298,6 @@ WHERE p1.oprcode = p2.oid AND -----+---------+-----+--------- (0 rows) -SELECT p1.oid, p1.oprname, p2.oid, p2.proname -FROM pg_operator AS p1, pg_proc AS p2 -WHERE p1.oprcode = p2.oid AND - p1.oprkind = 'r' AND - (p2.pronargs != 1 - OR NOT binary_coercible(p2.prorettype, p1.oprresult) - OR NOT binary_coercible(p1.oprleft, p2.proargtypes[0]) - OR p1.oprright != 0); - oid | oprname | oid | proname ------+---------+-----+--------- -(0 rows) - -- If the operator is mergejoinable or hashjoinable, its underlying function -- should not be volatile. SELECT p1.oid, p1.oprname, p2.oid, p2.proname @@ -2031,8 +2017,6 @@ ORDER BY 1, 2, 3; 783 | 11 | >^ 783 | 11 | |>> 783 | 12 | |&> - 783 | 13 | ~ - 783 | 14 | @ 783 | 15 | <-> 783 | 16 | @> 783 | 18 | = @@ -2143,7 +2127,7 @@ ORDER BY 1, 2, 3; 7013 | 5 | > 7013 | 5 | >> 7013 | 5 | ~>~ -(147 rows) +(145 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/expected/partition.out b/src/test/regress/expected/partition.out index 2efa8d74dd33..c8a160abadf3 100755 --- a/src/test/regress/expected/partition.out +++ b/src/test/regress/expected/partition.out @@ -4530,8 +4530,8 @@ create table tc default partition d, start (0) inclusive end(100) inclusive every (50) ); +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: PRIMARY KEY constraint on table "tc" lacks column "b" which is part of the partition key. -ERROR: insufficient columns in PRIMARY KEY constraint definition create table cc (a int primary key, b int, c int) distributed by (a) @@ -4540,8 +4540,8 @@ create table cc default partition d, start (0) inclusive end(100) inclusive every (50) ); +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: PRIMARY KEY constraint on table "cc" lacks column "b" which is part of the partition key. -ERROR: insufficient columns in PRIMARY KEY constraint definition create table at (a int, b int, c int) distributed by (a) @@ -4552,8 +4552,8 @@ create table at ); alter table at add primary key (a); +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: PRIMARY KEY constraint on table "at" lacks column "b" which is part of the partition key. -ERROR: insufficient columns in PRIMARY KEY constraint definition -- MPP-14471 end -- MPP-17606 (using table "at" from above) alter table at @@ -4619,6 +4619,8 @@ create table plst2 partition p3 values ( CAST('(1,2)' as plst2_partkey) ) ); ERROR: partition "plst2_1_prt_p3" would overlap partition "plst2_1_prt_p1" +LINE 11: partition p3 values ( CAST('(1,2)' as plst2_partkey)... + ^ -- postive; make sure inner part duplicates are accepted and quietly removed. drop table if exists plst2; NOTICE: table "plst2" does not exist, skipping @@ -4655,6 +4657,8 @@ Distributed by: (d) -- negative; make sure conflicting alters fail. alter table plst2 add partition p6 values (CAST('(7,8)' as plst2_partkey), CAST('(2,1)' as plst2_partkey)); ERROR: partition "plst2_1_prt_p6" would overlap partition "plst2_1_prt_p5" +LINE 1: alter table plst2 add partition p6 values (CAST('(7,8)' as p... + ^ drop table if exists plst2; -- MPP-17814 end -- MPP-18441 diff --git a/src/test/regress/expected/partition1.out b/src/test/regress/expected/partition1.out index b42e0feffe37..3cb6fdeaac93 100644 --- a/src/test/regress/expected/partition1.out +++ b/src/test/regress/expected/partition1.out @@ -311,6 +311,8 @@ partition by list (rankgender) partition bb values (CAST ('(1,M)' AS rank_partkey)) ); ERROR: partition "rank_1_prt_bb" would overlap partition "rank_1_prt_1" +LINE 10: partition bb values (CAST ('(1,M)' AS rank_partkey)) + ^ -- RANGE validation -- legal if end of aa not inclusive create table ggg (a char(1), b date, d char(3)) @@ -2411,7 +2413,7 @@ PARTITION BY list (b) PARTITION t2 values (2)); -- the following statement should fail because index cols does not contain part key CREATE UNIQUE INDEX uidx_t_idx_col_contain_partkey on t_idx_col_contain_partkey(a); -ERROR: insufficient columns in UNIQUE constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: UNIQUE constraint on table "t_idx_col_contain_partkey" lacks column "b" which is part of the partition key. -- the following statement should work CREATE UNIQUE INDEX uidx_t_idx_col_contain_partkey on t_idx_col_contain_partkey(a, b); @@ -2443,7 +2445,7 @@ SUBPARTITION BY LIST (r_name) SUBPARTITION TEMPLATE ); -- should fail, must contain all the partition keys of all levels CREATE UNIQUE INDEX uidx_t_idx_col_contain_partkey on t_idx_col_contain_partkey(r_regionkey); -ERROR: insufficient columns in UNIQUE constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: UNIQUE constraint on table "t_idx_col_contain_partkey_1_prt_region1" lacks column "r_name" which is part of the partition key. -- should work CREATE UNIQUE INDEX uidx_t_idx_col_contain_partkey on t_idx_col_contain_partkey(r_regionkey, r_name); diff --git a/src/test/regress/expected/partition_indexing.out b/src/test/regress/expected/partition_indexing.out index aec0bd0f1b00..f95879d89553 100644 --- a/src/test/regress/expected/partition_indexing.out +++ b/src/test/regress/expected/partition_indexing.out @@ -167,7 +167,7 @@ CREATE UNIQUE INDEX mpp3033a_stringu1 ON mpp3033a (stringu1); ERROR: UNIQUE index must contain all columns in the table's distribution key DETAIL: Distribution key column "unique1" is not included in the constraint. CREATE UNIQUE INDEX mpp3033b_unique1 ON mpp3033b (unique1); -ERROR: insufficient columns in UNIQUE constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: UNIQUE constraint on table "mpp3033b_1_prt_aa" lacks column "unique2" which is part of the partition key. CREATE UNIQUE INDEX mpp3033b_unique2 ON mpp3033b (unique2); ERROR: UNIQUE index must contain all columns in the table's distribution key @@ -435,7 +435,7 @@ CREATE UNIQUE INDEX mpp3033a_stringu1 ON mpp3033a (stringu1); ERROR: UNIQUE index must contain all columns in the table's distribution key DETAIL: Distribution key column "unique1" is not included in the constraint. CREATE UNIQUE INDEX mpp3033b_unique1 ON mpp3033b (unique1); -ERROR: insufficient columns in UNIQUE constraint definition +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: UNIQUE constraint on table "mpp3033b_1_prt_1" lacks column "unique2" which is part of the partition key. CREATE UNIQUE INDEX mpp3033b_unique2 ON mpp3033b (unique2); ERROR: UNIQUE index must contain all columns in the table's distribution key diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 0970726d1373..db9be167c604 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -5378,7 +5378,7 @@ ANALYZE plt3_adv; -- This tests that when merging partitions from plt1_adv and plt2_adv in -- merge_list_bounds(), process_outer_partition() returns an already-assigned -- merged partition when re-called with plt1_adv_p1 for the second list value --- '0001' of that partitin +-- '0001' of that partition EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.c = t2.c)) FULL JOIN plt3_adv t3 ON (t1.c = t3.c) WHERE coalesce(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; QUERY PLAN diff --git a/src/test/regress/expected/partition_locking.out b/src/test/regress/expected/partition_locking.out index bfc381798151..ed52ad42c65f 100644 --- a/src/test/regress/expected/partition_locking.out +++ b/src/test/regress/expected/partition_locking.out @@ -272,12 +272,11 @@ select * from locktest_master where coalesce not like 'gp_%' and coalesce not li select * from locktest_segments where coalesce not like 'gp_%' and coalesce not like 'pg_%'; coalesce | mode | locktype | node -------------------+------------------+----------+------------ - partlockt | AccessShareLock | relation | n segments partlockt | RowExclusiveLock | relation | n segments partlockt_1_prt_1 | RowExclusiveLock | relation | 1 segment partlockt_1_prt_2 | RowExclusiveLock | relation | 1 segment partlockt_1_prt_3 | RowExclusiveLock | relation | 1 segment -(5 rows) +(4 rows) commit; -- drop @@ -440,10 +439,9 @@ select * from locktest_master where coalesce not like 'gp_%' and coalesce not li select * from locktest_segments where coalesce not like 'gp_%' and coalesce not like 'pg_%'; coalesce | mode | locktype | node -------------------+------------------+----------+----------- - partlockt | AccessShareLock | relation | 1 segment partlockt | RowExclusiveLock | relation | 1 segment partlockt_1_prt_3 | RowExclusiveLock | relation | 1 segment -(3 rows) +(2 rows) commit; -- delete locking @@ -451,22 +449,20 @@ begin; delete from partlockt where i = 4; -- Known_opt_diff: MPP-20936 select * from locktest_master where coalesce not like 'gp_%' and coalesce not like 'pg_%'; - coalesce | mode | locktype | node --------------------------+-----------------+----------+-------- - partlockt | AccessShareLock | relation | master - partlockt | ExclusiveLock | relation | master - partlockt_1_prt_4 | ExclusiveLock | relation | master - partlockt_1_prt_4_i_idx | ExclusiveLock | relation | master -(4 rows) + coalesce | mode | locktype | node +-------------------------+---------------+----------+-------- + partlockt | ExclusiveLock | relation | master + partlockt_1_prt_4 | ExclusiveLock | relation | master + partlockt_1_prt_4_i_idx | ExclusiveLock | relation | master +(3 rows) select * from locktest_segments where coalesce not like 'gp_%' and coalesce not like 'pg_%'; - coalesce | mode | locktype | node --------------------------+-----------------+----------+----------- - partlockt | AccessShareLock | relation | 1 segment - partlockt | ExclusiveLock | relation | 1 segment - partlockt_1_prt_4 | ExclusiveLock | relation | 1 segment - partlockt_1_prt_4_i_idx | ExclusiveLock | relation | 1 segment -(4 rows) + coalesce | mode | locktype | node +-------------------------+---------------+----------+----------- + partlockt | ExclusiveLock | relation | 1 segment + partlockt_1_prt_4 | ExclusiveLock | relation | 1 segment + partlockt_1_prt_4_i_idx | ExclusiveLock | relation | 1 segment +(3 rows) commit; -- drop index diff --git a/src/test/regress/expected/partition_locking_optimizer.out b/src/test/regress/expected/partition_locking_optimizer.out index ab7f426cd910..c19c6e9fdce1 100644 --- a/src/test/regress/expected/partition_locking_optimizer.out +++ b/src/test/regress/expected/partition_locking_optimizer.out @@ -272,12 +272,11 @@ select * from locktest_master where coalesce not like 'gp_%' and coalesce not li select * from locktest_segments where coalesce not like 'gp_%' and coalesce not like 'pg_%'; coalesce | mode | locktype | node -------------------+------------------+----------+------------ - partlockt | AccessShareLock | relation | n segments partlockt | RowExclusiveLock | relation | n segments partlockt_1_prt_1 | RowExclusiveLock | relation | 1 segment partlockt_1_prt_2 | RowExclusiveLock | relation | 1 segment partlockt_1_prt_3 | RowExclusiveLock | relation | 1 segment -(5 rows) +(4 rows) commit; -- drop @@ -443,10 +442,9 @@ select * from locktest_master where coalesce not like 'gp_%' and coalesce not li select * from locktest_segments where coalesce not like 'gp_%' and coalesce not like 'pg_%'; coalesce | mode | locktype | node -------------------+------------------+----------+----------- - partlockt | AccessShareLock | relation | 1 segment partlockt | RowExclusiveLock | relation | 1 segment partlockt_1_prt_3 | RowExclusiveLock | relation | 1 segment -(3 rows) +(2 rows) commit; -- delete locking @@ -457,25 +455,23 @@ delete from partlockt where i = 4; -- GPDB_12_MERGE_FIXME Revisit this post merge and see if we have a chance to unlock the pruned partitions -- end_ignore select * from locktest_master where coalesce not like 'gp_%' and coalesce not like 'pg_%'; - coalesce | mode | locktype | node --------------------------+-----------------+----------+-------- - partlockt | AccessShareLock | relation | master - partlockt | ExclusiveLock | relation | master - partlockt_1_prt_4 | ExclusiveLock | relation | master - partlockt_1_prt_4_i_idx | ExclusiveLock | relation | master -(4 rows) + coalesce | mode | locktype | node +-------------------------+---------------+----------+-------- + partlockt | ExclusiveLock | relation | master + partlockt_1_prt_4 | ExclusiveLock | relation | master + partlockt_1_prt_4_i_idx | ExclusiveLock | relation | master +(3 rows) -- start_ignore -- GPDB_12_MERGE_FIXME Revisit this post merge and see if we have a chance to unlock the root table -- end_ignore select * from locktest_segments where coalesce not like 'gp_%' and coalesce not like 'pg_%'; - coalesce | mode | locktype | node --------------------------+-----------------+----------+----------- - partlockt | AccessShareLock | relation | 1 segment - partlockt | ExclusiveLock | relation | 1 segment - partlockt_1_prt_4 | ExclusiveLock | relation | 1 segment - partlockt_1_prt_4_i_idx | ExclusiveLock | relation | 1 segment -(4 rows) + coalesce | mode | locktype | node +-------------------------+---------------+----------+----------- + partlockt | ExclusiveLock | relation | 1 segment + partlockt_1_prt_4 | ExclusiveLock | relation | 1 segment + partlockt_1_prt_4_i_idx | ExclusiveLock | relation | 1 segment +(3 rows) commit; -- drop index diff --git a/src/test/regress/expected/partition_optimizer.out b/src/test/regress/expected/partition_optimizer.out index a4cdcfa893af..72ec1285899e 100755 --- a/src/test/regress/expected/partition_optimizer.out +++ b/src/test/regress/expected/partition_optimizer.out @@ -4531,8 +4531,8 @@ create table tc default partition d, start (0) inclusive end(100) inclusive every (50) ); +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: PRIMARY KEY constraint on table "tc" lacks column "b" which is part of the partition key. -ERROR: insufficient columns in PRIMARY KEY constraint definition create table cc (a int primary key, b int, c int) distributed by (a) @@ -4541,8 +4541,8 @@ create table cc default partition d, start (0) inclusive end(100) inclusive every (50) ); +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: PRIMARY KEY constraint on table "cc" lacks column "b" which is part of the partition key. -ERROR: insufficient columns in PRIMARY KEY constraint definition create table at (a int, b int, c int) distributed by (a) @@ -4553,8 +4553,8 @@ create table at ); alter table at add primary key (a); +ERROR: unique constraint on partitioned table must include all partitioning columns DETAIL: PRIMARY KEY constraint on table "at" lacks column "b" which is part of the partition key. -ERROR: insufficient columns in PRIMARY KEY constraint definition -- MPP-14471 end -- MPP-17606 (using table "at" from above) alter table at @@ -4620,6 +4620,8 @@ create table plst2 partition p3 values ( CAST('(1,2)' as plst2_partkey) ) ); ERROR: partition "plst2_1_prt_p3" would overlap partition "plst2_1_prt_p1" +LINE 11: partition p3 values ( CAST('(1,2)' as plst2_partkey)... + ^ -- postive; make sure inner part duplicates are accepted and quietly removed. drop table if exists plst2; NOTICE: table "plst2" does not exist, skipping @@ -4656,6 +4658,8 @@ Distributed by: (d) -- negative; make sure conflicting alters fail. alter table plst2 add partition p6 values (CAST('(7,8)' as plst2_partkey), CAST('(2,1)' as plst2_partkey)); ERROR: partition "plst2_1_prt_p6" would overlap partition "plst2_1_prt_p5" +LINE 1: alter table plst2 add partition p6 values (CAST('(7,8)' as p... + ^ drop table if exists plst2; -- MPP-17814 end -- MPP-18441 diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index c63a6d3b1d2a..6767d9b2e111 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -4201,6 +4201,102 @@ explain (costs off) update listp1 set a = 1 where a = 2; reset constraint_exclusion; reset enable_partition_pruning; drop table listp; +-- Ensure run-time pruning works correctly for nested Append nodes +set parallel_setup_cost to 0; +set parallel_tuple_cost to 0; +create table listp (a int) partition by list(a); +create table listp_12 partition of listp for values in(1,2) partition by list(a); +create table listp_12_1 partition of listp_12 for values in(1); +create table listp_12_2 partition of listp_12 for values in(2); +-- Force the 2nd subnode of the Append to be non-parallel. This results in +-- a nested Append node because the mixed parallel / non-parallel paths cannot +-- be pulled into the top-level Append. +alter table listp_12_1 set (parallel_workers = 0); +-- Ensure that listp_12_2 is not scanned. (The nested Append is not seen in +-- the plan as it's pulled in setref.c due to having just a single subnode). +select explain_parallel_append('select * from listp where a = (select 1);'); + explain_parallel_append +-------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) (actual rows=N loops=N) + InitPlan 1 (returns $0) (slice2) + -> Result (actual rows=N loops=N) + -> Append (actual rows=N loops=N) + -> Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N) + Filter: (a = $0) + -> Seq Scan on listp_12_2 listp_2 (never executed) + Filter: (a = $0) + Optimizer: Postgres query optimizer +(9 rows) + +-- Like the above but throw some more complexity at the planner by adding +-- a UNION ALL. We expect both sides of the union not to scan the +-- non-required partitions. +select explain_parallel_append( +'select * from listp where a = (select 1) + union all +select * from listp where a = (select 2);'); + explain_parallel_append +-------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) (actual rows=N loops=N) + -> Append (actual rows=N loops=N) + -> Append (actual rows=N loops=N) + InitPlan 1 (returns $0) (slice2) + -> Result (actual rows=N loops=N) + -> Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N) + Filter: (a = $0) + -> Seq Scan on listp_12_2 listp_2 (never executed) + Filter: (a = $0) + -> Append (actual rows=N loops=N) + InitPlan 2 (returns $1) (slice3) + -> Result (actual rows=N loops=N) + -> Seq Scan on listp_12_1 listp_4 (never executed) + Filter: (a = $1) + -> Seq Scan on listp_12_2 listp_5 (actual rows=N loops=N) + Filter: (a = $1) + Optimizer: Postgres query optimizer +(17 rows) + +drop table listp; +reset parallel_tuple_cost; +reset parallel_setup_cost; +-- Test case for run-time pruning with a nested Merge Append +set enable_sort to 0; +create table rangep (a int, b int) partition by range (a); +create table rangep_0_to_100 partition of rangep for values from (0) to (100) partition by list (b); +-- We need 3 sub-partitions. 1 to validate pruning worked and another two +-- because a single remaining partition would be pulled up to the main Append. +create table rangep_0_to_100_1 partition of rangep_0_to_100 for values in(1); +create table rangep_0_to_100_2 partition of rangep_0_to_100 for values in(2); +create table rangep_0_to_100_3 partition of rangep_0_to_100 for values in(3); +create table rangep_100_to_200 partition of rangep for values from (100) to (200); +create index on rangep (a); +-- Ensure run-time pruning works on the nested Merge Append +explain (analyze on, costs off, timing off, summary off) +select * from rangep where b IN((select 1),(select 2)) order by a; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) + Merge Key: rangep.a + InitPlan 1 (returns $0) (slice2) + -> Result (actual rows=1 loops=1) + InitPlan 2 (returns $1) (slice3) + -> Result (actual rows=1 loops=1) + -> Append (actual rows=0 loops=1) + -> Merge Append (actual rows=0 loops=1) + Sort Key: rangep_2.a + -> Index Scan using rangep_0_to_100_1_a_idx on rangep_0_to_100_1 rangep_2 (actual rows=0 loops=1) + Filter: (b = ANY (ARRAY[$0, $1])) + -> Index Scan using rangep_0_to_100_2_a_idx on rangep_0_to_100_2 rangep_3 (actual rows=0 loops=1) + Filter: (b = ANY (ARRAY[$0, $1])) + -> Index Scan using rangep_0_to_100_3_a_idx on rangep_0_to_100_3 rangep_4 (never executed) + Filter: (b = ANY (ARRAY[$0, $1])) + -> Index Scan using rangep_100_to_200_a_idx on rangep_100_to_200 rangep_5 (actual rows=0 loops=1) + Filter: (b = ANY (ARRAY[$0, $1])) + Optimizer: Postgres query optimizer +(18 rows) + +reset enable_sort; +drop table rangep; -- -- Check that gen_prune_steps_from_opexps() works well for various cases of -- clauses for different partition keys diff --git a/src/test/regress/expected/partition_prune_optimizer.out b/src/test/regress/expected/partition_prune_optimizer.out index cf5c79d72009..0f92c7136679 100644 --- a/src/test/regress/expected/partition_prune_optimizer.out +++ b/src/test/regress/expected/partition_prune_optimizer.out @@ -4111,6 +4111,102 @@ explain (costs off) update listp1 set a = 1 where a = 2; reset constraint_exclusion; reset enable_partition_pruning; drop table listp; +-- Ensure run-time pruning works correctly for nested Append nodes +set parallel_setup_cost to 0; +set parallel_tuple_cost to 0; +create table listp (a int) partition by list(a); +create table listp_12 partition of listp for values in(1,2) partition by list(a); +create table listp_12_1 partition of listp_12 for values in(1); +create table listp_12_2 partition of listp_12 for values in(2); +-- Force the 2nd subnode of the Append to be non-parallel. This results in +-- a nested Append node because the mixed parallel / non-parallel paths cannot +-- be pulled into the top-level Append. +alter table listp_12_1 set (parallel_workers = 0); +-- Ensure that listp_12_2 is not scanned. (The nested Append is not seen in +-- the plan as it's pulled in setref.c due to having just a single subnode). +select explain_parallel_append('select * from listp where a = (select 1);'); + explain_parallel_append +-------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) (actual rows=N loops=N) + InitPlan 1 (returns $0) (slice2) + -> Result (actual rows=N loops=N) + -> Append (actual rows=N loops=N) + -> Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N) + Filter: (a = $0) + -> Seq Scan on listp_12_2 listp_2 (never executed) + Filter: (a = $0) + Optimizer: Postgres query optimizer +(9 rows) + +-- Like the above but throw some more complexity at the planner by adding +-- a UNION ALL. We expect both sides of the union not to scan the +-- non-required partitions. +select explain_parallel_append( +'select * from listp where a = (select 1) + union all +select * from listp where a = (select 2);'); + explain_parallel_append +-------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) (actual rows=N loops=N) + -> Append (actual rows=N loops=N) + -> Append (actual rows=N loops=N) + InitPlan 1 (returns $0) (slice2) + -> Result (actual rows=N loops=N) + -> Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N) + Filter: (a = $0) + -> Seq Scan on listp_12_2 listp_2 (never executed) + Filter: (a = $0) + -> Append (actual rows=N loops=N) + InitPlan 2 (returns $1) (slice3) + -> Result (actual rows=N loops=N) + -> Seq Scan on listp_12_1 listp_4 (never executed) + Filter: (a = $1) + -> Seq Scan on listp_12_2 listp_5 (actual rows=N loops=N) + Filter: (a = $1) + Optimizer: Postgres query optimizer +(17 rows) + +drop table listp; +reset parallel_tuple_cost; +reset parallel_setup_cost; +-- Test case for run-time pruning with a nested Merge Append +set enable_sort to 0; +create table rangep (a int, b int) partition by range (a); +create table rangep_0_to_100 partition of rangep for values from (0) to (100) partition by list (b); +-- We need 3 sub-partitions. 1 to validate pruning worked and another two +-- because a single remaining partition would be pulled up to the main Append. +create table rangep_0_to_100_1 partition of rangep_0_to_100 for values in(1); +create table rangep_0_to_100_2 partition of rangep_0_to_100 for values in(2); +create table rangep_0_to_100_3 partition of rangep_0_to_100 for values in(3); +create table rangep_100_to_200 partition of rangep for values from (100) to (200); +create index on rangep (a); +-- Ensure run-time pruning works on the nested Merge Append +explain (analyze on, costs off, timing off, summary off) +select * from rangep where b IN((select 1),(select 2)) order by a; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) + Merge Key: rangep.a + InitPlan 1 (returns $0) (slice2) + -> Result (actual rows=1 loops=1) + InitPlan 2 (returns $1) (slice3) + -> Result (actual rows=1 loops=1) + -> Append (actual rows=0 loops=1) + -> Merge Append (actual rows=0 loops=1) + Sort Key: rangep_2.a + -> Index Scan using rangep_0_to_100_1_a_idx on rangep_0_to_100_1 rangep_2 (actual rows=0 loops=1) + Filter: (b = ANY (ARRAY[$0, $1])) + -> Index Scan using rangep_0_to_100_2_a_idx on rangep_0_to_100_2 rangep_3 (actual rows=0 loops=1) + Filter: (b = ANY (ARRAY[$0, $1])) + -> Index Scan using rangep_0_to_100_3_a_idx on rangep_0_to_100_3 rangep_4 (never executed) + Filter: (b = ANY (ARRAY[$0, $1])) + -> Index Scan using rangep_100_to_200_a_idx on rangep_100_to_200 rangep_5 (actual rows=0 loops=1) + Filter: (b = ANY (ARRAY[$0, $1])) + Optimizer: Postgres query optimizer +(18 rows) + +reset enable_sort; +drop table rangep; -- -- Check that gen_prune_steps_from_opexps() works well for various cases of -- clauses for different partition keys diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 13f153d0c343..63e59bebfb80 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -2816,20 +2816,28 @@ Argument data types | numeric Type | func \pset tuples_only false --- check conditional tableam display --- Create a heap2 table am handler with heapam handler +-- check conditional am display +\pset expanded off +CREATE SCHEMA tableam_display; +CREATE ROLE regress_display_role; +ALTER SCHEMA tableam_display OWNER TO regress_display_role; +SET search_path TO tableam_display; CREATE ACCESS METHOD heap_psql TYPE TABLE HANDLER heap_tableam_handler; +SET ROLE TO regress_display_role; +-- Use only relations with a physical size of zero. CREATE TABLE tbl_heap_psql(f1 int, f2 char(100)) using heap_psql; CREATE TABLE tbl_heap(f1 int, f2 char(100)) using heap; +CREATE VIEW view_heap_psql AS SELECT f1 from tbl_heap_psql; +CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tbl_heap_psql; \d+ tbl_heap_psql - Table "public.tbl_heap_psql" + Table "tableam_display.tbl_heap_psql" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+----------------+-----------+----------+---------+----------+--------------+------------- f1 | integer | | | | plain | | f2 | character(100) | | | | extended | | \d+ tbl_heap - Table "public.tbl_heap" + Table "tableam_display.tbl_heap" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+----------------+-----------+----------+---------+----------+--------------+------------- f1 | integer | | | | plain | | @@ -2837,7 +2845,7 @@ CREATE TABLE tbl_heap(f1 int, f2 char(100)) using heap; \set HIDE_TABLEAM off \d+ tbl_heap_psql - Table "public.tbl_heap_psql" + Table "tableam_display.tbl_heap_psql" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+----------------+-----------+----------+---------+----------+--------------+------------- f1 | integer | | | | plain | | @@ -2845,16 +2853,68 @@ CREATE TABLE tbl_heap(f1 int, f2 char(100)) using heap; Access method: heap_psql \d+ tbl_heap - Table "public.tbl_heap" + Table "tableam_display.tbl_heap" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+----------------+-----------+----------+---------+----------+--------------+------------- f1 | integer | | | | plain | | f2 | character(100) | | | | extended | | Access method: heap +-- AM is displayed for tables, indexes and materialized views. +\d+ + List of relations + Schema | Name | Type | Owner | Storage | Persistence | Access Method | Size | Description +-----------------+--------------------+-------------------+----------------------+-----------+-------------+---------------+---------+------------- + tableam_display | mat_view_heap_psql | materialized view | regress_display_role | heap_psql | permanent | heap_psql | 0 bytes | + tableam_display | tbl_heap | table | regress_display_role | heap | permanent | heap | 0 bytes | + tableam_display | tbl_heap_psql | table | regress_display_role | heap_psql | permanent | heap_psql | 0 bytes | + tableam_display | view_heap_psql | view | regress_display_role | | permanent | | 0 bytes | +(4 rows) + +\dt+ + List of relations + Schema | Name | Type | Owner | Storage | Persistence | Access Method | Size | Description +-----------------+---------------+-------+----------------------+-----------+-------------+---------------+---------+------------- + tableam_display | tbl_heap | table | regress_display_role | heap | permanent | heap | 0 bytes | + tableam_display | tbl_heap_psql | table | regress_display_role | heap_psql | permanent | heap_psql | 0 bytes | +(2 rows) + +\dm+ + List of relations + Schema | Name | Type | Owner | Persistence | Access Method | Size | Description +-----------------+--------------------+-------------------+----------------------+-------------+---------------+---------+------------- + tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes | +(1 row) + +-- But not for views and sequences. +\dv+ + List of relations + Schema | Name | Type | Owner | Persistence | Size | Description +-----------------+----------------+------+----------------------+-------------+---------+------------- + tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes | +(1 row) + \set HIDE_TABLEAM on -DROP TABLE tbl_heap, tbl_heap_psql; +\d+ + List of relations + Schema | Name | Type | Owner | Storage | Persistence | Size | Description +-----------------+--------------------+-------------------+----------------------+-----------+-------------+---------+------------- + tableam_display | mat_view_heap_psql | materialized view | regress_display_role | heap_psql | permanent | 0 bytes | + tableam_display | tbl_heap | table | regress_display_role | heap | permanent | 0 bytes | + tableam_display | tbl_heap_psql | table | regress_display_role | heap_psql | permanent | 0 bytes | + tableam_display | view_heap_psql | view | regress_display_role | | permanent | 0 bytes | +(4 rows) + +RESET ROLE; +RESET search_path; +DROP SCHEMA tableam_display CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table tableam_display.tbl_heap_psql +drop cascades to table tableam_display.tbl_heap +drop cascades to view tableam_display.view_heap_psql +drop cascades to materialized view tableam_display.mat_view_heap_psql DROP ACCESS METHOD heap_psql; +DROP ROLE regress_display_role; -- test numericlocale (as best we can without control of psql's locale) \pset format aligned \pset expanded off diff --git a/src/test/regress/expected/qp_misc_jiras.out b/src/test/regress/expected/qp_misc_jiras.out index 6d3f638eb1e2..1e3b8a64726c 100644 --- a/src/test/regress/expected/qp_misc_jiras.out +++ b/src/test/regress/expected/qp_misc_jiras.out @@ -2967,7 +2967,7 @@ select relname, reltuples, relpages from pg_class where relname like 'tbl6833_an ------------------------+-----------+---------- tbl6833_anl_1_prt_bb_1 | 0 | 1 tbl6833_anl_1_prt_bb_2 | 0 | 1 - tbl6833_anl | 0 | 0 + tbl6833_anl | -1 | 0 (3 rows) create table qp_misc_jiras.tbl6833_vac(a int, b int, c int) distributed by (a) partition by range (b) (partition bb start (0) end (2) every (1)); @@ -2987,7 +2987,7 @@ select relname, reltuples, relpages from pg_class where relname like 'tbl6833_va ------------------------+-----------+---------- tbl6833_vac_1_prt_bb_1 | 0 | 1 tbl6833_vac_1_prt_bb_2 | 0 | 1 - tbl6833_vac | 0 | 0 + tbl6833_vac | -1 | 0 (3 rows) drop table qp_misc_jiras.tbl6833_anl; diff --git a/src/test/regress/expected/qp_misc_jiras_optimizer.out b/src/test/regress/expected/qp_misc_jiras_optimizer.out index 064a8fc26b5c..63ad17804046 100644 --- a/src/test/regress/expected/qp_misc_jiras_optimizer.out +++ b/src/test/regress/expected/qp_misc_jiras_optimizer.out @@ -2963,7 +2963,7 @@ select relname, reltuples, relpages from pg_class where relname like 'tbl6833_an ------------------------+-----------+---------- tbl6833_anl_1_prt_bb_1 | 0 | 1 tbl6833_anl_1_prt_bb_2 | 0 | 1 - tbl6833_anl | 0 | 0 + tbl6833_anl | -1 | 0 (3 rows) create table qp_misc_jiras.tbl6833_vac(a int, b int, c int) distributed by (a) partition by range (b) (partition bb start (0) end (2) every (1)); @@ -2980,7 +2980,7 @@ explain select * from qp_misc_jiras.tbl6833_vac; -- should not hit cdbRelSize(); select relname, reltuples, relpages from pg_class where relname like 'tbl6833_vac%'; -- should show relpages = 1.0 relname | reltuples | relpages ------------------------+-----------+---------- - tbl6833_vac | 0 | 0 + tbl6833_vac | -1 | 0 tbl6833_vac_1_prt_bb_1 | 0 | 1 tbl6833_vac_1_prt_bb_2 | 0 | 1 (3 rows) diff --git a/src/test/regress/expected/qp_misc_rio.out b/src/test/regress/expected/qp_misc_rio.out index 53e20979e31d..514325ea6f53 100644 --- a/src/test/regress/expected/qp_misc_rio.out +++ b/src/test/regress/expected/qp_misc_rio.out @@ -730,9 +730,9 @@ order by a; -- does. The fix was submitted to upstream PostgreSQL and fixed there in -- version 8.4.16 (commit 5c4eb9166e.) -- ---------------------------------------------------------------------- -select to_date('-4713-11-23', 'yyyy-mm-dd'); -ERROR: date out of range: "-4713-11-23" -select to_date('-4713-11-24', 'yyyy-mm-dd'); +select to_date('-4714-11-23', 'yyyy-mm-dd'); +ERROR: date out of range: "-4714-11-23" +select to_date('-4714-11-24', 'yyyy-mm-dd'); to_date --------------- 11-24-4714 BC diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index cf22e2f80401..3230dbf63c7c 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -2069,6 +2069,66 @@ select * from testrngfunc(); 7.136178 | 7.14 (1 row) +create or replace function testrngfunc() returns setof rngfunc_type as $$ + select 1, 2 union select 3, 4 order by 1; +$$ language sql immutable; +explain (verbose, costs off) +select testrngfunc(); + QUERY PLAN +------------------------- + ProjectSet + Output: testrngfunc() + -> Result +(3 rows) + +select testrngfunc(); + testrngfunc +----------------- + (1.000000,2.00) + (3.000000,4.00) +(2 rows) + +explain (verbose, costs off) +select * from testrngfunc(); + QUERY PLAN +---------------------------------------------------------- + Subquery Scan on "*SELECT*" + Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1" + -> Sort + Output: (1), (2) + Sort Key: (1) + -> HashAggregate + Output: (1), (2) + Group Key: (1), (2) + -> Append + -> Result + Output: 1, 2 + -> Result + Output: 3, 4 + Optimizer: Postgres query optimizer + Settings: optimizer = 'off' +(15 rows) + +select * from testrngfunc(); + f1 | f2 +----------+------ + 1.000000 | 2.00 + 3.000000 | 4.00 +(2 rows) + +-- Check a couple of error cases while we're here +select * from testrngfunc() as t(f1 int8,f2 int8); -- fail, composite result +ERROR: a column definition list is redundant for a function returning a named composite type +LINE 1: select * from testrngfunc() as t(f1 int8,f2 int8); + ^ +select * from pg_get_keywords() as t(f1 int8,f2 int8); -- fail, OUT params +ERROR: a column definition list is redundant for a function with OUT parameters +LINE 1: select * from pg_get_keywords() as t(f1 int8,f2 int8); + ^ +select * from sin(3) as t(f1 int8,f2 int8); -- fail, scalar result type +ERROR: a column definition list is only allowed for functions returning "record" +LINE 1: select * from sin(3) as t(f1 int8,f2 int8); + ^ drop type rngfunc_type cascade; NOTICE: drop cascades to function testrngfunc() -- diff --git a/src/test/regress/expected/rangefuncs_optimizer.out b/src/test/regress/expected/rangefuncs_optimizer.out index 0d304d4e12a8..089c6f541150 100644 --- a/src/test/regress/expected/rangefuncs_optimizer.out +++ b/src/test/regress/expected/rangefuncs_optimizer.out @@ -2083,6 +2083,57 @@ select * from testrngfunc(); 7.136178 | 7.14 (1 row) +create or replace function testrngfunc() returns setof rngfunc_type as $$ + select 1, 2 union select 3, 4 order by 1; +$$ language sql immutable; +explain (verbose, costs off) +select testrngfunc(); + QUERY PLAN +--------------------------------------- + ProjectSet + Output: testrngfunc() + -> Result + Output: true + Optimizer: Pivotal Optimizer (GPORCA) +(5 rows) + +select testrngfunc(); + testrngfunc +----------------- + (1.000000,2.00) + (3.000000,4.00) +(2 rows) + +explain (verbose, costs off) +select * from testrngfunc(); + QUERY PLAN +--------------------------------------- + Function Scan on public.testrngfunc + Output: f1, f2 + Function Call: testrngfunc() + Optimizer: Pivotal Optimizer (GPORCA) +(4 rows) + +select * from testrngfunc(); + f1 | f2 +----------+------ + 1.000000 | 2.00 + 3.000000 | 4.00 +(2 rows) + +-- Check a couple of error cases while we're here +select * from testrngfunc() as t(f1 int8,f2 int8); -- fail, composite result +ERROR: a column definition list is redundant for a function returning a named composite type +LINE 1: select * from testrngfunc() as t(f1 int8,f2 int8); + ^ +select * from pg_get_keywords() as t(f1 int8,f2 int8); -- fail, OUT params +ERROR: a column definition list is redundant for a function with OUT parameters +LINE 1: select * from pg_get_keywords() as t(f1 int8,f2 int8); + ^ +select * from sin(3) as t(f1 int8,f2 int8); -- fail, scalar result type +ERROR: a column definition list is only allowed for functions returning "record" +LINE 1: select * from sin(3) as t(f1 int8,f2 int8); + ^ drop type rngfunc_type cascade; NOTICE: drop cascades to function testrngfunc() -- diff --git a/src/test/regress/expected/reindex_catalog.out b/src/test/regress/expected/reindex_catalog.out index 4b5fba494939..204f056c9a56 100644 --- a/src/test/regress/expected/reindex_catalog.out +++ b/src/test/regress/expected/reindex_catalog.out @@ -36,3 +36,13 @@ REINDEX INDEX pg_index_indexrelid_index; -- non-mapped, non-shared, critical REINDEX INDEX pg_index_indrelid_index; -- non-mapped, non-shared, non-critical REINDEX INDEX pg_database_oid_index; -- mapped, shared, critical REINDEX INDEX pg_shdescription_o_c_index; -- mapped, shared, non-critical +-- Check the same REINDEX INDEX statements under parallelism. +BEGIN; +SET min_parallel_table_scan_size = 0; +REINDEX INDEX pg_class_oid_index; -- mapped, non-shared, critical +REINDEX INDEX pg_class_relname_nsp_index; -- mapped, non-shared, non-critical +REINDEX INDEX pg_index_indexrelid_index; -- non-mapped, non-shared, critical +REINDEX INDEX pg_index_indrelid_index; -- non-mapped, non-shared, non-critical +REINDEX INDEX pg_database_oid_index; -- mapped, shared, critical +REINDEX INDEX pg_shdescription_o_c_index; -- mapped, shared, non-critical +ROLLBACK; diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a6509c011c8b..4d46044c8372 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2014,6 +2014,15 @@ pg_stat_replication| SELECT s.pid, FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid) JOIN pg_stat_get_wal_senders() w(pid, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag, sync_priority, sync_state, reply_time) ON ((s.pid = w.pid))) LEFT JOIN pg_authid u ON ((s.usesysid = u.oid))); +pg_stat_replication_slots| SELECT s.slot_name, + s.spill_txns, + s.spill_count, + s.spill_bytes, + s.stream_txns, + s.stream_count, + s.stream_bytes, + s.stats_reset + FROM pg_stat_get_replication_slots() s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, stats_reset); pg_stat_slru| SELECT s.name, s.blks_zeroed, s.blks_hit, @@ -2125,6 +2134,9 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid, pg_stat_all_tables.autoanalyze_count FROM pg_stat_all_tables WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text)); +pg_stat_wal| SELECT w.wal_buffers_full, + w.stats_reset + FROM pg_stat_get_wal() w(wal_buffers_full, stats_reset); pg_stat_wal_receiver| SELECT s.pid, s.status, s.receive_start_lsn, diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out index 61561300d4a7..7d1ff63d76e3 100644 --- a/src/test/regress/expected/select_parallel.out +++ b/src/test/regress/expected/select_parallel.out @@ -1108,10 +1108,11 @@ EXPLAIN (analyze, timing off, summary off, costs off) SELECT * FROM tenk1; ROLLBACK TO SAVEPOINT settings; -- provoke error in worker +-- (make the error message long enough to require multiple bufferloads) SAVEPOINT settings; SET LOCAL force_parallel_mode = 1; -select stringu1::int2 from tenk1 where unique1 = 1; -ERROR: invalid input syntax for type smallint: "BAAAAA" +select (stringu1 || repeat('abcd', 5000))::int2 from tenk1 where unique1 = 1; +ERROR: invalid input syntax for type smallint: "BAAAAAabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd" CONTEXT: parallel worker ROLLBACK TO SAVEPOINT settings; -- test interaction with set-returning functions diff --git a/src/test/regress/expected/select_parallel_optimizer.out b/src/test/regress/expected/select_parallel_optimizer.out index 8113d54da3d1..aaf723c01f37 100644 --- a/src/test/regress/expected/select_parallel_optimizer.out +++ b/src/test/regress/expected/select_parallel_optimizer.out @@ -1119,11 +1119,11 @@ EXPLAIN (analyze, timing off, summary off, costs off) SELECT * FROM tenk1; ROLLBACK TO SAVEPOINT settings; -- provoke error in worker +-- (make the error message long enough to require multiple bufferloads) SAVEPOINT settings; SET LOCAL force_parallel_mode = 1; -select stringu1::int2 from tenk1 where unique1 = 1; -ERROR: invalid input syntax for type smallint: "BAAAAA" -CONTEXT: parallel worker +select (stringu1 || repeat('abcd', 5000))::int2 from tenk1 where unique1 = 1; +ERROR: invalid input syntax for type smallint: "BAAAAAabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd" (seg1 slice1 172.22.0.2:8003 pid=350892) ROLLBACK TO SAVEPOINT settings; -- test interaction with set-returning functions SAVEPOINT settings; diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out index 3ab9749b0493..a17640bd86f2 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -108,6 +108,15 @@ WARNING: statistics object "public.ab1_a_b_stats" could not be computed for rel ALTER TABLE ab1 ALTER a SET STATISTICS -1; -- setting statistics target 0 skips the statistics, without printing any message, so check catalog ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0; +\d ab1 + Table "public.ab1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | +Statistics objects: + "public"."ab1_a_b_stats" (ndistinct, dependencies, mcv) ON a, b FROM ab1; STATISTICS 0 + ANALYZE ab1; SELECT stxname, stxdndistinct, stxddependencies, stxdmcv FROM pg_statistic_ext s, pg_statistic_ext_data d @@ -119,6 +128,15 @@ SELECT stxname, stxdndistinct, stxddependencies, stxdmcv (1 row) ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1; +\d+ ab1 + Table "public.ab1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | +Statistics objects: + "public"."ab1_a_b_stats" (ndistinct, dependencies, mcv) ON a, b FROM ab1 + -- partial analyze doesn't build stats either ANALYZE ab1 (a); WARNING: statistics object "public.ab1_a_b_stats" could not be computed for relation "public.ab1" diff --git a/src/test/regress/expected/stats_ext_optimizer.out b/src/test/regress/expected/stats_ext_optimizer.out index 54a4c741c6b5..4f258cc1b34b 100644 --- a/src/test/regress/expected/stats_ext_optimizer.out +++ b/src/test/regress/expected/stats_ext_optimizer.out @@ -113,6 +113,15 @@ WARNING: statistics object "public.ab1_a_b_stats" could not be computed for rel ALTER TABLE ab1 ALTER a SET STATISTICS -1; -- setting statistics target 0 skips the statistics, without printing any message, so check catalog ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0; +\d ab1 + Table "public.ab1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | +Statistics objects: + "public"."ab1_a_b_stats" (ndistinct, dependencies, mcv) ON a, b FROM ab1; STATISTICS 0 + ANALYZE ab1; SELECT stxname, stxdndistinct, stxddependencies, stxdmcv FROM pg_statistic_ext s, pg_statistic_ext_data d @@ -124,6 +133,15 @@ SELECT stxname, stxdndistinct, stxddependencies, stxdmcv (1 row) ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1; +\d+ ab1 + Table "public.ab1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | +Statistics objects: + "public"."ab1_a_b_stats" (ndistinct, dependencies, mcv) ON a, b FROM ab1 + -- partial analyze doesn't build stats either ANALYZE ab1 (a); WARNING: statistics object "public.ab1_a_b_stats" could not be computed for relation "public.ab1" diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out index 8fbc6a79146d..30225d64e52e 100644 --- a/src/test/regress/expected/subscription.out +++ b/src/test/regress/expected/subscription.out @@ -74,10 +74,10 @@ DROP SUBSCRIPTION regress_testsub3; ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar'; ERROR: invalid connection string syntax: missing "=" after "foobar" in connection info string \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Synchronous commit | Conninfo ------------------+---------------------------+---------+-------------+--------+--------------------+----------------------------- - regress_testsub | regress_subscription_user | f | {testpub} | f | off | dbname=regress_doesnotexist + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo +-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+----------------------------- + regress_testsub | regress_subscription_user | f | {testpub} | f | f | off | dbname=regress_doesnotexist (1 row) ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false); @@ -89,10 +89,10 @@ ERROR: subscription "regress_doesnotexist" does not exist ALTER SUBSCRIPTION regress_testsub SET (create_slot = false); ERROR: unrecognized subscription parameter: "create_slot" \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Synchronous commit | Conninfo ------------------+---------------------------+---------+---------------------+--------+--------------------+------------------------------ - regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | dbname=regress_doesnotexist2 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo +-----------------+---------------------------+---------+---------------------+--------+-----------+--------------------+------------------------------ + regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | f | off | dbname=regress_doesnotexist2 (1 row) BEGIN; @@ -124,10 +124,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar); ERROR: invalid value for parameter "synchronous_commit": "foobar" HINT: Available values: local, remote_write, remote_apply, on, off. \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Synchronous commit | Conninfo ----------------------+---------------------------+---------+---------------------+--------+--------------------+------------------------------ - regress_testsub_foo | regress_subscription_user | f | {testpub2,testpub3} | f | local | dbname=regress_doesnotexist2 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo +---------------------+---------------------------+---------+---------------------+--------+-----------+--------------------+------------------------------ + regress_testsub_foo | regress_subscription_user | f | {testpub2,testpub3} | f | f | local | dbname=regress_doesnotexist2 (1 row) -- rename back to keep the rest simple @@ -146,10 +146,10 @@ ERROR: DROP SUBSCRIPTION cannot run inside a transaction block COMMIT; ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE); \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Synchronous commit | Conninfo ------------------+----------------------------+---------+---------------------+--------+--------------------+------------------------------ - regress_testsub | regress_subscription_user2 | f | {testpub2,testpub3} | f | local | dbname=regress_doesnotexist2 + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo +-----------------+----------------------------+---------+---------------------+--------+-----------+--------------------+------------------------------ + regress_testsub | regress_subscription_user2 | f | {testpub2,testpub3} | f | f | local | dbname=regress_doesnotexist2 (1 row) -- now it works @@ -167,19 +167,42 @@ ERROR: binary requires a Boolean value CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, binary = true); WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Synchronous commit | Conninfo ------------------+---------------------------+---------+-------------+--------+--------------------+----------------------------- - regress_testsub | regress_subscription_user | f | {testpub} | t | off | dbname=regress_doesnotexist + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo +-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+----------------------------- + regress_testsub | regress_subscription_user | f | {testpub} | t | f | off | dbname=regress_doesnotexist (1 row) ALTER SUBSCRIPTION regress_testsub SET (binary = false); ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE); \dRs+ - List of subscriptions - Name | Owner | Enabled | Publication | Binary | Synchronous commit | Conninfo ------------------+---------------------------+---------+-------------+--------+--------------------+----------------------------- - regress_testsub | regress_subscription_user | f | {testpub} | f | off | dbname=regress_doesnotexist + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo +-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+----------------------------- + regress_testsub | regress_subscription_user | f | {testpub} | f | f | off | dbname=regress_doesnotexist +(1 row) + +DROP SUBSCRIPTION regress_testsub; +-- fail - streaming must be boolean +CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, streaming = foo); +ERROR: streaming requires a Boolean value +-- now it works +CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, streaming = true); +WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables +\dRs+ + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo +-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+----------------------------- + regress_testsub | regress_subscription_user | f | {testpub} | f | t | off | dbname=regress_doesnotexist +(1 row) + +ALTER SUBSCRIPTION regress_testsub SET (streaming = false); +ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE); +\dRs+ + List of subscriptions + Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo +-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+----------------------------- + regress_testsub | regress_subscription_user | f | {testpub} | f | f | off | dbname=regress_doesnotexist (1 row) DROP SUBSCRIPTION regress_testsub; diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index bf4227c61433..75925377d39d 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -883,6 +883,63 @@ select * from int8_tbl where q1 in (select c1 from inner_text); (2 rows) rollback; -- to get rid of the bogus operator +-- +-- Test resolution of hashed vs non-hashed implementation of EXISTS subplan +-- +explain (costs off) +select count(*) from tenk1 t +where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0); + QUERY PLAN +----------------------------------------------------------------------- + Finalize Aggregate + -> Gather Motion 3:1 (slice1; segments: 3) + -> Partial Aggregate + -> Seq Scan on tenk1 t + Filter: ((hashed SubPlan 2) OR (ten < 0)) + SubPlan 2 + -> Broadcast Motion 3:3 (slice2; segments: 3) + -> Seq Scan on tenk1 k + Optimizer: Postgres query optimizer +(9 rows) + +select count(*) from tenk1 t +where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0); + count +------- + 10000 +(1 row) + +explain (costs off) +select count(*) from tenk1 t +where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0) + and thousand = 1; + QUERY PLAN +----------------------------------------------------------------------------------- + Finalize Aggregate + -> Gather Motion 3:1 (slice1; segments: 3) + -> Partial Aggregate + -> Bitmap Heap Scan on tenk1 t + Recheck Cond: (thousand = 1) + Filter: ((SubPlan 1) OR (ten < 0)) + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: (thousand = 1) + SubPlan 1 + -> Result + Filter: (k.unique1 = t.unique2) + -> Materialize + -> Broadcast Motion 3:3 (slice2; segments: 3) + -> Seq Scan on tenk1 k + Optimizer: Postgres query optimizer +(15 rows) + +select count(*) from tenk1 t +where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0) + and thousand = 1; + count +------- + 10 +(1 row) + -- -- Test case for planner bug with nested EXISTS handling -- diff --git a/src/test/regress/expected/subselect_gp.out b/src/test/regress/expected/subselect_gp.out index 6916a1a41365..4a5009f9c6f0 100644 --- a/src/test/regress/expected/subselect_gp.out +++ b/src/test/regress/expected/subselect_gp.out @@ -1800,23 +1800,17 @@ EXPLAIN select count(distinct ss.ten) from -- we should see 2 subplans in the explain -- EXPLAIN SELECT EXISTS(SELECT * FROM tenk1 WHERE tenk1.unique1 = tenk2.unique1) FROM tenk2 LIMIT 1; - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- - Limit (cost=0.00..0.08 rows=1 width=4) - -> Gather Motion 3:1 (slice3; segments: 3) (cost=0.00..0.08 rows=1 width=4) - -> Limit (cost=0.00..0.06 rows=1 width=4) - -> Seq Scan on tenk2 (cost=0.00..625.00 rows=3334 width=4) - SubPlan 1 - -> Result (cost=0.00..439.00 rows=10000 width=4) - Filter: (tenk1.unique1 = tenk2.unique1) - -> Materialize (cost=0.00..439.00 rows=10000 width=4) - -> Broadcast Motion 3:3 (slice1; segments: 3) (cost=0.00..389.00 rows=3334 width=4) - -> Seq Scan on tenk1 (cost=0.00..189.00 rows=3334 width=4) + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Limit (cost=17.76..17.79 rows=1 width=1) + -> Gather Motion 3:1 (slice1; segments: 3) (cost=17.76..17.85 rows=3 width=1) + -> Limit (cost=17.76..17.81 rows=1 width=1) + -> Seq Scan on tenk2 (cost=0.00..177.56 rows=3333 width=1) SubPlan 2 - -> Broadcast Motion 3:3 (slice2; segments: 3) (cost=0.00..189.00 rows=3334 width=4) - -> Seq Scan on tenk1 tenk1_1 (cost=0.00..189.00 rows=3334 width=4) + -> Broadcast Motion 3:3 (slice2; segments: 3) (cost=0.00..62.33 rows=3333 width=4) + -> Seq Scan on tenk1 (cost=0.00..62.33 rows=3333 width=4) Optimizer: Postgres query optimizer -(14 rows) +(8 rows) SELECT EXISTS(SELECT * FROM tenk1 WHERE tenk1.unique1 = tenk2.unique1) FROM tenk2 LIMIT 1; exists diff --git a/src/test/regress/expected/subselect_optimizer.out b/src/test/regress/expected/subselect_optimizer.out index 6387b9cd9efb..4667207b1317 100644 --- a/src/test/regress/expected/subselect_optimizer.out +++ b/src/test/regress/expected/subselect_optimizer.out @@ -926,6 +926,71 @@ select * from int8_tbl where q1 in (select c1 from inner_text); (2 rows) rollback; -- to get rid of the bogus operator +-- +-- Test resolution of hashed vs non-hashed implementation of EXISTS subplan +-- +explain (costs off) +select count(*) from tenk1 t +where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------ + Finalize Aggregate + -> Gather Motion 3:1 (slice1; segments: 3) + -> Partial Aggregate + -> HashAggregate + Group Key: tenk1.unique1, tenk1.unique2, tenk1.ten, tenk1.ctid, tenk1.gp_segment_id, (true) + -> Result + Filter: (CASE WHEN (NOT ((true) IS NULL)) THEN true ELSE false END OR (tenk1.ten < 0)) + -> Hash Left Join + Hash Cond: (tenk1.unique2 = tenk1_1.unique1) + -> Redistribute Motion 3:3 (slice2; segments: 3) + Hash Key: tenk1.unique2 + -> Seq Scan on tenk1 + -> Hash + -> Seq Scan on tenk1 tenk1_1 + Optimizer: Pivotal Optimizer (GPORCA) +(15 rows) + +select count(*) from tenk1 t +where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0); + count +------- + 10000 +(1 row) + +explain (costs off) +select count(*) from tenk1 t +where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0) + and thousand = 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + -> Gather Motion 3:1 (slice1; segments: 3) + -> GroupAggregate + Group Key: tenk1_1.unique1, tenk1_1.unique2, tenk1_1.ten, tenk1_1.thousand, tenk1_1.ctid, tenk1_1.gp_segment_id, (true) + -> Sort + Sort Key: tenk1_1.unique1, tenk1_1.unique2, tenk1_1.ten, tenk1_1.thousand, tenk1_1.ctid, tenk1_1.gp_segment_id, (true) + -> Result + Filter: (CASE WHEN (NOT ((true) IS NULL)) THEN true ELSE false END OR (tenk1_1.ten < 0)) + -> Hash Right Join + Hash Cond: (tenk1.unique1 = tenk1_1.unique2) + -> Seq Scan on tenk1 + -> Hash + -> Redistribute Motion 3:3 (slice2; segments: 3) + Hash Key: tenk1_1.unique2 + -> Index Scan using tenk1_thous_tenthous on tenk1 tenk1_1 + Index Cond: (thousand = 1) + Optimizer: Pivotal Optimizer (GPORCA) +(17 rows) + +select count(*) from tenk1 t +where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0) + and thousand = 1; + count +------- + 10 +(1 row) + -- -- Test case for planner bug with nested EXISTS handling -- diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out index 32799be1caed..a787e969a14c 100644 --- a/src/test/regress/expected/sysviews.out +++ b/src/test/regress/expected/sysviews.out @@ -19,6 +19,15 @@ select count(*) >= 0 as ok from pg_available_extensions; t (1 row) +-- The entire output of pg_backend_memory_contexts is not stable, +-- we test only the existance and basic condition of TopMemoryContext. +select name, ident, parent, level, total_bytes >= free_bytes + from pg_backend_memory_contexts where level = 0; + name | ident | parent | level | ?column? +------------------+-------+--------+-------+---------- + TopMemoryContext | | | 0 | t +(1 row) + -- At introduction, pg_config had 23 entries; it may grow select count(*) > 20 as ok from pg_config; ok @@ -67,6 +76,13 @@ select count(*) >= 0 as ok from pg_prepared_xacts; t (1 row) +-- There must be only one record +select count(*) = 1 as ok from pg_stat_wal; + ok +---- + t +(1 row) + -- This is to record the prevailing planner enable_foo settings during -- a regression test run. select name, setting from pg_settings where name like 'enable%'; diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out index a5a54243fb67..6a6f91e77621 100644 --- a/src/test/regress/expected/timestamp.out +++ b/src/test/regress/expected/timestamp.out @@ -1834,13 +1834,6 @@ SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012 (4 rows) --- timestamp numeric fields constructor -SELECT make_timestamp(2014,12,28,6,30,45.887); - make_timestamp ------------------------------- - Sun Dec 28 06:30:45.887 2014 -(1 row) - SET DateStyle TO DEFAULT; -- Make sure timeofdate() and current_time() are doing roughly the same thing select timeofday()::date = current_timestamp::date; @@ -1849,3 +1842,19 @@ select timeofday()::date = current_timestamp::date; t (1 row) +-- timestamp numeric fields constructor +SELECT make_timestamp(2014, 12, 28, 6, 30, 45.887); + make_timestamp +------------------------------ + Sun Dec 28 06:30:45.887 2014 +(1 row) + +SELECT make_timestamp(-44, 3, 15, 12, 30, 15); + make_timestamp +----------------------------- + Fri Mar 15 12:30:15 0044 BC +(1 row) + +-- should fail +select make_timestamp(0, 7, 15, 12, 30, 15); +ERROR: date field value out of range: 0-07-15 diff --git a/src/test/regress/expected/timetz.out b/src/test/regress/expected/timetz.out index cf4e814bd69d..90800c3a1812 100644 --- a/src/test/regress/expected/timetz.out +++ b/src/test/regress/expected/timetz.out @@ -91,45 +91,45 @@ SELECT f1 AS "Ten" FROM TIMETZ_TBL WHERE f1 >= '00:00-07'; (12 rows) -- Check edge cases -SELECT '23:59:59.999999'::timetz; +SELECT '23:59:59.999999 PDT'::timetz; timetz -------------------- 23:59:59.999999-07 (1 row) -SELECT '23:59:59.9999999'::timetz; -- rounds up +SELECT '23:59:59.9999999 PDT'::timetz; -- rounds up timetz ------------- 24:00:00-07 (1 row) -SELECT '23:59:60'::timetz; -- rounds up +SELECT '23:59:60 PDT'::timetz; -- rounds up timetz ------------- 24:00:00-07 (1 row) -SELECT '24:00:00'::timetz; -- allowed +SELECT '24:00:00 PDT'::timetz; -- allowed timetz ------------- 24:00:00-07 (1 row) -SELECT '24:00:00.01'::timetz; -- not allowed -ERROR: date/time field value out of range: "24:00:00.01" -LINE 1: SELECT '24:00:00.01'::timetz; +SELECT '24:00:00.01 PDT'::timetz; -- not allowed +ERROR: date/time field value out of range: "24:00:00.01 PDT" +LINE 1: SELECT '24:00:00.01 PDT'::timetz; ^ -SELECT '23:59:60.01'::timetz; -- not allowed -ERROR: date/time field value out of range: "23:59:60.01" -LINE 1: SELECT '23:59:60.01'::timetz; +SELECT '23:59:60.01 PDT'::timetz; -- not allowed +ERROR: date/time field value out of range: "23:59:60.01 PDT" +LINE 1: SELECT '23:59:60.01 PDT'::timetz; ^ -SELECT '24:01:00'::timetz; -- not allowed -ERROR: date/time field value out of range: "24:01:00" -LINE 1: SELECT '24:01:00'::timetz; +SELECT '24:01:00 PDT'::timetz; -- not allowed +ERROR: date/time field value out of range: "24:01:00 PDT" +LINE 1: SELECT '24:01:00 PDT'::timetz; ^ -SELECT '25:00:00'::timetz; -- not allowed -ERROR: date/time field value out of range: "25:00:00" -LINE 1: SELECT '25:00:00'::timetz; +SELECT '25:00:00 PDT'::timetz; -- not allowed +ERROR: date/time field value out of range: "25:00:00 PDT" +LINE 1: SELECT '25:00:00 PDT'::timetz; ^ -- -- TIME simple math diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index dc3c6bbfc062..cfed5f86e724 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -226,6 +226,38 @@ select * from trigtest; ----+---- (0 rows) +drop table trigtest; +-- Check behavior with an implicit column default, too (bug #16644) +create table trigtest (a integer, id integer default 0) distributed by (id); +create trigger trigger_return_old + before insert or delete or update on trigtest + for each row execute procedure trigger_return_old(); +insert into trigtest values(1); +select a from trigtest; + a +--- + 1 +(1 row) + +alter table trigtest add column b integer default 42 not null; +select a, b from trigtest; + a | b +---+---- + 1 | 42 +(1 row) + +update trigtest set a = 2 where a = 1 returning a, b; + a | b +---+---- + 1 | 42 +(1 row) + +select a, b from trigtest; + a | b +---+---- + 1 | 42 +(1 row) + drop table trigtest; create sequence ttdummy_seq increment 10 start 0 minvalue 0 cache 1; create table tttest ( @@ -652,7 +684,7 @@ HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sur WARNING: referential integrity (FOREIGN KEY) constraints are not supported in Greenplum Database, will not be enforced create function trigtest() returns trigger as $$ begin - raise notice '% % % %', TG_RELNAME, TG_OP, TG_WHEN, TG_LEVEL; + raise notice '% % % %', TG_TABLE_NAME, TG_OP, TG_WHEN, TG_LEVEL; return new; end;$$ language plpgsql; create trigger trigtest_b_row_tg before insert or update or delete on trigtest @@ -939,7 +971,7 @@ begin argstr := argstr || TG_argv[i]; end loop; - raise notice '% % % % (%)', TG_RELNAME, TG_WHEN, TG_OP, TG_LEVEL, argstr; + raise notice '% % % % (%)', TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL, argstr; if TG_LEVEL = 'ROW' then if TG_OP = 'INSERT' then diff --git a/src/test/regress/expected/uao_compaction/index_stats.out b/src/test/regress/expected/uao_compaction/index_stats.out index 0eb52a03c8ec..5e328ba673d3 100644 --- a/src/test/regress/expected/uao_compaction/index_stats.out +++ b/src/test/regress/expected/uao_compaction/index_stats.out @@ -59,9 +59,9 @@ vacuum mytab2; SELECT gp_segment_id, relname, reltuples FROM gp_dist_random('pg_class') WHERE relname = 'mytab2_int_idx1'; gp_segment_id | relname | reltuples ---------------+-----------------+----------- - 0 | mytab2_int_idx1 | 0 - 1 | mytab2_int_idx1 | 0 - 2 | mytab2_int_idx1 | 0 + 0 | mytab2_int_idx1 | -1 + 1 | mytab2_int_idx1 | -1 + 2 | mytab2_int_idx1 | -1 (3 rows) -- second vacuum update index stat with table stat @@ -93,7 +93,7 @@ insert into mytab3 values(1,'aa',1001,101),(2,'bb',1002,102); select reltuples from pg_class where relname='mytab3'; reltuples ----------- - 0 + -1 (1 row) -- inspect the state of the stats on segments diff --git a/src/test/regress/expected/uaocs_compaction/index_stats.out b/src/test/regress/expected/uaocs_compaction/index_stats.out index 11ff27fa10c1..63fa84e2f26a 100644 --- a/src/test/regress/expected/uaocs_compaction/index_stats.out +++ b/src/test/regress/expected/uaocs_compaction/index_stats.out @@ -64,9 +64,9 @@ vacuum uaocs_index_stats2; SELECT gp_segment_id, relname, reltuples FROM gp_dist_random('pg_class') WHERE relname = 'uaocs_index_stats2_int_idx1'; gp_segment_id | relname | reltuples ---------------+-----------------------------+----------- - 0 | uaocs_index_stats2_int_idx1 | 0 - 1 | uaocs_index_stats2_int_idx1 | 0 - 2 | uaocs_index_stats2_int_idx1 | 0 + 1 | uaocs_index_stats2_int_idx1 | -1 + 0 | uaocs_index_stats2_int_idx1 | -1 + 2 | uaocs_index_stats2_int_idx1 | -1 (3 rows) -- second vacuum update index stat with table stat @@ -98,7 +98,7 @@ insert into uaocs_index_stats3 values(1,'aa',1001,101),(2,'bb',1002,102); select reltuples from pg_class where relname='uaocs_index_stats3'; reltuples ----------- - 0 + -1 (1 row) -- inspect the state of the stats on segments diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index a4c2c61989c9..af48d53185e8 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -1473,6 +1473,41 @@ NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to view rw_view1 drop cascades to view rw_view2 drop cascades to view rw_view3 +-- view on table with GENERATED columns +CREATE TABLE base_tbl (id int, idplus1 int GENERATED ALWAYS AS (id + 1) STORED); +CREATE VIEW rw_view1 AS SELECT * FROM base_tbl; +INSERT INTO base_tbl (id) VALUES (1); +INSERT INTO rw_view1 (id) VALUES (2); +INSERT INTO base_tbl (id, idplus1) VALUES (3, DEFAULT); +INSERT INTO rw_view1 (id, idplus1) VALUES (4, DEFAULT); +INSERT INTO base_tbl (id, idplus1) VALUES (5, 6); -- error +ERROR: cannot insert into column "idplus1" +DETAIL: Column "idplus1" is a generated column. +INSERT INTO rw_view1 (id, idplus1) VALUES (6, 7); -- error +ERROR: cannot insert into column "idplus1" +DETAIL: Column "idplus1" is a generated column. +SELECT * FROM base_tbl; + id | idplus1 +----+--------- + 1 | 2 + 2 | 3 + 3 | 4 + 4 | 5 +(4 rows) + +UPDATE base_tbl SET id = 2000 WHERE id = 2; +UPDATE rw_view1 SET id = 3000 WHERE id = 3; +SELECT * FROM base_tbl; + id | idplus1 +------+--------- + 1 | 2 + 4 | 5 + 2000 | 2001 + 3000 | 3001 +(4 rows) + +DROP TABLE base_tbl CASCADE; +NOTICE: drop cascades to view rw_view1 -- inheritance tests CREATE TABLE base_tbl_parent (a int); CREATE TABLE base_tbl_child (CHECK (a > 0)) INHERITS (base_tbl_parent); @@ -1885,11 +1920,8 @@ EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5); -> Materialize -> Broadcast Motion 3:1 (slice1; segments: 3) -> Seq Scan on ref_tbl r - SubPlan 2 - -> Broadcast Motion 3:1 (slice2; segments: 3) - -> Seq Scan on ref_tbl r_1 Optimizer: Postgres query optimizer -(12 rows) +(9 rows) ANALYZE base_tbl; EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5; @@ -1908,11 +1940,8 @@ EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5; -> Materialize -> Broadcast Motion 3:3 (slice2; segments: 3) -> Seq Scan on ref_tbl r_1 - SubPlan 2 - -> Broadcast Motion 3:3 (slice3; segments: 3) - -> Seq Scan on ref_tbl r_2 Optimizer: Postgres query optimizer -(17 rows) +(14 rows) DROP TABLE base_tbl, ref_tbl CASCADE; NOTICE: drop cascades to view rw_view1 @@ -2350,8 +2379,8 @@ SELECT * FROM v1 WHERE a=8; EXPLAIN (VERBOSE, COSTS OFF) UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------- Update on public.t1 Update on public.t1 Update on public.t11 t1_1 @@ -2363,7 +2392,7 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; Output: 100, t1.b, t1.c, t1.ctid, t1.gp_segment_id, DMLAction -> Seq Scan on public.t1 Output: 100, t1.b, t1.c, t1.ctid, t1.gp_segment_id - Filter: ((t1.a > 5) AND (t1.a < 7) AND (t1.a <> 6) AND (alternatives: SubPlan 1 (copy 11) or hashed SubPlan 2 (copy 12)) AND snoop(t1.a) AND leakproof(t1.a)) + Filter: ((t1.a > 5) AND (t1.a < 7) AND (t1.a <> 6) AND (SubPlan 1 (copy 11)) AND snoop(t1.a) AND leakproof(t1.a)) SubPlan 1 (copy 11) -> Result Filter: (t12_1.a = t1.a) @@ -2376,98 +2405,66 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; Output: t12_1.a -> Seq Scan on public.t111 t12_2 Output: t12_2.a - SubPlan 2 (copy 12) - -> Broadcast Motion 3:3 (slice3; segments: 3) - Output: t12_4.a - -> Append - -> Seq Scan on public.t12 t12_4 - Output: t12_4.a - -> Seq Scan on public.t111 t12_5 - Output: t12_5.a - -> Explicit Redistribute Motion 3:3 (slice4; segments: 3) + -> Explicit Redistribute Motion 3:3 (slice3; segments: 3) Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid, t1_1.gp_segment_id, (DMLAction) -> Split Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid, t1_1.gp_segment_id, DMLAction -> Seq Scan on public.t11 t1_1 Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid, t1_1.gp_segment_id - Filter: ((t1_1.a > 5) AND (t1_1.a < 7) AND (t1_1.a <> 6) AND (alternatives: SubPlan 1 (copy 13) or hashed SubPlan 2 (copy 14)) AND snoop(t1_1.a) AND leakproof(t1_1.a)) + Filter: ((t1_1.a > 5) AND (t1_1.a < 7) AND (t1_1.a <> 6) AND (SubPlan 1 (copy 13)) AND snoop(t1_1.a) AND leakproof(t1_1.a)) SubPlan 1 (copy 13) -> Result - Filter: (t12_7.a = t1_1.a) + Filter: (t12_4.a = t1_1.a) -> Materialize - Output: t12_7.a - -> Broadcast Motion 3:3 (slice5; segments: 3) - Output: t12_7.a + Output: t12_4.a + -> Broadcast Motion 3:3 (slice4; segments: 3) + Output: t12_4.a -> Append - -> Seq Scan on public.t12 t12_7 - Output: t12_7.a - -> Seq Scan on public.t111 t12_8 - Output: t12_8.a - SubPlan 2 (copy 14) - -> Broadcast Motion 3:3 (slice6; segments: 3) - Output: t12_10.a - -> Append - -> Seq Scan on public.t12 t12_10 - Output: t12_10.a - -> Seq Scan on public.t111 t12_11 - Output: t12_11.a - -> Explicit Redistribute Motion 3:3 (slice7; segments: 3) + -> Seq Scan on public.t12 t12_4 + Output: t12_4.a + -> Seq Scan on public.t111 t12_5 + Output: t12_5.a + -> Explicit Redistribute Motion 3:3 (slice5; segments: 3) Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid, t1_2.gp_segment_id, (DMLAction) -> Split Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid, t1_2.gp_segment_id, DMLAction -> Seq Scan on public.t12 t1_2 Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid, t1_2.gp_segment_id - Filter: ((t1_2.a > 5) AND (t1_2.a < 7) AND (t1_2.a <> 6) AND (alternatives: SubPlan 1 (copy 15) or hashed SubPlan 2 (copy 16)) AND snoop(t1_2.a) AND leakproof(t1_2.a)) + Filter: ((t1_2.a > 5) AND (t1_2.a < 7) AND (t1_2.a <> 6) AND (SubPlan 1 (copy 15)) AND snoop(t1_2.a) AND leakproof(t1_2.a)) SubPlan 1 (copy 15) -> Result - Filter: (t12_13.a = t1_2.a) + Filter: (t12_7.a = t1_2.a) -> Materialize - Output: t12_13.a - -> Broadcast Motion 3:3 (slice8; segments: 3) - Output: t12_13.a + Output: t12_7.a + -> Broadcast Motion 3:3 (slice6; segments: 3) + Output: t12_7.a -> Append - -> Seq Scan on public.t12 t12_13 - Output: t12_13.a - -> Seq Scan on public.t111 t12_14 - Output: t12_14.a - SubPlan 2 (copy 16) - -> Broadcast Motion 3:3 (slice9; segments: 3) - Output: t12_16.a - -> Append - -> Seq Scan on public.t12 t12_16 - Output: t12_16.a - -> Seq Scan on public.t111 t12_17 - Output: t12_17.a - -> Explicit Redistribute Motion 3:3 (slice10; segments: 3) + -> Seq Scan on public.t12 t12_7 + Output: t12_7.a + -> Seq Scan on public.t111 t12_8 + Output: t12_8.a + -> Explicit Redistribute Motion 3:3 (slice7; segments: 3) Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid, t1_3.gp_segment_id, (DMLAction) -> Split Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid, t1_3.gp_segment_id, DMLAction -> Seq Scan on public.t111 t1_3 Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid, t1_3.gp_segment_id - Filter: ((t1_3.a > 5) AND (t1_3.a < 7) AND (t1_3.a <> 6) AND (alternatives: SubPlan 1 (copy 17) or hashed SubPlan 2 (copy 18)) AND snoop(t1_3.a) AND leakproof(t1_3.a)) + Filter: ((t1_3.a > 5) AND (t1_3.a < 7) AND (t1_3.a <> 6) AND (SubPlan 1 (copy 17)) AND snoop(t1_3.a) AND leakproof(t1_3.a)) SubPlan 1 (copy 17) -> Result - Filter: (t12_19.a = t1_3.a) + Filter: (t12_10.a = t1_3.a) -> Materialize - Output: t12_19.a - -> Broadcast Motion 3:3 (slice11; segments: 3) - Output: t12_19.a + Output: t12_10.a + -> Broadcast Motion 3:3 (slice8; segments: 3) + Output: t12_10.a -> Append - -> Seq Scan on public.t12 t12_19 - Output: t12_19.a - -> Seq Scan on public.t111 t12_20 - Output: t12_20.a - SubPlan 2 (copy 18) - -> Broadcast Motion 3:3 (slice12; segments: 3) - Output: t12_22.a - -> Append - -> Seq Scan on public.t12 t12_22 - Output: t12_22.a - -> Seq Scan on public.t111 t12_23 - Output: t12_23.a + -> Seq Scan on public.t12 t12_10 + Output: t12_10.a + -> Seq Scan on public.t111 t12_11 + Output: t12_11.a Optimizer: Postgres query optimizer Settings: enable_mergejoin = 'on', enable_nestloop = 'on', optimizer = 'off' -(115 rows) +(83 rows) UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100 @@ -2482,8 +2479,8 @@ SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100 EXPLAIN (VERBOSE, COSTS OFF) UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- Update on public.t1 Update on public.t1 Update on public.t11 t1_1 @@ -2495,7 +2492,7 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; Output: ((t1.a + 1)), t1.b, t1.c, t1.ctid, t1.gp_segment_id, DMLAction -> Seq Scan on public.t1 Output: (t1.a + 1), t1.b, t1.c, t1.ctid, t1.gp_segment_id - Filter: ((t1.a > 5) AND (t1.a = 8) AND (alternatives: SubPlan 1 (copy 11) or hashed SubPlan 2 (copy 12)) AND snoop(t1.a) AND leakproof(t1.a)) + Filter: ((t1.a > 5) AND (t1.a = 8) AND (SubPlan 1 (copy 11)) AND snoop(t1.a) AND leakproof(t1.a)) SubPlan 1 (copy 11) -> Result Filter: (t12_1.a = t1.a) @@ -2508,98 +2505,66 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; Output: t12_1.a -> Seq Scan on public.t111 t12_2 Output: t12_2.a - SubPlan 2 (copy 12) - -> Broadcast Motion 3:1 (slice3; segments: 3) - Output: t12_4.a - -> Append - -> Seq Scan on public.t12 t12_4 - Output: t12_4.a - -> Seq Scan on public.t111 t12_5 - Output: t12_5.a - -> Explicit Redistribute Motion 1:3 (slice4; segments: 1) + -> Explicit Redistribute Motion 1:3 (slice3; segments: 1) Output: ((t1_1.a + 1)), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid, t1_1.gp_segment_id, (DMLAction) -> Split Output: ((t1_1.a + 1)), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid, t1_1.gp_segment_id, DMLAction -> Seq Scan on public.t11 t1_1 Output: (t1_1.a + 1), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid, t1_1.gp_segment_id - Filter: ((t1_1.a > 5) AND (t1_1.a = 8) AND (alternatives: SubPlan 1 (copy 13) or hashed SubPlan 2 (copy 14)) AND snoop(t1_1.a) AND leakproof(t1_1.a)) + Filter: ((t1_1.a > 5) AND (t1_1.a = 8) AND (SubPlan 1 (copy 13)) AND snoop(t1_1.a) AND leakproof(t1_1.a)) SubPlan 1 (copy 13) -> Result - Filter: (t12_7.a = t1_1.a) + Filter: (t12_4.a = t1_1.a) -> Materialize - Output: t12_7.a - -> Broadcast Motion 3:1 (slice5; segments: 3) - Output: t12_7.a + Output: t12_4.a + -> Broadcast Motion 3:1 (slice4; segments: 3) + Output: t12_4.a -> Append - -> Seq Scan on public.t12 t12_7 - Output: t12_7.a - -> Seq Scan on public.t111 t12_8 - Output: t12_8.a - SubPlan 2 (copy 14) - -> Broadcast Motion 3:1 (slice6; segments: 3) - Output: t12_10.a - -> Append - -> Seq Scan on public.t12 t12_10 - Output: t12_10.a - -> Seq Scan on public.t111 t12_11 - Output: t12_11.a - -> Explicit Redistribute Motion 1:3 (slice7; segments: 1) + -> Seq Scan on public.t12 t12_4 + Output: t12_4.a + -> Seq Scan on public.t111 t12_5 + Output: t12_5.a + -> Explicit Redistribute Motion 1:3 (slice5; segments: 1) Output: ((t1_2.a + 1)), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid, t1_2.gp_segment_id, (DMLAction) -> Split Output: ((t1_2.a + 1)), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid, t1_2.gp_segment_id, DMLAction -> Seq Scan on public.t12 t1_2 Output: (t1_2.a + 1), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid, t1_2.gp_segment_id - Filter: ((t1_2.a > 5) AND (t1_2.a = 8) AND (alternatives: SubPlan 1 (copy 15) or hashed SubPlan 2 (copy 16)) AND snoop(t1_2.a) AND leakproof(t1_2.a)) + Filter: ((t1_2.a > 5) AND (t1_2.a = 8) AND (SubPlan 1 (copy 15)) AND snoop(t1_2.a) AND leakproof(t1_2.a)) SubPlan 1 (copy 15) -> Result - Filter: (t12_13.a = t1_2.a) + Filter: (t12_7.a = t1_2.a) -> Materialize - Output: t12_13.a - -> Broadcast Motion 3:1 (slice8; segments: 3) - Output: t12_13.a + Output: t12_7.a + -> Broadcast Motion 3:1 (slice6; segments: 3) + Output: t12_7.a -> Append - -> Seq Scan on public.t12 t12_13 - Output: t12_13.a - -> Seq Scan on public.t111 t12_14 - Output: t12_14.a - SubPlan 2 (copy 16) - -> Broadcast Motion 3:1 (slice9; segments: 3) - Output: t12_16.a - -> Append - -> Seq Scan on public.t12 t12_16 - Output: t12_16.a - -> Seq Scan on public.t111 t12_17 - Output: t12_17.a - -> Explicit Redistribute Motion 1:3 (slice10; segments: 1) + -> Seq Scan on public.t12 t12_7 + Output: t12_7.a + -> Seq Scan on public.t111 t12_8 + Output: t12_8.a + -> Explicit Redistribute Motion 1:3 (slice7; segments: 1) Output: ((t1_3.a + 1)), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid, t1_3.gp_segment_id, (DMLAction) -> Split Output: ((t1_3.a + 1)), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid, t1_3.gp_segment_id, DMLAction -> Seq Scan on public.t111 t1_3 Output: (t1_3.a + 1), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid, t1_3.gp_segment_id - Filter: ((t1_3.a > 5) AND (t1_3.a = 8) AND (alternatives: SubPlan 1 (copy 17) or hashed SubPlan 2 (copy 18)) AND snoop(t1_3.a) AND leakproof(t1_3.a)) + Filter: ((t1_3.a > 5) AND (t1_3.a = 8) AND (SubPlan 1 (copy 17)) AND snoop(t1_3.a) AND leakproof(t1_3.a)) SubPlan 1 (copy 17) -> Result - Filter: (t12_19.a = t1_3.a) + Filter: (t12_10.a = t1_3.a) -> Materialize - Output: t12_19.a - -> Broadcast Motion 3:1 (slice11; segments: 3) - Output: t12_19.a + Output: t12_10.a + -> Broadcast Motion 3:1 (slice8; segments: 3) + Output: t12_10.a -> Append - -> Seq Scan on public.t12 t12_19 - Output: t12_19.a - -> Seq Scan on public.t111 t12_20 - Output: t12_20.a - SubPlan 2 (copy 18) - -> Broadcast Motion 3:1 (slice12; segments: 3) - Output: t12_22.a - -> Append - -> Seq Scan on public.t12 t12_22 - Output: t12_22.a - -> Seq Scan on public.t111 t12_23 - Output: t12_23.a + -> Seq Scan on public.t12 t12_10 + Output: t12_10.a + -> Seq Scan on public.t111 t12_11 + Output: t12_11.a Optimizer: Postgres query optimizer Settings: enable_mergejoin = 'on', enable_nestloop = 'on', optimizer = 'off' -(115 rows) +(83 rows) UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; NOTICE: snooped value: 8 (seg0 slice4 127.0.0.1:40000 pid=26920) diff --git a/src/test/regress/expected/updatable_views_optimizer.out b/src/test/regress/expected/updatable_views_optimizer.out index 3b918543b215..37495bdd145c 100644 --- a/src/test/regress/expected/updatable_views_optimizer.out +++ b/src/test/regress/expected/updatable_views_optimizer.out @@ -1483,6 +1483,41 @@ NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to view rw_view1 drop cascades to view rw_view2 drop cascades to view rw_view3 +-- view on table with GENERATED columns +CREATE TABLE base_tbl (id int, idplus1 int GENERATED ALWAYS AS (id + 1) STORED); +CREATE VIEW rw_view1 AS SELECT * FROM base_tbl; +INSERT INTO base_tbl (id) VALUES (1); +INSERT INTO rw_view1 (id) VALUES (2); +INSERT INTO base_tbl (id, idplus1) VALUES (3, DEFAULT); +INSERT INTO rw_view1 (id, idplus1) VALUES (4, DEFAULT); +INSERT INTO base_tbl (id, idplus1) VALUES (5, 6); -- error +ERROR: cannot insert into column "idplus1" +DETAIL: Column "idplus1" is a generated column. +INSERT INTO rw_view1 (id, idplus1) VALUES (6, 7); -- error +ERROR: cannot insert into column "idplus1" +DETAIL: Column "idplus1" is a generated column. +SELECT * FROM base_tbl; + id | idplus1 +----+--------- + 1 | 2 + 2 | 3 + 3 | 4 + 4 | 5 +(4 rows) + +UPDATE base_tbl SET id = 2000 WHERE id = 2; +UPDATE rw_view1 SET id = 3000 WHERE id = 3; +SELECT * FROM base_tbl; + id | idplus1 +------+--------- + 4 | 5 + 2000 | 2001 + 1 | 2 + 3000 | 3001 +(4 rows) + +DROP TABLE base_tbl CASCADE; +NOTICE: drop cascades to view rw_view1 -- inheritance tests CREATE TABLE base_tbl_parent (a int); CREATE TABLE base_tbl_child (CHECK (a > 0)) INHERITS (base_tbl_parent); @@ -1895,9 +1930,6 @@ EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5); -> Materialize -> Broadcast Motion 3:1 (slice1; segments: 3) -> Seq Scan on ref_tbl r - SubPlan 2 - -> Broadcast Motion 3:1 (slice2; segments: 3) - -> Seq Scan on ref_tbl r_1 Optimizer: Postgres query optimizer (12 rows) @@ -1918,9 +1950,6 @@ EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5; -> Materialize -> Broadcast Motion 3:3 (slice2; segments: 3) -> Seq Scan on ref_tbl r_1 - SubPlan 2 - -> Broadcast Motion 3:3 (slice3; segments: 3) - -> Seq Scan on ref_tbl r_2 Optimizer: Postgres query optimizer (18 rows) @@ -2376,8 +2405,8 @@ SELECT * FROM v1 WHERE a=8; EXPLAIN (VERBOSE, COSTS OFF) UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------- Update on public.t1 Update on public.t1 Update on public.t11 t1_1 @@ -2389,7 +2418,7 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; Output: 100, t1.b, t1.c, t1.ctid, t1.gp_segment_id, DMLAction -> Seq Scan on public.t1 Output: 100, t1.b, t1.c, t1.ctid, t1.gp_segment_id - Filter: ((t1.a > 5) AND (t1.a < 7) AND (t1.a <> 6) AND (alternatives: SubPlan 1 (copy 11) or hashed SubPlan 2 (copy 12)) AND snoop(t1.a) AND leakproof(t1.a)) + Filter: ((t1.a > 5) AND (t1.a < 7) AND (t1.a <> 6) AND (SubPlan 1 (copy 11)) AND snoop(t1.a) AND leakproof(t1.a)) SubPlan 1 (copy 11) -> Result Filter: (t12_1.a = t1.a) @@ -2402,98 +2431,66 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; Output: t12_1.a -> Seq Scan on public.t111 t12_2 Output: t12_2.a - SubPlan 2 (copy 12) - -> Broadcast Motion 3:3 (slice3; segments: 3) - Output: t12_4.a - -> Append - -> Seq Scan on public.t12 t12_4 - Output: t12_4.a - -> Seq Scan on public.t111 t12_5 - Output: t12_5.a - -> Explicit Redistribute Motion 3:3 (slice4; segments: 3) + -> Explicit Redistribute Motion 3:3 (slice3; segments: 3) Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid, t1_1.gp_segment_id, (DMLAction) -> Split Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid, t1_1.gp_segment_id, DMLAction -> Seq Scan on public.t11 t1_1 Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid, t1_1.gp_segment_id - Filter: ((t1_1.a > 5) AND (t1_1.a < 7) AND (t1_1.a <> 6) AND (alternatives: SubPlan 1 (copy 13) or hashed SubPlan 2 (copy 14)) AND snoop(t1_1.a) AND leakproof(t1_1.a)) + Filter: ((t1_1.a > 5) AND (t1_1.a < 7) AND (t1_1.a <> 6) AND (SubPlan 1 (copy 13)) AND snoop(t1_1.a) AND leakproof(t1_1.a)) SubPlan 1 (copy 13) -> Result - Filter: (t12_7.a = t1_1.a) + Filter: (t12_4.a = t1_1.a) -> Materialize - Output: t12_7.a - -> Broadcast Motion 3:3 (slice5; segments: 3) - Output: t12_7.a + Output: t12_4.a + -> Broadcast Motion 3:3 (slice4; segments: 3) + Output: t12_4.a -> Append - -> Seq Scan on public.t12 t12_7 - Output: t12_7.a - -> Seq Scan on public.t111 t12_8 - Output: t12_8.a - SubPlan 2 (copy 14) - -> Broadcast Motion 3:3 (slice6; segments: 3) - Output: t12_10.a - -> Append - -> Seq Scan on public.t12 t12_10 - Output: t12_10.a - -> Seq Scan on public.t111 t12_11 - Output: t12_11.a - -> Explicit Redistribute Motion 3:3 (slice7; segments: 3) + -> Seq Scan on public.t12 t12_4 + Output: t12_4.a + -> Seq Scan on public.t111 t12_5 + Output: t12_5.a + -> Explicit Redistribute Motion 3:3 (slice5; segments: 3) Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid, t1_2.gp_segment_id, (DMLAction) -> Split Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid, t1_2.gp_segment_id, DMLAction -> Seq Scan on public.t12 t1_2 Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid, t1_2.gp_segment_id - Filter: ((t1_2.a > 5) AND (t1_2.a < 7) AND (t1_2.a <> 6) AND (alternatives: SubPlan 1 (copy 15) or hashed SubPlan 2 (copy 16)) AND snoop(t1_2.a) AND leakproof(t1_2.a)) + Filter: ((t1_2.a > 5) AND (t1_2.a < 7) AND (t1_2.a <> 6) AND (SubPlan 1 (copy 15)) AND snoop(t1_2.a) AND leakproof(t1_2.a)) SubPlan 1 (copy 15) -> Result - Filter: (t12_13.a = t1_2.a) + Filter: (t12_7.a = t1_2.a) -> Materialize - Output: t12_13.a - -> Broadcast Motion 3:3 (slice8; segments: 3) - Output: t12_13.a + Output: t12_7.a + -> Broadcast Motion 3:3 (slice6; segments: 3) + Output: t12_7.a -> Append - -> Seq Scan on public.t12 t12_13 - Output: t12_13.a - -> Seq Scan on public.t111 t12_14 - Output: t12_14.a - SubPlan 2 (copy 16) - -> Broadcast Motion 3:3 (slice9; segments: 3) - Output: t12_16.a - -> Append - -> Seq Scan on public.t12 t12_16 - Output: t12_16.a - -> Seq Scan on public.t111 t12_17 - Output: t12_17.a - -> Explicit Redistribute Motion 3:3 (slice10; segments: 3) + -> Seq Scan on public.t12 t12_7 + Output: t12_7.a + -> Seq Scan on public.t111 t12_8 + Output: t12_8.a + -> Explicit Redistribute Motion 3:3 (slice7; segments: 3) Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid, t1_3.gp_segment_id, (DMLAction) -> Split Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid, t1_3.gp_segment_id, DMLAction -> Seq Scan on public.t111 t1_3 Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid, t1_3.gp_segment_id - Filter: ((t1_3.a > 5) AND (t1_3.a < 7) AND (t1_3.a <> 6) AND (alternatives: SubPlan 1 (copy 17) or hashed SubPlan 2 (copy 18)) AND snoop(t1_3.a) AND leakproof(t1_3.a)) + Filter: ((t1_3.a > 5) AND (t1_3.a < 7) AND (t1_3.a <> 6) AND (SubPlan 1 (copy 17)) AND snoop(t1_3.a) AND leakproof(t1_3.a)) SubPlan 1 (copy 17) -> Result - Filter: (t12_19.a = t1_3.a) + Filter: (t12_10.a = t1_3.a) -> Materialize - Output: t12_19.a - -> Broadcast Motion 3:3 (slice11; segments: 3) - Output: t12_19.a + Output: t12_10.a + -> Broadcast Motion 3:3 (slice8; segments: 3) + Output: t12_10.a -> Append - -> Seq Scan on public.t12 t12_19 - Output: t12_19.a - -> Seq Scan on public.t111 t12_20 - Output: t12_20.a - SubPlan 2 (copy 18) - -> Broadcast Motion 3:3 (slice12; segments: 3) - Output: t12_22.a - -> Append - -> Seq Scan on public.t12 t12_22 - Output: t12_22.a - -> Seq Scan on public.t111 t12_23 - Output: t12_23.a + -> Seq Scan on public.t12 t12_10 + Output: t12_10.a + -> Seq Scan on public.t111 t12_11 + Output: t12_11.a Optimizer: Postgres query optimizer Settings: enable_mergejoin = 'on', enable_nestloop = 'on' -(115 rows) +(83 rows) UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100 @@ -2508,8 +2505,8 @@ SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100 EXPLAIN (VERBOSE, COSTS OFF) UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- Update on public.t1 Update on public.t1 Update on public.t11 t1_1 @@ -2521,7 +2518,7 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; Output: ((t1.a + 1)), t1.b, t1.c, t1.ctid, t1.gp_segment_id, DMLAction -> Seq Scan on public.t1 Output: (t1.a + 1), t1.b, t1.c, t1.ctid, t1.gp_segment_id - Filter: ((t1.a > 5) AND (t1.a = 8) AND (alternatives: SubPlan 1 (copy 11) or hashed SubPlan 2 (copy 12)) AND snoop(t1.a) AND leakproof(t1.a)) + Filter: ((t1.a > 5) AND (t1.a = 8) AND (SubPlan 1 (copy 11)) AND snoop(t1.a) AND leakproof(t1.a)) SubPlan 1 (copy 11) -> Result Filter: (t12_1.a = t1.a) @@ -2534,98 +2531,66 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; Output: t12_1.a -> Seq Scan on public.t111 t12_2 Output: t12_2.a - SubPlan 2 (copy 12) - -> Broadcast Motion 3:1 (slice3; segments: 3) - Output: t12_4.a - -> Append - -> Seq Scan on public.t12 t12_4 - Output: t12_4.a - -> Seq Scan on public.t111 t12_5 - Output: t12_5.a - -> Explicit Redistribute Motion 1:3 (slice4; segments: 1) + -> Explicit Redistribute Motion 1:3 (slice3; segments: 1) Output: ((t1_1.a + 1)), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid, t1_1.gp_segment_id, (DMLAction) -> Split Output: ((t1_1.a + 1)), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid, t1_1.gp_segment_id, DMLAction -> Seq Scan on public.t11 t1_1 Output: (t1_1.a + 1), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid, t1_1.gp_segment_id - Filter: ((t1_1.a > 5) AND (t1_1.a = 8) AND (alternatives: SubPlan 1 (copy 13) or hashed SubPlan 2 (copy 14)) AND snoop(t1_1.a) AND leakproof(t1_1.a)) + Filter: ((t1_1.a > 5) AND (t1_1.a = 8) AND (SubPlan 1 (copy 13)) AND snoop(t1_1.a) AND leakproof(t1_1.a)) SubPlan 1 (copy 13) -> Result - Filter: (t12_7.a = t1_1.a) + Filter: (t12_4.a = t1_1.a) -> Materialize - Output: t12_7.a - -> Broadcast Motion 3:1 (slice5; segments: 3) - Output: t12_7.a + Output: t12_4.a + -> Broadcast Motion 3:1 (slice4; segments: 3) + Output: t12_4.a -> Append - -> Seq Scan on public.t12 t12_7 - Output: t12_7.a - -> Seq Scan on public.t111 t12_8 - Output: t12_8.a - SubPlan 2 (copy 14) - -> Broadcast Motion 3:1 (slice6; segments: 3) - Output: t12_10.a - -> Append - -> Seq Scan on public.t12 t12_10 - Output: t12_10.a - -> Seq Scan on public.t111 t12_11 - Output: t12_11.a - -> Explicit Redistribute Motion 1:3 (slice7; segments: 1) + -> Seq Scan on public.t12 t12_4 + Output: t12_4.a + -> Seq Scan on public.t111 t12_5 + Output: t12_5.a + -> Explicit Redistribute Motion 1:3 (slice5; segments: 1) Output: ((t1_2.a + 1)), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid, t1_2.gp_segment_id, (DMLAction) -> Split Output: ((t1_2.a + 1)), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid, t1_2.gp_segment_id, DMLAction -> Seq Scan on public.t12 t1_2 Output: (t1_2.a + 1), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid, t1_2.gp_segment_id - Filter: ((t1_2.a > 5) AND (t1_2.a = 8) AND (alternatives: SubPlan 1 (copy 15) or hashed SubPlan 2 (copy 16)) AND snoop(t1_2.a) AND leakproof(t1_2.a)) + Filter: ((t1_2.a > 5) AND (t1_2.a = 8) AND (SubPlan 1 (copy 15)) AND snoop(t1_2.a) AND leakproof(t1_2.a)) SubPlan 1 (copy 15) -> Result - Filter: (t12_13.a = t1_2.a) + Filter: (t12_7.a = t1_2.a) -> Materialize - Output: t12_13.a - -> Broadcast Motion 3:1 (slice8; segments: 3) - Output: t12_13.a + Output: t12_7.a + -> Broadcast Motion 3:1 (slice6; segments: 3) + Output: t12_7.a -> Append - -> Seq Scan on public.t12 t12_13 - Output: t12_13.a - -> Seq Scan on public.t111 t12_14 - Output: t12_14.a - SubPlan 2 (copy 16) - -> Broadcast Motion 3:1 (slice9; segments: 3) - Output: t12_16.a - -> Append - -> Seq Scan on public.t12 t12_16 - Output: t12_16.a - -> Seq Scan on public.t111 t12_17 - Output: t12_17.a - -> Explicit Redistribute Motion 1:3 (slice10; segments: 1) + -> Seq Scan on public.t12 t12_7 + Output: t12_7.a + -> Seq Scan on public.t111 t12_8 + Output: t12_8.a + -> Explicit Redistribute Motion 1:3 (slice7; segments: 1) Output: ((t1_3.a + 1)), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid, t1_3.gp_segment_id, (DMLAction) -> Split Output: ((t1_3.a + 1)), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid, t1_3.gp_segment_id, DMLAction -> Seq Scan on public.t111 t1_3 Output: (t1_3.a + 1), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid, t1_3.gp_segment_id - Filter: ((t1_3.a > 5) AND (t1_3.a = 8) AND (alternatives: SubPlan 1 (copy 17) or hashed SubPlan 2 (copy 18)) AND snoop(t1_3.a) AND leakproof(t1_3.a)) + Filter: ((t1_3.a > 5) AND (t1_3.a = 8) AND (SubPlan 1 (copy 17)) AND snoop(t1_3.a) AND leakproof(t1_3.a)) SubPlan 1 (copy 17) -> Result - Filter: (t12_19.a = t1_3.a) + Filter: (t12_10.a = t1_3.a) -> Materialize - Output: t12_19.a - -> Broadcast Motion 3:1 (slice11; segments: 3) - Output: t12_19.a + Output: t12_10.a + -> Broadcast Motion 3:1 (slice8; segments: 3) + Output: t12_10.a -> Append - -> Seq Scan on public.t12 t12_19 - Output: t12_19.a - -> Seq Scan on public.t111 t12_20 - Output: t12_20.a - SubPlan 2 (copy 18) - -> Broadcast Motion 3:1 (slice12; segments: 3) - Output: t12_22.a - -> Append - -> Seq Scan on public.t12 t12_22 - Output: t12_22.a - -> Seq Scan on public.t111 t12_23 - Output: t12_23.a + -> Seq Scan on public.t12 t12_10 + Output: t12_10.a + -> Seq Scan on public.t111 t12_11 + Output: t12_11.a Optimizer: Postgres query optimizer Settings: enable_mergejoin = 'on', enable_nestloop = 'on' -(115 rows) +(83 rows) UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; NOTICE: snooped value: 8 (seg0 slice4 127.0.0.1:40000 pid=26920) diff --git a/src/test/regress/expected/vacuum_full_heap.out b/src/test/regress/expected/vacuum_full_heap.out index 234f0e7252f9..d7842a8bc282 100644 --- a/src/test/regress/expected/vacuum_full_heap.out +++ b/src/test/regress/expected/vacuum_full_heap.out @@ -84,7 +84,7 @@ select relname, relpages, reltuples from gp_dist_random('pg_class') where (oid = relname | relpages | reltuples ---------+----------+----------- vfheap | 0 | 0 - ivfheap | 1 | 0 + ivfheap | 0 | -1 (2 rows) -- again, but delete second half diff --git a/src/test/regress/expected/vacuum_gp.out b/src/test/regress/expected/vacuum_gp.out index e7cab8704ec5..77e71f245397 100644 --- a/src/test/regress/expected/vacuum_gp.out +++ b/src/test/regress/expected/vacuum_gp.out @@ -289,16 +289,16 @@ INSERT INTO vacuum_gp_pt SELECT 0, 6 FROM generate_series(1, 12); SELECT relname, reltuples, relpages FROM pg_catalog.pg_class WHERE relname like 'vacuum_gp_pt%'; relname | reltuples | relpages ----------------------+-----------+---------- - vacuum_gp_pt | 0 | 0 - vacuum_gp_pt_1_prt_1 | 0 | 0 - vacuum_gp_pt_1_prt_2 | 0 | 0 + vacuum_gp_pt | -1 | 0 + vacuum_gp_pt_1_prt_1 | -1 | 0 + vacuum_gp_pt_1_prt_2 | -1 | 0 (3 rows) ANALYZE vacuum_gp_pt; SELECT relname, reltuples, relpages FROM pg_catalog.pg_class WHERE relname like 'vacuum_gp_pt%'; relname | reltuples | relpages ----------------------+-----------+---------- - vacuum_gp_pt | 0 | 0 + vacuum_gp_pt | -1 | 0 vacuum_gp_pt_1_prt_1 | 0 | 1 vacuum_gp_pt_1_prt_2 | 12 | 1 (3 rows) @@ -307,7 +307,7 @@ VACUUM vacuum_gp_pt; SELECT relname, reltuples, relpages FROM pg_catalog.pg_class WHERE relname like 'vacuum_gp_pt%'; relname | reltuples | relpages ----------------------+-----------+---------- - vacuum_gp_pt | 0 | 0 + vacuum_gp_pt | -1 | 0 vacuum_gp_pt_1_prt_1 | 0 | 1 vacuum_gp_pt_1_prt_2 | 12 | 1 (3 rows) @@ -316,7 +316,7 @@ VACUUM ANALYZE vacuum_gp_pt; SELECT relname, reltuples, relpages FROM pg_catalog.pg_class WHERE relname like 'vacuum_gp_pt%'; relname | reltuples | relpages ----------------------+-----------+---------- - vacuum_gp_pt | 0 | 0 + vacuum_gp_pt | -1 | 0 vacuum_gp_pt_1_prt_1 | 0 | 1 vacuum_gp_pt_1_prt_2 | 12 | 1 (3 rows) diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out index 178a2fdd5763..b49ee54713b7 100644 --- a/src/test/regress/expected/window.out +++ b/src/test/regress/expected/window.out @@ -3281,6 +3281,56 @@ FROM empsalary; Optimizer: Postgres query optimizer (11 rows) +-- Test incremental sorting +EXPLAIN (COSTS OFF) +SELECT * FROM + (SELECT depname, + empno, + salary, + enroll_date, + row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp, + row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp + FROM empsalary) emp +WHERE first_emp = 1 OR last_emp = 1; + QUERY PLAN +----------------------------------------------------------------------------------------- + Subquery Scan on emp + Filter: ((emp.first_emp = 1) OR (emp.last_emp = 1)) + -> Gather Motion 3:1 (slice1; segments: 3) + Merge Key: empsalary.depname, empsalary.enroll_date + -> WindowAgg + Partition By: empsalary.depname + Order By: empsalary.enroll_date + -> Sort + Sort Key: empsalary.depname, empsalary.enroll_date + -> WindowAgg + Partition By: empsalary.depname + Order By: empsalary.enroll_date + -> Sort + Sort Key: empsalary.depname, empsalary.enroll_date DESC + -> Seq Scan on empsalary + Optimizer: Postgres query optimizer +(16 rows) + +SELECT * FROM + (SELECT depname, + empno, + salary, + enroll_date, + row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp, + row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp + FROM empsalary) emp +WHERE first_emp = 1 OR last_emp = 1; + depname | empno | salary | enroll_date | first_emp | last_emp +-----------+-------+--------+-------------+-----------+---------- + develop | 8 | 6000 | 10-01-2006 | 1 | 5 + develop | 7 | 4200 | 01-01-2008 | 5 | 1 + personnel | 2 | 3900 | 12-23-2006 | 1 | 2 + personnel | 5 | 3500 | 12-10-2007 | 2 | 1 + sales | 1 | 5000 | 10-01-2006 | 1 | 3 + sales | 4 | 4800 | 08-08-2007 | 3 | 1 +(6 rows) + -- cleanup DROP TABLE empsalary; -- test user-defined window function with named args and default args diff --git a/src/test/regress/expected/window_optimizer.out b/src/test/regress/expected/window_optimizer.out index 58d406a67da2..63c1fd0b9d2c 100644 --- a/src/test/regress/expected/window_optimizer.out +++ b/src/test/regress/expected/window_optimizer.out @@ -3302,6 +3302,55 @@ FROM empsalary; Optimizer: Pivotal Optimizer (GPORCA) (13 rows) +-- Test incremental sorting +EXPLAIN (COSTS OFF) +SELECT * FROM + (SELECT depname, + empno, + salary, + enroll_date, + row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp, + row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp + FROM empsalary) emp +WHERE first_emp = 1 OR last_emp = 1; + QUERY PLAN +---------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Result + Filter: (((row_number() OVER (?)) = 1) OR ((row_number() OVER (?)) = 1)) + -> WindowAgg + Partition By: depname + Order By: enroll_date + -> Sort + Sort Key: depname, enroll_date DESC + -> WindowAgg + Partition By: depname + Order By: enroll_date + -> Sort + Sort Key: depname, enroll_date + -> Seq Scan on empsalary + Optimizer: Pivotal Optimizer (GPORCA) +(15 rows) + +SELECT * FROM + (SELECT depname, + empno, + salary, + enroll_date, + row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp, + row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp + FROM empsalary) emp +WHERE first_emp = 1 OR last_emp = 1; + depname | empno | salary | enroll_date | first_emp | last_emp +-----------+-------+--------+-------------+-----------+---------- + develop | 9 | 4500 | 01-01-2008 | 4 | 1 + develop | 8 | 6000 | 10-01-2006 | 1 | 5 + personnel | 5 | 3500 | 12-10-2007 | 2 | 1 + personnel | 2 | 3900 | 12-23-2006 | 1 | 2 + sales | 4 | 4800 | 08-08-2007 | 3 | 1 + sales | 1 | 5000 | 10-01-2006 | 1 | 3 +(6 rows) + -- cleanup DROP TABLE empsalary; -- test user-defined window function with named args and default args diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out index afa437ae6cd3..f742cb978bad 100644 --- a/src/test/regress/expected/with.out +++ b/src/test/regress/expected/with.out @@ -583,79 +583,79 @@ insert into graph values (1, 4, 'arc 1 -> 4'), (4, 5, 'arc 4 -> 5'), (5, 1, 'arc 5 -> 1'); -with recursive search_graph(f, t, label, path, cycle) as ( - select *, array[row(g.f, g.t)], false from graph g +with recursive search_graph(f, t, label, is_cycle, path) as ( + select *, false, array[row(g.f, g.t)] from graph g union all - select g.*, path || row(g.f, g.t), row(g.f, g.t) = any(path) + select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t) from graph g, search_graph sg - where g.f = sg.t and not cycle + where g.f = sg.t and not is_cycle ) select * from search_graph; - f | t | label | path | cycle ----+---+------------+-------------------------------------------+------- - 1 | 2 | arc 1 -> 2 | {"(1,2)"} | f - 1 | 3 | arc 1 -> 3 | {"(1,3)"} | f - 2 | 3 | arc 2 -> 3 | {"(2,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(1,4)"} | f - 4 | 5 | arc 4 -> 5 | {"(4,5)"} | f - 5 | 1 | arc 5 -> 1 | {"(5,1)"} | f - 1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"} | f - 1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f - 2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} | f - 4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} | f - 5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"} | f - 1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"} | f - 1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f - 2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"} | f - 4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"} | f - 5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"} | f - 1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"} | f - 1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t - 2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"} | f - 4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"} | t - 5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"} | t - 2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f + f | t | label | is_cycle | path +---+---+------------+----------+------------------------------------------- + 1 | 2 | arc 1 -> 2 | f | {"(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(1,3)"} + 2 | 3 | arc 2 -> 3 | f | {"(2,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | f | {"(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} + 1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} + 2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} (25 rows) -- ordering by the path column has same effect as SEARCH DEPTH FIRST -with recursive search_graph(f, t, label, path, cycle) as ( - select *, array[row(g.f, g.t)], false from graph g +with recursive search_graph(f, t, label, is_cycle, path) as ( + select *, false, array[row(g.f, g.t)] from graph g union all - select g.*, path || row(g.f, g.t), row(g.f, g.t) = any(path) + select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t) from graph g, search_graph sg - where g.f = sg.t and not cycle + where g.f = sg.t and not is_cycle ) select * from search_graph order by path; - f | t | label | path | cycle ----+---+------------+-------------------------------------------+------- - 1 | 2 | arc 1 -> 2 | {"(1,2)"} | f - 2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} | f - 1 | 3 | arc 1 -> 3 | {"(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(1,4)"} | f - 4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} | f - 5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"} | f - 1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"} | f - 2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f - 1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t - 2 | 3 | arc 2 -> 3 | {"(2,3)"} | f - 4 | 5 | arc 4 -> 5 | {"(4,5)"} | f - 5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"} | f - 1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"} | f - 2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"} | f - 1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f - 4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"} | t - 5 | 1 | arc 5 -> 1 | {"(5,1)"} | f - 1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"} | f - 2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"} | f - 1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"} | f - 1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f - 4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"} | f - 5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"} | t + f | t | label | is_cycle | path +---+---+------------+----------+------------------------------------------- + 1 | 2 | arc 1 -> 2 | f | {"(1,2)"} + 2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"} + 1 | 3 | arc 1 -> 3 | f | {"(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(1,4)"} + 4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} + 2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} + 1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} + 2 | 3 | arc 2 -> 3 | f | {"(2,3)"} + 4 | 5 | arc 4 -> 5 | f | {"(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"} + 2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"} + 4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | f | {"(5,1)"} + 1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"} + 2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"} + 1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"} + 4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"} + 5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} (25 rows) -- diff --git a/src/test/regress/input/external_table.source b/src/test/regress/input/external_table.source index 70e7dece0c37..62bc354dfe8b 100644 --- a/src/test/regress/input/external_table.source +++ b/src/test/regress/input/external_table.source @@ -42,7 +42,7 @@ FORMAT 'text' (delimiter '|'); -- Only tables with custom protocol should create dependency, due to a bug there -- used to be entries created for non custom protocol tables with refobjid=0. -SELECT * FROM pg_depend WHERE refclassid = 'pg_extprotocol'::regclass and refobjid = 0; +SELECT count(*) FROM pg_depend WHERE refclassid = 'pg_extprotocol'::regclass and refobjid = 0; -- drop tables diff --git a/src/test/regress/output/external_table.source b/src/test/regress/output/external_table.source index 5ecf46ced1a4..2c4d5ad435d8 100644 --- a/src/test/regress/output/external_table.source +++ b/src/test/regress/output/external_table.source @@ -39,10 +39,11 @@ location ('file://@hostname@@abs_srcdir@/data/region.tbl' ) FORMAT 'text' (delimiter '|'); -- Only tables with custom protocol should create dependency, due to a bug there -- used to be entries created for non custom protocol tables with refobjid=0. -SELECT * FROM pg_depend WHERE refclassid = 'pg_extprotocol'::regclass and refobjid = 0; - classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype ----------+-------+----------+------------+----------+-------------+--------- -(0 rows) +SELECT count(*) FROM pg_depend WHERE refclassid = 'pg_extprotocol'::regclass and refobjid = 0; + count +------- + 0 +(1 row) -- drop tables DROP EXTERNAL TABLE EXT_NATION; diff --git a/src/test/regress/output/table_functions.source b/src/test/regress/output/table_functions.source index d9139a14b3ef..22884921f683 100644 --- a/src/test/regress/output/table_functions.source +++ b/src/test/regress/output/table_functions.source @@ -2627,7 +2627,7 @@ FROM sessionize_static( ), '1 minute' ) AS sessionize(id integer, "time" timestamp, sessionnum integer) ORDER BY id, time; -- FAIL, double qualified -ERROR: a column definition list is only allowed for functions returning "record" +ERROR: a column definition list is redundant for a function with OUT parameters LINE 10: '1 minute' ) AS sessionize(id integer, "time" timestamp,... ^ -- Describe with qualification fails @@ -2642,7 +2642,7 @@ FROM sessionize_dynamic( ), '1 minute' ) AS sessionize(id integer, "time" timestamp, sessionnum integer) ORDER BY id, time; -- FAIL, double qualified -ERROR: a column definition list is only allowed for functions returning "record" +ERROR: a column definition list is redundant for a function with OUT parameters LINE 10: '1 minute' ) AS sessionize(id integer, "time" timestamp,... ^ -- Otherwise results should match diff --git a/src/test/regress/output/table_functions_optimizer.source b/src/test/regress/output/table_functions_optimizer.source index d6101512352f..33cd8a461b3a 100644 --- a/src/test/regress/output/table_functions_optimizer.source +++ b/src/test/regress/output/table_functions_optimizer.source @@ -2628,7 +2628,7 @@ FROM sessionize_static( ), '1 minute' ) AS sessionize(id integer, "time" timestamp, sessionnum integer) ORDER BY id, time; -- FAIL, double qualified -ERROR: a column definition list is only allowed for functions returning "record" +ERROR: a column definition list is redundant for a function with OUT parameters LINE 10: '1 minute' ) AS sessionize(id integer, "time" timestamp,... ^ -- Describe with qualification fails @@ -2643,7 +2643,7 @@ FROM sessionize_dynamic( ), '1 minute' ) AS sessionize(id integer, "time" timestamp, sessionnum integer) ORDER BY id, time; -- FAIL, double qualified -ERROR: a column definition list is only allowed for functions returning "record" +ERROR: a column definition list is redundant for a function with OUT parameters LINE 10: '1 minute' ) AS sessionize(id integer, "time" timestamp,... ^ -- Otherwise results should match diff --git a/src/test/regress/output/uao_ddl/analyze_ao_table_every_dml.source b/src/test/regress/output/uao_ddl/analyze_ao_table_every_dml.source index bf4fdffdb00b..e9c95c5d838c 100644 --- a/src/test/regress/output/uao_ddl/analyze_ao_table_every_dml.source +++ b/src/test/regress/output/uao_ddl/analyze_ao_table_every_dml.source @@ -22,7 +22,7 @@ select count(*) from sto_uao_city_analyze_everydml; select relname, reltuples from pg_class where oid='sto_uao_city_analyze_everydml'::regclass; relname | reltuples -------------------------------+----------- - sto_uao_city_analyze_everydml | 0 + sto_uao_city_analyze_everydml | -1 (1 row) SELECT 1 AS VisimapPresent FROM pg_appendonly WHERE visimaprelid is not NULL AND diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index d83b351d8d00..e037dae2207f 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -59,7 +59,7 @@ test: gp_gin_index # ---------- # Another group of parallel tests # ---------- -test: create_aggregate create_function_3 create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors +test: create_aggregate create_function_3 create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse # ---------- # Because vacuum will detect concurrently running transactions, it is necessary to diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c index 02397f2eb104..09bc42a8c0f2 100644 --- a/src/test/regress/regress.c +++ b/src/test/regress/regress.c @@ -720,6 +720,14 @@ test_atomic_uint32(void) EXPECT_EQ_U32(pg_atomic_read_u32(&var), (uint32) INT_MAX + 1); EXPECT_EQ_U32(pg_atomic_sub_fetch_u32(&var, INT_MAX), 1); pg_atomic_sub_fetch_u32(&var, 1); + expected = PG_INT16_MAX; + EXPECT_TRUE(!pg_atomic_compare_exchange_u32(&var, &expected, 1)); + expected = PG_INT16_MAX + 1; + EXPECT_TRUE(!pg_atomic_compare_exchange_u32(&var, &expected, 1)); + expected = PG_INT16_MIN; + EXPECT_TRUE(!pg_atomic_compare_exchange_u32(&var, &expected, 1)); + expected = PG_INT16_MIN - 1; + EXPECT_TRUE(!pg_atomic_compare_exchange_u32(&var, &expected, 1)); /* fail exchange because of old expected */ expected = 10; diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 3809408ac1de..0c173c05407a 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -86,6 +86,7 @@ test: roleattributes test: create_am test: hash_func test: errors +test: infinite_recurse test: sanity_check test: select_into test: select_distinct diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 8ae0695b30db..5973db153af0 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -2331,6 +2331,20 @@ ALTER TABLE ataddindex \d ataddindex DROP TABLE ataddindex; +CREATE TABLE ataddindex(id int, ref_id int); +ALTER TABLE ataddindex + ADD PRIMARY KEY (id), + ADD FOREIGN KEY (ref_id) REFERENCES ataddindex; +\d ataddindex +DROP TABLE ataddindex; + +CREATE TABLE ataddindex(id int, ref_id int); +ALTER TABLE ataddindex + ADD UNIQUE (id), + ADD FOREIGN KEY (ref_id) REFERENCES ataddindex (id); +\d ataddindex +DROP TABLE ataddindex; + -- unsupported constraint types for partitioned tables CREATE TABLE partitioned ( a int, diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index cad77df88e31..c3b417b6578e 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -549,6 +549,21 @@ select string_to_array('1,2,3,4,,6', ','); select string_to_array('1,2,3,4,,6', ',', ''); select string_to_array('1,2,3,4,*,6', ',', '*'); +select v, v is null as "is null" from string_to_table('1|2|3', '|') g(v); +select v, v is null as "is null" from string_to_table('1|2|3|', '|') g(v); +select v, v is null as "is null" from string_to_table('1||2|3||', '||') g(v); +select v, v is null as "is null" from string_to_table('1|2|3', '') g(v); +select v, v is null as "is null" from string_to_table('', '|') g(v); +select v, v is null as "is null" from string_to_table('1|2|3', NULL) g(v); +select v, v is null as "is null" from string_to_table(NULL, '|') g(v); +select v, v is null as "is null" from string_to_table('abc', '') g(v); +select v, v is null as "is null" from string_to_table('abc', '', 'abc') g(v); +select v, v is null as "is null" from string_to_table('abc', ',') g(v); +select v, v is null as "is null" from string_to_table('abc', ',', 'abc') g(v); +select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',') g(v); +select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',', '') g(v); +select v, v is null as "is null" from string_to_table('1,2,3,4,*,6', ',', '*') g(v); + select array_to_string(NULL::int4[], ',') IS NULL; select array_to_string('{}'::int4[], ','); select array_to_string(array[1,2,3,4,NULL,6], ','); diff --git a/src/test/regress/sql/bfv_joins.sql b/src/test/regress/sql/bfv_joins.sql index c2a585b4a99b..971c395cb808 100644 --- a/src/test/regress/sql/bfv_joins.sql +++ b/src/test/regress/sql/bfv_joins.sql @@ -386,6 +386,43 @@ explain select * from o1 left join o2 on a1 = a2 left join o3 on a2 is not disti explain select * from o1 left join o2 on a1 = a2 left join o3 on a2 is not distinct from a3 and b2 = b3; +-- +-- Test case for Hash Join rescan after squelched without hashtable built +-- See https://github.com/greenplum-db/gpdb/pull/15590 +-- +--- Lateral Join +set from_collapse_limit = 1; +set join_collapse_limit = 1; +select 1 from pg_namespace join lateral + (select * from aclexplode(nspacl) x join pg_authid on x.grantee = pg_authid.oid where rolname = current_user) z on true limit 1; +reset from_collapse_limit; +reset join_collapse_limit; + +--- NestLoop index join +create table l_table (a int, b int) distributed replicated; +create index l_table_idx on l_table(a); +create table r_table1 (ra1 int, rb1 int) distributed replicated; +create table r_table2 (ra2 int, rb2 int) distributed replicated; +insert into l_table select i % 10 , i from generate_series(1, 10000) i; +insert into r_table1 select i, i from generate_series(1, 1000) i; +insert into r_table2 values(11, 11), (1, 1) ; +analyze l_table; +analyze r_table1; +analyze r_table2; + +set optimizer to off; +set enable_nestloop to on; +set enable_bitmapscan to off; +explain select * from r_table2 where ra2 in ( select a from l_table join r_table1 on b = rb1); +select * from r_table2 where ra2 in ( select a from l_table join r_table1 on b = rb1); + +reset optimizer; +reset enable_nestloop; +reset enable_bitmapscan; +drop table l_table; +drop table r_table1; +drop table r_table2; + -- Clean up. None of the objects we create are very interesting to keep around. reset search_path; set client_min_messages='warning'; diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql index 67de7d97949f..4714c044d531 100644 --- a/src/test/regress/sql/collate.icu.utf8.sql +++ b/src/test/regress/sql/collate.icu.utf8.sql @@ -405,11 +405,6 @@ DROP SCHEMA test_schema; DROP ROLE regress_test_role; --- ALTER - -ALTER COLLATION "en-x-icu" REFRESH VERSION; - - -- dependencies CREATE COLLATION test0 FROM "C"; @@ -721,6 +716,138 @@ INSERT INTO test33 VALUES (2, 'DEF'); -- they end up in the same partition (but it's platform-dependent which one) SELECT (SELECT count(*) FROM test33_0) <> (SELECT count(*) FROM test33_1); +-- collation versioning support +CREATE TYPE t_en_fr AS (fr text COLLATE "fr-x-icu", en text COLLATE "en-x-icu"); +CREATE DOMAIN d_en_fr AS t_en_fr; +CREATE DOMAIN d_es AS text COLLATE "es-x-icu"; +CREATE TYPE t_en_fr_ga AS (en_fr t_en_fr, ga text COLLATE "ga-x-icu"); +CREATE DOMAIN d_en_fr_ga AS t_en_fr_ga; +CREATE TYPE t_custom AS (meh text, meh2 text); +CREATE DOMAIN d_custom AS t_custom; + +CREATE COLLATION custom ( + LOCALE = 'fr-x-icu', PROVIDER = 'icu' +); + +CREATE TYPE myrange AS range (subtype = text); +CREATE TYPE myrange_en_fr_ga AS range(subtype = t_en_fr_ga); + +CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); + +CREATE TABLE collate_test ( + id integer, + val text COLLATE "fr-x-icu", + t_en_fr t_en_fr, + d_en_fr d_en_fr, + d_es d_es, + t_en_fr_ga t_en_fr_ga, + d_en_fr_ga d_en_fr_ga, + d_en_fr_ga_arr d_en_fr_ga[], + myrange myrange, + myrange_en_fr_ga myrange_en_fr_ga, + mood mood +); + +CREATE INDEX icuidx00_val ON collate_test(val); +-- shouldn't get duplicated dependencies +CREATE INDEX icuidx00_val_val ON collate_test(val, val); +-- shouldn't track version +CREATE INDEX icuidx00_val_pattern ON collate_test(val text_pattern_ops); +-- should have single dependency, no version +CREATE INDEX icuidx00_val_pattern_val_pattern ON collate_test(val text_pattern_ops, val text_pattern_ops); +-- should have single dependency, with version +CREATE INDEX icuidx00_val_pattern_val ON collate_test(val text_pattern_ops, val); +-- should have single dependency, with version +CREATE INDEX icuidx00_val_val_pattern ON collate_test(val, val text_pattern_ops); +-- two rows expected, only one a version, because we don't try to merge these yet +CREATE INDEX icuidx00_val_pattern_where ON collate_test(val text_pattern_ops) WHERE val >= val; +-- two rows expected with version, because we don't try to merge these yet +CREATE INDEX icuidx00_val_where ON collate_test(val) WHERE val >= val; +-- two rows expected with version (expression walker + attribute) +CREATE INDEX icuidx00_val_pattern_expr ON collate_test(val varchar_pattern_ops, (val || val)); +-- two rows expected, one with a version (expression walker + attribute) +CREATE INDEX icuidx00_val_pattern_expr_pattern ON collate_test(val varchar_pattern_ops, (val || val) text_pattern_ops); +-- should have single dependency, with version tracked +CREATE INDEX icuidx01_t_en_fr__d_es ON collate_test (t_en_fr, d_es); +CREATE INDEX icuidx02_d_en_fr ON collate_test (d_en_fr); +CREATE INDEX icuidx03_t_en_fr_ga ON collate_test (t_en_fr_ga); +CREATE INDEX icuidx04_d_en_fr_ga ON collate_test (d_en_fr_ga); +CREATE INDEX icuidx05_d_en_fr_ga_arr ON collate_test (d_en_fr_ga_arr); +CREATE INDEX icuidx06_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga).en_fr.fr = 'foo'; +CREATE INDEX icuidx07_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga).ga = 'foo'; +CREATE INDEX icuidx08_d_en_fr_ga ON collate_test(id) WHERE (t_en_fr_ga) = ('foo', 'bar', 'baz'); +CREATE INDEX icuidx09_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga) = ('foo', 'bar', 'baz'); +CREATE INDEX icuidx10_d_en_fr_ga_es ON collate_test(id) WHERE (d_en_fr_ga) = ('foo', 'bar', 'baz' COLLATE "es-x-icu"); +CREATE INDEX icuidx11_d_es ON collate_test(id) WHERE (d_es) = ('foo'); +CREATE INDEX icuidx12_custom ON collate_test(id) WHERE ('foo', 'bar')::d_custom = ('foo', 'bar' COLLATE custom)::d_custom; +CREATE INDEX icuidx13_custom ON collate_test(id) WHERE ('foo' COLLATE custom, 'bar')::d_custom = ('foo', 'bar')::d_custom; +CREATE INDEX icuidx14_myrange ON collate_test(myrange); +CREATE INDEX icuidx15_myrange_en_fr_ga ON collate_test USING gist (myrange_en_fr_ga); +CREATE INDEX icuidx16_mood ON collate_test(id) WHERE mood > 'ok' COLLATE "fr-x-icu"; + +CREATE TABLE collate_part(id integer, val text COLLATE "en-x-icu") PARTITION BY range(id); +CREATE TABLE collate_part_0 PARTITION OF collate_part FOR VALUES FROM (0) TO (1); +CREATE TABLE collate_part_1 PARTITION OF collate_part FOR VALUES FROM (1) TO (1000000); +CREATE INDEX icuidx17_part ON collate_part_1 (val); + +SELECT objid::regclass::text collate "C", refobjid::regcollation::text collate "C", +CASE +WHEN refobjid = 'default'::regcollation THEN 'XXX' -- depends on libc version support +WHEN refobjversion IS NULL THEN 'version not tracked' +WHEN refobjversion = pg_collation_actual_version(refobjid) THEN 'up to date' +ELSE 'out of date' +END AS version +FROM pg_depend d +LEFT JOIN pg_class c ON c.oid = d.objid +WHERE refclassid = 'pg_collation'::regclass +AND coalesce(relkind, 'i') = 'i' +AND relname LIKE 'icuidx%' +ORDER BY 1, 2, 3; + +-- Validate that REINDEX will update the stored version. +UPDATE pg_depend SET refobjversion = 'not a version' +WHERE refclassid = 'pg_collation'::regclass +AND objid::regclass::text LIKE 'icuidx%' +AND refobjversion IS NOT NULL; + +REINDEX TABLE collate_test; +REINDEX TABLE collate_part_0; +REINDEX TABLE collate_part_1; + +SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version'; + +-- Validate that REINDEX CONCURRENTLY will update the stored version. +UPDATE pg_depend SET refobjversion = 'not a version' +WHERE refclassid = 'pg_collation'::regclass +AND objid::regclass::text LIKE 'icuidx%' +AND refobjversion IS NOT NULL; +REINDEX TABLE CONCURRENTLY collate_test; +REINDEX TABLE CONCURRENTLY collate_part_0; +REINDEX INDEX CONCURRENTLY icuidx17_part; + +SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version'; + +-- Validate that VACUUM FULL will update the stored version. +UPDATE pg_depend SET refobjversion = 'not a version' +WHERE refclassid = 'pg_collation'::regclass +AND objid::regclass::text LIKE 'icuidx%' +AND refobjversion IS NOT NULL; +VACUUM FULL collate_test; +VACUUM FULL collate_part_0; +VACUUM FULL collate_part_1; + +SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version'; + +-- Test ALTER INDEX name ALTER COLLATION name REFRESH VERSION +UPDATE pg_depend SET refobjversion = 'not a version' +WHERE refclassid = 'pg_collation'::regclass +AND objid::regclass::text = 'icuidx17_part' +AND refobjversion IS NOT NULL; +SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version'; +ALTER INDEX icuidx17_part ALTER COLLATION "en-x-icu" REFRESH VERSION; +SELECT objid::regclass, refobjversion = 'not a version' AS ver FROM pg_depend +WHERE refclassid = 'pg_collation'::regclass +AND objid::regclass::text = 'icuidx17_part'; -- cleanup RESET search_path; diff --git a/src/test/regress/sql/collate.linux.utf8.sql b/src/test/regress/sql/collate.linux.utf8.sql index cbbd2203e413..c697c9948805 100644 --- a/src/test/regress/sql/collate.linux.utf8.sql +++ b/src/test/regress/sql/collate.linux.utf8.sql @@ -406,11 +406,6 @@ DROP SCHEMA test_schema; DROP ROLE regress_test_role; --- ALTER - -ALTER COLLATION "en_US" REFRESH VERSION; - - -- dependencies CREATE COLLATION test0 FROM "C"; diff --git a/src/test/regress/sql/copy2.sql b/src/test/regress/sql/copy2.sql index 09a31a968d08..13ef7bf0a4ac 100644 --- a/src/test/regress/sql/copy2.sql +++ b/src/test/regress/sql/copy2.sql @@ -53,6 +53,20 @@ COPY x (a, b, c, d, e) from stdin; -- non-existent column in column list: should fail COPY x (xyz) from stdin; +-- redundant options +COPY x from stdin (format CSV, FORMAT CSV); +COPY x from stdin (freeze off, freeze on); +COPY x from stdin (delimiter ',', delimiter ','); +COPY x from stdin (null ' ', null ' '); +COPY x from stdin (header off, header on); +COPY x from stdin (quote ':', quote ':'); +COPY x from stdin (escape ':', escape ':'); +COPY x from stdin (force_quote (a), force_quote *); +COPY x from stdin (force_not_null (a), force_not_null (b)); +COPY x from stdin (force_null (a), force_null (b)); +COPY x from stdin (convert_selectively (a), convert_selectively (b)); +COPY x from stdin (encoding 'sql_ascii', encoding 'sql_ascii'); + -- too many columns in column list: should fail COPY x (a, b, c, d, e, d, c) from stdin; diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 7ba4286130c9..cfa1045e4356 100644 --- a/src/test/regress/sql/create_am.sql +++ b/src/test/regress/sql/create_am.sql @@ -28,8 +28,6 @@ CREATE OPERATOR CLASS box_ops DEFAULT OPERATOR 10 <<|, OPERATOR 11 |>>, OPERATOR 12 |&>, - OPERATOR 13 ~, - OPERATOR 14 @, FUNCTION 1 gist_box_consistent(internal, box, smallint, oid, internal), FUNCTION 2 gist_box_union(internal, internal), -- don't need compress, decompress, or fetch functions @@ -50,10 +48,10 @@ SET enable_bitmapscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; EXPLAIN (COSTS OFF) @@ -70,7 +68,12 @@ ROLLBACK; DROP ACCESS METHOD gist2; -- Drop access method cascade +-- To prevent a (rare) deadlock against autovacuum, +-- we must lock the table that owns the index that will be dropped +BEGIN; +LOCK TABLE fast_emp4000; DROP ACCESS METHOD gist2 CASCADE; +COMMIT; -- diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql index 7a2df0ea8a1b..bd108a918fbf 100644 --- a/src/test/regress/sql/create_function_3.sql +++ b/src/test/regress/sql/create_function_3.sql @@ -23,7 +23,7 @@ SET search_path TO temp_func_test, public; CREATE FUNCTION functest_A_1(text, date) RETURNS bool LANGUAGE 'sql' AS 'SELECT $1 = ''abcd'' AND $2 > ''2001-01-01'''; CREATE FUNCTION functest_A_2(text[]) RETURNS int LANGUAGE 'sql' - AS 'SELECT $1[0]::int'; + AS 'SELECT $1[1]::int'; CREATE FUNCTION functest_A_3() RETURNS bool LANGUAGE 'sql' AS 'SELECT false'; SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc @@ -31,6 +31,10 @@ SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc 'functest_A_2'::regproc, 'functest_A_3'::regproc) ORDER BY proname; +SELECT functest_A_1('abcd', '2020-01-01'); +SELECT functest_A_2(ARRAY['1', '2', '3']); +SELECT functest_A_3(); + -- -- IMMUTABLE | STABLE | VOLATILE -- diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index 9d834b615622..7ec6ce61d3f7 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -110,14 +110,14 @@ SET enable_indexscan = OFF; SET enable_bitmapscan = OFF; SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box; SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; -SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon +SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; SELECT * FROM circle_tbl WHERE f1 && circle(point(1,-2), 1) @@ -167,10 +167,10 @@ SET enable_bitmapscan = OFF; EXPLAIN (COSTS OFF) SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; SELECT * FROM fast_emp4000 - WHERE home_base @ '(200,200),(2000,1000)'::box + WHERE home_base <@ '(200,200),(2000,1000)'::box ORDER BY (home_base[0])[0]; EXPLAIN (COSTS OFF) @@ -182,9 +182,9 @@ SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; EXPLAIN (COSTS OFF) -SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon +SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; -SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon +SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; EXPLAIN (COSTS OFF) @@ -1112,6 +1112,12 @@ CREATE UNIQUE INDEX concur_exprs_index_pred ON concur_exprs_tab (c1) CREATE UNIQUE INDEX concur_exprs_index_pred_2 ON concur_exprs_tab (c1, (1 / c1)) WHERE ('-H') >= (c2::TEXT) COLLATE "C"; +ANALYZE concur_exprs_tab; +SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN ( + 'concur_exprs_index_expr'::regclass, + 'concur_exprs_index_pred'::regclass, + 'concur_exprs_index_pred_2'::regclass) + GROUP BY starelid ORDER BY starelid::regclass::text; SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass); SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass); SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass); @@ -1124,6 +1130,12 @@ ALTER TABLE concur_exprs_tab ALTER c2 TYPE TEXT; SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass); SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass); SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass); +-- Statistics should remain intact. +SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN ( + 'concur_exprs_index_expr'::regclass, + 'concur_exprs_index_pred'::regclass, + 'concur_exprs_index_pred_2'::regclass) + GROUP BY starelid ORDER BY starelid::regclass::text; DROP TABLE concur_exprs_tab; -- Temporary tables and on-commit actions, where CONCURRENTLY is ignored. diff --git a/src/test/regress/sql/create_operator.sql b/src/test/regress/sql/create_operator.sql index 8b6fd0bb43d6..4ff2c0ff2167 100644 --- a/src/test/regress/sql/create_operator.sql +++ b/src/test/regress/sql/create_operator.sql @@ -18,18 +18,13 @@ CREATE OPERATOR <% ( ); CREATE OPERATOR @#@ ( - rightarg = int8, -- left unary - procedure = numeric_fac -); - -CREATE OPERATOR #@# ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, -- prefix + procedure = factorial ); CREATE OPERATOR #%# ( - leftarg = int8, -- right unary - procedure = numeric_fac + leftarg = int8, -- fail, postfix is no longer supported + procedure = factorial ); -- Test operator created above @@ -37,12 +32,19 @@ SELECT point '(1,2)' <% widget '(0,0,3)' AS t, point '(1,2)' <% widget '(0,0,1)' AS f; -- Test comments -COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad right unary'; +COMMENT ON OPERATOR ###### (NONE, int4) IS 'bad prefix'; +COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad postfix'; +COMMENT ON OPERATOR ###### (int4, int8) IS 'bad infix'; --- => is disallowed now +-- Check that DROP on a nonexistent op behaves sanely, too +DROP OPERATOR ###### (NONE, int4); +DROP OPERATOR ###### (int4, NONE); +DROP OPERATOR ###### (int4, int8); + +-- => is disallowed as an operator name now CREATE OPERATOR => ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, + procedure = factorial ); -- lexing of <=, >=, <>, != has a number of edge cases @@ -50,10 +52,12 @@ CREATE OPERATOR => ( -- this is legal because ! is not allowed in sql ops CREATE OPERATOR !=- ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, + procedure = factorial ); -SELECT 2 !=-; +SELECT !=- 10; +-- postfix operators don't work anymore +SELECT 10 !=-; -- make sure lexer returns != as <> even in edge cases SELECT 2 !=/**/ 1, 2 !=/**/ 2; SELECT 2 !=-- comment to be removed by psql @@ -84,8 +88,8 @@ GRANT USAGE ON SCHEMA schema_op1 TO PUBLIC; REVOKE USAGE ON SCHEMA schema_op1 FROM regress_rol_op1; SET ROLE regress_rol_op1; CREATE OPERATOR schema_op1.#*# ( - leftarg = int8, -- right unary - procedure = numeric_fac + rightarg = int8, + procedure = factorial ); ROLLBACK; @@ -94,7 +98,7 @@ ROLLBACK; BEGIN TRANSACTION; CREATE OPERATOR #*# ( leftarg = SETOF int8, - procedure = numeric_fac + procedure = factorial ); ROLLBACK; @@ -103,7 +107,7 @@ ROLLBACK; BEGIN TRANSACTION; CREATE OPERATOR #*# ( rightarg = SETOF int8, - procedure = numeric_fac + procedure = factorial ); ROLLBACK; @@ -128,19 +132,19 @@ ROLLBACK; -- Should fail. Invalid attribute CREATE OPERATOR #@%# ( - leftarg = int8, -- right unary - procedure = numeric_fac, + rightarg = int8, + procedure = factorial, invalid_att = int8 ); --- Should fail. At least leftarg or rightarg should be mandatorily specified +-- Should fail. At least rightarg should be mandatorily specified CREATE OPERATOR #@%# ( - procedure = numeric_fac + procedure = factorial ); -- Should fail. Procedure should be mandatorily specified CREATE OPERATOR #@%# ( - leftarg = int8 + rightarg = int8 ); -- Should fail. CREATE OPERATOR requires USAGE on TYPE diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql index 89b96d580ffa..2ef1c82ceabe 100644 --- a/src/test/regress/sql/create_procedure.sql +++ b/src/test/regress/sql/create_procedure.sql @@ -112,6 +112,18 @@ $$; CALL ptest7(least('a', 'b'), 'a'); +-- OUT parameters + +CREATE PROCEDURE ptest9(OUT a int) +LANGUAGE SQL +AS $$ +INSERT INTO cp_test VALUES (1, 'a'); +SELECT 1; +$$; + +CALL ptest9(NULL); + + -- various error cases CALL version(); -- error: not a procedure @@ -119,7 +131,6 @@ CALL sum(1); -- error: not a procedure CREATE PROCEDURE ptestx() LANGUAGE SQL WINDOW AS $$ INSERT INTO cp_test VALUES (1, 'a') $$; CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT INTO cp_test VALUES (1, 'a') $$; -CREATE PROCEDURE ptestx(OUT a int) LANGUAGE SQL AS $$ INSERT INTO cp_test VALUES (1, 'a') $$; ALTER PROCEDURE ptest1(text) STRICT; ALTER FUNCTION ptest1(text) VOLATILE; -- error: not a function diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index 1942113ffa96..b63750d456f0 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -549,7 +549,6 @@ CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (sum(so CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (sum(1)); CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN ((select 1)); CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (generate_series(4, 6)); -CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN ('1' collate "POSIX"); CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN ((1+1) collate "POSIX"); -- syntax does not allow empty list of values for list partitions @@ -688,6 +687,7 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1); CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (minvalue) TO (1); CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (minvalue) TO (2); CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10); +CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (-1) TO (1); CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (maxvalue); CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30); CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40); @@ -802,6 +802,14 @@ insert into parted_notnull_inh_test (b) values (null); \d parted_notnull_inh_test1 drop table parted_notnull_inh_test; +-- check that collations are assigned in partition bound expressions +create table parted_boolean_col (a bool, b text) partition by list(a); +create table parted_boolean_less partition of parted_boolean_col + for values in ('foo' < 'bar'); +create table parted_boolean_greater partition of parted_boolean_col + for values in ('foo' > 'bar'); +drop table parted_boolean_col; + -- check for a conflicting COLLATE clause create table parted_collate_must_match (a text collate "C", b text collate "C") partition by range (a); @@ -813,23 +821,16 @@ create table parted_collate_must_match2 partition of parted_collate_must_match (b collate "POSIX") for values from ('m') to ('z'); drop table parted_collate_must_match; --- check that specifying incompatible collations for partition bound --- expressions fails promptly +-- check that non-matching collations for partition bound +-- expressions are coerced to the right collation create table test_part_coll_posix (a text) partition by range (a collate "POSIX"); --- fail +-- ok, collation is implicitly coerced create table test_part_coll partition of test_part_coll_posix for values from ('a' collate "C") to ('g'); -- ok -create table test_part_coll partition of test_part_coll_posix for values from ('a' collate "POSIX") to ('g'); --- ok create table test_part_coll2 partition of test_part_coll_posix for values from ('g') to ('m'); - --- using a cast expression uses the target type's default collation - --- fail +-- ok, collation is implicitly coerced create table test_part_coll_cast partition of test_part_coll_posix for values from (name 'm' collate "C") to ('s'); --- ok -create table test_part_coll_cast partition of test_part_coll_posix for values from (name 'm' collate "POSIX") to ('s'); -- ok; partition collation silently overrides the default collation of type 'name' create table test_part_coll_cast2 partition of test_part_coll_posix for values from (name 's') to ('z'); diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql index a5f309a2d064..14e23849b1d4 100644 --- a/src/test/regress/sql/create_table_like.sql +++ b/src/test/regress/sql/create_table_like.sql @@ -66,7 +66,9 @@ SELECT * FROM test_like_gen_3; DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3; -- also test generated column with a "forward" reference (bug #16342) -CREATE TABLE test_like_4 (b int DEFAULT 42, c int GENERATED ALWAYS AS (a * 2) STORED, a int); +CREATE TABLE test_like_4 (b int DEFAULT 42, + c int GENERATED ALWAYS AS (a * 2) STORED, + a int CHECK (a > 0)); \d test_like_4 CREATE TABLE test_like_4a (LIKE test_like_4); CREATE TABLE test_like_4b (LIKE test_like_4 INCLUDING DEFAULTS); @@ -84,7 +86,17 @@ SELECT a, b, c FROM test_like_4c; \d test_like_4d INSERT INTO test_like_4d (a) VALUES(11); SELECT a, b, c FROM test_like_4d; + +-- Test renumbering of Vars when combining LIKE with inheritance +CREATE TABLE test_like_5 (x point, y point, z point); +CREATE TABLE test_like_5x (p int CHECK (p > 0), + q int GENERATED ALWAYS AS (p * 2) STORED); +CREATE TABLE test_like_5c (LIKE test_like_4 INCLUDING ALL) + INHERITS (test_like_5, test_like_5x); +\d test_like_5c + DROP TABLE test_like_4, test_like_4a, test_like_4b, test_like_4c, test_like_4d; +DROP TABLE test_like_5, test_like_5x, test_like_5c; CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */ INSERT INTO inhg VALUES (5, 10); @@ -121,9 +133,10 @@ CREATE TABLE ctlt2 (c text); ALTER TABLE ctlt2 ALTER COLUMN c SET STORAGE EXTERNAL; COMMENT ON COLUMN ctlt2.c IS 'C'; -CREATE TABLE ctlt3 (a text CHECK (length(a) < 5), c text); +CREATE TABLE ctlt3 (a text CHECK (length(a) < 5), c text CHECK (length(c) < 7)); ALTER TABLE ctlt3 ALTER COLUMN c SET STORAGE EXTERNAL; ALTER TABLE ctlt3 ALTER COLUMN a SET STORAGE MAIN; +CREATE INDEX ctlt3_fnidx ON ctlt3 ((a || c)); COMMENT ON COLUMN ctlt3.a IS 'A3'; COMMENT ON COLUMN ctlt3.c IS 'C'; COMMENT ON CONSTRAINT ctlt3_a_check ON ctlt3 IS 't3_a_check'; @@ -140,7 +153,7 @@ CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INH SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass; CREATE TABLE ctlt13_inh () INHERITS (ctlt1, ctlt3); \d+ ctlt13_inh -CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1); +CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1); \d+ ctlt13_like SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass; @@ -152,6 +165,11 @@ SELECT s.stxname, objsubid, description FROM pg_description, pg_statistic_ext s CREATE TABLE inh_error1 () INHERITS (ctlt1, ctlt4); CREATE TABLE inh_error2 (LIKE ctlt4 INCLUDING STORAGE) INHERITS (ctlt1); +-- Check that LIKE isn't confused by a system catalog of the same name +CREATE TABLE pg_attrdef (LIKE ctlt1 INCLUDING ALL); +\d+ public.pg_attrdef +DROP TABLE public.pg_attrdef; + DROP TABLE ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctlb, ctla, ctlt1, ctlt2, ctlt3, ctlt4 CASCADE; -- LIKE must respect NO INHERIT property of constraints diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql index e8041443f669..2624d9f6be50 100644 --- a/src/test/regress/sql/date.sql +++ b/src/test/regress/sql/date.sql @@ -20,12 +20,13 @@ INSERT INTO DATE_TBL VALUES ('2000-04-03'); INSERT INTO DATE_TBL VALUES ('2038-04-08'); INSERT INTO DATE_TBL VALUES ('2039-04-09'); INSERT INTO DATE_TBL VALUES ('2040-04-10'); +INSERT INTO DATE_TBL VALUES ('2040-04-10 BC'); -SELECT f1 AS "Fifteen" FROM DATE_TBL; +SELECT f1 FROM DATE_TBL; -SELECT f1 AS "Nine" FROM DATE_TBL WHERE f1 < '2000-01-01'; +SELECT f1 FROM DATE_TBL WHERE f1 < '2000-01-01'; -SELECT f1 AS "Three" FROM DATE_TBL +SELECT f1 FROM DATE_TBL WHERE f1 BETWEEN '2000-01-01' AND '2001-01-01'; -- @@ -251,6 +252,23 @@ SELECT date 'tomorrow' - date 'yesterday' AS "Two days"; -- -- test extract! -- +SELECT f1 as "date", + date_part('year', f1) AS year, + date_part('month', f1) AS month, + date_part('day', f1) AS day, + date_part('quarter', f1) AS quarter, + date_part('decade', f1) AS decade, + date_part('century', f1) AS century, + date_part('millennium', f1) AS millennium, + date_part('isoyear', f1) AS isoyear, + date_part('week', f1) AS week, + date_part('dow', f1) AS dow, + date_part('isodow', f1) AS isodow, + date_part('doy', f1) AS doy, + date_part('julian', f1) AS julian, + date_part('epoch', f1) AS epoch + FROM date_tbl; +-- -- epoch -- SELECT EXTRACT(EPOCH FROM DATE '1970-01-01'); -- 0 @@ -297,6 +315,31 @@ SELECT EXTRACT(DECADE FROM DATE '0012-12-31 BC'); -- -2 SELECT EXTRACT(CENTURY FROM NOW())>=21 AS True; -- true SELECT EXTRACT(CENTURY FROM TIMESTAMP '1970-03-20 04:30:00.00000'); -- 20 -- +-- all possible fields +-- +SELECT EXTRACT(MICROSECONDS FROM DATE '2020-08-11'); +SELECT EXTRACT(MILLISECONDS FROM DATE '2020-08-11'); +SELECT EXTRACT(SECOND FROM DATE '2020-08-11'); +SELECT EXTRACT(MINUTE FROM DATE '2020-08-11'); +SELECT EXTRACT(HOUR FROM DATE '2020-08-11'); +SELECT EXTRACT(DAY FROM DATE '2020-08-11'); +SELECT EXTRACT(MONTH FROM DATE '2020-08-11'); +SELECT EXTRACT(YEAR FROM DATE '2020-08-11'); +SELECT EXTRACT(DECADE FROM DATE '2020-08-11'); +SELECT EXTRACT(CENTURY FROM DATE '2020-08-11'); +SELECT EXTRACT(MILLENNIUM FROM DATE '2020-08-11'); +SELECT EXTRACT(ISOYEAR FROM DATE '2020-08-11'); +SELECT EXTRACT(QUARTER FROM DATE '2020-08-11'); +SELECT EXTRACT(WEEK FROM DATE '2020-08-11'); +SELECT EXTRACT(DOW FROM DATE '2020-08-11'); +SELECT EXTRACT(ISODOW FROM DATE '2020-08-11'); +SELECT EXTRACT(DOY FROM DATE '2020-08-11'); +SELECT EXTRACT(TIMEZONE FROM DATE '2020-08-11'); +SELECT EXTRACT(TIMEZONE_M FROM DATE '2020-08-11'); +SELECT EXTRACT(TIMEZONE_H FROM DATE '2020-08-11'); +SELECT EXTRACT(EPOCH FROM DATE '2020-08-11'); +SELECT EXTRACT(JULIAN FROM DATE '2020-08-11'); +-- -- test trunc function! -- SELECT DATE_TRUNC('MILLENNIUM', TIMESTAMP '1970-03-20 04:30:00.00000'); -- 1001 @@ -368,6 +411,7 @@ select make_date(2013, 7, 15); select make_date(-44, 3, 15); select make_time(8, 20, 0.0); -- should fail +select make_date(0, 7, 15); select make_date(2013, 2, 30); select make_date(2013, 13, 1); select make_date(2013, 11, -1); diff --git a/src/test/regress/sql/errors.sql b/src/test/regress/sql/errors.sql index 0cf896157c06..9cdf82cf3abb 100644 --- a/src/test/regress/sql/errors.sql +++ b/src/test/regress/sql/errors.sql @@ -378,22 +378,3 @@ INT4 UNIQUE NOT NULL); - --- Check that stack depth detection mechanism works and --- max_stack_depth is not set too high. The full error report is not --- very stable, so show only SQLSTATE and primary error message. -create function infinite_recurse() returns int as -'select infinite_recurse()' language sql CONTAINS SQL; -\set VERBOSITY sqlstate --- start_matchsubs --- # mpp-2756 --- m/(ERROR|WARNING|CONTEXT|NOTICE):.*stack depth limit exceeded\s+at\s+character/ --- s/\s+at\s+character.*// --- m/ERROR:.*GPDB exception. Aborting Pivotal Optimizer \(GPORCA\).*/ --- s/ERROR:.*GPDB exception. Aborting Pivotal Optimizer \(GPORCA\).*// --- end_matchsubs --- start_ignore -select infinite_recurse(); --- end_ignore -\echo :LAST_ERROR_MESSAGE -select 1; -- test that this works diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql index 55bfe54730fa..e4eaf584f62b 100644 --- a/src/test/regress/sql/explain.sql +++ b/src/test/regress/sql/explain.sql @@ -29,6 +29,9 @@ begin -- Ignore text-mode buffers output because it varies depending -- on the system state CONTINUE WHEN (ln ~ ' +Buffers: .*'); + -- Ignore text-mode "Planning:" line because whether it's output + -- varies depending on the system state + CONTINUE WHEN (ln = 'Planning:'); return next ln; end loop; end; @@ -61,6 +64,8 @@ select explain_filter('explain (analyze, buffers, format text) select * from int select explain_filter('explain (analyze, buffers, format json) select * from int8_tbl i8'); select explain_filter('explain (analyze, buffers, format xml) select * from int8_tbl i8'); select explain_filter('explain (analyze, buffers, format yaml) select * from int8_tbl i8'); +select explain_filter('explain (buffers, format text) select * from int8_tbl i8'); +select explain_filter('explain (buffers, format json) select * from int8_tbl i8'); -- SETTINGS option -- We have to ignore other settings that might be imposed by the environment, diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql index db582d1b19a1..117bd954bb90 100644 --- a/src/test/regress/sql/generated.sql +++ b/src/test/regress/sql/generated.sql @@ -419,7 +419,7 @@ CREATE TABLE gtest30 ( b int GENERATED ALWAYS AS (a * 2) STORED ); CREATE TABLE gtest30_1 () INHERITS (gtest30); -ALTER TABLE ONLY gtest30 ALTER COLUMN b DROP EXPRESSION; +ALTER TABLE ONLY gtest30 ALTER COLUMN b DROP EXPRESSION; -- error \d gtest30 \d gtest30_1 ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 91996e906861..c2c12fdd1812 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -145,4 +145,36 @@ from reset enable_seqscan; reset enable_bitmapscan; +-- re-purpose t_gin_test_tbl to test scans involving posting trees +insert into t_gin_test_tbl select array[1, g, g/10], array[2, g, g/10] + from generate_series(1, 20000) g; + +select gin_clean_pending_list('t_gin_test_tbl_i_j_idx') is not null; + +analyze t_gin_test_tbl; + +set enable_seqscan = off; +set enable_bitmapscan = on; + +explain (costs off) +select count(*) from t_gin_test_tbl where j @> array[50]; +select count(*) from t_gin_test_tbl where j @> array[50]; +explain (costs off) +select count(*) from t_gin_test_tbl where j @> array[2]; +select count(*) from t_gin_test_tbl where j @> array[2]; +explain (costs off) +select count(*) from t_gin_test_tbl where j @> '{}'::int[]; +select count(*) from t_gin_test_tbl where j @> '{}'::int[]; + +-- test vacuuming of posting trees +delete from t_gin_test_tbl where j @> array[2]; +vacuum t_gin_test_tbl; + +select count(*) from t_gin_test_tbl where j @> array[50]; +select count(*) from t_gin_test_tbl where j @> array[2]; +select count(*) from t_gin_test_tbl where j @> '{}'::int[]; + +reset enable_seqscan; +reset enable_bitmapscan; + drop table t_gin_test_tbl; diff --git a/src/test/regress/sql/groupingsets.sql b/src/test/regress/sql/groupingsets.sql index 75b6f425ff58..1142a2bc4e58 100644 --- a/src/test/regress/sql/groupingsets.sql +++ b/src/test/regress/sql/groupingsets.sql @@ -247,6 +247,22 @@ select x, not x as not_x, q2 from group by grouping sets(x, q2) order by x, q2; +-- check qual push-down rules for a subquery with grouping sets +explain (verbose, costs off) +select * from ( + select 1 as x, q1, sum(q2) + from int8_tbl i1 + group by grouping sets(1, 2) +) ss +where x = 1 and q1 = 123; + +select * from ( + select 1 as x, q1, sum(q2) + from int8_tbl i1 + group by grouping sets(1, 2) +) ss +where x = 1 and q1 = 123; + -- simple rescan tests select a, b, sum(v.x) @@ -514,6 +530,7 @@ select array(select row(v.a,s1.*) from (select two,four, count(*) from onek grou set enable_indexscan = false; set work_mem = '64kB'; +set hash_mem_multiplier = 2; explain (costs off) select unique1, count(two), count(four), count(ten), @@ -526,6 +543,7 @@ explain (costs off) count(hundred), count(thousand), count(twothousand), count(*) from tenk1 group by grouping sets (unique1,hundred,ten,four,two); +reset hash_mem_multiplier; set work_mem = '384kB'; explain (costs off) diff --git a/src/test/regress/sql/hash_func.sql b/src/test/regress/sql/hash_func.sql index b7ce8b21a3a0..a3e2decc2c4b 100644 --- a/src/test/regress/sql/hash_func.sql +++ b/src/test/regress/sql/hash_func.sql @@ -14,66 +14,66 @@ WHERE hashint2(v)::bit(32) != hashint2extended(v, 0)::bit(32) OR hashint2(v)::bit(32) = hashint2extended(v, 1)::bit(32); SELECT v as value, hashint4(v)::bit(32) as standard, - hashint4extended(v, 0)::bit(32) as extended0, - hashint4extended(v, 1)::bit(32) as extended1 + hashint4extended(v, 0)::bit(32) as extended0, + hashint4extended(v, 1)::bit(32) as extended1 FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) WHERE hashint4(v)::bit(32) != hashint4extended(v, 0)::bit(32) OR hashint4(v)::bit(32) = hashint4extended(v, 1)::bit(32); SELECT v as value, hashint8(v)::bit(32) as standard, - hashint8extended(v, 0)::bit(32) as extended0, - hashint8extended(v, 1)::bit(32) as extended1 + hashint8extended(v, 0)::bit(32) as extended0, + hashint8extended(v, 1)::bit(32) as extended1 FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) WHERE hashint8(v)::bit(32) != hashint8extended(v, 0)::bit(32) OR hashint8(v)::bit(32) = hashint8extended(v, 1)::bit(32); SELECT v as value, hashfloat4(v)::bit(32) as standard, - hashfloat4extended(v, 0)::bit(32) as extended0, - hashfloat4extended(v, 1)::bit(32) as extended1 + hashfloat4extended(v, 0)::bit(32) as extended0, + hashfloat4extended(v, 1)::bit(32) as extended1 FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) WHERE hashfloat4(v)::bit(32) != hashfloat4extended(v, 0)::bit(32) OR hashfloat4(v)::bit(32) = hashfloat4extended(v, 1)::bit(32); SELECT v as value, hashfloat8(v)::bit(32) as standard, - hashfloat8extended(v, 0)::bit(32) as extended0, - hashfloat8extended(v, 1)::bit(32) as extended1 + hashfloat8extended(v, 0)::bit(32) as extended0, + hashfloat8extended(v, 1)::bit(32) as extended1 FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) WHERE hashfloat8(v)::bit(32) != hashfloat8extended(v, 0)::bit(32) OR hashfloat8(v)::bit(32) = hashfloat8extended(v, 1)::bit(32); SELECT v as value, hashoid(v)::bit(32) as standard, - hashoidextended(v, 0)::bit(32) as extended0, - hashoidextended(v, 1)::bit(32) as extended1 + hashoidextended(v, 0)::bit(32) as extended0, + hashoidextended(v, 1)::bit(32) as extended1 FROM (VALUES (0), (1), (17), (42), (550273), (207112489)) x(v) WHERE hashoid(v)::bit(32) != hashoidextended(v, 0)::bit(32) OR hashoid(v)::bit(32) = hashoidextended(v, 1)::bit(32); SELECT v as value, hashchar(v)::bit(32) as standard, - hashcharextended(v, 0)::bit(32) as extended0, - hashcharextended(v, 1)::bit(32) as extended1 + hashcharextended(v, 0)::bit(32) as extended0, + hashcharextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::"char"), ('1'), ('x'), ('X'), ('p'), ('N')) x(v) WHERE hashchar(v)::bit(32) != hashcharextended(v, 0)::bit(32) OR hashchar(v)::bit(32) = hashcharextended(v, 1)::bit(32); SELECT v as value, hashname(v)::bit(32) as standard, - hashnameextended(v, 0)::bit(32) as extended0, - hashnameextended(v, 1)::bit(32) as extended1 + hashnameextended(v, 0)::bit(32) as extended0, + hashnameextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL), ('PostgreSQL'), ('eIpUEtqmY89'), ('AXKEJBTK'), - ('muop28x03'), ('yi3nm0d73')) x(v) + ('muop28x03'), ('yi3nm0d73')) x(v) WHERE hashname(v)::bit(32) != hashnameextended(v, 0)::bit(32) OR hashname(v)::bit(32) = hashnameextended(v, 1)::bit(32); SELECT v as value, hashtext(v)::bit(32) as standard, - hashtextextended(v, 0)::bit(32) as extended0, - hashtextextended(v, 1)::bit(32) as extended1 + hashtextextended(v, 0)::bit(32) as extended0, + hashtextextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL), ('PostgreSQL'), ('eIpUEtqmY89'), ('AXKEJBTK'), - ('muop28x03'), ('yi3nm0d73')) x(v) + ('muop28x03'), ('yi3nm0d73')) x(v) WHERE hashtext(v)::bit(32) != hashtextextended(v, 0)::bit(32) OR hashtext(v)::bit(32) = hashtextextended(v, 1)::bit(32); SELECT v as value, hashoidvector(v)::bit(32) as standard, - hashoidvectorextended(v, 0)::bit(32) as extended0, - hashoidvectorextended(v, 1)::bit(32) as extended1 + hashoidvectorextended(v, 0)::bit(32) as extended0, + hashoidvectorextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::oidvector), ('0 1 2 3 4'), ('17 18 19 20'), ('42 43 42 45'), ('550273 550273 570274'), ('207112489 207112499 21512 2155 372325 1363252')) x(v) @@ -81,40 +81,40 @@ WHERE hashoidvector(v)::bit(32) != hashoidvectorextended(v, 0)::bit(32) OR hashoidvector(v)::bit(32) = hashoidvectorextended(v, 1)::bit(32); SELECT v as value, hash_aclitem(v)::bit(32) as standard, - hash_aclitem_extended(v, 0)::bit(32) as extended0, - hash_aclitem_extended(v, 1)::bit(32) as extended1 + hash_aclitem_extended(v, 0)::bit(32) as extended0, + hash_aclitem_extended(v, 1)::bit(32) as extended1 FROM (SELECT DISTINCT(relacl[1]) FROM pg_class LIMIT 10) x(v) WHERE hash_aclitem(v)::bit(32) != hash_aclitem_extended(v, 0)::bit(32) OR hash_aclitem(v)::bit(32) = hash_aclitem_extended(v, 1)::bit(32); SELECT v as value, hashmacaddr(v)::bit(32) as standard, - hashmacaddrextended(v, 0)::bit(32) as extended0, - hashmacaddrextended(v, 1)::bit(32) as extended1 + hashmacaddrextended(v, 0)::bit(32) as extended0, + hashmacaddrextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::macaddr), ('08:00:2b:01:02:04'), ('08:00:2b:01:02:04'), - ('e2:7f:51:3e:70:49'), ('d6:a9:4a:78:1c:d5'), + ('e2:7f:51:3e:70:49'), ('d6:a9:4a:78:1c:d5'), ('ea:29:b1:5e:1f:a5')) x(v) WHERE hashmacaddr(v)::bit(32) != hashmacaddrextended(v, 0)::bit(32) OR hashmacaddr(v)::bit(32) = hashmacaddrextended(v, 1)::bit(32); SELECT v as value, hashinet(v)::bit(32) as standard, - hashinetextended(v, 0)::bit(32) as extended0, - hashinetextended(v, 1)::bit(32) as extended1 + hashinetextended(v, 0)::bit(32) as extended0, + hashinetextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::inet), ('192.168.100.128/25'), ('192.168.100.0/8'), - ('172.168.10.126/16'), ('172.18.103.126/24'), ('192.188.13.16/32')) x(v) + ('172.168.10.126/16'), ('172.18.103.126/24'), ('192.188.13.16/32')) x(v) WHERE hashinet(v)::bit(32) != hashinetextended(v, 0)::bit(32) OR hashinet(v)::bit(32) = hashinetextended(v, 1)::bit(32); SELECT v as value, hash_numeric(v)::bit(32) as standard, - hash_numeric_extended(v, 0)::bit(32) as extended0, - hash_numeric_extended(v, 1)::bit(32) as extended1 + hash_numeric_extended(v, 0)::bit(32) as extended0, + hash_numeric_extended(v, 1)::bit(32) as extended1 FROM (VALUES (0), (1.149484958), (17.149484958), (42.149484958), (149484958.550273), (2071124898672)) x(v) WHERE hash_numeric(v)::bit(32) != hash_numeric_extended(v, 0)::bit(32) OR hash_numeric(v)::bit(32) = hash_numeric_extended(v, 1)::bit(32); SELECT v as value, hashmacaddr8(v)::bit(32) as standard, - hashmacaddr8extended(v, 0)::bit(32) as extended0, - hashmacaddr8extended(v, 1)::bit(32) as extended1 + hashmacaddr8extended(v, 0)::bit(32) as extended0, + hashmacaddr8extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::macaddr8), ('08:00:2b:01:02:04:36:49'), ('08:00:2b:01:02:04:f0:e8'), ('e2:7f:51:3e:70:49:16:29'), ('d6:a9:4a:78:1c:d5:47:32'), ('ea:29:b1:5e:1f:a5')) x(v) @@ -122,8 +122,8 @@ WHERE hashmacaddr8(v)::bit(32) != hashmacaddr8extended(v, 0)::bit(32) OR hashmacaddr8(v)::bit(32) = hashmacaddr8extended(v, 1)::bit(32); SELECT v as value, hash_array(v)::bit(32) as standard, - hash_array_extended(v, 0)::bit(32) as extended0, - hash_array_extended(v, 1)::bit(32) as extended1 + hash_array_extended(v, 0)::bit(32) as extended0, + hash_array_extended(v, 1)::bit(32) as extended1 FROM (VALUES ('{0}'::int4[]), ('{0,1,2,3,4}'), ('{17,18,19,20}'), ('{42,34,65,98}'), ('{550273,590027, 870273}'), ('{207112489, 807112489}')) x(v) @@ -131,92 +131,92 @@ WHERE hash_array(v)::bit(32) != hash_array_extended(v, 0)::bit(32) OR hash_array(v)::bit(32) = hash_array_extended(v, 1)::bit(32); SELECT v as value, hashbpchar(v)::bit(32) as standard, - hashbpcharextended(v, 0)::bit(32) as extended0, - hashbpcharextended(v, 1)::bit(32) as extended1 + hashbpcharextended(v, 0)::bit(32) as extended0, + hashbpcharextended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL), ('PostgreSQL'), ('eIpUEtqmY89'), ('AXKEJBTK'), - ('muop28x03'), ('yi3nm0d73')) x(v) + ('muop28x03'), ('yi3nm0d73')) x(v) WHERE hashbpchar(v)::bit(32) != hashbpcharextended(v, 0)::bit(32) OR hashbpchar(v)::bit(32) = hashbpcharextended(v, 1)::bit(32); SELECT v as value, time_hash(v)::bit(32) as standard, - time_hash_extended(v, 0)::bit(32) as extended0, - time_hash_extended(v, 1)::bit(32) as extended1 + time_hash_extended(v, 0)::bit(32) as extended0, + time_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::time), ('11:09:59'), ('1:09:59'), ('11:59:59'), ('7:9:59'), ('5:15:59')) x(v) WHERE time_hash(v)::bit(32) != time_hash_extended(v, 0)::bit(32) OR time_hash(v)::bit(32) = time_hash_extended(v, 1)::bit(32); SELECT v as value, timetz_hash(v)::bit(32) as standard, - timetz_hash_extended(v, 0)::bit(32) as extended0, - timetz_hash_extended(v, 1)::bit(32) as extended1 + timetz_hash_extended(v, 0)::bit(32) as extended0, + timetz_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::timetz), ('00:11:52.518762-07'), ('00:11:52.51762-08'), - ('00:11:52.62-01'), ('00:11:52.62+01'), ('11:59:59+04')) x(v) + ('00:11:52.62-01'), ('00:11:52.62+01'), ('11:59:59+04')) x(v) WHERE timetz_hash(v)::bit(32) != timetz_hash_extended(v, 0)::bit(32) OR timetz_hash(v)::bit(32) = timetz_hash_extended(v, 1)::bit(32); SELECT v as value, interval_hash(v)::bit(32) as standard, - interval_hash_extended(v, 0)::bit(32) as extended0, - interval_hash_extended(v, 1)::bit(32) as extended1 + interval_hash_extended(v, 0)::bit(32) as extended0, + interval_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::interval), ('5 month 7 day 46 minutes'), ('1 year 7 day 46 minutes'), - ('1 year 7 month 20 day 46 minutes'), ('5 month'), - ('17 year 11 month 7 day 9 hours 46 minutes 5 seconds')) x(v) + ('1 year 7 month 20 day 46 minutes'), ('5 month'), + ('17 year 11 month 7 day 9 hours 46 minutes 5 seconds')) x(v) WHERE interval_hash(v)::bit(32) != interval_hash_extended(v, 0)::bit(32) OR interval_hash(v)::bit(32) = interval_hash_extended(v, 1)::bit(32); SELECT v as value, timestamp_hash(v)::bit(32) as standard, - timestamp_hash_extended(v, 0)::bit(32) as extended0, - timestamp_hash_extended(v, 1)::bit(32) as extended1 + timestamp_hash_extended(v, 0)::bit(32) as extended0, + timestamp_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::timestamp), ('2017-08-22 00:09:59.518762'), ('2015-08-20 00:11:52.51762-08'), - ('2017-05-22 00:11:52.62-01'), + ('2017-05-22 00:11:52.62-01'), ('2013-08-22 00:11:52.62+01'), ('2013-08-22 11:59:59+04')) x(v) WHERE timestamp_hash(v)::bit(32) != timestamp_hash_extended(v, 0)::bit(32) OR timestamp_hash(v)::bit(32) = timestamp_hash_extended(v, 1)::bit(32); SELECT v as value, uuid_hash(v)::bit(32) as standard, - uuid_hash_extended(v, 0)::bit(32) as extended0, - uuid_hash_extended(v, 1)::bit(32) as extended1 + uuid_hash_extended(v, 0)::bit(32) as extended0, + uuid_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::uuid), ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'), - ('5a9ba4ac-8d6f-11e7-bb31-be2e44b06b34'), + ('5a9ba4ac-8d6f-11e7-bb31-be2e44b06b34'), ('99c6705c-d939-461c-a3c9-1690ad64ed7b'), - ('7deed3ca-8d6f-11e7-bb31-be2e44b06b34'), + ('7deed3ca-8d6f-11e7-bb31-be2e44b06b34'), ('9ad46d4f-6f2a-4edd-aadb-745993928e1e')) x(v) WHERE uuid_hash(v)::bit(32) != uuid_hash_extended(v, 0)::bit(32) OR uuid_hash(v)::bit(32) = uuid_hash_extended(v, 1)::bit(32); SELECT v as value, pg_lsn_hash(v)::bit(32) as standard, - pg_lsn_hash_extended(v, 0)::bit(32) as extended0, - pg_lsn_hash_extended(v, 1)::bit(32) as extended1 + pg_lsn_hash_extended(v, 0)::bit(32) as extended0, + pg_lsn_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::pg_lsn), ('16/B374D84'), ('30/B374D84'), - ('255/B374D84'), ('25/B379D90'), ('900/F37FD90')) x(v) + ('255/B374D84'), ('25/B379D90'), ('900/F37FD90')) x(v) WHERE pg_lsn_hash(v)::bit(32) != pg_lsn_hash_extended(v, 0)::bit(32) OR pg_lsn_hash(v)::bit(32) = pg_lsn_hash_extended(v, 1)::bit(32); CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); SELECT v as value, hashenum(v)::bit(32) as standard, - hashenumextended(v, 0)::bit(32) as extended0, - hashenumextended(v, 1)::bit(32) as extended1 + hashenumextended(v, 0)::bit(32) as extended0, + hashenumextended(v, 1)::bit(32) as extended1 FROM (VALUES ('sad'::mood), ('ok'), ('happy')) x(v) WHERE hashenum(v)::bit(32) != hashenumextended(v, 0)::bit(32) OR hashenum(v)::bit(32) = hashenumextended(v, 1)::bit(32); DROP TYPE mood; SELECT v as value, jsonb_hash(v)::bit(32) as standard, - jsonb_hash_extended(v, 0)::bit(32) as extended0, - jsonb_hash_extended(v, 1)::bit(32) as extended1 + jsonb_hash_extended(v, 0)::bit(32) as extended0, + jsonb_hash_extended(v, 1)::bit(32) as extended1 FROM (VALUES (NULL::jsonb), - ('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'), - ('{"foo": [true, "bar"], "tags": {"e": 1, "f": null}}'), - ('{"g": {"h": "value"}}')) x(v) + ('{"a": "aaa bbb ddd ccc", "b": ["eee fff ggg"], "c": {"d": "hhh iii"}}'), + ('{"foo": [true, "bar"], "tags": {"e": 1, "f": null}}'), + ('{"g": {"h": "value"}}')) x(v) WHERE jsonb_hash(v)::bit(32) != jsonb_hash_extended(v, 0)::bit(32) OR jsonb_hash(v)::bit(32) = jsonb_hash_extended(v, 1)::bit(32); SELECT v as value, hash_range(v)::bit(32) as standard, - hash_range_extended(v, 0)::bit(32) as extended0, - hash_range_extended(v, 1)::bit(32) as extended1 + hash_range_extended(v, 0)::bit(32) as extended0, + hash_range_extended(v, 1)::bit(32) as extended1 FROM (VALUES (int4range(10, 20)), (int4range(23, 43)), - (int4range(5675, 550273)), - (int4range(550274, 1550274)), (int4range(1550275, 208112489))) x(v) + (int4range(5675, 550273)), + (int4range(550274, 1550274)), (int4range(1550275, 208112489))) x(v) WHERE hash_range(v)::bit(32) != hash_range_extended(v, 0)::bit(32) OR hash_range(v)::bit(32) = hash_range_extended(v, 1)::bit(32); diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql index 0b010a7fd239..cead4f7d074d 100644 --- a/src/test/regress/sql/horology.sql +++ b/src/test/regress/sql/horology.sql @@ -185,6 +185,8 @@ reset datestyle; -- SET DateStyle = 'Postgres, MDY'; +SHOW TimeZone; -- Many of these tests depend on the prevailing setting + -- -- Test various input formats -- @@ -462,6 +464,31 @@ SELECT '' AS "16", f1 AS "timestamp", date(f1) AS date DROP TABLE TEMP_TIMESTAMP; +-- +-- Comparisons between datetime types, especially overflow cases +--- + +SELECT '2202020-10-05'::date::timestamp; -- fail +SELECT '2202020-10-05'::date > '2020-10-05'::timestamp as t; +SELECT '2020-10-05'::timestamp > '2202020-10-05'::date as f; + +SELECT '2202020-10-05'::date::timestamptz; -- fail +SELECT '2202020-10-05'::date > '2020-10-05'::timestamptz as t; +SELECT '2020-10-05'::timestamptz > '2202020-10-05'::date as f; + +-- This conversion may work depending on timezone +SELECT '4714-11-24 BC'::date::timestamptz; +SET TimeZone = 'UTC-2'; +SELECT '4714-11-24 BC'::date::timestamptz; -- fail + +SELECT '4714-11-24 BC'::date < '2020-10-05'::timestamptz as t; +SELECT '2020-10-05'::timestamptz >= '4714-11-24 BC'::date as t; + +SELECT '4714-11-24 BC'::timestamp < '2020-10-05'::timestamptz as t; +SELECT '2020-10-05'::timestamptz >= '4714-11-24 BC'::timestamp as t; + +RESET TimeZone; + -- -- Formats -- @@ -609,6 +636,17 @@ SELECT to_date('1 4 1902', 'Q MM YYYY'); -- Q is ignored SELECT to_date('3 4 21 01', 'W MM CC YY'); SELECT to_date('2458872', 'J'); +-- +-- Check handling of BC dates +-- + +SELECT to_date('44-02-01 BC','YYYY-MM-DD BC'); +SELECT to_date('-44-02-01','YYYY-MM-DD'); +SELECT to_date('-44-02-01 BC','YYYY-MM-DD BC'); +SELECT to_timestamp('44-02-01 11:12:13 BC','YYYY-MM-DD HH24:MI:SS BC'); +SELECT to_timestamp('-44-02-01 11:12:13','YYYY-MM-DD HH24:MI:SS'); +SELECT to_timestamp('-44-02-01 11:12:13 BC','YYYY-MM-DD HH24:MI:SS BC'); + -- -- Check handling of multiple spaces in format and/or input -- @@ -694,6 +732,7 @@ SELECT to_date('2015 366', 'YYYY DDD'); SELECT to_date('2016 365', 'YYYY DDD'); -- ok SELECT to_date('2016 366', 'YYYY DDD'); -- ok SELECT to_date('2016 367', 'YYYY DDD'); +SELECT to_date('0000-02-01','YYYY-MM-DD'); -- allowed, though it shouldn't be -- -- Check behavior with SQL-style fixed-GMT-offset time zone (cf bug #8572) diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql index d07b5aaa4b92..a84b685c9d5a 100644 --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -208,7 +208,7 @@ INSERT INTO itest6 DEFAULT VALUES; INSERT INTO itest6 DEFAULT VALUES; SELECT * FROM itest6; -SELECT table_name, column_name, is_identity, identity_generation FROM information_schema.columns WHERE table_name = 'itest6'; +SELECT table_name, column_name, is_identity, identity_generation FROM information_schema.columns WHERE table_name = 'itest6' ORDER BY 1, 2; ALTER TABLE itest6 ALTER COLUMN b SET INCREMENT BY 2; -- fail, not identity diff --git a/src/test/regress/sql/incremental_sort.sql b/src/test/regress/sql/incremental_sort.sql index 9c040c90e62d..3ee11c394b91 100644 --- a/src/test/regress/sql/incremental_sort.sql +++ b/src/test/regress/sql/incremental_sort.sql @@ -221,3 +221,34 @@ set enable_hashagg to off; explain (costs off) select * from t union select * from t order by 1,3; drop table t; + +-- Sort pushdown can't go below where expressions are part of the rel target. +-- In particular this is interesting for volatile expressions which have to +-- go above joins since otherwise we'll incorrectly use expression evaluations +-- across multiple rows. +set enable_hashagg=off; +set enable_seqscan=off; +set enable_incremental_sort = off; +set parallel_tuple_cost=0; +set parallel_setup_cost=0; +set min_parallel_table_scan_size = 0; +set min_parallel_index_scan_size = 0; + +-- Parallel sort below join. +explain (costs off) select distinct sub.unique1, stringu1 +from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub; +explain (costs off) select sub.unique1, stringu1 +from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub +order by 1, 2; +-- Parallel sort but with expression that can be safely generated at the base rel. +explain (costs off) select distinct sub.unique1, md5(stringu1) +from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub; +explain (costs off) select sub.unique1, md5(stringu1) +from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub +order by 1, 2; +-- Parallel sort but with expression not available until the upper rel. +explain (costs off) select distinct sub.unique1, stringu1 || random()::text +from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub; +explain (costs off) select sub.unique1, stringu1 || random()::text +from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub +order by 1, 2; diff --git a/src/test/regress/sql/index_constraint_naming_partition.sql b/src/test/regress/sql/index_constraint_naming_partition.sql index 32698aaa9a42..7493fe22f428 100644 --- a/src/test/regress/sql/index_constraint_naming_partition.sql +++ b/src/test/regress/sql/index_constraint_naming_partition.sql @@ -45,7 +45,8 @@ DROP FUNCTION IF EXISTS dependencies(); CREATE FUNCTION dependencies() RETURNS TABLE( depname NAME, classtype "char", depnsoid OID, refname NAME, refclasstype "char", refnsoid OID, classid REGCLASS, objid OID, objsubid INTEGER, - refclassid REGCLASS, refobjid OID, refobjsubid OID, deptype "char" ) + refclassid REGCLASS, refobjid OID, refobjsubid OID, + deptype "char", refobjversion "text" ) LANGUAGE SQL STABLE STRICT AS $fn$ WITH RECURSIVE w AS ( @@ -55,7 +56,8 @@ WITH RECURSIVE refclassid::regclass, refobjid, refobjsubid, - deptype + deptype, + refobjversion FROM pg_depend d WHERE classid IN ('pg_constraint'::regclass, 'pg_class'::regclass) AND (objid > 16384 OR refobjid > 16384) diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql index 96b8cbbbafb2..3d083f37089a 100644 --- a/src/test/regress/sql/indexing.sql +++ b/src/test/regress/sql/indexing.sql @@ -98,6 +98,7 @@ create table idxpart (a int) partition by range (a); create index on idxpart (a); create table idxpart1 partition of idxpart for values from (0) to (10); drop index idxpart1_a_idx; -- no way +drop index concurrently idxpart_a_idx; -- unsupported drop index idxpart_a_idx; -- both indexes go away select relname, relkind from pg_class where relname like 'idxpart%' order by relname; @@ -107,6 +108,18 @@ select relname, relkind from pg_class where relname like 'idxpart%' order by relname; drop table idxpart; +-- DROP behavior with temporary partitioned indexes +create temp table idxpart_temp (a int) partition by range (a); +create index on idxpart_temp(a); +create temp table idxpart1_temp partition of idxpart_temp + for values from (0) to (10); +drop index idxpart1_temp_a_idx; -- error +-- non-concurrent drop is enforced here, so it is a valid case. +drop index concurrently idxpart_temp_a_idx; +select relname, relkind from pg_class + where relname like 'idxpart_temp%' order by relname; +drop table idxpart_temp; + -- ALTER INDEX .. ATTACH, error cases create table idxpart (a int, b int) partition by range (a, b); create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10); diff --git a/src/test/regress/sql/infinite_recurse.sql b/src/test/regress/sql/infinite_recurse.sql new file mode 100644 index 000000000000..151dba4a7aef --- /dev/null +++ b/src/test/regress/sql/infinite_recurse.sql @@ -0,0 +1,29 @@ +-- Check that stack depth detection mechanism works and +-- max_stack_depth is not set too high. + +create function infinite_recurse() returns int as +'select infinite_recurse()' language sql; + +-- Unfortunately, up till mid 2020 the Linux kernel had a bug in PPC64 +-- signal handling that would cause this test to crash if it happened +-- to receive an sinval catchup interrupt while the stack is deep: +-- https://bugzilla.kernel.org/show_bug.cgi?id=205183 +-- It is likely to be many years before that bug disappears from all +-- production kernels, so disable this test on such platforms. +-- (We still create the function, so as not to have a cross-platform +-- difference in the end state of the regression database.) + +SELECT version() ~ 'powerpc64[^,]*-linux-gnu' + AS skip_test \gset +\if :skip_test +\quit +\endif + +-- The full error report is not very stable, so we show only SQLSTATE +-- and primary error message. + +\set VERBOSITY sqlstate + +select infinite_recurse(); + +\echo :LAST_ERROR_MESSAGE diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql index cf99cf37f60f..dff23328833a 100644 --- a/src/test/regress/sql/insert.sql +++ b/src/test/regress/sql/insert.sql @@ -556,9 +556,7 @@ drop table inserttest3; drop table brtrigpartcon; drop function brtrigpartcon1trigf(); --- check that "do nothing" BR triggers work with tuple-routing (this checks --- that estate->es_result_relation_info is appropriately set/reset for each --- routed tuple) +-- check that "do nothing" BR triggers work with tuple-routing create table donothingbrtrig_test (a int, b text) partition by list (a); create table donothingbrtrig_test1 (b text, a int); alter table donothingbrtrig_test1 set distributed by (a); diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 0e47120784c4..a6ed867e37e1 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -2036,6 +2036,38 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral drop table join_pt1; drop table join_ut1; + +-- +-- test estimation behavior with multi-column foreign key and constant qual +-- + +begin; + +-- GPDB: persuade the planner to choose same plan as in upstream. +set local enable_nestloop=on; + +create table fkest (x integer, x10 integer, x10b integer, x100 integer); +insert into fkest select x, x/10, x/10, x/100 from generate_series(1,1000) x; +create unique index on fkest(x, x10, x100); +analyze fkest; + +explain (costs off) +select * from fkest f1 + join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100) + join fkest f3 on f1.x = f3.x + where f1.x100 = 2; + +alter table fkest add constraint fk + foreign key (x, x10b, x100) references fkest (x, x10, x100); + +explain (costs off) +select * from fkest f1 + join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100) + join fkest f3 on f1.x = f3.x + where f1.x100 = 2; + +rollback; + -- -- test that foreign key join estimation performs sanely for outer joins -- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index a50abed95da7..60f73cb05906 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -368,6 +368,10 @@ select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH2 select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()'); select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()'); +select jsonb_path_query('"10-03-2017T12:34:56"', '$.datetime("dd-mm-yyyy\"T\"HH24:MI:SS")'); +select jsonb_path_query('"10-03-2017t12:34:56"', '$.datetime("dd-mm-yyyy\"T\"HH24:MI:SS")'); +select jsonb_path_query('"10-03-2017 12:34:56"', '$.datetime("dd-mm-yyyy\"T\"HH24:MI:SS")'); + set time zone '+00'; select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); @@ -404,117 +408,119 @@ select jsonb_path_query('"2017-03-10"', '$.datetime().type()'); select jsonb_path_query('"2017-03-10"', '$.datetime()'); select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()'); select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()'); -select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()'); -select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()'); -select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()'); -select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()'); +select jsonb_path_query('"2017-03-10 12:34:56+3"', '$.datetime().type()'); +select jsonb_path_query('"2017-03-10 12:34:56+3"', '$.datetime()'); +select jsonb_path_query('"2017-03-10 12:34:56+3:10"', '$.datetime().type()'); +select jsonb_path_query('"2017-03-10 12:34:56+3:10"', '$.datetime()'); +select jsonb_path_query('"2017-03-10T12:34:56+3:10"', '$.datetime()'); +select jsonb_path_query('"2017-03-10t12:34:56+3:10"', '$.datetime()'); select jsonb_path_query('"12:34:56"', '$.datetime().type()'); select jsonb_path_query('"12:34:56"', '$.datetime()'); -select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()'); -select jsonb_path_query('"12:34:56 +3"', '$.datetime()'); -select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()'); -select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()'); +select jsonb_path_query('"12:34:56+3"', '$.datetime().type()'); +select jsonb_path_query('"12:34:56+3"', '$.datetime()'); +select jsonb_path_query('"12:34:56+3:10"', '$.datetime().type()'); +select jsonb_path_query('"12:34:56+3:10"', '$.datetime()'); set time zone '+00'; -- date comparison select jsonb_path_query( - '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]', '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))'); select jsonb_path_query( - '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]', '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))'); select jsonb_path_query( - '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]', '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))'); select jsonb_path_query_tz( - '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]', '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))'); select jsonb_path_query_tz( - '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]', '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))'); select jsonb_path_query_tz( - '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03+04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03+04", "2017-03-10 03:00:00+03"]', '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))'); -- time comparison select jsonb_path_query( - '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]', '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))'); select jsonb_path_query( - '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]', '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))'); select jsonb_path_query( - '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]', '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))'); select jsonb_path_query_tz( - '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]', '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))'); select jsonb_path_query_tz( - '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]', '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))'); select jsonb_path_query_tz( - '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '["12:34:00", "12:35:00", "12:36:00", "12:35:00+00", "12:35:00+01", "13:35:00+01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00+01"]', '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))'); -- timetz comparison select jsonb_path_query( - '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))'); select jsonb_path_query( - '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))'); select jsonb_path_query( - '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))'); select jsonb_path_query_tz( - '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))'); select jsonb_path_query_tz( - '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))'); select jsonb_path_query_tz( - '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '["12:34:00+01", "12:35:00+01", "12:36:00+01", "12:35:00+02", "12:35:00-02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))'); -- timestamp comparison select jsonb_path_query( - '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); select jsonb_path_query( - '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); select jsonb_path_query( - '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); select jsonb_path_query_tz( - '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); select jsonb_path_query_tz( - '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); select jsonb_path_query_tz( - '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00+01", "2017-03-10 13:35:00+01", "2017-03-10 12:35:00-01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); -- timestamptz comparison select jsonb_path_query( - '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); select jsonb_path_query( - '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); select jsonb_path_query( - '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); select jsonb_path_query_tz( - '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); select jsonb_path_query_tz( - '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); select jsonb_path_query_tz( - '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '["2017-03-10 12:34:00+01", "2017-03-10 12:35:00+01", "2017-03-10 12:36:00+01", "2017-03-10 12:35:00+02", "2017-03-10 12:35:00-02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56+01"]', '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); -- overflow during comparison diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql index 19987d0297b2..46f891d0f6c3 100644 --- a/src/test/regress/sql/lock.sql +++ b/src/test/regress/sql/lock.sql @@ -13,6 +13,9 @@ CREATE VIEW lock_view3 AS SELECT * from lock_view2; CREATE VIEW lock_view4 AS SELECT (select a from lock_tbl1a limit 1) from lock_tbl1; CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from lock_tbl1a); CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub; +CREATE MATERIALIZED VIEW lock_mv1 AS SELECT * FROM lock_view6; +CREATE INDEX lock_mvi1 ON lock_mv1 (a); +CREATE SEQUENCE lock_seq; CREATE ROLE regress_rol_lock1; ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1; GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1; @@ -117,9 +120,18 @@ LOCK TABLE ONLY lock_tbl1; ROLLBACK; RESET ROLE; +-- Lock other relations +BEGIN TRANSACTION; +LOCK TABLE lock_mv1; +LOCK TABLE lock_mvi1; +LOCK TABLE lock_seq; +ROLLBACK; + + -- -- Clean up -- +DROP MATERIALIZED VIEW lock_mv1; DROP VIEW lock_view7; DROP VIEW lock_view6; DROP VIEW lock_view5; @@ -130,6 +142,7 @@ DROP TABLE lock_tbl3; DROP TABLE lock_tbl2; DROP TABLE lock_tbl1; DROP TABLE lock_tbl1a; +DROP SEQUENCE lock_seq; DROP SCHEMA lock_schema1 CASCADE; DROP ROLE regress_rol_lock1; diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql index 108f78c52860..d07a246ee308 100644 --- a/src/test/regress/sql/numeric.sql +++ b/src/test/regress/sql/numeric.sql @@ -752,6 +752,8 @@ SELECT power('-inf'::numeric, '-inf'); -- ****************************** -- numeric AVG used to fail on some platforms SELECT AVG(val) FROM num_data; +SELECT MAX(val) FROM num_data; +SELECT MIN(val) FROM num_data; SELECT STDDEV(val) FROM num_data; SELECT VARIANCE(val) FROM num_data; @@ -829,7 +831,6 @@ SELECT width_bucket(5.0::float8, 3.0::float8, 4.0::float8, -5); SELECT width_bucket(3.5::float8, 3.0::float8, 3.0::float8, 888); SELECT width_bucket('NaN', 3.0, 4.0, 888); SELECT width_bucket(0::float8, 'NaN', 4.0::float8, 888); -SELECT width_bucket('inf', 3.0, 4.0, 888); SELECT width_bucket(2.0, 3.0, '-inf', 888); SELECT width_bucket(0::float8, '-inf', 4.0::float8, 888); @@ -874,8 +875,12 @@ SELECT width_bucket(operand_f8, -25, 25, 10) AS wb_5f FROM width_bucket_test; --- for float8 only, check positive and negative infinity: we require +-- Check positive and negative infinity: we require -- finite bucket bounds, but allow an infinite operand +SELECT width_bucket(0.0::numeric, 'Infinity'::numeric, 5, 10); -- error +SELECT width_bucket(0.0::numeric, 5, '-Infinity'::numeric, 20); -- error +SELECT width_bucket('Infinity'::numeric, 1, 10, 10), + width_bucket('-Infinity'::numeric, 1, 10, 10); SELECT width_bucket(0.0::float8, 'Infinity'::float8, 5, 10); -- error SELECT width_bucket(0.0::float8, 5, '-Infinity'::float8, 20); -- error SELECT width_bucket('Infinity'::float8, 1, 10, 10), @@ -883,6 +888,15 @@ SELECT width_bucket('Infinity'::float8, 1, 10, 10), DROP TABLE width_bucket_test; +-- Simple test for roundoff error when results should be exact +SELECT x, width_bucket(x::float8, 10, 100, 9) as flt, + width_bucket(x::numeric, 10, 100, 9) as num +FROM generate_series(0, 110, 10) x; +SELECT x, width_bucket(x::float8, 100, 10, 9) as flt, + width_bucket(x::numeric, 100, 10, 9) as num +FROM generate_series(0, 110, 10) x; + +-- -- TO_CHAR() -- SELECT '' AS to_char_1, to_char(val, '9G999G999G999G999G999') @@ -1298,12 +1312,10 @@ SELECT lcm(9999 * (10::numeric)^131068 + (10::numeric^131068 - 1), 2); -- overfl -- -- Tests for factorial -- -SELECT 4!; -SELECT !!3; +SELECT factorial(4); SELECT factorial(15); -SELECT 100000!; -SELECT 0!; -SELECT -4!; +SELECT factorial(100000); +SELECT factorial(0); SELECT factorial(-4); -- diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index a06332bfc2a7..6d51247f2851 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -580,7 +580,7 @@ WHERE condefault AND SELECT p1.oid, p1.oprname FROM pg_operator as p1 -WHERE (p1.oprkind != 'b' AND p1.oprkind != 'l' AND p1.oprkind != 'r') OR +WHERE (p1.oprkind != 'b' AND p1.oprkind != 'l') OR p1.oprresult = 0 OR p1.oprcode = 0; -- Look for missing or unwanted operand types @@ -589,8 +589,7 @@ SELECT p1.oid, p1.oprname FROM pg_operator as p1 WHERE (p1.oprleft = 0 and p1.oprkind != 'l') OR (p1.oprleft != 0 and p1.oprkind = 'l') OR - (p1.oprright = 0 and p1.oprkind != 'r') OR - (p1.oprright != 0 and p1.oprkind = 'r'); + p1.oprright = 0; -- Look for conflicting operator definitions (same names and input datatypes). @@ -724,15 +723,6 @@ WHERE p1.oprcode = p2.oid AND OR NOT binary_coercible(p1.oprright, p2.proargtypes[0]) OR p1.oprleft != 0); -SELECT p1.oid, p1.oprname, p2.oid, p2.proname -FROM pg_operator AS p1, pg_proc AS p2 -WHERE p1.oprcode = p2.oid AND - p1.oprkind = 'r' AND - (p2.pronargs != 1 - OR NOT binary_coercible(p2.prorettype, p1.oprresult) - OR NOT binary_coercible(p1.oprleft, p2.proargtypes[0]) - OR p1.oprright != 0); - -- If the operator is mergejoinable or hashjoinable, its underlying function -- should not be volatile. diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql index 8c2d8729698c..e4a05615ab42 100644 --- a/src/test/regress/sql/partition_join.sql +++ b/src/test/regress/sql/partition_join.sql @@ -1106,7 +1106,7 @@ ANALYZE plt3_adv; -- This tests that when merging partitions from plt1_adv and plt2_adv in -- merge_list_bounds(), process_outer_partition() returns an already-assigned -- merged partition when re-called with plt1_adv_p1 for the second list value --- '0001' of that partitin +-- '0001' of that partition EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.c = t2.c)) FULL JOIN plt3_adv t3 ON (t1.c = t3.c) WHERE coalesce(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.c = t2.c)) FULL JOIN plt3_adv t3 ON (t1.c = t3.c) WHERE coalesce(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index 080b27821ded..380f5d212366 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -1101,6 +1101,54 @@ reset enable_partition_pruning; drop table listp; +-- Ensure run-time pruning works correctly for nested Append nodes +set parallel_setup_cost to 0; +set parallel_tuple_cost to 0; + +create table listp (a int) partition by list(a); +create table listp_12 partition of listp for values in(1,2) partition by list(a); +create table listp_12_1 partition of listp_12 for values in(1); +create table listp_12_2 partition of listp_12 for values in(2); + +-- Force the 2nd subnode of the Append to be non-parallel. This results in +-- a nested Append node because the mixed parallel / non-parallel paths cannot +-- be pulled into the top-level Append. +alter table listp_12_1 set (parallel_workers = 0); + +-- Ensure that listp_12_2 is not scanned. (The nested Append is not seen in +-- the plan as it's pulled in setref.c due to having just a single subnode). +select explain_parallel_append('select * from listp where a = (select 1);'); + +-- Like the above but throw some more complexity at the planner by adding +-- a UNION ALL. We expect both sides of the union not to scan the +-- non-required partitions. +select explain_parallel_append( +'select * from listp where a = (select 1) + union all +select * from listp where a = (select 2);'); + +drop table listp; +reset parallel_tuple_cost; +reset parallel_setup_cost; + +-- Test case for run-time pruning with a nested Merge Append +set enable_sort to 0; +create table rangep (a int, b int) partition by range (a); +create table rangep_0_to_100 partition of rangep for values from (0) to (100) partition by list (b); +-- We need 3 sub-partitions. 1 to validate pruning worked and another two +-- because a single remaining partition would be pulled up to the main Append. +create table rangep_0_to_100_1 partition of rangep_0_to_100 for values in(1); +create table rangep_0_to_100_2 partition of rangep_0_to_100 for values in(2); +create table rangep_0_to_100_3 partition of rangep_0_to_100 for values in(3); +create table rangep_100_to_200 partition of rangep for values from (100) to (200); +create index on rangep (a); + +-- Ensure run-time pruning works on the nested Merge Append +explain (analyze on, costs off, timing off, summary off) +select * from rangep where b IN((select 1),(select 2)) order by a; +reset enable_sort; +drop table rangep; + -- -- Check that gen_prune_steps_from_opexps() works well for various cases of -- clauses for different partition keys diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index d33c484011a7..eea878252271 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -455,20 +455,38 @@ select 1 where false; \df exp \pset tuples_only false --- check conditional tableam display +-- check conditional am display +\pset expanded off --- Create a heap2 table am handler with heapam handler +CREATE SCHEMA tableam_display; +CREATE ROLE regress_display_role; +ALTER SCHEMA tableam_display OWNER TO regress_display_role; +SET search_path TO tableam_display; CREATE ACCESS METHOD heap_psql TYPE TABLE HANDLER heap_tableam_handler; +SET ROLE TO regress_display_role; +-- Use only relations with a physical size of zero. CREATE TABLE tbl_heap_psql(f1 int, f2 char(100)) using heap_psql; CREATE TABLE tbl_heap(f1 int, f2 char(100)) using heap; +CREATE VIEW view_heap_psql AS SELECT f1 from tbl_heap_psql; +CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tbl_heap_psql; \d+ tbl_heap_psql \d+ tbl_heap \set HIDE_TABLEAM off \d+ tbl_heap_psql \d+ tbl_heap +-- AM is displayed for tables, indexes and materialized views. +\d+ +\dt+ +\dm+ +-- But not for views and sequences. +\dv+ \set HIDE_TABLEAM on -DROP TABLE tbl_heap, tbl_heap_psql; +\d+ +RESET ROLE; +RESET search_path; +DROP SCHEMA tableam_display CASCADE; DROP ACCESS METHOD heap_psql; +DROP ROLE regress_display_role; -- test numericlocale (as best we can without control of psql's locale) diff --git a/src/test/regress/sql/qp_misc_rio.sql b/src/test/regress/sql/qp_misc_rio.sql index 86acf6b5afc9..1d328ebc9325 100644 --- a/src/test/regress/sql/qp_misc_rio.sql +++ b/src/test/regress/sql/qp_misc_rio.sql @@ -535,7 +535,7 @@ order by a; -- does. The fix was submitted to upstream PostgreSQL and fixed there in -- version 8.4.16 (commit 5c4eb9166e.) -- ---------------------------------------------------------------------- -select to_date('-4713-11-23', 'yyyy-mm-dd'); -select to_date('-4713-11-24', 'yyyy-mm-dd'); +select to_date('-4714-11-23', 'yyyy-mm-dd'); +select to_date('-4714-11-24', 'yyyy-mm-dd'); select to_date('5874897-12-31', 'yyyy-mm-dd'); select to_date('5874898-01-01', 'yyyy-mm-dd'); diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql index 3016c089b993..fa20e3ef9371 100644 --- a/src/test/regress/sql/rangefuncs.sql +++ b/src/test/regress/sql/rangefuncs.sql @@ -649,6 +649,22 @@ explain (verbose, costs off) select * from testrngfunc(); select * from testrngfunc(); +create or replace function testrngfunc() returns setof rngfunc_type as $$ + select 1, 2 union select 3, 4 order by 1; +$$ language sql immutable; + +explain (verbose, costs off) +select testrngfunc(); +select testrngfunc(); +explain (verbose, costs off) +select * from testrngfunc(); +select * from testrngfunc(); + +-- Check a couple of error cases while we're here +select * from testrngfunc() as t(f1 int8,f2 int8); -- fail, composite result +select * from pg_get_keywords() as t(f1 int8,f2 int8); -- fail, OUT params +select * from sin(3) as t(f1 int8,f2 int8); -- fail, scalar result type + drop type rngfunc_type cascade; -- diff --git a/src/test/regress/sql/reindex_catalog.sql b/src/test/regress/sql/reindex_catalog.sql index 87ecf52244f1..8203641cf9d2 100644 --- a/src/test/regress/sql/reindex_catalog.sql +++ b/src/test/regress/sql/reindex_catalog.sql @@ -39,3 +39,14 @@ REINDEX INDEX pg_index_indexrelid_index; -- non-mapped, non-shared, critical REINDEX INDEX pg_index_indrelid_index; -- non-mapped, non-shared, non-critical REINDEX INDEX pg_database_oid_index; -- mapped, shared, critical REINDEX INDEX pg_shdescription_o_c_index; -- mapped, shared, non-critical + +-- Check the same REINDEX INDEX statements under parallelism. +BEGIN; +SET min_parallel_table_scan_size = 0; +REINDEX INDEX pg_class_oid_index; -- mapped, non-shared, critical +REINDEX INDEX pg_class_relname_nsp_index; -- mapped, non-shared, non-critical +REINDEX INDEX pg_index_indexrelid_index; -- non-mapped, non-shared, critical +REINDEX INDEX pg_index_indrelid_index; -- non-mapped, non-shared, non-critical +REINDEX INDEX pg_database_oid_index; -- mapped, shared, critical +REINDEX INDEX pg_shdescription_o_c_index; -- mapped, shared, non-critical +ROLLBACK; diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql index d52c2992404d..252772fae645 100644 --- a/src/test/regress/sql/select_parallel.sql +++ b/src/test/regress/sql/select_parallel.sql @@ -406,9 +406,10 @@ EXPLAIN (analyze, timing off, summary off, costs off) SELECT * FROM tenk1; ROLLBACK TO SAVEPOINT settings; -- provoke error in worker +-- (make the error message long enough to require multiple bufferloads) SAVEPOINT settings; SET LOCAL force_parallel_mode = 1; -select stringu1::int2 from tenk1 where unique1 = 1; +select (stringu1 || repeat('abcd', 5000))::int2 from tenk1 where unique1 = 1; ROLLBACK TO SAVEPOINT settings; -- test interaction with set-returning functions diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql index 3c9d04c07282..9a002acf4c85 100644 --- a/src/test/regress/sql/stats_ext.sql +++ b/src/test/regress/sql/stats_ext.sql @@ -79,12 +79,14 @@ ANALYZE ab1; ALTER TABLE ab1 ALTER a SET STATISTICS -1; -- setting statistics target 0 skips the statistics, without printing any message, so check catalog ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0; +\d ab1 ANALYZE ab1; SELECT stxname, stxdndistinct, stxddependencies, stxdmcv FROM pg_statistic_ext s, pg_statistic_ext_data d WHERE s.stxname = 'ab1_a_b_stats' AND d.stxoid = s.oid; ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1; +\d+ ab1 -- partial analyze doesn't build stats either ANALYZE ab1 (a); ANALYZE ab1; diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql index 63adce30d326..ac02cd5fc8e1 100644 --- a/src/test/regress/sql/subscription.sql +++ b/src/test/regress/sql/subscription.sql @@ -134,6 +134,21 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE); DROP SUBSCRIPTION regress_testsub; +-- fail - streaming must be boolean +CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, streaming = foo); + +-- now it works +CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, streaming = true); + +\dRs+ + +ALTER SUBSCRIPTION regress_testsub SET (streaming = false); +ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE); + +\dRs+ + +DROP SUBSCRIPTION regress_testsub; + RESET SESSION AUTHORIZATION; DROP ROLE regress_subscription_user; DROP ROLE regress_subscription_user2; diff --git a/src/test/regress/sql/subselect.sql b/src/test/regress/sql/subselect.sql index 24a9c3cd7d34..a364e4c32b4b 100644 --- a/src/test/regress/sql/subselect.sql +++ b/src/test/regress/sql/subselect.sql @@ -515,6 +515,23 @@ select * from int8_tbl where q1 in (select c1 from inner_text); rollback; -- to get rid of the bogus operator +-- +-- Test resolution of hashed vs non-hashed implementation of EXISTS subplan +-- +explain (costs off) +select count(*) from tenk1 t +where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0); +select count(*) from tenk1 t +where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0); + +explain (costs off) +select count(*) from tenk1 t +where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0) + and thousand = 1; +select count(*) from tenk1 t +where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0) + and thousand = 1; + -- -- Test case for planner bug with nested EXISTS handling -- diff --git a/src/test/regress/sql/sysviews.sql b/src/test/regress/sql/sysviews.sql index 28e412b73530..b9b875bc6abc 100644 --- a/src/test/regress/sql/sysviews.sql +++ b/src/test/regress/sql/sysviews.sql @@ -12,6 +12,11 @@ select count(*) >= 0 as ok from pg_available_extension_versions; select count(*) >= 0 as ok from pg_available_extensions; +-- The entire output of pg_backend_memory_contexts is not stable, +-- we test only the existance and basic condition of TopMemoryContext. +select name, ident, parent, level, total_bytes >= free_bytes + from pg_backend_memory_contexts where level = 0; + -- At introduction, pg_config had 23 entries; it may grow select count(*) > 20 as ok from pg_config; @@ -32,6 +37,9 @@ select count(*) = 0 as ok from pg_prepared_statements; -- See also prepared_xacts.sql select count(*) >= 0 as ok from pg_prepared_xacts; +-- There must be only one record +select count(*) = 1 as ok from pg_stat_wal; + -- This is to record the prevailing planner enable_foo settings during -- a regression test run. select name, setting from pg_settings where name like 'enable%'; diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql index 4c941d893c52..f27062c0dbd8 100644 --- a/src/test/regress/sql/timestamp.sql +++ b/src/test/regress/sql/timestamp.sql @@ -284,10 +284,13 @@ SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff ('2018-11-02 12:34:56.78901234') ) d(d); --- timestamp numeric fields constructor -SELECT make_timestamp(2014,12,28,6,30,45.887); - SET DateStyle TO DEFAULT; -- Make sure timeofdate() and current_time() are doing roughly the same thing select timeofday()::date = current_timestamp::date; + +-- timestamp numeric fields constructor +SELECT make_timestamp(2014, 12, 28, 6, 30, 45.887); +SELECT make_timestamp(-44, 3, 15, 12, 30, 15); +-- should fail +select make_timestamp(0, 7, 15, 12, 30, 15); diff --git a/src/test/regress/sql/timetz.sql b/src/test/regress/sql/timetz.sql index f1506279f067..1d8ce1898b5f 100644 --- a/src/test/regress/sql/timetz.sql +++ b/src/test/regress/sql/timetz.sql @@ -36,14 +36,14 @@ SELECT f1 AS "None" FROM TIMETZ_TBL WHERE f1 < '00:00-07'; SELECT f1 AS "Ten" FROM TIMETZ_TBL WHERE f1 >= '00:00-07'; -- Check edge cases -SELECT '23:59:59.999999'::timetz; -SELECT '23:59:59.9999999'::timetz; -- rounds up -SELECT '23:59:60'::timetz; -- rounds up -SELECT '24:00:00'::timetz; -- allowed -SELECT '24:00:00.01'::timetz; -- not allowed -SELECT '23:59:60.01'::timetz; -- not allowed -SELECT '24:01:00'::timetz; -- not allowed -SELECT '25:00:00'::timetz; -- not allowed +SELECT '23:59:59.999999 PDT'::timetz; +SELECT '23:59:59.9999999 PDT'::timetz; -- rounds up +SELECT '23:59:60 PDT'::timetz; -- rounds up +SELECT '24:00:00 PDT'::timetz; -- allowed +SELECT '24:00:00.01 PDT'::timetz; -- not allowed +SELECT '23:59:60.01 PDT'::timetz; -- not allowed +SELECT '24:01:00 PDT'::timetz; -- not allowed +SELECT '25:00:00 PDT'::timetz; -- not allowed -- -- TIME simple math diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index 44b3ac272354..309772527287 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -154,6 +154,24 @@ select * from trigtest; drop table trigtest; +-- Check behavior with an implicit column default, too (bug #16644) +create table trigtest (a integer, id integer default 0) distributed by (id); + +create trigger trigger_return_old + before insert or delete or update on trigtest + for each row execute procedure trigger_return_old(); + +insert into trigtest values(1); +select a from trigtest; + +alter table trigtest add column b integer default 42 not null; + +select a, b from trigtest; +update trigtest set a = 2 where a = 1 returning a, b; +select a, b from trigtest; + +drop table trigtest; + create sequence ttdummy_seq increment 10 start 0 minvalue 0 cache 1; create table tttest ( @@ -430,7 +448,7 @@ create table trigtest2 (i int references trigtest(i) on delete cascade); create function trigtest() returns trigger as $$ begin - raise notice '% % % %', TG_RELNAME, TG_OP, TG_WHEN, TG_LEVEL; + raise notice '% % % %', TG_TABLE_NAME, TG_OP, TG_WHEN, TG_LEVEL; return new; end;$$ language plpgsql; @@ -666,7 +684,7 @@ begin argstr := argstr || TG_argv[i]; end loop; - raise notice '% % % % (%)', TG_RELNAME, TG_WHEN, TG_OP, TG_LEVEL, argstr; + raise notice '% % % % (%)', TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL, argstr; if TG_LEVEL = 'ROW' then if TG_OP = 'INSERT' then diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql index 0352c6742372..dc3fc5eb3884 100644 --- a/src/test/regress/sql/updatable_views.sql +++ b/src/test/regress/sql/updatable_views.sql @@ -708,6 +708,27 @@ SELECT events & 4 != 0 AS upd, DROP TABLE base_tbl CASCADE; +-- view on table with GENERATED columns + +CREATE TABLE base_tbl (id int, idplus1 int GENERATED ALWAYS AS (id + 1) STORED); +CREATE VIEW rw_view1 AS SELECT * FROM base_tbl; + +INSERT INTO base_tbl (id) VALUES (1); +INSERT INTO rw_view1 (id) VALUES (2); +INSERT INTO base_tbl (id, idplus1) VALUES (3, DEFAULT); +INSERT INTO rw_view1 (id, idplus1) VALUES (4, DEFAULT); +INSERT INTO base_tbl (id, idplus1) VALUES (5, 6); -- error +INSERT INTO rw_view1 (id, idplus1) VALUES (6, 7); -- error + +SELECT * FROM base_tbl; + +UPDATE base_tbl SET id = 2000 WHERE id = 2; +UPDATE rw_view1 SET id = 3000 WHERE id = 3; + +SELECT * FROM base_tbl; + +DROP TABLE base_tbl CASCADE; + -- inheritance tests CREATE TABLE base_tbl_parent (a int); diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql index 7c4eabe9a5b4..8b094fe5ae21 100644 --- a/src/test/regress/sql/window.sql +++ b/src/test/regress/sql/window.sql @@ -957,6 +957,28 @@ SELECT lag(1) OVER (PARTITION BY depname ORDER BY salary,enroll_date,empno) FROM empsalary; +-- Test incremental sorting +EXPLAIN (COSTS OFF) +SELECT * FROM + (SELECT depname, + empno, + salary, + enroll_date, + row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp, + row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp + FROM empsalary) emp +WHERE first_emp = 1 OR last_emp = 1; + +SELECT * FROM + (SELECT depname, + empno, + salary, + enroll_date, + row_number() OVER (PARTITION BY depname ORDER BY enroll_date) AS first_emp, + row_number() OVER (PARTITION BY depname ORDER BY enroll_date DESC) AS last_emp + FROM empsalary) emp +WHERE first_emp = 1 OR last_emp = 1; + -- cleanup DROP TABLE empsalary; diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql index 437e6ce0591e..5c760b8ef707 100644 --- a/src/test/regress/sql/with.sql +++ b/src/test/regress/sql/with.sql @@ -316,22 +316,22 @@ insert into graph values (4, 5, 'arc 4 -> 5'), (5, 1, 'arc 5 -> 1'); -with recursive search_graph(f, t, label, path, cycle) as ( - select *, array[row(g.f, g.t)], false from graph g +with recursive search_graph(f, t, label, is_cycle, path) as ( + select *, false, array[row(g.f, g.t)] from graph g union all - select g.*, path || row(g.f, g.t), row(g.f, g.t) = any(path) + select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t) from graph g, search_graph sg - where g.f = sg.t and not cycle + where g.f = sg.t and not is_cycle ) select * from search_graph; -- ordering by the path column has same effect as SEARCH DEPTH FIRST -with recursive search_graph(f, t, label, path, cycle) as ( - select *, array[row(g.f, g.t)], false from graph g +with recursive search_graph(f, t, label, is_cycle, path) as ( + select *, false, array[row(g.f, g.t)] from graph g union all - select g.*, path || row(g.f, g.t), row(g.f, g.t) = any(path) + select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t) from graph g, search_graph sg - where g.f = sg.t and not cycle + where g.f = sg.t and not is_cycle ) select * from search_graph order by path; diff --git a/src/test/subscription/t/015_stream.pl b/src/test/subscription/t/015_stream.pl new file mode 100644 index 000000000000..fffe00196507 --- /dev/null +++ b/src/test/subscription/t/015_stream.pl @@ -0,0 +1,98 @@ +# Test streaming of simple large transaction +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 4; + +# Create publisher node +my $node_publisher = get_new_node('publisher'); +$node_publisher->init(allows_streaming => 'logical'); +$node_publisher->append_conf('postgresql.conf', 'logical_decoding_work_mem = 64kB'); +$node_publisher->start; + +# Create subscriber node +my $node_subscriber = get_new_node('subscriber'); +$node_subscriber->init(allows_streaming => 'logical'); +$node_subscriber->start; + +# Create some preexisting content on publisher +$node_publisher->safe_psql('postgres', + "CREATE TABLE test_tab (a int primary key, b varchar)"); +$node_publisher->safe_psql('postgres', + "INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')"); + +# Setup structure on subscriber +$node_subscriber->safe_psql('postgres', "CREATE TABLE test_tab (a int primary key, b text, c timestamptz DEFAULT now(), d bigint DEFAULT 999)"); + +# Setup logical replication +my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres'; +$node_publisher->safe_psql('postgres', "CREATE PUBLICATION tap_pub FOR TABLE test_tab"); + +my $appname = 'tap_sub'; +$node_subscriber->safe_psql('postgres', +"CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub WITH (streaming = on)" +); + +$node_publisher->wait_for_catchup($appname); + +# Also wait for initial table sync to finish +my $synced_query = +"SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');"; +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +my $result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d = 999) FROM test_tab"); +is($result, qq(2|2|2), 'check initial data was copied to subscriber'); + +# Insert, update and delete enough rows to exceed the 64kB limit. +$node_publisher->safe_psql('postgres', q{ +BEGIN; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3, 5000) s(i); +UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0; +DELETE FROM test_tab WHERE mod(a,3) = 0; +COMMIT; +}); + +$node_publisher->wait_for_catchup($appname); + +$result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d = 999) FROM test_tab"); +is($result, qq(3334|3334|3334), 'check extra columns contain local defaults'); + +# Test the streaming in binary mode +$node_subscriber->safe_psql('postgres', +"ALTER SUBSCRIPTION tap_sub SET (binary = on)" +); + +# Insert, update and delete enough rows to exceed the 64kB limit. +$node_publisher->safe_psql('postgres', q{ +BEGIN; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(5001, 10000) s(i); +UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0; +DELETE FROM test_tab WHERE mod(a,3) = 0; +COMMIT; +}); + +$node_publisher->wait_for_catchup($appname); + +$result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d = 999) FROM test_tab"); +is($result, qq(6667|6667|6667), 'check extra columns contain local defaults'); + +# Change the local values of the extra columns on the subscriber, +# update publisher, and check that subscriber retains the expected +# values. This is to ensure that non-streaming transactions behave +# properly after a streaming transaction. +$node_subscriber->safe_psql('postgres', "UPDATE test_tab SET c = 'epoch'::timestamptz + 987654321 * interval '1s'"); +$node_publisher->safe_psql('postgres', "UPDATE test_tab SET b = md5(a::text)"); + +$node_publisher->wait_for_catchup($appname); + +$result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(extract(epoch from c) = 987654321), count(d = 999) FROM test_tab"); +is($result, qq(6667|6667|6667), 'check extra columns contain locally changed data'); + +$node_subscriber->stop; +$node_publisher->stop; diff --git a/src/test/subscription/t/016_stream_subxact.pl b/src/test/subscription/t/016_stream_subxact.pl new file mode 100644 index 000000000000..b6a2d10e91ef --- /dev/null +++ b/src/test/subscription/t/016_stream_subxact.pl @@ -0,0 +1,81 @@ +# Test streaming of large transaction containing large subtransactions +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 2; + +# Create publisher node +my $node_publisher = get_new_node('publisher'); +$node_publisher->init(allows_streaming => 'logical'); +$node_publisher->append_conf('postgresql.conf', 'logical_decoding_work_mem = 64kB'); +$node_publisher->start; + +# Create subscriber node +my $node_subscriber = get_new_node('subscriber'); +$node_subscriber->init(allows_streaming => 'logical'); +$node_subscriber->start; + +# Create some preexisting content on publisher +$node_publisher->safe_psql('postgres', + "CREATE TABLE test_tab (a int primary key, b varchar)"); +$node_publisher->safe_psql('postgres', + "INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')"); + +# Setup structure on subscriber +$node_subscriber->safe_psql('postgres', "CREATE TABLE test_tab (a int primary key, b text, c timestamptz DEFAULT now(), d bigint DEFAULT 999)"); + +# Setup logical replication +my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres'; +$node_publisher->safe_psql('postgres', "CREATE PUBLICATION tap_pub FOR TABLE test_tab"); + +my $appname = 'tap_sub'; +$node_subscriber->safe_psql('postgres', +"CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub WITH (streaming = on)" +); + +$node_publisher->wait_for_catchup($appname); + +# Also wait for initial table sync to finish +my $synced_query = +"SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');"; +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +my $result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d = 999) FROM test_tab"); +is($result, qq(2|2|2), 'check initial data was copied to subscriber'); + +# Insert, update and delete enough rows to exceed 64kB limit. +$node_publisher->safe_psql('postgres', q{ +BEGIN; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series( 3, 500) s(i); +UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0; +DELETE FROM test_tab WHERE mod(a,3) = 0; +SAVEPOINT s1; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(501, 1000) s(i); +UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0; +DELETE FROM test_tab WHERE mod(a,3) = 0; +SAVEPOINT s2; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(1001, 1500) s(i); +UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0; +DELETE FROM test_tab WHERE mod(a,3) = 0; +SAVEPOINT s3; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(1501, 2000) s(i); +UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0; +DELETE FROM test_tab WHERE mod(a,3) = 0; +SAVEPOINT s4; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(2001, 2500) s(i); +UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0; +DELETE FROM test_tab WHERE mod(a,3) = 0; +COMMIT; +}); + +$node_publisher->wait_for_catchup($appname); + +$result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d = 999) FROM test_tab"); +is($result, qq(1667|1667|1667), 'check data was copied to subscriber in streaming mode and extra columns contain local defaults'); + +$node_subscriber->stop; +$node_publisher->stop; diff --git a/src/test/subscription/t/017_stream_ddl.pl b/src/test/subscription/t/017_stream_ddl.pl new file mode 100644 index 000000000000..be7d7d74e3fb --- /dev/null +++ b/src/test/subscription/t/017_stream_ddl.pl @@ -0,0 +1,110 @@ +# Test streaming of large transaction with DDL and subtransactions +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 3; + +# Create publisher node +my $node_publisher = get_new_node('publisher'); +$node_publisher->init(allows_streaming => 'logical'); +$node_publisher->append_conf('postgresql.conf', 'logical_decoding_work_mem = 64kB'); +$node_publisher->start; + +# Create subscriber node +my $node_subscriber = get_new_node('subscriber'); +$node_subscriber->init(allows_streaming => 'logical'); +$node_subscriber->start; + +# Create some preexisting content on publisher +$node_publisher->safe_psql('postgres', + "CREATE TABLE test_tab (a int primary key, b varchar)"); +$node_publisher->safe_psql('postgres', + "INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')"); + +# Setup structure on subscriber +$node_subscriber->safe_psql('postgres', "CREATE TABLE test_tab (a int primary key, b text, c INT, d INT, e INT, f INT)"); + +# Setup logical replication +my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres'; +$node_publisher->safe_psql('postgres', "CREATE PUBLICATION tap_pub FOR TABLE test_tab"); + +my $appname = 'tap_sub'; +$node_subscriber->safe_psql('postgres', +"CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub WITH (streaming = on)" +); + +$node_publisher->wait_for_catchup($appname); + +# Also wait for initial table sync to finish +my $synced_query = +"SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');"; +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +my $result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d = 999) FROM test_tab"); +is($result, qq(2|0|0), 'check initial data was copied to subscriber'); + +# a small (non-streamed) transaction with DDL and DML +$node_publisher->safe_psql('postgres', q{ +BEGIN; +INSERT INTO test_tab VALUES (3, md5(3::text)); +ALTER TABLE test_tab ADD COLUMN c INT; +SAVEPOINT s1; +INSERT INTO test_tab VALUES (4, md5(4::text), -4); +COMMIT; +}); + +# large (streamed) transaction with DDL and DML +$node_publisher->safe_psql('postgres', q{ +BEGIN; +INSERT INTO test_tab SELECT i, md5(i::text), -i FROM generate_series(5, 1000) s(i); +ALTER TABLE test_tab ADD COLUMN d INT; +SAVEPOINT s1; +INSERT INTO test_tab SELECT i, md5(i::text), -i, 2*i FROM generate_series(1001, 2000) s(i); +COMMIT; +}); + +# a small (non-streamed) transaction with DDL and DML +$node_publisher->safe_psql('postgres', q{ +BEGIN; +INSERT INTO test_tab VALUES (2001, md5(2001::text), -2001, 2*2001); +ALTER TABLE test_tab ADD COLUMN e INT; +SAVEPOINT s1; +INSERT INTO test_tab VALUES (2002, md5(2002::text), -2002, 2*2002, -3*2002); +COMMIT; +}); + +$node_publisher->wait_for_catchup($appname); + +$result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d), count(e) FROM test_tab"); +is($result, qq(2002|1999|1002|1), 'check data was copied to subscriber in streaming mode and extra columns contain local defaults'); + +# A large (streamed) transaction with DDL and DML. One of the DDL is performed +# after DML to ensure that we invalidate the schema sent for test_tab so that +# the next transaction has to send the schema again. +$node_publisher->safe_psql('postgres', q{ +BEGIN; +INSERT INTO test_tab SELECT i, md5(i::text), -i, 2*i, -3*i FROM generate_series(2003,5000) s(i); +ALTER TABLE test_tab ADD COLUMN f INT; +COMMIT; +}); + +# A small transaction that won't get streamed. This is just to ensure that we +# send the schema again to reflect the last column added in the previous test. +$node_publisher->safe_psql('postgres', q{ +BEGIN; +INSERT INTO test_tab SELECT i, md5(i::text), -i, 2*i, -3*i, 4*i FROM generate_series(5001,5005) s(i); +COMMIT; +}); + +$node_publisher->wait_for_catchup($appname); + +$result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d), count(e), count(f) FROM test_tab"); +is($result, qq(5005|5002|4005|3004|5), 'check data was copied to subscriber for both streaming and non-streaming transactions'); + +$node_subscriber->stop; +$node_publisher->stop; diff --git a/src/test/subscription/t/018_stream_subxact_abort.pl b/src/test/subscription/t/018_stream_subxact_abort.pl new file mode 100644 index 000000000000..ddf0621558a5 --- /dev/null +++ b/src/test/subscription/t/018_stream_subxact_abort.pl @@ -0,0 +1,117 @@ +# Test streaming of large transaction containing multiple subtransactions and rollbacks +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 4; + +# Create publisher node +my $node_publisher = get_new_node('publisher'); +$node_publisher->init(allows_streaming => 'logical'); +$node_publisher->append_conf('postgresql.conf', 'logical_decoding_work_mem = 64kB'); +$node_publisher->start; + +# Create subscriber node +my $node_subscriber = get_new_node('subscriber'); +$node_subscriber->init(allows_streaming => 'logical'); +$node_subscriber->start; + +# Create some preexisting content on publisher +$node_publisher->safe_psql('postgres', + "CREATE TABLE test_tab (a int primary key, b varchar)"); +$node_publisher->safe_psql('postgres', + "INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')"); + +# Setup structure on subscriber +$node_subscriber->safe_psql('postgres', "CREATE TABLE test_tab (a int primary key, b text, c INT, d INT, e INT)"); + +# Setup logical replication +my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres'; +$node_publisher->safe_psql('postgres', "CREATE PUBLICATION tap_pub FOR TABLE test_tab"); + +my $appname = 'tap_sub'; +$node_subscriber->safe_psql('postgres', +"CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub WITH (streaming = on)" +); + +$node_publisher->wait_for_catchup($appname); + +# Also wait for initial table sync to finish +my $synced_query = +"SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');"; +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +my $result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c) FROM test_tab"); +is($result, qq(2|0), 'check initial data was copied to subscriber'); + +# large (streamed) transaction with DDL, DML and ROLLBACKs +$node_publisher->safe_psql('postgres', q{ +BEGIN; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3,500) s(i); +SAVEPOINT s1; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(501,1000) s(i); +SAVEPOINT s2; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(1001,1500) s(i); +SAVEPOINT s3; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(1501,2000) s(i); +ROLLBACK TO s2; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(2001,2500) s(i); +ROLLBACK TO s1; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(2501,3000) s(i); +SAVEPOINT s4; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3001,3500) s(i); +SAVEPOINT s5; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3501,4000) s(i); +COMMIT; +}); + +$node_publisher->wait_for_catchup($appname); + +$result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c) FROM test_tab"); +is($result, qq(2000|0), 'check rollback to savepoint was reflected on subscriber and extra columns contain local defaults'); + +# large (streamed) transaction with subscriber receiving out of order +# subtransaction ROLLBACKs +$node_publisher->safe_psql('postgres', q{ +BEGIN; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(4001,4500) s(i); +SAVEPOINT s1; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(5001,5500) s(i); +SAVEPOINT s2; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(6001,6500) s(i); +SAVEPOINT s3; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(7001,7500) s(i); +RELEASE s2; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(8001,8500) s(i); +ROLLBACK TO s1; +COMMIT; +}); + +$node_publisher->wait_for_catchup($appname); + +$result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c) FROM test_tab"); +is($result, qq(2500|0), 'check rollback to savepoint was reflected on subscriber'); + +# large (streamed) transaction with subscriber receiving rollback +$node_publisher->safe_psql('postgres', q{ +BEGIN; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(8501,9000) s(i); +SAVEPOINT s1; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(9001,9500) s(i); +SAVEPOINT s2; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(9501,10000) s(i); +ROLLBACK; +}); + +$node_publisher->wait_for_catchup($appname); + +$result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c) FROM test_tab"); +is($result, qq(2500|0), 'check rollback was reflected on subscriber'); + +$node_subscriber->stop; +$node_publisher->stop; diff --git a/src/test/subscription/t/019_stream_subxact_ddl_abort.pl b/src/test/subscription/t/019_stream_subxact_ddl_abort.pl new file mode 100644 index 000000000000..33e42edfef26 --- /dev/null +++ b/src/test/subscription/t/019_stream_subxact_ddl_abort.pl @@ -0,0 +1,76 @@ +# Test streaming of large transaction with subtransactions, DDLs, DMLs, and +# rollbacks +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 2; + +# Create publisher node +my $node_publisher = get_new_node('publisher'); +$node_publisher->init(allows_streaming => 'logical'); +$node_publisher->append_conf('postgresql.conf', 'logical_decoding_work_mem = 64kB'); +$node_publisher->start; + +# Create subscriber node +my $node_subscriber = get_new_node('subscriber'); +$node_subscriber->init(allows_streaming => 'logical'); +$node_subscriber->start; + +# Create some preexisting content on publisher +$node_publisher->safe_psql('postgres', + "CREATE TABLE test_tab (a int primary key, b varchar)"); +$node_publisher->safe_psql('postgres', + "INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')"); + +# Setup structure on subscriber +$node_subscriber->safe_psql('postgres', "CREATE TABLE test_tab (a int primary key, b text, c INT, d INT, e INT)"); + +# Setup logical replication +my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres'; +$node_publisher->safe_psql('postgres', "CREATE PUBLICATION tap_pub FOR TABLE test_tab"); + +my $appname = 'tap_sub'; +$node_subscriber->safe_psql('postgres', +"CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub WITH (streaming = on)" +); + +$node_publisher->wait_for_catchup($appname); + +# Also wait for initial table sync to finish +my $synced_query = +"SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');"; +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +my $result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c) FROM test_tab"); +is($result, qq(2|0), 'check initial data was copied to subscriber'); + +# large (streamed) transaction with DDL, DML and ROLLBACKs +$node_publisher->safe_psql('postgres', q{ +BEGIN; +INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3,500) s(i); +ALTER TABLE test_tab ADD COLUMN c INT; +SAVEPOINT s1; +INSERT INTO test_tab SELECT i, md5(i::text), -i FROM generate_series(501,1000) s(i); +ALTER TABLE test_tab ADD COLUMN d INT; +SAVEPOINT s2; +INSERT INTO test_tab SELECT i, md5(i::text), -i, 2*i FROM generate_series(1001,1500) s(i); +ALTER TABLE test_tab ADD COLUMN e INT; +SAVEPOINT s3; +INSERT INTO test_tab SELECT i, md5(i::text), -i, 2*i, -3*i FROM generate_series(1501,2000) s(i); +ALTER TABLE test_tab DROP COLUMN c; +ROLLBACK TO s1; +INSERT INTO test_tab SELECT i, md5(i::text), i FROM generate_series(501,1000) s(i); +COMMIT; +}); + +$node_publisher->wait_for_catchup($appname); + +$result = + $node_subscriber->safe_psql('postgres', "SELECT count(*), count(c) FROM test_tab"); +is($result, qq(1000|500), 'check rollback to savepoint was reflected on subscriber and extra columns contain local defaults'); + +$node_subscriber->stop; +$node_publisher->stop; diff --git a/src/test/subscription/t/100_bugs.pl b/src/test/subscription/t/100_bugs.pl index 366a7a94350a..d1e407aacb32 100644 --- a/src/test/subscription/t/100_bugs.pl +++ b/src/test/subscription/t/100_bugs.pl @@ -3,7 +3,7 @@ use warnings; use PostgresNode; use TestLib; -use Test::More tests => 3; +use Test::More tests => 5; # Bug #15114 @@ -100,3 +100,56 @@ ); $node_publisher->stop('fast'); + +# Bug #16643 - https://postgr.es/m/16643-eaadeb2a1a58d28c@postgresql.org +# +# Initial sync doesn't complete; the protocol was not being followed per +# expectations after commit 07082b08cc5d. +my $node_twoways = get_new_node('twoways'); +$node_twoways->init(allows_streaming => 'logical'); +$node_twoways->start; +for my $db (qw(d1 d2)) +{ + $node_twoways->safe_psql('postgres', "CREATE DATABASE $db"); + $node_twoways->safe_psql($db, "CREATE TABLE t (f int)"); + $node_twoways->safe_psql($db, "CREATE TABLE t2 (f int)"); +} + +my $rows = 3000; +$node_twoways->safe_psql( + 'd1', qq{ + INSERT INTO t SELECT * FROM generate_series(1, $rows); + INSERT INTO t2 SELECT * FROM generate_series(1, $rows); + CREATE PUBLICATION testpub FOR TABLE t; + SELECT pg_create_logical_replication_slot('testslot', 'pgoutput'); + }); + +$node_twoways->safe_psql('d2', + "CREATE SUBSCRIPTION testsub CONNECTION \$\$" + . $node_twoways->connstr('d1') + . "\$\$ PUBLICATION testpub WITH (create_slot=false, " + . "slot_name='testslot')"); +$node_twoways->safe_psql( + 'd1', qq{ + INSERT INTO t SELECT * FROM generate_series(1, $rows); + INSERT INTO t2 SELECT * FROM generate_series(1, $rows); + }); +$node_twoways->safe_psql( + 'd1', 'ALTER PUBLICATION testpub ADD TABLE t2'); +$node_twoways->safe_psql( + 'd2', 'ALTER SUBSCRIPTION testsub REFRESH PUBLICATION'); + +# We cannot rely solely on wait_for_catchup() here; it isn't sufficient +# when tablesync workers might still be running. So in addition to that, +# verify that tables are synced. +# XXX maybe this should be integrated in wait_for_catchup() itself. +$node_twoways->wait_for_catchup('testsub'); +my $synced_query = + "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');"; +$node_twoways->poll_query_until('d2', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +is($node_twoways->safe_psql('d2', "SELECT count(f) FROM t"), + $rows * 2, "2x$rows rows in t"); +is($node_twoways->safe_psql('d2', "SELECT count(f) FROM t2"), + $rows * 2, "2x$rows rows in t2"); diff --git a/src/test/thread/.gitignore b/src/test/thread/.gitignore deleted file mode 100644 index 1d54d546a8ce..000000000000 --- a/src/test/thread/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/thread_test diff --git a/src/test/thread/Makefile b/src/test/thread/Makefile deleted file mode 100644 index a13c0c6cf530..000000000000 --- a/src/test/thread/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -#------------------------------------------------------------------------- -# -# Makefile for tools/thread -# -# Copyright (c) 2003-2020, PostgreSQL Global Development Group -# -# src/test/thread/Makefile -# -#------------------------------------------------------------------------- - -subdir = src/tools/thread -top_builddir = ../../.. -include $(top_builddir)/src/Makefile.global - -override CFLAGS += $(PTHREAD_CFLAGS) - -all: thread_test - -thread_test: thread_test.o -# no need for $LIBS, might not be compiled yet - $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(PTHREAD_LIBS) -o $@$(X) - -clean distclean maintainer-clean: - rm -f thread_test$(X) thread_test.o diff --git a/src/test/thread/README b/src/test/thread/README deleted file mode 100644 index 4da23440f6b5..000000000000 --- a/src/test/thread/README +++ /dev/null @@ -1,54 +0,0 @@ -src/test/thread/README - -Threading -========= - -This program is run by configure to determine if threading is -properly supported on the platform. - -You can run the program manually to see details, which shows if your -native libc functions are thread-safe, or if we use *_r functions or -thread locking. - -To use this program manually, you must: - - o run "configure" - o compile the main source tree - o compile and run this program - -If your platform requires special thread flags that are not tested by -/config/acx_pthread.m4, add PTHREAD_CFLAGS and PTHREAD_LIBS defines to -your template/${port} file. - -Windows Systems -=============== - -Windows systems do not vary in their thread-safeness in the same way that -other systems might, nor do they generally have pthreads installed, hence -on Windows this test is skipped by the configure program (pthreads is -required by the test program, but not PostgreSQL itself). If you do wish -to test your system however, you can do so as follows: - -1) Install pthreads in you Mingw/Msys environment. You can download pthreads - from ftp://sources.redhat.com/pub/pthreads-win32/. - -2) Build the test program: - - gcc -o thread_test.exe \ - -D_REENTRANT \ - -D_THREAD_SAFE \ - -D_POSIX_PTHREAD_SEMANTICS \ - -I../../../src/include/port/win32 \ - thread_test.c \ - -lws2_32 \ - -lpthreadgc2 - -3) Run thread_test.exe. You should see output like: - - dpage@PC30:/cvs/pgsql/src/tools/thread$ ./thread_test - Your GetLastError() is thread-safe. - Your system uses strerror() which is thread-safe. - getpwuid_r()/getpwuid() are not applicable to Win32 platforms. - Your system uses gethostbyname which is thread-safe. - - Your platform is thread-safe. diff --git a/src/timezone/Makefile b/src/timezone/Makefile index 715b63cee0cd..2b5d8ecbef81 100644 --- a/src/timezone/Makefile +++ b/src/timezone/Makefile @@ -56,7 +56,7 @@ zic: $(ZICOBJS) | submake-libpgport install: all installdirs ifeq (,$(with_system_tzdata)) - $(ZIC) -d '$(DESTDIR)$(datadir)/timezone' -b slim $(ZIC_OPTIONS) $(TZDATAFILES) + $(ZIC) -d '$(DESTDIR)$(datadir)/timezone' $(ZIC_OPTIONS) $(TZDATAFILES) endif $(MAKE) -C tznames $@ diff --git a/src/timezone/README b/src/timezone/README index 8af44449329a..f588d1f5add2 100644 --- a/src/timezone/README +++ b/src/timezone/README @@ -55,7 +55,7 @@ match properly on the old version. Time Zone code ============== -The code in this directory is currently synced with tzcode release 2020a. +The code in this directory is currently synced with tzcode release 2020d. There are many cosmetic (and not so cosmetic) differences from the original tzcode library, but diffs in the upstream version should usually be propagated to our version. Here are some notes about that. diff --git a/src/timezone/data/tzdata.zi b/src/timezone/data/tzdata.zi index e7c31b68c867..09afb428717f 100644 --- a/src/timezone/data/tzdata.zi +++ b/src/timezone/data/tzdata.zi @@ -1,4 +1,4 @@ -# version 2020a +# version 2020d # This zic input file is in the public domain. R d 1916 o - Jun 14 23s 1 S R d 1916 1919 - O Su>=1 23s 0 - @@ -22,7 +22,7 @@ R d 1978 o - Mar 24 1 1 S R d 1978 o - S 22 3 0 - R d 1980 o - Ap 25 0 1 S R d 1980 o - O 31 2 0 - -Z Africa/Algiers 0:12:12 - LMT 1891 Mar 15 0:1 +Z Africa/Algiers 0:12:12 - LMT 1891 Mar 16 0:9:21 - PMT 1911 Mar 11 0 d WE%sT 1940 F 25 2 1 d CE%sT 1946 O 7 @@ -193,7 +193,7 @@ R M 2021 o - May 16 2 0 - R M 2022 o - Mar 27 3 -1 - R M 2022 o - May 8 2 0 - R M 2023 o - Mar 19 3 -1 - -R M 2023 o - Ap 23 2 0 - +R M 2023 o - Ap 30 2 0 - R M 2024 o - Mar 10 3 -1 - R M 2024 o - Ap 14 2 0 - R M 2025 o - F 23 3 -1 - @@ -209,7 +209,7 @@ R M 2029 o - F 18 2 0 - R M 2029 o - D 30 3 -1 - R M 2030 o - F 10 2 0 - R M 2030 o - D 22 3 -1 - -R M 2031 o - Ja 26 2 0 - +R M 2031 o - F 2 2 0 - R M 2031 o - D 14 3 -1 - R M 2032 o - Ja 18 2 0 - R M 2032 o - N 28 3 -1 - @@ -225,7 +225,7 @@ R M 2036 o - N 23 2 0 - R M 2037 o - O 4 3 -1 - R M 2037 o - N 15 2 0 - R M 2038 o - S 26 3 -1 - -R M 2038 o - O 31 2 0 - +R M 2038 o - N 7 2 0 - R M 2039 o - S 18 3 -1 - R M 2039 o - O 23 2 0 - R M 2040 o - S 2 3 -1 - @@ -241,7 +241,7 @@ R M 2044 o - Au 28 2 0 - R M 2045 o - Jul 9 3 -1 - R M 2045 o - Au 20 2 0 - R M 2046 o - Jul 1 3 -1 - -R M 2046 o - Au 5 2 0 - +R M 2046 o - Au 12 2 0 - R M 2047 o - Jun 23 3 -1 - R M 2047 o - Jul 28 2 0 - R M 2048 o - Jun 7 3 -1 - @@ -257,7 +257,7 @@ R M 2052 o - Jun 2 2 0 - R M 2053 o - Ap 13 3 -1 - R M 2053 o - May 25 2 0 - R M 2054 o - Ap 5 3 -1 - -R M 2054 o - May 10 2 0 - +R M 2054 o - May 17 2 0 - R M 2055 o - Mar 28 3 -1 - R M 2055 o - May 2 2 0 - R M 2056 o - Mar 12 3 -1 - @@ -273,7 +273,7 @@ R M 2060 o - Mar 7 2 0 - R M 2061 o - Ja 16 3 -1 - R M 2061 o - F 27 2 0 - R M 2062 o - Ja 8 3 -1 - -R M 2062 o - F 12 2 0 - +R M 2062 o - F 19 2 0 - R M 2062 o - D 31 3 -1 - R M 2063 o - F 4 2 0 - R M 2063 o - D 16 3 -1 - @@ -289,7 +289,7 @@ R M 2067 o - D 11 2 0 - R M 2068 o - O 21 3 -1 - R M 2068 o - D 2 2 0 - R M 2069 o - O 13 3 -1 - -R M 2069 o - N 17 2 0 - +R M 2069 o - N 24 2 0 - R M 2070 o - O 5 3 -1 - R M 2070 o - N 9 2 0 - R M 2071 o - S 20 3 -1 - @@ -305,7 +305,7 @@ R M 2075 o - S 15 2 0 - R M 2076 o - Jul 26 3 -1 - R M 2076 o - S 6 2 0 - R M 2077 o - Jul 18 3 -1 - -R M 2077 o - Au 22 2 0 - +R M 2077 o - Au 29 2 0 - R M 2078 o - Jul 10 3 -1 - R M 2078 o - Au 14 2 0 - R M 2079 o - Jun 25 3 -1 - @@ -315,13 +315,13 @@ R M 2080 o - Jul 21 2 0 - R M 2081 o - Jun 1 3 -1 - R M 2081 o - Jul 13 2 0 - R M 2082 o - May 24 3 -1 - -R M 2082 o - Jun 28 2 0 - +R M 2082 o - Jul 5 2 0 - R M 2083 o - May 16 3 -1 - R M 2083 o - Jun 20 2 0 - R M 2084 o - Ap 30 3 -1 - R M 2084 o - Jun 11 2 0 - R M 2085 o - Ap 22 3 -1 - -R M 2085 o - May 27 2 0 - +R M 2085 o - Jun 3 2 0 - R M 2086 o - Ap 14 3 -1 - R M 2086 o - May 19 2 0 - R M 2087 o - Mar 30 3 -1 - @@ -426,7 +426,12 @@ Z Antarctica/Casey 0 - -00 1969 11 - +11 2012 F 21 17u 8 - +08 2016 O 22 11 - +11 2018 Mar 11 4 -8 - +08 +8 - +08 2018 O 7 4 +11 - +11 2019 Mar 17 3 +8 - +08 2019 O 4 3 +11 - +11 2020 Mar 8 3 +8 - +08 2020 O 4 0:1 +11 - +11 Z Antarctica/Davis 0 - -00 1957 Ja 13 7 - +07 1964 N 0 - -00 1969 F @@ -1091,10 +1096,10 @@ R P 2004 o - O 1 1 0 - R P 2005 o - O 4 2 0 - R P 2006 2007 - Ap 1 0 1 S R P 2006 o - S 22 0 0 - -R P 2007 o - S Th>=8 2 0 - +R P 2007 o - S 13 2 0 - R P 2008 2009 - Mar lastF 0 1 S R P 2008 o - S 1 0 0 - -R P 2009 o - S F>=1 1 0 - +R P 2009 o - S 4 1 0 - R P 2010 o - Mar 26 0 1 S R P 2010 o - Au 11 0 0 - R P 2011 o - Ap 1 0:1 1 S @@ -1103,12 +1108,16 @@ R P 2011 o - Au 30 0 1 S R P 2011 o - S 30 0 0 - R P 2012 2014 - Mar lastTh 24 1 S R P 2012 o - S 21 1 0 - -R P 2013 o - S F>=21 0 0 - -R P 2014 2015 - O F>=21 0 0 - -R P 2015 o - Mar lastF 24 1 S +R P 2013 o - S 27 0 0 - +R P 2014 o - O 24 0 0 - +R P 2015 o - Mar 28 0 1 S +R P 2015 o - O 23 1 0 - R P 2016 2018 - Mar Sa>=24 1 1 S -R P 2016 ma - O lastSa 1 0 - -R P 2019 ma - Mar lastF 0 1 S +R P 2016 2018 - O Sa>=24 1 0 - +R P 2019 o - Mar 29 0 1 S +R P 2019 o - O Sa>=24 0 0 - +R P 2020 ma - Mar Sa>=24 0 1 S +R P 2020 ma - O Sa>=24 1 0 - Z Asia/Gaza 2:17:52 - LMT 1900 O 2 Z EET/EEST 1948 May 15 2 K EE%sT 1967 Jun 5 @@ -1399,8 +1408,9 @@ Z Antarctica/Macquarie 0 - -00 1899 N 10 AU AE%sT 1919 Ap 1 0s 0 - -00 1948 Mar 25 10 AU AE%sT 1967 -10 AT AE%sT 2010 Ap 4 3 -11 - +11 +10 AT AE%sT 2010 +10 1 AEDT 2011 +10 AT AE%sT Z Indian/Christmas 7:2:52 - LMT 1895 F 7 - +07 Z Indian/Cocos 6:27:40 - LMT 1900 @@ -1415,7 +1425,9 @@ R FJ 2012 2013 - Ja Su>=18 3 0 - R FJ 2014 o - Ja Su>=18 2 0 - R FJ 2014 2018 - N Su>=1 2 1 - R FJ 2015 ma - Ja Su>=12 3 0 - -R FJ 2019 ma - N Su>=8 2 1 - +R FJ 2019 o - N Su>=8 2 1 - +R FJ 2020 o - D 20 2 1 - +R FJ 2021 ma - N Su>=8 2 1 - Z Pacific/Fiji 11:55:44 - LMT 1915 O 26 12 FJ +12/+13 Z Pacific/Gambier -8:59:48 - LMT 1912 O @@ -1992,8 +2004,8 @@ R F 1945 o - Ap 2 2 2 M R F 1945 o - S 16 3 0 - R F 1976 o - Mar 28 1 1 S R F 1976 o - S 26 1 0 - -Z Europe/Paris 0:9:21 - LMT 1891 Mar 15 0:1 -0:9:21 - PMT 1911 Mar 11 0:1 +Z Europe/Paris 0:9:21 - LMT 1891 Mar 16 +0:9:21 - PMT 1911 Mar 11 0 F WE%sT 1940 Jun 14 23 1 c CE%sT 1944 Au 25 0 F WE%sT 1945 S 16 3 @@ -2045,29 +2057,30 @@ Z Europe/Athens 1:34:52 - LMT 1895 S 14 1 g CE%sT 1944 Ap 4 2 g EE%sT 1981 2 E EE%sT -R h 1918 o - Ap 1 3 1 S -R h 1918 o - S 16 3 0 - -R h 1919 o - Ap 15 3 1 S -R h 1919 o - N 24 3 0 - +R h 1918 1919 - Ap 15 2 1 S +R h 1918 1920 - S M>=15 3 0 - +R h 1920 o - Ap 5 2 1 S R h 1945 o - May 1 23 1 S -R h 1945 o - N 1 0 0 - +R h 1945 o - N 1 1 0 - R h 1946 o - Mar 31 2s 1 S -R h 1946 1949 - O Su>=1 2s 0 - +R h 1946 o - O 7 2 0 - R h 1947 1949 - Ap Su>=4 2s 1 S -R h 1950 o - Ap 17 2s 1 S -R h 1950 o - O 23 2s 0 - -R h 1954 1955 - May 23 0 1 S -R h 1954 1955 - O 3 0 0 - -R h 1956 o - Jun Su>=1 0 1 S -R h 1956 o - S lastSu 0 0 - -R h 1957 o - Jun Su>=1 1 1 S -R h 1957 o - S lastSu 3 0 - -R h 1980 o - Ap 6 1 1 S -Z Europe/Budapest 1:16:20 - LMT 1890 O +R h 1947 1949 - O Su>=1 2s 0 - +R h 1954 o - May 23 0 1 S +R h 1954 o - O 3 0 0 - +R h 1955 o - May 22 2 1 S +R h 1955 o - O 2 3 0 - +R h 1956 1957 - Jun Su>=1 2 1 S +R h 1956 1957 - S lastSu 3 0 - +R h 1980 o - Ap 6 0 1 S +R h 1980 o - S 28 1 0 - +R h 1981 1983 - Mar lastSu 0 1 S +R h 1981 1983 - S lastSu 1 0 - +Z Europe/Budapest 1:16:20 - LMT 1890 N 1 c CE%sT 1918 -1 h CE%sT 1941 Ap 8 +1 h CE%sT 1941 Ap 7 23 1 c CE%sT 1945 -1 h CE%sT 1980 S 28 2s +1 h CE%sT 1984 1 E CE%sT R w 1917 1919 - F 19 23 1 - R w 1917 o - O 21 1 0 - @@ -2223,8 +2236,8 @@ Z Europe/Chisinau 1:55:20 - LMT 1880 2 R EE%sT 1992 2 e EE%sT 1997 2 MD EE%sT -Z Europe/Monaco 0:29:32 - LMT 1891 Mar 15 -0:9:21 - PMT 1911 Mar 11 +Z Europe/Monaco 0:29:32 - LMT 1892 Jun +0:9:21 - PMT 1911 Mar 29 0 F WE%sT 1945 S 16 3 1 F CE%sT 1977 1 E CE%sT @@ -3413,12 +3426,12 @@ Z America/Inuvik 0 - -00 1953 Z America/Whitehorse -9:0:12 - LMT 1900 Au 20 -9 Y Y%sT 1967 May 28 -8 Y P%sT 1980 --8 C P%sT 2020 Mar 8 2 +-8 C P%sT 2020 N -7 - MST Z America/Dawson -9:17:40 - LMT 1900 Au 20 -9 Y Y%sT 1973 O 28 -8 Y P%sT 1980 --8 C P%sT 2020 Mar 8 2 +-8 C P%sT 2020 N -7 - MST R m 1939 o - F 5 0 1 D R m 1939 o - Jun 25 0 0 S diff --git a/src/timezone/strftime.c b/src/timezone/strftime.c index 4b942c393a34..dd6c7db86958 100644 --- a/src/timezone/strftime.c +++ b/src/timezone/strftime.c @@ -128,12 +128,22 @@ size_t pg_strftime(char *s, size_t maxsize, const char *format, const struct pg_tm *t) { char *p; + int saved_errno = errno; enum warn warn = IN_NONE; p = _fmt(format, t, s, s + maxsize, &warn); + if (!p) + { + errno = EOVERFLOW; + return 0; + } if (p == s + maxsize) + { + errno = ERANGE; return 0; + } *p = '\0'; + errno = saved_errno; return p - s; } diff --git a/src/timezone/zic.c b/src/timezone/zic.c index 10c5b4bfb5b5..0ea6ead2db3a 100644 --- a/src/timezone/zic.c +++ b/src/timezone/zic.c @@ -37,10 +37,6 @@ typedef int64 zic_t; #define MKDIR_UMASK 0755 #endif #endif -#ifndef AT_SYMLINK_FOLLOW -#define linkat(fromdir, from, todir, to, flag) \ - (itssymlink(from) ? (errno = ENOTSUP, -1) : link(from, to)) -#endif /* Port to native MS-Windows and to ancient UNIX. */ #if !defined S_ISDIR && defined S_IFDIR && defined S_IFMT #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) @@ -66,7 +62,6 @@ struct rule zic_t r_loyear; /* for example, 1986 */ zic_t r_hiyear; /* for example, 1986 */ - const char *r_yrtype; bool r_lowasnum; bool r_hiwasnum; @@ -116,7 +111,11 @@ struct zone zic_t z_untiltime; }; -extern int link(const char *fromname, const char *toname); +extern int link(const char *target, const char *linkname); +#ifndef AT_SYMLINK_FOLLOW +#define linkat(targetdir, target, linknamedir, linkname, flag) \ + (itssymlink(target) ? (errno = ENOTSUP, -1) : link(target, linkname)) +#endif static void memory_exhausted(const char *msg) pg_attribute_noreturn(); static void verror(const char *string, va_list args) pg_attribute_printf(1, 0); @@ -154,7 +153,6 @@ static void rulesub(struct rule *rp, const char *typep, const char *monthp, const char *dayp, const char *timep); static zic_t tadd(zic_t t1, zic_t t2); -static bool yearistype(zic_t year, const char *type); /* Bound on length of what %z can expand to. */ enum @@ -253,8 +251,8 @@ static int typecnt; * Which fields are which on a Link line. */ -#define LF_FROM 1 -#define LF_TO 2 +#define LF_TARGET 1 +#define LF_LINKNAME 2 #define LINK_FIELDS 3 /* @@ -292,8 +290,8 @@ struct link { const char *l_filename; lineno_t l_linenum; - const char *l_from; - const char *l_to; + const char *l_target; + const char *l_linkname; }; static struct link *links; @@ -634,11 +632,10 @@ static const char *lcltime; static const char *directory; static const char *leapsec; static const char *tzdefault; -static const char *yitcommand; /* -1 if the TZif output file should be slim, 0 if default, 1 if the - output should be fat for backward compatibility. Currently the - default is fat, although this may change. */ + output should be fat for backward compatibility. ZIC_BLOAT_DEFAULT + determines the default. */ static int bloat; static bool @@ -648,7 +645,7 @@ want_bloat(void) } #ifndef ZIC_BLOAT_DEFAULT -#define ZIC_BLOAT_DEFAULT "fat" +#define ZIC_BLOAT_DEFAULT "slim" #endif int @@ -747,18 +744,7 @@ main(int argc, char **argv) tzdefault = optarg; break; case 'y': - if (yitcommand == NULL) - { - warning(_("-y is obsolescent")); - yitcommand = strdup(optarg); - } - else - { - fprintf(stderr, - _("%s: More than one -y option specified\n"), - progname); - return EXIT_FAILURE; - } + warning(_("-y ignored")); break; case 'L': if (leapsec == NULL) @@ -802,13 +788,20 @@ main(int argc, char **argv) if (optind == argc - 1 && strcmp(argv[optind], "=") == 0) usage(stderr, EXIT_FAILURE); /* usage message by request */ if (bloat == 0) - bloat = strcmp(ZIC_BLOAT_DEFAULT, "slim") == 0 ? -1 : 1; + { + static char const bloat_default[] = ZIC_BLOAT_DEFAULT; + + if (strcmp(bloat_default, "slim") == 0) + bloat = -1; + else if (strcmp(bloat_default, "fat") == 0) + bloat = 1; + else + abort(); /* Configuration error. */ + } if (directory == NULL) directory = "data"; if (tzdefault == NULL) tzdefault = TZDEFAULT; - if (yitcommand == NULL) - yitcommand = "yearistype"; if (optind < argc && leapsec != NULL) { @@ -838,11 +831,11 @@ main(int argc, char **argv) for (i = 0; i < nlinks; ++i) { eat(links[i].l_filename, links[i].l_linenum); - dolink(links[i].l_from, links[i].l_to, false); + dolink(links[i].l_target, links[i].l_linkname, false); if (noise) for (j = 0; j < nlinks; ++j) - if (strcmp(links[i].l_to, - links[j].l_from) == 0) + if (strcmp(links[i].l_linkname, + links[j].l_target) == 0) warning(_("link to link")); } if (lcltime != NULL) @@ -953,7 +946,7 @@ namecheck(const char *name) */ #ifdef HAVE_SYMLINK static char * -relname(char const *from, char const *to) +relname(char const *target, char const *linkname) { size_t i, taillen, @@ -961,26 +954,26 @@ relname(char const *from, char const *to) size_t dir_len = 0, dotdots = 0, linksize = SIZE_MAX; - char const *f = from; + char const *f = target; char *result = NULL; - if (*to == '/') + if (*linkname == '/') { /* Make F absolute too. */ size_t len = strlen(directory); bool needslash = len && directory[len - 1] != '/'; - linksize = len + needslash + strlen(from) + 1; + linksize = len + needslash + strlen(target) + 1; f = result = emalloc(linksize); strcpy(result, directory); result[len] = '/'; - strcpy(result + len + needslash, from); + strcpy(result + len + needslash, target); } - for (i = 0; f[i] && f[i] == to[i]; i++) + for (i = 0; f[i] && f[i] == linkname[i]; i++) if (f[i] == '/') dir_len = i + 1; - for (; to[i]; i++) - dotdots += to[i] == '/' && to[i - 1] != '/'; + for (; linkname[i]; i++) + dotdots += linkname[i] == '/' && linkname[i - 1] != '/'; taillen = strlen(f + dir_len); dotdotetcsize = 3 * dotdots + taillen + 1; if (dotdotetcsize <= linksize) @@ -998,62 +991,65 @@ relname(char const *from, char const *to) /* Hard link FROM to TO, following any symbolic links. Return 0 if successful, an error number otherwise. */ static int -hardlinkerr(char const *from, char const *to) +hardlinkerr(char const *target, char const *linkname) { - int r = linkat(AT_FDCWD, from, AT_FDCWD, to, AT_SYMLINK_FOLLOW); + int r = linkat(AT_FDCWD, target, AT_FDCWD, linkname, AT_SYMLINK_FOLLOW); return r == 0 ? 0 : errno; } static void -dolink(char const *fromfield, char const *tofield, bool staysymlink) +dolink(char const *target, char const *linkname, bool staysymlink) { - bool todirs_made = false; + bool remove_only = strcmp(target, "-") == 0; + bool linkdirs_made = false; int link_errno; /* * We get to be careful here since there's a fair chance of root running * us. */ - if (itsdir(fromfield)) + if (!remove_only && itsdir(target)) { - fprintf(stderr, _("%s: link from %s/%s failed: %s\n"), - progname, directory, fromfield, strerror(EPERM)); + fprintf(stderr, _("%s: linking target %s/%s failed: %s\n"), + progname, directory, target, strerror(EPERM)); exit(EXIT_FAILURE); } if (staysymlink) - staysymlink = itssymlink(tofield); - if (remove(tofield) == 0) - todirs_made = true; + staysymlink = itssymlink(linkname); + if (remove(linkname) == 0) + linkdirs_made = true; else if (errno != ENOENT) { char const *e = strerror(errno); fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"), - progname, directory, tofield, e); + progname, directory, linkname, e); exit(EXIT_FAILURE); } - link_errno = staysymlink ? ENOTSUP : hardlinkerr(fromfield, tofield); - if (link_errno == ENOENT && !todirs_made) + if (remove_only) + return; + link_errno = staysymlink ? ENOTSUP : hardlinkerr(target, linkname); + if (link_errno == ENOENT && !linkdirs_made) { - mkdirs(tofield, true); - todirs_made = true; - link_errno = hardlinkerr(fromfield, tofield); + mkdirs(linkname, true); + linkdirs_made = true; + link_errno = hardlinkerr(target, linkname); } if (link_errno != 0) { #ifdef HAVE_SYMLINK - bool absolute = *fromfield == '/'; - char *linkalloc = absolute ? NULL : relname(fromfield, tofield); - char const *contents = absolute ? fromfield : linkalloc; - int symlink_errno = symlink(contents, tofield) == 0 ? 0 : errno; + bool absolute = *target == '/'; + char *linkalloc = absolute ? NULL : relname(target, linkname); + char const *contents = absolute ? target : linkalloc; + int symlink_errno = symlink(contents, linkname) == 0 ? 0 : errno; - if (!todirs_made + if (!linkdirs_made && (symlink_errno == ENOENT || symlink_errno == ENOTSUP)) { - mkdirs(tofield, true); + mkdirs(linkname, true); if (symlink_errno == ENOENT) - symlink_errno = symlink(contents, tofield) == 0 ? 0 : errno; + symlink_errno = symlink(contents, linkname) == 0 ? 0 : errno; } free(linkalloc); if (symlink_errno == 0) @@ -1069,28 +1065,28 @@ dolink(char const *fromfield, char const *tofield, bool staysymlink) *tp; int c; - fp = fopen(fromfield, "rb"); + fp = fopen(target, "rb"); if (!fp) { char const *e = strerror(errno); fprintf(stderr, _("%s: Can't read %s/%s: %s\n"), - progname, directory, fromfield, e); + progname, directory, target, e); exit(EXIT_FAILURE); } - tp = fopen(tofield, "wb"); + tp = fopen(linkname, "wb"); if (!tp) { char const *e = strerror(errno); fprintf(stderr, _("%s: Can't create %s/%s: %s\n"), - progname, directory, tofield, e); + progname, directory, linkname, e); exit(EXIT_FAILURE); } while ((c = getc(fp)) != EOF) putc(c, tp); - close_file(fp, directory, fromfield); - close_file(tp, directory, tofield); + close_file(fp, directory, target); + close_file(tp, directory, linkname); if (link_errno != ENOTSUP) warning(_("copy used because hard link failed: %s"), strerror(link_errno)); @@ -1806,17 +1802,17 @@ inlink(char **fields, int nfields) error(_("wrong number of fields on Link line")); return; } - if (*fields[LF_FROM] == '\0') + if (*fields[LF_TARGET] == '\0') { - error(_("blank FROM field on Link line")); + error(_("blank TARGET field on Link line")); return; } - if (!namecheck(fields[LF_TO])) + if (!namecheck(fields[LF_LINKNAME])) return; l.l_filename = filename; l.l_linenum = linenum; - l.l_from = ecpyalloc(fields[LF_FROM]); - l.l_to = ecpyalloc(fields[LF_TO]); + l.l_target = ecpyalloc(fields[LF_TARGET]); + l.l_linkname = ecpyalloc(fields[LF_LINKNAME]); links = growalloc(links, sizeof *links, nlinks, &nlinks_alloc); links[nlinks++] = l; } @@ -1932,18 +1928,11 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp, error(_("starting year greater than ending year")); return; } - if (*typep == '\0') - rp->r_yrtype = NULL; - else + if (*typep != '\0') { - if (rp->r_loyear == rp->r_hiyear) - { - error(_("typed single year")); - return; - } - warning(_("year type \"%s\" is obsolete; use \"-\" instead"), - typep); - rp->r_yrtype = ecpyalloc(typep); + error(_("year type \"%s\" is unsupported; use \"-\" instead"), + typep); + return; } /* @@ -2848,8 +2837,6 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) rp = &zp->z_rules[i]; if (rp->r_hiwasnum || rp->r_hiyear != ZIC_MAX) continue; - if (rp->r_yrtype != NULL) - continue; if (!rp->r_isdst) { if (stdrp == NULL) @@ -3145,7 +3132,8 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) /* * Mark which rules to do in the current year. For those to - * do, calculate rpytime(rp, year); + * do, calculate rpytime(rp, year); The former TYPE field was + * also considered here. */ for (j = 0; j < zp->z_nrules; ++j) { @@ -3153,8 +3141,7 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) eats(zp->z_filename, zp->z_linenum, rp->r_filename, rp->r_linenum); rp->r_todo = year >= rp->r_loyear && - year <= rp->r_hiyear && - yearistype(year, rp->r_yrtype); + year <= rp->r_hiyear; if (rp->r_todo) { rp->r_temp = rpytime(rp, year); @@ -3474,54 +3461,6 @@ adjleap(void) } } -static char * -shellquote(char *b, char const *s) -{ - *b++ = '\''; - while (*s) - { - if (*s == '\'') - *b++ = '\'', *b++ = '\\', *b++ = '\''; - *b++ = *s++; - } - *b++ = '\''; - return b; -} - -static bool -yearistype(zic_t year, const char *type) -{ - char *buf; - char *b; - int result; - - if (type == NULL || *type == '\0') - return true; - buf = emalloc(1 + 4 * strlen(yitcommand) + 2 - + INT_STRLEN_MAXIMUM(zic_t) + 2 + 4 * strlen(type) + 2); - b = shellquote(buf, yitcommand); - *b++ = ' '; - b += sprintf(b, INT64_FORMAT, year); - *b++ = ' '; - b = shellquote(b, type); - *b = '\0'; - result = system(buf); - if (WIFEXITED(result)) - { - int status = WEXITSTATUS(result); - - if (status <= 1) - { - free(buf); - return status == 0; - } - } - error(_("Wild result from command execution")); - fprintf(stderr, _("%s: command was '%s', result was %d\n"), - progname, buf, result); - exit(EXIT_FAILURE); -} - /* Is A a space character in the C locale? */ static bool is_space(char a) diff --git a/src/tools/PerfectHash.pm b/src/tools/PerfectHash.pm index 74fb1f2ef628..964f79b71a27 100644 --- a/src/tools/PerfectHash.pm +++ b/src/tools/PerfectHash.pm @@ -81,13 +81,13 @@ sub generate_hash_function # to calculate via shift-and-add, so don't change them without care. # (Commonly, random seeds are tried, but we want reproducible results # from this program so we don't do that.) - my $hash_mult1 = 31; + my $hash_mult1 = 257; my $hash_mult2; my $hash_seed1; my $hash_seed2; my @subresult; FIND_PARAMS: - foreach (127, 257, 521, 1033, 2053) + foreach (17, 31, 127, 8191) { $hash_mult2 = $_; # "foreach $hash_mult2" doesn't work for ($hash_seed1 = 0; $hash_seed1 < 10; $hash_seed1++) @@ -121,13 +121,16 @@ sub generate_hash_function { $f .= sprintf "%s(const void *key, size_t keylen)\n{\n", $funcname; } - $f .= sprintf "\tstatic const %s h[%d] = {\n", $elemtype, $nhash; + $f .= sprintf "\tstatic const %s h[%d] = {\n\t\t", $elemtype, $nhash; for (my $i = 0; $i < $nhash; $i++) { - $f .= sprintf "%s%6d,%s", - ($i % 8 == 0 ? "\t\t" : " "), - $hashtab[$i], - ($i % 8 == 7 ? "\n" : ""); + # Hash element. + $f .= sprintf "%d", $hashtab[$i]; + next if ($i == $nhash - 1); + + # Optional indentation and newline, with eight items per line. + $f .= sprintf ",%s", + ($i % 8 == 7 ? "\n\t\t" : ' ' x (6 - length($hashtab[$i]))); } $f .= sprintf "\n" if ($nhash % 8 != 0); $f .= sprintf "\t};\n\n"; diff --git a/src/tools/RELEASE_CHANGES b/src/tools/RELEASE_CHANGES index 6ba9121e303a..5206640341d5 100644 --- a/src/tools/RELEASE_CHANGES +++ b/src/tools/RELEASE_CHANGES @@ -73,7 +73,7 @@ but there may be reasons to do them at other times as well. to lower numbers, using renumber_oids.pl (see notes in bki.sgml) * Update config.guess and config.sub - (from http://savannah.gnu.org/projects/config) + (from https://savannah.gnu.org/projects/config) * Update inet/cidr data types with newest Bind patches diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm index 0a9cc2750e97..97cc341f724b 100644 --- a/src/tools/msvc/Install.pm +++ b/src/tools/msvc/Install.pm @@ -382,7 +382,7 @@ sub GenerateTimezoneFiles print "Generating timezone files..."; my @args = ( - "$conf/zic/zic", '-d', "$target/share/timezone", '-b', 'slim'); + "$conf/zic/zic", '-d', "$target/share/timezone"); foreach (@tzfiles) { my $tzfile = $_; diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index a7e815fefa47..0393928efe2f 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -105,8 +105,8 @@ sub mkvcbuild pread.c pwrite.c pg_bitutils.c pg_strong_random.c pgcheckdir.c pgmkdirp.c pgsleep.c pgstrcasecmp.c pqsignal.c mkdtemp.c qsort.c qsort_arg.c quotes.c system.c - sprompt.c strerror.c tar.c thread.c - win32env.c win32error.c win32security.c win32setlocale.c); + strerror.c tar.c thread.c + win32env.c win32error.c win32security.c win32setlocale.c win32stat.c); push(@pgportfiles, 'strtof.c') if ($vsVersion < '14.00'); @@ -124,9 +124,9 @@ sub mkvcbuild our @pgcommonallfiles = qw( archive.c base64.c checksum_helper.c config_info.c controldata_utils.c d2s.c encnames.c exec.c - f2s.c file_perm.c hashfn.c ip.c jsonapi.c + f2s.c file_perm.c file_utils.c hashfn.c ip.c jsonapi.c keywords.c kwlookup.c link-canary.c md5.c - pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c + pg_get_line.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c saslprep.c scram-common.c string.c stringinfo.c unicode_norm.c username.c wait_error.c wchar.c); @@ -141,8 +141,8 @@ sub mkvcbuild } our @pgcommonfrontendfiles = ( - @pgcommonallfiles, qw(fe_memutils.c file_utils.c - logging.c restricted_token.c)); + @pgcommonallfiles, qw(fe_memutils.c + logging.c restricted_token.c sprompt.c)); our @pgcommonbkndfiles = @pgcommonallfiles; diff --git a/src/tools/pgindent/README b/src/tools/pgindent/README index 8eb15fafb996..d36f5088279c 100644 --- a/src/tools/pgindent/README +++ b/src/tools/pgindent/README @@ -101,6 +101,10 @@ the comment block with some dashes: Odd spacing around typedef names might indicate an incomplete typedefs list. +pgindent will mangle both declaration and definition of a C function whose +name matches a typedef. Currently the best workaround is to choose +non-conflicting names. + pgindent can get confused by #if sequences that look correct to the compiler but have mismatched braces/parentheses when considered as a whole. Usually that looks pretty unreadable to humans too, so best practice is to rearrange @@ -138,25 +142,11 @@ Which files are processed The pgindent run processes (nearly) all PostgreSQL *.c and *.h files, but we currently exclude *.y and *.l files, as well as *.c and *.h files derived from *.y and *.l files. Additional exceptions are listed -in exclude_file_patterns: - -src/include/storage/s_lock.h and src/include/port/atomics/ are excluded -because they contain assembly code that pgindent tends to mess up. - -src/backend/utils/fmgrtab.c is excluded because it confuses pgindent -and it's a derived file anyway. - -src/interfaces/ecpg/test/expected/ is excluded to avoid breaking the ecpg -regression tests, since what ecpg generates is not necessarily formatted -as pgindent would do it. (Note that we do not exclude ecpg's header files -from the run; some of them get copied verbatim into ecpg's output, meaning -that the expected files may need to be updated to match.) - -src/include/snowball/libstemmer/ and src/backend/snowball/libstemmer/ -are excluded because those files are imported from an external project, -not maintained locally, and are machine-generated anyway. Likewise for -plperl/ppport.h. +in exclude_file_patterns; see the notes therein for rationale. +Note that we do not exclude ecpg's header files from the run. Some of them +get copied verbatim into ecpg's output, meaning that ecpg's expected files +may need to be updated to match. The perltidy run processes all *.pl and *.pm files, plus a few executable Perl scripts that are not named that way. See the "find" diff --git a/src/tools/pgindent/exclude_file_patterns b/src/tools/pgindent/exclude_file_patterns index c8efc9a91310..f08180b0d089 100644 --- a/src/tools/pgindent/exclude_file_patterns +++ b/src/tools/pgindent/exclude_file_patterns @@ -1,10 +1,49 @@ -#list of file patterns to exclude from pgindent runs, see notes in README -/storage/s_lock\.h$ -/port/atomics/ -/utils/fmgrtab\.c$ -/ecpg/test/expected/ +# List of filename patterns to exclude from pgindent runs +# +# These contain assembly code that pgindent tends to mess up. +src/include/storage/s_lock\.h$ +src/include/port/atomics/ +# +# This contains C++ constructs that confuse pgindent. +src/include/jit/llvmjit\.h$ +# +# This confuses pgindent, and it's a derived file anyway. +src/backend/utils/fmgrtab\.c$ +# +# pgindent might mangle entries in this that match typedef names. +# Since it's a derived file anyway, just exclude it. +src/backend/utils/fmgrprotos\.h$ +# +# kwlist_d files are made by gen_keywordlist.pl. While we could insist that +# they match pgindent style, they'd look worse not better, so exclude them. +kwlist_d\.h$ +# +# These are generated by the scripts from src/common/unicode/. They use +# hash functions generated by PerfectHash.pm whose format looks worse with +# pgindent. +src/include/common/unicode_norm_hashfunc\.h$ +src/include/common/unicode_normprops_table\.h$ +# +# Exclude ecpg test files to avoid breaking the ecpg regression tests +# (but include files at the top level of the ecpg/test/ directory). +src/interfaces/ecpg/test/.*/ +# +# src/include/snowball/libstemmer/ and src/backend/snowball/libstemmer/ +# are excluded because those files are imported from an external project, +# rather than maintained locally, and they are machine-generated anyway. /snowball/libstemmer/ -/pl/plperl/ppport\.h$ -/jit/llvmjit\.h$ +# +# These files are machine-generated by code not under our control, +# so we shouldn't expect them to conform to our style. +# (Some versions of dtrace build probes.h files that confuse pgindent, too.) +src/backend/utils/probes\.h$ +src/include/pg_config\.h$ +src/pl/plperl/ppport\.h$ +src/pl/plperl/SPI\.c$ +src/pl/plperl/Util\.c$ +# +# Exclude any temporary installations that may be in the tree. /tmp_check/ /tmp_install/ +# ... and for paranoia's sake, don't touch git stuff. +/\.git/ diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent index 457e32882484..4124d27dea66 100755 --- a/src/tools/pgindent/pgindent +++ b/src/tools/pgindent/pgindent @@ -159,6 +159,7 @@ sub process_exclude while (my $line = <$eh>) { chomp $line; + next if $line =~ m/^#/; my $rgx = qr!$line!; @files = grep { $_ !~ /$rgx/ } @files if $rgx; } diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 13582442a1f3..62007ddb8019 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -111,6 +111,7 @@ Append AppendPath AppendRelInfo AppendState +ApplySubXactData Archive ArchiveEntryPtrType ArchiveFormat @@ -1018,6 +1019,7 @@ HbaToken HeadlineJsonState HeadlineParsedText HeadlineWordEntry +HeapCheckContext HeapScanDesc HeapTuple HeapTupleData @@ -1514,6 +1516,7 @@ PGEventResultCopy PGEventResultCreate PGEventResultDestroy PGFInfoFunction +PGFileType PGFunction PGLZ_HistEntry PGLZ_Strategy @@ -1830,7 +1833,9 @@ PgStat_MsgFuncstat PgStat_MsgHdr PgStat_MsgInquiry PgStat_MsgRecoveryConflict +PgStat_MsgReplSlot PgStat_MsgResetcounter +PgStat_MsgResetreplslotcounter PgStat_MsgResetsharedcounter PgStat_MsgResetsinglecounter PgStat_MsgResetslrucounter @@ -1839,6 +1844,8 @@ PgStat_MsgTabpurge PgStat_MsgTabstat PgStat_MsgTempFile PgStat_MsgVacuum +PgStat_MsgWal +PgStat_ReplSlotStats PgStat_SLRUStats PgStat_Shared_Reset_Target PgStat_Single_Reset_Type @@ -1850,6 +1857,7 @@ PgStat_TableCounts PgStat_TableEntry PgStat_TableStatus PgStat_TableXactStatus +PgStat_WalStats PgXmlErrorContext PgXmlStrictness Pg_finfo_record @@ -2283,6 +2291,7 @@ SimpleStringList SimpleStringListCell SingleBoundSortItem Size +SkipPages SlabBlock SlabChunk SlabContext @@ -2293,12 +2302,12 @@ SlotNumber SlruCtl SlruCtlData SlruErrorCause -SlruFlush -SlruFlushData SlruPageStatus SlruScanCallback SlruShared SlruSharedData +SlruWriteAll +SlruWriteAllData SnapBuild SnapBuildOnDisk SnapBuildState @@ -2371,6 +2380,7 @@ StopList StopWorkersData StrategyNumber StreamCtl +StreamXidHash StringInfo StringInfoData StripnullState @@ -2381,6 +2391,7 @@ SubPlanState SubTransactionId SubXactCallback SubXactCallbackItem +SubXactInfo SubXactEvent SubplanResultRelHashElem SubqueryScan @@ -2400,6 +2411,7 @@ Syn SyncOps SyncRepConfigData SyncRepStandbyData +SyncRequestHandler SyncRequestType SysScanDesc SyscacheCallbackFunction @@ -2781,6 +2793,8 @@ XactCallback XactCallbackItem XactEvent XactLockTableWaitInfo +XidBoundsViolation +XidCommitStatus XidHorizonPrefetchState XidStatus XmlExpr @@ -2899,6 +2913,8 @@ dlist_head dlist_iter dlist_mutable_iter dlist_node +do_collation_version_check_context +do_collation_version_update_context ds_state dsa_area dsa_area_control @@ -3181,6 +3197,7 @@ pg_tz pg_tz_cache pg_tzenum pg_unicode_decomposition +pg_unicode_norminfo pg_unicode_normprops pg_utf_to_local_combined pg_uuid_t @@ -3575,3 +3592,4 @@ yyscan_t z_stream z_streamp zic_t +HeapTupleForceOption diff --git a/src/tutorial/basics.source b/src/tutorial/basics.source index 9dbd75eb154b..fe1cdfde2a87 100644 --- a/src/tutorial/basics.source +++ b/src/tutorial/basics.source @@ -126,13 +126,13 @@ SELECT * FROM weather LEFT OUTER JOIN cities ON (weather.city = cities.name); -- Suppose we want to find all the records that are in the temperature range --- of other records. W1 and W2 are aliases for weather. +-- of other records. w1 and w2 are aliases for weather. -SELECT W1.city, W1.temp_lo, W1.temp_hi, - W2.city, W2.temp_lo, W2.temp_hi -FROM weather W1, weather W2 -WHERE W1.temp_lo < W2.temp_lo - and W1.temp_hi > W2.temp_hi; +SELECT w1.city, w1.temp_lo, w1.temp_hi, + w2.city, w2.temp_lo, w2.temp_hi +FROM weather w1, weather w2 +WHERE w1.temp_lo < w2.temp_lo + and w1.temp_hi > w2.temp_hi; ----------------------------- diff --git a/src/tutorial/complex.source b/src/tutorial/complex.source index 035592670162..d849ec0d4b70 100644 --- a/src/tutorial/complex.source +++ b/src/tutorial/complex.source @@ -111,7 +111,7 @@ CREATE FUNCTION complex_add(complex, complex) LANGUAGE C IMMUTABLE STRICT; -- we can now define the operator. We show a binary operator here but you --- can also define unary operators by omitting either of leftarg or rightarg. +-- can also define a prefix operator by omitting the leftarg. CREATE OPERATOR + ( leftarg = complex, rightarg = complex, diff --git a/src/tutorial/syscat.source b/src/tutorial/syscat.source index 3a1767f97be7..8a04d6a961f2 100644 --- a/src/tutorial/syscat.source +++ b/src/tutorial/syscat.source @@ -96,36 +96,22 @@ SELECT n.nspname, r.rolname, format_type(t.oid, null) as typname -- --- lists all left unary operators +-- lists all prefix operators -- -SELECT n.nspname, o.oprname AS left_unary, +SELECT n.nspname, o.oprname AS prefix_op, format_type(right_type.oid, null) AS operand, format_type(result.oid, null) AS return_type FROM pg_namespace n, pg_operator o, pg_type right_type, pg_type result WHERE o.oprnamespace = n.oid - and o.oprkind = 'l' -- left unary + and o.oprkind = 'l' -- prefix ("left unary") and o.oprright = right_type.oid and o.oprresult = result.oid ORDER BY nspname, operand; -- --- lists all right unary operators --- -SELECT n.nspname, o.oprname AS right_unary, - format_type(left_type.oid, null) AS operand, - format_type(result.oid, null) AS return_type - FROM pg_namespace n, pg_operator o, - pg_type left_type, pg_type result - WHERE o.oprnamespace = n.oid - and o.oprkind = 'r' -- right unary - and o.oprleft = left_type.oid - and o.oprresult = result.oid - ORDER BY nspname, operand; - --- --- lists all binary operators +-- lists all infix operators -- SELECT n.nspname, o.oprname AS binary_op, format_type(left_type.oid, null) AS left_opr, @@ -134,7 +120,7 @@ SELECT n.nspname, o.oprname AS binary_op, FROM pg_namespace n, pg_operator o, pg_type left_type, pg_type right_type, pg_type result WHERE o.oprnamespace = n.oid - and o.oprkind = 'b' -- binary + and o.oprkind = 'b' -- infix ("binary") and o.oprleft = left_type.oid and o.oprright = right_type.oid and o.oprresult = result.oid