diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index e01ef86..7bc8a8b 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -90,6 +90,10 @@ typedef struct PartitionBoundInfoData * for range partitioned tables */ int null_index; /* Index of the null-accepting partition; -1 * for range partitioned tables */ + bool has_def; /* Is there a default partition? Currently false + * for a range partitioned table */ + int def_index; /* Index of the default list partition. -1 for + * range partitioned tables */ } PartitionBoundInfoData; /* @@ -118,7 +122,8 @@ static int32 qsort_partition_list_value_cmp(const void *a, const void *b, static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg); -static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec); +static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, + bool is_def, List *boundspecs); static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec); static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy, bool *need_relabel); @@ -166,6 +171,8 @@ RelationBuildPartitionDesc(Relation rel) /* List partitioning specific */ PartitionListValue **all_values = NULL; bool found_null = false; + bool found_def = false; + int def_index = -1; int null_index = -1; /* Range partitioning specific */ @@ -249,9 +256,16 @@ RelationBuildPartitionDesc(Relation rel) foreach(c, spec->listdatums) { + Node *value = lfirst(c); Const *val = lfirst(c); PartitionListValue *list_value = NULL; + if (IsA(value, DefElem)) + { + found_def = true; + def_index = i; + continue; + } if (!val->constisnull) { list_value = (PartitionListValue *) @@ -459,6 +473,7 @@ RelationBuildPartitionDesc(Relation rel) case PARTITION_STRATEGY_LIST: { boundinfo->has_null = found_null; + boundinfo->has_def = found_def; boundinfo->indexes = (int *) palloc(ndatums * sizeof(int)); /* @@ -496,6 +511,11 @@ RelationBuildPartitionDesc(Relation rel) if (mapping[null_index] == -1) mapping[null_index] = next_index++; } + if (found_def) + { + if (mapping[def_index] == -1) + mapping[def_index] = next_index++; + } /* All partition must now have a valid mapping */ Assert(next_index == nparts); @@ -504,6 +524,11 @@ RelationBuildPartitionDesc(Relation rel) boundinfo->null_index = mapping[null_index]; else boundinfo->null_index = -1; + + if (found_def) + boundinfo->def_index = mapping[def_index]; + else + boundinfo->def_index = -1; break; } @@ -690,6 +715,12 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound) boundinfo->strategy == PARTITION_STRATEGY_LIST && (boundinfo->ndatums > 0 || boundinfo->has_null)); + if (boundinfo->has_def) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("parent table \"%s\" has a default partition", + RelationGetRelationName(parent)))); + foreach(cell, spec->listdatums) { Const *val = lfirst(cell); @@ -894,6 +925,12 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound) PartitionBoundSpec *spec = (PartitionBoundSpec *) bound; PartitionKey key = RelationGetPartitionKey(parent); List *my_qual = NIL; + List *inhoids, + *partoids; + List *boundspecs = NIL; + bool is_def = false; + ListCell *cell; + ListCell *cell2; Assert(key != NULL); @@ -901,9 +938,78 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound) { case PARTITION_STRATEGY_LIST: Assert(spec->strategy == PARTITION_STRATEGY_LIST); - my_qual = get_qual_for_list(key, spec); - break; + foreach(cell, spec->listdatums) + { + Node *value = lfirst(cell); + if (IsA(value, DefElem)) + is_def = true; + } + if (is_def) + { + /* Collect bound spec nodes in a list. This is done if the partition is + * a default partition. In case of default partition, constraint is formed + * by performing <> operation over the partition constraints of the + * existing partitions. + */ + inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock); + partoids = NIL; + foreach(cell2, inhoids) + { + Oid inhrelid = lfirst_oid(cell2); + HeapTuple tuple; + Datum datum; + bool isnull; + Node *boundspec; + bool def_elem = false; + PartitionBoundSpec *bspec; + ListCell *cell1; + ListCell *cell3; + + tuple = SearchSysCache1(RELOID, inhrelid); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", inhrelid); + + /* + * It is possible that the pg_class tuple of a partition has not been + * updated yet to set its relpartbound field. The only case where + * this happens is when we open the parent relation to check using its + * partition descriptor that a new partition's bound does not overlap + * some existing partition. + */ + if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition) + { + ReleaseSysCache(tuple); + continue; + } + datum = SysCacheGetAttr(RELOID, tuple, + Anum_pg_class_relpartbound, + &isnull); + Assert(!isnull); + boundspec = (Node *) stringToNode(TextDatumGetCString(datum)); + bspec = (PartitionBoundSpec *)boundspec; + foreach(cell1, bspec->listdatums) + { + Node *value = lfirst(cell1); + if (IsA(value, DefElem)) + def_elem = true; + } + if (def_elem) + { + ReleaseSysCache(tuple); + continue; + } + foreach(cell3, bspec->listdatums) + { + Node *value = lfirst(cell3); + boundspecs = lappend(boundspecs, value); + } + partoids = lappend_oid(partoids, inhrelid); + ReleaseSysCache(tuple); + } + } + my_qual = get_qual_for_list(key, spec, is_def, boundspecs); + break; case PARTITION_STRATEGY_RANGE: Assert(spec->strategy == PARTITION_STRATEGY_RANGE); my_qual = get_qual_for_range(key, spec); @@ -1147,7 +1253,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode, * Returns a list of expressions to use as a list partition's constraint. */ static List * -get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec) +get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, bool is_def, + List *boundspecs) { List *result; ArrayExpr *arr; @@ -1225,7 +1332,10 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec) : key->parttypid[0]; arr->array_collid = key->parttypcoll[0]; arr->element_typeid = key->parttypid[0]; - arr->elements = spec->listdatums; + if (is_def) + arr->elements = boundspecs; + else + arr->elements = spec->listdatums; arr->multidims = false; arr->location = -1; @@ -1239,15 +1349,28 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec) key->partcollation[0], COERCE_EXPLICIT_CAST); + /* Build leftop <> ALL(rightop). This is for default partition */ + if (is_def) + { + opexpr = makeNode(ScalarArrayOpExpr); + opexpr->opno = 518; + opexpr->opfuncid = get_opcode(opexpr->opno); + opexpr->useOr = false; + opexpr->inputcollid = key->partcollation[0]; + opexpr->args = list_make2(keyCol, arr); + opexpr->location = -1; + } /* Build leftop = ANY (rightop) */ - opexpr = makeNode(ScalarArrayOpExpr); - opexpr->opno = operoid; - opexpr->opfuncid = get_opcode(operoid); - opexpr->useOr = true; - opexpr->inputcollid = key->partcollation[0]; - opexpr->args = list_make2(keyCol, arr); - opexpr->location = -1; - + else + { + opexpr = makeNode(ScalarArrayOpExpr); + opexpr->opno = operoid; + opexpr->opfuncid = get_opcode(operoid); + opexpr->useOr = true; + opexpr->inputcollid = key->partcollation[0]; + opexpr->args = list_make2(keyCol, arr); + opexpr->location = -1; + } if (nulltest1) result = list_make2(nulltest1, opexpr); else if (nulltest2) @@ -1771,6 +1894,13 @@ get_partition_for_tuple(PartitionDispatch *pd, result = -1; *failed_at = parent; *failed_slot = slot; + /* + * If partitioned table has a default partition, return + * its sequence number + */ + if (partdesc->boundinfo->has_def && key->strategy + == PARTITION_STRATEGY_LIST) + result = parent->indexes[partdesc->boundinfo->def_index]; break; } else if (parent->indexes[cur_index] >= 0) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 82844a0..5b21dc9 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -2595,6 +2595,7 @@ partbound_datum: Sconst { $$ = makeStringConst($1, @1); } | NumericOnly { $$ = makeAConst($1, @1); } | NULL_P { $$ = makeNullAConst(@1); } + | DEFAULT { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); } ; partbound_datum_list: diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 673276a..ba05ac8 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3088,47 +3088,50 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound) result_spec->listdatums = NIL; foreach(cell, spec->listdatums) { - A_Const *con = (A_Const *) lfirst(cell); - Node *value; - ListCell *cell2; - bool duplicate; - - value = (Node *) make_const(pstate, &con->val, con->location); - value = coerce_to_target_type(pstate, - value, exprType(value), - get_partition_col_typid(key, 0), - get_partition_col_typmod(key, 0), - COERCION_ASSIGNMENT, - COERCE_IMPLICIT_CAST, - -1); - - if (value == NULL) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"", - format_type_be(get_partition_col_typid(key, 0)), - colname), - parser_errposition(pstate, - exprLocation((Node *) con)))); - - /* Simplify the expression */ - value = (Node *) expression_planner((Expr *) value); - - /* Don't add to the result if the value is a duplicate */ - duplicate = false; - foreach(cell2, result_spec->listdatums) + Node *value = lfirst(cell); + /* Perform the transformation only for non default partition */ + if (!IsA(value, DefElem)) { - Const *value2 = (Const *) lfirst(cell2); + A_Const *con = (A_Const *) lfirst(cell); + ListCell *cell2; + bool duplicate; + + value = (Node *) make_const(pstate, &con->val, con->location); + value = coerce_to_target_type(pstate, + value, exprType(value), + get_partition_col_typid(key, 0), + get_partition_col_typmod(key, 0), + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"", + format_type_be(get_partition_col_typid(key, 0)), + colname), + parser_errposition(pstate, + exprLocation((Node *) con)))); - if (equal(value, value2)) + /* Simplify the expression */ + value = (Node *) expression_planner((Expr *) value); + + /* Don't add to the result if the value is a duplicate */ + duplicate = false; + foreach(cell2, result_spec->listdatums) { - duplicate = true; - break; + Const *value2 = (Const *) lfirst(cell2); + + if (equal(value, value2)) + { + duplicate = true; + break; + } } + if (duplicate) + continue; } - if (duplicate) - continue; - result_spec->listdatums = lappend(result_spec->listdatums, value); } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 5c82325..ffdf633 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -8487,8 +8487,13 @@ get_rule_expr(Node *node, deparse_context *context, sep = ""; foreach(cell, spec->listdatums) { + Node *value = lfirst(cell); Const *val = lfirst(cell); - + if (IsA(value, DefElem)) + { + appendStringInfoString(buf, "DEFAULT"); + continue; + } appendStringInfoString(buf, sep); get_const_expr(val, context, -1); sep = ", ";