From: Robert Haas Date: Sat, 13 May 2017 16:04:53 +0000 (-0400) Subject: Teach \d+ to show partitioning constraints. X-Git-Tag: REL_10_BETA1~28 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=1848b73d4576e30c89ba450ad9f169774a6819bf;p=postgresql Teach \d+ to show partitioning constraints. The fact that we didn't have this in the first place is likely why the problem fixed by f8bffe9e6d700fd34759a92e47930ce9ba7dcbd5 escaped detection. Patch by Amit Langote, reviewed and slightly adjusted by me. Discussion: http://postgr.es/m/CA+TgmoYWnV2GMnYLG-Czsix-E1WGAbo4D+0tx7t9NdfYBDMFsA@mail.gmail.com --- diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index 9ef966607f..885c533280 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -976,6 +976,35 @@ RelationGetPartitionQual(Relation rel) return generate_partition_qual(rel); } +/* + * get_partition_qual_relid + * + * Returns an expression tree describing the passed-in relation's partition + * constraint. + */ +Expr * +get_partition_qual_relid(Oid relid) +{ + Relation rel = heap_open(relid, AccessShareLock); + Expr *result = NULL; + List *and_args; + + /* Do the work only if this relation is a partition. */ + if (rel->rd_rel->relispartition) + { + and_args = generate_partition_qual(rel); + if (list_length(and_args) > 1) + result = makeBoolExpr(AND_EXPR, and_args, -1); + else + result = linitial(and_args); + } + + /* Keep the lock. */ + heap_close(rel, NoLock); + + return result; +} + /* * Append OIDs of rel's partitions to the list 'partoids' and for each OID, * append pointer rel to the list 'parents'. diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 983b9800cc..c9bded082e 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -24,6 +24,7 @@ #include "access/sysattr.h" #include "catalog/dependency.h" #include "catalog/indexing.h" +#include "catalog/partition.h" #include "catalog/pg_aggregate.h" #include "catalog/pg_am.h" #include "catalog/pg_authid.h" @@ -1728,6 +1729,37 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags, return buf.data; } +/* + * pg_get_partition_constraintdef + * + * Returns partition constraint expression as a string for the input relation + */ +Datum +pg_get_partition_constraintdef(PG_FUNCTION_ARGS) +{ + Oid relationId = PG_GETARG_OID(0); + Expr *constr_expr; + int prettyFlags; + List *context; + char *consrc; + + constr_expr = get_partition_qual_relid(relationId); + + /* Quick exit if not a partition */ + if (constr_expr == NULL) + PG_RETURN_NULL(); + + /* + * Deparse and return the constraint expression. + */ + prettyFlags = PRETTYFLAG_INDENT; + context = deparse_context_for(get_relation_name(relationId), relationId); + consrc = deparse_expression_pretty((Node *) constr_expr, context, false, + false, prettyFlags, 0); + + PG_RETURN_TEXT_P(string_to_text(consrc)); +} + /* * pg_get_constraintdef * diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 386af6168f..cb17407036 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1858,13 +1858,26 @@ describeOneTableDetails(const char *schemaname, PGresult *result; char *parent_name; char *partdef; + char *partconstraintdef = NULL; - printfPQExpBuffer(&buf, - "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)" - " FROM pg_catalog.pg_class c" - " JOIN pg_catalog.pg_inherits" - " ON c.oid = inhrelid" - " WHERE c.oid = '%s' AND c.relispartition;", oid); + /* If verbose, also request the partition constraint definition */ + if (verbose) + printfPQExpBuffer(&buf, + "SELECT inhparent::pg_catalog.regclass," + " pg_get_expr(c.relpartbound, inhrelid)," + " pg_get_partition_constraintdef(inhrelid)" + " FROM pg_catalog.pg_class c" + " JOIN pg_catalog.pg_inherits" + " ON c.oid = inhrelid" + " WHERE c.oid = '%s' AND c.relispartition;", oid); + else + printfPQExpBuffer(&buf, + "SELECT inhparent::pg_catalog.regclass," + " pg_get_expr(c.relpartbound, inhrelid)" + " FROM pg_catalog.pg_class c" + " JOIN pg_catalog.pg_inherits" + " ON c.oid = inhrelid" + " WHERE c.oid = '%s' AND c.relispartition;", oid); result = PSQLexec(buf.data); if (!result) goto error_return; @@ -1873,9 +1886,21 @@ describeOneTableDetails(const char *schemaname, { parent_name = PQgetvalue(result, 0, 0); partdef = PQgetvalue(result, 0, 1); + + if (PQnfields(result) == 3) + partconstraintdef = PQgetvalue(result, 0, 2); + printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name, partdef); printTableAddFooter(&cont, tmpbuf.data); + + if (partconstraintdef) + { + printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"), + partconstraintdef); + printTableAddFooter(&cont, tmpbuf.data); + } + PQclear(result); } } diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 8e5b95a5ee..7adfd328f6 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201705122 +#define CATALOG_VERSION_NO 201705131 #endif diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h index 421644ca77..25fb0a0440 100644 --- a/src/include/catalog/partition.h +++ b/src/include/catalog/partition.h @@ -80,6 +80,7 @@ extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound) extern List *map_partition_varattnos(List *expr, int target_varno, Relation partrel, Relation parent); extern List *RelationGetPartitionQual(Relation rel); +extern Expr *get_partition_qual_relid(Oid relid); /* For tuple routing */ extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel, diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 8d84d9a890..74346963d9 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -1992,6 +1992,8 @@ DATA(insert OID = 3415 ( pg_get_statisticsextdef PGNSP PGUID 12 1 0 0 0 f f DESCR("index description"); DATA(insert OID = 3352 ( pg_get_partkeydef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ )); DESCR("partition key description"); +DATA(insert OID = 3408 ( pg_get_partition_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partition_constraintdef _null_ _null_ _null_ )); +DESCR("partition constraint description"); DATA(insert OID = 1662 ( pg_get_triggerdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ )); DESCR("trigger description"); DATA(insert OID = 1387 ( pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ )); diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 15d4ce591c..bbf039ccad 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -546,6 +546,7 @@ CREATE TABLE part_forced_oids PARTITION OF oids_parted FOR VALUES FROM (1) TO (1 --------+---------+-----------+----------+---------+---------+--------------+------------- a | integer | | not null | | plain | | Partition of: oids_parted FOR VALUES FROM (1) TO (10) +Partition constraint: ((a >= 1) AND (a < 10)) Has OIDs: yes DROP TABLE oids_parted, part_forced_oids; @@ -643,29 +644,43 @@ CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR -- create a level-2 partition CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10); -- Partition bound in describe output -\d part_b - Table "public.part_b" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | text | | | - b | integer | | not null | 1 +\d+ part_b + Table "public.part_b" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + a | text | | | | extended | | + b | integer | | not null | 1 | plain | | Partition of: parted FOR VALUES IN ('b') +Partition constraint: ((a IS NOT NULL) AND (a = ANY (ARRAY['b'::text]))) Check constraints: "check_a" CHECK (length(a) > 0) "part_b_b_check" CHECK (b >= 0) -- Both partition bound and partition key in describe output -\d part_c - Table "public.part_c" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | text | | | - b | integer | | not null | 0 +\d+ part_c + Table "public.part_c" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + a | text | | | | extended | | + b | integer | | not null | 0 | plain | | Partition of: parted FOR VALUES IN ('c') +Partition constraint: ((a IS NOT NULL) AND (a = ANY (ARRAY['c'::text]))) Partition key: RANGE (b) Check constraints: "check_a" CHECK (length(a) > 0) -Number of partitions: 1 (Use \d+ to list them.) +Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10) + +-- a level-2 partition's constraint will include the parent's expressions +\d+ part_c_1_10 + Table "public.part_c_1_10" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + a | text | | | | extended | | + b | integer | | not null | 0 | plain | | +Partition of: part_c FOR VALUES FROM (1) TO (10) +Partition constraint: ((a IS NOT NULL) AND (a = ANY (ARRAY['c'::text])) AND (b >= 1) AND (b < 10)) +Check constraints: + "check_a" CHECK (length(a) > 0) -- Show partition count in the parent's describe output -- Tempted to include \d+ output listing partitions with bound info but @@ -682,6 +697,54 @@ Check constraints: "check_a" CHECK (length(a) > 0) Number of partitions: 3 (Use \d+ to list them.) +-- check that we get the expected partition constraints +CREATE TABLE range_parted4 (a int, b int, c int) PARTITION BY RANGE (abs(a), abs(b), c); +CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) TO (UNBOUNDED, UNBOUNDED, UNBOUNDED); +\d+ unbounded_range_part + Table "public.unbounded_range_part" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | + c | integer | | not null | | plain | | +Partition of: range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) TO (UNBOUNDED, UNBOUNDED, UNBOUNDED) +Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL)) + +DROP TABLE unbounded_range_part; +CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) TO (1, UNBOUNDED, UNBOUNDED); +\d+ range_parted4_1 + Table "public.range_parted4_1" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | + c | integer | | not null | | plain | | +Partition of: range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) TO (1, UNBOUNDED, UNBOUNDED) +Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (abs(a) <= 1)) + +CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, UNBOUNDED); +\d+ range_parted4_2 + Table "public.range_parted4_2" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | + c | integer | | not null | | plain | | +Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, UNBOUNDED) +Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7)))) + +CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, UNBOUNDED) TO (9, UNBOUNDED, UNBOUNDED); +\d+ range_parted4_3 + Table "public.range_parted4_3" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | + c | integer | | not null | | plain | | +Partition of: range_parted4 FOR VALUES FROM (6, 8, UNBOUNDED) TO (9, UNBOUNDED, UNBOUNDED) +Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9)) + +DROP TABLE range_parted4; -- cleanup DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3; -- comments on partitioned tables columns diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out index 6a1f22ebeb..699309b3b2 100644 --- a/src/test/regress/expected/foreign_data.out +++ b/src/test/regress/expected/foreign_data.out @@ -1844,6 +1844,7 @@ Partitions: pt2_1 FOR VALUES IN (1) c2 | text | | | | | extended | | c3 | date | | | | | plain | | Partition of: pt2 FOR VALUES IN (1) +Partition constraint: ((c1 IS NOT NULL) AND (c1 = ANY (ARRAY[1]))) Server: s0 FDW Options: (delimiter ',', quote '"', "be quoted" 'value') @@ -1914,6 +1915,7 @@ Partitions: pt2_1 FOR VALUES IN (1) c2 | text | | | | | extended | | c3 | date | | | | | plain | | Partition of: pt2 FOR VALUES IN (1) +Partition constraint: ((c1 IS NOT NULL) AND (c1 = ANY (ARRAY[1]))) Server: s0 FDW Options: (delimiter ',', quote '"', "be quoted" 'value') @@ -1941,6 +1943,7 @@ Partitions: pt2_1 FOR VALUES IN (1) c2 | text | | | | | extended | | c3 | date | | not null | | | plain | | Partition of: pt2 FOR VALUES IN (1) +Partition constraint: ((c1 IS NOT NULL) AND (c1 = ANY (ARRAY[1]))) Check constraints: "p21chk" CHECK (c2 <> ''::text) Server: s0 diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index 95035c5947..766f35a3ed 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -598,10 +598,13 @@ CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10); -- Partition bound in describe output -\d part_b +\d+ part_b -- Both partition bound and partition key in describe output -\d part_c +\d+ part_c + +-- a level-2 partition's constraint will include the parent's expressions +\d+ part_c_1_10 -- Show partition count in the parent's describe output -- Tempted to include \d+ output listing partitions with bound info but @@ -609,6 +612,19 @@ CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10); -- returned. \d parted +-- check that we get the expected partition constraints +CREATE TABLE range_parted4 (a int, b int, c int) PARTITION BY RANGE (abs(a), abs(b), c); +CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) TO (UNBOUNDED, UNBOUNDED, UNBOUNDED); +\d+ unbounded_range_part +DROP TABLE unbounded_range_part; +CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) TO (1, UNBOUNDED, UNBOUNDED); +\d+ range_parted4_1 +CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, UNBOUNDED); +\d+ range_parted4_2 +CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, UNBOUNDED) TO (9, UNBOUNDED, UNBOUNDED); +\d+ range_parted4_3 +DROP TABLE range_parted4; + -- cleanup DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3;