diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 028e8ac..28fd15d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -16553,13 +16553,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 606c920..218054a 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -1265,19 +1265,15 @@ get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull) break; case PARTITION_STRATEGY_LIST: - if (isnull[0]) - { - if (partition_bound_accepts_nulls(boundinfo)) - part_index = boundinfo->null_index; - } - else { bool equal = false; bound_offset = partition_list_bsearch(key->partsupfunc, key->partcollation, boundinfo, - values[0], &equal); + key->partnatts, + values, isnull, + &equal); if (bound_offset >= 0 && equal) part_index = boundinfo->indexes[bound_offset]; } diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index d5b67d4..d3ece78 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -146,6 +146,9 @@ 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, + Relation parent); /* @@ -4017,6 +4020,42 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd) } /* + * checkForDuplicates + * + * Return TRUE if the list bound element is already present in the list of list + * bounds, FALSE otherwise. + */ +static bool +checkForDuplicates(List *source, List *searchElem) +{ + ListCell *cell = NULL; + + foreach(cell, source) + { + int i = 0; + 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 @@ -4029,7 +4068,6 @@ transformPartitionBound(ParseState *pstate, Relation parent, PartitionKey key = RelationGetPartitionKey(parent); char strategy = get_partition_strategy(key); int partnatts = get_partition_natts(key); - List *partexprs = get_partition_exprs(key); /* Avoid scribbling on input */ result_spec = copyObject(spec); @@ -4079,62 +4117,14 @@ transformPartitionBound(ParseState *pstate, Relation parent, } else if (strategy == PARTITION_STRATEGY_LIST) { - ListCell *cell; - char *colname; - Oid coltype; - int32 coltypmod; - Oid partcollation; - if (spec->strategy != PARTITION_STRATEGY_LIST) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("invalid bound specification for a list partition"), parser_errposition(pstate, exprLocation((Node *) spec)))); - /* Get the only column's name in case we need to output an error */ - if (key->partattrs[0] != 0) - colname = get_attname(RelationGetRelid(parent), - key->partattrs[0], false); - else - colname = deparse_expression((Node *) linitial(partexprs), - deparse_context_for(RelationGetRelationName(parent), - RelationGetRelid(parent)), - false, false); - /* Need its type data too */ - coltype = get_partition_col_typid(key, 0); - 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, parent); } else if (strategy == PARTITION_STRATEGY_RANGE) { @@ -4171,6 +4161,111 @@ transformPartitionBound(ParseState *pstate, Relation parent, } /* + * transformPartitionListBounds + * This functions converts the expressions of list partition bounds + * from the raw grammar representation. + */ +static List * +transformPartitionListBounds(ParseState *pstate, PartitionBoundSpec *spec, + Relation parent) +{ + int i = 0; + int j = 0; + ListCell *cell = NULL; + List *result = NIL; + PartitionKey key = RelationGetPartitionKey(parent); + List *partexprs = get_partition_exprs(key); + int partnatts = get_partition_natts(key); + char **colname = (char **) palloc0(partnatts * sizeof(char *)); + Oid *coltype = palloc0(partnatts * sizeof(Oid)); + int32 *coltypmod = palloc0(partnatts * sizeof(int)); + Oid *partcollation = palloc0(partnatts * sizeof(Oid)); + + for (i = 0; i < partnatts; i++) + { + if (key->partattrs[i] != 0) + { + colname[i] = (char *) palloc0(NAMEDATALEN * sizeof(char)); + colname[i] = get_attname(RelationGetRelid(parent), + key->partattrs[i], false); + } + else + { + colname[i] = + deparse_expression((Node *) list_nth(partexprs, j), + deparse_context_for(RelationGetRelationName(parent), + RelationGetRelid(parent)), + false, false); + ++j; + } + + coltype[i] = get_partition_col_typid(key, i); + coltypmod[i] = get_partition_col_typmod(key, i); + partcollation[i] = get_partition_col_collation(key, i); + } + + foreach(cell, spec->listdatums) + { + Node *expr = lfirst(cell); + List *values = NIL; + bool isDuplicate = false; + + if (partnatts == 1) + { + Const *val = + transformPartitionBoundValue(pstate, expr,colname[0], + coltype[0], coltypmod[0], + partcollation[0]); + values = lappend(values, val); + } + else + { + ListCell *cell2 = NULL; + RowExpr *rowexpr = NULL; + + if (!IsA(expr, RowExpr)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("Invalid list bound specification"), + parser_errposition(pstate, exprLocation((Node *) spec)))); + + rowexpr = (RowExpr *) expr; + if (partnatts != list_length(rowexpr->args)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("Must specify exactly one value per partitioning column"), + parser_errposition(pstate, exprLocation((Node *) spec)))); + + i = 0; + foreach(cell2, rowexpr->args) + { + Node *expr = lfirst(cell2); + Const *val = + transformPartitionBoundValue(pstate, expr, colname[i], + coltype[i], coltypmod[i], + partcollation[i]); + values = lappend(values, val); + i++; + } + } + + /* Don't add to the result if the value is a duplicate */ + isDuplicate = checkForDuplicates(result, values); + if (isDuplicate) + continue; + + result = lappend(result, values); + } + + pfree(colname); + pfree(coltype); + pfree(coltypmod); + pfree(partcollation); + + return result; +} + +/* * transformPartitionRangeBounds * This converts the expressions for range partition bounds from the raw * grammar representation to PartitionRangeDatum structs diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c index 7925fcc..bb6cddc 100644 --- a/src/backend/partitioning/partbounds.c +++ b/src/backend/partitioning/partbounds.c @@ -53,11 +53,12 @@ typedef struct PartitionHashBound int index; } PartitionHashBound; -/* One value coming from some (index'th) list partition */ +/* One bound of a list partition */ typedef struct PartitionListValue { int index; - Datum value; + Datum *values; + bool *isnulls; } PartitionListValue; /* One bound of a range partition */ @@ -175,6 +176,7 @@ static void generate_matching_part_pairs(RelOptInfo *outer_rel, List **inner_parts); static PartitionBoundInfo build_merged_partition_bounds(char strategy, List *merged_datums, + List *merged_isnulls, List *merged_kinds, List *merged_indexes, int null_index, @@ -230,6 +232,7 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy, bool *need_relabel); static List *get_qual_for_hash(Relation parent, PartitionBoundSpec *spec); static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec); +static List *get_qual_for_multi_column_list(Relation parent, PartitionBoundSpec *spec); static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec, bool for_default); static void get_range_key_properties(PartitionKey key, int keynum, @@ -367,8 +370,8 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts, palloc0(sizeof(PartitionBoundInfoData)); boundinfo->strategy = key->strategy; /* No special hash partitions. */ - boundinfo->null_index = -1; boundinfo->default_index = -1; + boundinfo->isnulls = NULL; ndatums = nparts; hbounds = (PartitionHashBound **) @@ -433,6 +436,56 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts, } /* + * partition_bound_accepts_nulls + * Returns TRUE if partition bound has NULL value, FALSE otherwise. + */ +bool partition_bound_accepts_nulls(PartitionBoundInfo boundinfo) +{ + int i = 0; + int j = 0; + + if (!boundinfo->isnulls) + return false; + + for (i = 0; i < boundinfo->ndatums; i++) + { + //TODO: Handle for multi-column cases + for (j = 0; j < 1; j++) + { + if (boundinfo->isnulls[i][j]) + return true; + } + } + + return false; +} + +/* + * get_partition_bound_null_index + * Returns the partition index of the partition bound which accepts NULL. + */ +int get_partition_bound_null_index(PartitionBoundInfo boundinfo) +{ + int i = 0; + int j = 0; + + if (!boundinfo->isnulls) + return -1; + + for (i = 0; i < boundinfo->ndatums; i++) + { + //TODO: Handle for multi-column cases + for (j = 0; j < 1; j++) + { + if (boundinfo->isnulls[i][j]) + return boundinfo->indexes[i]; + } + } + + return -1; +} + +/* * create_list_bounds * Create a PartitionBoundInfo for a list partitioned table */ @@ -447,14 +500,12 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, int ndatums = 0; int next_index = 0; int default_index = -1; - int null_index = -1; List *non_null_values = NIL; boundinfo = (PartitionBoundInfoData *) palloc0(sizeof(PartitionBoundInfoData)); boundinfo->strategy = key->strategy; /* Will be set correctly below. */ - boundinfo->null_index = -1; boundinfo->default_index = -1; /* Create a unified list of non-null values across all partitions. */ @@ -479,29 +530,30 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, foreach(c, spec->listdatums) { - Const *val = castNode(Const, lfirst(c)); + int j = 0; + List *elem = lfirst(c); + ListCell *cell = NULL; 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)); + list_value->isnulls = (bool *) palloc0(key->partnatts * sizeof(bool)); + + 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)); + + if (!val->constisnull) + list_value->values[j] = val->constvalue; + else + list_value->isnulls[j] = true; + + j++; } - if (list_value) - non_null_values = lappend(non_null_values, list_value); + non_null_values = lappend(non_null_values, list_value); } } @@ -520,7 +572,8 @@ 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]->isnulls= src->isnulls; all_values[i]->index = src->index; i++; } @@ -530,6 +583,7 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, boundinfo->ndatums = ndatums; boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *)); + boundinfo->isnulls = (bool **) palloc0(ndatums * sizeof(bool *)); boundinfo->nindexes = ndatums; boundinfo->indexes = (int *) palloc(ndatums * sizeof(int)); @@ -541,12 +595,19 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, */ for (i = 0; i < ndatums; i++) { + int j = 0; int orig_index = all_values[i]->index; + boundinfo->datums[i] = (Datum *) palloc(key->partnatts * sizeof(Datum)); + boundinfo->isnulls[i] = (bool *) palloc(key->partnatts * sizeof(bool)); - 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++) + { + if (!all_values[i]->isnulls[j]) + boundinfo->datums[i][j] = datumCopy(all_values[i]->values[j], + key->parttypbyval[j], + key->parttyplen[j]); + boundinfo->isnulls[i][j] = all_values[i]->isnulls[j]; + } /* If the old index has no mapping, assign one */ if ((*mapping)[orig_index] == -1) @@ -555,22 +616,6 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, boundinfo->indexes[i] = (*mapping)[orig_index]; } - /* - * Set the canonical value for null_index, if any. - * - * It is possible that the null-accepting partition has not been assigned - * an index yet, which could happen if such partition accepts only null - * and hence not handled in the above loop which only looked at non-null - * values. - */ - if (null_index != -1) - { - Assert(null_index >= 0); - if ((*mapping)[null_index] == -1) - (*mapping)[null_index] = next_index++; - boundinfo->null_index = (*mapping)[null_index]; - } - /* Set the canonical value for default_index, if any. */ if (default_index != -1) { @@ -611,10 +656,9 @@ create_range_bounds(PartitionBoundSpec **boundspecs, int nparts, boundinfo = (PartitionBoundInfoData *) palloc0(sizeof(PartitionBoundInfoData)); boundinfo->strategy = key->strategy; - /* There is no special null-accepting range partition. */ - boundinfo->null_index = -1; /* Will be set correctly below. */ boundinfo->default_index = -1; + boundinfo->isnulls = NULL; all_bounds = (PartitionRangeBound **) palloc0(2 * nparts * sizeof(PartitionRangeBound *)); @@ -802,6 +846,8 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval, PartitionBoundInfo b1, PartitionBoundInfo b2) { int i; + bool b1_isnull = false; + bool b2_isnull = false; if (b1->strategy != b2->strategy) return false; @@ -812,7 +858,7 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval, if (b1->nindexes != b2->nindexes) return false; - if (b1->null_index != b2->null_index) + if (get_partition_bound_null_index(b1) != get_partition_bound_null_index(b2)) return false; if (b1->default_index != b2->default_index) @@ -885,7 +931,22 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval, * context. datumIsEqual() should be simple enough to be * safe. */ - if (!datumIsEqual(b1->datums[i][j], b2->datums[i][j], + if (b1->isnulls) + b1_isnull = b1->isnulls[i][j]; + if (b2->isnulls) + b2_isnull = b2->isnulls[i][j]; + + /* + * If any of the partition bound has NULL value, then check + * equality for the NULL value. Dont compare the datums + * as it does not contain valid value in case of NULL. + */ + if (b1_isnull || b2_isnull) + { + if (b1_isnull != b2_isnull) + return false; + } + else if (!datumIsEqual(b1->datums[i][j], b2->datums[i][j], parttypbyval[j], parttyplen[j])) return false; } @@ -922,10 +983,11 @@ 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->isnulls) + dest->isnulls = (bool **) palloc(sizeof(bool *) * ndatums); + else + dest->isnulls = NULL; if (src->kind != NULL) { @@ -955,6 +1017,8 @@ partition_bounds_copy(PartitionBoundInfo src, int j; dest->datums[i] = (Datum *) palloc(sizeof(Datum) * natts); + if (src->isnulls) + dest->isnulls[i] = (bool *) palloc(sizeof(bool) * natts); for (j = 0; j < natts; j++) { @@ -972,17 +1036,22 @@ partition_bounds_copy(PartitionBoundInfo src, typlen = key->parttyplen[j]; } - if (dest->kind == NULL || - dest->kind[i][j] == PARTITION_RANGE_DATUM_VALUE) + + if ((dest->kind == NULL || + dest->kind[i][j] == PARTITION_RANGE_DATUM_VALUE) && + (key->strategy != PARTITION_STRATEGY_LIST || + !src->isnulls[i][j])) dest->datums[i][j] = datumCopy(src->datums[i][j], byval, typlen); + + if (src->isnulls) + dest->isnulls[i][j] = src->isnulls[i][j]; } } dest->indexes = (int *) palloc(sizeof(int) * nindexes); memcpy(dest->indexes, src->indexes, sizeof(int) * nindexes); - dest->null_index = src->null_index; dest->default_index = src->default_index; return dest; @@ -1100,6 +1169,8 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, int inner_default = inner_bi->default_index; bool outer_has_null = partition_bound_accepts_nulls(outer_bi); bool inner_has_null = partition_bound_accepts_nulls(inner_bi); + int outer_null_index = get_partition_bound_null_index(outer_bi); + int inner_null_index = get_partition_bound_null_index(inner_bi); PartitionMap outer_map; PartitionMap inner_map; int outer_pos; @@ -1109,6 +1180,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, int default_index = -1; List *merged_datums = NIL; List *merged_indexes = NIL; + List *merged_isnulls = NIL; Assert(*outer_parts == NIL); Assert(*inner_parts == NIL); @@ -1146,6 +1218,35 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, int cmpval; Datum *merged_datum = NULL; int merged_index = -1; + bool *outer_isnull; + bool *inner_isnull; + bool *merged_isnull = NULL; + bool is_all_match = false; + + if (outer_bi->isnulls && outer_pos < outer_bi->ndatums) + outer_isnull = outer_bi->isnulls[outer_pos]; + + if (inner_bi->isnulls && inner_pos < inner_bi->ndatums) + inner_isnull = inner_bi->isnulls[inner_pos]; + + //TODO: Handle for multi-column case. + if (outer_isnull[0] && inner_isnull[0]) + { + outer_pos++; + inner_pos++; + continue; + } + else if (outer_isnull[0]) + { + outer_pos++; + continue; + } + else if (inner_isnull[0]) + { + inner_pos++; + continue; + } + if (outer_pos < outer_bi->ndatums) { @@ -1196,13 +1297,15 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, else { Assert(outer_datums != NULL && inner_datums != NULL); - cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0], - partcollation[0], - outer_datums[0], - inner_datums[0])); + //TODO: handle multi-column case + cmpval = partition_lbound_datum_cmp(partsupfunc, partcollation, 1, //TODO: get attr count + outer_datums, + outer_isnull, + inner_datums, + inner_isnull, &is_all_match); } - if (cmpval == 0) + if (is_all_match) { /* Two list values match exactly. */ Assert(outer_pos < outer_bi->ndatums); @@ -1221,6 +1324,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, goto cleanup; merged_datum = outer_datums; + merged_isnull = outer_isnull; /* Move to the next pair of list values. */ outer_pos++; @@ -1254,6 +1358,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, if (merged_index == -1) goto cleanup; merged_datum = outer_datums; + merged_isnull = outer_isnull; } /* Move to the next list value on the outer side. */ @@ -1288,6 +1393,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, if (merged_index == -1) goto cleanup; merged_datum = inner_datums; + merged_isnull = inner_isnull; } /* Move to the next list value on the inner side. */ @@ -1302,6 +1408,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, { merged_datums = lappend(merged_datums, merged_datum); merged_indexes = lappend_int(merged_indexes, merged_index); + merged_isnulls = lappend(merged_isnulls, merged_isnull); } } @@ -1310,17 +1417,17 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, * non-existent. */ if (outer_has_null && - is_dummy_partition(outer_rel, outer_bi->null_index)) + is_dummy_partition(outer_rel, outer_null_index)) outer_has_null = false; if (inner_has_null && - is_dummy_partition(inner_rel, inner_bi->null_index)) + is_dummy_partition(inner_rel, inner_null_index)) inner_has_null = false; /* Merge the NULL partitions if any. */ if (outer_has_null || inner_has_null) merge_null_partitions(&outer_map, &inner_map, outer_has_null, inner_has_null, - outer_bi->null_index, inner_bi->null_index, + outer_null_index, inner_null_index, jointype, &next_index, &null_index); else Assert(null_index == -1); @@ -1358,6 +1465,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, /* Make a PartitionBoundInfo struct to return. */ merged_bounds = build_merged_partition_bounds(outer_bi->strategy, merged_datums, + merged_isnulls, NIL, merged_indexes, null_index, @@ -1368,6 +1476,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, cleanup: /* Free local memory before returning. */ list_free(merged_datums); + list_free(merged_isnulls); list_free(merged_indexes); free_partition_map(&outer_map); free_partition_map(&inner_map); @@ -1676,6 +1785,7 @@ merge_range_bounds(int partnatts, FmgrInfo *partsupfuncs, /* Make a PartitionBoundInfo struct to return. */ merged_bounds = build_merged_partition_bounds(outer_bi->strategy, merged_datums, + NIL, merged_kinds, merged_indexes, -1, @@ -2407,19 +2517,41 @@ generate_matching_part_pairs(RelOptInfo *outer_rel, RelOptInfo *inner_rel, */ static PartitionBoundInfo build_merged_partition_bounds(char strategy, List *merged_datums, - List *merged_kinds, List *merged_indexes, - int null_index, int default_index) + List *merged_isnulls, List *merged_kinds, + List *merged_indexes, int null_index, + int default_index) { PartitionBoundInfo merged_bounds; int ndatums = list_length(merged_datums); int pos; ListCell *lc; + int natts = 1; //TODO: Handle for multi-column case + bool *null = NULL; merged_bounds = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData)); merged_bounds->strategy = strategy; - merged_bounds->ndatums = ndatums; + if (merged_isnulls) + { + if (null_index >= 0) + { + null = (bool *) palloc0(sizeof(bool) * natts); + null[0] = true; + ndatums++; + } + merged_bounds->isnulls = (bool **) palloc(sizeof(bool *) * ndatums); + + pos = 0; + foreach(lc, merged_isnulls) + merged_bounds->isnulls[pos++] = (bool *) lfirst(lc); + + if (null_index >= 0) + merged_bounds->isnulls[pos] = null; + } + + merged_bounds->ndatums = ndatums; merged_bounds->datums = (Datum **) palloc(sizeof(Datum *) * ndatums); + pos = 0; foreach(lc, merged_datums) merged_bounds->datums[pos++] = (Datum *) lfirst(lc); @@ -2436,6 +2568,7 @@ build_merged_partition_bounds(char strategy, List *merged_datums, /* There are ndatums+1 indexes in the case of range partitioning. */ merged_indexes = lappend_int(merged_indexes, -1); ndatums++; + merged_bounds->isnulls = NULL; } else { @@ -2444,14 +2577,17 @@ build_merged_partition_bounds(char strategy, List *merged_datums, merged_bounds->kind = NULL; } - Assert(list_length(merged_indexes) == ndatums); + Assert(list_length(merged_indexes) == ndatums || + list_length(merged_indexes) == ndatums - 1); merged_bounds->nindexes = ndatums; merged_bounds->indexes = (int *) palloc(sizeof(int) * ndatums); pos = 0; foreach(lc, merged_indexes) merged_bounds->indexes[pos++] = lfirst_int(lc); - merged_bounds->null_index = null_index; + if (merged_isnulls && null_index >= 0) + merged_bounds->indexes[pos] = null_index; + merged_bounds->default_index = default_index; return merged_bounds; @@ -2953,32 +3089,37 @@ check_new_partition_bound(char *relname, Relation parent, foreach(cell, spec->listdatums) { - Const *val = castNode(Const, lfirst(cell)); - - overlap_location = val->location; - if (!val->constisnull) + int i = 0; + int offset = -1; + bool equal = false; + List *elem = lfirst(cell); + Datum *values = (Datum *) palloc0(key->partnatts * sizeof(Datum)); + bool *isnulls = (bool *) palloc0(key->partnatts * sizeof(bool)); + + 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; + isnulls[i] = val->constisnull; + overlap_location = val->location; } - else if (partition_bound_accepts_nulls(boundinfo)) + + offset = partition_list_bsearch(key->partsupfunc, + key->partcollation, + boundinfo, + key->partnatts, + values, isnulls, + &equal); + if (offset >= 0 && equal) { overlap = true; - with = boundinfo->null_index; + with = boundinfo->indexes[offset]; break; } + + pfree(values); + pfree(isnulls); } } @@ -3491,6 +3632,53 @@ 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 the value of 'cmpval' if the first bound value does + * not match, otherwise returns zero. If it successfully compares the bound + * values for all the partition key, then it sets is_all_match to TRUE. + */ +int32 +partition_lbound_datum_cmp(FmgrInfo *partsupfunc, Oid *partcollation, + int nvalues, Datum *lb_datums, bool *lb_isnulls, + Datum *values, bool *isnulls, bool *is_all_match) +{ + int i = 0; + int32 cmpval = 0; + + for (i = 0; i < nvalues; i++) + { + bool isnull = false; + + if (isnulls) + isnull = isnulls[i]; + + if (lb_isnulls[i] && isnull) + cmpval = 0; + else if (lb_isnulls[i]) + cmpval = 1; + else if (isnull) + cmpval = -1; + else + cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i], + partcollation[i], + lb_datums[i], values[i])); + + if (cmpval != 0) + break; + } + + if (i == 0) + return cmpval; + + if (i == nvalues && cmpval == 0) + *is_all_match = true; + + return 0; +} + +/* * 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 @@ -3500,8 +3688,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 *isnulls, bool *is_equal) { int lo, hi, @@ -3512,16 +3700,76 @@ partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation, while (lo < hi) { int32 cmpval; + bool is_all_match = false; mid = (lo + hi + 1) / 2; - cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0], - partcollation[0], - boundinfo->datums[mid][0], - value)); + cmpval = partition_lbound_datum_cmp(partsupfunc, partcollation, + nvalues, boundinfo->datums[mid], + boundinfo->isnulls[mid], values, + isnulls, &is_all_match); + + if (is_all_match) + { + *is_equal = true; + return mid; + } + + if (cmpval == 0) + { + /* + * Once we find the matching for the first column but if it does not + * match for the any of the other columns, then the binary search + * will not work in all the cases. We should traverse just below + * and above the mid index until we find the match or we reach the + * first mismatch. + */ + int32 off = mid; + while (off >= 1) + { + cmpval = partition_lbound_datum_cmp(partsupfunc, partcollation, + nvalues, boundinfo->datums[off - 1], + boundinfo->isnulls[off - 1], values, + isnulls, &is_all_match); + + if (is_all_match) + { + /* Found the matching bound. Return the offset. */ + *is_equal = true; + return (off - 1); + } + + if (cmpval != 0) + break; + + off--; + } + + off = mid; + while (off < boundinfo->ndatums - 1) + { + cmpval = partition_lbound_datum_cmp(partsupfunc, partcollation, + nvalues, boundinfo->datums[off + 1], + boundinfo->isnulls[off + 1], values, + isnulls, &is_all_match); + + if (is_all_match) + { + /* Found the matching bound. Return the offset. */ + *is_equal = true; + return (off + 1); + } + + if (cmpval != 0) + break; + + off++; + } + } + if (cmpval <= 0) { lo = mid; - *is_equal = (cmpval == 0); + *is_equal = (cmpval == 0 && is_all_match); if (*is_equal) break; } @@ -3687,13 +3935,23 @@ 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; + bool *null1 = (*(PartitionListValue *const *) a)->isnulls; + bool *null2 = (*(PartitionListValue *const *) b)->isnulls; + PartitionKey key = (PartitionKey) arg; - return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0], - key->partcollation[0], - val1, val2)); + if (null1[0] && null2[0]) + return 0; + else if (null1[0]) + return 1; + else if (null2[0]) + return -1; + else + return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0], + key->partcollation[0], + val1[0], val2[0])); } /* @@ -3793,9 +4051,8 @@ make_partition_op_expr(PartitionKey key, int keynum, int nelems = list_length(elems); Assert(nelems >= 1); - Assert(keynum == 0); - if (nelems > 1 && + if (key->partnatts == 1 && nelems > 1 && !type_is_array(key->parttypid[keynum])) { ArrayExpr *arrexpr; @@ -3820,10 +4077,9 @@ make_partition_op_expr(PartitionKey key, int keynum, saopexpr->inputcollid = key->partcollation[keynum]; saopexpr->args = list_make2(arg1, arrexpr); saopexpr->location = -1; - result = (Expr *) saopexpr; } - else + else if (key->partnatts == 1) { List *elemops = NIL; ListCell *lc; @@ -3844,9 +4100,17 @@ make_partition_op_expr(PartitionKey key, int keynum, result = nelems > 1 ? makeBoolExpr(OR_EXPR, elemops, -1) : linitial(elemops); } + else + { + result = make_opclause(operoid, + BOOLOID, + false, + arg1, arg2, + InvalidOid, + key->partcollation[keynum]); + } break; } - case PARTITION_STRATEGY_RANGE: result = make_opclause(operoid, BOOLOID, @@ -3968,11 +4232,8 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec) List *elems = NIL; bool list_has_null = false; - /* - * Only single-column list partitioning is supported, so we are worried - * only about the partition key with index 0. - */ - Assert(key->partnatts == 1); + if (key->partnatts > 1) + return get_qual_for_multi_column_list(parent, spec); /* Construct Var or expression representing the partition column */ if (key->partattrs[0] != 0) @@ -3998,13 +4259,8 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec) PartitionBoundInfo boundinfo = pdesc->boundinfo; if (boundinfo) - { ndatums = boundinfo->ndatums; - if (partition_bound_accepts_nulls(boundinfo)) - list_has_null = true; - } - /* * If default is the only partition, there need not be any partition * constraint on it. @@ -4016,6 +4272,12 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec) { Const *val; + if (boundinfo->isnulls[i][0]) + { + list_has_null = true; + continue; + } + /* * Construct Const from known-not-null datum. We must be careful * to copy the value, because our result has to be able to outlive @@ -4025,7 +4287,7 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec) key->parttypmod[0], key->parttypcoll[0], key->parttyplen[0], - datumCopy(*boundinfo->datums[i], + datumCopy(boundinfo->datums[i][0], key->parttypbyval[0], key->parttyplen[0]), false, /* isnull */ @@ -4041,12 +4303,17 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec) */ foreach(cell, spec->listdatums) { - Const *val = castNode(Const, lfirst(cell)); + ListCell *cell2 = NULL; - if (val->constisnull) - list_has_null = true; - else - elems = lappend(elems, copyObject(val)); + foreach(cell2, (List *) lfirst(cell)) + { + Const *val = castNode(Const, lfirst(cell2)); + + if (val->constisnull) + list_has_null = true; + else + elems = lappend(elems, copyObject(val)); + } } } @@ -4122,6 +4389,158 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec) } /* + * get_qual_for_list_for_multi_column + * + * Returns a list of expressions to use as a list partition's constraint, + * given the parent relation and partition bound structure. + * + * The function returns NIL for a default partition when it's the only + * partition since in that case there is no constraint. + */ +static List * +get_qual_for_multi_column_list(Relation parent, PartitionBoundSpec *spec) +{ + int i = 0; + int j = 0; + PartitionKey key = RelationGetPartitionKey(parent); + List *result; + Expr *opexpr; + NullTest *nulltest; + ListCell *cell; + List *elems = NIL; + Expr **keyCol = (Expr **) palloc0 (key->partnatts * sizeof(Expr *)); + + /* Construct Var or expression representing the partition columns */ + for (i = 0; i < key->partnatts; i++) + { + if (key->partattrs[i] != 0) + keyCol[i] = (Expr *) makeVar(1, + key->partattrs[i], + key->parttypid[i], + key->parttypmod[i], + key->parttypcoll[i], + 0); + else + { + keyCol[i] = (Expr *) copyObject(list_nth(key->partexprs, j)); + ++j; + } + } + + /* + * For default list partition, collect datums for all the partitions. The + * default partition constraint should check that the partition key is + * equal to none of those. + */ + if (spec->is_default) + { + int ndatums = 0; + PartitionDesc pdesc = RelationGetPartitionDesc(parent, false); + PartitionBoundInfo boundinfo = pdesc->boundinfo; + + if (boundinfo) + ndatums = boundinfo->ndatums; + + /* + * If default is the only partition, there need not be any partition + * constraint on it. + */ + if (ndatums == 0) + return NIL; + + for (i = 0; i < ndatums; i++) + { + List *andexpr = NIL; + + for (j = 0; j < key->partnatts; j++) + { + Const *val = NULL; + + if (boundinfo->isnulls[i][j]) + { + nulltest = makeNode(NullTest); + nulltest->arg = keyCol[j]; + nulltest->nulltesttype = IS_NULL; + nulltest->argisrow = false; + nulltest->location = -1; + andexpr = lappend(andexpr, nulltest); + } + else + { + val = makeConst(key->parttypid[j], + key->parttypmod[j], + key->parttypcoll[j], + key->parttyplen[j], + datumCopy(boundinfo->datums[i][j], + key->parttypbyval[j], + key->parttyplen[j]), + false, /* isnull */ + key->parttypbyval[j]); + + opexpr = make_partition_op_expr(key, j, BTEqualStrategyNumber, + keyCol[j], (Expr *) val); + andexpr = lappend(andexpr, opexpr); + } + } + + opexpr = makeBoolExpr(AND_EXPR, andexpr, -1); + elems = lappend(elems, opexpr); + } + } + else + { + /* + * Create list of Consts for the allowed values. + */ + foreach(cell, spec->listdatums) + { + List *andexpr = NIL; + ListCell *cell2 = NULL; + + j = 0; + foreach(cell2, (List *) lfirst(cell)) + { + Const *val = castNode(Const, lfirst(cell2)); + + if (val->constisnull) + { + nulltest = makeNode(NullTest); + nulltest->arg = keyCol[j]; + nulltest->nulltesttype = IS_NULL; + nulltest->argisrow = false; + nulltest->location = -1; + andexpr = lappend(andexpr, nulltest); + } + else + { + opexpr = make_partition_op_expr(key, j, BTEqualStrategyNumber, + keyCol[j], (Expr *) val); + andexpr = lappend(andexpr, opexpr); + } + j++; + } + + opexpr = makeBoolExpr(AND_EXPR, andexpr, -1); + elems = lappend(elems, opexpr); + } + } + + opexpr = makeBoolExpr(OR_EXPR, elems, -1); + result = list_make1(opexpr); + + /* + * Note that, in general, applying NOT to a constraint expression doesn't + * necessarily invert the set of rows it accepts, because NOT (NULL) is + * NULL. However, the partition constraints we construct here never + * evaluate to NULL, so applying NOT works as intended. + */ + if (spec->is_default) + result = list_make1(makeBoolExpr(NOT_EXPR, result, -1)); + + return result; +} + +/* * get_qual_for_range * * Returns an implicit-AND list of expressions to use as a range partition's diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index c793742..d4e9ba3 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -185,7 +185,8 @@ static PruneStepResult *get_matching_hash_bounds(PartitionPruneContext *context, StrategyNumber opstrategy, Datum *values, int nvalues, FmgrInfo *partsupfunc, Bitmapset *nullkeys); static PruneStepResult *get_matching_list_bounds(PartitionPruneContext *context, - StrategyNumber opstrategy, Datum value, int nvalues, + StrategyNumber opstrategy, + Datum *values, int nvalues, FmgrInfo *partsupfunc, Bitmapset *nullkeys); static PruneStepResult *get_matching_range_bounds(PartitionPruneContext *context, StrategyNumber opstrategy, Datum *values, int nvalues, @@ -909,7 +910,8 @@ get_matching_partitions(PartitionPruneContext *context, List *pruning_steps) { Assert(context->strategy == PARTITION_STRATEGY_LIST); Assert(partition_bound_accepts_nulls(context->boundinfo)); - result = bms_add_member(result, context->boundinfo->null_index); + result = bms_add_member(result, + get_partition_bound_null_index(context->boundinfo)); } if (scan_default) { @@ -2659,7 +2661,7 @@ get_matching_hash_bounds(PartitionPruneContext *context, */ static PruneStepResult * get_matching_list_bounds(PartitionPruneContext *context, - StrategyNumber opstrategy, Datum value, int nvalues, + StrategyNumber opstrategy, Datum *values, int nvalues, FmgrInfo *partsupfunc, Bitmapset *nullkeys) { PruneStepResult *result = (PruneStepResult *) palloc0(sizeof(PruneStepResult)); @@ -2672,7 +2674,6 @@ get_matching_list_bounds(PartitionPruneContext *context, Oid *partcollation = context->partcollation; Assert(context->strategy == PARTITION_STRATEGY_LIST); - Assert(context->partnatts == 1); result->scan_null = result->scan_default = false; @@ -2710,9 +2711,19 @@ get_matching_list_bounds(PartitionPruneContext *context, */ if (nvalues == 0) { + int i = 0; + int j = 0; + Assert(boundinfo->ndatums > 0); - result->bound_offsets = bms_add_range(NULL, 0, - boundinfo->ndatums - 1); + + for (i = 0; i < boundinfo->ndatums; i++) + { + for (j = 0; j < context->partnatts; j++) + { + if (!boundinfo->isnulls[i][j]) + result->bound_offsets = bms_add_member(result->bound_offsets, i); + } + } result->scan_default = partition_bound_has_default(boundinfo); return result; } @@ -2720,15 +2731,24 @@ get_matching_list_bounds(PartitionPruneContext *context, /* Special case handling of values coming from a <> operator clause. */ if (opstrategy == InvalidStrategy) { + int i = 0; + int j = 0; + /* * First match to all bounds. We'll remove any matching datums below. */ Assert(boundinfo->ndatums > 0); - result->bound_offsets = bms_add_range(NULL, 0, - boundinfo->ndatums - 1); + for (i = 0; i < boundinfo->ndatums; i++) + { + for (j = 0; j < context->partnatts; j++) + { + if (!boundinfo->isnulls[i][j]) + result->bound_offsets = bms_add_member(result->bound_offsets, i); + } + } off = partition_list_bsearch(partsupfunc, partcollation, boundinfo, - value, &is_equal); + nvalues, values, NULL, &is_equal); if (off >= 0 && is_equal) { @@ -2760,8 +2780,8 @@ get_matching_list_bounds(PartitionPruneContext *context, case BTEqualStrategyNumber: off = partition_list_bsearch(partsupfunc, partcollation, - boundinfo, value, - &is_equal); + boundinfo, nvalues, + values, NULL, &is_equal); if (off >= 0 && is_equal) { Assert(boundinfo->indexes[off] >= 0); @@ -2777,8 +2797,9 @@ get_matching_list_bounds(PartitionPruneContext *context, case BTGreaterStrategyNumber: off = partition_list_bsearch(partsupfunc, partcollation, - boundinfo, value, - &is_equal); + boundinfo, nvalues, + values, NULL, &is_equal); + if (off >= 0) { /* We don't want the matched datum to be in the result. */ @@ -2812,8 +2833,9 @@ get_matching_list_bounds(PartitionPruneContext *context, case BTLessStrategyNumber: off = partition_list_bsearch(partsupfunc, partcollation, - boundinfo, value, - &is_equal); + boundinfo, nvalues, + values, NULL, &is_equal); + if (off >= 0 && is_equal && !inclusive) off--; @@ -3452,7 +3474,7 @@ perform_pruning_base_step(PartitionPruneContext *context, case PARTITION_STRATEGY_LIST: return get_matching_list_bounds(context, opstep->opstrategy, - values[0], nvalues, + values, nvalues, &partsupfunc[0], opstep->nullkeys); diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 84ad62c..053aa6b 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9399,13 +9399,11 @@ get_rule_expr(Node *node, deparse_context *context, sep = ""; foreach(cell, spec->listdatums) { - Const *val = castNode(Const, lfirst(cell)); - appendStringInfoString(buf, sep); - get_const_expr(val, context, -1); + appendStringInfoString + (buf, get_list_partbound_value_string(lfirst(cell))); sep = ", "; } - appendStringInfoChar(buf, ')'); break; @@ -11963,6 +11961,45 @@ flatten_reloptions(Oid relid) } /* + * get_list_partbound_value_string + * A C string representation of one list partition bound value + */ +char * +get_list_partbound_value_string(List *bound_value) +{ + StringInfo buf = makeStringInfo(); + StringInfo boundconstraint = makeStringInfo(); + deparse_context context; + ListCell *cell = NULL; + char *sep = ""; + int ncols = 0; + + memset(&context, 0, sizeof(deparse_context)); + context.buf = buf; + + foreach(cell, bound_value) + { + Const *val = castNode(Const, lfirst(cell)); + + appendStringInfoString(buf, sep); + get_const_expr(val, &context, -1); + sep = ", "; + ncols++; + } + + if (ncols > 1) + { + appendStringInfoChar(boundconstraint, '('); + appendStringInfoString(boundconstraint, buf->data); + appendStringInfoChar(boundconstraint, ')'); + + return boundconstraint->data; + } + else + return buf->data; +} + +/* * get_range_partbound_string * A C string representation of one range partition bound */ diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h index ebf3ff1..a4b301b 100644 --- a/src/include/partitioning/partbounds.h +++ b/src/include/partitioning/partbounds.h @@ -67,20 +67,21 @@ typedef struct PartitionBoundInfoData char strategy; /* hash, list or range? */ int ndatums; /* Length of the datums[] array */ Datum **datums; + bool **isnulls; PartitionRangeDatumKind **kind; /* The kind of each range bound datum; * NULL for hash and list partitioned * tables */ int nindexes; /* Length of the indexes[] array */ int *indexes; /* Partition indexes */ - int null_index; /* Index of the null-accepting partition; -1 - * if there isn't one */ int default_index; /* Index of the default partition; -1 if there * isn't one */ } PartitionBoundInfoData; -#define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1) #define partition_bound_has_default(bi) ((bi)->default_index != -1) +extern bool partition_bound_accepts_nulls(PartitionBoundInfo boundinfo); +extern int get_partition_bound_null_index(PartitionBoundInfo boundinfo); + extern int get_hash_partition_greatest_modulus(PartitionBoundInfo b); extern uint64 compute_partition_hash_value(int partnatts, FmgrInfo *partsupfunc, Oid *partcollation, @@ -117,12 +118,17 @@ 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 *values, + bool *isnulls, bool *is_equal); extern int partition_range_datum_bsearch(FmgrInfo *partsupfunc, Oid *partcollation, PartitionBoundInfo boundinfo, int nvalues, Datum *values, bool *is_equal); extern int partition_hash_bsearch(PartitionBoundInfo boundinfo, int modulus, int remainder); - +extern int32 partition_lbound_datum_cmp(FmgrInfo *partsupfunc, + Oid *partcollation, + int nvalues, Datum *lb_datums, + bool *lb_isnulls, Datum *values, + bool *isnulls, bool *is_all_match); #endif /* PARTBOUNDS_H */ diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index d333e5e..60dac6d 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -40,6 +40,7 @@ extern List *select_rtable_names_for_explain(List *rtable, extern char *generate_collation_name(Oid collid); extern char *generate_opclass_name(Oid opclass); extern char *get_range_partbound_string(List *bound_datums); +extern char *get_list_partbound_value_string(List *bound_value); extern char *pg_get_statisticsobjdef_string(Oid statextid); diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index ad89dd0..43f669d 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -352,12 +352,6 @@ CREATE TABLE partitioned ( a int ) INHERITS (some_table) PARTITION BY LIST (a); ERROR: cannot create partitioned table as inheritance child --- cannot use more than 1 column as partition key for list partitioned table -CREATE TABLE partitioned ( - a1 int, - a2 int -) PARTITION BY LIST (a1, a2); -- fail -ERROR: cannot use "list" partition strategy with more than one column -- unsupported constraint type for partitioned tables CREATE TABLE partitioned ( a int, @@ -913,6 +907,34 @@ CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) ERROR: partition "fail_part" would overlap partition "part10" LINE 1: ..._part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalu... ^ +-- now check for multi-column list partition key +CREATE TABLE list_parted3 ( + a int, + b varchar +) PARTITION BY LIST (a, b); +CREATE TABLE list_parted3_p1 PARTITION OF list_parted3 FOR VALUES IN ((1, 'A')); +CREATE TABLE list_parted3_p2 PARTITION OF list_parted3 FOR VALUES IN ((1, 'B'),(1, 'E'), (1, 'E'), (2, 'C'),(2, 'D')); +CREATE TABLE list_parted3_p3 PARTITION OF list_parted3 FOR VALUES IN ((1, NULL),(NULL, 'F')); +CREATE TABLE list_parted3_p4 PARTITION OF list_parted3 FOR VALUES IN ((NULL, NULL)); +CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((1, 'E')); +ERROR: partition "fail_part" would overlap partition "list_parted3_p2" +LINE 1: ...ail_part PARTITION OF list_parted3 FOR VALUES IN ((1, 'E')); + ^ +CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((1, NULL)); +ERROR: partition "fail_part" would overlap partition "list_parted3_p3" +LINE 1: ...il_part PARTITION OF list_parted3 FOR VALUES IN ((1, NULL)); + ^ +CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((NULL, 'F')); +ERROR: partition "fail_part" would overlap partition "list_parted3_p3" +LINE 1: ..._part PARTITION OF list_parted3 FOR VALUES IN ((NULL, 'F')); + ^ +CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((NULL, NULL)); +ERROR: partition "fail_part" would overlap partition "list_parted3_p4" +LINE 1: ...part PARTITION OF list_parted3 FOR VALUES IN ((NULL, NULL)); + ^ +CREATE TABLE list_parted3_default PARTITION OF list_parted3 DEFAULT; +-- cleanup +DROP TABLE list_parted3; -- check for partition bound overlap and other invalid specifications for the hash partition CREATE TABLE hash_parted2 ( a varchar diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out index 5063a3d..174f8c2 100644 --- a/src/test/regress/expected/insert.out +++ b/src/test/regress/expected/insert.out @@ -808,6 +808,63 @@ select tableoid::regclass::text, * from mcrparted order by 1; -- cleanup drop table mcrparted; +-- Test multi-column list partitioning with 3 partition keys +create table mclparted (a int, b text, c int) partition by list (a, b, c); +create table mclparted_p1 partition of mclparted for values in ((1, 'a', 1)); +create table mclparted_p2 partition of mclparted for values in ((1, 'a', 2), (1, 'b', 1), (2, 'a', 1)); +create table mclparted_p3 partition of mclparted for values in ((3, 'c', 3), (4, 'd', 4), (5, 'e', 5), (6, null, 6)); +create table mclparted_p4 partition of mclparted for values in ((null, 'a', 1), (1, null, 1), (1, 'a', null)); +create table mclparted_p5 partition of mclparted for values in ((null, null, null)); +-- routed to mclparted_p1 +insert into mclparted values (1, 'a', 1); +-- routed to mclparted_p2 +insert into mclparted values (1, 'a', 2); +insert into mclparted values (1, 'b', 1); +insert into mclparted values (2, 'a', 1); +-- routed to mclparted_p3 +insert into mclparted values (3, 'c', 3); +insert into mclparted values (4, 'd', 4); +insert into mclparted values (5, 'e', 5); +insert into mclparted values (6, null, 6); +-- routed to mclparted_p4 +insert into mclparted values (null, 'a', 1); +insert into mclparted values (1, null, 1); +insert into mclparted values (1, 'a', null); +-- routed to mclparted_p5 +insert into mclparted values (null, null, null); +-- error cases +insert into mclparted values (10, 'a', 1); +ERROR: no partition of relation "mclparted" found for row +DETAIL: Partition key of the failing row contains (a, b, c) = (10, a, 1). +insert into mclparted values (1, 'z', 1); +ERROR: no partition of relation "mclparted" found for row +DETAIL: Partition key of the failing row contains (a, b, c) = (1, z, 1). +insert into mclparted values (1, 'a', 10); +ERROR: no partition of relation "mclparted" found for row +DETAIL: Partition key of the failing row contains (a, b, c) = (1, a, 10). +insert into mclparted values (1, null, null); +ERROR: no partition of relation "mclparted" found for row +DETAIL: Partition key of the failing row contains (a, b, c) = (1, null, null). +-- check rows +select tableoid::regclass::text, * from mclparted order by 1; + tableoid | a | b | c +--------------+---+---+--- + mclparted_p1 | 1 | a | 1 + mclparted_p2 | 1 | b | 1 + mclparted_p2 | 2 | a | 1 + mclparted_p2 | 1 | a | 2 + mclparted_p3 | 3 | c | 3 + mclparted_p3 | 5 | e | 5 + mclparted_p3 | 6 | | 6 + mclparted_p3 | 4 | d | 4 + mclparted_p4 | | a | 1 + mclparted_p4 | 1 | | 1 + mclparted_p4 | 1 | a | + mclparted_p5 | | | +(12 rows) + +-- cleanup +drop table mclparted; -- check that a BR constraint can't make partition contain violating rows create table brtrigpartcon (a int, b text) partition by list (a); create table brtrigpartcon1 partition of brtrigpartcon for values in (1); @@ -981,6 +1038,96 @@ select tableoid::regclass, * from mcrparted order by a, b; (11 rows) drop table mcrparted; +-- check multi-column list partitioning with partition key constraint +create table mclparted (a text, b int) partition by list(a, b); +create table mclparted_p1 partition of mclparted for values in (('a', 1)); +create table mclparted_p2 partition of mclparted for values in (('a', 2), ('b', 1), ('c', 3), ('d', 3), ('e', 3)); +create table mclparted_p3 partition of mclparted for values in (('a', 3), ('a', 4), ('a', null), (null, 1)); +create table mclparted_p4 partition of mclparted for values in (('b', null), (null, 2)); +create table mclparted_p5 partition of mclparted for values in ((null, null)); +create table mclparted_p6 partition of mclparted DEFAULT; +\d+ mclparted + Partitioned table "public.mclparted" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + a | text | | | | extended | | + b | integer | | | | plain | | +Partition key: LIST (a, b) +Partitions: mclparted_p1 FOR VALUES IN (('a', 1)), + mclparted_p2 FOR VALUES IN (('a', 2), ('b', 1), ('c', 3), ('d', 3), ('e', 3)), + mclparted_p3 FOR VALUES IN (('a', 3), ('a', 4), ('a', NULL), (NULL, 1)), + mclparted_p4 FOR VALUES IN (('b', NULL), (NULL, 2)), + mclparted_p5 FOR VALUES IN ((NULL, NULL)), + mclparted_p6 DEFAULT + +\d+ mclparted_p1 + Table "public.mclparted_p1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + a | text | | | | extended | | + b | integer | | | | plain | | +Partition of: mclparted FOR VALUES IN (('a', 1)) +Partition constraint: (((a = 'a'::text) AND (b = 1))) + +\d+ mclparted_p2 + Table "public.mclparted_p2" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + a | text | | | | extended | | + b | integer | | | | plain | | +Partition of: mclparted FOR VALUES IN (('a', 2), ('b', 1), ('c', 3), ('d', 3), ('e', 3)) +Partition constraint: (((a = 'a'::text) AND (b = 2)) OR ((a = 'b'::text) AND (b = 1)) OR ((a = 'c'::text) AND (b = 3)) OR ((a = 'd'::text) AND (b = 3)) OR ((a = 'e'::text) AND (b = 3))) + +\d+ mclparted_p3 + Table "public.mclparted_p3" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + a | text | | | | extended | | + b | integer | | | | plain | | +Partition of: mclparted FOR VALUES IN (('a', 3), ('a', 4), ('a', NULL), (NULL, 1)) +Partition constraint: (((a = 'a'::text) AND (b = 3)) OR ((a = 'a'::text) AND (b = 4)) OR ((a = 'a'::text) AND (b IS NULL)) OR ((a IS NULL) AND (b = 1))) + +\d+ mclparted_p4 + Table "public.mclparted_p4" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + a | text | | | | extended | | + b | integer | | | | plain | | +Partition of: mclparted FOR VALUES IN (('b', NULL), (NULL, 2)) +Partition constraint: (((a = 'b'::text) AND (b IS NULL)) OR ((a IS NULL) AND (b = 2))) + +\d+ mclparted_p5 + Table "public.mclparted_p5" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + a | text | | | | extended | | + b | integer | | | | plain | | +Partition of: mclparted FOR VALUES IN ((NULL, NULL)) +Partition constraint: (((a IS NULL) AND (b IS NULL))) + +insert into mclparted values ('a', 1), ('a', 2), ('b', 1), ('c', 3), ('d', 3), + ('e', 3), ('a', 3), ('a', 4), ('a', null), (null, 1), ('b', null), + (null, 2), (null, null), ('z', 10); +select tableoid::regclass, * from mclparted order by a, b; + tableoid | a | b +--------------+---+---- + mclparted_p1 | a | 1 + mclparted_p2 | a | 2 + mclparted_p3 | a | 3 + mclparted_p3 | a | 4 + mclparted_p3 | a | + mclparted_p2 | b | 1 + mclparted_p4 | b | + mclparted_p2 | c | 3 + mclparted_p2 | d | 3 + mclparted_p2 | e | 3 + mclparted_p6 | z | 10 + mclparted_p3 | | 1 + mclparted_p4 | | 2 + mclparted_p5 | | +(14 rows) + +drop table mclparted; -- check that wholerow vars in the RETURNING list work with partitioned tables create table returningwrtest (a int) partition by list (a); create table returningwrtest1 partition of returningwrtest for values in (1); diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index 54cbf6c..8462ff0 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -342,12 +342,6 @@ CREATE TABLE partitioned ( a int ) INHERITS (some_table) PARTITION BY LIST (a); --- cannot use more than 1 column as partition key for list partitioned table -CREATE TABLE partitioned ( - a1 int, - a2 int -) PARTITION BY LIST (a1, a2); -- fail - -- unsupported constraint type for partitioned tables CREATE TABLE partitioned ( a int, @@ -725,6 +719,25 @@ CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT; -- more specific ranges CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue); +-- now check for multi-column list partition key +CREATE TABLE list_parted3 ( + a int, + b varchar +) PARTITION BY LIST (a, b); + +CREATE TABLE list_parted3_p1 PARTITION OF list_parted3 FOR VALUES IN ((1, 'A')); +CREATE TABLE list_parted3_p2 PARTITION OF list_parted3 FOR VALUES IN ((1, 'B'),(1, 'E'), (1, 'E'), (2, 'C'),(2, 'D')); +CREATE TABLE list_parted3_p3 PARTITION OF list_parted3 FOR VALUES IN ((1, NULL),(NULL, 'F')); +CREATE TABLE list_parted3_p4 PARTITION OF list_parted3 FOR VALUES IN ((NULL, NULL)); +CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((1, 'E')); +CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((1, NULL)); +CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((NULL, 'F')); +CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((NULL, NULL)); +CREATE TABLE list_parted3_default PARTITION OF list_parted3 DEFAULT; + +-- cleanup +DROP TABLE list_parted3; + -- check for partition bound overlap and other invalid specifications for the hash partition CREATE TABLE hash_parted2 ( a varchar diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql index bfaa8a3..76e0d00 100644 --- a/src/test/regress/sql/insert.sql +++ b/src/test/regress/sql/insert.sql @@ -536,6 +536,48 @@ select tableoid::regclass::text, * from mcrparted order by 1; -- cleanup drop table mcrparted; +-- Test multi-column list partitioning with 3 partition keys +create table mclparted (a int, b text, c int) partition by list (a, b, c); +create table mclparted_p1 partition of mclparted for values in ((1, 'a', 1)); +create table mclparted_p2 partition of mclparted for values in ((1, 'a', 2), (1, 'b', 1), (2, 'a', 1)); +create table mclparted_p3 partition of mclparted for values in ((3, 'c', 3), (4, 'd', 4), (5, 'e', 5), (6, null, 6)); +create table mclparted_p4 partition of mclparted for values in ((null, 'a', 1), (1, null, 1), (1, 'a', null)); +create table mclparted_p5 partition of mclparted for values in ((null, null, null)); + +-- routed to mclparted_p1 +insert into mclparted values (1, 'a', 1); + +-- routed to mclparted_p2 +insert into mclparted values (1, 'a', 2); +insert into mclparted values (1, 'b', 1); +insert into mclparted values (2, 'a', 1); + +-- routed to mclparted_p3 +insert into mclparted values (3, 'c', 3); +insert into mclparted values (4, 'd', 4); +insert into mclparted values (5, 'e', 5); +insert into mclparted values (6, null, 6); + +-- routed to mclparted_p4 +insert into mclparted values (null, 'a', 1); +insert into mclparted values (1, null, 1); +insert into mclparted values (1, 'a', null); + +-- routed to mclparted_p5 +insert into mclparted values (null, null, null); + +-- error cases +insert into mclparted values (10, 'a', 1); +insert into mclparted values (1, 'z', 1); +insert into mclparted values (1, 'a', 10); +insert into mclparted values (1, null, null); + +-- check rows +select tableoid::regclass::text, * from mclparted order by 1; + +-- cleanup +drop table mclparted; + -- check that a BR constraint can't make partition contain violating rows create table brtrigpartcon (a int, b text) partition by list (a); create table brtrigpartcon1 partition of brtrigpartcon for values in (1); @@ -612,6 +654,28 @@ insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10), select tableoid::regclass, * from mcrparted order by a, b; drop table mcrparted; +-- check multi-column list partitioning with partition key constraint +create table mclparted (a text, b int) partition by list(a, b); +create table mclparted_p1 partition of mclparted for values in (('a', 1)); +create table mclparted_p2 partition of mclparted for values in (('a', 2), ('b', 1), ('c', 3), ('d', 3), ('e', 3)); +create table mclparted_p3 partition of mclparted for values in (('a', 3), ('a', 4), ('a', null), (null, 1)); +create table mclparted_p4 partition of mclparted for values in (('b', null), (null, 2)); +create table mclparted_p5 partition of mclparted for values in ((null, null)); +create table mclparted_p6 partition of mclparted DEFAULT; + +\d+ mclparted +\d+ mclparted_p1 +\d+ mclparted_p2 +\d+ mclparted_p3 +\d+ mclparted_p4 +\d+ mclparted_p5 + +insert into mclparted values ('a', 1), ('a', 2), ('b', 1), ('c', 3), ('d', 3), + ('e', 3), ('a', 3), ('a', 4), ('a', null), (null, 1), ('b', null), + (null, 2), (null, null), ('z', 10); +select tableoid::regclass, * from mclparted order by a, b; +drop table mclparted; + -- check that wholerow vars in the RETURNING list work with partitioned tables create table returningwrtest (a int) partition by list (a); create table returningwrtest1 partition of returningwrtest for values in (1);