Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1h4fz7-0008PV-2Z for pgsql-hackers@arkaria.postgresql.org; Fri, 15 Mar 2019 06:06:25 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1h4fz5-0008Tb-EV for pgsql-hackers@arkaria.postgresql.org; Fri, 15 Mar 2019 06:06:23 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1h4fz4-0008MJ-TZ for pgsql-hackers@lists.postgresql.org; Fri, 15 Mar 2019 06:06:23 +0000 Received: from tama50.ecl.ntt.co.jp ([129.60.39.147]) by makus.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1h4fz0-0005bt-IO for pgsql-hackers@lists.postgresql.org; Fri, 15 Mar 2019 06:06:21 +0000 Received: from vc1.ecl.ntt.co.jp (vc1.ecl.ntt.co.jp [129.60.86.153]) by tama50.ecl.ntt.co.jp (8.13.8/8.13.8) with ESMTP id x2F66Bbm020395; Fri, 15 Mar 2019 15:06:11 +0900 Received: from vc1.ecl.ntt.co.jp (localhost [127.0.0.1]) by vc1.ecl.ntt.co.jp (Postfix) with ESMTP id 1DA29EA78CD; Fri, 15 Mar 2019 15:06:11 +0900 (JST) Received: from jcms-pop11.ecl.ntt.co.jp (jcms-pop11.ecl.ntt.co.jp [129.60.87.132]) by vc1.ecl.ntt.co.jp (Postfix) with ESMTP id 1269CEA770E; Fri, 15 Mar 2019 15:06:11 +0900 (JST) Received: from hosoyahp (unknown [129.60.241.74]) by jcms-pop11.ecl.ntt.co.jp (Postfix) with ESMTPSA id EA15870C099C; Fri, 15 Mar 2019 15:06:10 +0900 (JST) From: "Yuzuko Hosoya" References: <00e601d4ca86$932b8bc0$b982a340$@lab.ntt.co.jp> <010e01d4ce68$c0427b10$40c77130$@lab.ntt.co.jp> <0F97FA9ABBDBE54F91744A9B37151A5129A78A@g01jpexmbkw24> In-Reply-To: Subject: RE: Problem with default partition pruning Date: Fri, 15 Mar 2019 15:05:41 +0900 Message-ID: <001901d4daf5$1ef4f640$5cdee2c0$@lab.ntt.co.jp> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_NextPart_000_001A_01D4DB40.8EDD6190" X-Mailer: Microsoft Outlook 15.0 Content-language: ja Thread-index: AQLwSThp7L0tC61luGAd4dJt3HxY3QJvrgn9AgtGZCUBpM85VAEOZ04Co5vcj5A= X-CC-Mail-RelayStamp: 1 To: "'Thibaut'" , "'Imai, Yoshikazu'" Cc: "'Amit Langote'" , "'PostgreSQL Hackers'" X-TM-AS-MML: disable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk This is a multipart message in MIME format. ------=_NextPart_000_001A_01D4DB40.8EDD6190 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi Thibaut, Thanks a lot for your test and comments. >=20 > Le 28/02/2019 =C3=A0 09:26, Imai, Yoshikazu a =C3=A9crit : > > Hosoya-san > > > > On Wed, Feb 27, 2019 at 6:51 AM, Yuzuko Hosoya wrote: > >>> From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp] > >>> Sent: Wednesday, February 27, 2019 11:22 AM > >>> > >>> Hosoya-san, > >>> > >>> On 2019/02/22 17:14, Yuzuko Hosoya wrote: > >>>> Hi, > >>>> > >>>> I found the bug of default partition pruning when executing a = range > >> query. > >>>> ----- > >>>> postgres=3D# create table test1(id int, val text) partition by = range > >>>> (id); postgres=3D# create table test1_1 partition of test1 for = values > >>>> from (0) to (100); postgres=3D# create table test1_2 partition of > >>>> test1 for values from (150) to (200); postgres=3D# create table > >>>> test1_def partition of test1 default; > >>>> > >>>> postgres=3D# explain select * from test1 where id > 0 and id < = 30; > >>>> QUERY PLAN > >>>> ---------------------------------------------------------------- > >>>> Append (cost=3D0.00..11.83 rows=3D59 width=3D11) > >>>> -> Seq Scan on test1_1 (cost=3D0.00..5.00 rows=3D58 = width=3D11) > >>>> Filter: ((id > 0) AND (id < 30)) > >>>> -> Seq Scan on test1_def (cost=3D0.00..6.53 rows=3D1 = width=3D12) > >>>> Filter: ((id > 0) AND (id < 30)) > >>>> (5 rows) > >>>> > >>>> There is no need to scan the default partition, but it's scanned. > >>>> ----- > >>>> > >>>> In the current implement, whether the default partition is = scanned > >>>> or not is determined according to each condition of given WHERE > >>>> clause at get_matching_range_bounds(). In this example, > >>>> scan_default is set true according to id > 0 because id >=3D 200 > >>>> matches the default partition. Similarly, according to id < 30, > >> scan_default is set true. > >>>> Then, these results are combined according to AND/OR at > >> perform_pruning_combine_step(). > >>>> In this case, final result's scan_default is set true. > >>>> > >>>> The modifications I made are as follows: > >>>> - get_matching_range_bounds() determines only offsets of range = bounds > >>>> according to each condition > >>>> - These results are combined at perform_pruning_combine_step() > >>>> - Whether the default partition is scanned or not is determined = at > >>>> get_matching_partitions() > >>>> > >>>> Attached the patch. Any feedback is greatly appreciated. > >>> Thank you for reporting. Can you please add this to March CF in > >>> Bugs category so as not to lose > >> track > >>> of this? > >>> > >>> I will try to send review comments soon. > >>> > >> Thank you for your reply. I added this to March CF. > > I tested with simple use case and I confirmed it works correctly = like below. > > > > In case using between clause: > > postgres=3D# create table test1(id int, val text) partition by range > > (id); postgres=3D# create table test1_1 partition of test1 for = values > > from (0) to (100); postgres=3D# create table test1_2 partition of = test1 > > for values from (150) to (200); postgres=3D# create table test1_def > > partition of test1 default; > > > > [HEAD] > > postgres=3D# explain analyze select * from test1 where id between 0 = and 50; > > QUERY PLAN > > = ---------------------------------------------------------------------- > > ------------------------------------- > > Append (cost=3D0.00..58.16 rows=3D12 width=3D36) (actual = time=3D0.008..0.008 rows=3D0 loops=3D1) > > -> Seq Scan on test1_1 (cost=3D0.00..29.05 rows=3D6 width=3D36) = (actual time=3D0.005..0.005 > rows=3D0 loops=3D1) > > Filter: ((id >=3D 0) AND (id <=3D 50)) > > -> Seq Scan on test1_def (cost=3D0.00..29.05 rows=3D6 = width=3D36) (actual > time=3D0.002..0.002 rows=3D0 loops=3D1) > > Filter: ((id >=3D 0) AND (id <=3D 50)) > > > > > > [patched] > > postgres=3D# explain analyze select * from test1 where id between 0 = and 50; > > QUERY PLAN > > = ---------------------------------------------------------------------- > > ----------------------------------- > > Append (cost=3D0.00..29.08 rows=3D6 width=3D36) (actual = time=3D0.006..0.006 rows=3D0 loops=3D1) > > -> Seq Scan on test1_1 (cost=3D0.00..29.05 rows=3D6 width=3D36) = (actual time=3D0.004..0.005 > rows=3D0 loops=3D1) > > Filter: ((id >=3D 0) AND (id <=3D 50)) > > > > > > > > I considered about another use case. If default partition contains = rows whose id =3D 300 > and then we add another partition which have constraints like id >=3D = 300 and id < 400, I thought > we won't scan the rows anymore. But I noticed we simply can't add such = a partition. > > > > postgres=3D# insert into test1 values (300); INSERT 0 1 postgres=3D# > > create table test1_3 partition of test1 for values from (300) to > > (400); > > ERROR: updated partition constraint for default partition = "test1_def" > > would be violated by some row > > > > > > So I haven't come up with bad cases so far :) > > > > -- > > Yoshikazu Imai >=20 > Hello Yoshikazu-San, >=20 > I tested your patch using some sub-partitions and found a possible = problem. >=20 > I create a new partitioned partition test1_3 with 2 sub-partitions : >=20 > ------------------------- >=20 > create table test1_3 partition of test1 for values from (200) to (400) = partition by range > (id); create table test1_3_1 partition of test1_3 for values from = (200) to (250); create > table test1_3_2 partition of test1_3 for values from (250) to (350); >=20 > # explain select * from test1 where (id > 0 and id < 30); > QUERY PLAN > --------------------------------------------------------------- > Append (cost=3D0.00..29.08 rows=3D6 width=3D36) > -> Seq Scan on test1_1 (cost=3D0.00..29.05 rows=3D6 width=3D36) > Filter: ((id > 0) AND (id < 30)) > (3 rows) >=20 > # explain select * from test1 where (id > 220 and id < 230); > QUERY PLAN > ----------------------------------------------------------------- > Append (cost=3D0.00..29.08 rows=3D6 width=3D36) > -> Seq Scan on test1_3_1 (cost=3D0.00..29.05 rows=3D6 width=3D36) > Filter: ((id > 220) AND (id < 230)) > (3 rows) >=20 > # explain select * from test1 > where (id > 0 and id < 30) or (id > 220 and id < 230); > QUERY PLAN > = -------------------------------------------------------------------------= -- > Append (cost=3D0.00..106.40 rows=3D39 width=3D36) > -> Seq Scan on test1_1 (cost=3D0.00..35.40 rows=3D13 width=3D36) > Filter: (((id > 0) AND (id < 30)) OR ((id > 220) AND (id < = 230))) > -> Seq Scan on test1_3_1 (cost=3D0.00..35.40 rows=3D13 = width=3D36) > Filter: (((id > 0) AND (id < 30)) OR ((id > 220) AND (id < = 230))) > -> Seq Scan on test1_3_2 (cost=3D0.00..35.40 rows=3D13 = width=3D36) > Filter: (((id > 0) AND (id < 30)) OR ((id > 220) AND (id < = 230))) > (7 rows) >=20 > ----------------- >=20 > Partition pruning is functioning when only the sub-partition is = required. When both the > partition and the sub-partition is required, there is no pruning on = the sub-partition. >=20 Indeed, it's problematic. I also did test and I found that=20 this problem was occurred when any partition didn't match=20 WHERE clauses. So following query didn't work correctly. # explain select * from test1_3 where (id > 0 and id < 30); =20 QUERY PLAN =20 ----------------------------------------------------------------- Append (cost=3D0.00..58.16 rows=3D12 width=3D36) -> Seq Scan on test1_3_1 (cost=3D0.00..29.05 rows=3D6 width=3D36) Filter: ((id > 0) AND (id < 30)) -> Seq Scan on test1_3_2 (cost=3D0.00..29.05 rows=3D6 width=3D36) Filter: ((id > 0) AND (id < 30)) (5 rows) I created a new patch to handle this problem, and confirmed the query you mentioned works as expected # explain select * from test1 where (id > 0 and id < 30) or (id > 220 = and id < 230); QUERY PLAN = =20 -------------------------------------------------------------------------= -- Append (cost=3D0.00..70.93 rows=3D26 width=3D36) -> Seq Scan on test1_1_1 (cost=3D0.00..35.40 rows=3D13 width=3D36) Filter: (((id > 0) AND (id < 30)) OR ((id > 220) AND (id < = 230))) -> Seq Scan on test1_3_1 (cost=3D0.00..35.40 rows=3D13 width=3D36) Filter: (((id > 0) AND (id < 30)) OR ((id > 220) AND (id < = 230))) (5 rows) v2 patch attached. Could you please check it again? -- Best regards, Yuzuko Hosoya ------=_NextPart_000_001A_01D4DB40.8EDD6190 Content-Type: application/octet-stream; name="v2_default_partition_pruning.patch" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="v2_default_partition_pruning.patch" diff --git a/src/backend/partitioning/partprune.c = b/src/backend/partitioning/partprune.c=0A= index b5c0889..302061e 100644=0A= --- a/src/backend/partitioning/partprune.c=0A= +++ b/src/backend/partitioning/partprune.c=0A= @@ -619,6 +619,7 @@ get_matching_partitions(PartitionPruneContext = *context, List *pruning_steps)=0A= PruneStepResult **results,=0A= *final_result;=0A= ListCell *lc;=0A= + bool scan_default;=0A= =0A= /* If there are no pruning steps then all partitions match. */=0A= if (num_steps =3D=3D 0)=0A= @@ -670,20 +671,23 @@ get_matching_partitions(PartitionPruneContext = *context, List *pruning_steps)=0A= Assert(final_result !=3D NULL);=0A= i =3D -1;=0A= result =3D NULL;=0A= + scan_default =3D final_result->scan_default;=0A= while ((i =3D bms_next_member(final_result->bound_offsets, i)) >=3D 0)=0A= {=0A= int partindex =3D context->boundinfo->indexes[i];=0A= =0A= /*=0A= - * In range and hash partitioning cases, some slots may contain -1,=0A= + * In range and hash partitioning cases, some index values may be -1,=0A= * indicating that no partition has been defined to accept a given=0A= - * range of data or for a given remainder, respectively. The default=0A= - * partition, if any, in case of range partitioning, will be added to=0A= - * the result, because the specified range still satisfies the query's=0A= - * conditions.=0A= + * range of values (that is, the bound at given offset is the upper=0A= + * bound of this unassigned range of values) or for a given remainder,=0A= + * respectively. As it's still part of the queried range of values,=0A= + * add the default partition, if any.=0A= */=0A= if (partindex >=3D 0)=0A= result =3D bms_add_member(result, partindex);=0A= + else=0A= + scan_default |=3D partition_bound_has_default(context->boundinfo);=0A= }=0A= =0A= /* Add the null and/or default partition if needed and if present. */=0A= @@ -693,7 +697,7 @@ get_matching_partitions(PartitionPruneContext = *context, List *pruning_steps)=0A= Assert(partition_bound_accepts_nulls(context->boundinfo));=0A= result =3D bms_add_member(result, context->boundinfo->null_index);=0A= }=0A= - if (final_result->scan_default)=0A= + if (scan_default)=0A= {=0A= Assert(context->strategy =3D=3D PARTITION_STRATEGY_LIST ||=0A= context->strategy =3D=3D PARTITION_STRATEGY_RANGE);=0A= @@ -2207,6 +2211,11 @@ get_matching_hash_bounds(PartitionPruneContext = *context,=0A= * get_matching_list_bounds=0A= * Determine the offsets of list bounds matching the specified value,=0A= * according to the semantics of the given operator strategy=0A= + *=0A= + * scan_default will be set in the returned struct, if the default = partition=0A= + * needs to be scanned, provided one exists at all. scan_null will be = set if=0A= + * the special null-accepting partition needs to be scanned.=0A= + *=0A= * 'opstrategy' if non-zero must be a btree strategy number.=0A= *=0A= * 'value' contains the value to use for pruning.=0A= @@ -2409,8 +2418,17 @@ get_matching_list_bounds(PartitionPruneContext = *context,=0A= * Each datum whose offset is in result is to be treated as the upper = bound of=0A= * the partition that will contain the desired values.=0A= *=0A= - * If default partition needs to be scanned for given values, set = scan_default=0A= - * in result if present.=0A= + * scan_default will be set in the returned struct, if the default = partition=0A= + * needs to be scanned, provided one exists at all. Although note that = we=0A= + * intentionally don't set scan_default in this function if only = because the=0A= + * matching set of values, found by comparing the input values using the=0A= + * specified opstrategy, contains unassigned portions of key space, = which=0A= + * we normally assume to belong to the default partition. We don't = infer=0A= + * that until all steps have been executed, including any combination = steps,=0A= + * because even if such unassinged portion of key space appears to be = part of=0A= + * the set of queried values based on comparisons in the individual = OpExprs,=0A= + * the final set of bounds obtained after combining multiple OpExprs may=0A= + * exclude it.=0A= *=0A= * 'opstrategy' if non-zero must be a btree strategy number.=0A= *=0A= @@ -2436,8 +2454,7 @@ get_matching_range_bounds(PartitionPruneContext = *context,=0A= int *partindices =3D boundinfo->indexes;=0A= int off,=0A= minoff,=0A= - maxoff,=0A= - i;=0A= + maxoff;=0A= bool is_equal;=0A= bool inclusive =3D false;=0A= =0A= @@ -2501,11 +2518,7 @@ get_matching_range_bounds(PartitionPruneContext = *context,=0A= if (nvalues =3D=3D partnatts)=0A= {=0A= /* There can only be zero or one matching partition. */=0A= - if (partindices[off + 1] >=3D 0)=0A= - result->bound_offsets =3D bms_make_singleton(off + 1);=0A= - else=0A= - result->scan_default =3D=0A= - partition_bound_has_default(boundinfo);=0A= + result->bound_offsets =3D bms_make_singleton(off + 1);=0A= return result;=0A= }=0A= else=0A= @@ -2593,57 +2606,21 @@ get_matching_range_bounds(PartitionPruneContext = *context,=0A= maxoff =3D off + 1;=0A= }=0A= =0A= - /*=0A= - * Skip if minoff/maxoff are actually the upper bound of a=0A= - * un-assigned portion of values.=0A= - */=0A= - if (partindices[minoff] < 0 && minoff < boundinfo->ndatums)=0A= - minoff++;=0A= - if (partindices[maxoff] < 0 && maxoff >=3D 1)=0A= - maxoff--;=0A= -=0A= - /*=0A= - * There may exist a range of values unassigned to any=0A= - * non-default partition between the datums at minoff and=0A= - * maxoff. Add the default partition in that case.=0A= - */=0A= - if (partition_bound_has_default(boundinfo))=0A= - {=0A= - for (i =3D minoff; i <=3D maxoff; i++)=0A= - {=0A= - if (partindices[i] < 0)=0A= - {=0A= - result->scan_default =3D true;=0A= - break;=0A= - }=0A= - }=0A= - }=0A= -=0A= Assert(minoff >=3D 0 && maxoff >=3D 0);=0A= result->bound_offsets =3D bms_add_range(NULL, minoff, maxoff);=0A= }=0A= - else if (off >=3D 0) /* !is_equal */=0A= + else=0A= {=0A= /*=0A= * The lookup value falls in the range between some bounds in=0A= * boundinfo. 'off' would be the offset of the greatest bound=0A= * that is <=3D lookup value, so add off + 1 to the result=0A= * instead as the offset of the upper bound of the only=0A= - * partition that may contain the lookup value.=0A= + * partition that may contain the lookup value. If 'off' is=0A= + * -1 indicating that all bounds are greater, then we simply=0A= + * end up adding the first bound's offset, that is, 0.=0A= */=0A= - if (partindices[off + 1] >=3D 0)=0A= - result->bound_offsets =3D bms_make_singleton(off + 1);=0A= - else=0A= - result->scan_default =3D=0A= - partition_bound_has_default(boundinfo);=0A= - }=0A= - else=0A= - {=0A= - /*=0A= - * off < 0: the lookup value is smaller than all bounds, so=0A= - * only the default partition qualifies, if there is one.=0A= - */=0A= - result->scan_default =3D partition_bound_has_default(boundinfo);=0A= + result->bound_offsets =3D bms_make_singleton(off + 1);=0A= }=0A= =0A= return result;=0A= @@ -2742,15 +2719,15 @@ get_matching_range_bounds(PartitionPruneContext = *context,=0A= nvalues, values,=0A= &is_equal);=0A= if (off < 0)=0A= - {=0A= - /*=0A= - * All bounds are greater than the key, so we could only=0A= - * expect to find the lookup key in the default partition.=0A= - */=0A= - result->scan_default =3D partition_bound_has_default(boundinfo);=0A= - return result;=0A= - }=0A= - else=0A= + {=0A= + /* = =0A= + * All bounds are greater than the key, so we could = only =0A= + * expect to find the lookup key in the default = partition. =0A= + */=0A= + result->scan_default =3D = partition_bound_has_default(boundinfo);=0A= + return result;=0A= + }=0A= + else=0A= {=0A= /*=0A= * See the comment above.=0A= @@ -2791,7 +2768,9 @@ get_matching_range_bounds(PartitionPruneContext = *context,=0A= * instead as the offset of the upper bound of the greatest=0A= * partition that may contain lookup value. If the lookup=0A= * value had exactly matched the bound, but it isn't=0A= - * inclusive, no need add the adjacent partition.=0A= + * inclusive, no need add the adjacent partition. If 'off' is=0A= + * -1 indicating that all bounds are greater, then we simply=0A= + * end up adding the first bound's offset, that is, 0.=0A= */=0A= else if (!is_equal || inclusive)=0A= maxoff =3D off + 1;=0A= @@ -2805,58 +2784,43 @@ get_matching_range_bounds(PartitionPruneContext = *context,=0A= break;=0A= }=0A= =0A= + Assert(minoff >=3D 0 && minoff <=3D boundinfo->ndatums);=0A= + Assert(maxoff >=3D 0 && maxoff <=3D boundinfo->ndatums);=0A= +=0A= /*=0A= - * Skip a gap and when doing so, check if the bound contains a finite=0A= - * value to decide if we need to add the default partition. If it's an=0A= - * infinite bound, we need not add the default partition, as having an=0A= - * infinite bound means the partition in question catches any values = that=0A= - * would otherwise be in the default partition.=0A= + * If the smallest partition to return has MINVALUE (negative = infinity) as=0A= + * its lower bound, increment it to point to the next finite bound=0A= + * (supposedly its upper bound), so that we don't advertently end up=0A= + * scanning the default partition.=0A= */=0A= - if (partindices[minoff] < 0)=0A= + if (minoff < boundinfo->ndatums && partindices[minoff] < 0)=0A= {=0A= int lastkey =3D nvalues - 1;=0A= =0A= - if (minoff >=3D 0 &&=0A= - minoff < boundinfo->ndatums &&=0A= - boundinfo->kind[minoff][lastkey] =3D=3D=0A= - PARTITION_RANGE_DATUM_VALUE)=0A= - result->scan_default =3D partition_bound_has_default(boundinfo);=0A= -=0A= - minoff++;=0A= + if (boundinfo->kind[minoff][lastkey] =3D=3D=0A= + PARTITION_RANGE_DATUM_MINVALUE)=0A= + {=0A= + minoff++;=0A= + Assert(boundinfo->indexes[minoff] >=3D 0);=0A= + }=0A= }=0A= =0A= /*=0A= - * Skip a gap. See the above comment about how we decide whether or = not=0A= - * to scan the default partition based whether the datum that will = become=0A= - * the maximum datum is finite or not.=0A= + * If the previous greatest partition has MAXVALUE (positive infinity) = as=0A= + * its upper bound (something only possible to do with multi-column = range=0A= + * partitioning), we scan switch to it as the greatest partition to=0A= + * return. Again, so that we don't advertently end up scanning the=0A= + * default partition.=0A= */=0A= if (maxoff >=3D 1 && partindices[maxoff] < 0)=0A= {=0A= int lastkey =3D nvalues - 1;=0A= =0A= - if (maxoff >=3D 0 &&=0A= - maxoff <=3D boundinfo->ndatums &&=0A= - boundinfo->kind[maxoff - 1][lastkey] =3D=3D=0A= - PARTITION_RANGE_DATUM_VALUE)=0A= - result->scan_default =3D partition_bound_has_default(boundinfo);=0A= -=0A= - maxoff--;=0A= - }=0A= -=0A= - if (partition_bound_has_default(boundinfo))=0A= - {=0A= - /*=0A= - * There may exist a range of values unassigned to any non-default=0A= - * partition between the datums at minoff and maxoff. Add the default=0A= - * partition in that case.=0A= - */=0A= - for (i =3D minoff; i <=3D maxoff; i++)=0A= + if (boundinfo->kind[maxoff - 1][lastkey] =3D=3D=0A= + PARTITION_RANGE_DATUM_MAXVALUE)=0A= {=0A= - if (partindices[i] < 0)=0A= - {=0A= - result->scan_default =3D true;=0A= - break;=0A= - }=0A= + maxoff--;=0A= + Assert(boundinfo->indexes[maxoff] >=3D 0);=0A= }=0A= }=0A= =0A= @@ -3136,14 +3100,23 @@ = perform_pruning_combine_step(PartitionPruneContext *context,=0A= =0A= /*=0A= * A combine step without any source steps is an indication to not = perform=0A= - * any partition pruning, we just return all partitions.=0A= + * any partition pruning, we just return all datum offsets.=0A= */=0A= result =3D (PruneStepResult *) palloc0(sizeof(PruneStepResult));=0A= if (list_length(cstep->source_stepids) =3D=3D 0)=0A= {=0A= PartitionBoundInfo boundinfo =3D context->boundinfo;=0A= =0A= - result->bound_offsets =3D bms_add_range(NULL, 0, boundinfo->ndatums - = 1);=0A= + /*=0A= + * Add all valid offsets into the boundinfo->indexes array. For range=0A= + * partitioning, boundinfo->indexes contains (boundinfo->ndatums + 1)=0A= + * valid entries.=0A= + */=0A= + if (context->strategy =3D=3D PARTITION_STRATEGY_RANGE)=0A= + result->bound_offsets =3D bms_add_range(NULL, 0, boundinfo->ndatums);=0A= + else=0A= + result->bound_offsets =3D bms_add_range(NULL, 0,=0A= + boundinfo->ndatums - 1);=0A= result->scan_default =3D partition_bound_has_default(boundinfo);=0A= result->scan_null =3D partition_bound_accepts_nulls(boundinfo);=0A= return result;=0A= diff --git a/src/test/regress/expected/partition_prune.out = b/src/test/regress/expected/partition_prune.out=0A= index 30946f7..046e722 100644=0A= --- a/src/test/regress/expected/partition_prune.out=0A= +++ b/src/test/regress/expected/partition_prune.out=0A= @@ -517,15 +517,13 @@ explain (costs off) select * from rlp where a <=3D = 31;=0A= Filter: (a <=3D 31)=0A= -> Seq Scan on rlp5_1=0A= Filter: (a <=3D 31)=0A= - -> Seq Scan on rlp5_default=0A= - Filter: (a <=3D 31)=0A= -> Seq Scan on rlp_default_10=0A= Filter: (a <=3D 31)=0A= -> Seq Scan on rlp_default_30=0A= Filter: (a <=3D 31)=0A= -> Seq Scan on rlp_default_default=0A= Filter: (a <=3D 31)=0A= -(29 rows)=0A= +(27 rows)=0A= =0A= explain (costs off) select * from rlp where a =3D 1 or a =3D 7;=0A= QUERY PLAN =0A= @@ -573,11 +571,7 @@ explain (costs off) select * from rlp where a > 20 = and a < 27;=0A= Filter: ((a > 20) AND (a < 27))=0A= -> Seq Scan on rlp4_2=0A= Filter: ((a > 20) AND (a < 27))=0A= - -> Seq Scan on rlp4_default=0A= - Filter: ((a > 20) AND (a < 27))=0A= - -> Seq Scan on rlp_default_default=0A= - Filter: ((a > 20) AND (a < 27))=0A= -(9 rows)=0A= +(5 rows)=0A= =0A= explain (costs off) select * from rlp where a =3D 29;=0A= QUERY PLAN =0A= ------=_NextPart_000_001A_01D4DB40.8EDD6190--