diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index d9ba87a..e85adc5 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -16422,13 +16422,6 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy) errmsg("unrecognized partitioning strategy \"%s\"", partspec->strategy))); - /* Check valid number of columns for strategy */ - if (*strategy == PARTITION_STRATEGY_LIST && - list_length(partspec->partParams) != 1) - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("cannot use \"list\" partition strategy with more than one column"))); - /* * Create a dummy ParseState and insert the target relation as its sole * rangetable entry. We need a ParseState for transformExpr. diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 8afddca..bbe97b1 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -1272,7 +1272,8 @@ get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull) bound_offset = partition_list_bsearch(key->partsupfunc, key->partcollation, boundinfo, - values[0], &equal); + key->partnatts, + values, &equal); if (bound_offset >= 0 && equal) part_index = boundinfo->indexes[bound_offset]; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index b4ab401..466801a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -607,6 +607,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type part_params %type PartitionBoundSpec %type hash_partbound +%type list_values %type hash_partbound_elem %type optColumnCompression @@ -2880,13 +2881,13 @@ PartitionBoundSpec: } /* a LIST partition */ - | FOR VALUES IN_P '(' expr_list ')' + | FOR VALUES IN_P list_values { PartitionBoundSpec *n = makeNode(PartitionBoundSpec); n->strategy = PARTITION_STRATEGY_LIST; n->is_default = false; - n->listdatums = $5; + n->listdatums = $4; n->location = @3; $$ = n; @@ -2936,6 +2937,17 @@ hash_partbound: } ; +list_values: + '(' expr_list ')' + { + $$ = list_make1($2); + } + | list_values ',' '(' expr_list ')' + { + $$ = lappend($1, $4); + } + ; + /***************************************************************************** * * ALTER TYPE diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 9dd3037..14956bc 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -146,6 +146,11 @@ static void validateInfiniteBounds(ParseState *pstate, List *blist); static Const *transformPartitionBoundValue(ParseState *pstate, Node *con, const char *colName, Oid colType, int32 colTypmod, Oid partCollation); +static List *transformPartitionListBounds(ParseState *pstate, + PartitionBoundSpec *spec, + char *colname, Oid coltype, + int32 coltypmod, Oid partcollation, + int partnatts); /* @@ -4015,6 +4020,41 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd) } /* + * checkForDuplicates + * + * Return TRUE if the element is already present in the list. + * FALSE otherwise. + */ +static bool +checkForDuplicates(List *source, List *searchElem) +{ + ListCell *cell; + + foreach(cell, source) + { + int i; + List *elem = lfirst(cell); + bool isDuplicate = true; + + for (i = 0; i < list_length(elem); i++) + { + Const *value1 = castNode(Const, list_nth(elem, i)); + Const *value2 = castNode(Const, list_nth(searchElem, i)); + + if (!equal(value1, value2)){ + isDuplicate = false; + break; + } + } + + if (isDuplicate) + return true; + } + + return false; +} + +/* * transformPartitionBound * * Transform a partition bound specification @@ -4077,7 +4117,6 @@ transformPartitionBound(ParseState *pstate, Relation parent, } else if (strategy == PARTITION_STRATEGY_LIST) { - ListCell *cell; char *colname; Oid coltype; int32 coltypmod; @@ -4103,36 +4142,10 @@ transformPartitionBound(ParseState *pstate, Relation parent, coltypmod = get_partition_col_typmod(key, 0); partcollation = get_partition_col_collation(key, 0); - result_spec->listdatums = NIL; - foreach(cell, spec->listdatums) - { - Node *expr = lfirst(cell); - Const *value; - ListCell *cell2; - bool duplicate; - - value = transformPartitionBoundValue(pstate, expr, - colname, coltype, coltypmod, - partcollation); - - /* Don't add to the result if the value is a duplicate */ - duplicate = false; - foreach(cell2, result_spec->listdatums) - { - Const *value2 = castNode(Const, lfirst(cell2)); - - if (equal(value, value2)) - { - duplicate = true; - break; - } - } - if (duplicate) - continue; - - result_spec->listdatums = lappend(result_spec->listdatums, - value); - } + result_spec->listdatums = + transformPartitionListBounds(pstate, spec, colname, coltype, + coltypmod, partcollation, + key->partnatts); } else if (strategy == PARTITION_STRATEGY_RANGE) { @@ -4168,6 +4181,72 @@ transformPartitionBound(ParseState *pstate, Relation parent, return result_spec; } +static List * +transformPartitionListBounds(ParseState *pstate, PartitionBoundSpec *spec, + char *colname, Oid coltype, int32 coltypmod, + Oid partcollation, int partnatts) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, spec->listdatums) + { + List *elem = lfirst(cell); + List *value = NIL; + ListCell *cell2; + bool isDuplicate; + + if (partnatts == 1 && list_length(spec->listdatums) == 1) + { + foreach(cell2, elem) + { + List *value = NIL; + Node *expr = lfirst(cell2); + Const *val; + + val = transformPartitionBoundValue(pstate, expr, colname, + coltype, coltypmod, + partcollation); + + value = lappend(value, val); + + isDuplicate = checkForDuplicates(result, value); + if (isDuplicate) + continue; + + result = lappend(result, value); + } + } + else + { + if (partnatts != list_length(elem)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("Must specify exactly one value per partitioning column"), + parser_errposition(pstate, exprLocation((Node *) spec)))); + + foreach(cell2, elem) + { + Node *expr = lfirst(cell2); + Const *val; + + val = transformPartitionBoundValue(pstate, expr, colname, + coltype, coltypmod, + partcollation); + value = lappend(value, val); + } + + isDuplicate = checkForDuplicates(result, value); + if (isDuplicate) + continue; + + result = lappend(result, value); + } + } + + return result; +} + /* * transformPartitionRangeBounds * This converts the expressions for range partition bounds from the raw diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c index c9c7892..2056cc3 100644 --- a/src/backend/partitioning/partbounds.c +++ b/src/backend/partitioning/partbounds.c @@ -57,7 +57,7 @@ typedef struct PartitionHashBound typedef struct PartitionListValue { int index; - Datum value; + Datum *values; } PartitionListValue; /* One bound of a range partition */ @@ -239,6 +239,12 @@ static void get_range_key_properties(PartitionKey key, int keynum, Expr **keyCol, Const **lower_val, Const **upper_val); static List *get_range_nulltest(PartitionKey key); +static int partition_list_bserach_complete(FmgrInfo *partsupfunc, + Oid *partcollation, + PartitionBoundInfo boundinfo, + int nvalues, + Datum *values, bool *is_equal, + int mid); /* * get_qual_from_partbound @@ -444,6 +450,7 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, PartitionListValue **all_values = NULL; ListCell *cell; int i = 0; + int j = 0; int ndatums = 0; int next_index = 0; int default_index = -1; @@ -479,25 +486,22 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, foreach(c, spec->listdatums) { - Const *val = castNode(Const, lfirst(c)); + List *elem = lfirst(c); + ListCell *cell; PartitionListValue *list_value = NULL; - if (!val->constisnull) - { - list_value = (PartitionListValue *) - palloc0(sizeof(PartitionListValue)); - list_value->index = i; - list_value->value = val->constvalue; - } - else + list_value = (PartitionListValue *) + palloc0(sizeof(PartitionListValue)); + list_value->index = i; + list_value->values = (Datum *) palloc0(key->partnatts * sizeof(Datum)); + + j = 0; + foreach(cell, elem) { - /* - * Never put a null into the values array; save the index of - * the partition that stores nulls, instead. - */ - if (null_index != -1) - elog(ERROR, "found null more than once"); - null_index = i; + Const *val = castNode(Const, lfirst(cell)); + + list_value->values[j] = val->constvalue; + j++; } if (list_value) @@ -520,7 +524,7 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, all_values[i] = (PartitionListValue *) palloc(sizeof(PartitionListValue)); - all_values[i]->value = src->value; + all_values[i]->values = src->values; all_values[i]->index = src->index; i++; } @@ -542,11 +546,12 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, for (i = 0; i < ndatums; i++) { int orig_index = all_values[i]->index; + boundinfo->datums[i] = (Datum *) palloc(key->partnatts * sizeof(Datum)); - boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum)); - boundinfo->datums[i][0] = datumCopy(all_values[i]->value, - key->parttypbyval[0], - key->parttyplen[0]); + for (j = 0; j < key->partnatts; j++) + boundinfo->datums[i][j] = datumCopy(all_values[i]->values[j], + key->parttypbyval[0], + key->parttyplen[0]); /* If the old index has no mapping, assign one */ if ((*mapping)[orig_index] == -1) @@ -922,9 +927,6 @@ partition_bounds_copy(PartitionBoundInfo src, nindexes = dest->nindexes = src->nindexes; partnatts = key->partnatts; - /* List partitioned tables have only a single partition key. */ - Assert(key->strategy != PARTITION_STRATEGY_LIST || partnatts == 1); - dest->datums = (Datum **) palloc(sizeof(Datum *) * ndatums); if (src->kind != NULL) @@ -2950,32 +2952,31 @@ check_new_partition_bound(char *relname, Relation parent, foreach(cell, spec->listdatums) { - Const *val = castNode(Const, lfirst(cell)); + int i; + int offset; + bool equal; + List *elem = lfirst(cell); + Datum *values = (Datum *) palloc0(key->partnatts * sizeof(Datum));; - overlap_location = val->location; - if (!val->constisnull) + for (i = 0; i < key->partnatts; i++) { - int offset; - bool equal; - - offset = partition_list_bsearch(&key->partsupfunc[0], - key->partcollation, - boundinfo, - val->constvalue, - &equal); - if (offset >= 0 && equal) - { - overlap = true; - with = boundinfo->indexes[offset]; - break; - } + Const *val = castNode(Const, list_nth(elem, i)); + values[i] = val->constvalue; } - else if (partition_bound_accepts_nulls(boundinfo)) + + offset = partition_list_bsearch(&key->partsupfunc[0], + key->partcollation, + boundinfo, + key->partnatts, + values, + &equal); + if (offset >= 0 && equal) { overlap = true; - with = boundinfo->null_index; + with = boundinfo->indexes[offset]; break; } + pfree(values); } } @@ -3488,6 +3489,109 @@ partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int remainder2) } /* + * partition_lbound_datum_cmp + * + * This function compares the list bound values of all the partition key + * columns. Returns TRUE if all the values are matching. Returns FALSE if any + * of the value is not matching. + * + */ +static bool +partition_lbound_datum_cmp(FmgrInfo *partsupfunc, Oid *partcollation, + Datum *lb_datums, int nvalues, Datum *values) +{ + int i = 0; + int32 cmpval = 0; + + for (i = 1; i < nvalues; i++) + { + cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0], + partcollation[0], + lb_datums[i], + values[i])); + if (cmpval != 0) + return false; + } + + return true; +} + +/* + * partition_list_bserach_complete + * + * This function is called once it finds the first match for first value of the + * list. Then this function will continue the serach and return the index of + * matching element if found. + * + */ +static int +partition_list_bserach_complete(FmgrInfo *partsupfunc, Oid *partcollation, + PartitionBoundInfo boundinfo, int nvalues, + Datum *values, bool *is_equal, int mid) +{ + + int32 res = partition_lbound_datum_cmp(partsupfunc, partcollation, + boundinfo->datums[mid], + nvalues, values); + if (res == true) + { + *is_equal = true; + return mid; + } + else + { + int i; + int idx = mid; + + for (i = mid-1; i >= 0; i--) + { + idx--; + res = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0], + partcollation[0], + boundinfo->datums[i][0], + values[0])); + if (res != 0) + break; + + res = partition_lbound_datum_cmp(partsupfunc, partcollation, + boundinfo->datums[i], + nvalues, values); + if (res == true) + { + *is_equal = true; + return idx; + } + } + + idx = mid; + for (i = mid+1; i < boundinfo->ndatums; i++) + { + idx++; + res = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0], + partcollation[0], + boundinfo->datums[i][0], + values[0])); + + if (res != 0) + break; + + res = partition_lbound_datum_cmp(partsupfunc, partcollation, + boundinfo->datums[i], + nvalues, values); + + if (res == true) + { + *is_equal = true; + return idx; + } + } + + *is_equal = false; + return -1; + } +} + +/* * partition_list_bsearch * Returns the index of the greatest bound datum that is less than equal * to the given value or -1 if all of the bound datums are greater @@ -3497,8 +3601,8 @@ partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int remainder2) */ int partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation, - PartitionBoundInfo boundinfo, - Datum value, bool *is_equal) + PartitionBoundInfo boundinfo, int nvalues, + Datum *values, bool *is_equal) { int lo, hi, @@ -3514,7 +3618,16 @@ partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation, cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0], partcollation[0], boundinfo->datums[mid][0], - value)); + values[0])); + + if (cmpval == 0) + { + return partition_list_bserach_complete(partsupfunc, partcollation, + boundinfo, nvalues, values, + is_equal, mid); + } + + if (cmpval <= 0) { lo = mid; @@ -3684,13 +3797,14 @@ qsort_partition_hbound_cmp(const void *a, const void *b) static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg) { - Datum val1 = (*(PartitionListValue *const *) a)->value, - val2 = (*(PartitionListValue *const *) b)->value; + Datum *val1 = (*(PartitionListValue *const *) a)->values; + Datum *val2 = (*(PartitionListValue *const *) b)->values; + PartitionKey key = (PartitionKey) arg; return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0], key->partcollation[0], - val1, val2)); + val1[0], val2[0])); } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index c793742..fbc9e29 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -2728,7 +2728,7 @@ get_matching_list_bounds(PartitionPruneContext *context, boundinfo->ndatums - 1); off = partition_list_bsearch(partsupfunc, partcollation, boundinfo, - value, &is_equal); + nvalues, value, &is_equal); if (off >= 0 && is_equal) { @@ -2760,8 +2760,8 @@ get_matching_list_bounds(PartitionPruneContext *context, case BTEqualStrategyNumber: off = partition_list_bsearch(partsupfunc, partcollation, - boundinfo, value, - &is_equal); + boundinfo, nvalues, + value, &is_equal); if (off >= 0 && is_equal) { Assert(boundinfo->indexes[off] >= 0); @@ -2777,8 +2777,8 @@ get_matching_list_bounds(PartitionPruneContext *context, case BTGreaterStrategyNumber: off = partition_list_bsearch(partsupfunc, partcollation, - boundinfo, value, - &is_equal); + boundinfo, nvalues, + value, &is_equal); if (off >= 0) { /* We don't want the matched datum to be in the result. */ @@ -2812,8 +2812,8 @@ get_matching_list_bounds(PartitionPruneContext *context, case BTLessStrategyNumber: off = partition_list_bsearch(partsupfunc, partcollation, - boundinfo, value, - &is_equal); + boundinfo, nvalues, + value, &is_equal); if (off >= 0 && is_equal && !inclusive) off--; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 0a4fa93..b22d39d 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9394,18 +9394,27 @@ get_rule_expr(Node *node, deparse_context *context, case PARTITION_STRATEGY_LIST: Assert(spec->listdatums != NIL); - appendStringInfoString(buf, "FOR VALUES IN ("); + appendStringInfoString(buf, "FOR VALUES IN "); sep = ""; foreach(cell, spec->listdatums) { - Const *val = castNode(Const, lfirst(cell)); + ListCell *cell2; + List *elem = lfirst(cell); appendStringInfoString(buf, sep); - get_const_expr(val, context, -1); + appendStringInfoChar(buf, '('); + sep = ""; + foreach(cell2, elem) + { + Const *val = castNode(Const, lfirst(cell2)); + + appendStringInfoString(buf, sep); + get_const_expr(val, context, -1); + sep = ", "; + } + appendStringInfoChar(buf, ')'); sep = ", "; } - - appendStringInfoChar(buf, ')'); break; case PARTITION_STRATEGY_RANGE: diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h index ebf3ff1..7bf20ff 100644 --- a/src/include/partitioning/partbounds.h +++ b/src/include/partitioning/partbounds.h @@ -117,7 +117,7 @@ extern int32 partition_rbound_datum_cmp(FmgrInfo *partsupfunc, extern int partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation, PartitionBoundInfo boundinfo, - Datum value, bool *is_equal); + int nvalues, Datum *value, bool *is_equal); extern int partition_range_datum_bsearch(FmgrInfo *partsupfunc, Oid *partcollation, PartitionBoundInfo boundinfo,