Re: Multi-Column List Partitioning

From: Amul Sul <sulamul(at)gmail(dot)com>
To: Nitin Jadhav <nitinjadhavpostgres(at)gmail(dot)com>
Cc: Amit Langote <amitlangote09(at)gmail(dot)com>, Ashutosh Sharma <ashu(dot)coek88(at)gmail(dot)com>, Rajkumar Raghuwanshi <rajkumar(dot)raghuwanshi(at)enterprisedb(dot)com>, Zhihong Yu <zyu(at)yugabyte(dot)com>, Pg Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: Multi-Column List Partitioning
Date: 2021-12-23 13:02:44
Message-ID: CAAJ_b97fTnKngnnskZ4rBar4yczHLVjvYoxEQ7THw7asT7MaUQ@mail.gmail.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Tue, Dec 21, 2021 at 6:34 PM Nitin Jadhav
<nitinjadhavpostgres(at)gmail(dot)com> wrote:
>
> ---
>
> > + if (isnulls && isnulls[i])
> > + cmpval = 0; /* NULL "=" NULL */
> > + else
> > + cmpval = 1; /* NULL ">" not-NULL */
> > + }
> > + else if (isnulls && isnulls[i])
> > + cmpval = -1; /* not-NULL "<" NULL */
> >
> > I really doubt this assumption is correct; aren't those strict operators?
>
> Now there are possibilities of multiple NULL values. We should have a
> mechanism to sort it when the bound values contain Non NULL and NULL
> values. As per the above logic we put the NULL values at the end.
> Please let me know if I am wrong.

Ok, but I am not sure about the comparison approach, let's see what
others think.

> ---
[...]
>
> > typedef struct PartitionBoundInfoData
> > {
> > char strategy; /* hash, list or range? */
> > + int partnatts; /* number of partition key columns */
> > int ndatums; /* Length of the datums[] array */
> > Datum **datums;
> > + bool **isnulls;
> >
> > Adding "partnatts" to this struct seems to be unnecessary, AFAIUC,
> > added that for partition_bound_accepts_nulls(), but we can easily get
> > that value from the partitioning key & pass an additional argument.
> > Also, no information about the length of the "isnulls" array.
>
> This is required during merge_list_bounds(). AFAIK partition key
> information is not available here.
>

You can get that as an argument, see merge_range_bounds().

> > I think it would be helpful if you could split the patch: one for
> > multi-value list partitioning and another for the partition wise join, thanks.
>
> I have split the patch into 2 patches. One is for the multi column
> list partitioning core changes and the other is for partition-wise
> join support. Each patch has its respective test cases in the
> regression suit and regression tests run successfully on each patch.
> Kindly let me know if any other changes are required here.
>

Thanks, for the slit that is much helpful, I have a few comments for
the 0001 patch as follow:

+ char **colname = (char **) palloc0(partnatts * sizeof(char *));

palloc0 is unnecessary.
---

+ foreach(cell2, rowexpr->args)
+ {
+ int idx = foreach_current_index(cell2);
+ Node *expr = lfirst(cell2);
+ Const *val =
+ transformPartitionBoundValue(pstate, expr, colname[i],
+ get_partition_col_typid(key, idx),
+ get_partition_col_typmod(key, idx),
+ get_partition_col_collation(key, idx));
+
+ values = lappend(values, val);
+ }

Array index for colname should be "idx".
---

result->scan_default = partition_bound_has_default(boundinfo);
+
return result;
...

/* Always include the default partition if any. */
result->scan_default = partition_bound_has_default(boundinfo);
-
return result;

...
else
result->scan_default = partition_bound_has_default(boundinfo);
+
return result;
...

- /* Add columns specified to SET NULL or SET DEFAULT if
provided. */
+ /*
+ * Add columns specified to SET NULL or SET DEFAULT if
+ * provided.
+ */

spurious change -- look like something not related to your patch.
--

- * For range partitioning, we must only perform pruning with values
- * for either all partition keys or a prefix thereof.
+ * For range partitioning and list partitioning, we must only perform
+ * pruning with values for either all partition keys or a prefix
+ * thereof.
*/
- if (keyno > nvalues && context->strategy == PARTITION_STRATEGY_RANGE)
+ if (keyno > nvalues && (context->strategy == PARTITION_STRATEGY_RANGE ||
+ context->strategy == PARTITION_STRATEGY_LIST))
break;

I think this is not true for multi-value list partitions, we might
still want prune partitions for e.g. (100, IS NULL, 20). Correct me
if I am missing something here.
---

/*
- * For range partitioning, if we have no clauses for the current key,
- * we can't consider any later keys either, so we can stop here.
+ * For range partitioning and list partitioning, if we have no clauses
+ * for the current key, we can't consider any later keys either, so we
+ * can stop here.
*/
- if (part_scheme->strategy == PARTITION_STRATEGY_RANGE &&
+ if ((part_scheme->strategy == PARTITION_STRATEGY_RANGE ||
+ part_scheme->strategy == PARTITION_STRATEGY_LIST) &&
clauselist == NIL)
break

Similarly, why would this be true for list partitioning? How can we
prune partitions if values is for e.g. (100, <not given>, 20).
--

- if (bms_is_member(keyno, opstep->nullkeys))
+ if (bms_is_member(keyno, opstep->nullkeys) &&
+ context->strategy != PARTITION_STRATEGY_LIST)
continue;
Will that prune for all NULL partitioning key values?
---

+ appendStringInfoString
+ (buf,
get_list_partbound_value_string(lfirst(cell)));

Formatting is not quite right.
--

+/*
+ * get_min_and_max_offset
+ *
+ * Fetches the minimum and maximum offset of the matching partitions.
+ */

...

+/*
+ * get_min_or_max_off
+ *
+ * Fetches either minimum or maximum offset of the matching partitions
+ * depending on the value of is_min parameter.
+ */

I am not sure we really have to have separate functions but if needed
then I would prefer to have a separate function for each min and max
rather than combining.
---

+ if (part_scheme->strategy != PARTITION_STRATEGY_LIST)
+ {
+ *clause_is_not_null = (nulltest->nulltesttype == IS_NOT_NULL);
+ return PARTCLAUSE_MATCH_NULLNESS;
+ }
+
+ expr = makeConst(UNKNOWNOID, -1, InvalidOid, -2, (Datum) 0,
true, false);
+ partclause = (PartClauseInfo *) palloc(sizeof(PartClauseInfo));
+
+ partclause->keyno = partkeyidx;
+ partclause->expr = (Expr *) expr;
+ partclause->is_null = true;
+
+ if (nulltest->nulltesttype == IS_NOT_NULL)
+ {
+ partclause->op_is_ne = true;
+ partclause->op_strategy = InvalidStrategy;
+ }
+ else
+ {
+ partclause->op_is_ne = false;
+ partclause->op_strategy = BTEqualStrategyNumber;
+ }

- return PARTCLAUSE_MATCH_NULLNESS;
+ *pc = partclause;
+ return PARTCLAUSE_MATCH_CLAUSE;

I still believe considering NULL value for match clause is not a
fundamentally correct thing. And that is only for List partitioning
which isn't aligned with the other partitioning.
---

Regards,
Amul

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Fabien COELHO 2021-12-23 13:14:18 Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
Previous Message Bharath Rupireddy 2021-12-23 12:58:46 pg_archivecleanup - add the ability to detect, archive and delete the unneeded wal files on the primary