From ee121e7005c0869a793127b6e4c3e31f36565f85 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 15 Sep 2023 17:01:26 -0400 Subject: [PATCH 01/22] Track nesting depth correctly when drilling down into RECORD Vars. expandRecordVariable() failed to adjust the parse nesting structure correctly when recursing to inspect an outer-level Var. This could result in assertion failures or core dumps in corner cases. Likewise, get_name_for_var_field() failed to adjust the deparse namespace stack correctly when recursing to inspect an outer-level Var. In this case the likely result was a "bogus varno" error while deparsing a view. Per bug #18077 from Jingzhou Fu. Back-patch to all supported branches. Richard Guo, with some adjustments by me Discussion: https://postgr.es/m/18077-b9db97c6e0ab45d8@postgresql.org --- src/backend/parser/parse_target.c | 20 ++++++--- src/backend/utils/adt/ruleutils.c | 37 +++++++++------- src/test/regress/expected/rowtypes.out | 60 ++++++++++++++++++++++++++ src/test/regress/sql/rowtypes.sql | 25 +++++++++++ 4 files changed, 120 insertions(+), 22 deletions(-) diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 039952aa8ae..274b38d2643 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1507,7 +1507,8 @@ ExpandRowReference(ParseState *pstate, Node *expr, * drill down to find the ultimate defining expression and attempt to infer * the tupdesc from it. We ereport if we can't determine the tupdesc. * - * levelsup is an extra offset to interpret the Var's varlevelsup correctly. + * levelsup is an extra offset to interpret the Var's varlevelsup correctly + * when recursing. Outside callers should pass zero. */ TupleDesc expandRecordVariable(ParseState *pstate, Var *var, int levelsup) @@ -1595,11 +1596,17 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) /* * Recurse into the sub-select to see what its Var refers * to. We have to build an additional level of ParseState - * to keep in step with varlevelsup in the subselect. + * to keep in step with varlevelsup in the subselect; + * furthermore, the subquery RTE might be from an outer + * query level, in which case the ParseState for the + * subselect must have that outer level as parent. */ - ParseState mypstate; + ParseState mypstate = {0}; + Index levelsup; - MemSet(&mypstate, 0, sizeof(mypstate)); + /* this loop must work, since GetRTEByRangeTablePosn did */ + for (levelsup = 0; levelsup < netlevelsup; levelsup++) + pstate = pstate->parentParseState; mypstate.parentParseState = pstate; mypstate.p_rtable = rte->subquery->rtable; /* don't bother filling the rest of the fake pstate */ @@ -1651,12 +1658,11 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) * Recurse into the CTE to see what its Var refers to. We * have to build an additional level of ParseState to keep * in step with varlevelsup in the CTE; furthermore it - * could be an outer CTE. + * could be an outer CTE (compare SUBQUERY case above). */ - ParseState mypstate; + ParseState mypstate = {0}; Index levelsup; - MemSet(&mypstate, 0, sizeof(mypstate)); /* this loop must work, since GetCTEForRTE did */ for (levelsup = 0; levelsup < rte->ctelevelsup + netlevelsup; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 186cb8fed16..89d4d91c6c1 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7727,22 +7727,28 @@ get_name_for_var_field(Var *var, int fieldno, * Recurse into the sub-select to see what its Var * refers to. We have to build an additional level of * namespace to keep in step with varlevelsup in the - * subselect. + * subselect; furthermore, the subquery RTE might be + * from an outer query level, in which case the + * namespace for the subselect must have that outer + * level as parent namespace. */ + List *save_nslist = context->namespaces; + List *parent_namespaces; deparse_namespace mydpns; const char *result; + parent_namespaces = list_copy_tail(context->namespaces, + netlevelsup); + set_deparse_for_query(&mydpns, rte->subquery, - context->namespaces); + parent_namespaces); - context->namespaces = lcons(&mydpns, - context->namespaces); + context->namespaces = lcons(&mydpns, parent_namespaces); result = get_name_for_var_field((Var *) expr, fieldno, 0, context); - context->namespaces = - list_delete_first(context->namespaces); + context->namespaces = save_nslist; return result; } @@ -7835,7 +7841,7 @@ get_name_for_var_field(Var *var, int fieldno, 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)) @@ -7843,21 +7849,22 @@ get_name_for_var_field(Var *var, int fieldno, /* * Recurse into the CTE to see what its Var refers to. * We have to build an additional level of namespace - * to keep in step with varlevelsup in the CTE. - * Furthermore it could be an outer CTE, so we may - * have to delete some levels of namespace. + * to keep in step with varlevelsup in the CTE; + * furthermore it could be an outer CTE (compare + * SUBQUERY case above). */ List *save_nslist = context->namespaces; - List *new_nslist; + List *parent_namespaces; deparse_namespace mydpns; const char *result; + parent_namespaces = list_copy_tail(context->namespaces, + ctelevelsup); + set_deparse_for_query(&mydpns, ctequery, - context->namespaces); + parent_namespaces); - new_nslist = list_copy_tail(context->namespaces, - ctelevelsup); - context->namespaces = lcons(&mydpns, new_nslist); + context->namespaces = lcons(&mydpns, parent_namespaces); result = get_name_for_var_field((Var *) expr, fieldno, 0, context); diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 03dc2ab3a79..2c85583cce0 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -1234,6 +1234,66 @@ select r, r is null as isnull, r is not null as isnotnull from r; (,) | t | f (6 rows) +-- +-- Check parsing of indirect references to composite values (bug #18077) +-- +explain (verbose, costs off) +with cte(c) as materialized (select row(1, 2)), + cte2(c) as (select * from cte) +select * from cte2 as t +where (select * from (select c as c1) s + where (select (c1).f1 > 0)) is not null; + QUERY PLAN +-------------------------------------------- + CTE Scan on cte + Output: cte.c + Filter: ((SubPlan 3) IS NOT NULL) + CTE cte + -> Result + Output: '(1,2)'::record + SubPlan 3 + -> Result + Output: cte.c + One-Time Filter: $2 + InitPlan 2 (returns $2) + -> Result + Output: ((cte.c).f1 > 0) +(13 rows) + +with cte(c) as materialized (select row(1, 2)), + cte2(c) as (select * from cte) +select * from cte2 as t +where (select * from (select c as c1) s + where (select (c1).f1 > 0)) is not null; + c +------- + (1,2) +(1 row) + +-- Also check deparsing of such cases +create view composite_v as +with cte(c) as materialized (select row(1, 2)), + cte2(c) as (select * from cte) +select 1 as one from cte2 as t +where (select * from (select c as c1) s + where (select (c1).f1 > 0)) is not null; +select pg_get_viewdef('composite_v', true); + pg_get_viewdef +-------------------------------------------------------- + WITH cte(c) AS MATERIALIZED ( + + SELECT ROW(1, 2) AS "row" + + ), cte2(c) AS ( + + SELECT cte.c + + FROM cte + + ) + + SELECT 1 AS one + + FROM cte2 t + + WHERE (( SELECT s.c1 + + FROM ( SELECT t.c AS c1) s + + WHERE ( SELECT (s.c1).f1 > 0))) IS NOT NULL; +(1 row) + +drop view composite_v; -- -- Tests for component access / FieldSelect -- diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index 54f338af1f8..50e0ca6cd6b 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -501,6 +501,31 @@ with r(a,b) as materialized (null,row(1,2)), (null,row(null,null)), (null,null) ) select r, r is null as isnull, r is not null as isnotnull from r; +-- +-- Check parsing of indirect references to composite values (bug #18077) +-- +explain (verbose, costs off) +with cte(c) as materialized (select row(1, 2)), + cte2(c) as (select * from cte) +select * from cte2 as t +where (select * from (select c as c1) s + where (select (c1).f1 > 0)) is not null; + +with cte(c) as materialized (select row(1, 2)), + cte2(c) as (select * from cte) +select * from cte2 as t +where (select * from (select c as c1) s + where (select (c1).f1 > 0)) is not null; + +-- Also check deparsing of such cases +create view composite_v as +with cte(c) as materialized (select row(1, 2)), + cte2(c) as (select * from cte) +select 1 as one from cte2 as t +where (select * from (select c as c1) s + where (select (c1).f1 > 0)) is not null; +select pg_get_viewdef('composite_v', true); +drop view composite_v; -- -- Tests for component access / FieldSelect From a8f336c8146b9d8f558a9fbda7691c509fa4b77b Mon Sep 17 00:00:00 2001 From: Etsuro Fujita Date: Thu, 21 Sep 2023 19:45:05 +0900 Subject: [PATCH 02/22] Update comment about set_join_pathlist_hook(). The comment introduced by commit e7cb7ee14 was a bit too terse, which could lead to extensions doing different things within the hook function than we intend to allow. Extend the comment to explain what they can do within the hook function. Back-patch to all supported branches. In passing, I rephrased a nearby comment that I recently added to the back branches. Reviewed by David Rowley and Andrei Lepikhov. Discussion: https://postgr.es/m/CAPmGK15SBPA1nr3Aqsdm%2BYyS-ay0Ayo2BRYQ8_A2To9eLqwopQ%40mail.gmail.com --- src/backend/optimizer/path/joinpath.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index b5e7ef3b60c..5defbdd3613 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -367,6 +367,18 @@ add_paths_to_join_relation(PlannerInfo *root, hash_inner_and_outer(root, joinrel, outerrel, innerrel, jointype, &extra); + /* + * createplan.c does not currently support handling of pseudoconstant + * clauses assigned to joins pushed down by extensions; check if the + * restrictlist has such clauses, and if not, allow them to consider + * pushing down joins. + */ + if ((joinrel->fdwroutine && + joinrel->fdwroutine->GetForeignJoinPaths) || + set_join_pathlist_hook) + consider_join_pushdown = !has_pseudoconstant_clauses(root, + restrictlist); + /* * 5. If inner and outer relations are foreign tables (or joins) belonging * to the same server and assigned to the same user to check access @@ -424,7 +436,10 @@ add_paths_to_join_relation(PlannerInfo *root, } /* - * 6. Finally, give extensions a chance to manipulate the path list. + * 6. Finally, give extensions a chance to manipulate the path list. They + * could add new paths (such as CustomPaths) by calling add_path(), or + * add_partial_path() if parallel aware. They could also delete or modify + * paths added by the core code. */ if (set_join_pathlist_hook) set_join_pathlist_hook(root, joinrel, outerrel, innerrel, From 238d397d908680a9b6399cf6a24350f836a9d4c3 Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Sat, 23 Sep 2023 10:26:24 +1200 Subject: [PATCH 03/22] Don't trust unvalidated xl_tot_len. xl_tot_len comes first in a WAL record. Usually we don't trust it to be the true length until we've validated the record header. If the record header was split across two pages, previously we wouldn't do the validation until after we'd already tried to allocate enough memory to hold the record, which was bad because it might actually be garbage bytes from a recycled WAL file, so we could try to allocate a lot of memory. Release 15 made it worse. Since 70b4f82a4b5, we'd at least generate an end-of-WAL condition if the garbage 4 byte value happened to be > 1GB, but we'd still try to allocate up to 1GB of memory bogusly otherwise. That was an improvement, but unfortunately release 15 tries to allocate another object before that, so you could get a FATAL error and recovery could fail. We can fix both variants of the problem more fundamentally using pre-existing page-level validation, if we just re-order some logic. The new order of operations in the split-header case defers all memory allocation based on xl_tot_len until we've read the following page. At that point we know that its first few bytes are not recycled data, by checking its xlp_pageaddr, and that its xlp_rem_len agrees with xl_tot_len on the preceding page. That is strong evidence that xl_tot_len was truly the start of a record that was logged. This problem was most likely to occur on a standby, because walreceiver.c recycles WAL files without zeroing out trailing regions of each page. We could fix that too, but it wouldn't protect us from rare crash scenarios where the trailing zeroes don't make it to disk. With reliable xl_tot_len validation in place, the ancient policy of considering malloc failure to indicate corruption at end-of-WAL seems quite surprising, but changing that is left for later work. Also included is a new TAP test to exercise various cases of end-of-WAL detection by writing contrived data into the WAL from Perl. Back-patch to 12. We decided not to put this change into the final release of 11. Author: Thomas Munro Author: Michael Paquier Reported-by: Alexander Lakhin Reviewed-by: Noah Misch (the idea, not the code) Reviewed-by: Michael Paquier Reviewed-by: Sergei Kornilov Reviewed-by: Alexander Lakhin Discussion: https://postgr.es/m/17928-aa92416a70ff44a2%40postgresql.org --- src/backend/access/transam/xlogreader.c | 75 ++-- src/test/perl/TestLib.pm | 41 +++ src/test/recovery/t/039_end_of_wal.pl | 466 ++++++++++++++++++++++++ 3 files changed, 544 insertions(+), 38 deletions(-) create mode 100644 src/test/recovery/t/039_end_of_wal.pl diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index ba34d5ca6fa..67e6b9cf717 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -176,6 +176,9 @@ XLogReaderFree(XLogReaderState *state) * XLOG_BLCKSZ, and make sure it's at least 5*Max(BLCKSZ, XLOG_BLCKSZ) to start * with. (That is enough for all "normal" records, but very large commit or * abort records might need more space.) + * + * Note: This routine should *never* be called for xl_tot_len until the header + * of the record has been fully validated. */ static bool allocate_recordbuf(XLogReaderState *state, uint32 reclength) @@ -185,25 +188,6 @@ allocate_recordbuf(XLogReaderState *state, uint32 reclength) newSize += XLOG_BLCKSZ - (newSize % XLOG_BLCKSZ); newSize = Max(newSize, 5 * Max(BLCKSZ, XLOG_BLCKSZ)); -#ifndef FRONTEND - - /* - * Note that in much unlucky circumstances, the random data read from a - * recycled segment can cause this routine to be called with a size - * causing a hard failure at allocation. For a standby, this would cause - * the instance to stop suddenly with a hard failure, preventing it to - * retry fetching WAL from one of its sources which could allow it to move - * on with replay without a manual restart. If the data comes from a past - * recycled segment and is still valid, then the allocation may succeed - * but record checks are going to fail so this would be short-lived. If - * the allocation fails because of a memory shortage, then this is not a - * hard failure either per the guarantee given by MCXT_ALLOC_NO_OOM. - */ - if (!AllocSizeIsValid(newSize)) - return false; - -#endif - if (state->readRecordBuf) pfree(state->readRecordBuf); state->readRecordBuf = @@ -404,15 +388,7 @@ XLogReadRecord(XLogReaderState *state, char **errormsg) } else { - /* XXX: more validation should be done here */ - if (total_len < SizeOfXLogRecord) - { - report_invalid_record(state, - "invalid record length at %X/%X: wanted %u, got %u", - LSN_FORMAT_ARGS(RecPtr), - (uint32) SizeOfXLogRecord, total_len); - goto err; - } + /* We'll validate the header once we have the next page. */ gotheader = false; } @@ -428,16 +404,11 @@ XLogReadRecord(XLogReaderState *state, char **errormsg) assembled = true; /* - * Enlarge readRecordBuf as needed. + * We always have space for a couple of pages, enough to validate a + * boundary-spanning record header. */ - if (total_len > state->readRecordBufSize && - !allocate_recordbuf(state, total_len)) - { - /* We treat this as a "bogus data" condition */ - report_invalid_record(state, "record length %u at %X/%X too long", - total_len, LSN_FORMAT_ARGS(RecPtr)); - goto err; - } + Assert(state->readRecordBufSize >= XLOG_BLCKSZ * 2); + Assert(state->readRecordBufSize >= len); /* Copy the first fragment of the record from the first page. */ memcpy(state->readRecordBuf, @@ -533,8 +504,36 @@ XLogReadRecord(XLogReaderState *state, char **errormsg) goto err; gotheader = true; } - } while (gotlen < total_len); + /* + * We might need a bigger buffer. We have validated the record + * header, in the case that it split over a page boundary. We've + * also cross-checked total_len against xlp_rem_len on the second + * page, and verified xlp_pageaddr on both. + */ + Assert(gotheader); + if (total_len > state->readRecordBufSize) + { + char save_copy[XLOG_BLCKSZ * 2]; + + /* + * Save and restore the data we already had. It can't be more + * than two pages. + */ + Assert(gotlen <= lengthof(save_copy)); + Assert(gotlen <= state->readRecordBufSize); + memcpy(save_copy, state->readRecordBuf, gotlen); + if (!allocate_recordbuf(state, total_len)) + { + /* We treat this as a "bogus data" condition */ + report_invalid_record(state, "record length %u at %X/%X too long", + total_len, LSN_FORMAT_ARGS(RecPtr)); + goto err; + } + memcpy(state->readRecordBuf, save_copy, gotlen); + buffer = state->readRecordBuf + gotlen; + } + } while (gotlen < total_len); Assert(gotheader); record = (XLogRecord *) state->readRecordBuf; diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm index 7860425b543..1aed8d4c213 100644 --- a/src/test/perl/TestLib.pm +++ b/src/test/perl/TestLib.pm @@ -70,6 +70,7 @@ our @EXPORT = qw( chmod_recursive check_pg_config dir_symlink + scan_server_header system_or_bail system_log run_log @@ -648,6 +649,46 @@ sub chmod_recursive =pod +=item scan_server_header(header_path, regexp) + +Returns an array that stores all the matches of the given regular expression +within the PostgreSQL installation's C. This can be used to +retrieve specific value patterns from the installation's header files. + +=cut + +sub scan_server_header +{ + my ($header_path, $regexp) = @_; + + my ($stdout, $stderr); + my $result = IPC::Run::run [ 'pg_config', '--includedir-server' ], '>', + \$stdout, '2>', \$stderr + or die "could not execute pg_config"; + chomp($stdout); + $stdout =~ s/\r$//; + + open my $header_h, '<', "$stdout/$header_path" or die "$!"; + + my @match = undef; + while (<$header_h>) + { + my $line = $_; + + if (@match = $line =~ /^$regexp/) + { + last; + } + } + + close $header_h; + die "could not find match in header $header_path\n" + unless @match; + return @match; +} + +=pod + =item check_pg_config(regexp) Return the number of matches of the given regular expression diff --git a/src/test/recovery/t/039_end_of_wal.pl b/src/test/recovery/t/039_end_of_wal.pl new file mode 100644 index 00000000000..00a669c77d9 --- /dev/null +++ b/src/test/recovery/t/039_end_of_wal.pl @@ -0,0 +1,466 @@ +# Copyright (c) 2023, PostgreSQL Global Development Group +# +# Test detecting end-of-WAL conditions. This test suite generates +# fake defective page and record headers to trigger various failure +# scenarios. + +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; +use Fcntl qw(SEEK_SET); + +use integer; # causes / operator to use integer math + +# Header size of record header. +my $RECORD_HEADER_SIZE = 24; + +# Fields retrieved from code headers. +my @scan_result = scan_server_header('access/xlog_internal.h', + '#define\s+XLOG_PAGE_MAGIC\s+(\w+)'); +my $XLP_PAGE_MAGIC = hex($scan_result[0]); +@scan_result = scan_server_header('access/xlog_internal.h', + '#define\s+XLP_FIRST_IS_CONTRECORD\s+(\w+)'); +my $XLP_FIRST_IS_CONTRECORD = hex($scan_result[0]); + +# Values queried from the server +my $WAL_SEGMENT_SIZE; +my $WAL_BLOCK_SIZE; +my $TLI; + +# Build path of a WAL segment. +sub wal_segment_path +{ + my $node = shift; + my $tli = shift; + my $segment = shift; + my $wal_path = + sprintf("%s/pg_wal/%08X%08X%08X", $node->data_dir, $tli, 0, $segment); + return $wal_path; +} + +# Calculate from a LSN (in bytes) its segment number and its offset. +sub lsn_to_segment_and_offset +{ + my $lsn = shift; + return ($lsn / $WAL_SEGMENT_SIZE, $lsn % $WAL_SEGMENT_SIZE); +} + +# Write some arbitrary data in WAL for the given segment at LSN. +# This should be called while the cluster is not running. +sub write_wal +{ + my $node = shift; + my $tli = shift; + my $lsn = shift; + my $data = shift; + + my ($segment, $offset) = lsn_to_segment_and_offset($lsn); + my $path = wal_segment_path($node, $tli, $segment); + + open my $fh, "+<:raw", $path or die; + seek($fh, $offset, SEEK_SET) or die; + print $fh $data; + close $fh; +} + +sub format_lsn +{ + my $lsn = shift; + return sprintf("%X/%X", $lsn >> 32, $lsn & 0xffffffff); +} + +# Emit a WAL record of arbitrary size. Returns the end LSN of the +# record inserted, in bytes. +sub emit_message +{ + my $node = shift; + my $size = shift; + return int( + $node->safe_psql( + 'postgres', + "SELECT pg_logical_emit_message(true, '', repeat('a', $size)) - '0/0'" + )); +} + +# Get the current insert LSN of a node, in bytes. +sub get_insert_lsn +{ + my $node = shift; + return int( + $node->safe_psql( + 'postgres', "SELECT pg_current_wal_insert_lsn() - '0/0'")); +} + +# Get GUC value, converted to an int. +sub get_int_setting +{ + my $node = shift; + my $name = shift; + return int( + $node->safe_psql( + 'postgres', + "SELECT setting FROM pg_settings WHERE name = '$name'")); +} + +sub start_of_page +{ + my $lsn = shift; + return $lsn & ~($WAL_BLOCK_SIZE - 1); +} + +sub start_of_next_page +{ + my $lsn = shift; + return start_of_page($lsn) + $WAL_BLOCK_SIZE; +} + +# Build a fake WAL record header based on the data given by the caller. +# This needs to follow the format of the C structure XLogRecord. To +# be inserted with write_wal(). +sub build_record_header +{ + my $xl_tot_len = shift; + my $xl_xid = shift || 0; + my $xl_prev = shift || 0; + my $xl_info = shift || 0; + my $xl_rmid = shift || 0; + my $xl_crc = shift || 0; + + # This needs to follow the structure XLogRecord: + # I for xl_tot_len + # I for xl_xid + # Q for xl_prev + # C for xl_info + # C for xl_rmid + # BB for two bytes of padding + # I for xl_crc + return pack("IIQCCBBI", + $xl_tot_len, $xl_xid, $xl_prev, $xl_info, $xl_rmid, 0, 0, $xl_crc); +} + +# Build a fake WAL page header, based on the data given by the caller +# This needs to follow the format of the C structure XLogPageHeaderData. +# To be inserted with write_wal(). +sub build_page_header +{ + my $xlp_magic = shift; + my $xlp_info = shift || 0; + my $xlp_tli = shift || 0; + my $xlp_pageaddr = shift || 0; + my $xlp_rem_len = shift || 0; + + # This needs to follow the structure XLogPageHeaderData: + # S for xlp_magic + # S for xlp_info + # I for xlp_tli + # Q for xlp_pageaddr + # I for xlp_rem_len + return pack("SSIQI", + $xlp_magic, $xlp_info, $xlp_tli, $xlp_pageaddr, $xlp_rem_len); +} + +# Make sure we are far away enough from the end of a page that we could insert +# a couple of small records. This inserts a few records of a fixed size, until +# the threshold gets close enough to the end of the WAL page inserting records +# to. +sub advance_out_of_record_splitting_zone +{ + my $node = shift; + + my $page_threshold = 2000; + my $end_lsn = get_insert_lsn($node); + my $page_offset = $end_lsn % $WAL_BLOCK_SIZE; + while ($page_offset >= $WAL_BLOCK_SIZE - $page_threshold) + { + emit_message($node, $page_threshold); + $end_lsn = get_insert_lsn($node); + $page_offset = $end_lsn % $WAL_BLOCK_SIZE; + } + return $end_lsn; +} + +# Advance so close to the end of a page that an XLogRecordHeader would not +# fit on it. +sub advance_to_record_splitting_zone +{ + my $node = shift; + + my $end_lsn = get_insert_lsn($node); + my $page_offset = $end_lsn % $WAL_BLOCK_SIZE; + + # Get fairly close to the end of a page in big steps + while ($page_offset <= $WAL_BLOCK_SIZE - 512) + { + emit_message($node, $WAL_BLOCK_SIZE - $page_offset - 256); + $end_lsn = get_insert_lsn($node); + $page_offset = $end_lsn % $WAL_BLOCK_SIZE; + } + + # Calibrate our message size so that we can get closer 8 bytes at + # a time. + my $message_size = $WAL_BLOCK_SIZE - 80; + while ($page_offset <= $WAL_BLOCK_SIZE - $RECORD_HEADER_SIZE) + { + emit_message($node, $message_size); + $end_lsn = get_insert_lsn($node); + + my $old_offset = $page_offset; + $page_offset = $end_lsn % $WAL_BLOCK_SIZE; + + # Adjust the message size until it causes 8 bytes changes in + # offset, enough to be able to split a record header. + my $delta = $page_offset - $old_offset; + if ($delta > 8) + { + $message_size -= 8; + } + elsif ($delta <= 0) + { + $message_size += 8; + } + } + return $end_lsn; +} + +# Setup a new node. The configuration chosen here minimizes the number +# of arbitrary records that could get generated in a cluster. Enlarging +# checkpoint_timeout avoids noise with checkpoint activity. wal_level +# set to "minimal" avoids random standby snapshot records. Autovacuum +# could also trigger randomly, generating random WAL activity of its own. +my $node = PostgreSQL::Test::Cluster->new("node"); +$node->init; +$node->append_conf( + 'postgresql.conf', + q[wal_level = minimal + autovacuum = off + checkpoint_timeout = '30min' +]); +$node->start; +$node->safe_psql('postgres', "CREATE TABLE t AS SELECT 42"); + +$WAL_SEGMENT_SIZE = get_int_setting($node, 'wal_segment_size'); +$WAL_BLOCK_SIZE = get_int_setting($node, 'wal_block_size'); +$TLI = $node->safe_psql('postgres', + "SELECT timeline_id FROM pg_control_checkpoint();"); + +my $end_lsn; +my $prev_lsn; + +########################################################################### +note "Single-page end-of-WAL detection"; +########################################################################### + +# xl_tot_len is 0 (a common case, we hit trailing zeroes). +emit_message($node, 0); +$end_lsn = advance_out_of_record_splitting_zone($node); +$node->stop('immediate'); +my $log_size = -s $node->logfile; +$node->start; +ok( $node->log_contains( + "invalid record length at .*: wanted 24, got 0", $log_size + ), + "xl_tot_len zero"); + +# xl_tot_len is < 24 (presumably recycled garbage). +emit_message($node, 0); +$end_lsn = advance_out_of_record_splitting_zone($node); +$node->stop('immediate'); +write_wal($node, $TLI, $end_lsn, build_record_header(23)); +$log_size = -s $node->logfile; +$node->start; +ok( $node->log_contains( + "invalid record length at .*: wanted 24, got 23", + $log_size), + "xl_tot_len short"); + +# Need more pages, but xl_prev check fails first. +emit_message($node, 0); +$end_lsn = advance_out_of_record_splitting_zone($node); +$node->stop('immediate'); +write_wal($node, $TLI, $end_lsn, + build_record_header(2 * 1024 * 1024 * 1024, 0, 0xdeadbeef)); +$log_size = -s $node->logfile; +$node->start; +ok( $node->log_contains( + "record with incorrect prev-link 0/DEADBEEF at .*", $log_size), + "xl_prev bad"); + +# xl_crc check fails. +emit_message($node, 0); +advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 10); +$node->stop('immediate'); +# Corrupt a byte in that record, breaking its CRC. +write_wal($node, $TLI, $end_lsn - 8, '!'); +$log_size = -s $node->logfile; +$node->start; +ok( $node->log_contains( + "incorrect resource manager data checksum in record at .*", $log_size + ), + "xl_crc bad"); + + +########################################################################### +note "Multi-page end-of-WAL detection, header is not split"; +########################################################################### + +# This series of tests requires a valid xl_prev set in the record header +# written to WAL. + +# Good xl_prev, we hit zero page next (zero magic). +emit_message($node, 0); +$prev_lsn = advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 0); +$node->stop('immediate'); +write_wal($node, $TLI, $end_lsn, + build_record_header(2 * 1024 * 1024 * 1024, 0, $prev_lsn)); +$log_size = -s $node->logfile; +$node->start; +ok($node->log_contains("invalid magic number 0000 ", $log_size), + "xlp_magic zero"); + +# Good xl_prev, we hit garbage page next (bad magic). +emit_message($node, 0); +$prev_lsn = advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 0); +$node->stop('immediate'); +write_wal($node, $TLI, $end_lsn, + build_record_header(2 * 1024 * 1024 * 1024, 0, $prev_lsn)); +write_wal( + $node, $TLI, + start_of_next_page($end_lsn), + build_page_header(0xcafe, 0, 1, 0)); +$log_size = -s $node->logfile; +$node->start; +ok($node->log_contains("invalid magic number CAFE ", $log_size), + "xlp_magic bad"); + +# Good xl_prev, we hit typical recycled page (good xlp_magic, bad +# xlp_pageaddr). +emit_message($node, 0); +$prev_lsn = advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 0); +$node->stop('immediate'); +write_wal($node, $TLI, $end_lsn, + build_record_header(2 * 1024 * 1024 * 1024, 0, $prev_lsn)); +write_wal( + $node, $TLI, + start_of_next_page($end_lsn), + build_page_header($XLP_PAGE_MAGIC, 0, 1, 0xbaaaaaad)); +$log_size = -s $node->logfile; +$node->start; +ok( $node->log_contains( + "unexpected pageaddr 0/BAAAAAAD ", $log_size), + "xlp_pageaddr bad"); + +# Good xl_prev, xlp_magic, xlp_pageaddr, but bogus xlp_info. +emit_message($node, 0); +$prev_lsn = advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 0); +$node->stop('immediate'); +write_wal($node, $TLI, $end_lsn, + build_record_header(2 * 1024 * 1024 * 1024, 42, $prev_lsn)); +write_wal( + $node, $TLI, + start_of_next_page($end_lsn), + build_page_header( + $XLP_PAGE_MAGIC, 0x1234, 1, start_of_next_page($end_lsn))); +$log_size = -s $node->logfile; +$node->start; +ok($node->log_contains("invalid info bits 1234 ", $log_size), + "xlp_info bad"); + +# Good xl_prev, xlp_magic, xlp_pageaddr, but xlp_info doesn't mention +# continuation record. +emit_message($node, 0); +$prev_lsn = advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 0); +$node->stop('immediate'); +write_wal($node, $TLI, $end_lsn, + build_record_header(2 * 1024 * 1024 * 1024, 42, $prev_lsn)); +write_wal( + $node, $TLI, + start_of_next_page($end_lsn), + build_page_header($XLP_PAGE_MAGIC, 0, 1, start_of_next_page($end_lsn))); +$log_size = -s $node->logfile; +$node->start; +ok($node->log_contains("there is no contrecord flag at .*", $log_size), + "xlp_info lacks XLP_FIRST_IS_CONTRECORD"); + +# Good xl_prev, xlp_magic, xlp_pageaddr, xlp_info but xlp_rem_len doesn't add +# up. +emit_message($node, 0); +$prev_lsn = advance_out_of_record_splitting_zone($node); +$end_lsn = emit_message($node, 0); +$node->stop('immediate'); +write_wal($node, $TLI, $end_lsn, + build_record_header(2 * 1024 * 1024 * 1024, 42, $prev_lsn)); +write_wal( + $node, $TLI, + start_of_next_page($end_lsn), + build_page_header( + $XLP_PAGE_MAGIC, $XLP_FIRST_IS_CONTRECORD, + 1, start_of_next_page($end_lsn), + 123456)); +$log_size = -s $node->logfile; +$node->start; +ok( $node->log_contains( + "invalid contrecord length 123456 .* at .*", $log_size), + "xlp_rem_len bad"); + + +########################################################################### +note "Multi-page, but header is split, so page checks are done first"; +########################################################################### + +# xl_prev is bad and xl_tot_len is too big, but we'll check xlp_magic first. +emit_message($node, 0); +$end_lsn = advance_to_record_splitting_zone($node); +$node->stop('immediate'); +write_wal($node, $TLI, $end_lsn, + build_record_header(2 * 1024 * 1024 * 1024, 0, 0xdeadbeef)); +$log_size = -s $node->logfile; +$node->start; +ok($node->log_contains("invalid magic number 0000 ", $log_size), + "xlp_magic zero (split record header)"); + +# And we'll also check xlp_pageaddr before any header checks. +emit_message($node, 0); +$end_lsn = advance_to_record_splitting_zone($node); +$node->stop('immediate'); +write_wal($node, $TLI, $end_lsn, + build_record_header(2 * 1024 * 1024 * 1024, 0, 0xdeadbeef)); +write_wal( + $node, $TLI, + start_of_next_page($end_lsn), + build_page_header( + $XLP_PAGE_MAGIC, $XLP_FIRST_IS_CONTRECORD, 1, 0xbaaaaaad)); +$log_size = -s $node->logfile; +$node->start; +ok( $node->log_contains( + "unexpected pageaddr 0/BAAAAAAD ", $log_size), + "xlp_pageaddr bad (split record header)"); + +# We'll also discover that xlp_rem_len doesn't add up before any +# header checks, +emit_message($node, 0); +$end_lsn = advance_to_record_splitting_zone($node); +$node->stop('immediate'); +write_wal($node, $TLI, $end_lsn, + build_record_header(2 * 1024 * 1024 * 1024, 0, 0xdeadbeef)); +write_wal( + $node, $TLI, + start_of_next_page($end_lsn), + build_page_header( + $XLP_PAGE_MAGIC, $XLP_FIRST_IS_CONTRECORD, + 1, start_of_next_page($end_lsn), + 123456)); +$log_size = -s $node->logfile; +$node->start; +ok( $node->log_contains( + "invalid contrecord length 123456 .* at .*", $log_size), + "xlp_rem_len bad (split record header)"); + +done_testing(); From 10cbbb487a36bad719af1445d48c659519a24240 Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Sat, 23 Sep 2023 14:13:06 +1200 Subject: [PATCH 04/22] Don't use Perl pack('Q') in 039_end_of_wal.pl. 'Q' for 64 bit integers turns out not to work on 32 bit Perl, as revealed by the build farm. Use 'II' instead, and deal with endianness. Back-patch to 12, like bae868ca. Discussion: https://postgr.es/m/ZQ4r1vHcryBsSi_V%40paquier.xyz --- src/test/recovery/t/039_end_of_wal.pl | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/test/recovery/t/039_end_of_wal.pl b/src/test/recovery/t/039_end_of_wal.pl index 00a669c77d9..1eb65c07654 100644 --- a/src/test/recovery/t/039_end_of_wal.pl +++ b/src/test/recovery/t/039_end_of_wal.pl @@ -13,6 +13,13 @@ use integer; # causes / operator to use integer math +# Is this a big-endian system ("network" byte order)? We can't use 'Q' in +# pack() calls because it's not available in some perl builds, so we need to +# break 64 bit LSN values into two 'I' values. Fortunately we don't need to +# deal with high values, so we can just write 0 for the high order 32 bits, but +# we need to know the endianness to do that. +my $BIG_ENDIAN = pack("L", 0x12345678) eq pack("N", 0x12345678); + # Header size of record header. my $RECORD_HEADER_SIZE = 24; @@ -131,13 +138,16 @@ sub build_record_header # This needs to follow the structure XLogRecord: # I for xl_tot_len # I for xl_xid - # Q for xl_prev + # II for xl_prev # C for xl_info # C for xl_rmid # BB for two bytes of padding # I for xl_crc - return pack("IIQCCBBI", - $xl_tot_len, $xl_xid, $xl_prev, $xl_info, $xl_rmid, 0, 0, $xl_crc); + return pack("IIIICCBBI", + $xl_tot_len, $xl_xid, + $BIG_ENDIAN ? 0 : $xl_prev, + $BIG_ENDIAN ? $xl_prev : 0, + $xl_info, $xl_rmid, 0, 0, $xl_crc); } # Build a fake WAL page header, based on the data given by the caller @@ -155,10 +165,12 @@ sub build_page_header # S for xlp_magic # S for xlp_info # I for xlp_tli - # Q for xlp_pageaddr + # II for xlp_pageaddr # I for xlp_rem_len - return pack("SSIQI", - $xlp_magic, $xlp_info, $xlp_tli, $xlp_pageaddr, $xlp_rem_len); + return pack("SSIIII", + $xlp_magic, $xlp_info, $xlp_tli, + $BIG_ENDIAN ? 0 : $xlp_pageaddr, + $BIG_ENDIAN ? $xlp_pageaddr : 0, $xlp_rem_len); } # Make sure we are far away enough from the end of a page that we could insert From dac28dca2563428dde0902071d0e9535f67776c5 Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Tue, 26 Sep 2023 09:07:26 +1300 Subject: [PATCH 05/22] Fix edge-case for xl_tot_len broken by bae868ca. bae868ca removed a check that was still needed. If you had an xl_tot_len at the end of a page that was too small for a record header, but not big enough to span onto the next page, we'd immediately perform the CRC check using a bogus large length. Because of arbitrary coding differences between the CRC implementations on different platforms, nothing very bad happened on common modern systems. On systems using the _sb8.c fallback we could segfault. Restore that check, add a new assertion and supply a test for that case. Back-patch to 12, like bae868ca. Tested-by: Tom Lane Tested-by: Alexander Lakhin Discussion: https://postgr.es/m/CA%2BhUKGLCkTT7zYjzOxuLGahBdQ%3DMcF%3Dz5ZvrjSOnW4EDhVjT-g%40mail.gmail.com --- src/backend/access/transam/xlogreader.c | 11 +++++++++++ src/test/recovery/t/039_end_of_wal.pl | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index 67e6b9cf717..af5e8508bf1 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -388,6 +388,15 @@ XLogReadRecord(XLogReaderState *state, char **errormsg) } else { + /* There may be no next page if it's too small. */ + if (total_len < SizeOfXLogRecord) + { + report_invalid_record(state, + "invalid record length at %X/%X: wanted %u, got %u", + LSN_FORMAT_ARGS(RecPtr), + (uint32) SizeOfXLogRecord, total_len); + goto err; + } /* We'll validate the header once we have the next page. */ gotheader = false; } @@ -800,6 +809,8 @@ ValidXLogRecord(XLogReaderState *state, XLogRecord *record, XLogRecPtr recptr) { pg_crc32c crc; + Assert(record->xl_tot_len >= SizeOfXLogRecord); + /* Calculate the CRC */ INIT_CRC32C(crc); COMP_CRC32C(crc, ((char *) record) + SizeOfXLogRecord, record->xl_tot_len - SizeOfXLogRecord); diff --git a/src/test/recovery/t/039_end_of_wal.pl b/src/test/recovery/t/039_end_of_wal.pl index 1eb65c07654..1d1d883be6a 100644 --- a/src/test/recovery/t/039_end_of_wal.pl +++ b/src/test/recovery/t/039_end_of_wal.pl @@ -287,6 +287,19 @@ sub advance_to_record_splitting_zone $log_size), "xl_tot_len short"); +# xl_tot_len in final position, not big enough to span into a new page but +# also not eligible for regular record header validation +emit_message($node, 0); +$end_lsn = advance_to_record_splitting_zone($node); +$node->stop('immediate'); +write_wal($node, $TLI, $end_lsn, build_record_header(1)); +$log_size = -s $node->logfile; +$node->start; +ok( $node->log_contains( + "invalid record length at .*: wanted 24, got 1", $log_size + ), + "xl_tot_len short at end-of-page"); + # Need more pages, but xl_prev check fails first. emit_message($node, 0); $end_lsn = advance_out_of_record_splitting_zone($node); From bb34ca8a76ea89b6ec36951849c78a55b8da47c8 Mon Sep 17 00:00:00 2001 From: reshke Date: Mon, 21 Sep 2026 13:53:39 +0300 Subject: [PATCH 06/22] Backport fixup: wrap consider_join_pushdown block in joinpath.c in #ifdef NOT_USED --- src/backend/optimizer/path/joinpath.c | 3 +++ src/test/regress/expected/rowtypes.out | 29 +++++++++++++------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 5defbdd3613..82678b8a522 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -367,6 +367,7 @@ add_paths_to_join_relation(PlannerInfo *root, hash_inner_and_outer(root, joinrel, outerrel, innerrel, jointype, &extra); +#ifdef NOT_USED /* * createplan.c does not currently support handling of pseudoconstant * clauses assigned to joins pushed down by extensions; check if the @@ -379,6 +380,8 @@ add_paths_to_join_relation(PlannerInfo *root, consider_join_pushdown = !has_pseudoconstant_clauses(root, restrictlist); +#endif + /* * 5. If inner and outer relations are foreign tables (or joins) belonging * to the same server and assigned to the same user to check access diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 2c85583cce0..dd93e7c8ca7 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -1243,22 +1243,23 @@ with cte(c) as materialized (select row(1, 2)), select * from cte2 as t where (select * from (select c as c1) s where (select (c1).f1 > 0)) is not null; - QUERY PLAN --------------------------------------------- - CTE Scan on cte - Output: cte.c - Filter: ((SubPlan 3) IS NOT NULL) - CTE cte - -> Result - Output: '(1,2)'::record - SubPlan 3 + QUERY PLAN +------------------------------------------ + Subquery Scan on t + Output: t.c + Filter: ((SubPlan 2) IS NOT NULL) + -> Shared Scan (share slice:id 0:0) + Output: share0_ref1."row" + -> Result + Output: ROW(1, 2) + SubPlan 2 -> Result - Output: cte.c - One-Time Filter: $2 - InitPlan 2 (returns $2) + Output: t.c + One-Time Filter: $1 + InitPlan 1 (returns $1) -> Result - Output: ((cte.c).f1 > 0) -(13 rows) + Output: ((t.c).f1 > 0) +GP_IGNORE:(16 rows) with cte(c) as materialized (select row(1, 2)), cte2(c) as (select * from cte) From 3646726e46e19d60c3b432a1935fac770655c66c Mon Sep 17 00:00:00 2001 From: reshke Date: Tue, 22 Sep 2026 07:46:36 +0300 Subject: [PATCH 07/22] Fix rowtypes expected output for bug #18077 explain plan The hand-written GP_IGNORE:(16 rows) footer broke atmsort's block parsing (the (N rows) row-count regexp doesn't match a GP_IGNORE- prefixed footer), so the explain directive leaked into the following SELECT block and its (1 row) footer got GP_IGNORE-ified in the expected file only, producing a bogus -GP_IGNORE:(1 row) / +(1 row) diff and a failed test. Record the actual server output instead: the Settings and Optimizer lines are globally ignored by gpdiff, and the plan is identical with optimizer on or off. --- src/test/regress/expected/rowtypes.out | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index dd93e7c8ca7..4cb3a1c06fa 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -1259,7 +1259,9 @@ where (select * from (select c as c1) s InitPlan 1 (returns $1) -> Result Output: ((t.c).f1 > 0) -GP_IGNORE:(16 rows) + Settings: optimizer = 'off' + Optimizer: Postgres query optimizer +(16 rows) with cte(c) as materialized (select row(1, 2)), cte2(c) as (select * from cte) From 0f586aabaf4afaa14c4ba47e03c8d44d50aa0728 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Tue, 3 Oct 2023 10:25:15 +0900 Subject: [PATCH 08/22] Fail hard on out-of-memory failures in xlogreader.c This commit changes the WAL reader routines so as a FATAL for the backend or exit(FAILURE) for the frontend is triggered if an allocation for a WAL record decode fails in walreader.c, rather than treating this case as bogus data, which would be equivalent to the end of WAL. The key is to avoid palloc_extended(MCXT_ALLOC_NO_OOM) in walreader.c, relying on plain palloc() calls. The previous behavior could make WAL replay finish too early than it should. For example, crash recovery finishing earlier may corrupt clusters because not all the WAL available locally was replayed to ensure a consistent state. Out-of-memory failures would show up randomly depending on the memory pressure on the host, but one simple case would be to generate a large record, then replay this record after downsizing a host, as Ethan Mertz originally reported. This relies on bae868caf222, as the WAL reader routines now do the memory allocation required for a record only once its header has been fully read and validated, making xl_tot_len trustable. Making the WAL reader react differently on out-of-memory or bogus record data would require ABI changes, so this is the safest choice for stable branches. Also, it is worth noting that 3f1ce973467a has been using a plain palloc() in this code for some time now. Thanks to Noah Misch and Thomas Munro for the discussion. Like the other commit, backpatch down to 12, leaving out v11 that will be EOL'd soon. The behavior of considering a failed allocation as bogus data comes originally from 0ffe11abd3a0, where the record length retrieved from its header was not entirely trustable. Reported-by: Ethan Mertz Discussion: https://postgr.es/m/ZRKKdI5-RRlta3aF@paquier.xyz Backpatch-through: 12 (cherry picked from commit 50e4a61936d2e44e4addff79a7f4b831e22b7522) --- src/backend/access/transam/xlogreader.c | 31 ++++--------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index af5e8508bf1..9ea662c5be8 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -40,7 +40,7 @@ static void report_invalid_record(XLogReaderState *state, const char *fmt,...) pg_attribute_printf(2, 3); -static bool allocate_recordbuf(XLogReaderState *state, uint32 reclength); +static void allocate_recordbuf(XLogReaderState *state, uint32 reclength); static int ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen); static void XLogReaderInvalReadState(XLogReaderState *state); @@ -132,14 +132,7 @@ XLogReaderAllocate(int wal_segment_size, const char *waldir, * Allocate an initial readRecordBuf of minimal size, which can later be * enlarged if necessary. */ - if (!allocate_recordbuf(state, 0)) - { - pfree(state->errormsg_buf); - pfree(state->readBuf); - pfree(state); - return NULL; - } - + allocate_recordbuf(state, 0); return state; } @@ -168,7 +161,6 @@ XLogReaderFree(XLogReaderState *state) /* * Allocate readRecordBuf to fit a record of at least the given length. - * Returns true if successful, false if out of memory. * * readRecordBufSize is set to the new buffer size. * @@ -180,7 +172,7 @@ XLogReaderFree(XLogReaderState *state) * Note: This routine should *never* be called for xl_tot_len until the header * of the record has been fully validated. */ -static bool +static void allocate_recordbuf(XLogReaderState *state, uint32 reclength) { uint32 newSize = reclength; @@ -190,15 +182,8 @@ allocate_recordbuf(XLogReaderState *state, uint32 reclength) if (state->readRecordBuf) pfree(state->readRecordBuf); - state->readRecordBuf = - (char *) palloc_extended(newSize, MCXT_ALLOC_NO_OOM); - if (state->readRecordBuf == NULL) - { - state->readRecordBufSize = 0; - return false; - } + state->readRecordBuf = (char *) palloc(newSize); state->readRecordBufSize = newSize; - return true; } /* @@ -532,13 +517,7 @@ XLogReadRecord(XLogReaderState *state, char **errormsg) Assert(gotlen <= lengthof(save_copy)); Assert(gotlen <= state->readRecordBufSize); memcpy(save_copy, state->readRecordBuf, gotlen); - if (!allocate_recordbuf(state, total_len)) - { - /* We treat this as a "bogus data" condition */ - report_invalid_record(state, "record length %u at %X/%X too long", - total_len, LSN_FORMAT_ARGS(RecPtr)); - goto err; - } + allocate_recordbuf(state, total_len); memcpy(state->readRecordBuf, save_copy, gotlen); buffer = state->readRecordBuf + gotlen; } From e2a6f70f7111356a2b4fe80d09ee39ffac306b73 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 9 Jun 2023 11:56:27 +0900 Subject: [PATCH 09/22] Refactor routine to find single log content pattern in TAP tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same routine to check if a specific pattern can be found in the server logs was copied over four different test scripts. This refactors the whole to use a single routine located in PostgreSQL::Test::Cluster, named log_contains, to grab the contents of the server logs and check for a specific pattern. On HEAD, the code previously used assumed that slurp_file() could not handle an undefined offset, setting it to zero, but slurp_file() does do an extra fseek() before retrieving the log contents only if an offset is defined. In two places, the test was retrieving the full log contents with slurp_file() after calling substr() to apply an offset, ignoring that slurp_file() would be able to handle that. Backpatch all the way down to ease the introduction of new tests that could rely on the new routine. Author: Vignesh C Reviewed-by: Andrew Dunstan, Dagfinn Ilmari Mannsåker, Michael Paquier Discussion: https://postgr.es/m/CALDaNm0YSiLpjCmajwLfidQrFOrLNKPQir7s__PeVvh9U3uoTQ@mail.gmail.com Backpatch-through: 11 (cherry picked from commit 392ea0c78fdb6cb92f1af0793f6c2d48526e6fed) --- src/test/perl/PostgresNode.pm | 16 +++++++++++ src/test/recovery/t/019_replslot_limit.pl | 31 +++++---------------- src/test/recovery/t/033_replay_tsp_drops.pl | 29 ++----------------- 3 files changed, 26 insertions(+), 50 deletions(-) diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm index b81f6e7b68d..0613823a1a9 100644 --- a/src/test/perl/PostgresNode.pm +++ b/src/test/perl/PostgresNode.pm @@ -2764,6 +2764,22 @@ sub log_check =pod +=item log_contains(pattern, offset) + +Find pattern in logfile of node after offset byte. + +=cut + +sub log_contains +{ + my ($self, $pattern, $offset) = @_; + + return TestLib::slurp_file($self->logfile, $offset) =~ + m/$pattern/; +} + +=pod + =item $node->run_log(...) Runs a shell command like TestLib::run_log, but with connection parameters set diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index b365fcc8863..648cab393ea 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -172,8 +172,7 @@ $node_standby->stop; -ok( !find_in_log( - $node_standby, +ok( !$node_standby->log_contains( "requested WAL segment [0-9A-F]+ has already been removed"), 'check that required WAL segments are still available'); @@ -195,8 +194,7 @@ my $invalidated = 0; for (my $i = 0; $i < 10000; $i++) { - if (find_in_log( - $node_primary, + if ($node_primary->log_contains( "invalidating slot \"rep1\" because its restart_lsn [0-9A-F/]+ exceeds max_slot_wal_keep_size", $logstart)) { @@ -219,7 +217,7 @@ my $checkpoint_ended = 0; for (my $i = 0; $i < 10000; $i++) { - if (find_in_log($node_primary, "checkpoint complete: ", $logstart)) + if ($node_primary->log_contains("checkpoint complete: ", $logstart)) { $checkpoint_ended = 1; last; @@ -249,8 +247,7 @@ my $failed = 0; for (my $i = 0; $i < 10000; $i++) { - if (find_in_log( - $node_standby, + if ($node_standby->log_contains( "requested WAL segment [0-9A-F]+ has already been removed", $logstart)) { @@ -356,8 +353,7 @@ my $max_attempts = $TestLib::timeout_default; while ($max_attempts-- >= 0) { - if (find_in_log( - $node_primary3, + if ($node_primary3->log_contains( "terminating process $senderpid to release replication slot \"rep3\"", $logstart)) { @@ -379,8 +375,7 @@ $max_attempts = $TestLib::timeout_default; while ($max_attempts-- >= 0) { - if (find_in_log( - $node_primary3, + if ($node_primary3->log_contains( 'invalidating slot "rep3" because its restart_lsn', $logstart)) { ok(1, "slot invalidation logged"); @@ -418,16 +413,4 @@ sub get_log_size return (stat $node->logfile)[7]; } -# find $pat in logfile of $node after $off-th byte -sub find_in_log -{ - my ($node, $pat, $off) = @_; - - $off = 0 unless defined $off; - my $log = TestLib::slurp_file($node->logfile); - return 0 if (length($log) <= $off); - - $log = substr($log, $off); - - return $log =~ m/$pat/; -} +done_testing(); diff --git a/src/test/recovery/t/033_replay_tsp_drops.pl b/src/test/recovery/t/033_replay_tsp_drops.pl index fb0b6150f27..ac7b8fe6e4e 100644 --- a/src/test/recovery/t/033_replay_tsp_drops.pl +++ b/src/test/recovery/t/033_replay_tsp_drops.pl @@ -113,7 +113,7 @@ sub test_tablespace my $tspdir = $node_standby->data_dir . "/pg_tblspc/$tspoid"; File::Path::rmtree($tspdir); -my $logstart = get_log_size($node_standby); +my $logstart = -s $node_standby->logfile; # Create a database in the tablespace and a table in default tablespace $node_primary->safe_psql( @@ -132,34 +132,11 @@ sub test_tablespace { last if ( - find_in_log( - $node_standby, "WARNING: creating missing directory: pg_tblspc/", + $node_standby->log_contains( + qr!WARNING: ( [A-Z0-9]+:)? creating missing directory: pg_tblspc/!, $logstart)); sleep 1; } ok($max_attempts > 0, "invalid directory creation is detected"); done_testing(); - - -# return the size of logfile of $node in bytes -sub get_log_size -{ - my ($node) = @_; - - return (stat $node->logfile)[7]; -} - -# find $pat in logfile of $node after $off-th byte -sub find_in_log -{ - my ($node, $pat, $off) = @_; - - $off = 0 unless defined $off; - my $log = PostgreSQL::Test::Utils::slurp_file($node->logfile); - return 0 if (length($log) <= $off); - - $log = substr($log, $off); - - return $log =~ m/$pat/; -} From 6b2af20b61332da267965255044a4ef808464f3c Mon Sep 17 00:00:00 2001 From: reshke Date: Tue, 22 Sep 2026 13:36:10 +0300 Subject: [PATCH 10/22] Backport fixup: relax magic number check in 039_end_of_wal split case Cloudberry writes an extra distributed-commit WAL record after every statement, so the insert LSN that advance_to_record_splitting_zone() calibrates ends up 8 bytes further away from the page boundary than in upstream. Consequently the bytes of the synthetic record header that spill over to the next page overwrite xlp_magic with bytes of xl_prev (BEEF) instead of the zeroed xl_info/xl_rmid (0000). Any invalid magic number still proves that the page header is validated before the record header, so accept any 4-hex magic there. --- src/test/recovery/t/039_end_of_wal.pl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test/recovery/t/039_end_of_wal.pl b/src/test/recovery/t/039_end_of_wal.pl index 1d1d883be6a..8d652354dd9 100644 --- a/src/test/recovery/t/039_end_of_wal.pl +++ b/src/test/recovery/t/039_end_of_wal.pl @@ -448,7 +448,16 @@ sub advance_to_record_splitting_zone build_record_header(2 * 1024 * 1024 * 1024, 0, 0xdeadbeef)); $log_size = -s $node->logfile; $node->start; -ok($node->log_contains("invalid magic number 0000 ", $log_size), +# The bytes of the split record header that spill over to the new page +# overwrite xlp_magic. In upstream the spill-over is the xl_info/xl_rmid +# padding bytes, which are zero here, but Cloudberry writes an extra +# distributed-commit WAL record after every statement, so the calibrated +# insert LSN ends up 8 bytes further and the xl_prev bytes land on +# xlp_magic instead. Accept any magic number here; the point of the +# test is that the page header is validated first. +ok( $node->log_contains( + "invalid magic number [0-9A-F]{4} in log segment", + $log_size), "xlp_magic zero (split record header)"); # And we'll also check xlp_pageaddr before any header checks. From 15f9565b644505c3caaa3a4df961e07229eccc19 Mon Sep 17 00:00:00 2001 From: David Rowley Date: Thu, 12 Oct 2023 19:52:31 +1300 Subject: [PATCH 11/22] Fix incorrect step generation in HASH partition pruning get_steps_using_prefix_recurse() incorrectly assumed that it could stop recursive processing of the 'prefix' list when cur_keyno was one before the step_lastkeyno. Since hash partition pruning can prune using IS NULL quals, and these IS NULL quals are not present in the 'prefix' list, then that logic could cause more levels of recursion than what is needed and lead to there being no more items in the 'prefix' list to process. This would manifest itself as a crash in some code that expected the 'start' ListCell not to be NULL. Here we adjust the logic so that instead of stopping recursion at 1 key before the step_lastkeyno, we just look at the llast(prefix) item and ensure we only recursively process up until just before whichever the last key is. This effectively allows keys to be missing in the 'prefix' list. This change does mean that step_lastkeyno is no longer needed, so we remove that from the static functions. I also spent quite some time reading this code and testing it to try to convince myself that there are no other issues. That resulted in the irresistible temptation of rewriting some comments, many of which were just not true or inconcise. Reported-by: Sergei Glukhov Reviewed-by: Sergei Glukhov, tender wang Discussion: https://postgr.es/m/2f09ce72-315e-2a33-589a-8519ada8df61@postgrespro.ru Backpatch-through: 11, where partition pruning was introduced. (cherry picked from commit 1cf463ea7f6658ae7ac93455a53afadd09693e19) --- src/backend/partitioning/partprune.c | 103 ++++---- src/test/regress/expected/partition_prune.out | 221 ++++++++++++++++-- src/test/regress/sql/partition_prune.sql | 55 ++++- 3 files changed, 315 insertions(+), 64 deletions(-) diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index a9ff52941ca..6d7c2a5f53d 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -172,7 +172,6 @@ static List *get_steps_using_prefix(GeneratePruningStepsContext *context, bool step_op_is_ne, Expr *step_lastexpr, Oid step_lastcmpfn, - int step_lastkeyno, Bitmapset *step_nullkeys, List *prefix); static List *get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, @@ -180,7 +179,6 @@ static List *get_steps_using_prefix_recurse(GeneratePruningStepsContext *context bool step_op_is_ne, Expr *step_lastexpr, Oid step_lastcmpfn, - int step_lastkeyno, Bitmapset *step_nullkeys, List *prefix, ListCell *start, @@ -1557,7 +1555,6 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context, pc->op_is_ne, pc->expr, pc->cmpfn, - 0, NULL, NIL); opsteps = list_concat(opsteps, pc_steps); @@ -1683,7 +1680,6 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context, pc->op_is_ne, pc->expr, pc->cmpfn, - pc->keyno, NULL, prefix); opsteps = list_concat(opsteps, pc_steps); @@ -1757,7 +1753,6 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context, false, pc->expr, pc->cmpfn, - pc->keyno, nullkeys, prefix); opsteps = list_concat(opsteps, pc_steps); @@ -2380,25 +2375,31 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context, /* * get_steps_using_prefix - * Generate list of PartitionPruneStepOp steps each consisting of given - * opstrategy - * - * To generate steps, step_lastexpr and step_lastcmpfn are appended to - * expressions and cmpfns, respectively, extracted from the clauses in - * 'prefix'. Actually, since 'prefix' may contain multiple clauses for the - * same partition key column, we must generate steps for various combinations - * of the clauses of different keys. - * - * For list/range partitioning, callers must ensure that step_nullkeys is - * NULL, and that prefix contains at least one clause for each of the - * partition keys earlier than one specified in step_lastkeyno if it's - * greater than zero. For hash partitioning, step_nullkeys is allowed to be - * non-NULL, but they must ensure that prefix contains at least one clause - * for each of the partition keys other than those specified in step_nullkeys - * and step_lastkeyno. - * - * For both cases, callers must also ensure that clauses in prefix are sorted - * in ascending order of their partition key numbers. + * Generate a list of PartitionPruneStepOps based on the given input. + * + * 'step_lastexpr' and 'step_lastcmpfn' are the Expr and comparison function + * belonging to the final partition key that we have a clause for. 'prefix' + * is a list of PartClauseInfos for partition key numbers prior to the given + * 'step_lastexpr' and 'step_lastcmpfn'. 'prefix' may contain multiple + * PartClauseInfos belonging to a single partition key. We will generate a + * PartitionPruneStepOp for each combination of the given PartClauseInfos + * using, at most, one PartClauseInfo per partition key. + * + * For LIST and RANGE partitioned tables, callers must ensure that + * step_nullkeys is NULL, and that prefix contains at least one clause for + * each of the partition keys prior to the key that 'step_lastexpr' and + * 'step_lastcmpfn'belong to. + * + * For HASH partitioned tables, callers must ensure that 'prefix' contains at + * least one clause for each of the partition keys apart from the final key + * (the expr and comparison function for the final key are in 'step_lastexpr' + * and 'step_lastcmpfn'). A bit set in step_nullkeys can substitute clauses + * in the 'prefix' list for any given key. If a bit is set in 'step_nullkeys' + * for a given key, then there must be no PartClauseInfo for that key in the + * 'prefix' list. + * + * For each of the above cases, callers must ensure that PartClauseInfos in + * 'prefix' are sorted in ascending order of keyno. */ static List * get_steps_using_prefix(GeneratePruningStepsContext *context, @@ -2406,14 +2407,17 @@ get_steps_using_prefix(GeneratePruningStepsContext *context, bool step_op_is_ne, Expr *step_lastexpr, Oid step_lastcmpfn, - int step_lastkeyno, Bitmapset *step_nullkeys, List *prefix) { + /* step_nullkeys must be empty for RANGE and LIST partitioned tables */ Assert(step_nullkeys == NULL || context->rel->part_scheme->strategy == PARTITION_STRATEGY_HASH); - /* Quick exit if there are no values to prefix with. */ + /* + * No recursive processing is required when 'prefix' is an empty list. This + * occurs when there is only 1 partition key column. + */ if (list_length(prefix) == 0) { PartitionPruneStep *step; @@ -2427,13 +2431,12 @@ get_steps_using_prefix(GeneratePruningStepsContext *context, return list_make1(step); } - /* Recurse to generate steps for various combinations. */ + /* Recurse to generate steps for every combination of clauses. */ return get_steps_using_prefix_recurse(context, step_opstrategy, step_op_is_ne, step_lastexpr, step_lastcmpfn, - step_lastkeyno, step_nullkeys, prefix, list_head(prefix), @@ -2442,13 +2445,17 @@ get_steps_using_prefix(GeneratePruningStepsContext *context, /* * get_steps_using_prefix_recurse - * Recursively generate combinations of clauses for different partition - * keys and start generating steps upon reaching clauses for the greatest - * column that is less than the one for which we're currently generating - * steps (that is, step_lastkeyno) + * Generate and return a list of PartitionPruneStepOps using the 'prefix' + * list of PartClauseInfos starting at the 'start' cell. + * + * When 'prefix' contains multiple PartClauseInfos for a single partition key + * we create a PartitionPruneStepOp for each combination of duplicated + * PartClauseInfos. The returned list will contain a PartitionPruneStepOp + * for each unique combination of input PartClauseInfos containing at most one + * PartClauseInfo per partition key. * - * 'prefix' is the list of PartClauseInfos. - * 'start' is where we should start iterating for the current invocation. + * 'prefix' is the input list of PartClauseInfos sorted by keyno. + * 'start' marks the cell that searching the 'prefix' list should start from. * 'step_exprs' and 'step_cmpfns' each contains the expressions and cmpfns * we've generated so far from the clauses for the previous part keys. */ @@ -2458,7 +2465,6 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, bool step_op_is_ne, Expr *step_lastexpr, Oid step_lastcmpfn, - int step_lastkeyno, Bitmapset *step_nullkeys, List *prefix, ListCell *start, @@ -2468,23 +2474,25 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, List *result = NIL; ListCell *lc; int cur_keyno; + int final_keyno; /* Actually, recursion would be limited by PARTITION_MAX_KEYS. */ check_stack_depth(); - /* Check if we need to recurse. */ Assert(start != NULL); cur_keyno = ((PartClauseInfo *) lfirst(start))->keyno; - if (cur_keyno < step_lastkeyno - 1) + final_keyno = ((PartClauseInfo *) llast(prefix))->keyno; + + /* Check if we need to recurse. */ + if (cur_keyno < final_keyno) { PartClauseInfo *pc; ListCell *next_start; /* - * For each clause with cur_keyno, add its expr and cmpfn to - * step_exprs and step_cmpfns, respectively, and recurse after setting - * next_start to the ListCell of the first clause for the next - * partition key. + * Find the first PartClauseInfo belonging to the next partition key, the + * next recursive call must start iteration of the prefix list from that + * point. */ for_each_cell(lc, prefix, start) { @@ -2493,8 +2501,15 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, if (pc->keyno > cur_keyno) break; } + + /* record where to start iterating in the next recursive call */ next_start = lc; + /* + * For each PartClauseInfo with keyno set to cur_keyno, add its expr and + * cmpfn to step_exprs and step_cmpfns, respectively, and recurse using + * 'next_start' as the starting point in the 'prefix' list. + */ for_each_cell(lc, prefix, start) { List *moresteps; @@ -2514,6 +2529,7 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, } else { + /* check the 'prefix' list is sorted correctly */ Assert(pc->keyno > cur_keyno); break; } @@ -2523,7 +2539,6 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, step_op_is_ne, step_lastexpr, step_lastcmpfn, - step_lastkeyno, step_nullkeys, prefix, next_start, @@ -2542,8 +2557,8 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context, * each clause with cur_keyno, which is all clauses from here onward * till the end of the list. Note that for hash partitioning, * step_nullkeys is allowed to be non-empty, in which case step_exprs - * would only contain expressions for the earlier partition keys that - * are not specified in step_nullkeys. + * would only contain expressions for the partition keys that are not + * specified in step_nullkeys. */ Assert(list_length(step_exprs) == cur_keyno || !bms_is_empty(step_nullkeys)); diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 75e646374be..566779d21a1 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -4359,22 +4359,217 @@ explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b Optimizer: Postgres query optimizer (4 rows) -create table hp_prefix_test (a int, b int, c int, d int) partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops); -create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 2, remainder 0); -create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 2, remainder 1); --- Test that get_steps_using_prefix() handles non-NULL step_nullkeys -explain (costs off) select * from hp_prefix_test where a = 1 and b is null and c = 1 and d = 1; - QUERY PLAN -------------------------------------------------------------------- - Gather Motion 1:1 (slice1; segments: 1) - -> Seq Scan on hp_prefix_test_p1 hp_prefix_test - Filter: ((b IS NULL) AND (a = 1) AND (c = 1) AND (d = 1)) - Optimizer: Postgres query optimizer -(4 rows) - drop table rp_prefix_test1; drop table rp_prefix_test2; drop table rp_prefix_test3; +-- +-- Test that get_steps_using_prefix() handles IS NULL clauses correctly +-- +create table hp_prefix_test (a int, b int, c int, d int) + partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops); +-- create 8 partitions +select 'create table hp_prefix_test_p' || x::text || ' partition of hp_prefix_test for values with (modulus 8, remainder ' || x::text || ');' +from generate_Series(0,7) x; + ?column? +------------------------------------------------------------------------------------------------------ + create table hp_prefix_test_p0 partition of hp_prefix_test for values with (modulus 8, remainder 0); + create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 8, remainder 1); + create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 8, remainder 2); + create table hp_prefix_test_p3 partition of hp_prefix_test for values with (modulus 8, remainder 3); + create table hp_prefix_test_p4 partition of hp_prefix_test for values with (modulus 8, remainder 4); + create table hp_prefix_test_p5 partition of hp_prefix_test for values with (modulus 8, remainder 5); + create table hp_prefix_test_p6 partition of hp_prefix_test for values with (modulus 8, remainder 6); + create table hp_prefix_test_p7 partition of hp_prefix_test for values with (modulus 8, remainder 7); +(8 rows) + +\gexec +create table hp_prefix_test_p0 partition of hp_prefix_test for values with (modulus 8, remainder 0); +create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 8, remainder 1); +create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 8, remainder 2); +create table hp_prefix_test_p3 partition of hp_prefix_test for values with (modulus 8, remainder 3); +create table hp_prefix_test_p4 partition of hp_prefix_test for values with (modulus 8, remainder 4); +create table hp_prefix_test_p5 partition of hp_prefix_test for values with (modulus 8, remainder 5); +create table hp_prefix_test_p6 partition of hp_prefix_test for values with (modulus 8, remainder 6); +create table hp_prefix_test_p7 partition of hp_prefix_test for values with (modulus 8, remainder 7); +-- insert 16 rows, one row for each test to perform. +insert into hp_prefix_test +select + case a when 0 then null else 1 end, + case b when 0 then null else 2 end, + case c when 0 then null else 3 end, + case d when 0 then null else 4 end +from + generate_series(0,1) a, + generate_series(0,1) b, + generate_Series(0,1) c, + generate_Series(0,1) d; +-- Ensure partition pruning works correctly for each combination of IS NULL +-- and equality quals. This may seem a little excessive, but there have been +-- a number of bugs in this area over the years. We make use of row only +-- output to reduce the size of the expected results. +\t on +select + 'explain (costs off) select tableoid::regclass,* from hp_prefix_test where ' || + string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos) +from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s) +group by g.s +order by g.s; + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 + +\gexec +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null + Seq Scan on hp_prefix_test_p0 hp_prefix_test + Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d IS NULL)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null + Seq Scan on hp_prefix_test_p1 hp_prefix_test + Filter: ((b IS NULL) AND (c IS NULL) AND (d IS NULL) AND (a = 1)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null + Seq Scan on hp_prefix_test_p2 hp_prefix_test + Filter: ((a IS NULL) AND (c IS NULL) AND (d IS NULL) AND (b = 2)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null + Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((c IS NULL) AND (d IS NULL) AND (a = 1) AND (b = 2)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null + Seq Scan on hp_prefix_test_p3 hp_prefix_test + Filter: ((a IS NULL) AND (b IS NULL) AND (d IS NULL) AND (c = 3)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null + Seq Scan on hp_prefix_test_p7 hp_prefix_test + Filter: ((b IS NULL) AND (d IS NULL) AND (a = 1) AND (c = 3)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null + Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((a IS NULL) AND (d IS NULL) AND (b = 2) AND (c = 3)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null + Seq Scan on hp_prefix_test_p5 hp_prefix_test + Filter: ((d IS NULL) AND (a = 1) AND (b = 2) AND (c = 3)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 + Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d = 4)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 + Seq Scan on hp_prefix_test_p6 hp_prefix_test + Filter: ((b IS NULL) AND (c IS NULL) AND (a = 1) AND (d = 4)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 + Seq Scan on hp_prefix_test_p5 hp_prefix_test + Filter: ((a IS NULL) AND (c IS NULL) AND (b = 2) AND (d = 4)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 + Seq Scan on hp_prefix_test_p6 hp_prefix_test + Filter: ((c IS NULL) AND (a = 1) AND (b = 2) AND (d = 4)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 + Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((a IS NULL) AND (b IS NULL) AND (c = 3) AND (d = 4)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 + Seq Scan on hp_prefix_test_p5 hp_prefix_test + Filter: ((b IS NULL) AND (a = 1) AND (c = 3) AND (d = 4)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 + Seq Scan on hp_prefix_test_p6 hp_prefix_test + Filter: ((a IS NULL) AND (b = 2) AND (c = 3) AND (d = 4)) + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 + Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((a = 1) AND (b = 2) AND (c = 3) AND (d = 4)) + +-- And ensure we get exactly 1 row from each. Again, all 16 possible combinations. +select + 'select tableoid::regclass,* from hp_prefix_test where ' || + string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos) +from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s) +group by g.s +order by g.s; + select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null + select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null + select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null + select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null + select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null + select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null + select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null + select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null + select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 + select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 + select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 + select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 + select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 + select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 + select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 + select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 + +\gexec +select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null + hp_prefix_test_p0 | | | | + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null + hp_prefix_test_p1 | 1 | | | + +select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null + hp_prefix_test_p2 | | 2 | | + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null + hp_prefix_test_p4 | 1 | 2 | | + +select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null + hp_prefix_test_p3 | | | 3 | + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null + hp_prefix_test_p7 | 1 | | 3 | + +select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null + hp_prefix_test_p4 | | 2 | 3 | + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null + hp_prefix_test_p5 | 1 | 2 | 3 | + +select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 + hp_prefix_test_p4 | | | | 4 + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 + hp_prefix_test_p6 | 1 | | | 4 + +select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 + hp_prefix_test_p5 | | 2 | | 4 + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 + hp_prefix_test_p6 | 1 | 2 | | 4 + +select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 + hp_prefix_test_p4 | | | 3 | 4 + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 + hp_prefix_test_p5 | 1 | | 3 | 4 + +select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 + hp_prefix_test_p6 | | 2 | 3 | 4 + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 + hp_prefix_test_p4 | 1 | 2 | 3 | 4 + +\t off drop table hp_prefix_test; -- -- Check that gen_partprune_steps() detects self-contradiction from clauses diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index f9e3b1f2013..0dca83079e4 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -1191,16 +1191,57 @@ explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b -- that the caller arranges clauses in that prefix in the required order) explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b = 2 and c = 2 and d >= 0; -create table hp_prefix_test (a int, b int, c int, d int) partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops); -create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 2, remainder 0); -create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 2, remainder 1); - --- Test that get_steps_using_prefix() handles non-NULL step_nullkeys -explain (costs off) select * from hp_prefix_test where a = 1 and b is null and c = 1 and d = 1; - drop table rp_prefix_test1; drop table rp_prefix_test2; drop table rp_prefix_test3; + +-- +-- Test that get_steps_using_prefix() handles IS NULL clauses correctly +-- +create table hp_prefix_test (a int, b int, c int, d int) + partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops); + +-- create 8 partitions +select 'create table hp_prefix_test_p' || x::text || ' partition of hp_prefix_test for values with (modulus 8, remainder ' || x::text || ');' +from generate_Series(0,7) x; +\gexec + +-- insert 16 rows, one row for each test to perform. +insert into hp_prefix_test +select + case a when 0 then null else 1 end, + case b when 0 then null else 2 end, + case c when 0 then null else 3 end, + case d when 0 then null else 4 end +from + generate_series(0,1) a, + generate_series(0,1) b, + generate_Series(0,1) c, + generate_Series(0,1) d; + +-- Ensure partition pruning works correctly for each combination of IS NULL +-- and equality quals. This may seem a little excessive, but there have been +-- a number of bugs in this area over the years. We make use of row only +-- output to reduce the size of the expected results. +\t on +select + 'explain (costs off) select tableoid::regclass,* from hp_prefix_test where ' || + string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos) +from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s) +group by g.s +order by g.s; +\gexec + +-- And ensure we get exactly 1 row from each. Again, all 16 possible combinations. +select + 'select tableoid::regclass,* from hp_prefix_test where ' || + string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos) +from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s) +group by g.s +order by g.s; +\gexec +\t off + drop table hp_prefix_test; -- From bc35d4cfbfa260472b399c696de945d16134e4d2 Mon Sep 17 00:00:00 2001 From: David Rowley Date: Fri, 13 Oct 2023 01:13:59 +1300 Subject: [PATCH 12/22] Fix runtime partition pruning for HASH partitioned tables This could only affect HASH partitioned tables with at least 2 partition key columns. If partition pruning was delayed until execution and the query contained an IS NULL qual on one of the partitioned keys, and some subsequent partitioned key was being compared to a non-Const, then this could result in a crash due to the incorrect keyno being used to calculate the stateidx for the expression evaluation code. Here we fix this by properly skipping partitioned keys which have a nullkey set. Effectively, this must be the same as what's going on inside perform_pruning_base_step(). Sergei Glukhov also provided a patch, but that's not what's being used here. Reported-by: Sergei Glukhov Reviewed-by: tender wang, Sergei Glukhov Discussion: https://postgr.es/m/d05b26fa-af54-27e1-f693-6c31590802fa@postgrespro.ru Backpatch-through: 11, where runtime partition pruning was added. (cherry picked from commit dd80563c5ce76229dcead78aa284ffae5b1d0937) --- src/backend/executor/execPartition.c | 29 +++++++++++-------- src/test/regress/expected/partition_prune.out | 22 +++++++++++++- src/test/regress/sql/partition_prune.sql | 21 ++++++++++++-- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 629eca05483..fcb51b2fbbd 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -2008,7 +2008,7 @@ ExecInitPruningContext(PartitionPruneContext *context, foreach(lc, pruning_steps) { PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc); - ListCell *lc2; + ListCell *lc2 = list_head(step->exprs); int keyno; /* not needed for other step kinds */ @@ -2017,22 +2017,27 @@ ExecInitPruningContext(PartitionPruneContext *context, Assert(list_length(step->exprs) <= partnatts); - keyno = 0; - foreach(lc2, step->exprs) + for (keyno = 0; keyno < partnatts; keyno++) { - Expr *expr = (Expr *) lfirst(lc2); + if (bms_is_member(keyno, step->nullkeys)) + continue; - /* not needed for Consts */ - if (!IsA(expr, Const)) + if (lc2 != NULL) { - int stateidx = PruneCxtStateIdx(partnatts, - step->step.step_id, - keyno); + Expr *expr = lfirst(lc2); + + /* not needed for Consts */ + if (!IsA(expr, Const)) + { + int stateidx = PruneCxtStateIdx(partnatts, + step->step.step_id, + keyno); - context->exprstates[stateidx] = - ExecInitExpr(expr, context->planstate); + context->exprstates[stateidx] = + ExecInitExpr(expr, context->planstate); + } + lc2 = lnext(step->exprs, lc2); } - keyno++; } } } diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 566779d21a1..b3e82a7b229 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2034,7 +2034,6 @@ explain (costs off) select * from hp where a = 1 and b = 'abcde' and One-Time Filter: false (2 rows) -drop table hp; -- -- Test runtime partition pruning -- @@ -2168,6 +2167,27 @@ explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2); Optimizer: Postgres query optimizer (12 rows) +-- +-- Test runtime pruning with hash partitioned tables +-- +-- recreate partitions dropped above +create table hp1 partition of hp for values with (modulus 4, remainder 1); +create table hp2 partition of hp for values with (modulus 4, remainder 2); +create table hp3 partition of hp for values with (modulus 4, remainder 3); +-- Ensure we correctly prune unneeded partitions when there is an IS NULL qual +prepare hp_q1 (text) as +select * from hp where a is null and b = $1; +explain (costs off) execute hp_q1('xxx'); + QUERY PLAN +-------------------------------------------- + Append + Subplans Removed: 3 + -> Seq Scan on hp2 hp_1 + Filter: ((a IS NULL) AND (b = $1)) +(4 rows) + +deallocate hp_q1; +drop table hp; -- Test a backwards Append scan create table list_part (a int) partition by list (a); create table list_part1 partition of list_part for values in (1); diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index 0dca83079e4..cf87264b69b 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -375,8 +375,6 @@ drop table hp2; explain (costs off) select * from hp where a = 1 and b = 'abcde' and (c = 2 or c = 3); -drop table hp; - -- -- Test runtime partition pruning -- @@ -427,6 +425,25 @@ select a from ab where b between $1 and $2 and a < (select 3); explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2); +-- +-- Test runtime pruning with hash partitioned tables +-- + +-- recreate partitions dropped above +create table hp1 partition of hp for values with (modulus 4, remainder 1); +create table hp2 partition of hp for values with (modulus 4, remainder 2); +create table hp3 partition of hp for values with (modulus 4, remainder 3); + +-- Ensure we correctly prune unneeded partitions when there is an IS NULL qual +prepare hp_q1 (text) as +select * from hp where a is null and b = $1; + +explain (costs off) execute hp_q1('xxx'); + +deallocate hp_q1; + +drop table hp; + -- Test a backwards Append scan create table list_part (a int) partition by list (a); create table list_part1 partition of list_part for values in (1); From 73017987064a9ce820d06511a76596518a3c4313 Mon Sep 17 00:00:00 2001 From: Noah Misch Date: Sat, 14 Oct 2023 16:33:51 -0700 Subject: [PATCH 13/22] Dissociate btequalimage() from interval_ops, ending its deduplication. Under interval_ops, some equal values are distinguishable. One such pair is '24:00:00' and '1 day'. With that being so, btequalimage() breaches the documented contract for the "equalimage" btree support function. This can cause incorrect results from index-only scans. Users should REINDEX any btree indexes having interval-type columns. After updating, pg_amcheck will report an error for almost all such indexes. This fix makes interval_ops simply omit the support function, like numeric_ops does. Back-pack to v13, where btequalimage() first appeared. In back branches, for the benefit of old catalog content, btequalimage() code will return false for type "interval". Going forward, back-branch initdb will include the catalog change. Reviewed by Peter Geoghegan. Discussion: https://postgr.es/m/20231011013317.22.nmisch@google.com (cherry picked from commit 0d17fda7c0af0be2973e0342572a358a51565281) --- contrib/amcheck/verify_nbtree.c | 13 ++++++++++++- src/backend/utils/adt/datum.c | 14 ++++++-------- src/include/catalog/pg_amproc.dat | 2 -- src/include/catalog/pg_opfamily.dat | 2 +- src/test/regress/expected/opr_sanity.out | 1 + 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c index 7f8231a6815..6c3b3c27ccc 100644 --- a/contrib/amcheck/verify_nbtree.c +++ b/contrib/amcheck/verify_nbtree.c @@ -31,6 +31,7 @@ #include "access/xact.h" #include "catalog/index.h" #include "catalog/pg_am.h" +#include "catalog/pg_opfamily_d.h" #include "commands/tablecmds.h" #include "lib/bloomfilter.h" #include "miscadmin.h" @@ -337,10 +338,20 @@ bt_index_check_internal(Oid indrelid, bool parentcheck, bool heapallindexed, errmsg("index \"%s\" metapage has equalimage field set on unsupported nbtree version", RelationGetRelationName(indrel)))); if (allequalimage && !_bt_allequalimage(indrel, false)) + { + bool has_interval_ops = false; + + for (int i = 0; i < IndexRelationGetNumberOfKeyAttributes(indrel); i++) + if (indrel->rd_opfamily[i] == INTERVAL_BTREE_FAM_OID) + has_interval_ops = true; ereport(ERROR, (errcode(ERRCODE_INDEX_CORRUPTED), errmsg("index \"%s\" metapage incorrectly indicates that deduplication is safe", - RelationGetRelationName(indrel)))); + RelationGetRelationName(indrel)), + has_interval_ops + ? errhint("This is known of \"interval\" indexes last built on a version predating 2023-11.") + : 0)); + } /* Check index, possibly against table it is an index on */ bt_check_every_level(indrel, heaprel, heapkeyspace, parentcheck, diff --git a/src/backend/utils/adt/datum.c b/src/backend/utils/adt/datum.c index eed6ae262f2..788b7d61bcc 100644 --- a/src/backend/utils/adt/datum.c +++ b/src/backend/utils/adt/datum.c @@ -43,6 +43,7 @@ #include "postgres.h" #include "access/detoast.h" +#include "catalog/pg_type_d.h" #include "common/hashfn.h" #include "fmgr.h" #include "utils/builtins.h" @@ -388,20 +389,17 @@ datum_image_hash(Datum value, bool typByVal, int typLen) * datum_image_eq() in all cases can use this as their "equalimage" support * function. * - * Currently, we unconditionally assume that any B-Tree operator class that - * registers btequalimage as its support function 4 must be able to safely use - * optimizations like deduplication (i.e. we return true unconditionally). If - * it ever proved necessary to rescind support for an operator class, we could - * do that in a targeted fashion by doing something with the opcintype - * argument. + * Earlier minor releases erroneously associated this function with + * interval_ops. Detect that case to rescind deduplication support, without + * requiring initdb. *------------------------------------------------------------------------- */ Datum btequalimage(PG_FUNCTION_ARGS) { - /* Oid opcintype = PG_GETARG_OID(0); */ + Oid opcintype = PG_GETARG_OID(0); - PG_RETURN_BOOL(true); + PG_RETURN_BOOL(opcintype != INTERVALOID); } /*------------------------------------------------------------------------- diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat index 8ba4de4f868..5ec2f50c9fa 100644 --- a/src/include/catalog/pg_amproc.dat +++ b/src/include/catalog/pg_amproc.dat @@ -172,8 +172,6 @@ { amprocfamily => 'btree/interval_ops', amproclefttype => 'interval', amprocrighttype => 'interval', amprocnum => '3', amproc => 'in_range(interval,interval,interval,bool,bool)' }, -{ amprocfamily => 'btree/interval_ops', amproclefttype => 'interval', - amprocrighttype => 'interval', amprocnum => '4', amproc => 'btequalimage' }, { amprocfamily => 'btree/macaddr_ops', amproclefttype => 'macaddr', amprocrighttype => 'macaddr', amprocnum => '1', amproc => 'macaddr_cmp' }, { amprocfamily => 'btree/macaddr_ops', amproclefttype => 'macaddr', diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat index 8cb8ce386d2..0e73063ac3f 100644 --- a/src/include/catalog/pg_opfamily.dat +++ b/src/include/catalog/pg_opfamily.dat @@ -50,7 +50,7 @@ opfmethod => 'btree', opfname => 'integer_ops' }, { oid => '1977', opfmethod => 'hash', opfname => 'integer_ops' }, -{ oid => '1982', +{ oid => '1982', oid_symbol => 'INTERVAL_BTREE_FAM_OID', opfmethod => 'btree', opfname => 'interval_ops' }, { oid => '1983', opfmethod => 'hash', opfname => 'interval_ops' }, diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 2fb7ba06584..4ab11e74d6e 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -2231,6 +2231,7 @@ ORDER BY 1, 2, 3; | complex_ops | complex_ops | complex | float_ops | float4_ops | real | float_ops | float8_ops | double precision + | interval_ops | interval_ops | interval | jsonb_ops | jsonb_ops | jsonb | multirange_ops | multirange_ops | anymultirange | numeric_ops | numeric_ops | numeric From 08e25885b46b56e87008c4f730e330f81903b33b Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 24 Oct 2023 14:48:28 -0400 Subject: [PATCH 14/22] Fix problems when a plain-inheritance parent table is excluded. When an UPDATE/DELETE/MERGE's target table is an old-style inheritance tree, it's possible for the parent to get excluded from the plan while some children are not. (I believe this is only possible if we can prove that a CHECK ... NO INHERIT constraint on the parent contradicts the query WHERE clause, so it's a very unusual case.) In such a case, ExecInitModifyTable mistakenly concluded that the first surviving child is the target table, leading to at least two bugs: 1. The wrong table's statement-level triggers would get fired. 2. In v16 and up, it was possible to fail with "invalid perminfoindex 0 in RTE with relid nnnn" due to the child RTE not having permissions data included in the query plan. This was hard to reproduce reliably because it did not occur unless the update triggered some non-HOT index updates. In v14 and up, this is easy to fix by defining ModifyTable.rootRelation to be the parent RTE in plain inheritance as well as partitioned cases. While the wrong-triggers bug also appears in older branches, the relevant code in both the planner and executor is quite a bit different, so it would take a good deal of effort to develop and test a suitable patch. Given the lack of field complaints about the trigger issue, I'll desist for now. (Patching v11 for this seems unwise anyway, given that it will have no more releases after next month.) Per bug #18147 from Hans Buschmann. Amit Langote and Tom Lane Discussion: https://postgr.es/m/18147-6fc796538913ee88@postgresql.org (cherry picked from commit f752045231ea1bbd4f9afaa7dfbbf123f8cd91e5) --- src/backend/executor/nodeModifyTable.c | 9 +++++---- src/backend/optimizer/plan/planner.c | 14 ++++--------- src/backend/optimizer/util/pathnode.c | 2 +- src/include/nodes/pathnodes.h | 2 +- src/include/nodes/plannodes.h | 13 +++++++------ src/test/regress/expected/inherit.out | 27 ++++++++++++++++++++++++++ src/test/regress/sql/inherit.sql | 19 ++++++++++++++++++ 7 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 4e718bd7b31..8c11a1e826e 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -3172,10 +3172,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) * 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 it's a partitioned or inherited table, the root partition or + * appendrel RTE doesn't appear elsewhere in the plan and its RT index is + * given explicitly in node->rootRelation. Otherwise, the target relation + * is the sole relation in the node->resultRelations list. *---------- */ if (node->rootRelation > 0) @@ -3186,6 +3186,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) } else { + Assert(list_length(node->resultRelations) == 1); mtstate->rootResultRelInfo = mtstate->resultRelInfo; ExecInitResultRelation(estate, mtstate->resultRelInfo, linitial_int(node->resultRelations)); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 7b55eded430..cdb2ed911e1 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -2463,6 +2463,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) parse->resultRelation); int resultRelation = -1; + /* Pass the root result rel forward to the executor. */ + rootRelation = parse->resultRelation; + /* Add only leaf children to ModifyTable. */ while ((resultRelation = bms_next_member(root->leaf_result_relids, resultRelation)) >= 0) @@ -2547,6 +2550,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) else { /* Single-relation INSERT/UPDATE/DELETE. */ + rootRelation = 0; /* there's no separate root rel */ resultRelations = list_make1_int(parse->resultRelation); if (parse->commandType == CMD_UPDATE) updateColnosLists = list_make1(root->update_colnos); @@ -2556,16 +2560,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) returningLists = list_make1(parse->returningList); } - /* - * If target is a partition root table, we need to mark the - * ModifyTable node appropriately for that. - */ - if (rt_fetch(parse->resultRelation, parse->rtable)->relkind == - RELKIND_PARTITIONED_TABLE) - rootRelation = parse->resultRelation; - else - rootRelation = 0; - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node * will have dealt with fetching non-locked marked rows, else we diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 4648a2f2719..04db89e92f9 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -5815,7 +5815,7 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel, * 'operation' is the operation type * 'canSetTag' is true if we set the command tag/es_processed * 'nominalRelation' is the parent RT index for use of EXPLAIN - * 'rootRelation' is the partitioned table root RT index, or 0 if none + * 'rootRelation' is the partitioned/inherited table root RTI, or 0 if none * 'partColsUpdated' is true if any partitioning columns are being updated, * either from the target relation or a descendent partitioned table. * 'resultRelations' is an integer list of actual RT indexes of target rel(s) diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 9281ea90729..d08a5841b18 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -2369,7 +2369,7 @@ typedef struct ModifyTablePath CmdType operation; /* INSERT, UPDATE, or DELETE */ bool canSetTag; /* do we set the command tag/es_processed? */ Index nominalRelation; /* Parent RT index for use of EXPLAIN */ - Index rootRelation; /* Root RT index, if target is partitioned */ + Index rootRelation; /* Root RT index, if partitioned/inherited */ bool partColsUpdated; /* some part key in hierarchy updated? */ bool splitUpdate; /* if distribution key is updated */ List *resultRelations; /* integer list of RT indexes */ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index dd214cb9996..5392015a738 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -389,11 +389,12 @@ typedef struct ProjectSet * Apply rows produced by outer plan to result table(s), * by inserting, updating, or deleting. * - * If the originally named target table is a partitioned table, both - * nominalRelation and rootRelation contain the RT index of the partition - * root, which is not otherwise mentioned in the plan. Otherwise rootRelation - * is zero. However, nominalRelation will always be set, as it's the rel that - * EXPLAIN should claim is the INSERT/UPDATE/DELETE target. + * If the originally named target table is a partitioned table or inheritance + * tree, both nominalRelation and rootRelation contain the RT index of the + * partition root or appendrel RTE, which is not otherwise mentioned in the + * plan. Otherwise rootRelation is zero. However, nominalRelation will + * always be set, as it's the rel that EXPLAIN should claim is the + * INSERT/UPDATE/DELETE target. * * Note that rowMarks and epqParam are presumed to be valid for all the * table(s); they can't contain any info that varies across tables. @@ -405,7 +406,7 @@ typedef struct ModifyTable CmdType operation; /* INSERT, UPDATE, or DELETE */ bool canSetTag; /* do we set the command tag/es_processed? */ Index nominalRelation; /* Parent RT index for use of EXPLAIN */ - Index rootRelation; /* Root RT index, if target is partitioned */ + Index rootRelation; /* Root RT index, if partitioned/inherited */ bool partColsUpdated; /* some part key in hierarchy updated? */ bool splitUpdate; /* if it's split update */ List *resultRelations; /* integer list of RT indexes */ diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 7d20cc2bdc4..e74710dd51d 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -539,6 +539,33 @@ CREATE TEMP TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a); INSERT INTO z VALUES (NULL, 'text'); -- should fail ERROR: null value in column "aa" of relation "z" violates not-null constraint DETAIL: Failing row contains (null, text). +-- Check inherited UPDATE with first child excluded +create table some_tab (f1 int, f2 int, f3 int, check (f1 < 10) no inherit); +create table some_tab_child () inherits(some_tab); +insert into some_tab_child select i, i+1, 0 from generate_series(1,1000) i; +create index on some_tab_child(f1, f2); +-- while at it, also check that statement-level triggers fire +create function some_tab_stmt_trig_func() returns trigger as +$$begin raise notice 'updating some_tab'; return NULL; end;$$ +language plpgsql; +create trigger some_tab_stmt_trig + before update on some_tab execute function some_tab_stmt_trig_func(); +explain (costs off) +update some_tab set f3 = 11 where f1 = 12 and f2 = 13; + QUERY PLAN +------------------------------------------------------------------------------------ + Update on some_tab + Update on some_tab_child some_tab_1 + -> Result + -> Index Scan using some_tab_child_f1_f2_idx on some_tab_child some_tab_1 + Index Cond: ((f1 = 12) AND (f2 = 13)) +(5 rows) + +update some_tab set f3 = 11 where f1 = 12 and f2 = 13; +NOTICE: updating some_tab +drop table some_tab cascade; +NOTICE: drop cascades to table some_tab_child +drop function some_tab_stmt_trig_func(); -- Check inherited UPDATE with all children excluded create table some_tab (a int, b int) distributed randomly; create table some_tab_child () inherits (some_tab); diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql index e5f9b980776..c6acd74cb84 100644 --- a/src/test/regress/sql/inherit.sql +++ b/src/test/regress/sql/inherit.sql @@ -97,6 +97,25 @@ SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; CREATE TEMP TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a); INSERT INTO z VALUES (NULL, 'text'); -- should fail +-- Check inherited UPDATE with first child excluded +create table some_tab (f1 int, f2 int, f3 int, check (f1 < 10) no inherit); +create table some_tab_child () inherits(some_tab); +insert into some_tab_child select i, i+1, 0 from generate_series(1,1000) i; +create index on some_tab_child(f1, f2); +-- while at it, also check that statement-level triggers fire +create function some_tab_stmt_trig_func() returns trigger as +$$begin raise notice 'updating some_tab'; return NULL; end;$$ +language plpgsql; +create trigger some_tab_stmt_trig + before update on some_tab execute function some_tab_stmt_trig_func(); + +explain (costs off) +update some_tab set f3 = 11 where f1 = 12 and f2 = 13; +update some_tab set f3 = 11 where f1 = 12 and f2 = 13; + +drop table some_tab cascade; +drop function some_tab_stmt_trig_func(); + -- Check inherited UPDATE with all children excluded create table some_tab (a int, b int) distributed randomly; create table some_tab_child () inherits (some_tab); From c6e0b4210dc48aee82684500476e70168613f4fd Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Fri, 27 Oct 2023 17:56:27 +0200 Subject: [PATCH 15/22] Fix overflow when calculating timestamp distance in BRIN When calculating distances for timestamp values for BRIN minmax-multi indexes, we need to be careful about overflows for extreme values. If the value overflows into a negative value, the index may be inefficient. The new regression test checks this for the timestamp type by adding a table with enough values to force range compaction/merging. The values are close to min/max, which means a risk of overflow. Fixed by converting the int64 values to double first, before calculating the distance. This prevents the overflow. We may lose some precision, of course, but that's good enough. In the worst case we build a slightly less efficient index, but for large distances this won't matter. This only affects minmax-multi indexes on timestamp columns, with ranges containing values sufficiently distant to cause an overflow. That seems like a fairly rare case in practice. Backpatch to 14, where minmax-multi indexes were introduced. Reported-by: Ashutosh Bapat Reviewed-by: Ashutosh Bapat, Dean Rasheed Backpatch-through: 14 Discussion: https://postgr.es/m/eef0ea8c-4aaa-8d0d-027f-58b1f35dd170@enterprisedb.com (cherry picked from commit 31c67145ce49103710cb37096ee8eaf368098845) --- src/backend/access/brin/brin_minmax_multi.c | 2 +- src/test/regress/expected/brin_multi.out | 15 +++++++++++++++ src/test/regress/sql/brin_multi.sql | 21 +++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c index b4e50937609..270fe7a4baf 100644 --- a/src/backend/access/brin/brin_minmax_multi.c +++ b/src/backend/access/brin/brin_minmax_multi.c @@ -2138,7 +2138,7 @@ brin_minmax_multi_distance_timestamp(PG_FUNCTION_ARGS) if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2)) PG_RETURN_FLOAT8(0); - delta = dt2 - dt1; + delta = (float8) dt2 - (float8) dt1; Assert(delta >= 0); diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out index 677fb45f1fd..e24935298bc 100644 --- a/src/test/regress/expected/brin_multi.out +++ b/src/test/regress/expected/brin_multi.out @@ -477,3 +477,18 @@ EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1; Optimizer: Postgres query optimizer (4 rows) +-- test overflows during CREATE INDEX with extreme timestamp values +CREATE TABLE brin_timestamp_test(a TIMESTAMPTZ); +SET datestyle TO iso; +-- values close to timetamp minimum +INSERT INTO brin_timestamp_test +SELECT '4713-01-01 00:00:01 BC'::timestamptz + (i || ' seconds')::interval + FROM generate_series(1,30) s(i); +-- values close to timetamp maximum +INSERT INTO brin_timestamp_test +SELECT '294276-12-01 00:00:01'::timestamptz + (i || ' seconds')::interval + FROM generate_series(1,30) s(i); +CREATE INDEX ON brin_timestamp_test USING brin (a timestamptz_minmax_multi_ops) WITH (pages_per_range=1); +DROP TABLE brin_timestamp_test; +RESET enable_seqscan; +RESET datestyle; diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql index 327a4215bf9..06ed60b3dd0 100644 --- a/src/test/regress/sql/brin_multi.sql +++ b/src/test/regress/sql/brin_multi.sql @@ -424,3 +424,24 @@ VACUUM ANALYZE brin_test_multi; EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE a = 1; -- Ensure brin index is not used when values are not correlated EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1; + +-- test overflows during CREATE INDEX with extreme timestamp values +CREATE TABLE brin_timestamp_test(a TIMESTAMPTZ); + +SET datestyle TO iso; + +-- values close to timetamp minimum +INSERT INTO brin_timestamp_test +SELECT '4713-01-01 00:00:01 BC'::timestamptz + (i || ' seconds')::interval + FROM generate_series(1,30) s(i); + +-- values close to timetamp maximum +INSERT INTO brin_timestamp_test +SELECT '294276-12-01 00:00:01'::timestamptz + (i || ' seconds')::interval + FROM generate_series(1,30) s(i); + +CREATE INDEX ON brin_timestamp_test USING brin (a timestamptz_minmax_multi_ops) WITH (pages_per_range=1); +DROP TABLE brin_timestamp_test; + +RESET enable_seqscan; +RESET datestyle; From 2172166490b7b65f73658ad0998e580bb2c6362b Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Fri, 27 Oct 2023 17:57:11 +0200 Subject: [PATCH 16/22] Fix calculation in brin_minmax_multi_distance_date When calculating the distance between date values, make sure to subtract them in the right order, i.e. (larger - smaller). The distance is used to determine which values to merge, and is expected to be a positive value. The code unfortunately did the subtraction in the opposite order, i.e. (smaller - larger), thus producing negative values and merging values the most distant values first. The resulting index is correct (i.e. produces correct results), but may be significantly less efficient. This affects all minmax-multi indexes on date columns. Backpatch to 14, where minmax-multi indexes were introduced. Reported-by: Ashutosh Bapat Reviewed-by: Ashutosh Bapat, Dean Rasheed Backpatch-through: 14 Discussion: https://postgr.es/m/eef0ea8c-4aaa-8d0d-027f-58b1f35dd170@enterprisedb.com (cherry picked from commit a6b1b84d3a1909d7268a25c44e57fa2af7b603dc) --- src/backend/access/brin/brin_minmax_multi.c | 7 ++++++- src/test/regress/expected/brin_multi.out | 20 ++++++++++++++++++++ src/test/regress/sql/brin_multi.sql | 18 ++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c index 270fe7a4baf..407b7d8992a 100644 --- a/src/backend/access/brin/brin_minmax_multi.c +++ b/src/backend/access/brin/brin_minmax_multi.c @@ -2075,13 +2075,18 @@ brin_minmax_multi_distance_uuid(PG_FUNCTION_ARGS) Datum brin_minmax_multi_distance_date(PG_FUNCTION_ARGS) { + float8 delta = 0; DateADT dateVal1 = PG_GETARG_DATEADT(0); DateADT dateVal2 = PG_GETARG_DATEADT(1); if (DATE_NOT_FINITE(dateVal1) || DATE_NOT_FINITE(dateVal2)) PG_RETURN_FLOAT8(0); - PG_RETURN_FLOAT8(dateVal1 - dateVal2); + delta = (float8) dateVal2 - (float8) dateVal1; + + Assert(delta >= 0); + + PG_RETURN_FLOAT8(delta); } /* diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out index e24935298bc..6f305c651a5 100644 --- a/src/test/regress/expected/brin_multi.out +++ b/src/test/regress/expected/brin_multi.out @@ -490,5 +490,25 @@ SELECT '294276-12-01 00:00:01'::timestamptz + (i || ' seconds')::interval FROM generate_series(1,30) s(i); CREATE INDEX ON brin_timestamp_test USING brin (a timestamptz_minmax_multi_ops) WITH (pages_per_range=1); DROP TABLE brin_timestamp_test; +-- test overflows during CREATE INDEX with extreme date values +CREATE TABLE brin_date_test(a DATE); +-- insert values close to date minimum +INSERT INTO brin_date_test SELECT '4713-01-01 BC'::date + i FROM generate_series(1, 30) s(i); +-- insert values close to date minimum +INSERT INTO brin_date_test SELECT '5874897-12-01'::date + i FROM generate_series(1, 30) s(i); +CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_per_range=1); +SET enable_seqscan = off; +-- make sure the ranges were built correctly and 2023-01-01 eliminates all +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; + QUERY PLAN +------------------------------------------------------------------------- + Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) + Recheck Cond: (a = '2023-01-01'::date) + -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '2023-01-01'::date) +(4 rows) + +DROP TABLE brin_date_test; RESET enable_seqscan; RESET datestyle; diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql index 06ed60b3dd0..f764ac931a7 100644 --- a/src/test/regress/sql/brin_multi.sql +++ b/src/test/regress/sql/brin_multi.sql @@ -443,5 +443,23 @@ SELECT '294276-12-01 00:00:01'::timestamptz + (i || ' seconds')::interval CREATE INDEX ON brin_timestamp_test USING brin (a timestamptz_minmax_multi_ops) WITH (pages_per_range=1); DROP TABLE brin_timestamp_test; +-- test overflows during CREATE INDEX with extreme date values +CREATE TABLE brin_date_test(a DATE); + +-- insert values close to date minimum +INSERT INTO brin_date_test SELECT '4713-01-01 BC'::date + i FROM generate_series(1, 30) s(i); + +-- insert values close to date minimum +INSERT INTO brin_date_test SELECT '5874897-12-01'::date + i FROM generate_series(1, 30) s(i); + +CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_per_range=1); + +SET enable_seqscan = off; + +-- make sure the ranges were built correctly and 2023-01-01 eliminates all +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; + +DROP TABLE brin_date_test; RESET enable_seqscan; RESET datestyle; From a5839de197f25dc68e5c34fdb1482f42ee8a9035 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Fri, 27 Oct 2023 17:57:28 +0200 Subject: [PATCH 17/22] Fix minmax-multi on infinite date/timestamp values Make sure that infinite values in date/timestamp columns are treated as if in infinite distance. Infinite values should not be merged with other values, leaving them as outliers. The code however returned distance 0 in this case, so that infinite values were merged first. While this does not break the index (i.e. it still produces correct query results), it may make it much less efficient. We don't need explicit handling of infinite date/timestamp values when calculating distances, because those values are represented as extreme but regular values (e.g. INT64_MIN/MAX for the timestamp type). We don't need an exact distance, just a value that is much larger than distanced between regular values. With the added cast to double values, we can simply subtract the values. The regression test queries a value in the "gap" and checks the range was properly eliminated by the BRIN index. This only affects minmax-multi indexes on timestamp/date columns with infinite values, which is not very common in practice. The affected indexes may need to be rebuilt. Backpatch to 14, where minmax-multi indexes were introduced. Reported-by: Ashutosh Bapat Reviewed-by: Ashutosh Bapat, Dean Rasheed Backpatch-through: 14 Discussion: https://postgr.es/m/eef0ea8c-4aaa-8d0d-027f-58b1f35dd170@enterprisedb.com (cherry picked from commit 346e007b1fb6a7ca456391d4781febd3c544deee) --- src/backend/access/brin/brin_minmax_multi.c | 6 --- src/test/regress/expected/brin_multi.out | 57 +++++++++++++++++++++ src/test/regress/sql/brin_multi.sql | 39 ++++++++++++++ 3 files changed, 96 insertions(+), 6 deletions(-) diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c index 407b7d8992a..9f7d6448a7c 100644 --- a/src/backend/access/brin/brin_minmax_multi.c +++ b/src/backend/access/brin/brin_minmax_multi.c @@ -2079,9 +2079,6 @@ brin_minmax_multi_distance_date(PG_FUNCTION_ARGS) DateADT dateVal1 = PG_GETARG_DATEADT(0); DateADT dateVal2 = PG_GETARG_DATEADT(1); - if (DATE_NOT_FINITE(dateVal1) || DATE_NOT_FINITE(dateVal2)) - PG_RETURN_FLOAT8(0); - delta = (float8) dateVal2 - (float8) dateVal1; Assert(delta >= 0); @@ -2140,9 +2137,6 @@ brin_minmax_multi_distance_timestamp(PG_FUNCTION_ARGS) Timestamp dt1 = PG_GETARG_TIMESTAMP(0); Timestamp dt2 = PG_GETARG_TIMESTAMP(1); - if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2)) - PG_RETURN_FLOAT8(0); - delta = (float8) dt2 - (float8) dt1; Assert(delta >= 0); diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out index 6f305c651a5..5c3b46235e4 100644 --- a/src/test/regress/expected/brin_multi.out +++ b/src/test/regress/expected/brin_multi.out @@ -509,6 +509,63 @@ SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; Index Cond: (a = '2023-01-01'::date) (4 rows) +DROP TABLE brin_date_test; +RESET enable_seqscan; +-- test handling of infinite timestamp values +CREATE TABLE brin_timestamp_test(a TIMESTAMP); +INSERT INTO brin_timestamp_test VALUES ('-infinity'), ('infinity'); +INSERT INTO brin_timestamp_test +SELECT i FROM generate_series('2000-01-01'::timestamp, '2000-02-09'::timestamp, '1 day'::interval) s(i); +CREATE INDEX ON brin_timestamp_test USING brin (a timestamp_minmax_multi_ops) WITH (pages_per_range=1); +SET enable_seqscan = off; +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_timestamp_test WHERE a = '2023-01-01'::timestamp; + QUERY PLAN +------------------------------------------------------------------------------ + Bitmap Heap Scan on brin_timestamp_test (actual rows=0 loops=1) + Recheck Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone) + -> Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone) +(4 rows) + +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_timestamp_test WHERE a = '1900-01-01'::timestamp; + QUERY PLAN +------------------------------------------------------------------------------ + Bitmap Heap Scan on brin_timestamp_test (actual rows=0 loops=1) + Recheck Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone) + -> Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone) +(4 rows) + +DROP TABLE brin_timestamp_test; +RESET enable_seqscan; +-- test handling of infinite date values +CREATE TABLE brin_date_test(a DATE); +INSERT INTO brin_date_test VALUES ('-infinity'), ('infinity'); +INSERT INTO brin_date_test SELECT '2000-01-01'::date + i FROM generate_series(1, 40) s(i); +CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_per_range=1); +SET enable_seqscan = off; +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; + QUERY PLAN +------------------------------------------------------------------------- + Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) + Recheck Cond: (a = '2023-01-01'::date) + -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '2023-01-01'::date) +(4 rows) + +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date; + QUERY PLAN +------------------------------------------------------------------------- + Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) + Recheck Cond: (a = '1900-01-01'::date) + -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '1900-01-01'::date) +(4 rows) + DROP TABLE brin_date_test; RESET enable_seqscan; RESET datestyle; diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql index f764ac931a7..3028d6f2238 100644 --- a/src/test/regress/sql/brin_multi.sql +++ b/src/test/regress/sql/brin_multi.sql @@ -460,6 +460,45 @@ SET enable_seqscan = off; EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; +DROP TABLE brin_date_test; +RESET enable_seqscan; + +-- test handling of infinite timestamp values +CREATE TABLE brin_timestamp_test(a TIMESTAMP); + +INSERT INTO brin_timestamp_test VALUES ('-infinity'), ('infinity'); +INSERT INTO brin_timestamp_test +SELECT i FROM generate_series('2000-01-01'::timestamp, '2000-02-09'::timestamp, '1 day'::interval) s(i); + +CREATE INDEX ON brin_timestamp_test USING brin (a timestamp_minmax_multi_ops) WITH (pages_per_range=1); + +SET enable_seqscan = off; + +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_timestamp_test WHERE a = '2023-01-01'::timestamp; + +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_timestamp_test WHERE a = '1900-01-01'::timestamp; + +DROP TABLE brin_timestamp_test; +RESET enable_seqscan; + +-- test handling of infinite date values +CREATE TABLE brin_date_test(a DATE); + +INSERT INTO brin_date_test VALUES ('-infinity'), ('infinity'); +INSERT INTO brin_date_test SELECT '2000-01-01'::date + i FROM generate_series(1, 40) s(i); + +CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_per_range=1); + +SET enable_seqscan = off; + +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; + +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date; + DROP TABLE brin_date_test; RESET enable_seqscan; RESET datestyle; From 9a4413cade01447f80094b7aea89adaa7d43d385 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Fri, 27 Oct 2023 17:57:44 +0200 Subject: [PATCH 18/22] Fix minmax-multi distance for extreme interval values When calculating distance for interval values, the code mostly mimicked interval_mi, i.e. it built a new interval value for the difference. That however does not work for sufficiently distant interval values, when the difference overflows the interval range. Instead, we can calculate the distance directly, without constructing the intermediate (and unnecessary) interval value. Backpatch to 14, where minmax-multi indexes were introduced. Reported-by: Dean Rasheed Reviewed-by: Ashutosh Bapat, Dean Rasheed Backpatch-through: 14 Discussion: https://postgr.es/m/eef0ea8c-4aaa-8d0d-027f-58b1f35dd170@enterprisedb.com (cherry picked from commit 1487acb539573afcb5d3566c2381443f39ab36f3) --- src/backend/access/brin/brin_minmax_multi.c | 33 +++------------------ src/test/regress/expected/brin_multi.out | 29 ++++++++++++++++++ src/test/regress/sql/brin_multi.sql | 21 +++++++++++++ 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c index 9f7d6448a7c..1e1159d6382 100644 --- a/src/backend/access/brin/brin_minmax_multi.c +++ b/src/backend/access/brin/brin_minmax_multi.c @@ -2154,45 +2154,20 @@ brin_minmax_multi_distance_interval(PG_FUNCTION_ARGS) Interval *ia = PG_GETARG_INTERVAL_P(0); Interval *ib = PG_GETARG_INTERVAL_P(1); - Interval *result; int64 dayfraction; int64 days; - result = (Interval *) palloc(sizeof(Interval)); - - result->month = ib->month - ia->month; - /* overflow check copied from int4mi */ - if (!SAMESIGN(ib->month, ia->month) && - !SAMESIGN(result->month, ib->month)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("interval out of range"))); - - result->day = ib->day - ia->day; - if (!SAMESIGN(ib->day, ia->day) && - !SAMESIGN(result->day, ib->day)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("interval out of range"))); - - result->time = ib->time - ia->time; - if (!SAMESIGN(ib->time, ia->time) && - !SAMESIGN(result->time, ib->time)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("interval out of range"))); - /* * Delta is (fractional) number of days between the intervals. Assume * months have 30 days for consistency with interval_cmp_internal. We * don't need to be exact, in the worst case we'll build a bit less * efficient ranges. But we should not contradict interval_cmp. */ - dayfraction = result->time % USECS_PER_DAY; - days = result->time / USECS_PER_DAY; - days += result->month * INT64CONST(30); - days += result->day; + dayfraction = (ib->time % USECS_PER_DAY) - (ia->time % USECS_PER_DAY); + days = (ib->time / USECS_PER_DAY) - (ia->time / USECS_PER_DAY); + days += (int64) ib->day - (int64) ia->day; + days += ((int64) ib->month - (int64) ia->month) * INT64CONST(30); /* convert to double precision */ delta = (double) days + dayfraction / (double) USECS_PER_DAY; diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out index 5c3b46235e4..987f010273b 100644 --- a/src/test/regress/expected/brin_multi.out +++ b/src/test/regress/expected/brin_multi.out @@ -569,3 +569,32 @@ SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date; DROP TABLE brin_date_test; RESET enable_seqscan; RESET datestyle; +-- test handling of overflow for interval values +CREATE TABLE brin_interval_test(a INTERVAL); +INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series(-178000000, -177999980) s(i); +INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series( 177999980, 178000000) s(i); +CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1); +SET enable_seqscan = off; +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval; + QUERY PLAN +----------------------------------------------------------------------------- + Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1) + Recheck Cond: (a = '@ 30 years ago'::interval) + -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '@ 30 years ago'::interval) +(4 rows) + +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_interval_test WHERE a = '30 years'::interval; + QUERY PLAN +----------------------------------------------------------------------------- + Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1) + Recheck Cond: (a = '@ 30 years'::interval) + -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '@ 30 years'::interval) +(4 rows) + +DROP TABLE brin_interval_test; +RESET enable_seqscan; +RESET datestyle; diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql index 3028d6f2238..8051ec997e4 100644 --- a/src/test/regress/sql/brin_multi.sql +++ b/src/test/regress/sql/brin_multi.sql @@ -502,3 +502,24 @@ SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date; DROP TABLE brin_date_test; RESET enable_seqscan; RESET datestyle; + +-- test handling of overflow for interval values +CREATE TABLE brin_interval_test(a INTERVAL); + +INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series(-178000000, -177999980) s(i); + +INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series( 177999980, 178000000) s(i); + +CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1); + +SET enable_seqscan = off; + +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval; + +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_interval_test WHERE a = '30 years'::interval; + +DROP TABLE brin_interval_test; +RESET enable_seqscan; +RESET datestyle; From 2340d3f480dea6b5482414fe39cac1f3ec6f9d09 Mon Sep 17 00:00:00 2001 From: Noah Misch Date: Mon, 30 Oct 2023 14:46:05 -0700 Subject: [PATCH 19/22] amcheck: Distinguish interrupted page deletion from corruption. This prevents false-positive reports about "the first child of leftmost target page is not leftmost of its level", "block %u is not leftmost" and "left link/right link pair". They appeared if amcheck ran before VACUUM cleaned things, after a cluster exited recovery between the first-stage and second-stage WAL records of a deletion. Back-patch to v11 (all supported versions). Reviewed by Peter Geoghegan. Discussion: https://postgr.es/m/20231005025232.c7.nmisch@google.com (cherry picked from commit 6eb1b293b3c8c9a544272d3d7ff72dc42ed94879) --- contrib/amcheck/t/005_pitr.pl | 89 +++++++++++++++++++++++++++++++++ contrib/amcheck/verify_nbtree.c | 83 ++++++++++++++++++++++++++++-- 2 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 contrib/amcheck/t/005_pitr.pl diff --git a/contrib/amcheck/t/005_pitr.pl b/contrib/amcheck/t/005_pitr.pl new file mode 100644 index 00000000000..07187a799be --- /dev/null +++ b/contrib/amcheck/t/005_pitr.pl @@ -0,0 +1,89 @@ +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +# Test integrity of intermediate states by PITR to those states +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# origin node: generate WAL records of interest. +my $origin = PostgreSQL::Test::Cluster->new('origin'); +$origin->init(has_archiving => 1, allows_streaming => 1); +$origin->append_conf('postgresql.conf', 'autovacuum = off'); +$origin->start; +$origin->backup('my_backup'); +# Create a table with each of 6 PK values spanning 1/4 of a block. Delete the +# first four, so one index leaf is eligible for deletion. Make a replication +# slot just so pg_waldump will always have access to later WAL. +my $setup = <safe_psql('postgres', $setup); +my $before_vacuum_walfile = + $origin->safe_psql('postgres', "SELECT pg_walfile_name(pg_current_wal_lsn())"); +# VACUUM to delete the aforementioned leaf page. Force an XLogFlush() by +# dropping a permanent table. That way, the XLogReader infrastructure can +# always see VACUUM's records, even under synchronous_commit=off. Finally, +# find the LSN of that VACUUM's last UNLINK_PAGE record. +my $vacuum = <safe_psql('postgres', $vacuum); +$origin->stop; +my $unlink_lsn = do { + local %ENV = $origin->_get_env(); + my $stdout; + run_log(['pg_waldump', '-p', $origin->data_dir . '/pg_wal', + $before_vacuum_walfile, $after_unlink_walfile], + '>', \$stdout); + $stdout =~ m|^rmgr: Btree .*, lsn: ([/0-9A-F]+), .*, desc: UNLINK_PAGE left|m; + $1; +}; +die "did not find UNLINK_PAGE record" unless $unlink_lsn; + +# replica node: amcheck at notable points in the WAL stream +my $replica = PostgreSQL::Test::Cluster->new('replica'); +$replica->init_from_backup($origin, 'my_backup', has_restoring => 1); +$replica->append_conf('postgresql.conf', + "recovery_target_lsn = '$unlink_lsn'"); +$replica->append_conf('postgresql.conf', 'recovery_target_inclusive = off'); +$replica->append_conf('postgresql.conf', 'recovery_target_action = promote'); +$replica->start; +$replica->poll_query_until('postgres', "SELECT pg_is_in_recovery() = 'f';") + or die "Timed out while waiting for PITR promotion"; +# recovery done; run amcheck +my $debug = "SET client_min_messages = 'debug1'"; +my ($rc, $stderr); +$rc = $replica->psql( + 'postgres', + "$debug; SELECT bt_index_parent_check('not_leftmost_pk', true)", + stderr => \$stderr); +print STDERR $stderr, "\n"; +is($rc, 0, "bt_index_parent_check passes"); +like( + $stderr, + qr/interrupted page deletion detected/, + "bt_index_parent_check: interrupted page deletion detected"); +$rc = $replica->psql( + 'postgres', + "$debug; SELECT bt_index_check('not_leftmost_pk', true)", + stderr => \$stderr); +print STDERR $stderr, "\n"; +is($rc, 0, "bt_index_check passes"); + +done_testing(); diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c index 6c3b3c27ccc..a1b581871e6 100644 --- a/contrib/amcheck/verify_nbtree.c +++ b/contrib/amcheck/verify_nbtree.c @@ -146,6 +146,9 @@ static void bt_check_every_level(Relation rel, Relation heaprel, bool rootdescend); static BtreeLevel bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level); +static bool bt_leftmost_ignoring_half_dead(BtreeCheckState *state, + BlockNumber start, + BTPageOpaque start_opaque); static void bt_recheck_sibling_links(BtreeCheckState *state, BlockNumber btpo_prev_from_target, BlockNumber leftcurrent); @@ -775,7 +778,7 @@ bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level) */ if (state->readonly) { - if (!P_LEFTMOST(opaque)) + if (!bt_leftmost_ignoring_half_dead(state, current, opaque)) ereport(ERROR, (errcode(ERRCODE_INDEX_CORRUPTED), errmsg("block %u is not leftmost in index \"%s\"", @@ -829,8 +832,16 @@ bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level) */ } - /* Sibling links should be in mutual agreement */ - if (opaque->btpo_prev != leftcurrent) + /* + * Sibling links should be in mutual agreement. There arises + * leftcurrent == P_NONE && btpo_prev != P_NONE when the left sibling + * of the parent's low-key downlink is half-dead. (A half-dead page + * has no downlink from its parent.) Under heavyweight locking, the + * last bt_leftmost_ignoring_half_dead() validated this btpo_prev. + * Without heavyweight locking, validation of the P_NONE case remains + * unimplemented. + */ + if (opaque->btpo_prev != leftcurrent && leftcurrent != P_NONE) bt_recheck_sibling_links(state, opaque->btpo_prev, leftcurrent); /* Check level */ @@ -911,6 +922,66 @@ bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level) return nextleveldown; } +/* + * Like P_LEFTMOST(start_opaque), but accept an arbitrarily-long chain of + * half-dead, sibling-linked pages to the left. If a half-dead page appears + * under state->readonly, the database exited recovery between the first-stage + * and second-stage WAL records of a deletion. + */ +static bool +bt_leftmost_ignoring_half_dead(BtreeCheckState *state, + BlockNumber start, + BTPageOpaque start_opaque) +{ + BlockNumber reached = start_opaque->btpo_prev, + reached_from = start; + bool all_half_dead = true; + + /* + * To handle the !readonly case, we'd need to accept BTP_DELETED pages and + * potentially observe nbtree/README "Page deletion and backwards scans". + */ + Assert(state->readonly); + + while (reached != P_NONE && all_half_dead) + { + Page page = palloc_btree_page(state, reached); + BTPageOpaque reached_opaque = (BTPageOpaque) PageGetSpecialPointer(page); + + CHECK_FOR_INTERRUPTS(); + + /* + * Try to detect btpo_prev circular links. _bt_unlink_halfdead_page() + * writes that side-links will continue to point to the siblings. + * Check btpo_next for that property. + */ + all_half_dead = P_ISHALFDEAD(reached_opaque) && + reached != start && + reached != reached_from && + reached_opaque->btpo_next == reached_from; + if (all_half_dead) + { + XLogRecPtr pagelsn = PageGetLSN(page); + + /* pagelsn should point to an XLOG_BTREE_MARK_PAGE_HALFDEAD */ + ereport(DEBUG1, + (errcode(ERRCODE_NO_DATA), + errmsg_internal("harmless interrupted page deletion detected in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Block=%u right block=%u page lsn=%X/%X.", + reached, reached_from, + LSN_FORMAT_ARGS(pagelsn)))); + + reached_from = reached; + reached = reached_opaque->btpo_prev; + } + + pfree(page); + } + + return all_half_dead; +} + /* * Raise an error when target page's left link does not point back to the * previous target page, called leftcurrent here. The leftcurrent page's @@ -951,6 +1022,9 @@ bt_recheck_sibling_links(BtreeCheckState *state, BlockNumber btpo_prev_from_target, BlockNumber leftcurrent) { + /* taking BTPageOpaque from metapage would give irrelevant findings */ + Assert(leftcurrent != P_NONE); + if (!state->readonly) { Buffer lbuf; @@ -1934,7 +2008,8 @@ bt_child_highkey_check(BtreeCheckState *state, opaque = (BTPageOpaque) PageGetSpecialPointer(page); /* The first page we visit at the level should be leftmost */ - if (first && !BlockNumberIsValid(state->prevrightlink) && !P_LEFTMOST(opaque)) + if (first && !BlockNumberIsValid(state->prevrightlink) && + !bt_leftmost_ignoring_half_dead(state, blkno, opaque)) ereport(ERROR, (errcode(ERRCODE_INDEX_CORRUPTED), errmsg("the first child of leftmost target page is not leftmost of its level in index \"%s\"", From 5dd7097beadbbcd6c613712b7771f2ec60368a03 Mon Sep 17 00:00:00 2001 From: reshke Date: Tue, 22 Sep 2026 22:44:52 +0300 Subject: [PATCH 20/22] Update regression expected files to match new backports Squashed fixups for the partition_pruning/inheritance/amcheck/BRIN backports: adjust expected outputs for CBDB plans (Gather Motion, Settings/Optimizer lines), honest row footers instead of hand-written GP_IGNORE placeholders, opr_sanity for new btequalimage/interval amproc entries (including singlenode/pax variants), and BRIN minmax-multi expected fixes. --- .../src/test/regress/expected/opr_sanity.out | 3 +- src/test/regress/expected/brin_multi.out | 105 +- .../expected/brin_multi_optimizer_1.out | 147 ++- src/test/regress/expected/inherit.out | 595 ++++++--- .../regress/expected/inherit_optimizer.out | 681 ++++++---- src/test/regress/expected/opr_sanity.out | 8 +- src/test/regress/expected/partition_prune.out | 453 +++++-- .../expected/partition_prune_optimizer.out | 1130 +++++++++++++---- .../expected/opr_sanity.out | 3 +- 9 files changed, 2316 insertions(+), 809 deletions(-) diff --git a/contrib/pax_storage/src/test/regress/expected/opr_sanity.out b/contrib/pax_storage/src/test/regress/expected/opr_sanity.out index 2fb7ba06584..d3bc9936213 100644 --- a/contrib/pax_storage/src/test/regress/expected/opr_sanity.out +++ b/contrib/pax_storage/src/test/regress/expected/opr_sanity.out @@ -2231,6 +2231,7 @@ ORDER BY 1, 2, 3; | complex_ops | complex_ops | complex | float_ops | float4_ops | real | float_ops | float8_ops | double precision + | interval_ops | interval_ops | interval | jsonb_ops | jsonb_ops | jsonb | multirange_ops | multirange_ops | anymultirange | numeric_ops | numeric_ops | numeric @@ -2239,7 +2240,7 @@ ORDER BY 1, 2, 3; | record_ops | record_ops | record | tsquery_ops | tsquery_ops | tsquery | tsvector_ops | tsvector_ops | tsvector -(16 rows) +(17 rows) -- **************** pg_index **************** -- Look for illegal values in pg_index fields. diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out index 987f010273b..0075a17569a 100644 --- a/src/test/regress/expected/brin_multi.out +++ b/src/test/regress/expected/brin_multi.out @@ -501,13 +501,14 @@ SET enable_seqscan = off; -- make sure the ranges were built correctly and 2023-01-01 eliminates all EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; - QUERY PLAN -------------------------------------------------------------------------- - Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) - Recheck Cond: (a = '2023-01-01'::date) - -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) - Index Cond: (a = '2023-01-01'::date) -(4 rows) +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) + Recheck Cond: (a = '2023-01-01'::date) + -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '2023-01-01'::date) +GP_IGNORE:(6 rows) DROP TABLE brin_date_test; RESET enable_seqscan; @@ -520,23 +521,25 @@ CREATE INDEX ON brin_timestamp_test USING brin (a timestamp_minmax_multi_ops) WI SET enable_seqscan = off; EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_timestamp_test WHERE a = '2023-01-01'::timestamp; - QUERY PLAN ------------------------------------------------------------------------------- - Bitmap Heap Scan on brin_timestamp_test (actual rows=0 loops=1) - Recheck Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone) - -> Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0 loops=1) - Index Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone) -(4 rows) +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_timestamp_test (actual rows=0 loops=1) + Recheck Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone) + -> Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone) +GP_IGNORE:(6 rows) EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_timestamp_test WHERE a = '1900-01-01'::timestamp; - QUERY PLAN ------------------------------------------------------------------------------- - Bitmap Heap Scan on brin_timestamp_test (actual rows=0 loops=1) - Recheck Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone) - -> Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0 loops=1) - Index Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone) -(4 rows) +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_timestamp_test (actual rows=0 loops=1) + Recheck Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone) + -> Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone) +GP_IGNORE:(6 rows) DROP TABLE brin_timestamp_test; RESET enable_seqscan; @@ -548,23 +551,25 @@ CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_ SET enable_seqscan = off; EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; - QUERY PLAN -------------------------------------------------------------------------- - Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) - Recheck Cond: (a = '2023-01-01'::date) - -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) - Index Cond: (a = '2023-01-01'::date) -(4 rows) +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) + Recheck Cond: (a = '2023-01-01'::date) + -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '2023-01-01'::date) +GP_IGNORE:(6 rows) EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date; - QUERY PLAN -------------------------------------------------------------------------- - Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) - Recheck Cond: (a = '1900-01-01'::date) - -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) - Index Cond: (a = '1900-01-01'::date) -(4 rows) +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) + Recheck Cond: (a = '1900-01-01'::date) + -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '1900-01-01'::date) +GP_IGNORE:(6 rows) DROP TABLE brin_date_test; RESET enable_seqscan; @@ -577,23 +582,25 @@ CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH SET enable_seqscan = off; EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval; - QUERY PLAN ------------------------------------------------------------------------------ - Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1) - Recheck Cond: (a = '@ 30 years ago'::interval) - -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1) - Index Cond: (a = '@ 30 years ago'::interval) -(4 rows) +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1) + Recheck Cond: (a = '@ 30 years ago'::interval) + -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '@ 30 years ago'::interval) +GP_IGNORE:(6 rows) EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_interval_test WHERE a = '30 years'::interval; - QUERY PLAN ------------------------------------------------------------------------------ - Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1) - Recheck Cond: (a = '@ 30 years'::interval) - -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1) - Index Cond: (a = '@ 30 years'::interval) -(4 rows) +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1) + Recheck Cond: (a = '@ 30 years'::interval) + -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '@ 30 years'::interval) +GP_IGNORE:(6 rows) DROP TABLE brin_interval_test; RESET enable_seqscan; diff --git a/src/test/regress/expected/brin_multi_optimizer_1.out b/src/test/regress/expected/brin_multi_optimizer_1.out index d56000ba8fb..64cd538de0e 100644 --- a/src/test/regress/expected/brin_multi_optimizer_1.out +++ b/src/test/regress/expected/brin_multi_optimizer_1.out @@ -603,31 +603,152 @@ CREATE INDEX brin_test_multi_a_idx ON brin_test_multi USING brin (a) WITH (pages CREATE INDEX brin_test_multi_b_idx ON brin_test_multi USING brin (b) WITH (pages_per_range = 2); VACUUM ANALYZE brin_test_multi; -- Ensure brin index is used when columns are perfectly correlated ---start_ignore ---GPDB_14_MERGE_FIXME ---It should choose bitmap index scan, but seq scan here, which is caused by ---inaccurate index correlation calculation in compute_scalar_stats. ---end_ignore EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE a = 1; - QUERY PLAN --------------------------------------------------------- +QUERY PLAN +___________ Gather Motion 1:1 (slice1; segments: 1) -> Bitmap Heap Scan on brin_test_multi Recheck Cond: (a = 1) -> Bitmap Index Scan on brin_test_multi_a_idx Index Cond: (a = 1) - Optimizer: Pivotal Optimizer (GPORCA) -(6 rows) +GP_IGNORE:(6 rows) -- Ensure brin index is not used when values are not correlated EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1; - QUERY PLAN --------------------------------------------------------- +QUERY PLAN +___________ Gather Motion 3:1 (slice1; segments: 3) -> Bitmap Heap Scan on brin_test_multi Recheck Cond: (b = 1) -> Bitmap Index Scan on brin_test_multi_b_idx Index Cond: (b = 1) - Optimizer: Pivotal Optimizer (GPORCA) -(6 rows) +GP_IGNORE:(6 rows) +-- test overflows during CREATE INDEX with extreme timestamp values +CREATE TABLE brin_timestamp_test(a TIMESTAMPTZ); +SET datestyle TO iso; +-- values close to timetamp minimum +INSERT INTO brin_timestamp_test +SELECT '4713-01-01 00:00:01 BC'::timestamptz + (i || ' seconds')::interval + FROM generate_series(1,30) s(i); +-- values close to timetamp maximum +INSERT INTO brin_timestamp_test +SELECT '294276-12-01 00:00:01'::timestamptz + (i || ' seconds')::interval + FROM generate_series(1,30) s(i); +CREATE INDEX ON brin_timestamp_test USING brin (a timestamptz_minmax_multi_ops) WITH (pages_per_range=1); +DROP TABLE brin_timestamp_test; +-- test overflows during CREATE INDEX with extreme date values +CREATE TABLE brin_date_test(a DATE); +-- insert values close to date minimum +INSERT INTO brin_date_test SELECT '4713-01-01 BC'::date + i FROM generate_series(1, 30) s(i); +-- insert values close to date minimum +INSERT INTO brin_date_test SELECT '5874897-12-01'::date + i FROM generate_series(1, 30) s(i); +CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_per_range=1); +SET enable_seqscan = off; +-- make sure the ranges were built correctly and 2023-01-01 eliminates all +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) + Recheck Cond: (a = '2023-01-01'::date) + -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '2023-01-01'::date) +GP_IGNORE:(6 rows) + +DROP TABLE brin_date_test; +RESET enable_seqscan; +-- test handling of infinite timestamp values +CREATE TABLE brin_timestamp_test(a TIMESTAMP); +INSERT INTO brin_timestamp_test VALUES ('-infinity'), ('infinity'); +INSERT INTO brin_timestamp_test +SELECT i FROM generate_series('2000-01-01'::timestamp, '2000-02-09'::timestamp, '1 day'::interval) s(i); +CREATE INDEX ON brin_timestamp_test USING brin (a timestamp_minmax_multi_ops) WITH (pages_per_range=1); +SET enable_seqscan = off; +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_timestamp_test WHERE a = '2023-01-01'::timestamp; +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_timestamp_test (actual rows=0 loops=1) + Recheck Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone) + -> Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone) +GP_IGNORE:(6 rows) + +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_timestamp_test WHERE a = '1900-01-01'::timestamp; +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_timestamp_test (actual rows=0 loops=1) + Recheck Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone) + -> Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone) +GP_IGNORE:(6 rows) + +DROP TABLE brin_timestamp_test; +RESET enable_seqscan; +-- test handling of infinite date values +CREATE TABLE brin_date_test(a DATE); +INSERT INTO brin_date_test VALUES ('-infinity'), ('infinity'); +INSERT INTO brin_date_test SELECT '2000-01-01'::date + i FROM generate_series(1, 40) s(i); +CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_per_range=1); +SET enable_seqscan = off; +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) + Recheck Cond: (a = '2023-01-01'::date) + -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '2023-01-01'::date) +GP_IGNORE:(6 rows) + +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date; +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) + Recheck Cond: (a = '1900-01-01'::date) + -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '1900-01-01'::date) +GP_IGNORE:(6 rows) + +DROP TABLE brin_date_test; +RESET enable_seqscan; +RESET datestyle; +-- test handling of overflow for interval values +CREATE TABLE brin_interval_test(a INTERVAL); +INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series(-178000000, -177999980) s(i); +INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series( 177999980, 178000000) s(i); +CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1); +SET enable_seqscan = off; +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval; +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1) + Recheck Cond: (a = '@ 30 years ago'::interval) + -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '@ 30 years ago'::interval) +GP_IGNORE:(6 rows) + +EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) +SELECT * FROM brin_interval_test WHERE a = '30 years'::interval; +QUERY PLAN +___________ + Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) + -> Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1) + Recheck Cond: (a = '@ 30 years'::interval) + -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1) + Index Cond: (a = '@ 30 years'::interval) +GP_IGNORE:(6 rows) + +DROP TABLE brin_interval_test; +RESET enable_seqscan; +RESET datestyle; diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index e74710dd51d..042108476da 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -3,8 +3,11 @@ -- CREATE TABLE a (aa TEXT) distributed randomly; CREATE TABLE b (bb TEXT) INHERITS (a); +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE c (cc TEXT) INHERITS (a); +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE d (dd TEXT) INHERITS (b,c,a); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "aa" NOTICE: merging multiple inherited definitions of column "aa" INSERT INTO a(aa) VALUES('aaa'); @@ -34,119 +37,119 @@ INSERT INTO d(aa) VALUES('dddddddd'); SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; relname | aa ---------+---------- - a | aaa a | aaaa - a | aaaaa - a | aaaaaa a | aaaaaaa + b | bbbb + b | bbbbbbb + c | cccc + c | ccccccc + d | ddddddd + d | dddddddd + a | aaa + a | aaaaaa a | aaaaaaaa b | bbb - b | bbbb b | bbbbb - b | bbbbbb - b | bbbbbbb b | bbbbbbbb c | ccc - c | cccc + d | dddd + d | ddddd + d | dddddd + a | aaaaa + b | bbbbbb c | ccccc c | cccccc - c | ccccccc c | cccccccc d | ddd - d | dddd - d | ddddd - d | dddddd - d | ddddddd - d | dddddddd (24 rows) SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; relname | aa | bb ---------+----------+---- - b | bbb | - b | bbbb | - b | bbbbb | b | bbbbbb | + d | ddd | + b | bbbb | b | bbbbbbb | + d | ddddddd | + d | dddddddd | + b | bbb | + b | bbbbb | b | bbbbbbbb | - d | ddd | d | dddd | d | ddddd | d | dddddd | - d | ddddddd | - d | dddddddd | (12 rows) SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid; relname | aa | cc ---------+----------+---- - c | ccc | - c | cccc | c | ccccc | c | cccccc | - c | ccccccc | c | cccccccc | d | ddd | + c | cccc | + c | ccccccc | + d | ddddddd | + d | dddddddd | + c | ccc | d | dddd | d | ddddd | d | dddddd | - d | ddddddd | - d | dddddddd | (12 rows) SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid; relname | aa | bb | cc | dd ---------+----------+----+----+---- - d | ddd | | | d | dddd | | | d | ddddd | | | d | dddddd | | | d | ddddddd | | | d | dddddddd | | | + d | ddd | | | (6 rows) SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid; relname | aa ---------+---------- a | aaa - a | aaaa - a | aaaaa a | aaaaaa - a | aaaaaaa a | aaaaaaaa + a | aaaa + a | aaaaaaa + a | aaaaa (6 rows) SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid; relname | aa | bb ---------+----------+---- + b | bbbbbb | b | bbb | - b | bbbb | b | bbbbb | - b | bbbbbb | - b | bbbbbbb | b | bbbbbbbb | + b | bbbb | + b | bbbbbbb | (6 rows) SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid; relname | aa | cc ---------+----------+---- - c | ccc | - c | cccc | c | ccccc | c | cccccc | - c | ccccccc | c | cccccccc | + c | ccc | + c | cccc | + c | ccccccc | (6 rows) SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; relname | aa | bb | cc | dd ---------+----------+----+----+---- - d | ddd | | | d | dddd | | | d | ddddd | | | d | dddddd | | | d | ddddddd | | | d | dddddddd | | | + d | ddd | | | (6 rows) UPDATE a SET aa='zzzz' WHERE aa='aaaa'; @@ -158,81 +161,81 @@ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; relname | aa ---------+---------- a | zzzz - a | zzzzz a | zzzzzz + b | bbbb + b | bbbbbbb + c | cccc + c | ccccccc + d | ddddddd + d | dddddddd a | zzzzzz a | zzzzzz a | zzzzzz b | bbb - b | bbbb b | bbbbb - b | bbbbbb - b | bbbbbbb b | bbbbbbbb c | ccc - c | cccc + d | dddd + d | ddddd + d | dddddd + a | zzzzz + b | bbbbbb c | ccccc c | cccccc - c | ccccccc c | cccccccc d | ddd - d | dddd - d | ddddd - d | dddddd - d | ddddddd - d | dddddddd (24 rows) SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; relname | aa | bb ---------+----------+---- - b | bbb | b | bbbb | - b | bbbbb | - b | bbbbbb | b | bbbbbbb | + d | ddddddd | + d | dddddddd | + b | bbb | + b | bbbbb | b | bbbbbbbb | - d | ddd | d | dddd | d | ddddd | d | dddddd | - d | ddddddd | - d | dddddddd | + b | bbbbbb | + d | ddd | (12 rows) SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid; relname | aa | cc ---------+----------+---- - c | ccc | c | cccc | + c | ccccccc | + d | ddddddd | + d | dddddddd | c | ccccc | c | cccccc | - c | ccccccc | c | cccccccc | d | ddd | + c | ccc | d | dddd | d | ddddd | d | dddddd | - d | ddddddd | - d | dddddddd | (12 rows) SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid; relname | aa | bb | cc | dd ---------+----------+----+----+---- - d | ddd | | | + d | ddddddd | | | + d | dddddddd | | | d | dddd | | | d | ddddd | | | d | dddddd | | | - d | ddddddd | | | - d | dddddddd | | | + d | ddd | | | (6 rows) SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid; relname | aa ---------+-------- - a | zzzz a | zzzzz + a | zzzz a | zzzzzz a | zzzzzz a | zzzzzz @@ -243,11 +246,11 @@ SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid; relname | aa | bb ---------+----------+---- b | bbb | - b | bbbb | b | bbbbb | + b | bbbbbbbb | b | bbbbbb | + b | bbbb | b | bbbbbbb | - b | bbbbbbbb | (6 rows) SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid; @@ -255,19 +258,19 @@ SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid; ---------+----------+---- c | ccc | c | cccc | + c | ccccccc | c | ccccc | c | cccccc | - c | ccccccc | c | cccccccc | (6 rows) SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; relname | aa | bb | cc | dd ---------+----------+----+----+---- - d | ddd | | | d | dddd | | | d | ddddd | | | d | dddddd | | | + d | ddd | | | d | ddddddd | | | d | dddddddd | | | (6 rows) @@ -276,28 +279,28 @@ UPDATE b SET aa='new'; SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; relname | aa ---------+---------- - a | zzzz - a | zzzzz - a | zzzzzz a | zzzzzz a | zzzzzz a | zzzzzz b | new b | new b | new - b | new - b | new - b | new c | ccc - c | cccc - c | ccccc - c | cccccc - c | ccccccc - c | cccccccc d | new d | new d | new + a | zzzzz + b | new + c | ccccc + c | cccccc + c | cccccccc d | new + a | zzzz + a | zzzzzz + b | new + b | new + c | cccc + c | ccccccc d | new d | new (24 rows) @@ -307,15 +310,15 @@ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; ---------+-----+---- b | new | b | new | + d | new | + d | new | b | new | b | new | b | new | - b | new | - d | new | - d | new | d | new | d | new | d | new | + b | new | d | new | (12 rows) @@ -323,15 +326,15 @@ SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid; relname | aa | cc ---------+----------+---- c | ccc | - c | cccc | - c | ccccc | - c | cccccc | - c | ccccccc | - c | cccccccc | d | new | d | new | d | new | + c | ccccc | + c | cccccc | + c | cccccccc | d | new | + c | cccc | + c | ccccccc | d | new | d | new | (12 rows) @@ -350,11 +353,11 @@ SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid; SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid; relname | aa ---------+-------- - a | zzzz - a | zzzzz a | zzzzzz a | zzzzzz a | zzzzzz + a | zzzzz + a | zzzz a | zzzzzz (6 rows) @@ -373,11 +376,11 @@ SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid; relname | aa | cc ---------+----------+---- c | ccc | - c | cccc | c | ccccc | c | cccccc | - c | ccccccc | c | cccccccc | + c | cccc | + c | ccccccc | (6 rows) SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; @@ -399,19 +402,19 @@ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; a | new a | new a | new - a | new - a | new - a | new - b | new - b | new - b | new b | new b | new b | new d | new d | new d | new + a | new + b | new d | new + a | new + a | new + b | new + b | new d | new d | new (18 rows) @@ -419,17 +422,17 @@ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; relname | aa | bb ---------+-----+---- - b | new | - b | new | - b | new | b | new | b | new | b | new | d | new | d | new | d | new | + b | new | + b | new | d | new | d | new | + b | new | d | new | (12 rows) @@ -537,11 +540,14 @@ SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; -- Confirm PRIMARY KEY adds NOT NULL constraint to child table CREATE TEMP TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a); INSERT INTO z VALUES (NULL, 'text'); -- should fail -ERROR: null value in column "aa" of relation "z" violates not-null constraint +ERROR: null value in column "aa" of relation "z" violates not-null constraint (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3236638) DETAIL: Failing row contains (null, text). -- Check inherited UPDATE with first child excluded create table some_tab (f1 int, f2 int, f3 int, check (f1 < 10) no inherit); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table some_tab_child () inherits(some_tab); +NOTICE: table has parent, setting distribution columns to match parent table insert into some_tab_child select i, i+1, 0 from generate_series(1,1000) i; create index on some_tab_child(f1, f2); -- while at it, also check that statement-level triggers fire @@ -550,6 +556,7 @@ $$begin raise notice 'updating some_tab'; return NULL; end;$$ language plpgsql; create trigger some_tab_stmt_trig before update on some_tab execute function some_tab_stmt_trig_func(); +ERROR: Triggers for statements are not yet supported explain (costs off) update some_tab set f3 = 11 where f1 = 12 and f2 = 13; QUERY PLAN @@ -559,16 +566,17 @@ update some_tab set f3 = 11 where f1 = 12 and f2 = 13; -> Result -> Index Scan using some_tab_child_f1_f2_idx on some_tab_child some_tab_1 Index Cond: ((f1 = 12) AND (f2 = 13)) -(5 rows) + Optimizer: Postgres query optimizer +(6 rows) update some_tab set f3 = 11 where f1 = 12 and f2 = 13; -NOTICE: updating some_tab drop table some_tab cascade; NOTICE: drop cascades to table some_tab_child drop function some_tab_stmt_trig_func(); -- Check inherited UPDATE with all children excluded create table some_tab (a int, b int) distributed randomly; create table some_tab_child () inherits (some_tab); +NOTICE: table has parent, setting distribution columns to match parent table insert into some_tab_child values(1,2); explain (verbose, costs off) update some_tab set a = a + 1 where false; @@ -578,8 +586,9 @@ update some_tab set a = a + 1 where false; -> Result Output: (some_tab.a + 1), NULL::oid, NULL::tid, NULL::integer One-Time Filter: false + Settings: optimizer = 'off' Optimizer: Postgres query optimizer -(5 rows) +(6 rows) update some_tab set a = a + 1 where false; explain (verbose, costs off) @@ -593,8 +602,9 @@ update some_tab set a = a + 1 where false returning b, a; -> Result Output: (some_tab.a + 1), NULL::oid, NULL::tid, NULL::integer One-Time Filter: false + Settings: optimizer = 'off' Optimizer: Postgres query optimizer -(8 rows) +(9 rows) update some_tab set a = a + 1 where false returning b, a; b | a @@ -611,9 +621,15 @@ drop table some_tab cascade; NOTICE: drop cascades to table some_tab_child -- Check UPDATE with inherited target and an inherited source table create temp table foo(f1 int, f2 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table foo2(f3 int) inherits (foo); +NOTICE: table has parent, setting distribution columns to match parent table create temp table bar(f1 int, f2 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table bar2(f3 int) inherits (bar); +NOTICE: table has parent, setting distribution columns to match parent table insert into foo values(1,1); insert into foo values(3,3); insert into foo2 values(2,2,2); @@ -662,11 +678,17 @@ select tableoid::regclass::text as relname, bar.* from bar order by 1,2; create table some_tab (a int) distributed randomly; insert into some_tab values (0); create table some_tab_child () inherits (some_tab); +NOTICE: table has parent, setting distribution columns to match parent table insert into some_tab_child values (1); create table parted_tab (a int, b char) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table parted_tab_part1 partition of parted_tab for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table parted_tab_part2 partition of parted_tab for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table create table parted_tab_part3 partition of parted_tab for values in (3); +NOTICE: table has parent, setting distribution columns to match parent table insert into parted_tab values (1, 'a'), (2, 'a'), (3, 'a'); update parted_tab set b = 'b' from @@ -698,19 +720,27 @@ select tableoid::regclass::text as relname, parted_tab.* from parted_tab order b explain update parted_tab set a = 2 where false; QUERY PLAN -------------------------------------------------------- - Update on parted_tab (cost=0.00..0.00 rows=0 width=0) - -> Result (cost=0.00..0.00 rows=0 width=10) + Update on parted_tab (cost=0.00..0.01 rows=0 width=0) + -> Result (cost=0.00..0.00 rows=0 width=22) One-Time Filter: false -(3 rows) + Optimizer: Postgres query optimizer +(4 rows) drop table parted_tab; -- Check UPDATE with multi-level partitioned inherited target create table mlparted_tab (a int, b char, c text) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table mlparted_tab_part1 partition of mlparted_tab for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table mlparted_tab_part2 partition of mlparted_tab for values in (2) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table mlparted_tab_part3 partition of mlparted_tab for values in (3); +NOTICE: table has parent, setting distribution columns to match parent table create table mlparted_tab_part2a partition of mlparted_tab_part2 for values in ('a'); +NOTICE: table has parent, setting distribution columns to match parent table create table mlparted_tab_part2b partition of mlparted_tab_part2 for values in ('b'); +NOTICE: table has parent, setting distribution columns to match parent table insert into mlparted_tab values (1, 'a'), (2, 'a'), (2, 'b'), (3, 'a'); update mlparted_tab mlp set c = 'xxx' from @@ -730,16 +760,25 @@ drop table some_tab cascade; NOTICE: drop cascades to table some_tab_child /* Test multiple inheritance of column defaults */ CREATE TABLE firstparent (tomorrow date default now()::date + 1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'tomorrow' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE secondparent (tomorrow date default now() :: date + 1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'tomorrow' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE jointchild () INHERITS (firstparent, secondparent); -- ok +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "tomorrow" CREATE TABLE thirdparent (tomorrow date default now()::date - 1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'tomorrow' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE otherchild () INHERITS (firstparent, thirdparent); -- not ok +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "tomorrow" ERROR: column "tomorrow" inherits conflicting default values HINT: To resolve the conflict, specify a default explicitly. CREATE TABLE otherchild (tomorrow date default now()) INHERITS (firstparent, thirdparent); -- ok, child resolves ambiguous default +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "tomorrow" NOTICE: merging column "tomorrow" with inherited definition DROP TABLE firstparent, secondparent, jointchild, thirdparent, otherchild; @@ -757,14 +796,21 @@ select * from d; -- column; but we should reject that if any definition was inherited from -- an unrelated parent. create temp table parent1(f1 int, f2 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table parent2(f1 int, f3 bigint); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table childtab(f4 int) inherits(parent1, parent2); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "f1" alter table parent1 alter column f1 type bigint; -- fail, conflict w/parent2 ERROR: cannot alter inherited column "f1" of relation "childtab" alter table parent1 alter column f2 type bigint; -- ok -- Test non-inheritable parent constraints create table p1(ff1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'ff1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. alter table p1 add constraint p1chk check (ff1 > 0) no inherit; alter table p1 add constraint p2chk check (ff1 > 10); -- connoinherit should be true for NO INHERIT constraint @@ -777,6 +823,7 @@ select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg -- Test that child does not inherit NO INHERIT constraints create table c1 () inherits (p1); +NOTICE: table has parent, setting distribution columns to match parent table \d p1 Table "public.p1" Column | Type | Collation | Nullable | Default @@ -800,14 +847,19 @@ Distributed by: (ff1) -- Test that child does not override inheritable constraints of the parent create table c2 (constraint p2chk check (ff1 > 10) no inherit) inherits (p1); --fails +NOTICE: table has parent, setting distribution columns to match parent table ERROR: constraint "p2chk" conflicts with inherited constraint on relation "c2" drop table p1 cascade; NOTICE: drop cascades to table c1 -- Tests for casting between the rowtypes of parent and child -- tables. See the pgsql-hackers thread beginning Dec. 4/04 create table base (i integer); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'i' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table derived () inherits (base); +NOTICE: table has parent, setting distribution columns to match parent table create table more_derived (like derived, b int) inherits (derived); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging column "i" with inherited definition insert into derived (i) values (0); select derived::base from derived; @@ -830,26 +882,31 @@ explain (verbose on, costs off) select row(i, b)::more_derived::derived::base fr Output: ((ROW(i, b)::more_derived)::base) -> Seq Scan on public.more_derived Output: (ROW(i, b)::more_derived)::base + Settings: optimizer = 'off' Optimizer: Postgres query optimizer - Settings: optimizer=off (6 rows) explain (verbose on, costs off) select (1, 2)::more_derived::derived::base; - QUERY PLAN ------------------------ + QUERY PLAN +------------------------------------- Result Output: '(1)'::base -(2 rows) + Settings: optimizer = 'off' + Optimizer: Postgres query optimizer +(4 rows) drop table more_derived; drop table derived; drop table base; create table p1(ff1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'ff1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table p2(f1 text); NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create function p2text(p2) returns text as 'select $1.f1' language sql; create table c1(f3 int) inherits(p1,p2); +NOTICE: table has parent, setting distribution columns to match parent table insert into c1 values(123456789, 'hi', 42); select p2text(c1.*) from c1; p2text @@ -862,8 +919,11 @@ drop table c1; drop table p2; drop table p1; CREATE TABLE ac (aa TEXT); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'aa' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. alter table ac add constraint ac_check check (aa is not null); CREATE TABLE bc (bb TEXT) INHERITS (ac); +NOTICE: table has parent, setting distribution columns to match parent table select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc') order by 1,2; relname | conname | contype | conislocal | coninhcount | consrc ---------+----------+---------+------------+-------------+------------------ @@ -872,10 +932,10 @@ select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg (2 rows) insert into ac (aa) values (NULL); -ERROR: new row for relation "ac" violates check constraint "ac_check" +ERROR: new row for relation "ac" violates check constraint "ac_check" (seg0 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=3236630) DETAIL: Failing row contains (null). insert into bc (aa) values (NULL); -ERROR: new row for relation "bc" violates check constraint "ac_check" +ERROR: new row for relation "bc" violates check constraint "ac_check" (seg0 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=3236630) DETAIL: Failing row contains (null, null). alter table bc drop constraint ac_check; -- fail, disallowed ERROR: cannot drop inherited constraint "ac_check" of relation "bc" @@ -895,10 +955,10 @@ select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg (2 rows) insert into ac (aa) values (NULL); -ERROR: new row for relation "ac" violates check constraint "ac_aa_check" +ERROR: new row for relation "ac" violates check constraint "ac_aa_check" (seg0 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=3236630) DETAIL: Failing row contains (null). insert into bc (aa) values (NULL); -ERROR: new row for relation "bc" violates check constraint "ac_aa_check" +ERROR: new row for relation "bc" violates check constraint "ac_aa_check" (seg0 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=3236630) DETAIL: Failing row contains (null, null). alter table bc drop constraint ac_aa_check; -- fail, disallowed ERROR: cannot drop inherited constraint "ac_aa_check" of relation "bc" @@ -933,7 +993,10 @@ select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg drop table bc; drop table ac; create table ac (a int constraint check_a check (a <> 0)); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table bc (a int constraint check_a check (a <> 0), b int constraint check_b check (b <> 0)) inherits (ac); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging column "a" with inherited definition NOTICE: merging constraint "check_a" with inherited definition select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc') order by 1,2; @@ -947,8 +1010,13 @@ select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg drop table bc; drop table ac; create table ac (a int constraint check_a check (a <> 0)); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table bc (b int constraint check_b check (b <> 0)); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table cc (c int constraint check_c check (c <> 0)) inherits (ac, bc); +NOTICE: table has parent, setting distribution columns to match parent table select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc', 'cc') order by 1,2; relname | conname | contype | conislocal | coninhcount | consrc ---------+---------+---------+------------+-------------+---------- @@ -974,20 +1042,26 @@ drop table cc; drop table bc; drop table ac; create table p1(f1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table p2(f2 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f2' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table c1(f3 int) inherits(p1,p2); +NOTICE: table has parent, setting distribution columns to match parent table insert into c1 values(1,-1,2); alter table p2 add constraint cc check (f2>0); -- fail -ERROR: check constraint "cc" of relation "c1" is violated by some row +ERROR: check constraint "cc" of relation "c1" is violated by some row (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) alter table p2 add check (f2>0); -- check it without a name, too -ERROR: check constraint "p2_f2_check" of relation "c1" is violated by some row +ERROR: check constraint "p2_f2_check" of relation "c1" is violated by some row (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) delete from c1; insert into c1 values(1,1,2); alter table p2 add check (f2>0); insert into c1 values(1,-1,2); -- fail -ERROR: new row for relation "c1" violates check constraint "p2_f2_check" +ERROR: new row for relation "c1" violates check constraint "p2_f2_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (1, -1, 2). create table c2(f3 int) inherits(p1,p2); +NOTICE: table has parent, setting distribution columns to match parent table \d c2 Table "public.c2" Column | Type | Collation | Nullable | Default @@ -1002,6 +1076,7 @@ Inherits: p1, Distributed by: (f1) create table c3 (f4 int) inherits(c1,c2); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "f1" NOTICE: merging multiple inherited definitions of column "f2" NOTICE: merging multiple inherited definitions of column "f3" @@ -1026,7 +1101,10 @@ drop cascades to table c2 drop cascades to table c3 drop table p2 cascade; create table pp1 (f1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table cc1 (f2 text, f3 int) inherits (pp1); +NOTICE: table has parent, setting distribution columns to match parent table alter table pp1 add column a1 int check (a1 > 0); \d cc1 Table "public.cc1" @@ -1042,6 +1120,7 @@ Inherits: pp1 Distributed by: (f1) create table cc2(f4 float) inherits(pp1,cc1); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "f1" NOTICE: merging multiple inherited definitions of column "a1" \d cc2 @@ -1085,8 +1164,13 @@ DETAIL: drop cascades to table cc1 drop cascades to table cc2 -- Test for renaming in simple multiple inheritance CREATE TABLE inht1 (a int, b int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE inhs1 (b int, c int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE inhts (d int) INHERITS (inht1, inhs1); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "b" ALTER TABLE inht1 RENAME a TO aa; ALTER TABLE inht1 RENAME b TO bb; -- to be failed @@ -1109,8 +1193,11 @@ Distributed by: (aa) DROP TABLE inhts; -- Test for renaming in diamond inheritance CREATE TABLE inht2 (x int) INHERITS (inht1); +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE inht3 (y int) INHERITS (inht1); +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE inht4 (z int) INHERITS (inht2, inht3); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "aa" NOTICE: merging multiple inherited definitions of column "b" ALTER TABLE inht1 RENAME aa TO aaa; @@ -1128,6 +1215,7 @@ Inherits: inht2, Distributed by: (aaa) CREATE TABLE inhts (d int) INHERITS (inht2, inhs1); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "b" ALTER TABLE inht1 RENAME aaa TO aaaa; ALTER TABLE inht1 RENAME b TO bb; -- to be failed @@ -1180,6 +1268,7 @@ drop cascades to table inht4 -- Test non-inheritable indices [UNIQUE, EXCLUDE] constraints CREATE TABLE test_constraints (id int, val1 varchar, val2 int, UNIQUE(val1, val2)); CREATE TABLE test_constraints_inh () INHERITS (test_constraints); +NOTICE: table has parent, setting distribution columns to match parent table \d+ test_constraints Table "public.test_constraints" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description @@ -1220,7 +1309,10 @@ CREATE TABLE test_ex_constraints ( dkey inet, EXCLUDE USING gist (dkey inet_ops WITH =, c WITH &&) ); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'dkey' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints); +NOTICE: table has parent, setting distribution columns to match parent table \d+ test_ex_constraints Table "public.test_ex_constraints" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description @@ -1256,7 +1348,11 @@ DROP TABLE test_ex_constraints; -- Test non-inheritable foreign key constraints CREATE TABLE test_primary_constraints(id int PRIMARY KEY); CREATE TABLE test_foreign_constraints(id1 int REFERENCES test_primary_constraints(id)); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'id1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +WARNING: referential integrity (FOREIGN KEY) constraints are not supported in Apache Cloudberry, will not be enforced CREATE TABLE test_foreign_constraints_inh () INHERITS (test_foreign_constraints); +NOTICE: table has parent, setting distribution columns to match parent table \d+ test_primary_constraints Table "public.test_primary_constraints" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description @@ -1302,8 +1398,10 @@ DROP TABLE test_primary_constraints; create table inh_fk_1 (a int primary key); insert into inh_fk_1 values (1), (2), (3); create table inh_fk_2 (x int primary key, y int references inh_fk_1 on delete cascade); +WARNING: referential integrity (FOREIGN KEY) constraints are not supported in Apache Cloudberry, will not be enforced insert into inh_fk_2 values (11, 1), (22, 2), (33, 3); create table inh_fk_2_child () inherits (inh_fk_2); +NOTICE: table has parent, setting distribution columns to match parent table insert into inh_fk_2_child values (111, 1), (222, 2); -- The cascading deletion doesn't work on GPDB, because foreign keys are not -- enforced in general. So this produces different result than on upstream. @@ -1328,7 +1426,10 @@ select * from inh_fk_2 order by 1, 2; drop table inh_fk_1, inh_fk_2, inh_fk_2_child; -- Test that parent and child CHECK constraints can be created in either order create table p1(f1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table p1_c1() inherits(p1); +NOTICE: table has parent, setting distribution columns to match parent table alter table p1 add constraint inh_check_constraint1 check (f1 > 0); alter table p1_c1 add constraint inh_check_constraint1 check (f1 > 0); NOTICE: merging constraint "inh_check_constraint1" with inherited definition @@ -1350,7 +1451,10 @@ drop table p1 cascade; NOTICE: drop cascades to table p1_c1 -- Test that a valid child can have not-valid parent, but not vice versa create table invalid_check_con(f1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table invalid_check_con_child() inherits(invalid_check_con); +NOTICE: table has parent, setting distribution columns to match parent table alter table invalid_check_con_child add constraint inh_check_constraint check(f1 > 0) not valid; alter table invalid_check_con add constraint inh_check_constraint check(f1 > 0); -- fail ERROR: constraint "inh_check_constraint" conflicts with NOT VALID constraint on relation "invalid_check_con_child" @@ -1360,10 +1464,10 @@ alter table invalid_check_con_child add constraint inh_check_constraint check(f1 alter table invalid_check_con add constraint inh_check_constraint check(f1 > 0) not valid; NOTICE: merging constraint "inh_check_constraint" with inherited definition insert into invalid_check_con values(0); -- fail -ERROR: new row for relation "invalid_check_con" violates check constraint "inh_check_constraint" +ERROR: new row for relation "invalid_check_con" violates check constraint "inh_check_constraint" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (0). insert into invalid_check_con_child values(0); -- fail -ERROR: new row for relation "invalid_check_con_child" violates check constraint "inh_check_constraint" +ERROR: new row for relation "invalid_check_con_child" violates check constraint "inh_check_constraint" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (0). select conrelid::regclass::text as relname, conname, convalidated, conislocal, coninhcount, connoinherit @@ -1383,9 +1487,11 @@ create temp table patest0 (id, x) as select x, x from generate_series(0,1000) x distributed by (id); create temp table patest1() inherits (patest0); +NOTICE: table has parent, setting distribution columns to match parent table insert into patest1 select x, x from generate_series(0,1000) x; create temp table patest2() inherits (patest0); +NOTICE: table has parent, setting distribution columns to match parent table insert into patest2 select x, x from generate_series(0,1000) x; create index patest0i on patest0(id); @@ -1398,8 +1504,8 @@ set enable_seqscan=off; set enable_bitmapscan=off; explain (costs off) select * from patest0 join (select f1 from int4_tbl where f1 < 10 and f1 > -10 limit 1) ss on id = f1; - QUERY PLAN ----------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Hash Join Hash Cond: (patest0.id = int4_tbl.f1) @@ -1504,8 +1610,8 @@ explain (verbose, costs off) select * from matest0 order by 1-id; Output: matest0_3.id, matest0_3.name -> Seq Scan on public.matest3 matest0_4 Output: matest0_4.id, matest0_4.name + Settings: enable_indexscan = 'off', optimizer = 'off' Optimizer: Postgres query optimizer - Settings: enable_indexscan=off (19 rows) select * from matest0 order by 1-id; @@ -1520,8 +1626,8 @@ select * from matest0 order by 1-id; (6 rows) explain (verbose, costs off) select min(1-id) from matest0; - QUERY PLAN ------------------------------------------------------ + QUERY PLAN +-------------------------------------------------------------- Finalize Aggregate Output: min((1 - matest0.id)) -> Gather Motion 3:1 (slice1; segments: 3) @@ -1537,8 +1643,8 @@ explain (verbose, costs off) select min(1-id) from matest0; Output: matest0_3.id -> Seq Scan on public.matest3 matest0_4 Output: matest0_4.id + Settings: enable_indexscan = 'off', optimizer = 'off' Optimizer: Postgres query optimizer - Settings: enable_indexscan=off (17 rows) select min(1-id) from matest0; @@ -1555,8 +1661,8 @@ set enable_parallel_append = off; -- Don't let parallel-append interfere -- of append with bitmapscan + sort set enable_bitmapscan = off; explain (verbose, costs off) select * from matest0 order by 1-id; - QUERY PLAN ------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Output: matest0.id, matest0.name, ((1 - matest0.id)) Merge Key: ((1 - matest0.id)) @@ -1573,8 +1679,8 @@ explain (verbose, costs off) select * from matest0 order by 1-id; Output: matest0_3.id, matest0_3.name, (1 - matest0_3.id) -> Index Scan using matest3i on public.matest3 matest0_4 Output: matest0_4.id, matest0_4.name, (1 - matest0_4.id) + Settings: enable_bitmapscan = 'off', enable_parallel_append = 'off', enable_seqscan = 'off', optimizer = 'off' Optimizer: Postgres query optimizer - Settings: enable_bitmapscan=off, enable_parallel_append=off, enable_seqscan=off (18 rows) select * from matest0 order by 1-id; @@ -1589,8 +1695,8 @@ select * from matest0 order by 1-id; (6 rows) explain (verbose, costs off) select min(1-id) from matest0; - QUERY PLAN --------------------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------------------------------- Result Output: $0 InitPlan 1 (returns $0) (slice1) @@ -1618,8 +1724,8 @@ explain (verbose, costs off) select min(1-id) from matest0; -> Index Scan using matest3i on public.matest3 matest0_4 Output: matest0_4.id, (1 - matest0_4.id) Index Cond: ((1 - matest0_4.id) IS NOT NULL) + Settings: enable_bitmapscan = 'off', enable_parallel_append = 'off', enable_seqscan = 'off', optimizer = 'off' Optimizer: Postgres query optimizer - Settings: enable_bitmapscan=off, enable_parallel_append=off, enable_seqscan=off (29 rows) select min(1-id) from matest0; @@ -1641,7 +1747,10 @@ drop cascades to table matest3 -- a plan with extraneous sorting -- create table matest0 (a int, b int, c int, d int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table matest1 () inherits(matest0); +NOTICE: table has parent, setting distribution columns to match parent table create index matest0i on matest0 (b, c); create index matest1i on matest1 (b, c); set enable_nestloop = off; -- we want a plan with two MergeAppends @@ -1857,16 +1966,19 @@ rollback; -- Check handling of a constant-null CHECK constraint -- create table cnullparent (f1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table cnullchild (check (f1 = 1 or f1 = null)) inherits(cnullparent); +NOTICE: table has parent, setting distribution columns to match parent table insert into cnullchild values(1); insert into cnullchild values(2); insert into cnullchild values(null); select * from cnullparent; f1 ---- - 1 2 + 1 (3 rows) select * from cnullparent where f1 = 2; @@ -1881,11 +1993,18 @@ NOTICE: drop cascades to table cnullchild -- Check use of temporary tables with inheritance trees -- create table inh_perm_parent (a1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table inh_temp_parent (a1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table inh_temp_child () inherits (inh_perm_parent); -- ok +NOTICE: table has parent, setting distribution columns to match parent table create table inh_perm_child () inherits (inh_temp_parent); -- error +NOTICE: table has parent, setting distribution columns to match parent table ERROR: cannot inherit from temporary relation "inh_temp_parent" create temp table inh_temp_child_2 () inherits (inh_temp_parent); -- ok +NOTICE: table has parent, setting distribution columns to match parent table insert into inh_perm_parent values (1); insert into inh_temp_parent values (2); insert into inh_temp_child values (3); @@ -1893,8 +2012,8 @@ insert into inh_temp_child_2 values (4); select tableoid::regclass, a1 from inh_perm_parent; tableoid | a1 -----------------+---- - inh_perm_parent | 1 inh_temp_child | 3 + inh_perm_parent | 1 (2 rows) select tableoid::regclass, a1 from inh_temp_parent; @@ -1915,9 +2034,14 @@ NOTICE: drop cascades to table inh_temp_child_2 create table list_parted ( a varchar ) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table part_ab_cd partition of list_parted for values in ('ab', 'cd'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_ef_gh partition of list_parted for values in ('ef', 'gh'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_null_xy partition of list_parted for values in (null, 'xy'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from list_parted; QUERY PLAN ---------------------------------------------------- @@ -1986,22 +2110,37 @@ create table range_list_parted ( a int, b char(2) ) partition by range (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table part_1_10_ab partition of part_1_10 for values in ('ab'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_1_10_cd partition of part_1_10 for values in ('cd'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table part_10_20_ab partition of part_10_20 for values in ('ab'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_10_20_cd partition of part_10_20 for values in ('cd'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table part_21_30_ab partition of part_21_30 for values in ('ab'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_21_30_cd partition of part_21_30 for values in ('cd'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_40_inf partition of range_list_parted for values from (40) to (maxvalue) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table part_40_inf_ab partition of part_40_inf for values in ('ab'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_40_inf_cd partition of part_40_inf for values in ('cd'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_40_inf_null partition of part_40_inf for values in (null); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from range_list_parted; - QUERY PLAN ------------------------------------------- + QUERY PLAN +-------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Append -> Seq Scan on part_1_10_ab range_list_parted_1 @@ -2060,8 +2199,8 @@ explain (costs off) select * from range_list_parted where a between 3 and 23 and /* Should select no rows because range partition key cannot be null */ explain (costs off) select * from range_list_parted where a is null; - QUERY PLAN --------------------------- + QUERY PLAN +------------------------------------- Result One-Time Filter: false Optimizer: Postgres query optimizer @@ -2069,8 +2208,8 @@ explain (costs off) select * from range_list_parted where a is null; /* Should only select rows from the null-accepting partition */ explain (costs off) select * from range_list_parted where b is null; - QUERY PLAN ------------------------------------------- + QUERY PLAN +------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) -> Seq Scan on part_40_inf_null range_list_parted Filter: (b IS NULL) @@ -2078,8 +2217,8 @@ explain (costs off) select * from range_list_parted where b is null; (4 rows) explain (costs off) select * from range_list_parted where a is not null and a < 67; - QUERY PLAN ------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Append -> Seq Scan on part_1_10_ab range_list_parted_1 @@ -2104,8 +2243,8 @@ explain (costs off) select * from range_list_parted where a is not null and a < (21 rows) explain (costs off) select * from range_list_parted where a >= 30; - QUERY PLAN ------------------------------------------- + QUERY PLAN +-------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Append -> Seq Scan on part_40_inf_ab range_list_parted_1 @@ -2122,16 +2261,25 @@ drop table range_list_parted; -- check that constraint exclusion is able to cope with the partition -- constraint emitted for multi-column range partitioned tables create table mcrparted (a int, b int, c int) partition by range (a, abs(b), c); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table mcrparted_def partition of mcrparted default; +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted0 partition of mcrparted for values from (minvalue, minvalue, minvalue) to (1, 1, 1); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted1 partition of mcrparted for values from (1, 1, 1) to (10, 5, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted2 partition of mcrparted for values from (10, 5, 10) to (10, 10, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted3 partition of mcrparted for values from (11, 1, 1) to (20, 10, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted4 partition of mcrparted for values from (20, 10, 10) to (20, 20, 20); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted5 partition of mcrparted for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from mcrparted where a = 0; -- scans mcrparted0, mcrparted_def - QUERY PLAN ------------------------------------------- + QUERY PLAN +--------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Append -> Seq Scan on mcrparted0 mcrparted_1 @@ -2168,8 +2316,8 @@ explain (costs off) select * from mcrparted where a = 10 and abs(b) = 5; -- scan (9 rows) explain (costs off) select * from mcrparted where abs(b) = 5; -- scans all partitions - QUERY PLAN ------------------------------------------- + QUERY PLAN +--------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Append -> Seq Scan on mcrparted0 mcrparted_1 @@ -2190,8 +2338,8 @@ explain (costs off) select * from mcrparted where abs(b) = 5; -- scans all parti (17 rows) explain (costs off) select * from mcrparted where a > -1; -- scans all partitions - QUERY PLAN -------------------------------------------- + QUERY PLAN +--------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Append -> Seq Scan on mcrparted0 mcrparted_1 @@ -2221,8 +2369,8 @@ explain (costs off) select * from mcrparted where a = 20 and abs(b) = 10 and c > (4 rows) explain (costs off) select * from mcrparted where a = 20 and c > 20; -- scans mcrparted3, mcrparte4, mcrparte5, mcrparted_def - QUERY PLAN ------------------------------------------------ + QUERY PLAN +--------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Append -> Seq Scan on mcrparted3 mcrparted_1 @@ -2239,12 +2387,15 @@ explain (costs off) select * from mcrparted where a = 20 and c > 20; -- scans mc -- check that partitioned table Appends cope with being referenced in -- subplans create table parted_minmax (a int, b varchar(16)) partition by range (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table parted_minmax1 partition of parted_minmax for values from (1) to (10); +NOTICE: table has parent, setting distribution columns to match parent table create index parted_minmax1i on parted_minmax1 (a, b); insert into parted_minmax values (1,'12345'); explain (costs off) select min(a), max(a) from parted_minmax where b = '12345'; - QUERY PLAN -------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------ Result InitPlan 1 (returns $0) (slice1) -> Limit @@ -2273,8 +2424,8 @@ drop table parted_minmax; create index mcrparted_a_abs_c_idx on mcrparted (a, abs(b), c); -- MergeAppend must be used when a default partition exists explain (costs off) select * from mcrparted order by a, abs(b), c; - QUERY PLAN -------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: mcrparted.a, (abs(mcrparted.b)), mcrparted.c -> Merge Append @@ -2327,11 +2478,14 @@ explain (costs off) select * from mcrparted order by a desc, abs(b) desc, c desc -- that are unordered. drop table mcrparted5; create table mcrparted5 partition of mcrparted for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue) partition by list (a); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted5a partition of mcrparted5 for values in(20); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted5_def partition of mcrparted5 default; +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from mcrparted order by a, abs(b), c; - QUERY PLAN ---------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: mcrparted.a, (abs(mcrparted.b)), mcrparted.c -> Append @@ -2352,8 +2506,8 @@ drop table mcrparted5_def; -- into the main Append when the sub-partition is unordered but contains -- just a single sub-partition. explain (costs off) select a, abs(b) from mcrparted order by a, abs(b), c; - QUERY PLAN ---------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: mcrparted.a, (abs(mcrparted.b)), mcrparted.c -> Append @@ -2369,8 +2523,8 @@ explain (costs off) select a, abs(b) from mcrparted order by a, abs(b), c; -- check that Append is used when the sub-partitioned tables are pruned -- during planning. explain (costs off) select * from mcrparted where a < 20 order by a, abs(b), c; - QUERY PLAN -------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: mcrparted.a, (abs(mcrparted.b)), mcrparted.c -> Append @@ -2386,13 +2540,17 @@ explain (costs off) select * from mcrparted where a < 20 order by a, abs(b), c; (12 rows) create table mclparted (a int) partition by list(a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table mclparted1 partition of mclparted for values in(1); +NOTICE: table has parent, setting distribution columns to match parent table create table mclparted2 partition of mclparted for values in(2); +NOTICE: table has parent, setting distribution columns to match parent table create index on mclparted (a); -- Ensure an Append is used for a list partition with an order by. explain (costs off) select * from mclparted order by a; - QUERY PLAN ------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) Merge Key: mclparted.a -> Append @@ -2404,10 +2562,12 @@ explain (costs off) select * from mclparted order by a; -- Ensure a MergeAppend is used when a partition exists with interleaved -- datums in the partition bound. create table mclparted3_5 partition of mclparted for values in(3,5); +NOTICE: table has parent, setting distribution columns to match parent table create table mclparted4 partition of mclparted for values in(4); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from mclparted order by a; - QUERY PLAN ----------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: mclparted.a -> Merge Append @@ -2428,8 +2588,8 @@ create index on mcrparted2 (a, abs(b), c); create index on mcrparted3 (a, abs(b), c); create index on mcrparted4 (a, abs(b), c); explain (costs off) select * from mcrparted where a < 20 order by a, abs(b), c limit 1; - QUERY PLAN -------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------- Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: mcrparted.a, (abs(mcrparted.b)), mcrparted.c @@ -2452,8 +2612,8 @@ set enable_bitmapscan = 0; -- Ensure Append node can be used when the partition is ordered by some -- pathkeys which were deemed redundant. explain (costs off) select * from mcrparted where a = 10 order by a, abs(b), c; - QUERY PLAN -------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) Merge Key: (abs(mcrparted.b)), mcrparted.c -> Append @@ -2468,12 +2628,16 @@ reset enable_bitmapscan; drop table mcrparted; -- Ensure LIST partitions allow an Append to be used instead of a MergeAppend create table bool_lp (b bool) partition by list(b); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table bool_lp_true partition of bool_lp for values in(true); +NOTICE: table has parent, setting distribution columns to match parent table create table bool_lp_false partition of bool_lp for values in(false); +NOTICE: table has parent, setting distribution columns to match parent table create index on bool_lp (b); explain (costs off) select * from bool_lp order by b; - QUERY PLAN ------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: bool_lp.b -> Append @@ -2485,14 +2649,20 @@ explain (costs off) select * from bool_lp order by b; drop table bool_lp; -- Ensure const bool quals can be properly detected as redundant create table bool_rp (b bool, a int) partition by range(b,a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table bool_rp_false_1k partition of bool_rp for values from (false,0) to (false,1000); +NOTICE: table has parent, setting distribution columns to match parent table create table bool_rp_true_1k partition of bool_rp for values from (true,0) to (true,1000); +NOTICE: table has parent, setting distribution columns to match parent table create table bool_rp_false_2k partition of bool_rp for values from (false,1000) to (false,2000); +NOTICE: table has parent, setting distribution columns to match parent table create table bool_rp_true_2k partition of bool_rp for values from (true,1000) to (true,2000); +NOTICE: table has parent, setting distribution columns to match parent table create index on bool_rp (b,a); explain (costs off) select * from bool_rp where b = true order by b,a; - QUERY PLAN ------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) Merge Key: bool_rp.b, bool_rp.a -> Append @@ -2504,8 +2674,8 @@ explain (costs off) select * from bool_rp where b = true order by b,a; (8 rows) explain (costs off) select * from bool_rp where b = false order by b,a; - QUERY PLAN --------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) Merge Key: bool_rp.b, bool_rp.a -> Append @@ -2520,8 +2690,8 @@ explain (costs off) select * from bool_rp where b = false order by b,a; set enable_seqscan=off; set enable_bitmapscan=off; explain (costs off) select * from bool_rp where b = true order by a; - QUERY PLAN ------------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) Merge Key: bool_rp.a -> Sort @@ -2535,8 +2705,8 @@ explain (costs off) select * from bool_rp where b = true order by a; (10 rows) explain (costs off) select * from bool_rp where b = false order by a; - QUERY PLAN --------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) Merge Key: bool_rp.a -> Sort @@ -2555,12 +2725,16 @@ drop table bool_rp; -- Ensure an Append scan is chosen when the partition order is a subset of -- the required order. create table range_parted (a int, b int, c int) partition by range(a, b); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table range_parted1 partition of range_parted for values from (0,0) to (10,10); +NOTICE: table has parent, setting distribution columns to match parent table create table range_parted2 partition of range_parted for values from (10,10) to (20,20); +NOTICE: table has parent, setting distribution columns to match parent table create index on range_parted (a,b,c); explain (costs off) select * from range_parted order by a,b,c; - QUERY PLAN ----------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: range_parted.a, range_parted.b, range_parted.c -> Append @@ -2570,8 +2744,8 @@ explain (costs off) select * from range_parted order by a,b,c; (6 rows) explain (costs off) select * from range_parted order by a desc,b desc,c desc; - QUERY PLAN -------------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: range_parted.a, range_parted.b, range_parted.c -> Append @@ -2584,6 +2758,8 @@ drop table range_parted; -- Check that we allow access to a child table's statistics when the user -- has permissions only for the parent table. create table permtest_parent (a int, b text, c text) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table permtest_child (b text, c text, a int) partition by list (b) distributed by (a); create table permtest_grandchild (c text, b text, a int) distributed by (a); alter table permtest_child attach partition permtest_grandchild for values in ('a'); @@ -2593,6 +2769,7 @@ insert into permtest_parent select 1, 'a', left(md5(i::text), 5) from generate_series(0, 100) i; analyze permtest_parent; create role regress_no_child_access; +NOTICE: resource queue required -- using default resource queue "pg_default" revoke all on permtest_grandchild from regress_no_child_access; NOTICE: no privileges could be revoked grant select on permtest_parent to regress_no_child_access; @@ -2601,8 +2778,8 @@ set session authorization regress_no_child_access; explain (costs off) select * from permtest_parent p1 inner join permtest_parent p2 on p1.a = p2.a and p1.c ~ 'a1$'; - QUERY PLAN ------------------------------------------- + QUERY PLAN +------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) -> Hash Join Hash Cond: (p2.a = p1.a) @@ -2617,8 +2794,8 @@ explain (costs off) explain (costs off) select * from permtest_parent p1 inner join permtest_parent p2 on p1.a = p2.a and left(p1.c, 3) ~ 'a1$'; - QUERY PLAN ----------------------------------------------- + QUERY PLAN +---------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Hash Join Hash Cond: (p2.a = p1.a) @@ -2637,8 +2814,8 @@ set session authorization regress_no_child_access; explain (costs off) select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2 on p1.a = p2.a and p1.c ~ 'a1$'; - QUERY PLAN ------------------------------------------- + QUERY PLAN +------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) -> Hash Join Hash Cond: (p2.a = p1.a) @@ -2679,6 +2856,8 @@ CREATE TABLE errtst_parent ( data int NOT NULL DEFAULT 0, CONSTRAINT shdata_small CHECK(shdata < 3) ) PARTITION BY RANGE (partid); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'partid' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- fast defaults lead to attribute mapping being used in one -- direction, but not the other CREATE TABLE errtst_child_fastdef ( @@ -2686,6 +2865,8 @@ CREATE TABLE errtst_child_fastdef ( shdata int not null, CONSTRAINT shdata_small CHECK(shdata < 3) ); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'partid' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- no remapping in either direction necessary CREATE TABLE errtst_child_plaindef ( partid int not null, @@ -2694,6 +2875,8 @@ CREATE TABLE errtst_child_plaindef ( CONSTRAINT shdata_small CHECK(shdata < 3), CHECK(data < 10) ); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'partid' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- remapping in both direction CREATE TABLE errtst_child_reorder ( data int NOT NULL DEFAULT 0, @@ -2713,33 +2896,33 @@ INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', '5'); INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '5'); -- insert with child check constraint error INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', '10'); -ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" +ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (0, 1, 10). INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', '10'); -ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" +ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3236638) DETAIL: Failing row contains (10, 1, 10). INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '10'); -ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" +ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (20, 1, 10). -- insert with child not null constraint error INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', NULL); -ERROR: null value in column "data" of relation "errtst_child_fastdef" violates not-null constraint +ERROR: null value in column "data" of relation "errtst_child_fastdef" violates not-null constraint (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (0, 1, null). INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', NULL); -ERROR: null value in column "data" of relation "errtst_child_plaindef" violates not-null constraint +ERROR: null value in column "data" of relation "errtst_child_plaindef" violates not-null constraint (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3236638) DETAIL: Failing row contains (10, 1, null). INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', NULL); -ERROR: null value in column "data" of relation "errtst_child_reorder" violates not-null constraint +ERROR: null value in column "data" of relation "errtst_child_reorder" violates not-null constraint (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (20, 1, null). -- insert with shared check constraint error INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '5', '5'); -ERROR: new row for relation "errtst_child_fastdef" violates check constraint "shdata_small" +ERROR: new row for relation "errtst_child_fastdef" violates check constraint "shdata_small" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (0, 5, 5). INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '5', '5'); -ERROR: new row for relation "errtst_child_plaindef" violates check constraint "shdata_small" +ERROR: new row for relation "errtst_child_plaindef" violates check constraint "shdata_small" (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3236638) DETAIL: Failing row contains (10, 5, 5). INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '5', '5'); -ERROR: new row for relation "errtst_child_reorder" violates check constraint "shdata_small" +ERROR: new row for relation "errtst_child_reorder" violates check constraint "shdata_small" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (20, 5, 5). -- within partition update without child check constraint violation BEGIN; @@ -2749,13 +2932,13 @@ UPDATE errtst_parent SET data = data + 1 WHERE partid = 20; ROLLBACK; -- within partition update with child check constraint violation UPDATE errtst_parent SET data = data + 10 WHERE partid = 0; -ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" +ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (0, 1, 15). UPDATE errtst_parent SET data = data + 10 WHERE partid = 10; -ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" +ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3236638) DETAIL: Failing row contains (10, 1, 15). UPDATE errtst_parent SET data = data + 10 WHERE partid = 20; -ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" +ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (20, 1, 15). -- direct leaf partition update, without partition id violation BEGIN; @@ -2765,13 +2948,13 @@ UPDATE errtst_child_reorder SET partid = 21 WHERE partid = 20; ROLLBACK; -- direct leaf partition update, with partition id violation UPDATE errtst_child_fastdef SET partid = partid + 10 WHERE partid = 0; -ERROR: new row for relation "errtst_child_fastdef" violates partition constraint +ERROR: new row for relation "errtst_child_fastdef" violates partition constraint (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3236638) DETAIL: Failing row contains (10, 1, 5). UPDATE errtst_child_plaindef SET partid = partid + 10 WHERE partid = 10; -ERROR: new row for relation "errtst_child_plaindef" violates partition constraint +ERROR: new row for relation "errtst_child_plaindef" violates partition constraint (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (20, 1, 5). UPDATE errtst_child_reorder SET partid = partid + 10 WHERE partid = 20; -ERROR: new row for relation "errtst_child_reorder" violates partition constraint +ERROR: new row for relation "errtst_child_reorder" violates partition constraint (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (5, 1, 30). -- partition move, without child check constraint violation BEGIN; @@ -2781,16 +2964,16 @@ UPDATE errtst_parent SET partid = 0, data = data + 1 WHERE partid = 20; ROLLBACK; -- partition move, with child check constraint violation UPDATE errtst_parent SET partid = 10, data = data + 10 WHERE partid = 0; -ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" +ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3236638) DETAIL: Failing row contains (10, 1, 15). UPDATE errtst_parent SET partid = 20, data = data + 10 WHERE partid = 10; -ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" +ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (20, 1, 15). UPDATE errtst_parent SET partid = 0, data = data + 10 WHERE partid = 20; -ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" +ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Failing row contains (0, 1, 15). -- partition move, without target partition UPDATE errtst_parent SET partid = 30, data = data + 10 WHERE partid = 20; -ERROR: no partition of relation "errtst_parent" found for row +ERROR: no partition of relation "errtst_parent" found for row (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3236632) DETAIL: Partition key of the failing row contains (partid) = (30). DROP TABLE errtst_parent; diff --git a/src/test/regress/expected/inherit_optimizer.out b/src/test/regress/expected/inherit_optimizer.out index ef4a44eac33..3ce9bfa269e 100644 --- a/src/test/regress/expected/inherit_optimizer.out +++ b/src/test/regress/expected/inherit_optimizer.out @@ -3,8 +3,11 @@ -- CREATE TABLE a (aa TEXT) distributed randomly; CREATE TABLE b (bb TEXT) INHERITS (a); +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE c (cc TEXT) INHERITS (a); +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE d (dd TEXT) INHERITS (b,c,a); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "aa" NOTICE: merging multiple inherited definitions of column "aa" INSERT INTO a(aa) VALUES('aaa'); @@ -35,45 +38,45 @@ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; relname | aa ---------+---------- a | aaa + a | aaaaaaaa + b | bbbbbbb + c | cccccccc + d | ddddd + d | dddddddd a | aaaa a | aaaaa a | aaaaaa a | aaaaaaa - a | aaaaaaaa b | bbb b | bbbb - b | bbbbb - b | bbbbbb - b | bbbbbbb b | bbbbbbbb - c | ccc c | cccc c | ccccc + d | ddddddd + b | bbbbb + b | bbbbbb + c | ccc c | cccccc c | ccccccc - c | cccccccc d | ddd d | dddd - d | ddddd d | dddddd - d | ddddddd - d | dddddddd (24 rows) SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; relname | aa | bb ---------+----------+---- - b | bbb | - b | bbbb | b | bbbbb | b | bbbbbb | - b | bbbbbbb | - b | bbbbbbbb | d | ddd | d | dddd | - d | ddddd | d | dddddd | + b | bbb | + b | bbbb | + b | bbbbbbbb | d | ddddddd | + b | bbbbbbb | + d | ddddd | d | dddddddd | (12 rows) @@ -81,39 +84,39 @@ SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid; relname | aa | cc ---------+----------+---- c | ccc | - c | cccc | - c | ccccc | c | cccccc | c | ccccccc | - c | cccccccc | d | ddd | d | dddd | - d | ddddd | d | dddddd | + c | cccc | + c | ccccc | d | ddddddd | + c | cccccccc | + d | ddddd | d | dddddddd | (12 rows) SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid; relname | aa | bb | cc | dd ---------+----------+----+----+---- + d | ddddd | | | + d | dddddddd | | | + d | ddddddd | | | d | ddd | | | d | dddd | | | - d | ddddd | | | d | dddddd | | | - d | ddddddd | | | - d | dddddddd | | | (6 rows) SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid; relname | aa ---------+---------- a | aaa + a | aaaaaaaa a | aaaa a | aaaaa a | aaaaaa a | aaaaaaa - a | aaaaaaaa (6 rows) SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid; @@ -121,32 +124,32 @@ SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid; ---------+----------+---- b | bbb | b | bbbb | + b | bbbbbbbb | + b | bbbbbbb | b | bbbbb | b | bbbbbb | - b | bbbbbbb | - b | bbbbbbbb | (6 rows) SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid; relname | aa | cc ---------+----------+---- c | ccc | - c | cccc | - c | ccccc | c | cccccc | c | ccccccc | c | cccccccc | + c | cccc | + c | ccccc | (6 rows) SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; relname | aa | bb | cc | dd ---------+----------+----+----+---- + d | ddddd | | | + d | dddddddd | | | + d | ddddddd | | | d | ddd | | | d | dddd | | | - d | ddddd | | | d | dddddd | | | - d | ddddddd | | | - d | dddddddd | | | (6 rows) UPDATE a SET aa='zzzz' WHERE aa='aaaa'; @@ -157,64 +160,64 @@ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%'; SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; relname | aa ---------+---------- - a | zzzz - a | zzzzz a | zzzzzz a | zzzzzz + b | bbbbbbb + c | cccccccc + d | ddddd + d | dddddddd + a | zzzz + a | zzzzz a | zzzzzz a | zzzzzz b | bbb b | bbbb - b | bbbbb - b | bbbbbb - b | bbbbbbb b | bbbbbbbb - c | ccc c | cccc c | ccccc + d | ddddddd + b | bbbbb + b | bbbbbb + c | ccc c | cccccc c | ccccccc - c | cccccccc d | ddd d | dddd - d | ddddd d | dddddd - d | ddddddd - d | dddddddd (24 rows) SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; relname | aa | bb ---------+----------+---- - b | bbb | - b | bbbb | + b | bbbbbbb | + d | ddddd | + d | dddddddd | b | bbbbb | b | bbbbbb | - b | bbbbbbb | - b | bbbbbbbb | d | ddd | d | dddd | - d | ddddd | d | dddddd | + b | bbb | + b | bbbb | + b | bbbbbbbb | d | ddddddd | - d | dddddddd | (12 rows) SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid; relname | aa | cc ---------+----------+---- - c | ccc | c | cccc | c | ccccc | + d | ddddddd | + c | cccccccc | + d | ddddd | + d | dddddddd | + c | ccc | c | cccccc | c | ccccccc | - c | cccccccc | d | ddd | d | dddd | - d | ddddd | d | dddddd | - d | ddddddd | - d | dddddddd | (12 rows) SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid; @@ -222,10 +225,10 @@ SELECT relname, d.* FROM d, pg_class where d.tableoid = pg_class.oid; ---------+----------+----+----+---- d | ddd | | | d | dddd | | | - d | ddddd | | | d | dddddd | | | - d | ddddddd | | | + d | ddddd | | | d | dddddddd | | | + d | ddddddd | | | (6 rows) SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid; @@ -242,61 +245,61 @@ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid; SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid; relname | aa | bb ---------+----------+---- - b | bbb | - b | bbbb | + b | bbbbbbb | b | bbbbb | b | bbbbbb | - b | bbbbbbb | + b | bbb | + b | bbbb | b | bbbbbbbb | (6 rows) SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid; relname | aa | cc ---------+----------+---- + c | cccccccc | c | ccc | - c | cccc | - c | ccccc | c | cccccc | c | ccccccc | - c | cccccccc | + c | cccc | + c | ccccc | (6 rows) SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; relname | aa | bb | cc | dd ---------+----------+----+----+---- + d | ddddd | | | + d | dddddddd | | | d | ddd | | | d | dddd | | | - d | ddddd | | | d | dddddd | | | d | ddddddd | | | - d | dddddddd | | | (6 rows) UPDATE b SET aa='new'; SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; relname | aa ---------+---------- - a | zzzz - a | zzzzz a | zzzzzz a | zzzzzz + b | new + c | cccccccc + d | new + d | new + a | zzzz + a | zzzzz a | zzzzzz a | zzzzzz b | new b | new b | new - b | new + c | cccc + c | ccccc + d | new b | new b | new c | ccc - c | cccc - c | ccccc c | cccccc c | ccccccc - c | cccccccc - d | new - d | new - d | new d | new d | new d | new @@ -308,13 +311,13 @@ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; b | new | b | new | b | new | + d | new | b | new | b | new | - b | new | - d | new | d | new | d | new | d | new | + b | new | d | new | d | new | (12 rows) @@ -323,16 +326,16 @@ SELECT relname, c.* FROM c, pg_class where c.tableoid = pg_class.oid; relname | aa | cc ---------+----------+---- c | ccc | - c | cccc | - c | ccccc | c | cccccc | c | ccccccc | - c | cccccccc | d | new | d | new | d | new | + c | cccccccc | d | new | d | new | + c | cccc | + c | ccccc | d | new | (12 rows) @@ -372,12 +375,12 @@ SELECT relname, b.* FROM ONLY b, pg_class where b.tableoid = pg_class.oid; SELECT relname, c.* FROM ONLY c, pg_class where c.tableoid = pg_class.oid; relname | aa | cc ---------+----------+---- - c | ccc | + c | cccccccc | c | cccc | c | ccccc | + c | ccc | c | cccccc | c | ccccccc | - c | cccccccc | (6 rows) SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; @@ -396,8 +399,16 @@ DELETE FROM ONLY c WHERE aa='new'; SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; relname | aa ---------+----- + b | new + b | new + d | new + d | new + d | new a | new a | new + b | new + d | new + d | new a | new a | new a | new @@ -405,31 +416,23 @@ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid; b | new b | new b | new - b | new - b | new - b | new - d | new - d | new - d | new - d | new - d | new d | new (18 rows) SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid; relname | aa | bb ---------+-----+---- - b | new | - b | new | - b | new | - b | new | - b | new | b | new | d | new | d | new | + b | new | + b | new | d | new | d | new | d | new | + b | new | + b | new | + b | new | d | new | (12 rows) @@ -537,27 +540,60 @@ SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; -- Confirm PRIMARY KEY adds NOT NULL constraint to child table CREATE TEMP TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a); INSERT INTO z VALUES (NULL, 'text'); -- should fail -ERROR: null value in column "aa" of relation "z" violates not-null constraint +ERROR: null value in column "aa" of relation "z" violates not-null constraint (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3130282) DETAIL: Failing row contains (null, text). +-- Check inherited UPDATE with first child excluded +create table some_tab (f1 int, f2 int, f3 int, check (f1 < 10) no inherit); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +create table some_tab_child () inherits(some_tab); +NOTICE: table has parent, setting distribution columns to match parent table +insert into some_tab_child select i, i+1, 0 from generate_series(1,1000) i; +create index on some_tab_child(f1, f2); +-- while at it, also check that statement-level triggers fire +create function some_tab_stmt_trig_func() returns trigger as +$$begin raise notice 'updating some_tab'; return NULL; end;$$ +language plpgsql; +create trigger some_tab_stmt_trig + before update on some_tab execute function some_tab_stmt_trig_func(); +ERROR: Triggers for statements are not yet supported +explain (costs off) +update some_tab set f3 = 11 where f1 = 12 and f2 = 13; + QUERY PLAN +------------------------------------------------------------------------------------ + Update on some_tab + Update on some_tab_child some_tab_1 + -> Result + -> Index Scan using some_tab_child_f1_f2_idx on some_tab_child some_tab_1 + Index Cond: ((f1 = 12) AND (f2 = 13)) + Optimizer: Postgres query optimizer +(6 rows) + +update some_tab set f3 = 11 where f1 = 12 and f2 = 13; +drop table some_tab cascade; +NOTICE: drop cascades to table some_tab_child +drop function some_tab_stmt_trig_func(); -- Check inherited UPDATE with all children excluded create table some_tab (a int, b int) distributed randomly; create table some_tab_child () inherits (some_tab); +NOTICE: table has parent, setting distribution columns to match parent table insert into some_tab_child values(1,2); explain (verbose, costs off) update some_tab set a = a + 1 where false; - QUERY PLAN + QUERY PLAN ----------------------------------------------------------------------- Update on public.some_tab -> Result Output: (some_tab.a + 1), NULL::oid, NULL::tid, NULL::integer One-Time Filter: false + Settings: optimizer = 'on' Optimizer: Postgres query optimizer -(5 rows) +(6 rows) update some_tab set a = a + 1 where false; explain (verbose, costs off) update some_tab set a = a + 1 where false returning b, a; - QUERY PLAN + QUERY PLAN ----------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Output: some_tab.b, some_tab.a @@ -566,8 +602,9 @@ update some_tab set a = a + 1 where false returning b, a; -> Result Output: (some_tab.a + 1), NULL::oid, NULL::tid, NULL::integer One-Time Filter: false + Settings: optimizer = 'on' Optimizer: Postgres query optimizer -(8 rows) +(9 rows) update some_tab set a = a + 1 where false returning b, a; b | a @@ -584,9 +621,15 @@ drop table some_tab cascade; NOTICE: drop cascades to table some_tab_child -- Check UPDATE with inherited target and an inherited source table create temp table foo(f1 int, f2 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table foo2(f3 int) inherits (foo); +NOTICE: table has parent, setting distribution columns to match parent table create temp table bar(f1 int, f2 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table bar2(f3 int) inherits (bar); +NOTICE: table has parent, setting distribution columns to match parent table insert into foo values(1,1); insert into foo values(3,3); insert into foo2 values(2,2,2); @@ -635,17 +678,25 @@ select tableoid::regclass::text as relname, bar.* from bar order by 1,2; create table some_tab (a int) distributed randomly; insert into some_tab values (0); create table some_tab_child () inherits (some_tab); +NOTICE: table has parent, setting distribution columns to match parent table insert into some_tab_child values (1); create table parted_tab (a int, b char) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table parted_tab_part1 partition of parted_tab for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table parted_tab_part2 partition of parted_tab for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table create table parted_tab_part3 partition of parted_tab for values in (3); +NOTICE: table has parent, setting distribution columns to match parent table insert into parted_tab values (1, 'a'), (2, 'a'), (3, 'a'); update parted_tab set b = 'b' from (select a from some_tab union all select a+1 from some_tab) ss (a) where parted_tab.a = ss.a; select tableoid::regclass::text as relname, parted_tab.* from parted_tab order by 1,2; +NOTICE: One or more columns in the following table(s) do not have statistics: parted_tab +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. relname | a | b ------------------+---+--- parted_tab_part1 | 1 | b @@ -660,6 +711,8 @@ from (select 0 from parted_tab union all select 1 from parted_tab) ss (a) where parted_tab.a = ss.a; select tableoid::regclass::text as relname, parted_tab.* from parted_tab order by 1,2; +NOTICE: One or more columns in the following table(s) do not have statistics: parted_tab +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. relname | a | b ------------------+---+--- parted_tab_part1 | 1 | b @@ -671,8 +724,8 @@ select tableoid::regclass::text as relname, parted_tab.* from parted_tab order b explain update parted_tab set a = 2 where false; QUERY PLAN -------------------------------------------------------- - Update on parted_tab (cost=0.00..0.00 rows=0 width=0) - -> Result (cost=0.00..0.00 rows=0 width=0) + Update on parted_tab (cost=0.00..0.01 rows=0 width=0) + -> Result (cost=0.00..0.00 rows=0 width=22) One-Time Filter: false Optimizer: Postgres query optimizer (4 rows) @@ -680,11 +733,18 @@ explain update parted_tab set a = 2 where false; drop table parted_tab; -- Check UPDATE with multi-level partitioned inherited target create table mlparted_tab (a int, b char, c text) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table mlparted_tab_part1 partition of mlparted_tab for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table mlparted_tab_part2 partition of mlparted_tab for values in (2) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table mlparted_tab_part3 partition of mlparted_tab for values in (3); +NOTICE: table has parent, setting distribution columns to match parent table create table mlparted_tab_part2a partition of mlparted_tab_part2 for values in ('a'); +NOTICE: table has parent, setting distribution columns to match parent table create table mlparted_tab_part2b partition of mlparted_tab_part2 for values in ('b'); +NOTICE: table has parent, setting distribution columns to match parent table insert into mlparted_tab values (1, 'a'), (2, 'a'), (2, 'b'), (3, 'a'); update mlparted_tab mlp set c = 'xxx' from @@ -704,16 +764,25 @@ drop table some_tab cascade; NOTICE: drop cascades to table some_tab_child /* Test multiple inheritance of column defaults */ CREATE TABLE firstparent (tomorrow date default now()::date + 1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'tomorrow' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE secondparent (tomorrow date default now() :: date + 1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'tomorrow' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE jointchild () INHERITS (firstparent, secondparent); -- ok +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "tomorrow" CREATE TABLE thirdparent (tomorrow date default now()::date - 1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'tomorrow' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE otherchild () INHERITS (firstparent, thirdparent); -- not ok +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "tomorrow" ERROR: column "tomorrow" inherits conflicting default values HINT: To resolve the conflict, specify a default explicitly. CREATE TABLE otherchild (tomorrow date default now()) INHERITS (firstparent, thirdparent); -- ok, child resolves ambiguous default +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "tomorrow" NOTICE: merging column "tomorrow" with inherited definition DROP TABLE firstparent, secondparent, jointchild, thirdparent, otherchild; @@ -731,14 +800,21 @@ select * from d; -- column; but we should reject that if any definition was inherited from -- an unrelated parent. create temp table parent1(f1 int, f2 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table parent2(f1 int, f3 bigint); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table childtab(f4 int) inherits(parent1, parent2); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "f1" alter table parent1 alter column f1 type bigint; -- fail, conflict w/parent2 ERROR: cannot alter inherited column "f1" of relation "childtab" alter table parent1 alter column f2 type bigint; -- ok -- Test non-inheritable parent constraints create table p1(ff1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'ff1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. alter table p1 add constraint p1chk check (ff1 > 0) no inherit; alter table p1 add constraint p2chk check (ff1 > 10); -- connoinherit should be true for NO INHERIT constraint @@ -751,6 +827,7 @@ select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg -- Test that child does not inherit NO INHERIT constraints create table c1 () inherits (p1); +NOTICE: table has parent, setting distribution columns to match parent table \d p1 Table "public.p1" Column | Type | Collation | Nullable | Default @@ -774,14 +851,19 @@ Distributed by: (ff1) -- Test that child does not override inheritable constraints of the parent create table c2 (constraint p2chk check (ff1 > 10) no inherit) inherits (p1); --fails +NOTICE: table has parent, setting distribution columns to match parent table ERROR: constraint "p2chk" conflicts with inherited constraint on relation "c2" drop table p1 cascade; NOTICE: drop cascades to table c1 -- Tests for casting between the rowtypes of parent and child -- tables. See the pgsql-hackers thread beginning Dec. 4/04 create table base (i integer); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'i' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table derived () inherits (base); +NOTICE: table has parent, setting distribution columns to match parent table create table more_derived (like derived, b int) inherits (derived); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging column "i" with inherited definition insert into derived (i) values (0); select derived::base from derived; @@ -804,25 +886,31 @@ explain (verbose on, costs off) select row(i, b)::more_derived::derived::base fr Output: ((ROW(i, b)::more_derived)::base) -> Seq Scan on public.more_derived Output: (ROW(i, b)::more_derived)::base + Settings: optimizer = 'on' Optimizer: Postgres query optimizer -(5 rows) +(6 rows) explain (verbose on, costs off) select (1, 2)::more_derived::derived::base; - QUERY PLAN ------------------------ + QUERY PLAN +---------------------------- Result Output: '(1)'::base -(2 rows) + Settings: optimizer = 'on' + Optimizer: GPORCA +(4 rows) drop table more_derived; drop table derived; drop table base; create table p1(ff1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'ff1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table p2(f1 text); NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create function p2text(p2) returns text as 'select $1.f1' language sql; create table c1(f3 int) inherits(p1,p2); +NOTICE: table has parent, setting distribution columns to match parent table insert into c1 values(123456789, 'hi', 42); select p2text(c1.*) from c1; p2text @@ -835,8 +923,11 @@ drop table c1; drop table p2; drop table p1; CREATE TABLE ac (aa TEXT); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'aa' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. alter table ac add constraint ac_check check (aa is not null); CREATE TABLE bc (bb TEXT) INHERITS (ac); +NOTICE: table has parent, setting distribution columns to match parent table select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc') order by 1,2; relname | conname | contype | conislocal | coninhcount | consrc ---------+----------+---------+------------+-------------+------------------ @@ -845,10 +936,10 @@ select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg (2 rows) insert into ac (aa) values (NULL); -ERROR: new row for relation "ac" violates check constraint "ac_check" +ERROR: new row for relation "ac" violates check constraint "ac_check" (seg0 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=3130283) DETAIL: Failing row contains (null). insert into bc (aa) values (NULL); -ERROR: new row for relation "bc" violates check constraint "ac_check" +ERROR: new row for relation "bc" violates check constraint "ac_check" (seg0 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=3130283) DETAIL: Failing row contains (null, null). alter table bc drop constraint ac_check; -- fail, disallowed ERROR: cannot drop inherited constraint "ac_check" of relation "bc" @@ -868,10 +959,10 @@ select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg (2 rows) insert into ac (aa) values (NULL); -ERROR: new row for relation "ac" violates check constraint "ac_aa_check" +ERROR: new row for relation "ac" violates check constraint "ac_aa_check" (seg0 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=3130283) DETAIL: Failing row contains (null). insert into bc (aa) values (NULL); -ERROR: new row for relation "bc" violates check constraint "ac_aa_check" +ERROR: new row for relation "bc" violates check constraint "ac_aa_check" (seg0 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=3130283) DETAIL: Failing row contains (null, null). alter table bc drop constraint ac_aa_check; -- fail, disallowed ERROR: cannot drop inherited constraint "ac_aa_check" of relation "bc" @@ -906,7 +997,10 @@ select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg drop table bc; drop table ac; create table ac (a int constraint check_a check (a <> 0)); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table bc (a int constraint check_a check (a <> 0), b int constraint check_b check (b <> 0)) inherits (ac); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging column "a" with inherited definition NOTICE: merging constraint "check_a" with inherited definition select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc') order by 1,2; @@ -920,8 +1014,13 @@ select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg drop table bc; drop table ac; create table ac (a int constraint check_a check (a <> 0)); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table bc (b int constraint check_b check (b <> 0)); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table cc (c int constraint check_c check (c <> 0)) inherits (ac, bc); +NOTICE: table has parent, setting distribution columns to match parent table select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg_get_expr(pgc.conbin, pc.oid) as consrc from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname in ('ac', 'bc', 'cc') order by 1,2; relname | conname | contype | conislocal | coninhcount | consrc ---------+---------+---------+------------+-------------+---------- @@ -947,20 +1046,26 @@ drop table cc; drop table bc; drop table ac; create table p1(f1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table p2(f2 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f2' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table c1(f3 int) inherits(p1,p2); +NOTICE: table has parent, setting distribution columns to match parent table insert into c1 values(1,-1,2); alter table p2 add constraint cc check (f2>0); -- fail -ERROR: check constraint "cc" of relation "c1" is violated by some row +ERROR: check constraint "cc" of relation "c1" is violated by some row (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) alter table p2 add check (f2>0); -- check it without a name, too -ERROR: check constraint "p2_f2_check" of relation "c1" is violated by some row +ERROR: check constraint "p2_f2_check" of relation "c1" is violated by some row (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) delete from c1; insert into c1 values(1,1,2); alter table p2 add check (f2>0); insert into c1 values(1,-1,2); -- fail -ERROR: new row for relation "c1" violates check constraint "p2_f2_check" +ERROR: new row for relation "c1" violates check constraint "p2_f2_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (1, -1, 2). create table c2(f3 int) inherits(p1,p2); +NOTICE: table has parent, setting distribution columns to match parent table \d c2 Table "public.c2" Column | Type | Collation | Nullable | Default @@ -975,6 +1080,7 @@ Inherits: p1, Distributed by: (f1) create table c3 (f4 int) inherits(c1,c2); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "f1" NOTICE: merging multiple inherited definitions of column "f2" NOTICE: merging multiple inherited definitions of column "f3" @@ -999,7 +1105,10 @@ drop cascades to table c2 drop cascades to table c3 drop table p2 cascade; create table pp1 (f1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table cc1 (f2 text, f3 int) inherits (pp1); +NOTICE: table has parent, setting distribution columns to match parent table alter table pp1 add column a1 int check (a1 > 0); \d cc1 Table "public.cc1" @@ -1015,6 +1124,7 @@ Inherits: pp1 Distributed by: (f1) create table cc2(f4 float) inherits(pp1,cc1); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "f1" NOTICE: merging multiple inherited definitions of column "a1" \d cc2 @@ -1058,8 +1168,13 @@ DETAIL: drop cascades to table cc1 drop cascades to table cc2 -- Test for renaming in simple multiple inheritance CREATE TABLE inht1 (a int, b int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE inhs1 (b int, c int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE inhts (d int) INHERITS (inht1, inhs1); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "b" ALTER TABLE inht1 RENAME a TO aa; ALTER TABLE inht1 RENAME b TO bb; -- to be failed @@ -1082,8 +1197,11 @@ Distributed by: (aa) DROP TABLE inhts; -- Test for renaming in diamond inheritance CREATE TABLE inht2 (x int) INHERITS (inht1); +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE inht3 (y int) INHERITS (inht1); +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE inht4 (z int) INHERITS (inht2, inht3); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "aa" NOTICE: merging multiple inherited definitions of column "b" ALTER TABLE inht1 RENAME aa TO aaa; @@ -1101,6 +1219,7 @@ Inherits: inht2, Distributed by: (aaa) CREATE TABLE inhts (d int) INHERITS (inht2, inhs1); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging multiple inherited definitions of column "b" ALTER TABLE inht1 RENAME aaa TO aaaa; ALTER TABLE inht1 RENAME b TO bb; -- to be failed @@ -1153,6 +1272,7 @@ drop cascades to table inht4 -- Test non-inheritable indices [UNIQUE, EXCLUDE] constraints CREATE TABLE test_constraints (id int, val1 varchar, val2 int, UNIQUE(val1, val2)); CREATE TABLE test_constraints_inh () INHERITS (test_constraints); +NOTICE: table has parent, setting distribution columns to match parent table \d+ test_constraints Table "public.test_constraints" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description @@ -1193,7 +1313,10 @@ CREATE TABLE test_ex_constraints ( dkey inet, EXCLUDE USING gist (dkey inet_ops WITH =, c WITH &&) ); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'dkey' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints); +NOTICE: table has parent, setting distribution columns to match parent table \d+ test_ex_constraints Table "public.test_ex_constraints" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description @@ -1229,7 +1352,11 @@ DROP TABLE test_ex_constraints; -- Test non-inheritable foreign key constraints CREATE TABLE test_primary_constraints(id int PRIMARY KEY); CREATE TABLE test_foreign_constraints(id1 int REFERENCES test_primary_constraints(id)); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'id1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +WARNING: referential integrity (FOREIGN KEY) constraints are not supported in Apache Cloudberry, will not be enforced CREATE TABLE test_foreign_constraints_inh () INHERITS (test_foreign_constraints); +NOTICE: table has parent, setting distribution columns to match parent table \d+ test_primary_constraints Table "public.test_primary_constraints" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description @@ -1275,8 +1402,10 @@ DROP TABLE test_primary_constraints; create table inh_fk_1 (a int primary key); insert into inh_fk_1 values (1), (2), (3); create table inh_fk_2 (x int primary key, y int references inh_fk_1 on delete cascade); +WARNING: referential integrity (FOREIGN KEY) constraints are not supported in Apache Cloudberry, will not be enforced insert into inh_fk_2 values (11, 1), (22, 2), (33, 3); create table inh_fk_2_child () inherits (inh_fk_2); +NOTICE: table has parent, setting distribution columns to match parent table insert into inh_fk_2_child values (111, 1), (222, 2); -- The cascading deletion doesn't work on GPDB, because foreign keys are not -- enforced in general. So this produces different result than on upstream. @@ -1301,7 +1430,10 @@ select * from inh_fk_2 order by 1, 2; drop table inh_fk_1, inh_fk_2, inh_fk_2_child; -- Test that parent and child CHECK constraints can be created in either order create table p1(f1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table p1_c1() inherits(p1); +NOTICE: table has parent, setting distribution columns to match parent table alter table p1 add constraint inh_check_constraint1 check (f1 > 0); alter table p1_c1 add constraint inh_check_constraint1 check (f1 > 0); NOTICE: merging constraint "inh_check_constraint1" with inherited definition @@ -1323,7 +1455,10 @@ drop table p1 cascade; NOTICE: drop cascades to table p1_c1 -- Test that a valid child can have not-valid parent, but not vice versa create table invalid_check_con(f1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table invalid_check_con_child() inherits(invalid_check_con); +NOTICE: table has parent, setting distribution columns to match parent table alter table invalid_check_con_child add constraint inh_check_constraint check(f1 > 0) not valid; alter table invalid_check_con add constraint inh_check_constraint check(f1 > 0); -- fail ERROR: constraint "inh_check_constraint" conflicts with NOT VALID constraint on relation "invalid_check_con_child" @@ -1333,10 +1468,10 @@ alter table invalid_check_con_child add constraint inh_check_constraint check(f1 alter table invalid_check_con add constraint inh_check_constraint check(f1 > 0) not valid; NOTICE: merging constraint "inh_check_constraint" with inherited definition insert into invalid_check_con values(0); -- fail -ERROR: new row for relation "invalid_check_con" violates check constraint "inh_check_constraint" +ERROR: new row for relation "invalid_check_con" violates check constraint "inh_check_constraint" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (0). insert into invalid_check_con_child values(0); -- fail -ERROR: new row for relation "invalid_check_con_child" violates check constraint "inh_check_constraint" +ERROR: new row for relation "invalid_check_con_child" violates check constraint "inh_check_constraint" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (0). select conrelid::regclass::text as relname, conname, convalidated, conislocal, coninhcount, connoinherit @@ -1356,9 +1491,11 @@ create temp table patest0 (id, x) as select x, x from generate_series(0,1000) x distributed by (id); create temp table patest1() inherits (patest0); +NOTICE: table has parent, setting distribution columns to match parent table insert into patest1 select x, x from generate_series(0,1000) x; create temp table patest2() inherits (patest0); +NOTICE: table has parent, setting distribution columns to match parent table insert into patest2 select x, x from generate_series(0,1000) x; create index patest0i on patest0(id); @@ -1371,8 +1508,8 @@ set enable_seqscan=off; set enable_bitmapscan=off; explain (costs off) select * from patest0 join (select f1 from int4_tbl where f1 < 10 and f1 > -10 limit 1) ss on id = f1; - QUERY PLAN ----------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Hash Join Hash Cond: (patest0.id = int4_tbl.f1) @@ -1493,8 +1630,8 @@ select * from matest0 order by 1-id; (6 rows) explain (verbose, costs off) select min(1-id) from matest0; - QUERY PLAN ------------------------------------------------------ + QUERY PLAN +-------------------------------------------------------------- Finalize Aggregate Output: min((1 - matest0.id)) -> Gather Motion 3:1 (slice1; segments: 3) @@ -1528,8 +1665,8 @@ set enable_parallel_append = off; -- Don't let parallel-append interfere -- of append with bitmapscan + sort set enable_bitmapscan = off; explain (verbose, costs off) select * from matest0 order by 1-id; - QUERY PLAN ------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Output: matest0.id, matest0.name, ((1 - matest0.id)) Merge Key: ((1 - matest0.id)) @@ -1562,8 +1699,8 @@ select * from matest0 order by 1-id; (6 rows) explain (verbose, costs off) select min(1-id) from matest0; - QUERY PLAN --------------------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------------------- Result Output: $0 InitPlan 1 (returns $0) (slice1) @@ -1614,7 +1751,10 @@ drop cascades to table matest3 -- a plan with extraneous sorting -- create table matest0 (a int, b int, c int, d int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table matest1 () inherits(matest0); +NOTICE: table has parent, setting distribution columns to match parent table create index matest0i on matest0 (b, c); create index matest1i on matest1 (b, c); set enable_nestloop = off; -- we want a plan with two MergeAppends @@ -1678,7 +1818,7 @@ ORDER BY thousand, tenthous; -> Append -> Seq Scan on tenk1 -> Seq Scan on tenk1 tenk1_1 - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (8 rows) explain (costs off) @@ -1686,8 +1826,8 @@ SELECT thousand, tenthous, thousand+tenthous AS x FROM tenk1 UNION ALL SELECT 42, 42, hundred FROM tenk1 ORDER BY thousand, tenthous; - QUERY PLAN ------------------------------------------------------- + QUERY PLAN +-------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: tenk1.thousand, tenk1.tenthous -> Sort @@ -1695,7 +1835,7 @@ ORDER BY thousand, tenthous; -> Append -> Seq Scan on tenk1 -> Seq Scan on tenk1 tenk1_1 - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (8 rows) explain (costs off) @@ -1712,7 +1852,7 @@ ORDER BY thousand, tenthous; -> Append -> Seq Scan on tenk1 -> Seq Scan on tenk1 tenk1_1 - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (8 rows) -- Check min/max aggregate optimization @@ -1721,15 +1861,15 @@ SELECT min(x) FROM (SELECT unique1 AS x FROM tenk1 a UNION ALL SELECT unique2 AS x FROM tenk1 b) s; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +------------------------------------------------ Finalize Aggregate -> Gather Motion 3:1 (slice1; segments: 3) -> Partial Aggregate -> Append -> Seq Scan on tenk1 a -> Seq Scan on tenk1 b - Optimizer: Pivotal Optimizer (GPORCA) version 2.74.0 + Optimizer: GPORCA (7 rows) explain (costs off) @@ -1737,15 +1877,15 @@ SELECT min(y) FROM (SELECT unique1 AS x, unique1 AS y FROM tenk1 a UNION ALL SELECT unique2 AS x, unique2 AS y FROM tenk1 b) s; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +------------------------------------------------ Finalize Aggregate -> Gather Motion 3:1 (slice1; segments: 3) -> Partial Aggregate -> Append -> Seq Scan on tenk1 a -> Seq Scan on tenk1 b - Optimizer: Pivotal Optimizer (GPORCA) version 3.83.0 + Optimizer: GPORCA (7 rows) -- XXX planner doesn't recognize that index on unique2 is sufficiently sorted @@ -1755,8 +1895,8 @@ SELECT x, y FROM UNION ALL SELECT unique2 AS x, unique2 AS y FROM tenk1 b) s ORDER BY x, y; - QUERY PLAN --------------------------------------------------- + QUERY PLAN +------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) Merge Key: a.thousand, a.tenthous -> Sort @@ -1764,7 +1904,7 @@ ORDER BY x, y; -> Append -> Seq Scan on tenk1 a -> Seq Scan on tenk1 b - Optimizer: Pivotal Optimizer (GPORCA) version 2.74.0 + Optimizer: GPORCA (8 rows) -- exercise rescan code path via a repeatedly-evaluated subquery @@ -1816,7 +1956,10 @@ rollback; -- Check handling of a constant-null CHECK constraint -- create table cnullparent (f1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table cnullchild (check (f1 = 1 or f1 = null)) inherits(cnullparent); +NOTICE: table has parent, setting distribution columns to match parent table insert into cnullchild values(1); insert into cnullchild values(2); insert into cnullchild values(null); @@ -1840,11 +1983,18 @@ NOTICE: drop cascades to table cnullchild -- Check use of temporary tables with inheritance trees -- create table inh_perm_parent (a1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table inh_temp_parent (a1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table inh_temp_child () inherits (inh_perm_parent); -- ok +NOTICE: table has parent, setting distribution columns to match parent table create table inh_perm_child () inherits (inh_temp_parent); -- error +NOTICE: table has parent, setting distribution columns to match parent table ERROR: cannot inherit from temporary relation "inh_temp_parent" create temp table inh_temp_child_2 () inherits (inh_temp_parent); -- ok +NOTICE: table has parent, setting distribution columns to match parent table insert into inh_perm_parent values (1); insert into inh_temp_parent values (2); insert into inh_temp_child values (3); @@ -1852,8 +2002,8 @@ insert into inh_temp_child_2 values (4); select tableoid::regclass, a1 from inh_perm_parent; tableoid | a1 -----------------+---- - inh_perm_parent | 1 inh_temp_child | 3 + inh_perm_parent | 1 (2 rows) select tableoid::regclass, a1 from inh_temp_parent; @@ -1874,85 +2024,117 @@ NOTICE: drop cascades to table inh_temp_child_2 create table list_parted ( a varchar ) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table part_ab_cd partition of list_parted for values in ('ab', 'cd'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_ef_gh partition of list_parted for values in ('ef', 'gh'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_null_xy partition of list_parted for values in (null, 'xy'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from list_parted; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: list_parted +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on list_parted Number of partitions to scan: 3 (out of 3) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (4 rows) explain (costs off) select * from list_parted where a is null; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: list_parted +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on list_parted Number of partitions to scan: 1 (out of 3) Filter: (a IS NULL) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from list_parted where a is not null; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: list_parted +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on list_parted Number of partitions to scan: 3 (out of 3) Filter: (NOT (a IS NULL)) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef'); - QUERY PLAN +NOTICE: One or more columns in the following table(s) do not have statistics: list_parted +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN ---------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on list_parted Number of partitions to scan: 2 (out of 3) Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd'); - QUERY PLAN +NOTICE: One or more columns in the following table(s) do not have statistics: list_parted +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN --------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on list_parted Number of partitions to scan: 1 (out of 3) Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[]))) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from list_parted where a = 'ab'; +NOTICE: One or more columns in the following table(s) do not have statistics: list_parted +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on list_parted Number of partitions to scan: 1 (out of 3) Filter: ((a)::text = 'ab'::text) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) create table range_list_parted ( a int, b char(2) ) partition by range (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table part_1_10_ab partition of part_1_10 for values in ('ab'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_1_10_cd partition of part_1_10 for values in ('cd'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table part_10_20_ab partition of part_10_20 for values in ('ab'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_10_20_cd partition of part_10_20 for values in ('cd'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table part_21_30_ab partition of part_21_30 for values in ('ab'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_21_30_cd partition of part_21_30 for values in ('cd'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_40_inf partition of range_list_parted for values from (40) to (maxvalue) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table part_40_inf_ab partition of part_40_inf for values in ('ab'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_40_inf_cd partition of part_40_inf for values in ('cd'); +NOTICE: table has parent, setting distribution columns to match parent table create table part_40_inf_null partition of part_40_inf for values in (null); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from range_list_parted; QUERY PLAN -------------------------------------------------------------- @@ -2014,16 +2196,17 @@ explain (costs off) select * from range_list_parted where a between 3 and 23 and /* Should select no rows because range partition key cannot be null */ explain (costs off) select * from range_list_parted where a is null; - QUERY PLAN --------------------------- + QUERY PLAN +------------------------------------- Result One-Time Filter: false -(2 rows) + Optimizer: Postgres query optimizer +(3 rows) /* Should only select rows from the null-accepting partition */ explain (costs off) select * from range_list_parted where b is null; - QUERY PLAN ------------------------------------------- + QUERY PLAN +------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) -> Seq Scan on part_40_inf_null range_list_parted Filter: (b IS NULL) @@ -2031,8 +2214,8 @@ explain (costs off) select * from range_list_parted where b is null; (4 rows) explain (costs off) select * from range_list_parted where a is not null and a < 67; - QUERY PLAN ------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Append -> Seq Scan on part_1_10_ab range_list_parted_1 @@ -2075,16 +2258,25 @@ drop table range_list_parted; -- check that constraint exclusion is able to cope with the partition -- constraint emitted for multi-column range partitioned tables create table mcrparted (a int, b int, c int) partition by range (a, abs(b), c); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table mcrparted_def partition of mcrparted default; +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted0 partition of mcrparted for values from (minvalue, minvalue, minvalue) to (1, 1, 1); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted1 partition of mcrparted for values from (1, 1, 1) to (10, 5, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted2 partition of mcrparted for values from (10, 5, 10) to (10, 10, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted3 partition of mcrparted for values from (11, 1, 1) to (20, 10, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted4 partition of mcrparted for values from (20, 10, 10) to (20, 20, 20); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted5 partition of mcrparted for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from mcrparted where a = 0; -- scans mcrparted0, mcrparted_def - QUERY PLAN ------------------------------------------- + QUERY PLAN +--------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Append -> Seq Scan on mcrparted0 mcrparted_1 @@ -2121,8 +2313,8 @@ explain (costs off) select * from mcrparted where a = 10 and abs(b) = 5; -- scan (9 rows) explain (costs off) select * from mcrparted where abs(b) = 5; -- scans all partitions - QUERY PLAN ------------------------------------------- + QUERY PLAN +--------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Append -> Seq Scan on mcrparted0 mcrparted_1 @@ -2143,8 +2335,8 @@ explain (costs off) select * from mcrparted where abs(b) = 5; -- scans all parti (17 rows) explain (costs off) select * from mcrparted where a > -1; -- scans all partitions - QUERY PLAN -------------------------------------------- + QUERY PLAN +--------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Append -> Seq Scan on mcrparted0 mcrparted_1 @@ -2174,8 +2366,8 @@ explain (costs off) select * from mcrparted where a = 20 and abs(b) = 10 and c > (4 rows) explain (costs off) select * from mcrparted where a = 20 and c > 20; -- scans mcrparted3, mcrparte4, mcrparte5, mcrparted_def - QUERY PLAN ------------------------------------------------ + QUERY PLAN +--------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Append -> Seq Scan on mcrparted3 mcrparted_1 @@ -2192,22 +2384,29 @@ explain (costs off) select * from mcrparted where a = 20 and c > 20; -- scans mc -- check that partitioned table Appends cope with being referenced in -- subplans create table parted_minmax (a int, b varchar(16)) partition by range (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table parted_minmax1 partition of parted_minmax for values from (1) to (10); +NOTICE: table has parent, setting distribution columns to match parent table create index parted_minmax1i on parted_minmax1 (a, b); insert into parted_minmax values (1,'12345'); explain (costs off) select min(a), max(a) from parted_minmax where b = '12345'; - QUERY PLAN ---------------------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: parted_minmax +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------------------- Finalize Aggregate -> Gather Motion 3:1 (slice1; segments: 3) -> Partial Aggregate -> Dynamic Seq Scan on parted_minmax Number of partitions to scan: 1 (out of 1) Filter: ((b)::text = '12345'::text) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (7 rows) select min(a), max(a) from parted_minmax where b = '12345'; +NOTICE: One or more columns in the following table(s) do not have statistics: parted_minmax +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. min | max -----+----- 1 | 1 @@ -2219,8 +2418,8 @@ drop table parted_minmax; create index mcrparted_a_abs_c_idx on mcrparted (a, abs(b), c); -- MergeAppend must be used when a default partition exists explain (costs off) select * from mcrparted order by a, abs(b), c; - QUERY PLAN -------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: mcrparted.a, (abs(mcrparted.b)), mcrparted.c -> Merge Append @@ -2273,11 +2472,14 @@ explain (costs off) select * from mcrparted order by a desc, abs(b) desc, c desc -- that are unordered. drop table mcrparted5; create table mcrparted5 partition of mcrparted for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue) partition by list (a); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted5a partition of mcrparted5 for values in(20); +NOTICE: table has parent, setting distribution columns to match parent table create table mcrparted5_def partition of mcrparted5 default; +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from mcrparted order by a, abs(b), c; - QUERY PLAN ---------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: mcrparted.a, (abs(mcrparted.b)), mcrparted.c -> Append @@ -2298,8 +2500,8 @@ drop table mcrparted5_def; -- into the main Append when the sub-partition is unordered but contains -- just a single sub-partition. explain (costs off) select a, abs(b) from mcrparted order by a, abs(b), c; - QUERY PLAN ---------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: mcrparted.a, (abs(mcrparted.b)), mcrparted.c -> Append @@ -2315,8 +2517,8 @@ explain (costs off) select a, abs(b) from mcrparted order by a, abs(b), c; -- check that Append is used when the sub-partitioned tables are pruned -- during planning. explain (costs off) select * from mcrparted where a < 20 order by a, abs(b), c; - QUERY PLAN -------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: mcrparted.a, (abs(mcrparted.b)), mcrparted.c -> Append @@ -2332,36 +2534,46 @@ explain (costs off) select * from mcrparted where a < 20 order by a, abs(b), c; (12 rows) create table mclparted (a int) partition by list(a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table mclparted1 partition of mclparted for values in(1); +NOTICE: table has parent, setting distribution columns to match parent table create table mclparted2 partition of mclparted for values in(2); +NOTICE: table has parent, setting distribution columns to match parent table create index on mclparted (a); -- Ensure an Append is used for a list partition with an order by. explain (costs off) select * from mclparted order by a; - QUERY PLAN ------------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: mclparted +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: a -> Sort Sort Key: a -> Dynamic Seq Scan on mclparted Number of partitions to scan: 2 (out of 2) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (7 rows) -- Ensure a MergeAppend is used when a partition exists with interleaved -- datums in the partition bound. create table mclparted3_5 partition of mclparted for values in(3,5); +NOTICE: table has parent, setting distribution columns to match parent table create table mclparted4 partition of mclparted for values in(4); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from mclparted order by a; - QUERY PLAN ------------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: mclparted +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: a -> Sort Sort Key: a -> Dynamic Seq Scan on mclparted Number of partitions to scan: 4 (out of 4) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (7 rows) drop table mclparted; @@ -2373,8 +2585,8 @@ create index on mcrparted2 (a, abs(b), c); create index on mcrparted3 (a, abs(b), c); create index on mcrparted4 (a, abs(b), c); explain (costs off) select * from mcrparted where a < 20 order by a, abs(b), c limit 1; - QUERY PLAN -------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------- Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: mcrparted.a, (abs(mcrparted.b)), mcrparted.c @@ -2397,8 +2609,8 @@ set enable_bitmapscan = 0; -- Ensure Append node can be used when the partition is ordered by some -- pathkeys which were deemed redundant. explain (costs off) select * from mcrparted where a = 10 order by a, abs(b), c; - QUERY PLAN -------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) Merge Key: (abs(mcrparted.b)), mcrparted.c -> Append @@ -2413,32 +2625,44 @@ reset enable_bitmapscan; drop table mcrparted; -- Ensure LIST partitions allow an Append to be used instead of a MergeAppend create table bool_lp (b bool) partition by list(b); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table bool_lp_true partition of bool_lp for values in(true); +NOTICE: table has parent, setting distribution columns to match parent table create table bool_lp_false partition of bool_lp for values in(false); +NOTICE: table has parent, setting distribution columns to match parent table create index on bool_lp (b); explain (costs off) select * from bool_lp order by b; - QUERY PLAN ------------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: bool_lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: b -> Sort Sort Key: b -> Dynamic Seq Scan on bool_lp Number of partitions to scan: 2 (out of 2) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (7 rows) drop table bool_lp; -- Ensure const bool quals can be properly detected as redundant create table bool_rp (b bool, a int) partition by range(b,a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table bool_rp_false_1k partition of bool_rp for values from (false,0) to (false,1000); +NOTICE: table has parent, setting distribution columns to match parent table create table bool_rp_true_1k partition of bool_rp for values from (true,0) to (true,1000); +NOTICE: table has parent, setting distribution columns to match parent table create table bool_rp_false_2k partition of bool_rp for values from (false,1000) to (false,2000); +NOTICE: table has parent, setting distribution columns to match parent table create table bool_rp_true_2k partition of bool_rp for values from (true,1000) to (true,2000); +NOTICE: table has parent, setting distribution columns to match parent table create index on bool_rp (b,a); explain (costs off) select * from bool_rp where b = true order by b,a; - QUERY PLAN ------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) Merge Key: bool_rp.b, bool_rp.a -> Append @@ -2450,8 +2674,8 @@ explain (costs off) select * from bool_rp where b = true order by b,a; (8 rows) explain (costs off) select * from bool_rp where b = false order by b,a; - QUERY PLAN --------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) Merge Key: bool_rp.b, bool_rp.a -> Append @@ -2466,8 +2690,8 @@ explain (costs off) select * from bool_rp where b = false order by b,a; set enable_seqscan=off; set enable_bitmapscan=off; explain (costs off) select * from bool_rp where b = true order by a; - QUERY PLAN ------------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) Merge Key: bool_rp.a -> Sort @@ -2481,8 +2705,8 @@ explain (costs off) select * from bool_rp where b = true order by a; (10 rows) explain (costs off) select * from bool_rp where b = false order by a; - QUERY PLAN --------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) Merge Key: bool_rp.a -> Sort @@ -2501,12 +2725,16 @@ drop table bool_rp; -- Ensure an Append scan is chosen when the partition order is a subset of -- the required order. create table range_parted (a int, b int, c int) partition by range(a, b); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table range_parted1 partition of range_parted for values from (0,0) to (10,10); +NOTICE: table has parent, setting distribution columns to match parent table create table range_parted2 partition of range_parted for values from (10,10) to (20,20); +NOTICE: table has parent, setting distribution columns to match parent table create index on range_parted (a,b,c); explain (costs off) select * from range_parted order by a,b,c; - QUERY PLAN ----------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: range_parted.a, range_parted.b, range_parted.c -> Append @@ -2516,8 +2744,8 @@ explain (costs off) select * from range_parted order by a,b,c; (6 rows) explain (costs off) select * from range_parted order by a desc,b desc,c desc; - QUERY PLAN -------------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Merge Key: range_parted.a, range_parted.b, range_parted.c -> Append @@ -2530,6 +2758,8 @@ drop table range_parted; -- Check that we allow access to a child table's statistics when the user -- has permissions only for the parent table. create table permtest_parent (a int, b text, c text) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table permtest_child (b text, c text, a int) partition by list (b) distributed by (a); create table permtest_grandchild (c text, b text, a int) distributed by (a); alter table permtest_child attach partition permtest_grandchild for values in ('a'); @@ -2539,6 +2769,7 @@ insert into permtest_parent select 1, 'a', left(md5(i::text), 5) from generate_series(0, 100) i; analyze permtest_parent; create role regress_no_child_access; +NOTICE: resource queue required -- using default resource queue "pg_default" revoke all on permtest_grandchild from regress_no_child_access; NOTICE: no privileges could be revoked grant select on permtest_parent to regress_no_child_access; @@ -2547,8 +2778,8 @@ set session authorization regress_no_child_access; explain (costs off) select * from permtest_parent p1 inner join permtest_parent p2 on p1.a = p2.a and p1.c ~ 'a1$'; - QUERY PLAN ------------------------------------------- + QUERY PLAN +------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) -> Hash Join Hash Cond: (p2.a = p1.a) @@ -2563,8 +2794,8 @@ explain (costs off) explain (costs off) select * from permtest_parent p1 inner join permtest_parent p2 on p1.a = p2.a and left(p1.c, 3) ~ 'a1$'; - QUERY PLAN ----------------------------------------------- + QUERY PLAN +---------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Hash Join Hash Cond: (p2.a = p1.a) @@ -2583,8 +2814,8 @@ set session authorization regress_no_child_access; explain (costs off) select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2 on p1.a = p2.a and p1.c ~ 'a1$'; - QUERY PLAN ------------------------------------------- + QUERY PLAN +------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) -> Hash Join Hash Cond: (p2.a = p1.a) @@ -2625,6 +2856,8 @@ CREATE TABLE errtst_parent ( data int NOT NULL DEFAULT 0, CONSTRAINT shdata_small CHECK(shdata < 3) ) PARTITION BY RANGE (partid); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'partid' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- fast defaults lead to attribute mapping being used in one -- direction, but not the other CREATE TABLE errtst_child_fastdef ( @@ -2632,6 +2865,8 @@ CREATE TABLE errtst_child_fastdef ( shdata int not null, CONSTRAINT shdata_small CHECK(shdata < 3) ); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'partid' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- no remapping in either direction necessary CREATE TABLE errtst_child_plaindef ( partid int not null, @@ -2640,6 +2875,8 @@ CREATE TABLE errtst_child_plaindef ( CONSTRAINT shdata_small CHECK(shdata < 3), CHECK(data < 10) ); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'partid' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- remapping in both direction CREATE TABLE errtst_child_reorder ( data int NOT NULL DEFAULT 0, @@ -2659,33 +2896,33 @@ INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', '5'); INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '5'); -- insert with child check constraint error INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', '10'); -ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" +ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (0, 1, 10). INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', '10'); -ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" +ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3130282) DETAIL: Failing row contains (10, 1, 10). INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '10'); -ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" +ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (20, 1, 10). -- insert with child not null constraint error INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', NULL); -ERROR: null value in column "data" of relation "errtst_child_fastdef" violates not-null constraint +ERROR: null value in column "data" of relation "errtst_child_fastdef" violates not-null constraint (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (0, 1, null). INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', NULL); -ERROR: null value in column "data" of relation "errtst_child_plaindef" violates not-null constraint +ERROR: null value in column "data" of relation "errtst_child_plaindef" violates not-null constraint (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3130282) DETAIL: Failing row contains (10, 1, null). INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', NULL); -ERROR: null value in column "data" of relation "errtst_child_reorder" violates not-null constraint +ERROR: null value in column "data" of relation "errtst_child_reorder" violates not-null constraint (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (20, 1, null). -- insert with shared check constraint error INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '5', '5'); -ERROR: new row for relation "errtst_child_fastdef" violates check constraint "shdata_small" +ERROR: new row for relation "errtst_child_fastdef" violates check constraint "shdata_small" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (0, 5, 5). INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '5', '5'); -ERROR: new row for relation "errtst_child_plaindef" violates check constraint "shdata_small" +ERROR: new row for relation "errtst_child_plaindef" violates check constraint "shdata_small" (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3130282) DETAIL: Failing row contains (10, 5, 5). INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '5', '5'); -ERROR: new row for relation "errtst_child_reorder" violates check constraint "shdata_small" +ERROR: new row for relation "errtst_child_reorder" violates check constraint "shdata_small" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (20, 5, 5). -- within partition update without child check constraint violation BEGIN; @@ -2695,13 +2932,13 @@ UPDATE errtst_parent SET data = data + 1 WHERE partid = 20; ROLLBACK; -- within partition update with child check constraint violation UPDATE errtst_parent SET data = data + 10 WHERE partid = 0; -ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" +ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (0, 1, 15). UPDATE errtst_parent SET data = data + 10 WHERE partid = 10; -ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" +ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3130282) DETAIL: Failing row contains (10, 1, 15). UPDATE errtst_parent SET data = data + 10 WHERE partid = 20; -ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" +ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (20, 1, 15). -- direct leaf partition update, without partition id violation BEGIN; @@ -2711,13 +2948,13 @@ UPDATE errtst_child_reorder SET partid = 21 WHERE partid = 20; ROLLBACK; -- direct leaf partition update, with partition id violation UPDATE errtst_child_fastdef SET partid = partid + 10 WHERE partid = 0; -ERROR: new row for relation "errtst_child_fastdef" violates partition constraint +ERROR: new row for relation "errtst_child_fastdef" violates partition constraint (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3130282) DETAIL: Failing row contains (10, 1, 5). UPDATE errtst_child_plaindef SET partid = partid + 10 WHERE partid = 10; -ERROR: new row for relation "errtst_child_plaindef" violates partition constraint +ERROR: new row for relation "errtst_child_plaindef" violates partition constraint (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (20, 1, 5). UPDATE errtst_child_reorder SET partid = partid + 10 WHERE partid = 20; -ERROR: new row for relation "errtst_child_reorder" violates partition constraint +ERROR: new row for relation "errtst_child_reorder" violates partition constraint (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (5, 1, 30). -- partition move, without child check constraint violation BEGIN; @@ -2727,16 +2964,16 @@ UPDATE errtst_parent SET partid = 0, data = data + 1 WHERE partid = 20; ROLLBACK; -- partition move, with child check constraint violation UPDATE errtst_parent SET partid = 10, data = data + 10 WHERE partid = 0; -ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" +ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" (seg2 2a02:6b8:c37:834b:0:5644:602c:0:7004 pid=3130282) DETAIL: Failing row contains (10, 1, 15). UPDATE errtst_parent SET partid = 20, data = data + 10 WHERE partid = 10; -ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" +ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (20, 1, 15). UPDATE errtst_parent SET partid = 0, data = data + 10 WHERE partid = 20; -ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" +ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Failing row contains (0, 1, 15). -- partition move, without target partition UPDATE errtst_parent SET partid = 30, data = data + 10 WHERE partid = 20; -ERROR: no partition of relation "errtst_parent" found for row +ERROR: no partition of relation "errtst_parent" found for row (seg1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=3130276) DETAIL: Partition key of the failing row contains (partid) = (30). DROP TABLE errtst_parent; diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 4ab11e74d6e..be4221fac9f 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1531,10 +1531,10 @@ WHERE a.aggfnoid = p.oid AND ); aggfnoid | proname | oid | proname ----------+--------------------+------+--------------------- - 9189 | gp_percentile_cont | 9188 | gp_percentile_final - 9190 | gp_percentile_cont | 9188 | gp_percentile_final - 9191 | gp_percentile_cont | 9188 | gp_percentile_final 9192 | gp_percentile_cont | 9188 | gp_percentile_final + 9191 | gp_percentile_cont | 9188 | gp_percentile_final + 9190 | gp_percentile_cont | 9188 | gp_percentile_final + 9189 | gp_percentile_cont | 9188 | gp_percentile_final (4 rows) -- If transfn is strict then either initval should be non-NULL, or @@ -2240,7 +2240,7 @@ ORDER BY 1, 2, 3; | record_ops | record_ops | record | tsquery_ops | tsquery_ops | tsquery | tsvector_ops | tsvector_ops | tsvector -(16 rows) +(17 rows) -- **************** pg_index **************** -- Look for illegal values in pg_index fields. diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index b3e82a7b229..dbb7c48cdf8 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -27,12 +27,20 @@ -- Force generic plans to be used for all prepared statements in this file. set plan_cache_mode = force_generic_plan; create table lp (a char) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table lp_default partition of lp default; +NOTICE: table has parent, setting distribution columns to match parent table create table lp_ef partition of lp for values in ('e', 'f'); +NOTICE: table has parent, setting distribution columns to match parent table create table lp_ad partition of lp for values in ('a', 'd'); +NOTICE: table has parent, setting distribution columns to match parent table create table lp_bc partition of lp for values in ('b', 'c'); +NOTICE: table has parent, setting distribution columns to match parent table create table lp_g partition of lp for values in ('g'); +NOTICE: table has parent, setting distribution columns to match parent table create table lp_null partition of lp for values in (null); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from lp; QUERY PLAN ------------------------------------------ @@ -192,9 +200,14 @@ explain (costs off) select * from lp where a not in ('a', 'd'); -- collation matches the partitioning collation, pruning works create table coll_pruning (a text collate "C") partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table coll_pruning_a partition of coll_pruning for values in ('a'); +NOTICE: table has parent, setting distribution columns to match parent table create table coll_pruning_b partition of coll_pruning for values in ('b'); +NOTICE: table has parent, setting distribution columns to match parent table create table coll_pruning_def partition of coll_pruning default; +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from coll_pruning where a collate "C" = 'a' collate "C"; QUERY PLAN ----------------------------------------------- @@ -220,28 +233,50 @@ explain (costs off) select * from coll_pruning where a collate "POSIX" = 'a' col (9 rows) create table rlp (a int, b varchar) partition by range (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rlp_default partition of rlp default partition by list (a); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp_default_default partition of rlp_default default; +NOTICE: table has parent, setting distribution columns to match parent table create table rlp_default_10 partition of rlp_default for values in (10); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp_default_30 partition of rlp_default for values in (30); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp_default_null partition of rlp_default for values in (null); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp1 partition of rlp for values from (minvalue) to (1); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp2 partition of rlp for values from (1) to (10); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp3 (b varchar, a int) partition by list (b varchar_ops); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- GPDB: distribution policy must match the parent table. alter table rlp3 set distributed by (a); create table rlp3_default partition of rlp3 default; +NOTICE: table has parent, setting distribution columns to match parent table create table rlp3abcd partition of rlp3 for values in ('ab', 'cd'); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp3efgh partition of rlp3 for values in ('ef', 'gh'); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp3nullxy partition of rlp3 for values in (null, 'xy'); +NOTICE: table has parent, setting distribution columns to match parent table alter table rlp attach partition rlp3 for values from (15) to (20); create table rlp4 partition of rlp for values from (20) to (30) partition by range (a); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp4_default partition of rlp4 default; +NOTICE: table has parent, setting distribution columns to match parent table create table rlp4_1 partition of rlp4 for values from (20) to (25); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp4_2 partition of rlp4 for values from (25) to (29); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp5 partition of rlp for values from (31) to (maxvalue) partition by range (a); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp5_default partition of rlp5 default; +NOTICE: table has parent, setting distribution columns to match parent table create table rlp5_1 partition of rlp5 for values from (31) to (40); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from rlp where a < 1; QUERY PLAN ------------------------------------------ @@ -792,15 +827,26 @@ explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = -- multi-column keys create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table mc3p_default partition of mc3p default; +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p0 partition of mc3p for values from (minvalue, minvalue, minvalue) to (1, 1, 1); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p1 partition of mc3p for values from (1, 1, 1) to (10, 5, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p2 partition of mc3p for values from (10, 5, 10) to (10, 10, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p3 partition of mc3p for values from (10, 10, 10) to (10, 10, 20); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p4 partition of mc3p for values from (10, 10, 20) to (10, maxvalue, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p5 partition of mc3p for values from (11, 1, 1) to (20, 10, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p6 partition of mc3p for values from (20, 10, 10) to (20, 20, 20); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p7 partition of mc3p for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from mc3p where a = 1; QUERY PLAN --------------------------------------------- @@ -1103,13 +1149,22 @@ explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 a -- a simpler multi-column keys case create table mc2p (a int, b int) partition by range (a, b); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table mc2p_default partition of mc2p default; +NOTICE: table has parent, setting distribution columns to match parent table create table mc2p0 partition of mc2p for values from (minvalue, minvalue) to (1, minvalue); +NOTICE: table has parent, setting distribution columns to match parent table create table mc2p1 partition of mc2p for values from (1, minvalue) to (1, 1); +NOTICE: table has parent, setting distribution columns to match parent table create table mc2p2 partition of mc2p for values from (1, 1) to (2, minvalue); +NOTICE: table has parent, setting distribution columns to match parent table create table mc2p3 partition of mc2p for values from (2, minvalue) to (2, 1); +NOTICE: table has parent, setting distribution columns to match parent table create table mc2p4 partition of mc2p for values from (2, 1) to (2, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table create table mc2p5 partition of mc2p for values from (2, maxvalue) to (maxvalue, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from mc2p where a < 2; QUERY PLAN --------------------------------------------- @@ -1210,9 +1265,14 @@ explain (costs off) select * from mc2p where b is null; -- boolean partitioning create table boolpart (a bool) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table boolpart_default partition of boolpart default; +NOTICE: table has parent, setting distribution columns to match parent table create table boolpart_t partition of boolpart for values in ('true'); +NOTICE: table has parent, setting distribution columns to match parent table create table boolpart_f partition of boolpart for values in ('false'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from boolpart where a in (true, false); QUERY PLAN ------------------------------------------------------ @@ -1301,10 +1361,16 @@ explain (costs off) select * from boolpart where a is not unknown; (9 rows) create table boolrangep (a bool, b bool, c int) partition by range (a,b,c); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table boolrangep_tf partition of boolrangep for values from ('true', 'false', 0) to ('true', 'false', 100); +NOTICE: table has parent, setting distribution columns to match parent table create table boolrangep_ft partition of boolrangep for values from ('false', 'true', 0) to ('false', 'true', 100); +NOTICE: table has parent, setting distribution columns to match parent table create table boolrangep_ff1 partition of boolrangep for values from ('false', 'false', 0) to ('false', 'false', 50); +NOTICE: table has parent, setting distribution columns to match parent table create table boolrangep_ff2 partition of boolrangep for values from ('false', 'false', 50) to ('false', 'false', 100); +NOTICE: table has parent, setting distribution columns to match parent table -- try a more complex case that's been known to trip up pruning in the past explain (costs off) select * from boolrangep where not a and not b and c = 25; QUERY PLAN @@ -1317,9 +1383,14 @@ explain (costs off) select * from boolrangep where not a and not b and c = 25; -- test scalar-to-array operators create table coercepart (a varchar) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table coercepart_ab partition of coercepart for values in ('ab'); +NOTICE: table has parent, setting distribution columns to match parent table create table coercepart_bc partition of coercepart for values in ('bc'); +NOTICE: table has parent, setting distribution columns to match parent table create table coercepart_cd partition of coercepart for values in ('cd'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from coercepart where a in ('ab', to_char(125, '999')); QUERY PLAN ----------------------------------------------------------- @@ -1449,10 +1520,17 @@ explain (costs off) select * from coercepart where a = all (null::text[]); drop table coercepart; CREATE TABLE part (a INT, b INT) PARTITION BY LIST (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE part_p1 PARTITION OF part FOR VALUES IN (-2,-1,0,1,2); +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE part_p2 PARTITION OF part DEFAULT PARTITION BY RANGE(a); +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE part_p2_p1 PARTITION OF part_p2 DEFAULT; +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE part_rev (b INT, c INT, a INT); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- The distribution key must be the same for partition tables. ALTER TABLE part_rev SET DISTRIBUTED BY(a); ALTER TABLE part ATTACH PARTITION part_rev FOR VALUES IN (3); -- fail @@ -1474,8 +1552,8 @@ EXPLAIN (COSTS OFF) SELECT tableoid::regclass as part, a, b FROM part WHERE a IS (7 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM part p(x) ORDER BY x; - QUERY PLAN ------------------------------------------------------ + QUERY PLAN +--------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) Output: p.x, p.b Merge Key: p.x @@ -1489,8 +1567,8 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM part p(x) ORDER BY x; Output: p_2.x, p_2.b -> Seq Scan on public.part_p2_p1 p_3 Output: p_3.x, p_3.b + Settings: optimizer = 'off', plan_cache_mode = 'force_generic_plan' Optimizer: Postgres query optimizer - Settings: plan_cache_mode = 'force_generic_plan' (15 rows) -- @@ -1597,9 +1675,14 @@ explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 -- -- doesn't prune range partitions create table rp (a int) partition by range (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rp0 partition of rp for values from (minvalue) to (1); +NOTICE: table has parent, setting distribution columns to match parent table create table rp1 partition of rp for values from (1) to (2); +NOTICE: table has parent, setting distribution columns to match parent table create table rp2 partition of rp for values from (2) to (maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from rp where a <> 1; QUERY PLAN ------------------------------------------ @@ -1693,9 +1776,14 @@ explain (costs off) select * from rlp where a = 15 and b <> 'ab' and b <> 'cd' a -- different collations for different keys with same expression -- create table coll_pruning_multi (a text) partition by range (substr(a, 1) collate "POSIX", substr(a, 1) collate "C"); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table coll_pruning_multi1 partition of coll_pruning_multi for values from ('a', 'a') to ('a', 'e'); +NOTICE: table has parent, setting distribution columns to match parent table create table coll_pruning_multi2 partition of coll_pruning_multi for values from ('a', 'e') to ('a', 'z'); +NOTICE: table has parent, setting distribution columns to match parent table create table coll_pruning_multi3 partition of coll_pruning_multi for values from ('b', 'a') to ('b', 'e'); +NOTICE: table has parent, setting distribution columns to match parent table -- no pruning, because no value for the leading key explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e' collate "C"; QUERY PLAN @@ -1738,8 +1826,12 @@ explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e' co -- LIKE operators don't prune -- create table like_op_noprune (a text) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table like_op_noprune1 partition of like_op_noprune for values in ('ABC'); +NOTICE: table has parent, setting distribution columns to match parent table create table like_op_noprune2 partition of like_op_noprune for values in ('BCD'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from like_op_noprune where a like '%BC'; QUERY PLAN ------------------------------------------------------------ @@ -1756,8 +1848,12 @@ explain (costs off) select * from like_op_noprune where a like '%BC'; -- tests wherein clause value requires a cross-type comparison function -- create table lparted_by_int2 (a smallint) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table lparted_by_int2_1 partition of lparted_by_int2 for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table lparted_by_int2_16384 partition of lparted_by_int2 for values in (16384); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from lparted_by_int2 where a = 100000000000000; QUERY PLAN ------------------------------------- @@ -1767,8 +1863,12 @@ explain (costs off) select * from lparted_by_int2 where a = 100000000000000; (3 rows) create table rparted_by_int2 (a smallint) partition by range (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rparted_by_int2_1 partition of rparted_by_int2 for values from (1) to (10); +NOTICE: table has parent, setting distribution columns to match parent table create table rparted_by_int2_16384 partition of rparted_by_int2 for values from (10) to (16384); +NOTICE: table has parent, setting distribution columns to match parent table -- all partitions pruned explain (costs off) select * from rparted_by_int2 where a > 100000000000000; QUERY PLAN @@ -1779,6 +1879,7 @@ explain (costs off) select * from rparted_by_int2 where a > 100000000000000; (3 rows) create table rparted_by_int2_maxvalue partition of rparted_by_int2 for values from (16384) to (maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table -- all partitions but rparted_by_int2_maxvalue pruned explain (costs off) select * from rparted_by_int2 where a > 100000000000000; QUERY PLAN @@ -1799,10 +1900,16 @@ drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, boolrangep, rp, coll_pru -- create table hp (a int, b text, c int) partition by hash (a part_test_int4_ops, b part_test_text_ops); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table hp0 partition of hp for values with (modulus 4, remainder 0); +NOTICE: table has parent, setting distribution columns to match parent table create table hp3 partition of hp for values with (modulus 4, remainder 3); +NOTICE: table has parent, setting distribution columns to match parent table create table hp1 partition of hp for values with (modulus 4, remainder 1); +NOTICE: table has parent, setting distribution columns to match parent table create table hp2 partition of hp for values with (modulus 4, remainder 2); +NOTICE: table has parent, setting distribution columns to match parent table insert into hp values (null, null, 0); insert into hp values (1, null, 1); insert into hp values (1, 'xxx', 2); @@ -2028,28 +2135,43 @@ explain (costs off) select * from hp where a = 1 and b = 'abcde' and drop table hp2; explain (costs off) select * from hp where a = 1 and b = 'abcde' and (c = 2 or c = 3); - QUERY PLAN --------------------------- + QUERY PLAN +------------------------------------- Result One-Time Filter: false -(2 rows) + Optimizer: Postgres query optimizer +(3 rows) -- -- Test runtime partition pruning -- create table ab (a int not null, b int not null) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table ab_a2 partition of ab for values in(2) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a2_b1 partition of ab_a2 for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a2_b2 partition of ab_a2 for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a2_b3 partition of ab_a2 for values in (3); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a1 partition of ab for values in(1) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a1_b1 partition of ab_a1 for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a1_b2 partition of ab_a1 for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a1_b3 partition of ab_a1 for values in (3); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a3 partition of ab for values in(3) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a3_b1 partition of ab_a3 for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a3_b2 partition of ab_a3 for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a3_b3 partition of ab_a3 for values in (3); +NOTICE: table has parent, setting distribution columns to match parent table -- Disallow index only scans as concurrent transactions may stop visibility -- bits being set causing "Heap Fetches" to be unstable in the EXPLAIN ANALYZE -- output. @@ -2172,28 +2294,39 @@ explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2); -- -- recreate partitions dropped above create table hp1 partition of hp for values with (modulus 4, remainder 1); +NOTICE: table has parent, setting distribution columns to match parent table create table hp2 partition of hp for values with (modulus 4, remainder 2); +NOTICE: table has parent, setting distribution columns to match parent table create table hp3 partition of hp for values with (modulus 4, remainder 3); +NOTICE: table has parent, setting distribution columns to match parent table -- Ensure we correctly prune unneeded partitions when there is an IS NULL qual prepare hp_q1 (text) as select * from hp where a is null and b = $1; explain (costs off) execute hp_q1('xxx'); - QUERY PLAN --------------------------------------------- - Append - Subplans Removed: 3 - -> Seq Scan on hp2 hp_1 - Filter: ((a IS NULL) AND (b = $1)) -(4 rows) + QUERY PLAN +-------------------------------------------------- + Gather Motion 1:1 (slice1; segments: 1) + -> Append + Subplans Removed: 3 + -> Seq Scan on hp2 hp_1 + Filter: ((a IS NULL) AND (b = $1)) + Optimizer: Postgres query optimizer +(6 rows) deallocate hp_q1; drop table hp; -- Test a backwards Append scan create table list_part (a int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table list_part1 partition of list_part for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table list_part2 partition of list_part for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table create table list_part3 partition of list_part for values in (3); +NOTICE: table has parent, setting distribution columns to match parent table create table list_part4 partition of list_part for values in (4); +NOTICE: table has parent, setting distribution columns to match parent table insert into list_part select generate_series(1,4); begin; -- Don't select an actual value out of the table as the order of the Append's @@ -2387,6 +2520,8 @@ select explain_parallel_append('select count(*) from ab where (a = (select 1) or -- Test pruning during parallel nested loop query create table lprt_a (a int not null); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- Insert some values we won't find in ab insert into lprt_a select 0 from generate_series(1,100); -- and insert some values that we should find. @@ -2431,7 +2566,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on Index Cond: (a = ANY ('{0,0,1}'::integer[])) -> Sort (actual rows=N loops=N) Sort Key: a.a - Sort Method: quicksort Memory: 60kB + Sort Method: quicksort Memory: 29kB -> Partition Selector (selector id: $0) (actual rows=N loops=N) -> Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{0,0,1}'::integer[])) @@ -2513,7 +2648,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on Index Cond: (a = ANY ('{1,0,3}'::integer[])) -> Sort (actual rows=N loops=N) Sort Key: a.a - Sort Method: quicksort Memory: 110kB + Sort Method: quicksort Memory: 54kB -> Partition Selector (selector id: $0) (actual rows=N loops=N) -> Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{1,0,3}'::integer[])) @@ -2547,7 +2682,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on Index Cond: (a = ANY ('{1,0,0}'::integer[])) -> Sort (actual rows=N loops=N) Sort Key: a.a - Sort Method: quicksort Memory: 60kB + Sort Method: quicksort Memory: 29kB -> Partition Selector (selector id: $0) (actual rows=N loops=N) -> Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{1,0,0}'::integer[])) @@ -2582,7 +2717,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on Index Cond: (a = ANY ('{1,0,0}'::integer[])) -> Sort (actual rows=N loops=N) Sort Key: a.a - Sort Method: quicksort Memory: 60kB + Sort Method: quicksort Memory: 29kB -> Partition Selector (selector id: $0) (actual rows=N loops=N) -> Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{1,0,0}'::integer[])) @@ -2759,6 +2894,8 @@ select * from (select * from ab where a = 1 union all (values(10,5)) union all s -- Another UNION ALL test, but containing a mix of exec init and exec run-time pruning. create table xy_1 (x int, y int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'x' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. insert into xy_1 values(100,-10); set enable_bitmapscan = 0; set enable_indexscan = 0; @@ -2891,25 +3028,35 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1); select tableoid::regclass, * from ab; tableoid | a | b ----------+---+--- + ab_a2_b1 | 2 | 1 ab_a1_b3 | 1 | 3 ab_a1_b3 | 1 | 3 ab_a1_b3 | 1 | 3 - ab_a2_b1 | 2 | 1 (4 rows) drop table ab, lprt_a; -- Join create table tbl1(col1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'col1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. insert into tbl1 values (501), (505); analyze tbl1; -- Basic table create table tprt (col1 int) partition by range (col1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'col1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table tprt_1 partition of tprt for values from (1) to (501); +NOTICE: table has parent, setting distribution columns to match parent table create table tprt_2 partition of tprt for values from (501) to (1001); +NOTICE: table has parent, setting distribution columns to match parent table create table tprt_3 partition of tprt for values from (1001) to (2001); +NOTICE: table has parent, setting distribution columns to match parent table create table tprt_4 partition of tprt for values from (2001) to (3001); +NOTICE: table has parent, setting distribution columns to match parent table create table tprt_5 partition of tprt for values from (3001) to (4001); +NOTICE: table has parent, setting distribution columns to match parent table create table tprt_6 partition of tprt for values from (4001) to (5001); +NOTICE: table has parent, setting distribution columns to match parent table create index tprt1_idx on tprt_1 (col1); create index tprt2_idx on tprt_2 (col1); create index tprt3_idx on tprt_3 (col1); @@ -3222,9 +3369,17 @@ order by tbl1.col1, tprt.col1; drop table tbl1, tprt; -- Test with columns defined in varying orders between each level create table part_abc (a int not null, b int not null, c int not null) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table part_bac (b int not null, a int not null, c int not null) partition by list (b); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table part_cab (c int not null, a int not null, b int not null) partition by list (c); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'c' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table part_abc_p1 (a int not null, b int not null, c int not null); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- GPDB: the distribution keys must be the same in all parts of partition -- hierarchy. alter table part_bac set distributed by (a); @@ -3249,10 +3404,16 @@ drop table part_abc; -- Ensure that an Append node properly handles a sub-partitioned table -- matching without any of its leaf partitions matching the clause. create table listp (a int, b int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table listp_1 partition of listp for values in(1) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table listp_1_1 partition of listp_1 for values in(1); +NOTICE: table has parent, setting distribution columns to match parent table create table listp_2 partition of listp for values in(2) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table listp_2_1 partition of listp_2 for values in(2); +NOTICE: table has parent, setting distribution columns to match parent table select * from listp where b = 1; a | b ---+--- @@ -3344,10 +3505,13 @@ drop table listp; create table stable_qual_pruning (a timestamp) distributed randomly partition by range (a); create table stable_qual_pruning1 partition of stable_qual_pruning for values from ('2000-01-01') to ('2000-02-01'); +NOTICE: table has parent, setting distribution columns to match parent table create table stable_qual_pruning2 partition of stable_qual_pruning for values from ('2000-02-01') to ('2000-03-01'); +NOTICE: table has parent, setting distribution columns to match parent table create table stable_qual_pruning3 partition of stable_qual_pruning for values from ('3000-02-01') to ('3000-03-01'); +NOTICE: table has parent, setting distribution columns to match parent table -- comparison against a stable value requires run-time pruning explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a < localtimestamp; @@ -3356,9 +3520,9 @@ select * from stable_qual_pruning where a < localtimestamp; Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) -> Append (actual rows=0 loops=1) -> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (actual rows=0 loops=1) - Filter: (a < 'Mon Oct 14 23:09:32.667368 2024'::timestamp without time zone) + Filter: (a < 'Tue Sep 22 05:21:45.987024 2026'::timestamp without time zone) -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_2 (actual rows=0 loops=1) - Filter: (a < 'Mon Oct 14 23:09:32.667368 2024'::timestamp without time zone) + Filter: (a < 'Tue Sep 22 05:21:45.987024 2026'::timestamp without time zone) Optimizer: Postgres query optimizer (7 rows) @@ -3404,7 +3568,7 @@ select * from stable_qual_pruning ----------------------------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) -> Seq Scan on stable_qual_pruning2 stable_qual_pruning (actual rows=0 loops=1) - Filter: (a = ANY ('{"Tue Feb 01 00:00:00 2000","Mon Oct 14 23:09:32.675018 2024"}'::timestamp without time zone[])) + Filter: (a = ANY ('{"Tue Feb 01 00:00:00 2000","Tue Sep 22 05:21:45.990163 2026"}'::timestamp without time zone[])) Optimizer: Postgres query optimizer (4 rows) @@ -3455,12 +3619,17 @@ drop table stable_qual_pruning; -- non-inclusive operator for an earlier key -- create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table mc3p0 partition of mc3p for values from (0, 0, 0) to (0, maxvalue, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p1 partition of mc3p for values from (1, 1, 1) to (2, minvalue, minvalue); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p2 partition of mc3p for values from (2, minvalue, minvalue) to (3, maxvalue, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table insert into mc3p values (0, 1, 1), (1, 1, 1), (2, 1, 1); explain (analyze, costs off, summary off, timing off) select * from mc3p where a < 3 and abs(b) = 1; @@ -3521,10 +3690,16 @@ deallocate ps2; drop table mc3p; -- Ensure runtime pruning works with initplans params with boolean types create table boolvalues (value bool not null); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'value' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. insert into boolvalues values('t'),('f'); create table boolp (a bool) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table boolp_t partition of boolp for values in('t'); +NOTICE: table has parent, setting distribution columns to match parent table create table boolp_f partition of boolp for values in('f'); +NOTICE: table has parent, setting distribution columns to match parent table explain (analyze, costs off, summary off, timing off) select * from boolp where a = (select value from boolvalues where value); QUERY PLAN @@ -3568,9 +3743,14 @@ drop table boolp; set enable_seqscan = off; set enable_sort = off; create table ma_test (a int, b int) partition by range (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table ma_test_p1 partition of ma_test for values from (0) to (10); +NOTICE: table has parent, setting distribution columns to match parent table create table ma_test_p2 partition of ma_test for values from (10) to (20); +NOTICE: table has parent, setting distribution columns to match parent table create table ma_test_p3 partition of ma_test for values from (20) to (30); +NOTICE: table has parent, setting distribution columns to match parent table insert into ma_test select x,x from generate_series(0,29) t(x); create index on ma_test (b); analyze ma_test; @@ -3640,8 +3820,8 @@ deallocate mt_q1; prepare mt_q2 (int) as select * from ma_test where a >= $1 order by b limit 1; -- Ensure output list looks sane when the MergeAppend has no subplans. explain (analyze, verbose, costs off, summary off, timing off) execute mt_q2 (35); - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit (actual rows=0 loops=1) Output: ma_test.a, ma_test.b -> Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) @@ -3652,8 +3832,8 @@ explain (analyze, verbose, costs off, summary off, timing off) execute mt_q2 (35 -> Merge Append (actual rows=0 loops=1) Sort Key: ma_test.b Subplans Removed: 3 + Settings: enable_hashjoin = 'off', enable_indexonlyscan = 'off', enable_mergejoin = 'off', enable_seqscan = 'off', enable_sort = 'off', optimizer = 'off', plan_cache_mode = 'force_generic_plan' Optimizer: Postgres query optimizer - Settings: enable_hashjoin = 'off', enable_indexonlyscan = 'off', enable_mergejoin = 'off', enable_seqscan = 'off', enable_sort = 'off', plan_cache_mode = 'force_generic_plan' (12 rows) deallocate mt_q2; @@ -3692,8 +3872,12 @@ reset enable_indexonlyscan; -- -- array type list partition key create table pp_arrpart (a int[]) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table pp_arrpart1 partition of pp_arrpart for values in ('{1}'); +NOTICE: table has parent, setting distribution columns to match parent table create table pp_arrpart2 partition of pp_arrpart for values in ('{2, 3}', '{4, 5}'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from pp_arrpart where a = '{1}'; QUERY PLAN ------------------------------------------ @@ -3746,8 +3930,12 @@ explain (costs off) delete from pp_arrpart where a = '{1}'; drop table pp_arrpart; -- array type hash partition key create table pph_arrpart (a int[]) partition by hash (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table pph_arrpart1 partition of pph_arrpart for values with (modulus 2, remainder 0); +NOTICE: table has parent, setting distribution columns to match parent table create table pph_arrpart2 partition of pph_arrpart for values with (modulus 2, remainder 1); +NOTICE: table has parent, setting distribution columns to match parent table insert into pph_arrpart values ('{1}'), ('{1, 2}'), ('{4, 5}'); select tableoid::regclass, * from pph_arrpart order by 1; tableoid | a @@ -3791,11 +3979,15 @@ drop table pph_arrpart; -- enum type list partition key create type pp_colors as enum ('green', 'blue', 'black'); create table pp_enumpart (a pp_colors) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table pp_enumpart_green partition of pp_enumpart for values in ('green'); +NOTICE: table has parent, setting distribution columns to match parent table create table pp_enumpart_blue partition of pp_enumpart for values in ('blue'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from pp_enumpart where a = 'blue'; - QUERY PLAN ------------------------------------------- + QUERY PLAN +------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) -> Seq Scan on pp_enumpart_blue pp_enumpart Filter: (a = 'blue'::pp_colors) @@ -3815,8 +4007,12 @@ drop type pp_colors; -- record type as partition key create type pp_rectype as (a int, b int); create table pp_recpart (a pp_rectype) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table pp_recpart_11 partition of pp_recpart for values in ('(1,1)'); +NOTICE: table has parent, setting distribution columns to match parent table create table pp_recpart_23 partition of pp_recpart for values in ('(2,3)'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from pp_recpart where a = '(1,1)'::pp_rectype; QUERY PLAN -------------------------------------------- @@ -3838,8 +4034,12 @@ drop table pp_recpart; drop type pp_rectype; -- range type partition key create table pp_intrangepart (a int4range) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table pp_intrangepart12 partition of pp_intrangepart for values in ('[1,2]'); +NOTICE: table has parent, setting distribution columns to match parent table create table pp_intrangepart2inf partition of pp_intrangepart for values in ('[2,)'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from pp_intrangepart where a = '[1,2]'::int4range; QUERY PLAN ----------------------------------------------------- @@ -3862,8 +4062,12 @@ drop table pp_intrangepart; -- Ensure the enable_partition_prune GUC properly disables partition pruning. -- create table pp_lp (a int, value int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table pp_lp1 partition of pp_lp for values in(1); +NOTICE: table has parent, setting distribution columns to match parent table create table pp_lp2 partition of pp_lp for values in(2); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from pp_lp where a = 1; QUERY PLAN ------------------------------------------ @@ -3918,7 +4122,8 @@ explain (costs off) update pp_lp set value = 10 where a = 1; Filter: (a = 1) -> Seq Scan on pp_lp2 pp_lp_2 Filter: (a = 1) -(8 rows) + Optimizer: Postgres query optimizer +(9 rows) explain (costs off) delete from pp_lp where a = 1; QUERY PLAN @@ -3931,7 +4136,8 @@ explain (costs off) delete from pp_lp where a = 1; Filter: (a = 1) -> Seq Scan on pp_lp2 pp_lp_2 Filter: (a = 1) -(8 rows) + Optimizer: Postgres query optimizer +(9 rows) set constraint_exclusion = 'off'; -- this should not affect the result. explain (costs off) select * from pp_lp where a = 1; @@ -3957,7 +4163,8 @@ explain (costs off) update pp_lp set value = 10 where a = 1; Filter: (a = 1) -> Seq Scan on pp_lp2 pp_lp_2 Filter: (a = 1) -(8 rows) + Optimizer: Postgres query optimizer +(9 rows) explain (costs off) delete from pp_lp where a = 1; QUERY PLAN @@ -3970,15 +4177,20 @@ explain (costs off) delete from pp_lp where a = 1; Filter: (a = 1) -> Seq Scan on pp_lp2 pp_lp_2 Filter: (a = 1) -(8 rows) + Optimizer: Postgres query optimizer +(9 rows) drop table pp_lp; -- Ensure enable_partition_prune does not affect non-partitioned tables. create table inh_lp (a int, value int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table inh_lp1 (a int, value int, check(a = 1)) inherits (inh_lp); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging column "a" with inherited definition NOTICE: merging column "value" with inherited definition create table inh_lp2 (a int, value int, check(a = 2)) inherits (inh_lp); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging column "a" with inherited definition NOTICE: merging column "value" with inherited definition set constraint_exclusion = 'partition'; @@ -4007,7 +4219,8 @@ explain (costs off) update inh_lp set value = 10 where a = 1; Filter: (a = 1) -> Seq Scan on inh_lp1 inh_lp_2 Filter: (a = 1) -(9 rows) + Optimizer: Postgres query optimizer +(10 rows) explain (costs off) delete from inh_lp where a = 1; QUERY PLAN @@ -4020,7 +4233,8 @@ explain (costs off) delete from inh_lp where a = 1; Filter: (a = 1) -> Seq Scan on inh_lp1 inh_lp_2 Filter: (a = 1) -(8 rows) + Optimizer: Postgres query optimizer +(9 rows) -- Ensure we don't exclude normal relations when we only expect to exclude -- inheritance children @@ -4041,8 +4255,12 @@ reset enable_partition_pruning; reset constraint_exclusion; -- Check pruning for a partition tree containing only temporary relations create temp table pp_temp_parent (a int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table pp_temp_part_1 partition of pp_temp_parent for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create temp table pp_temp_part_def partition of pp_temp_parent default; +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from pp_temp_parent where true; QUERY PLAN ----------------------------------------------------------- @@ -4065,15 +4283,27 @@ explain (costs off) select * from pp_temp_parent where a = 2; drop table pp_temp_parent; -- Stress run-time partition pruning a bit more, per bug reports create temp table p (a int, b int, c int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table p1 partition of p for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create temp table p2 partition of p for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table create temp table q (a int, b int, c int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table q1 partition of q for values in (1) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create temp table q11 partition of q1 for values in (1) partition by list (c); +NOTICE: table has parent, setting distribution columns to match parent table create temp table q111 partition of q11 for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create temp table q2 partition of q for values in (2) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create temp table q21 partition of q2 for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create temp table q22 partition of q2 for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table insert into q22 values (2, 2, 3); -- GPDB: This is the query that needs the "matchsubs" rule at the top of the file -- The constant third branch of the UNION is executed at random segment. If the @@ -4092,7 +4322,7 @@ from ( where s.a = 1 and s.b = 1 and s.c = (select 1); QUERY PLAN ------------------------------------------------------------- - Gather Motion 1:1 (slice1; segments: 1) + Gather Motion 2:1 (slice1; segments: 2) InitPlan 1 (returns $0) (slice2) -> Result -> Append @@ -4101,7 +4331,7 @@ where s.a = 1 and s.b = 1 and s.c = (select 1); -> Seq Scan on q111 q1 Filter: ((a = 1) AND (b = 1) AND (c = $0)) -> Result - One-Time Filter: (gp_execution_segment() = 1) + One-Time Filter: (gp_execution_segment() = 2) -> Result One-Time Filter: (1 = $0) Optimizer: Postgres query optimizer @@ -4144,7 +4374,7 @@ explain (costs off) execute q (1, 1); -> Seq Scan on q111 q1 Filter: ((a = $1) AND (b = $2) AND (c = $0)) -> Result - One-Time Filter: (gp_execution_segment() = 1) + One-Time Filter: (gp_execution_segment() = 2) -> Result One-Time Filter: ((1 = $1) AND (1 = $2) AND (1 = $0)) Optimizer: Postgres query optimizer @@ -4160,9 +4390,14 @@ drop table p, q; -- Ensure run-time pruning works correctly when we match a partitioned table -- on the first level but find no matching partitions on the second level. create table listp (a int, b int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table listp1 partition of listp for values in(1); +NOTICE: table has parent, setting distribution columns to match parent table create table listp2 partition of listp for values in(2) partition by list(b); +NOTICE: table has parent, setting distribution columns to match parent table create table listp2_10 partition of listp2 for values in (10); +NOTICE: table has parent, setting distribution columns to match parent table explain (analyze, costs off, summary off, timing off) select * from listp where a = (select 2) and b <> 10; QUERY PLAN @@ -4229,9 +4464,14 @@ drop table listp; set parallel_setup_cost to 0; set parallel_tuple_cost to 0; create table listp (a int) partition by list(a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table listp_12 partition of listp for values in(1,2) partition by list(a); +NOTICE: table has parent, setting distribution columns to match parent table create table listp_12_1 partition of listp_12 for values in(1); +NOTICE: table has parent, setting distribution columns to match parent table create table listp_12_2 partition of listp_12 for values in(2); +NOTICE: table has parent, setting distribution columns to match parent table -- 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. @@ -4286,13 +4526,20 @@ 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); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rangep_0_to_100 partition of rangep for values from (0) to (100) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table -- 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); +NOTICE: table has parent, setting distribution columns to match parent table create table rangep_0_to_100_2 partition of rangep_0_to_100 for values in(2); +NOTICE: table has parent, setting distribution columns to match parent table create table rangep_0_to_100_3 partition of rangep_0_to_100 for values in(3); +NOTICE: table has parent, setting distribution columns to match parent table create table rangep_100_to_200 partition of rangep for values from (100) to (200); +NOTICE: table has parent, setting distribution columns to match parent table create index on rangep (a); -- Ensure run-time pruning works on the nested Merge Append explain (analyze on, costs off, timing off, summary off) @@ -4326,8 +4573,12 @@ drop table rangep; -- clauses for different partition keys -- create table rp_prefix_test1 (a int, b varchar) partition by range(a, b); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rp_prefix_test1_p1 partition of rp_prefix_test1 for values from (1, 'a') to (1, 'b'); +NOTICE: table has parent, setting distribution columns to match parent table create table rp_prefix_test1_p2 partition of rp_prefix_test1 for values from (2, 'a') to (2, 'b'); +NOTICE: table has parent, setting distribution columns to match parent table -- Don't call get_steps_using_prefix() with the last partition key b plus -- an empty prefix explain (costs off) select * from rp_prefix_test1 where a <= 1 and b = 'a'; @@ -4340,8 +4591,12 @@ explain (costs off) select * from rp_prefix_test1 where a <= 1 and b = 'a'; (4 rows) create table rp_prefix_test2 (a int, b int, c int) partition by range(a, b, c); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rp_prefix_test2_p1 partition of rp_prefix_test2 for values from (1, 1, 0) to (1, 1, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table rp_prefix_test2_p2 partition of rp_prefix_test2 for values from (2, 2, 0) to (2, 2, 10); +NOTICE: table has parent, setting distribution columns to match parent table -- Don't call get_steps_using_prefix() with the last partition key c plus -- an invalid prefix (ie, b = 1) explain (costs off) select * from rp_prefix_test2 where a <= 1 and b = 1 and c >= 0; @@ -4354,8 +4609,12 @@ explain (costs off) select * from rp_prefix_test2 where a <= 1 and b = 1 and c > (4 rows) create table rp_prefix_test3 (a int, b int, c int, d int) partition by range(a, b, c, d); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rp_prefix_test3_p1 partition of rp_prefix_test3 for values from (1, 1, 1, 0) to (1, 1, 1, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table rp_prefix_test3_p2 partition of rp_prefix_test3 for values from (2, 2, 2, 0) to (2, 2, 2, 10); +NOTICE: table has parent, setting distribution columns to match parent table -- Test that get_steps_using_prefix() handles a prefix that contains multiple -- clauses for the partition key b (ie, b >= 1 and b >= 2) explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b >= 2 and c >= 2 and d >= 0; @@ -4387,6 +4646,8 @@ drop table rp_prefix_test3; -- create table hp_prefix_test (a int, b int, c int, d int) partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- create 8 partitions select 'create table hp_prefix_test_p' || x::text || ' partition of hp_prefix_test for values with (modulus 8, remainder ' || x::text || ');' from generate_Series(0,7) x; @@ -4404,13 +4665,21 @@ from generate_Series(0,7) x; \gexec create table hp_prefix_test_p0 partition of hp_prefix_test for values with (modulus 8, remainder 0); +NOTICE: table has parent, setting distribution columns to match parent table create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 8, remainder 1); +NOTICE: table has parent, setting distribution columns to match parent table create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 8, remainder 2); +NOTICE: table has parent, setting distribution columns to match parent table create table hp_prefix_test_p3 partition of hp_prefix_test for values with (modulus 8, remainder 3); +NOTICE: table has parent, setting distribution columns to match parent table create table hp_prefix_test_p4 partition of hp_prefix_test for values with (modulus 8, remainder 4); +NOTICE: table has parent, setting distribution columns to match parent table create table hp_prefix_test_p5 partition of hp_prefix_test for values with (modulus 8, remainder 5); +NOTICE: table has parent, setting distribution columns to match parent table create table hp_prefix_test_p6 partition of hp_prefix_test for values with (modulus 8, remainder 6); +NOTICE: table has parent, setting distribution columns to match parent table create table hp_prefix_test_p7 partition of hp_prefix_test for values with (modulus 8, remainder 7); +NOTICE: table has parent, setting distribution columns to match parent table -- insert 16 rows, one row for each test to perform. insert into hp_prefix_test select @@ -4453,68 +4722,100 @@ order by g.s; \gexec explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null - Seq Scan on hp_prefix_test_p0 hp_prefix_test - Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d IS NULL)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p0 hp_prefix_test + Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d IS NULL)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null - Seq Scan on hp_prefix_test_p1 hp_prefix_test - Filter: ((b IS NULL) AND (c IS NULL) AND (d IS NULL) AND (a = 1)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p1 hp_prefix_test + Filter: ((b IS NULL) AND (c IS NULL) AND (d IS NULL) AND (a = 1)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null - Seq Scan on hp_prefix_test_p2 hp_prefix_test - Filter: ((a IS NULL) AND (c IS NULL) AND (d IS NULL) AND (b = 2)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p2 hp_prefix_test + Filter: ((a IS NULL) AND (c IS NULL) AND (d IS NULL) AND (b = 2)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null - Seq Scan on hp_prefix_test_p4 hp_prefix_test - Filter: ((c IS NULL) AND (d IS NULL) AND (a = 1) AND (b = 2)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((c IS NULL) AND (d IS NULL) AND (a = 1) AND (b = 2)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null - Seq Scan on hp_prefix_test_p3 hp_prefix_test - Filter: ((a IS NULL) AND (b IS NULL) AND (d IS NULL) AND (c = 3)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p3 hp_prefix_test + Filter: ((a IS NULL) AND (b IS NULL) AND (d IS NULL) AND (c = 3)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null - Seq Scan on hp_prefix_test_p7 hp_prefix_test - Filter: ((b IS NULL) AND (d IS NULL) AND (a = 1) AND (c = 3)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p7 hp_prefix_test + Filter: ((b IS NULL) AND (d IS NULL) AND (a = 1) AND (c = 3)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null - Seq Scan on hp_prefix_test_p4 hp_prefix_test - Filter: ((a IS NULL) AND (d IS NULL) AND (b = 2) AND (c = 3)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((a IS NULL) AND (d IS NULL) AND (b = 2) AND (c = 3)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null - Seq Scan on hp_prefix_test_p5 hp_prefix_test - Filter: ((d IS NULL) AND (a = 1) AND (b = 2) AND (c = 3)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p5 hp_prefix_test + Filter: ((d IS NULL) AND (a = 1) AND (b = 2) AND (c = 3)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 - Seq Scan on hp_prefix_test_p4 hp_prefix_test - Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d = 4)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d = 4)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 - Seq Scan on hp_prefix_test_p6 hp_prefix_test - Filter: ((b IS NULL) AND (c IS NULL) AND (a = 1) AND (d = 4)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p6 hp_prefix_test + Filter: ((b IS NULL) AND (c IS NULL) AND (a = 1) AND (d = 4)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 - Seq Scan on hp_prefix_test_p5 hp_prefix_test - Filter: ((a IS NULL) AND (c IS NULL) AND (b = 2) AND (d = 4)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p5 hp_prefix_test + Filter: ((a IS NULL) AND (c IS NULL) AND (b = 2) AND (d = 4)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 - Seq Scan on hp_prefix_test_p6 hp_prefix_test - Filter: ((c IS NULL) AND (a = 1) AND (b = 2) AND (d = 4)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p6 hp_prefix_test + Filter: ((c IS NULL) AND (a = 1) AND (b = 2) AND (d = 4)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 - Seq Scan on hp_prefix_test_p4 hp_prefix_test - Filter: ((a IS NULL) AND (b IS NULL) AND (c = 3) AND (d = 4)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((a IS NULL) AND (b IS NULL) AND (c = 3) AND (d = 4)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 - Seq Scan on hp_prefix_test_p5 hp_prefix_test - Filter: ((b IS NULL) AND (a = 1) AND (c = 3) AND (d = 4)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p5 hp_prefix_test + Filter: ((b IS NULL) AND (a = 1) AND (c = 3) AND (d = 4)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 - Seq Scan on hp_prefix_test_p6 hp_prefix_test - Filter: ((a IS NULL) AND (b = 2) AND (c = 3) AND (d = 4)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p6 hp_prefix_test + Filter: ((a IS NULL) AND (b = 2) AND (c = 3) AND (d = 4)) + Optimizer: Postgres query optimizer explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 - Seq Scan on hp_prefix_test_p4 hp_prefix_test - Filter: ((a = 1) AND (b = 2) AND (c = 3) AND (d = 4)) + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((a = 1) AND (b = 2) AND (c = 3) AND (d = 4)) + Optimizer: Postgres query optimizer -- And ensure we get exactly 1 row from each. Again, all 16 possible combinations. select @@ -4609,21 +4910,27 @@ using hash as operator 1 ===, function 2 part_hashint4_noop(int4, int8); create table hp_contradict_test (a int, b int) partition by hash (a part_test_int4_ops2, b part_test_int4_ops2); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table hp_contradict_test_p1 partition of hp_contradict_test for values with (modulus 2, remainder 0); +NOTICE: table has parent, setting distribution columns to match parent table create table hp_contradict_test_p2 partition of hp_contradict_test for values with (modulus 2, remainder 1); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from hp_contradict_test where a is null and a === 1 and b === 1; - QUERY PLAN --------------------------- + QUERY PLAN +------------------------------------- Result One-Time Filter: false -(2 rows) + Optimizer: Postgres query optimizer +(3 rows) explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 and a is null; - QUERY PLAN --------------------------- + QUERY PLAN +------------------------------------- Result One-Time Filter: false -(2 rows) + Optimizer: Postgres query optimizer +(3 rows) drop table hp_contradict_test; drop operator class part_test_int4_ops2 using hash; diff --git a/src/test/regress/expected/partition_prune_optimizer.out b/src/test/regress/expected/partition_prune_optimizer.out index 72e87807c8a..47dce8020b9 100644 --- a/src/test/regress/expected/partition_prune_optimizer.out +++ b/src/test/regress/expected/partition_prune_optimizer.out @@ -27,139 +27,176 @@ -- Force generic plans to be used for all prepared statements in this file. set plan_cache_mode = force_generic_plan; create table lp (a char) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table lp_default partition of lp default; +NOTICE: table has parent, setting distribution columns to match parent table create table lp_ef partition of lp for values in ('e', 'f'); +NOTICE: table has parent, setting distribution columns to match parent table create table lp_ad partition of lp for values in ('a', 'd'); +NOTICE: table has parent, setting distribution columns to match parent table create table lp_bc partition of lp for values in ('b', 'c'); +NOTICE: table has parent, setting distribution columns to match parent table create table lp_g partition of lp for values in ('g'); +NOTICE: table has parent, setting distribution columns to match parent table create table lp_null partition of lp for values in (null); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from lp; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on lp Number of partitions to scan: 6 (out of 6) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (4 rows) explain (costs off) select * from lp where a > 'a' and a < 'd'; +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ----------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on lp Number of partitions to scan: 2 (out of 6) Filter: ((a > 'a'::bpchar) AND (a < 'd'::bpchar)) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from lp where a > 'a' and a <= 'd'; +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on lp Number of partitions to scan: 3 (out of 6) Filter: ((a > 'a'::bpchar) AND (a <= 'd'::bpchar)) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from lp where a = 'a'; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on lp Number of partitions to scan: 1 (out of 6) Filter: (a = 'a'::bpchar) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from lp where 'a' = a; /* commuted */ - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on lp Number of partitions to scan: 1 (out of 6) Filter: (a = 'a'::bpchar) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from lp where a is not null; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on lp Number of partitions to scan: 5 (out of 6) Filter: (NOT (a IS NULL)) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from lp where a is null; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on lp Number of partitions to scan: 1 (out of 6) Filter: (a IS NULL) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from lp where a = 'a' or a = 'c'; +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ---------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on lp Number of partitions to scan: 2 (out of 6) Filter: ((a = 'a'::bpchar) OR (a = 'c'::bpchar)) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from lp where a is not null and (a = 'a' or a = 'c'); +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ---------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on lp Number of partitions to scan: 2 (out of 6) Filter: ((NOT (a IS NULL)) AND ((a = 'a'::bpchar) OR (a = 'c'::bpchar))) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from lp where a <> 'g'; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on lp Number of partitions to scan: 4 (out of 6) Filter: (a <> 'g'::bpchar) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from lp where a <> 'a' and a <> 'd'; +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on lp Number of partitions to scan: 4 (out of 6) Filter: ((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from lp where a not in ('a', 'd'); - QUERY PLAN ------------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on lp Number of partitions to scan: 4 (out of 6) Filter: (a <> ALL ('{a,d}'::bpchar[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) -- collation matches the partitioning collation, pruning works create table coll_pruning (a text collate "C") partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table coll_pruning_a partition of coll_pruning for values in ('a'); +NOTICE: table has parent, setting distribution columns to match parent table create table coll_pruning_b partition of coll_pruning for values in ('b'); +NOTICE: table has parent, setting distribution columns to match parent table create table coll_pruning_def partition of coll_pruning default; +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from coll_pruning where a collate "C" = 'a' collate "C"; - QUERY PLAN ---------------------------------------------- + QUERY PLAN +----------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Seq Scan on coll_pruning_a coll_pruning Filter: (a = 'a'::text COLLATE "C") @@ -182,28 +219,50 @@ explain (costs off) select * from coll_pruning where a collate "POSIX" = 'a' col (9 rows) create table rlp (a int, b varchar) partition by range (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rlp_default partition of rlp default partition by list (a); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp_default_default partition of rlp_default default; +NOTICE: table has parent, setting distribution columns to match parent table create table rlp_default_10 partition of rlp_default for values in (10); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp_default_30 partition of rlp_default for values in (30); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp_default_null partition of rlp_default for values in (null); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp1 partition of rlp for values from (minvalue) to (1); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp2 partition of rlp for values from (1) to (10); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp3 (b varchar, a int) partition by list (b varchar_ops); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- GPDB: distribution policy must match the parent table. alter table rlp3 set distributed by (a); create table rlp3_default partition of rlp3 default; +NOTICE: table has parent, setting distribution columns to match parent table create table rlp3abcd partition of rlp3 for values in ('ab', 'cd'); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp3efgh partition of rlp3 for values in ('ef', 'gh'); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp3nullxy partition of rlp3 for values in (null, 'xy'); +NOTICE: table has parent, setting distribution columns to match parent table alter table rlp attach partition rlp3 for values from (15) to (20); create table rlp4 partition of rlp for values from (20) to (30) partition by range (a); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp4_default partition of rlp4 default; +NOTICE: table has parent, setting distribution columns to match parent table create table rlp4_1 partition of rlp4 for values from (20) to (25); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp4_2 partition of rlp4 for values from (25) to (29); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp5 partition of rlp for values from (31) to (maxvalue) partition by range (a); +NOTICE: table has parent, setting distribution columns to match parent table create table rlp5_default partition of rlp5 default; +NOTICE: table has parent, setting distribution columns to match parent table create table rlp5_1 partition of rlp5 for values from (31) to (40); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from rlp where a < 1; QUERY PLAN ------------------------------------------ @@ -679,11 +738,11 @@ explain (costs off) select * from rlp where a = 20 or a = 40; (7 rows) explain (costs off) select * from rlp3 where a = 20; /* empty */ - QUERY PLAN ---------------------------------------- + QUERY PLAN +-------------------------- Result One-Time Filter: false - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (3 rows) -- redundant clauses are eliminated @@ -754,15 +813,26 @@ explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = -- multi-column keys create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table mc3p_default partition of mc3p default; +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p0 partition of mc3p for values from (minvalue, minvalue, minvalue) to (1, 1, 1); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p1 partition of mc3p for values from (1, 1, 1) to (10, 5, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p2 partition of mc3p for values from (10, 5, 10) to (10, 10, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p3 partition of mc3p for values from (10, 10, 10) to (10, 10, 20); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p4 partition of mc3p for values from (10, 10, 20) to (10, maxvalue, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p5 partition of mc3p for values from (11, 1, 1) to (20, 10, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p6 partition of mc3p for values from (20, 10, 10) to (20, 20, 20); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p7 partition of mc3p for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from mc3p where a = 1; QUERY PLAN --------------------------------------------- @@ -1065,13 +1135,22 @@ explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 a -- a simpler multi-column keys case create table mc2p (a int, b int) partition by range (a, b); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table mc2p_default partition of mc2p default; +NOTICE: table has parent, setting distribution columns to match parent table create table mc2p0 partition of mc2p for values from (minvalue, minvalue) to (1, minvalue); +NOTICE: table has parent, setting distribution columns to match parent table create table mc2p1 partition of mc2p for values from (1, minvalue) to (1, 1); +NOTICE: table has parent, setting distribution columns to match parent table create table mc2p2 partition of mc2p for values from (1, 1) to (2, minvalue); +NOTICE: table has parent, setting distribution columns to match parent table create table mc2p3 partition of mc2p for values from (2, minvalue) to (2, 1); +NOTICE: table has parent, setting distribution columns to match parent table create table mc2p4 partition of mc2p for values from (2, 1) to (2, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table create table mc2p5 partition of mc2p for values from (2, maxvalue) to (maxvalue, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from mc2p where a < 2; QUERY PLAN --------------------------------------------- @@ -1172,87 +1251,108 @@ explain (costs off) select * from mc2p where b is null; -- boolean partitioning create table boolpart (a bool) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table boolpart_default partition of boolpart default; +NOTICE: table has parent, setting distribution columns to match parent table create table boolpart_t partition of boolpart for values in ('true'); +NOTICE: table has parent, setting distribution columns to match parent table create table boolpart_f partition of boolpart for values in ('false'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from boolpart where a in (true, false); - QUERY PLAN ------------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: boolpart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on boolpart Number of partitions to scan: 2 (out of 3) Filter: (a = ANY ('{t,f}'::boolean[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from boolpart where a = false; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: boolpart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on boolpart Number of partitions to scan: 1 (out of 3) Filter: (NOT a) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from boolpart where not a = false; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: boolpart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on boolpart Number of partitions to scan: 1 (out of 3) Filter: a - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from boolpart where a is true or a is not true; - QUERY PLAN --------------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: boolpart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on boolpart Number of partitions to scan: 3 (out of 3) Filter: ((a IS TRUE) OR (a IS NOT TRUE)) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from boolpart where a is not true; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: boolpart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on boolpart Number of partitions to scan: 2 (out of 3) Filter: (a IS NOT TRUE) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from boolpart where a is not true and a is not false; - QUERY PLAN ---------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: boolpart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +-------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on boolpart Number of partitions to scan: 1 (out of 3) Filter: ((a IS NOT TRUE) AND (a IS NOT FALSE)) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from boolpart where a is unknown; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: boolpart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on boolpart Number of partitions to scan: 1 (out of 3) Filter: (a IS UNKNOWN) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from boolpart where a is not unknown; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: boolpart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on boolpart Number of partitions to scan: 2 (out of 3) Filter: (a IS NOT UNKNOWN) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) create table boolrangep (a bool, b bool, c int) partition by range (a,b,c); @@ -1278,135 +1378,171 @@ explain (costs off) select * from boolrangep where not a and not b and c = 25; -- test scalar-to-array operators create table coercepart (a varchar) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table coercepart_ab partition of coercepart for values in ('ab'); +NOTICE: table has parent, setting distribution columns to match parent table create table coercepart_bc partition of coercepart for values in ('bc'); +NOTICE: table has parent, setting distribution columns to match parent table create table coercepart_cd partition of coercepart for values in ('cd'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from coercepart where a in ('ab', to_char(125, '999')); - QUERY PLAN +NOTICE: One or more columns in the following table(s) do not have statistics: coercepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN ----------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on coercepart Number of partitions to scan: 1 (out of 3) Filter: ((a)::text = ANY ('{ab," 125"}'::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from coercepart where a ~ any ('{ab}'); - QUERY PLAN +NOTICE: One or more columns in the following table(s) do not have statistics: coercepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN ---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on coercepart Number of partitions to scan: 3 (out of 3) Filter: ((a)::text ~ ANY ('{ab}'::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from coercepart where a !~ all ('{ab}'); - QUERY PLAN +NOTICE: One or more columns in the following table(s) do not have statistics: coercepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN ----------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on coercepart Number of partitions to scan: 3 (out of 3) Filter: ((a)::text !~ ALL ('{ab}'::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from coercepart where a ~ any ('{ab,bc}'); - QUERY PLAN +NOTICE: One or more columns in the following table(s) do not have statistics: coercepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN ------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on coercepart Number of partitions to scan: 3 (out of 3) Filter: ((a)::text ~ ANY ('{ab,bc}'::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from coercepart where a !~ all ('{ab,bc}'); - QUERY PLAN +NOTICE: One or more columns in the following table(s) do not have statistics: coercepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN -------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on coercepart Number of partitions to scan: 3 (out of 3) Filter: ((a)::text !~ ALL ('{ab,bc}'::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from coercepart where a = any ('{ab,bc}'); +NOTICE: One or more columns in the following table(s) do not have statistics: coercepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on coercepart Number of partitions to scan: 2 (out of 3) Filter: ((a)::text = ANY ('{ab,bc}'::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from coercepart where a = any ('{ab,null}'); +NOTICE: One or more columns in the following table(s) do not have statistics: coercepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN --------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on coercepart Number of partitions to scan: 1 (out of 3) Filter: ((a)::text = ANY ('{ab,NULL}'::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from coercepart where a = any (null::text[]); - QUERY PLAN --------------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: coercepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on coercepart Number of partitions to scan: 3 (out of 3) Filter: ((a)::text = ANY (NULL::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from coercepart where a = all ('{ab}'); +NOTICE: One or more columns in the following table(s) do not have statistics: coercepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on coercepart Number of partitions to scan: 1 (out of 3) Filter: ((a)::text = ALL ('{ab}'::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from coercepart where a = all ('{ab,bc}'); +NOTICE: One or more columns in the following table(s) do not have statistics: coercepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on coercepart Number of partitions to scan: 2 (out of 3) Filter: ((a)::text = ALL ('{ab,bc}'::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from coercepart where a = all ('{ab,null}'); +NOTICE: One or more columns in the following table(s) do not have statistics: coercepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN --------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on coercepart Number of partitions to scan: 1 (out of 3) Filter: ((a)::text = ALL ('{ab,NULL}'::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from coercepart where a = all (null::text[]); - QUERY PLAN --------------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: coercepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on coercepart Number of partitions to scan: 3 (out of 3) Filter: ((a)::text = ALL (NULL::text[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) drop table coercepart; CREATE TABLE part (a INT, b INT) PARTITION BY LIST (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE TABLE part_p1 PARTITION OF part FOR VALUES IN (-2,-1,0,1,2); +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE part_p2 PARTITION OF part DEFAULT PARTITION BY RANGE(a); +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE part_p2_p1 PARTITION OF part_p2 DEFAULT; +NOTICE: table has parent, setting distribution columns to match parent table CREATE TABLE part_rev (b INT, c INT, a INT); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- The distribution key must be the same for partition tables. ALTER TABLE part_rev SET DISTRIBUTED BY(a); ALTER TABLE part ATTACH PARTITION part_rev FOR VALUES IN (3); -- fail @@ -1551,57 +1687,70 @@ explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 -- -- doesn't prune range partitions create table rp (a int) partition by range (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rp0 partition of rp for values from (minvalue) to (1); +NOTICE: table has parent, setting distribution columns to match parent table create table rp1 partition of rp for values from (1) to (2); +NOTICE: table has parent, setting distribution columns to match parent table create table rp2 partition of rp for values from (2) to (maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from rp where a <> 1; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: rp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on rp Number of partitions to scan: 3 (out of 3) Filter: (a <> 1) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from rp where a <> 1 and a <> 2; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: rp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on rp Number of partitions to scan: 3 (out of 3) Filter: ((a <> 1) AND (a <> 2)) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) -- null partition should be eliminated due to strict <> clause. explain (costs off) select * from lp where a <> 'a'; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on lp Number of partitions to scan: 5 (out of 6) Filter: (a <> 'a'::bpchar) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) -- ensure we detect contradictions in clauses; a can't be NULL and NOT NULL. explain (costs off) select * from lp where a <> 'a' and a is null; - QUERY PLAN ---------------------------------------- + QUERY PLAN +-------------------------- Result One-Time Filter: false - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (3 rows) explain (costs off) select * from lp where (a <> 'a' and a <> 'd') or a is null; - QUERY PLAN +NOTICE: One or more columns in the following table(s) do not have statistics: lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN ------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on lp Number of partitions to scan: 5 (out of 6) Filter: (((a <> 'a'::bpchar) AND (a <> 'd'::bpchar)) OR (a IS NULL)) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) -- check that it also works for a partitioned table that's not root, @@ -1623,9 +1772,14 @@ explain (costs off) select * from rlp where a = 15 and b <> 'ab' and b <> 'cd' a -- different collations for different keys with same expression -- create table coll_pruning_multi (a text) partition by range (substr(a, 1) collate "POSIX", substr(a, 1) collate "C"); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table coll_pruning_multi1 partition of coll_pruning_multi for values from ('a', 'a') to ('a', 'e'); +NOTICE: table has parent, setting distribution columns to match parent table create table coll_pruning_multi2 partition of coll_pruning_multi for values from ('a', 'e') to ('a', 'z'); +NOTICE: table has parent, setting distribution columns to match parent table create table coll_pruning_multi3 partition of coll_pruning_multi for values from ('b', 'a') to ('b', 'e'); +NOTICE: table has parent, setting distribution columns to match parent table -- no pruning, because no value for the leading key explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e' collate "C"; QUERY PLAN @@ -1668,54 +1822,71 @@ explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e' co -- LIKE operators don't prune -- create table like_op_noprune (a text) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table like_op_noprune1 partition of like_op_noprune for values in ('ABC'); +NOTICE: table has parent, setting distribution columns to match parent table create table like_op_noprune2 partition of like_op_noprune for values in ('BCD'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from like_op_noprune where a like '%BC'; - QUERY PLAN -------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: like_op_noprune +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on like_op_noprune Number of partitions to scan: 2 (out of 2) Filter: (a ~~ '%BC'::text) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) -- -- tests wherein clause value requires a cross-type comparison function -- create table lparted_by_int2 (a smallint) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table lparted_by_int2_1 partition of lparted_by_int2 for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table lparted_by_int2_16384 partition of lparted_by_int2 for values in (16384); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from lparted_by_int2 where a = 100000000000000; - QUERY PLAN ---------------------------------------- + QUERY PLAN +-------------------------- Result One-Time Filter: false - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (3 rows) create table rparted_by_int2 (a smallint) partition by range (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rparted_by_int2_1 partition of rparted_by_int2 for values from (1) to (10); +NOTICE: table has parent, setting distribution columns to match parent table create table rparted_by_int2_16384 partition of rparted_by_int2 for values from (10) to (16384); +NOTICE: table has parent, setting distribution columns to match parent table -- all partitions pruned explain (costs off) select * from rparted_by_int2 where a > 100000000000000; - QUERY PLAN -------------------------------------- + QUERY PLAN +-------------------------- Result One-Time Filter: false - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (3 rows) create table rparted_by_int2_maxvalue partition of rparted_by_int2 for values from (16384) to (maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table -- all partitions but rparted_by_int2_maxvalue pruned explain (costs off) select * from rparted_by_int2 where a > 100000000000000; - QUERY PLAN -------------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: rparted_by_int2 +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on rparted_by_int2 Number of partitions to scan: 1 (out of 3) Filter: (a > '100000000000000'::bigint) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, boolrangep, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2; @@ -1728,10 +1899,16 @@ drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, boolrangep, rp, coll_pru -- create table hp (a int, b text, c int) partition by hash (a part_test_int4_ops, b part_test_text_ops); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table hp0 partition of hp for values with (modulus 4, remainder 0); +NOTICE: table has parent, setting distribution columns to match parent table create table hp3 partition of hp for values with (modulus 4, remainder 3); +NOTICE: table has parent, setting distribution columns to match parent table create table hp1 partition of hp for values with (modulus 4, remainder 1); +NOTICE: table has parent, setting distribution columns to match parent table create table hp2 partition of hp for values with (modulus 4, remainder 2); +NOTICE: table has parent, setting distribution columns to match parent table insert into hp values (null, null, 0); insert into hp values (1, null, 1); insert into hp values (1, 'xxx', 2); @@ -1957,29 +2134,43 @@ explain (costs off) select * from hp where a = 1 and b = 'abcde' and drop table hp2; explain (costs off) select * from hp where a = 1 and b = 'abcde' and (c = 2 or c = 3); - QUERY PLAN --------------------------- + QUERY PLAN +------------------------------------- Result One-Time Filter: false -(2 rows) + Optimizer: Postgres query optimizer +(3 rows) -drop table hp; -- -- Test runtime partition pruning -- create table ab (a int not null, b int not null) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table ab_a2 partition of ab for values in(2) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a2_b1 partition of ab_a2 for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a2_b2 partition of ab_a2 for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a2_b3 partition of ab_a2 for values in (3); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a1 partition of ab for values in(1) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a1_b1 partition of ab_a1 for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a1_b2 partition of ab_a1 for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a1_b3 partition of ab_a1 for values in (3); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a3 partition of ab for values in(3) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a3_b1 partition of ab_a3 for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a3_b2 partition of ab_a3 for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table create table ab_a3_b3 partition of ab_a3 for values in (3); +NOTICE: table has parent, setting distribution columns to match parent table -- Disallow index only scans as concurrent transactions may stop visibility -- bits being set causing "Heap Fetches" to be unstable in the EXPLAIN ANALYZE -- output. @@ -2097,12 +2288,44 @@ explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 2); Optimizer: Postgres query optimizer (12 rows) +-- +-- Test runtime pruning with hash partitioned tables +-- +-- recreate partitions dropped above +create table hp1 partition of hp for values with (modulus 4, remainder 1); +NOTICE: table has parent, setting distribution columns to match parent table +create table hp2 partition of hp for values with (modulus 4, remainder 2); +NOTICE: table has parent, setting distribution columns to match parent table +create table hp3 partition of hp for values with (modulus 4, remainder 3); +NOTICE: table has parent, setting distribution columns to match parent table +-- Ensure we correctly prune unneeded partitions when there is an IS NULL qual +prepare hp_q1 (text) as +select * from hp where a is null and b = $1; +explain (costs off) execute hp_q1('xxx'); + QUERY PLAN +-------------------------------------------------- + Gather Motion 1:1 (slice1; segments: 1) + -> Append + Subplans Removed: 3 + -> Seq Scan on hp2 hp_1 + Filter: ((a IS NULL) AND (b = $1)) + Optimizer: Postgres query optimizer +(6 rows) + +deallocate hp_q1; +drop table hp; -- Test a backwards Append scan create table list_part (a int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table list_part1 partition of list_part for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create table list_part2 partition of list_part for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table create table list_part3 partition of list_part for values in (3); +NOTICE: table has parent, setting distribution columns to match parent table create table list_part4 partition of list_part for values in (4); +NOTICE: table has parent, setting distribution columns to match parent table insert into list_part select generate_series(1,4); begin; -- Don't select an actual value out of the table as the order of the Append's @@ -2119,6 +2342,8 @@ begin; create function list_part_fn(int) returns int as $$ begin return $1; end;$$ language plpgsql stable; -- Ensure pruning works using a stable function containing no Vars explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1); +NOTICE: One or more columns in the following table(s) do not have statistics: list_part +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) (actual rows=1 loops=1) @@ -2126,11 +2351,13 @@ explain (analyze, costs off, summary off, timing off) select * from list_part wh Number of partitions to scan: 1 (out of 4) Filter: (a = 1) Partitions scanned: 1 . - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (6 rows) -- Ensure pruning does not take place when the function has a Var parameter explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(a); +NOTICE: One or more columns in the following table(s) do not have statistics: list_part +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (actual rows=4 loops=1) @@ -2138,11 +2365,13 @@ explain (analyze, costs off, summary off, timing off) select * from list_part wh Number of partitions to scan: 4 (out of 4) Filter: (a = list_part_fn(a)) Partitions scanned: Avg 4.0 x 3 workers. Max 4 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (6 rows) -- Ensure pruning does not take place when the expression contains a Var. explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1) + a; +NOTICE: One or more columns in the following table(s) do not have statistics: list_part +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) @@ -2150,7 +2379,7 @@ explain (analyze, costs off, summary off, timing off) select * from list_part wh Number of partitions to scan: 4 (out of 4) Filter: (a = (1 + a)) Partitions scanned: Avg 4.0 x 3 workers. Max 4 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (6 rows) rollback; @@ -2285,6 +2514,8 @@ select explain_parallel_append('select count(*) from ab where (a = (select 1) or -- Test pruning during parallel nested loop query create table lprt_a (a int not null); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- Insert some values we won't find in ab insert into lprt_a select 0 from generate_series(1,100); -- and insert some values that we should find. @@ -2329,7 +2560,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on Index Cond: (a = ANY ('{0,0,1}'::integer[])) -> Sort (actual rows=N loops=N) Sort Key: a.a - Sort Method: quicksort Memory: 60kB + Sort Method: quicksort Memory: 29kB -> Partition Selector (selector id: $0) (actual rows=N loops=N) -> Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{0,0,1}'::integer[])) @@ -2411,7 +2642,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on Index Cond: (a = ANY ('{1,0,3}'::integer[])) -> Sort (actual rows=N loops=N) Sort Key: a.a - Sort Method: quicksort Memory: 110kB + Sort Method: quicksort Memory: 54kB -> Partition Selector (selector id: $0) (actual rows=N loops=N) -> Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{1,0,3}'::integer[])) @@ -2445,7 +2676,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on Index Cond: (a = ANY ('{1,0,0}'::integer[])) -> Sort (actual rows=N loops=N) Sort Key: a.a - Sort Method: quicksort Memory: 60kB + Sort Method: quicksort Memory: 29kB -> Partition Selector (selector id: $0) (actual rows=N loops=N) -> Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{1,0,0}'::integer[])) @@ -2480,7 +2711,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on Index Cond: (a = ANY ('{1,0,0}'::integer[])) -> Sort (actual rows=N loops=N) Sort Key: a.a - Sort Method: quicksort Memory: 60kB + Sort Method: quicksort Memory: 29kB -> Partition Selector (selector id: $0) (actual rows=N loops=N) -> Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{1,0,0}'::integer[])) @@ -2562,8 +2793,8 @@ select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1 -- Test run-time partition pruning with UNION ALL parents explain (analyze, costs off, summary off, timing off) select * from (select * from ab where a = 1 union all select * from ab) ab where b = (select 1); - QUERY PLAN ------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) InitPlan 1 (returns $0) (slice2) -> Result (actual rows=1 loops=1) @@ -2608,8 +2839,8 @@ select * from (select * from ab where a = 1 union all select * from ab) ab where -- A case containing a UNION ALL with a non-partitioned child. explain (analyze, costs off, summary off, timing off) select * from (select * from ab where a = 1 union all (values(10,5)) union all select * from ab) ab where b = (select 1); - QUERY PLAN ------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) InitPlan 1 (returns $0) (slice2) -> Result (actual rows=1 loops=1) @@ -2657,6 +2888,8 @@ select * from (select * from ab where a = 1 union all (values(10,5)) union all s -- Another UNION ALL test, but containing a mix of exec init and exec run-time pruning. create table xy_1 (x int, y int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'x' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. insert into xy_1 values(100,-10); set enable_bitmapscan = 0; set enable_indexscan = 0; @@ -2789,25 +3022,35 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1); select tableoid::regclass, * from ab; tableoid | a | b ----------+---+--- + ab_a2_b1 | 2 | 1 ab_a1_b3 | 1 | 3 ab_a1_b3 | 1 | 3 ab_a1_b3 | 1 | 3 - ab_a2_b1 | 2 | 1 (4 rows) drop table ab, lprt_a; -- Join create table tbl1(col1 int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'col1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. insert into tbl1 values (501), (505); analyze tbl1; -- Basic table create table tprt (col1 int) partition by range (col1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'col1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table tprt_1 partition of tprt for values from (1) to (501); +NOTICE: table has parent, setting distribution columns to match parent table create table tprt_2 partition of tprt for values from (501) to (1001); +NOTICE: table has parent, setting distribution columns to match parent table create table tprt_3 partition of tprt for values from (1001) to (2001); +NOTICE: table has parent, setting distribution columns to match parent table create table tprt_4 partition of tprt for values from (2001) to (3001); +NOTICE: table has parent, setting distribution columns to match parent table create table tprt_5 partition of tprt for values from (3001) to (4001); +NOTICE: table has parent, setting distribution columns to match parent table create table tprt_6 partition of tprt for values from (4001) to (5001); +NOTICE: table has parent, setting distribution columns to match parent table create index tprt1_idx on tprt_1 (col1); create index tprt2_idx on tprt_2 (col1); create index tprt3_idx on tprt_3 (col1); @@ -2820,6 +3063,8 @@ set enable_mergejoin = off; set enable_seqscan=off; explain (analyze, costs off, summary off, timing off) select * from tbl1 join tprt on tbl1.col1 > tprt.col1; +NOTICE: One or more columns in the following table(s) do not have statistics: tprt +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (actual rows=6 loops=1) @@ -2831,13 +3076,15 @@ select * from tbl1 join tprt on tbl1.col1 > tprt.col1; Number of partitions to scan: 6 (out of 6) Partitions scanned: Avg 6.0 x 3 workers. Max 6 parts (seg0). -> Seq Scan on tbl1 (actual rows=1 loops=8) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (10 rows) explain (analyze, costs off, summary off, timing off) select * from tbl1 join tprt on tbl1.col1 = tprt.col1; - QUERY PLAN ------------------------------------------------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: tprt +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=2 loops=1) -> Hash Join (actual rows=1 loops=1) Hash Cond: (tbl1.col1 = tprt.col1) @@ -2848,12 +3095,14 @@ select * from tbl1 join tprt on tbl1.col1 = tprt.col1; -> Dynamic Seq Scan on tprt (actual rows=3 loops=1) Number of partitions to scan: 6 (out of 6) Partitions scanned: Avg 6.0 x 3 workers. Max 6 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) -(10 rows) + Optimizer: GPORCA +(11 rows) select tbl1.col1, tprt.col1 from tbl1 inner join tprt on tbl1.col1 > tprt.col1 order by tbl1.col1, tprt.col1; +NOTICE: One or more columns in the following table(s) do not have statistics: tprt +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. col1 | col1 ------+------ 501 | 10 @@ -2867,6 +3116,8 @@ order by tbl1.col1, tprt.col1; select tbl1.col1, tprt.col1 from tbl1 inner join tprt on tbl1.col1 = tprt.col1 order by tbl1.col1, tprt.col1; +NOTICE: One or more columns in the following table(s) do not have statistics: tprt +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. col1 | col1 ------+------ 501 | 501 @@ -2877,6 +3128,8 @@ order by tbl1.col1, tprt.col1; insert into tbl1 values (1001), (1010), (1011); explain (analyze, costs off, summary off, timing off) select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1; +NOTICE: One or more columns in the following table(s) do not have statistics: tprt +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (actual rows=23 loops=1) @@ -2888,13 +3141,15 @@ select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1; Number of partitions to scan: 6 (out of 6) Partitions scanned: Avg 6.0 x 3 workers. Max 6 parts (seg0). -> Seq Scan on tbl1 (actual rows=3 loops=8) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (10 rows) explain (analyze, costs off, summary off, timing off) select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1; - QUERY PLAN ------------------------------------------------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: tprt +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=3 loops=1) -> Hash Join (actual rows=2 loops=1) Hash Cond: (tbl1.col1 = tprt.col1) @@ -2905,12 +3160,14 @@ select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1; -> Dynamic Seq Scan on tprt (actual rows=3 loops=1) Number of partitions to scan: 6 (out of 6) Partitions scanned: Avg 6.0 x 3 workers. Max 6 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) -(10 rows) + Optimizer: GPORCA +(11 rows) select tbl1.col1, tprt.col1 from tbl1 inner join tprt on tbl1.col1 > tprt.col1 order by tbl1.col1, tprt.col1; +NOTICE: One or more columns in the following table(s) do not have statistics: tprt +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. col1 | col1 ------+------ 501 | 10 @@ -2941,6 +3198,8 @@ order by tbl1.col1, tprt.col1; select tbl1.col1, tprt.col1 from tbl1 inner join tprt on tbl1.col1 = tprt.col1 order by tbl1.col1, tprt.col1; +NOTICE: One or more columns in the following table(s) do not have statistics: tprt +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. col1 | col1 ------+------ 501 | 501 @@ -2953,6 +3212,8 @@ delete from tbl1; insert into tbl1 values (4400); explain (analyze, costs off, summary off, timing off) select * from tbl1 join tprt on tbl1.col1 < tprt.col1; +NOTICE: One or more columns in the following table(s) do not have statistics: tprt +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (actual rows=1 loops=1) @@ -2964,12 +3225,14 @@ select * from tbl1 join tprt on tbl1.col1 < tprt.col1; Number of partitions to scan: 6 (out of 6) Partitions scanned: Avg 6.0 x 3 workers. Max 6 parts (seg0). -> Seq Scan on tbl1 (actual rows=1 loops=8) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (10 rows) select tbl1.col1, tprt.col1 from tbl1 inner join tprt on tbl1.col1 < tprt.col1 order by tbl1.col1, tprt.col1; +NOTICE: One or more columns in the following table(s) do not have statistics: tprt +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. col1 | col1 ------+------ 4400 | 4500 @@ -2981,6 +3244,8 @@ delete from tbl1; insert into tbl1 values (10000); explain (analyze, costs off, summary off, timing off) select * from tbl1 join tprt on tbl1.col1 = tprt.col1; +NOTICE: One or more columns in the following table(s) do not have statistics: tprt +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) @@ -2992,12 +3257,14 @@ select * from tbl1 join tprt on tbl1.col1 = tprt.col1; -> Dynamic Seq Scan on tprt (actual rows=3 loops=1) Number of partitions to scan: 6 (out of 6) Partitions scanned: Avg 6.0 x 3 workers. Max 6 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (10 rows) select tbl1.col1, tprt.col1 from tbl1 inner join tprt on tbl1.col1 = tprt.col1 order by tbl1.col1, tprt.col1; +NOTICE: One or more columns in the following table(s) do not have statistics: tprt +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. col1 | col1 ------+------ (0 rows) @@ -3005,9 +3272,17 @@ order by tbl1.col1, tprt.col1; drop table tbl1, tprt; -- Test with columns defined in varying orders between each level create table part_abc (a int not null, b int not null, c int not null) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table part_bac (b int not null, a int not null, c int not null) partition by list (b); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'b' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table part_cab (c int not null, a int not null, b int not null) partition by list (c); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'c' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table part_abc_p1 (a int not null, b int not null, c int not null); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- GPDB: the distribution keys must be the same in all parts of partition -- hierarchy. alter table part_bac set distributed by (a); @@ -3032,10 +3307,16 @@ drop table part_abc; -- Ensure that an Append node properly handles a sub-partitioned table -- matching without any of its leaf partitions matching the clause. create table listp (a int, b int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table listp_1 partition of listp for values in(1) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table listp_1_1 partition of listp_1 for values in(1); +NOTICE: table has parent, setting distribution columns to match parent table create table listp_2 partition of listp for values in(2) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create table listp_2_1 partition of listp_2 for values in(2); +NOTICE: table has parent, setting distribution columns to match parent table select * from listp where b = 1; a | b ---+--- @@ -3046,8 +3327,8 @@ select * from listp where b = 1; -- which match the given parameter. prepare q1 (int,int) as select * from listp where b in ($1,$2); explain (analyze, costs off, summary off, timing off) execute q1 (1,1); - QUERY PLAN ------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) -> Append (actual rows=0 loops=1) Subplans Removed: 1 @@ -3057,8 +3338,8 @@ explain (analyze, costs off, summary off, timing off) execute q1 (1,1); (6 rows) explain (analyze, costs off, summary off, timing off) execute q1 (2,2); - QUERY PLAN ------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) -> Append (actual rows=0 loops=1) Subplans Removed: 1 @@ -3127,26 +3408,33 @@ drop table listp; create table stable_qual_pruning (a timestamp) distributed randomly partition by range (a); create table stable_qual_pruning1 partition of stable_qual_pruning for values from ('2000-01-01') to ('2000-02-01'); +NOTICE: table has parent, setting distribution columns to match parent table create table stable_qual_pruning2 partition of stable_qual_pruning for values from ('2000-02-01') to ('2000-03-01'); +NOTICE: table has parent, setting distribution columns to match parent table create table stable_qual_pruning3 partition of stable_qual_pruning for values from ('3000-02-01') to ('3000-03-01'); +NOTICE: table has parent, setting distribution columns to match parent table -- comparison against a stable value requires run-time pruning explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a < localtimestamp; +NOTICE: One or more columns in the following table(s) do not have statistics: stable_qual_pruning +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN -------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) -> Dynamic Seq Scan on stable_qual_pruning (actual rows=0 loops=1) Number of partitions to scan: 2 (out of 3) - Filter: (a < 'Thu Dec 19 19:04:45.779097 2024'::timestamp without time zone) + Filter: (a < 'Tue Sep 22 04:45:44.293727 2026'::timestamp without time zone) Partitions scanned: Avg 2.0 x 3 workers. Max 2 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (6 rows) -- timestamp < timestamptz comparison is only stable, not immutable explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a < '2000-02-01'::timestamptz; +NOTICE: One or more columns in the following table(s) do not have statistics: stable_qual_pruning +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN -------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) @@ -3154,23 +3442,25 @@ select * from stable_qual_pruning where a < '2000-02-01'::timestamptz; Number of partitions to scan: 1 (out of 3) Filter: (a < 'Tue Feb 01 00:00:00 2000 PST'::timestamp with time zone) Partitions scanned: Avg 1.0 x 3 workers. Max 1 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (6 rows) -- check ScalarArrayOp cases explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a = any(array['2010-02-01', '2020-01-01']::timestamp[]); - QUERY PLAN ---------------------------------------- + QUERY PLAN +-------------------------------- Result (actual rows=0 loops=1) One-Time Filter: false - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (3 rows) explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a = any(array['2000-02-01', '2010-01-01']::timestamp[]); +NOTICE: One or more columns in the following table(s) do not have statistics: stable_qual_pruning +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) @@ -3178,35 +3468,39 @@ select * from stable_qual_pruning Number of partitions to scan: 1 (out of 3) Filter: (a = ANY ('{"Tue Feb 01 00:00:00 2000","Fri Jan 01 00:00:00 2010"}'::timestamp without time zone[])) Partitions scanned: Avg 1.0 x 3 workers. Max 1 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (6 rows) explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a = any(array['2000-02-01', localtimestamp]::timestamp[]); +NOTICE: One or more columns in the following table(s) do not have statistics: stable_qual_pruning +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) -> Dynamic Seq Scan on stable_qual_pruning (actual rows=0 loops=1) Number of partitions to scan: 1 (out of 3) - Filter: (a = ANY ('{"Tue Feb 01 00:00:00 2000","Thu Dec 19 19:04:45.852371 2024"}'::timestamp without time zone[])) + Filter: (a = ANY ('{"Tue Feb 01 00:00:00 2000","Tue Sep 22 04:45:44.303755 2026"}'::timestamp without time zone[])) Partitions scanned: Avg 1.0 x 3 workers. Max 1 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (6 rows) explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a = any(array['2010-02-01', '2020-01-01']::timestamptz[]); - QUERY PLAN ---------------------------------------- + QUERY PLAN +-------------------------------- Result (actual rows=0 loops=1) One-Time Filter: false - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (3 rows) explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a = any(array['2000-02-01', '2010-01-01']::timestamptz[]); +NOTICE: One or more columns in the following table(s) do not have statistics: stable_qual_pruning +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) @@ -3214,12 +3508,14 @@ select * from stable_qual_pruning Number of partitions to scan: 1 (out of 3) Filter: (a = ANY ('{"Tue Feb 01 00:00:00 2000 PST","Fri Jan 01 00:00:00 2010 PST"}'::timestamp with time zone[])) Partitions scanned: Avg 1.0 x 3 workers. Max 1 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (6 rows) explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a = any(null::timestamptz[]); +NOTICE: One or more columns in the following table(s) do not have statistics: stable_qual_pruning +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) @@ -3227,7 +3523,7 @@ select * from stable_qual_pruning Number of partitions to scan: 3 (out of 3) Filter: (a = ANY (NULL::timestamp with time zone[])) Partitions scanned: Avg 3.0 x 3 workers. Max 3 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (6 rows) drop table stable_qual_pruning; @@ -3237,12 +3533,17 @@ drop table stable_qual_pruning; -- non-inclusive operator for an earlier key -- create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table mc3p0 partition of mc3p for values from (0, 0, 0) to (0, maxvalue, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p1 partition of mc3p for values from (1, 1, 1) to (2, minvalue, minvalue); +NOTICE: table has parent, setting distribution columns to match parent table create table mc3p2 partition of mc3p for values from (2, minvalue, minvalue) to (3, maxvalue, maxvalue); +NOTICE: table has parent, setting distribution columns to match parent table insert into mc3p values (0, 1, 1), (1, 1, 1), (2, 1, 1); explain (analyze, costs off, summary off, timing off) select * from mc3p where a < 3 and abs(b) = 1; @@ -3303,12 +3604,20 @@ deallocate ps2; drop table mc3p; -- Ensure runtime pruning works with initplans params with boolean types create table boolvalues (value bool not null); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'value' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. insert into boolvalues values('t'),('f'); create table boolp (a bool) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table boolp_t partition of boolp for values in('t'); +NOTICE: table has parent, setting distribution columns to match parent table create table boolp_f partition of boolp for values in('f'); +NOTICE: table has parent, setting distribution columns to match parent table explain (analyze, costs off, summary off, timing off) select * from boolp where a = (select value from boolvalues where value); +NOTICE: One or more columns in the following table(s) do not have statistics: boolp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) @@ -3328,11 +3637,13 @@ select * from boolp where a = (select value from boolvalues where value); -> Dynamic Seq Scan on boolp (actual rows=0 loops=1) Number of partitions to scan: 2 (out of 2) Partitions scanned: Avg 2.0 x 3 workers. Max 2 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (18 rows) explain (analyze, costs off, summary off, timing off) select * from boolp where a = (select value from boolvalues where not value); +NOTICE: One or more columns in the following table(s) do not have statistics: boolp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ------------------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) @@ -3352,7 +3663,7 @@ select * from boolp where a = (select value from boolvalues where not value); -> Dynamic Seq Scan on boolp (actual rows=0 loops=1) Number of partitions to scan: 2 (out of 2) Partitions scanned: Avg 2.0 x 3 workers. Max 2 parts (seg0). - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (18 rows) drop table boolp; @@ -3362,9 +3673,14 @@ drop table boolp; set enable_seqscan = off; set enable_sort = off; create table ma_test (a int, b int) partition by range (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table ma_test_p1 partition of ma_test for values from (0) to (10); +NOTICE: table has parent, setting distribution columns to match parent table create table ma_test_p2 partition of ma_test for values from (10) to (20); +NOTICE: table has parent, setting distribution columns to match parent table create table ma_test_p3 partition of ma_test for values from (20) to (30); +NOTICE: table has parent, setting distribution columns to match parent table insert into ma_test select x,x from generate_series(0,29) t(x); create index on ma_test (b); analyze ma_test; @@ -3383,7 +3699,7 @@ explain (analyze, costs off, summary off, timing off) execute mt_q1(15); Filter: ((a >= $1) AND ((a % 10) = 5)) Partitions scanned: Avg 3.0 x 3 workers. Max 3 parts (seg0). Optimizer: GPORCA -(12 rows) +(11 rows) execute mt_q1(15); a @@ -3406,7 +3722,7 @@ explain (analyze, costs off, summary off, timing off) execute mt_q1(25); Filter: ((a >= $1) AND ((a % 10) = 5)) Partitions scanned: Avg 3.0 x 3 workers. Max 3 parts (seg0). Optimizer: GPORCA -(12 rows) +(11 rows) execute mt_q1(25); a @@ -3429,7 +3745,7 @@ explain (analyze, costs off, summary off, timing off) execute mt_q1(35); Filter: ((a >= $1) AND ((a % 10) = 5)) Partitions scanned: Avg 3.0 x 3 workers. Max 3 parts (seg0). Optimizer: GPORCA -(12 rows) +(11 rows) execute mt_q1(35); a @@ -3440,8 +3756,8 @@ deallocate mt_q1; prepare mt_q2 (int) as select * from ma_test where a >= $1 order by b limit 1; -- Ensure output list looks sane when the MergeAppend has no subplans. explain (analyze, verbose, costs off, summary off, timing off) execute mt_q2 (35); - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit (actual rows=0 loops=1) Output: a, b -> Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) @@ -3459,15 +3775,15 @@ explain (analyze, verbose, costs off, summary off, timing off) execute mt_q2 (35 Number of partitions to scan: 3 (out of 3) Filter: (ma_test.a >= $1) Partitions scanned: Avg 3.0 x 3 workers. Max 3 parts (seg0). - Settings: enable_hashjoin = 'off', enable_indexonlyscan = 'off', enable_mergejoin = 'off', enable_parallel = 'off', enable_seqscan = 'off', enable_sort = 'off', optimizer = 'on', plan_cache_mode = 'force_generic_plan' + Settings: enable_hashjoin = 'off', enable_indexonlyscan = 'off', enable_mergejoin = 'off', enable_seqscan = 'off', enable_sort = 'off', optimizer = 'on', plan_cache_mode = 'force_generic_plan' Optimizer: GPORCA (19 rows) deallocate mt_q2; -- ensure initplan params properly prune partitions explain (analyze, costs off, summary off, timing off) select * from ma_test where a >= (select min(b) from ma_test_p2) order by b; - QUERY PLAN ------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (actual rows=20 loops=1) Merge Key: ma_test.b -> Sort (actual rows=8 loops=1) @@ -3488,7 +3804,7 @@ explain (analyze, costs off, summary off, timing off) select * from ma_test wher Number of partitions to scan: 3 (out of 3) Partitions scanned: Avg 2.0 x 3 workers of 2 scans. Max 2 parts (seg0). Optimizer: GPORCA -(17 rows) +(20 rows) reset enable_seqscan; reset enable_sort; @@ -3500,34 +3816,42 @@ reset enable_indexonlyscan; -- -- array type list partition key create table pp_arrpart (a int[]) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table pp_arrpart1 partition of pp_arrpart for values in ('{1}'); +NOTICE: table has parent, setting distribution columns to match parent table create table pp_arrpart2 partition of pp_arrpart for values in ('{2, 3}', '{4, 5}'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from pp_arrpart where a = '{1}'; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: pp_arrpart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on pp_arrpart Number of partitions to scan: 1 (out of 2) Filter: (a = '{1}'::integer[]) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from pp_arrpart where a = '{1, 2}'; - QUERY PLAN -------------------------------------- + QUERY PLAN +-------------------------- Result One-Time Filter: false - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (3 rows) explain (costs off) select * from pp_arrpart where a in ('{4, 5}', '{1}'); +NOTICE: One or more columns in the following table(s) do not have statistics: pp_arrpart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. QUERY PLAN ---------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on pp_arrpart Number of partitions to scan: 2 (out of 2) Filter: ((a = '{4,5}'::integer[]) OR (a = '{1}'::integer[])) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) update pp_arrpart set a = a where a = '{1}'; @@ -3553,8 +3877,12 @@ explain (costs off) delete from pp_arrpart where a = '{1}'; drop table pp_arrpart; -- array type hash partition key create table pph_arrpart (a int[]) partition by hash (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table pph_arrpart1 partition of pph_arrpart for values with (modulus 2, remainder 0); +NOTICE: table has parent, setting distribution columns to match parent table create table pph_arrpart2 partition of pph_arrpart for values with (modulus 2, remainder 1); +NOTICE: table has parent, setting distribution columns to match parent table insert into pph_arrpart values ('{1}'), ('{1, 2}'), ('{4, 5}'); select tableoid::regclass, * from pph_arrpart order by 1; tableoid | a @@ -3601,23 +3929,27 @@ create table pp_enumpart (a pp_colors) partition by list (a); NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table pp_enumpart_green partition of pp_enumpart for values in ('green'); +NOTICE: table has parent, setting distribution columns to match parent table create table pp_enumpart_blue partition of pp_enumpart for values in ('blue'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from pp_enumpart where a = 'blue'; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: pp_enumpart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on pp_enumpart Number of partitions to scan: 1 (out of 2) Filter: (a = 'blue'::pp_colors) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from pp_enumpart where a = 'black'; - QUERY PLAN ---------------------------------------- + QUERY PLAN +-------------------------- Result One-Time Filter: false - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (3 rows) drop table pp_enumpart; @@ -3625,40 +3957,52 @@ drop type pp_colors; -- record type as partition key create type pp_rectype as (a int, b int); create table pp_recpart (a pp_rectype) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table pp_recpart_11 partition of pp_recpart for values in ('(1,1)'); +NOTICE: table has parent, setting distribution columns to match parent table create table pp_recpart_23 partition of pp_recpart for values in ('(2,3)'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from pp_recpart where a = '(1,1)'::pp_rectype; - QUERY PLAN -------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: pp_recpart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on pp_recpart Number of partitions to scan: 1 (out of 2) Filter: (a = '(1,1)'::pp_rectype) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from pp_recpart where a = '(1,2)'::pp_rectype; - QUERY PLAN ---------------------------------------- + QUERY PLAN +-------------------------- Result One-Time Filter: false - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (3 rows) drop table pp_recpart; drop type pp_rectype; -- range type partition key create table pp_intrangepart (a int4range) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table pp_intrangepart12 partition of pp_intrangepart for values in ('[1,2]'); +NOTICE: table has parent, setting distribution columns to match parent table create table pp_intrangepart2inf partition of pp_intrangepart for values in ('[2,)'); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from pp_intrangepart where a = '[1,2]'::int4range; - QUERY PLAN -------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: pp_intrangepart +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on pp_intrangepart Number of partitions to scan: 1 (out of 2) Filter: (a = '[1,3)'::int4range) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range; @@ -3666,7 +4010,7 @@ explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range; -------------------------- Result One-Time Filter: false - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (3 rows) drop table pp_intrangepart; @@ -3674,16 +4018,22 @@ drop table pp_intrangepart; -- Ensure the enable_partition_prune GUC properly disables partition pruning. -- create table pp_lp (a int, value int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table pp_lp1 partition of pp_lp for values in(1); +NOTICE: table has parent, setting distribution columns to match parent table create table pp_lp2 partition of pp_lp for values in(2); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from pp_lp where a = 1; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: pp_lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on pp_lp Number of partitions to scan: 1 (out of 2) Filter: (a = 1) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) update pp_lp set value = 10 where a = 1; @@ -3709,18 +4059,20 @@ explain (costs off) delete from pp_lp where a = 1; set enable_partition_pruning = off; set constraint_exclusion = 'partition'; -- this should not affect the result. explain (costs off) select * from pp_lp where a = 1; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: pp_lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on pp_lp Number of partitions to scan: 1 (out of 2) Filter: (a = 1) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) update pp_lp set value = 10 where a = 1; - QUERY PLAN -------------------------------------- + QUERY PLAN +---------------------------------------- Update on pp_lp Update on pp_lp1 pp_lp_1 Update on pp_lp2 pp_lp_2 @@ -3733,8 +4085,8 @@ explain (costs off) update pp_lp set value = 10 where a = 1; (9 rows) explain (costs off) delete from pp_lp where a = 1; - QUERY PLAN -------------------------------------- + QUERY PLAN +---------------------------------------- Delete on pp_lp Delete on pp_lp1 pp_lp_1 Delete on pp_lp2 pp_lp_2 @@ -3748,18 +4100,20 @@ explain (costs off) delete from pp_lp where a = 1; set constraint_exclusion = 'off'; -- this should not affect the result. explain (costs off) select * from pp_lp where a = 1; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: pp_lp +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on pp_lp Number of partitions to scan: 1 (out of 2) Filter: (a = 1) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) explain (costs off) update pp_lp set value = 10 where a = 1; - QUERY PLAN -------------------------------------- + QUERY PLAN +---------------------------------------- Update on pp_lp Update on pp_lp1 pp_lp_1 Update on pp_lp2 pp_lp_2 @@ -3772,8 +4126,8 @@ explain (costs off) update pp_lp set value = 10 where a = 1; (9 rows) explain (costs off) delete from pp_lp where a = 1; - QUERY PLAN -------------------------------------- + QUERY PLAN +---------------------------------------- Delete on pp_lp Delete on pp_lp1 pp_lp_1 Delete on pp_lp2 pp_lp_2 @@ -3788,10 +4142,14 @@ explain (costs off) delete from pp_lp where a = 1; drop table pp_lp; -- Ensure enable_partition_prune does not affect non-partitioned tables. create table inh_lp (a int, value int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table inh_lp1 (a int, value int, check(a = 1)) inherits (inh_lp); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging column "a" with inherited definition NOTICE: merging column "value" with inherited definition create table inh_lp2 (a int, value int, check(a = 2)) inherits (inh_lp); +NOTICE: table has parent, setting distribution columns to match parent table NOTICE: merging column "a" with inherited definition NOTICE: merging column "value" with inherited definition set constraint_exclusion = 'partition'; @@ -3809,8 +4167,8 @@ explain (costs off) select * from inh_lp where a = 1; (7 rows) explain (costs off) update inh_lp set value = 10 where a = 1; - QUERY PLAN -------------------------------------- + QUERY PLAN +------------------------------------------------ Update on inh_lp Update on inh_lp inh_lp_1 Update on inh_lp1 inh_lp_2 @@ -3824,8 +4182,8 @@ explain (costs off) update inh_lp set value = 10 where a = 1; (10 rows) explain (costs off) delete from inh_lp where a = 1; - QUERY PLAN -------------------------------------- + QUERY PLAN +------------------------------------------ Delete on inh_lp Delete on inh_lp inh_lp_1 Delete on inh_lp1 inh_lp_2 @@ -3835,19 +4193,19 @@ explain (costs off) delete from inh_lp where a = 1; -> Seq Scan on inh_lp1 inh_lp_2 Filter: (a = 1) Optimizer: Postgres query optimizer -(8 rows) +(9 rows) -- Ensure we don't exclude normal relations when we only expect to exclude -- inheritance children explain (costs off) update inh_lp1 set value = 10 where a = 2; - QUERY PLAN -------------------------------------- + QUERY PLAN +-------------------------------------------- Update on inh_lp1 -> Result -> Result -> Result One-Time Filter: false - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (6 rows) drop table inh_lp cascade; @@ -3858,39 +4216,59 @@ reset enable_partition_pruning; reset constraint_exclusion; -- Check pruning for a partition tree containing only temporary relations create temp table pp_temp_parent (a int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table pp_temp_part_1 partition of pp_temp_parent for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create temp table pp_temp_part_def partition of pp_temp_parent default; +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from pp_temp_parent where true; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: pp_temp_parent +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Dynamic Seq Scan on pp_temp_parent Number of partitions to scan: 2 (out of 2) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (4 rows) explain (costs off) select * from pp_temp_parent where a = 2; - QUERY PLAN ------------------------------------------- +NOTICE: One or more columns in the following table(s) do not have statistics: pp_temp_parent +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Dynamic Seq Scan on pp_temp_parent Number of partitions to scan: 1 (out of 2) Filter: (a = 2) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (5 rows) drop table pp_temp_parent; -- Stress run-time partition pruning a bit more, per bug reports create temp table p (a int, b int, c int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table p1 partition of p for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create temp table p2 partition of p for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table create temp table q (a int, b int, c int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create temp table q1 partition of q for values in (1) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create temp table q11 partition of q1 for values in (1) partition by list (c); +NOTICE: table has parent, setting distribution columns to match parent table create temp table q111 partition of q11 for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create temp table q2 partition of q for values in (2) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table create temp table q21 partition of q2 for values in (1); +NOTICE: table has parent, setting distribution columns to match parent table create temp table q22 partition of q2 for values in (2); +NOTICE: table has parent, setting distribution columns to match parent table insert into q22 values (2, 2, 3); -- GPDB: This is the query that needs the "matchsubs" rule at the top of the file -- The constant third branch of the UNION is executed at random segment. If the @@ -3909,7 +4287,7 @@ from ( where s.a = 1 and s.b = 1 and s.c = (select 1); QUERY PLAN ------------------------------------------------------------- - Gather Motion 1:1 (slice1; segments: 1) + Gather Motion 2:1 (slice1; segments: 2) InitPlan 1 (returns $0) (slice2) -> Result -> Append @@ -3918,7 +4296,7 @@ where s.a = 1 and s.b = 1 and s.c = (select 1); -> Seq Scan on q111 q1 Filter: ((a = 1) AND (b = 1) AND (c = $0)) -> Result - One-Time Filter: (gp_execution_segment() = 1) + One-Time Filter: (gp_execution_segment() = 0) -> Result One-Time Filter: (1 = $0) Optimizer: Postgres query optimizer @@ -3961,7 +4339,7 @@ explain (costs off) execute q (1, 1); -> Seq Scan on q111 q1 Filter: ((a = $1) AND (b = $2) AND (c = $0)) -> Result - One-Time Filter: (gp_execution_segment() = 1) + One-Time Filter: (gp_execution_segment() = 0) -> Result One-Time Filter: ((1 = $1) AND (1 = $2) AND (1 = $0)) Optimizer: Postgres query optimizer @@ -3977,9 +4355,14 @@ drop table p, q; -- Ensure run-time pruning works correctly when we match a partitioned table -- on the first level but find no matching partitions on the second level. create table listp (a int, b int) partition by list (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table listp1 partition of listp for values in(1); +NOTICE: table has parent, setting distribution columns to match parent table create table listp2 partition of listp for values in(2) partition by list(b); +NOTICE: table has parent, setting distribution columns to match parent table create table listp2_10 partition of listp2 for values in (10); +NOTICE: table has parent, setting distribution columns to match parent table explain (analyze, costs off, summary off, timing off) select * from listp where a = (select 2) and b <> 10; QUERY PLAN @@ -4006,7 +4389,7 @@ explain (costs off) select * from listp1 where a = 2; Gather Motion 1:1 (slice1; segments: 1) -> Seq Scan on listp1 Filter: (a = 2) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (4 rows) explain (costs off) update listp1 set a = 1 where a = 2; @@ -4021,7 +4404,7 @@ explain (costs off) update listp1 set a = 1 where a = 2; -> Split -> Seq Scan on listp1 Filter: (a = 2) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (10 rows) -- constraint exclusion enabled @@ -4032,7 +4415,7 @@ explain (costs off) select * from listp1 where a = 2; Gather Motion 1:1 (slice1; segments: 1) -> Seq Scan on listp1 Filter: (a = 2) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (4 rows) explain (costs off) update listp1 set a = 1 where a = 2; @@ -4047,7 +4430,7 @@ explain (costs off) update listp1 set a = 1 where a = 2; -> Split -> Seq Scan on listp1 Filter: (a = 2) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (10 rows) reset constraint_exclusion; @@ -4057,9 +4440,14 @@ drop table listp; set parallel_setup_cost to 0; set parallel_tuple_cost to 0; create table listp (a int) partition by list(a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table listp_12 partition of listp for values in(1,2) partition by list(a); +NOTICE: table has parent, setting distribution columns to match parent table create table listp_12_1 partition of listp_12 for values in(1); +NOTICE: table has parent, setting distribution columns to match parent table create table listp_12_2 partition of listp_12 for values in(2); +NOTICE: table has parent, setting distribution columns to match parent table -- 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. @@ -4114,19 +4502,26 @@ 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); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rangep_0_to_100 partition of rangep for values from (0) to (100) partition by list (b); +NOTICE: table has parent, setting distribution columns to match parent table -- 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); +NOTICE: table has parent, setting distribution columns to match parent table create table rangep_0_to_100_2 partition of rangep_0_to_100 for values in(2); +NOTICE: table has parent, setting distribution columns to match parent table create table rangep_0_to_100_3 partition of rangep_0_to_100 for values in(3); +NOTICE: table has parent, setting distribution columns to match parent table create table rangep_100_to_200 partition of rangep for values from (100) to (200); +NOTICE: table has parent, setting distribution columns to match parent table 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 ------------------------------------------------------------------------------------------------------------ + QUERY PLAN +------------------------------------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (actual rows=0 loops=1) Merge Key: rangep.a InitPlan 1 (returns $0) (slice2) @@ -4154,8 +4549,12 @@ drop table rangep; -- clauses for different partition keys -- create table rp_prefix_test1 (a int, b varchar) partition by range(a, b); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rp_prefix_test1_p1 partition of rp_prefix_test1 for values from (1, 'a') to (1, 'b'); +NOTICE: table has parent, setting distribution columns to match parent table create table rp_prefix_test1_p2 partition of rp_prefix_test1 for values from (2, 'a') to (2, 'b'); +NOTICE: table has parent, setting distribution columns to match parent table -- Don't call get_steps_using_prefix() with the last partition key b plus -- an empty prefix explain (costs off) select * from rp_prefix_test1 where a <= 1 and b = 'a'; @@ -4168,8 +4567,12 @@ explain (costs off) select * from rp_prefix_test1 where a <= 1 and b = 'a'; (4 rows) create table rp_prefix_test2 (a int, b int, c int) partition by range(a, b, c); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rp_prefix_test2_p1 partition of rp_prefix_test2 for values from (1, 1, 0) to (1, 1, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table rp_prefix_test2_p2 partition of rp_prefix_test2 for values from (2, 2, 0) to (2, 2, 10); +NOTICE: table has parent, setting distribution columns to match parent table -- Don't call get_steps_using_prefix() with the last partition key c plus -- an invalid prefix (ie, b = 1) explain (costs off) select * from rp_prefix_test2 where a <= 1 and b = 1 and c >= 0; @@ -4182,8 +4585,12 @@ explain (costs off) select * from rp_prefix_test2 where a <= 1 and b = 1 and c > (4 rows) create table rp_prefix_test3 (a int, b int, c int, d int) partition by range(a, b, c, d); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table rp_prefix_test3_p1 partition of rp_prefix_test3 for values from (1, 1, 1, 0) to (1, 1, 1, 10); +NOTICE: table has parent, setting distribution columns to match parent table create table rp_prefix_test3_p2 partition of rp_prefix_test3 for values from (2, 2, 2, 0) to (2, 2, 2, 10); +NOTICE: table has parent, setting distribution columns to match parent table -- Test that get_steps_using_prefix() handles a prefix that contains multiple -- clauses for the partition key b (ie, b >= 1 and b >= 2) explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b >= 2 and c >= 2 and d >= 0; @@ -4207,22 +4614,259 @@ explain (costs off) select * from rp_prefix_test3 where a >= 1 and b >= 1 and b Optimizer: Postgres query optimizer (4 rows) -create table hp_prefix_test (a int, b int, c int, d int) partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops); -create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 2, remainder 0); -create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 2, remainder 1); --- Test that get_steps_using_prefix() handles non-NULL step_nullkeys -explain (costs off) select * from hp_prefix_test where a = 1 and b is null and c = 1 and d = 1; - QUERY PLAN -------------------------------------------------------------------- +drop table rp_prefix_test1; +drop table rp_prefix_test2; +drop table rp_prefix_test3; +-- +-- Test that get_steps_using_prefix() handles IS NULL clauses correctly +-- +create table hp_prefix_test (a int, b int, c int, d int) + partition by hash (a part_test_int4_ops, b part_test_int4_ops, c part_test_int4_ops, d part_test_int4_ops); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +-- create 8 partitions +select 'create table hp_prefix_test_p' || x::text || ' partition of hp_prefix_test for values with (modulus 8, remainder ' || x::text || ');' +from generate_Series(0,7) x; + ?column? +------------------------------------------------------------------------------------------------------ + create table hp_prefix_test_p0 partition of hp_prefix_test for values with (modulus 8, remainder 0); + create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 8, remainder 1); + create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 8, remainder 2); + create table hp_prefix_test_p3 partition of hp_prefix_test for values with (modulus 8, remainder 3); + create table hp_prefix_test_p4 partition of hp_prefix_test for values with (modulus 8, remainder 4); + create table hp_prefix_test_p5 partition of hp_prefix_test for values with (modulus 8, remainder 5); + create table hp_prefix_test_p6 partition of hp_prefix_test for values with (modulus 8, remainder 6); + create table hp_prefix_test_p7 partition of hp_prefix_test for values with (modulus 8, remainder 7); +(8 rows) + +\gexec +create table hp_prefix_test_p0 partition of hp_prefix_test for values with (modulus 8, remainder 0); +NOTICE: table has parent, setting distribution columns to match parent table +create table hp_prefix_test_p1 partition of hp_prefix_test for values with (modulus 8, remainder 1); +NOTICE: table has parent, setting distribution columns to match parent table +create table hp_prefix_test_p2 partition of hp_prefix_test for values with (modulus 8, remainder 2); +NOTICE: table has parent, setting distribution columns to match parent table +create table hp_prefix_test_p3 partition of hp_prefix_test for values with (modulus 8, remainder 3); +NOTICE: table has parent, setting distribution columns to match parent table +create table hp_prefix_test_p4 partition of hp_prefix_test for values with (modulus 8, remainder 4); +NOTICE: table has parent, setting distribution columns to match parent table +create table hp_prefix_test_p5 partition of hp_prefix_test for values with (modulus 8, remainder 5); +NOTICE: table has parent, setting distribution columns to match parent table +create table hp_prefix_test_p6 partition of hp_prefix_test for values with (modulus 8, remainder 6); +NOTICE: table has parent, setting distribution columns to match parent table +create table hp_prefix_test_p7 partition of hp_prefix_test for values with (modulus 8, remainder 7); +NOTICE: table has parent, setting distribution columns to match parent table +-- insert 16 rows, one row for each test to perform. +insert into hp_prefix_test +select + case a when 0 then null else 1 end, + case b when 0 then null else 2 end, + case c when 0 then null else 3 end, + case d when 0 then null else 4 end +from + generate_series(0,1) a, + generate_series(0,1) b, + generate_Series(0,1) c, + generate_Series(0,1) d; +-- Ensure partition pruning works correctly for each combination of IS NULL +-- and equality quals. This may seem a little excessive, but there have been +-- a number of bugs in this area over the years. We make use of row only +-- output to reduce the size of the expected results. +\t on +select + 'explain (costs off) select tableoid::regclass,* from hp_prefix_test where ' || + string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos) +from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s) +group by g.s +order by g.s; + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 + explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 + +\gexec +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p0 hp_prefix_test + Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d IS NULL)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null Gather Motion 1:1 (slice1; segments: 1) -> Seq Scan on hp_prefix_test_p1 hp_prefix_test - Filter: ((b IS NULL) AND (a = 1) AND (c = 1) AND (d = 1)) + Filter: ((b IS NULL) AND (c IS NULL) AND (d IS NULL) AND (a = 1)) Optimizer: Postgres query optimizer -(4 rows) -drop table rp_prefix_test1; -drop table rp_prefix_test2; -drop table rp_prefix_test3; +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p2 hp_prefix_test + Filter: ((a IS NULL) AND (c IS NULL) AND (d IS NULL) AND (b = 2)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((c IS NULL) AND (d IS NULL) AND (a = 1) AND (b = 2)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p3 hp_prefix_test + Filter: ((a IS NULL) AND (b IS NULL) AND (d IS NULL) AND (c = 3)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p7 hp_prefix_test + Filter: ((b IS NULL) AND (d IS NULL) AND (a = 1) AND (c = 3)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((a IS NULL) AND (d IS NULL) AND (b = 2) AND (c = 3)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p5 hp_prefix_test + Filter: ((d IS NULL) AND (a = 1) AND (b = 2) AND (c = 3)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((a IS NULL) AND (b IS NULL) AND (c IS NULL) AND (d = 4)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p6 hp_prefix_test + Filter: ((b IS NULL) AND (c IS NULL) AND (a = 1) AND (d = 4)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p5 hp_prefix_test + Filter: ((a IS NULL) AND (c IS NULL) AND (b = 2) AND (d = 4)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p6 hp_prefix_test + Filter: ((c IS NULL) AND (a = 1) AND (b = 2) AND (d = 4)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((a IS NULL) AND (b IS NULL) AND (c = 3) AND (d = 4)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p5 hp_prefix_test + Filter: ((b IS NULL) AND (a = 1) AND (c = 3) AND (d = 4)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p6 hp_prefix_test + Filter: ((a IS NULL) AND (b = 2) AND (c = 3) AND (d = 4)) + Optimizer: Postgres query optimizer + +explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 + Gather Motion 1:1 (slice1; segments: 1) + -> Seq Scan on hp_prefix_test_p4 hp_prefix_test + Filter: ((a = 1) AND (b = 2) AND (c = 3) AND (d = 4)) + Optimizer: Postgres query optimizer + +-- And ensure we get exactly 1 row from each. Again, all 16 possible combinations. +select + 'select tableoid::regclass,* from hp_prefix_test where ' || + string_agg(c.colname || case when g.s & (1 << c.colpos) = 0 then ' is null' else ' = ' || (colpos+1)::text end, ' and ' order by c.colpos) +from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series(0,15) g(s) +group by g.s +order by g.s; + select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null + select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null + select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null + select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null + select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null + select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null + select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null + select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null + select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 + select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 + select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 + select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 + select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 + select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 + select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 + select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 + +\gexec +select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d is null + hp_prefix_test_p0 | | | | + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d is null + hp_prefix_test_p1 | 1 | | | + +select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d is null + hp_prefix_test_p2 | | 2 | | + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d is null + hp_prefix_test_p4 | 1 | 2 | | + +select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d is null + hp_prefix_test_p3 | | | 3 | + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d is null + hp_prefix_test_p7 | 1 | | 3 | + +select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d is null + hp_prefix_test_p4 | | 2 | 3 | + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d is null + hp_prefix_test_p5 | 1 | 2 | 3 | + +select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c is null and d = 4 + hp_prefix_test_p4 | | | | 4 + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c is null and d = 4 + hp_prefix_test_p6 | 1 | | | 4 + +select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c is null and d = 4 + hp_prefix_test_p5 | | 2 | | 4 + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c is null and d = 4 + hp_prefix_test_p6 | 1 | 2 | | 4 + +select tableoid::regclass,* from hp_prefix_test where a is null and b is null and c = 3 and d = 4 + hp_prefix_test_p4 | | | 3 | 4 + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b is null and c = 3 and d = 4 + hp_prefix_test_p5 | 1 | | 3 | 4 + +select tableoid::regclass,* from hp_prefix_test where a is null and b = 2 and c = 3 and d = 4 + hp_prefix_test_p6 | | 2 | 3 | 4 + +select tableoid::regclass,* from hp_prefix_test where a = 1 and b = 2 and c = 3 and d = 4 + hp_prefix_test_p4 | 1 | 2 | 3 | 4 + +\t off drop table hp_prefix_test; -- -- Check that gen_partprune_steps() detects self-contradiction from clauses @@ -4242,21 +4886,27 @@ using hash as operator 1 ===, function 2 part_hashint4_noop(int4, int8); create table hp_contradict_test (a int, b int) partition by hash (a part_test_int4_ops2, b part_test_int4_ops2); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. create table hp_contradict_test_p1 partition of hp_contradict_test for values with (modulus 2, remainder 0); +NOTICE: table has parent, setting distribution columns to match parent table create table hp_contradict_test_p2 partition of hp_contradict_test for values with (modulus 2, remainder 1); +NOTICE: table has parent, setting distribution columns to match parent table explain (costs off) select * from hp_contradict_test where a is null and a === 1 and b === 1; - QUERY PLAN --------------------------- + QUERY PLAN +------------------------------------- Result One-Time Filter: false -(2 rows) + Optimizer: Postgres query optimizer +(3 rows) explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 and a is null; - QUERY PLAN --------------------------- + QUERY PLAN +------------------------------------- Result One-Time Filter: false -(2 rows) + Optimizer: Postgres query optimizer +(3 rows) drop table hp_contradict_test; drop operator class part_test_int4_ops2 using hash; diff --git a/src/test/singlenode_regress/expected/opr_sanity.out b/src/test/singlenode_regress/expected/opr_sanity.out index 2fb7ba06584..d3bc9936213 100644 --- a/src/test/singlenode_regress/expected/opr_sanity.out +++ b/src/test/singlenode_regress/expected/opr_sanity.out @@ -2231,6 +2231,7 @@ ORDER BY 1, 2, 3; | complex_ops | complex_ops | complex | float_ops | float4_ops | real | float_ops | float8_ops | double precision + | interval_ops | interval_ops | interval | jsonb_ops | jsonb_ops | jsonb | multirange_ops | multirange_ops | anymultirange | numeric_ops | numeric_ops | numeric @@ -2239,7 +2240,7 @@ ORDER BY 1, 2, 3; | record_ops | record_ops | record | tsquery_ops | tsquery_ops | tsquery | tsvector_ops | tsvector_ops | tsvector -(16 rows) +(17 rows) -- **************** pg_index **************** -- Look for illegal values in pg_index fields. From 644e0a1dcb33eff3bde91f3863e20fc05636f303 Mon Sep 17 00:00:00 2001 From: reshke Date: Tue, 22 Sep 2026 22:44:52 +0300 Subject: [PATCH 21/22] Backport fixup: port amcheck 005_pitr to PostgresNode/PG14 TAP API The test arrived with the amcheck 'interrupted page deletion' backport using PostgreSQL 16+ module names (PostgreSQL::Test::Cluster/Utils) and pg_control_init(), which do not exist in this PG14-lineage tree; the script died at compile time (prove exit code 29, no TAP output). Port it to PostgresNode/TestLib and size the test values against the real block size minus INDEX_SIZE_MASK slack so the intended btree leaf layout (and thus the interrupted page deletion UNLINK record) still arises on CBDB's 32KB pages. Accept the UNLINK_PAGE_META variant of the record that this codebase emits. --- contrib/amcheck/t/005_pitr.pl | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/contrib/amcheck/t/005_pitr.pl b/contrib/amcheck/t/005_pitr.pl index 07187a799be..b776dd32917 100644 --- a/contrib/amcheck/t/005_pitr.pl +++ b/contrib/amcheck/t/005_pitr.pl @@ -3,12 +3,12 @@ # Test integrity of intermediate states by PITR to those states use strict; use warnings; -use PostgreSQL::Test::Cluster; -use PostgreSQL::Test::Utils; +use PostgresNode; +use TestLib; use Test::More; # origin node: generate WAL records of interest. -my $origin = PostgreSQL::Test::Cluster->new('origin'); +my $origin = PostgresNode->new('origin'); $origin->init(has_archiving => 1, allows_streaming => 1); $origin->append_conf('postgresql.conf', 'autovacuum = off'); $origin->start; @@ -22,8 +22,14 @@ CREATE TABLE not_leftmost (c text); ALTER TABLE not_leftmost ALTER c SET STORAGE PLAIN; INSERT INTO not_leftmost - SELECT repeat(n::text, database_block_size / 4) - FROM generate_series(1,6) t(n), pg_control_init(); +-- Each value occupies roughly a quarter of a btree leaf page. This limits +-- each index tuple to fit INDEX_SIZE_MASK (8160-ish) even where the index +-- page can hold more (e.g. CBDB's 32KB pages), so leaf pages hold up to +-- four index tuples. Both this and upstream's quarter-of-8KB sizing end +-- with the same leaf layout, where deleting the first four PK values +-- leaves the leftmost leaf and one other leaf empty. +SELECT repeat(n::text, current_setting('block_size')::int / 4 - 100) +FROM generate_series(1,6) t(n); ALTER TABLE not_leftmost ADD CONSTRAINT not_leftmost_pk PRIMARY KEY (c); DELETE FROM not_leftmost WHERE c ~ '^[1-4]'; SELECT pg_create_physical_replication_slot('for_waldump', true, false); @@ -51,13 +57,14 @@ run_log(['pg_waldump', '-p', $origin->data_dir . '/pg_wal', $before_vacuum_walfile, $after_unlink_walfile], '>', \$stdout); - $stdout =~ m|^rmgr: Btree .*, lsn: ([/0-9A-F]+), .*, desc: UNLINK_PAGE left|m; + # CBDB/GPDB may emit this record as UNLINK_PAGE_META + $stdout =~ m|^rmgr: Btree .*, lsn: ([/0-9A-F]+), .*, desc: UNLINK_PAGE(?:_META)? left|m; $1; }; die "did not find UNLINK_PAGE record" unless $unlink_lsn; # replica node: amcheck at notable points in the WAL stream -my $replica = PostgreSQL::Test::Cluster->new('replica'); +my $replica = PostgresNode->new('replica'); $replica->init_from_backup($origin, 'my_backup', has_restoring => 1); $replica->append_conf('postgresql.conf', "recovery_target_lsn = '$unlink_lsn'"); From c0d53373a46cfc581aea0d358a6fdfe67849fcbf Mon Sep 17 00:00:00 2001 From: reshke Date: Wed, 23 Sep 2026 08:31:43 +0300 Subject: [PATCH 22/22] Mask parallel-sensitive explain output via start_ignore The force-parallel suites (ic-cbdb-parallel, ic-orca-parallel) run the same expected files with force_parallel_mode=enable_parallel GUCs, so 'Parallel Seq Scan' plan nodes and extra plan lines differ from the normal ic-good runs in: - brin_multi.sql: the 8 EXPLAIN (ANALYZE/COSTS OFF) statements of the minmax_multi date/timestamp/interval overflow tests, - partition_prune.sql: the headerless (\t on) hp_prefix_test explain \gexec section, whose output cannot be canonicalized as plan blocks by the comparison machinery. Wrap them in --start_ignore/--end_ignore so all suites skip comparing the region contents, and refresh the brin_multi expected outputs (both optimizer variants) to match the real parallel-suite output inside the ignored regions. --- src/test/regress/expected/brin_multi.out | 102 +++++++++++----- .../expected/brin_multi_optimizer_1.out | 113 +++++++++++++----- src/test/regress/expected/partition_prune.out | 6 + .../expected/partition_prune_optimizer.out | 6 + src/test/regress/sql/brin_multi.sql | 19 ++- src/test/regress/sql/partition_prune.sql | 6 + 6 files changed, 191 insertions(+), 61 deletions(-) diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out index 0075a17569a..6b312a73462 100644 --- a/src/test/regress/expected/brin_multi.out +++ b/src/test/regress/expected/brin_multi.out @@ -20,6 +20,8 @@ CREATE TABLE brintest_multi ( uuidcol uuid, lsncol pg_lsn ) WITH (fillfactor=10); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'int8col' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brintest_multi SELECT 142857 * tenthous, thousand, @@ -111,6 +113,8 @@ CREATE TABLE brinopers_multi (colname name, typ text, op text[], value text[], matches int[], check (cardinality(op) = cardinality(value)), check (cardinality(op) = cardinality(matches))); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'colname' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brinopers_multi VALUES ('int2col', 'int2', '{>, >=, =, <=, <}', @@ -355,16 +359,18 @@ insert into public.brintest_multi (float8col) values (real 'nan'); UPDATE brintest_multi SET int8col = int8col * int4col; -- Test handling of inet netmasks with inet_minmax_multi_ops CREATE TABLE brin_test_inet (a inet); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE INDEX ON brin_test_inet USING brin (a inet_minmax_multi_ops); INSERT INTO brin_test_inet VALUES ('127.0.0.1/0'); INSERT INTO brin_test_inet VALUES ('0.0.0.0/12'); DROP TABLE brin_test_inet; -- Tests for brin_summarize_new_values SELECT brin_summarize_new_values('brintest_multi'); -- error, not an index -ERROR: "brintest_multi" is not an index +ERROR: "brintest_multi" is not an index (seg0 slice1 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=4083798) CONTEXT: SQL function "brin_summarize_new_values" statement 1 SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index -ERROR: "tenk1_unique1" is not a BRIN index +ERROR: "tenk1_unique1" is not a BRIN index (seg0 slice1 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=4083798) CONTEXT: SQL function "brin_summarize_new_values" statement 1 SELECT brin_summarize_new_values('brinidx_multi') > 0 AS result; -- ok, no change expected result @@ -395,6 +401,8 @@ SELECT brin_desummarize_range('brinidx_multi', 100000000); -- test building an index with many values, to force compaction of the buffer CREATE TABLE brin_large_range (a int4); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brin_large_range SELECT i FROM generate_series(1,10000) s(i); CREATE INDEX brin_large_range_idx ON brin_large_range USING brin (a int4_minmax_multi_ops); DROP TABLE brin_large_range; @@ -402,6 +410,8 @@ DROP TABLE brin_large_range; CREATE TABLE brin_summarize_multi ( value int ) WITH (fillfactor=10, autovacuum_enabled=false); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'value' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE INDEX brin_summarize_multi_idx ON brin_summarize_multi USING brin (value) WITH (pages_per_range=2); -- Fill a few pages DO $$ @@ -443,21 +453,23 @@ SELECT brin_summarize_range('brin_summarize_multi_idx', 4294967295); -- invalid block number values SELECT brin_summarize_range('brin_summarize_multi_idx', -1); -ERROR: block number out of range: -1 +ERROR: block number out of range: -1 (seg1 slice1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=4083799) +CONTEXT: SQL function "brin_summarize_range" statement 1 SELECT brin_summarize_range('brin_summarize_multi_idx', 4294967296); -ERROR: block number out of range: 4294967296 +ERROR: block number out of range: 4294967296 (seg1 slice1 2a02:6b8:c37:834b:0:5644:602c:0:7003 pid=4083799) +CONTEXT: SQL function "brin_summarize_range" statement 1 -- test brin cost estimates behave sanely based on correlation of values CREATE TABLE brin_test_multi (a INT, b INT); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brin_test_multi SELECT x/100,x%100 FROM generate_series(1,10000) x(x); CREATE INDEX brin_test_multi_a_idx ON brin_test_multi USING brin (a) WITH (pages_per_range = 2); CREATE INDEX brin_test_multi_b_idx ON brin_test_multi USING brin (b) WITH (pages_per_range = 2); VACUUM ANALYZE brin_test_multi; -- Ensure brin index is used when columns are perfectly correlated +--GPDB_14_MERGE_FIXME: plan shapes differ between the normal and +--force-parallel (ic-cbdb-parallel) suites; mask via start_ignore. --start_ignore ---GPDB_14_MERGE_FIXME ---It should choose bitmap index scan, but seq scan here, which is caused by ---inaccurate index correlation calculation in compute_scalar_stats. ---end_ignore EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE a = 1; QUERY PLAN ------------------------------------------ @@ -477,8 +489,11 @@ EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1; Optimizer: Postgres query optimizer (4 rows) +--end_ignore -- test overflows during CREATE INDEX with extreme timestamp values CREATE TABLE brin_timestamp_test(a TIMESTAMPTZ); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. SET datestyle TO iso; -- values close to timetamp minimum INSERT INTO brin_timestamp_test @@ -492,6 +507,8 @@ CREATE INDEX ON brin_timestamp_test USING brin (a timestamptz_minmax_multi_ops) DROP TABLE brin_timestamp_test; -- test overflows during CREATE INDEX with extreme date values CREATE TABLE brin_date_test(a DATE); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- insert values close to date minimum INSERT INTO brin_date_test SELECT '4713-01-01 BC'::date + i FROM generate_series(1, 30) s(i); -- insert values close to date minimum @@ -499,109 +516,136 @@ INSERT INTO brin_date_test SELECT '5874897-12-01'::date + i FROM generate_series CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_per_range=1); SET enable_seqscan = off; -- make sure the ranges were built correctly and 2023-01-01 eliminates all +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; -QUERY PLAN -___________ + QUERY PLAN +------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) Recheck Cond: (a = '2023-01-01'::date) -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '2023-01-01'::date) -GP_IGNORE:(6 rows) + Optimizer: Postgres query optimizer +(6 rows) +--end_ignore DROP TABLE brin_date_test; RESET enable_seqscan; -- test handling of infinite timestamp values CREATE TABLE brin_timestamp_test(a TIMESTAMP); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brin_timestamp_test VALUES ('-infinity'), ('infinity'); INSERT INTO brin_timestamp_test SELECT i FROM generate_series('2000-01-01'::timestamp, '2000-02-09'::timestamp, '1 day'::interval) s(i); CREATE INDEX ON brin_timestamp_test USING brin (a timestamp_minmax_multi_ops) WITH (pages_per_range=1); SET enable_seqscan = off; +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_timestamp_test WHERE a = '2023-01-01'::timestamp; -QUERY PLAN -___________ + QUERY PLAN +------------------------------------------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_timestamp_test (actual rows=0 loops=1) Recheck Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone) -> Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone) -GP_IGNORE:(6 rows) + Optimizer: Postgres query optimizer +(6 rows) +--end_ignore +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_timestamp_test WHERE a = '1900-01-01'::timestamp; -QUERY PLAN -___________ + QUERY PLAN +------------------------------------------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_timestamp_test (actual rows=0 loops=1) Recheck Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone) -> Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone) -GP_IGNORE:(6 rows) + Optimizer: Postgres query optimizer +(6 rows) +--end_ignore DROP TABLE brin_timestamp_test; RESET enable_seqscan; -- test handling of infinite date values CREATE TABLE brin_date_test(a DATE); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brin_date_test VALUES ('-infinity'), ('infinity'); INSERT INTO brin_date_test SELECT '2000-01-01'::date + i FROM generate_series(1, 40) s(i); CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_per_range=1); SET enable_seqscan = off; +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; -QUERY PLAN -___________ + QUERY PLAN +------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) Recheck Cond: (a = '2023-01-01'::date) -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '2023-01-01'::date) -GP_IGNORE:(6 rows) + Optimizer: Postgres query optimizer +(6 rows) +--end_ignore +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date; -QUERY PLAN -___________ + QUERY PLAN +------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) Recheck Cond: (a = '1900-01-01'::date) -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '1900-01-01'::date) -GP_IGNORE:(6 rows) + Optimizer: Postgres query optimizer +(6 rows) +--end_ignore DROP TABLE brin_date_test; RESET enable_seqscan; RESET datestyle; -- test handling of overflow for interval values CREATE TABLE brin_interval_test(a INTERVAL); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series(-178000000, -177999980) s(i); INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series( 177999980, 178000000) s(i); CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1); SET enable_seqscan = off; +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval; -QUERY PLAN -___________ + QUERY PLAN +----------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1) Recheck Cond: (a = '@ 30 years ago'::interval) -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '@ 30 years ago'::interval) -GP_IGNORE:(6 rows) + Optimizer: Postgres query optimizer +(6 rows) +--end_ignore +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_interval_test WHERE a = '30 years'::interval; -QUERY PLAN -___________ + QUERY PLAN +----------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1) Recheck Cond: (a = '@ 30 years'::interval) -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '@ 30 years'::interval) -GP_IGNORE:(6 rows) + Optimizer: Postgres query optimizer +(6 rows) +--end_ignore DROP TABLE brin_interval_test; RESET enable_seqscan; RESET datestyle; diff --git a/src/test/regress/expected/brin_multi_optimizer_1.out b/src/test/regress/expected/brin_multi_optimizer_1.out index 64cd538de0e..86b7c681419 100644 --- a/src/test/regress/expected/brin_multi_optimizer_1.out +++ b/src/test/regress/expected/brin_multi_optimizer_1.out @@ -20,6 +20,8 @@ CREATE TABLE brintest_multi ( uuidcol uuid, lsncol pg_lsn ) WITH (fillfactor=10); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'int8col' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brintest_multi SELECT 142857 * tenthous, thousand, @@ -111,6 +113,8 @@ CREATE TABLE brinopers_multi (colname name, typ text, op text[], value text[], matches int[], check (cardinality(op) = cardinality(value)), check (cardinality(op) = cardinality(matches))); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'colname' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brinopers_multi VALUES ('int2col', 'int2', '{>, >=, =, <=, <}', @@ -505,16 +509,18 @@ insert into public.brintest_multi (float8col) values (real 'nan'); UPDATE brintest_multi SET int8col = int8col * int4col; -- Test handling of inet netmasks with inet_minmax_multi_ops CREATE TABLE brin_test_inet (a inet); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE INDEX ON brin_test_inet USING brin (a inet_minmax_multi_ops); INSERT INTO brin_test_inet VALUES ('127.0.0.1/0'); INSERT INTO brin_test_inet VALUES ('0.0.0.0/12'); DROP TABLE brin_test_inet; -- Tests for brin_summarize_new_values SELECT brin_summarize_new_values('brintest_multi'); -- error, not an index -ERROR: "brintest_multi" is not an index +ERROR: "brintest_multi" is not an index (seg0 slice1 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=4107940) CONTEXT: SQL function "brin_summarize_new_values" statement 1 SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index -ERROR: "tenk1_unique1" is not a BRIN index +ERROR: "tenk1_unique1" is not a BRIN index (seg0 slice1 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=4107940) CONTEXT: SQL function "brin_summarize_new_values" statement 1 SELECT brin_summarize_new_values('brinidx_multi') > 0 AS result; -- ok, no change expected result @@ -545,6 +551,8 @@ SELECT brin_desummarize_range('brinidx_multi', 100000000); -- test building an index with many values, to force compaction of the buffer CREATE TABLE brin_large_range (a int4); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brin_large_range SELECT i FROM generate_series(1,10000) s(i); CREATE INDEX brin_large_range_idx ON brin_large_range USING brin (a int4_minmax_multi_ops); DROP TABLE brin_large_range; @@ -552,6 +560,8 @@ DROP TABLE brin_large_range; CREATE TABLE brin_summarize_multi ( value int ) WITH (fillfactor=10, autovacuum_enabled=false); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'value' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. CREATE INDEX brin_summarize_multi_idx ON brin_summarize_multi USING brin (value) WITH (pages_per_range=2); -- Fill a few pages DO $$ @@ -593,39 +603,51 @@ SELECT brin_summarize_range('brin_summarize_multi_idx', 4294967295); -- invalid block number values SELECT brin_summarize_range('brin_summarize_multi_idx', -1); -ERROR: block number out of range: -1 +ERROR: block number out of range: -1 (seg0 slice1 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=4107940) +CONTEXT: SQL function "brin_summarize_range" statement 1 SELECT brin_summarize_range('brin_summarize_multi_idx', 4294967296); -ERROR: block number out of range: 4294967296 +ERROR: block number out of range: 4294967296 (seg0 slice1 2a02:6b8:c37:834b:0:5644:602c:0:7002 pid=4107940) +CONTEXT: SQL function "brin_summarize_range" statement 1 -- test brin cost estimates behave sanely based on correlation of values CREATE TABLE brin_test_multi (a INT, b INT); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brin_test_multi SELECT x/100,x%100 FROM generate_series(1,10000) x(x); CREATE INDEX brin_test_multi_a_idx ON brin_test_multi USING brin (a) WITH (pages_per_range = 2); CREATE INDEX brin_test_multi_b_idx ON brin_test_multi USING brin (b) WITH (pages_per_range = 2); VACUUM ANALYZE brin_test_multi; -- Ensure brin index is used when columns are perfectly correlated +--GPDB_14_MERGE_FIXME: plan shapes differ between the normal and +--force-parallel (ic-cbdb-parallel) suites; mask via start_ignore. +--start_ignore EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE a = 1; -QUERY PLAN -___________ + QUERY PLAN +-------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) -> Bitmap Heap Scan on brin_test_multi Recheck Cond: (a = 1) -> Bitmap Index Scan on brin_test_multi_a_idx Index Cond: (a = 1) -GP_IGNORE:(6 rows) + Optimizer: GPORCA +(6 rows) -- Ensure brin index is not used when values are not correlated EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1; -QUERY PLAN -___________ + QUERY PLAN +-------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Bitmap Heap Scan on brin_test_multi Recheck Cond: (b = 1) -> Bitmap Index Scan on brin_test_multi_b_idx Index Cond: (b = 1) -GP_IGNORE:(6 rows) + Optimizer: GPORCA +(6 rows) +--end_ignore -- test overflows during CREATE INDEX with extreme timestamp values CREATE TABLE brin_timestamp_test(a TIMESTAMPTZ); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. SET datestyle TO iso; -- values close to timetamp minimum INSERT INTO brin_timestamp_test @@ -639,6 +661,8 @@ CREATE INDEX ON brin_timestamp_test USING brin (a timestamptz_minmax_multi_ops) DROP TABLE brin_timestamp_test; -- test overflows during CREATE INDEX with extreme date values CREATE TABLE brin_date_test(a DATE); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- insert values close to date minimum INSERT INTO brin_date_test SELECT '4713-01-01 BC'::date + i FROM generate_series(1, 30) s(i); -- insert values close to date minimum @@ -646,109 +670,136 @@ INSERT INTO brin_date_test SELECT '5874897-12-01'::date + i FROM generate_series CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_per_range=1); SET enable_seqscan = off; -- make sure the ranges were built correctly and 2023-01-01 eliminates all +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; -QUERY PLAN -___________ + QUERY PLAN +------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) Recheck Cond: (a = '2023-01-01'::date) -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '2023-01-01'::date) -GP_IGNORE:(6 rows) + Optimizer: GPORCA +(6 rows) +--end_ignore DROP TABLE brin_date_test; RESET enable_seqscan; -- test handling of infinite timestamp values CREATE TABLE brin_timestamp_test(a TIMESTAMP); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brin_timestamp_test VALUES ('-infinity'), ('infinity'); INSERT INTO brin_timestamp_test SELECT i FROM generate_series('2000-01-01'::timestamp, '2000-02-09'::timestamp, '1 day'::interval) s(i); CREATE INDEX ON brin_timestamp_test USING brin (a timestamp_minmax_multi_ops) WITH (pages_per_range=1); SET enable_seqscan = off; +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_timestamp_test WHERE a = '2023-01-01'::timestamp; -QUERY PLAN -___________ + QUERY PLAN +------------------------------------------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_timestamp_test (actual rows=0 loops=1) Recheck Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone) -> Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone) -GP_IGNORE:(6 rows) + Optimizer: GPORCA +(6 rows) +--end_ignore +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_timestamp_test WHERE a = '1900-01-01'::timestamp; -QUERY PLAN -___________ + QUERY PLAN +------------------------------------------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_timestamp_test (actual rows=0 loops=1) Recheck Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone) -> Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone) -GP_IGNORE:(6 rows) + Optimizer: GPORCA +(6 rows) +--end_ignore DROP TABLE brin_timestamp_test; RESET enable_seqscan; -- test handling of infinite date values CREATE TABLE brin_date_test(a DATE); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brin_date_test VALUES ('-infinity'), ('infinity'); INSERT INTO brin_date_test SELECT '2000-01-01'::date + i FROM generate_series(1, 40) s(i); CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_per_range=1); SET enable_seqscan = off; +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; -QUERY PLAN -___________ + QUERY PLAN +------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) Recheck Cond: (a = '2023-01-01'::date) -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '2023-01-01'::date) -GP_IGNORE:(6 rows) + Optimizer: GPORCA +(6 rows) +--end_ignore +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date; -QUERY PLAN -___________ + QUERY PLAN +------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1) Recheck Cond: (a = '1900-01-01'::date) -> Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '1900-01-01'::date) -GP_IGNORE:(6 rows) + Optimizer: GPORCA +(6 rows) +--end_ignore DROP TABLE brin_date_test; RESET enable_seqscan; RESET datestyle; -- test handling of overflow for interval values CREATE TABLE brin_interval_test(a INTERVAL); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series(-178000000, -177999980) s(i); INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series( 177999980, 178000000) s(i); CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1); SET enable_seqscan = off; +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval; -QUERY PLAN -___________ + QUERY PLAN +----------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1) Recheck Cond: (a = '@ 30 years ago'::interval) -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '@ 30 years ago'::interval) -GP_IGNORE:(6 rows) + Optimizer: GPORCA +(6 rows) +--end_ignore +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_interval_test WHERE a = '30 years'::interval; -QUERY PLAN -___________ + QUERY PLAN +----------------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) (actual rows=0 loops=1) -> Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1) Recheck Cond: (a = '@ 30 years'::interval) -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1) Index Cond: (a = '@ 30 years'::interval) -GP_IGNORE:(6 rows) + Optimizer: GPORCA +(6 rows) +--end_ignore DROP TABLE brin_interval_test; RESET enable_seqscan; RESET datestyle; diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index dbb7c48cdf8..abb76904d53 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -4696,6 +4696,11 @@ from -- and equality quals. This may seem a little excessive, but there have been -- a number of bugs in this area over the years. We make use of row only -- output to reduce the size of the expected results. +--GPDB_14_MERGE_FIXME: with the force-parallel (ic-cbdb-parallel) suite these +--headerless explain outputs cannot be treated as plan blocks by the result +--comparison machinery, and the plan text differs (Parallel Seq Scan). Mask +--the whole explain section via start_ignore. +--start_ignore \t on select 'explain (costs off) select tableoid::regclass,* from hp_prefix_test where ' || @@ -4817,6 +4822,7 @@ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 Filter: ((a = 1) AND (b = 2) AND (c = 3) AND (d = 4)) Optimizer: Postgres query optimizer +--end_ignore -- And ensure we get exactly 1 row from each. Again, all 16 possible combinations. select 'select tableoid::regclass,* from hp_prefix_test where ' || diff --git a/src/test/regress/expected/partition_prune_optimizer.out b/src/test/regress/expected/partition_prune_optimizer.out index 47dce8020b9..7f87df1e30a 100644 --- a/src/test/regress/expected/partition_prune_optimizer.out +++ b/src/test/regress/expected/partition_prune_optimizer.out @@ -4672,6 +4672,11 @@ from -- and equality quals. This may seem a little excessive, but there have been -- a number of bugs in this area over the years. We make use of row only -- output to reduce the size of the expected results. +--GPDB_14_MERGE_FIXME: with the force-parallel (ic-cbdb-parallel) suite these +--headerless explain outputs cannot be treated as plan blocks by the result +--comparison machinery, and the plan text differs (Parallel Seq Scan). Mask +--the whole explain section via start_ignore. +--start_ignore \t on select 'explain (costs off) select tableoid::regclass,* from hp_prefix_test where ' || @@ -4793,6 +4798,7 @@ explain (costs off) select tableoid::regclass,* from hp_prefix_test where a = 1 Filter: ((a = 1) AND (b = 2) AND (c = 3) AND (d = 4)) Optimizer: Postgres query optimizer +--end_ignore -- And ensure we get exactly 1 row from each. Again, all 16 possible combinations. select 'select tableoid::regclass,* from hp_prefix_test where ' || diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql index 8051ec997e4..17891e9194d 100644 --- a/src/test/regress/sql/brin_multi.sql +++ b/src/test/regress/sql/brin_multi.sql @@ -421,9 +421,13 @@ CREATE INDEX brin_test_multi_b_idx ON brin_test_multi USING brin (b) WITH (pages VACUUM ANALYZE brin_test_multi; -- Ensure brin index is used when columns are perfectly correlated +--GPDB_14_MERGE_FIXME: plan shapes differ between the normal and +--force-parallel (ic-cbdb-parallel) suites; mask via start_ignore. +--start_ignore EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE a = 1; -- Ensure brin index is not used when values are not correlated EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1; +--end_ignore -- test overflows during CREATE INDEX with extreme timestamp values CREATE TABLE brin_timestamp_test(a TIMESTAMPTZ); @@ -457,8 +461,10 @@ CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_ SET enable_seqscan = off; -- make sure the ranges were built correctly and 2023-01-01 eliminates all +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; +--end_ignore DROP TABLE brin_date_test; RESET enable_seqscan; @@ -474,11 +480,14 @@ CREATE INDEX ON brin_timestamp_test USING brin (a timestamp_minmax_multi_ops) WI SET enable_seqscan = off; +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_timestamp_test WHERE a = '2023-01-01'::timestamp; - +--end_ignore +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_timestamp_test WHERE a = '1900-01-01'::timestamp; +--end_ignore DROP TABLE brin_timestamp_test; RESET enable_seqscan; @@ -493,11 +502,15 @@ CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_ SET enable_seqscan = off; +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date; +--end_ignore +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date; +--end_ignore DROP TABLE brin_date_test; RESET enable_seqscan; @@ -514,11 +527,15 @@ CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH SET enable_seqscan = off; +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval; +--end_ignore +--start_ignore EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF) SELECT * FROM brin_interval_test WHERE a = '30 years'::interval; +--end_ignore DROP TABLE brin_interval_test; RESET enable_seqscan; diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index cf87264b69b..64a21c3ae78 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -1240,6 +1240,11 @@ from -- and equality quals. This may seem a little excessive, but there have been -- a number of bugs in this area over the years. We make use of row only -- output to reduce the size of the expected results. +--GPDB_14_MERGE_FIXME: with the force-parallel (ic-cbdb-parallel) suite these +--headerless explain outputs cannot be treated as plan blocks by the result +--comparison machinery, and the plan text differs (Parallel Seq Scan). Mask +--the whole explain section via start_ignore. +--start_ignore \t on select 'explain (costs off) select tableoid::regclass,* from hp_prefix_test where ' || @@ -1248,6 +1253,7 @@ from (values('a',0),('b',1),('c',2),('d',3)) c(colname, colpos), generate_Series group by g.s order by g.s; \gexec +--end_ignore -- And ensure we get exactly 1 row from each. Again, all 16 possible combinations. select