From 385ab8bfceafe99f4bcd9b44735f9c524c09ef27 Mon Sep 17 00:00:00 2001 From: amit Date: Tue, 12 Jul 2016 17:50:33 +0900 Subject: [PATCH 4/9] psql and pg_dump support for partitions. Takes care of both the partition bound deparse stuff and handling parent-partition relationship (filtering pg_inherits entries pertaining to partitions and handling appropriately). --- src/backend/utils/adt/ruleutils.c | 78 ++++++++++++++++++++++ src/bin/pg_dump/common.c | 86 ++++++++++++++++++++++++ src/bin/pg_dump/pg_dump.c | 98 ++++++++++++++++++++++++++-- src/bin/pg_dump/pg_dump.h | 12 ++++ src/bin/psql/describe.c | 85 +++++++++++++++++++++---- src/test/regress/expected/create_table.out | 39 +++++++++++ src/test/regress/sql/create_table.sql | 12 ++++ 7 files changed, 393 insertions(+), 17 deletions(-) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 03be202..4bc4e91 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -8405,6 +8405,84 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_PartitionListSpec: + { + PartitionListSpec *list_spec = (PartitionListSpec *) node; + ListCell *cell; + char *sep; + + appendStringInfoString(buf, "FOR VALUES"); + + appendStringInfoString(buf, " IN ("); + sep = ""; + foreach (cell, list_spec->values) + { + Const *val = lfirst(cell); + + appendStringInfoString(buf, sep); + get_const_expr(val, context, -1); + sep = ", "; + } + + appendStringInfoString(buf, ")"); + } + break; + + case T_PartitionRangeSpec: + { + PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node; + ListCell *cell; + char *sep; + + appendStringInfoString(buf, "FOR VALUES"); + + appendStringInfoString(buf, " START"); + if (!range_spec->lower) + appendStringInfoString(buf, " UNBOUNDED"); + else + { + appendStringInfoString(buf, " ("); + + sep = ""; + foreach (cell, range_spec->lower) + { + Const *val = lfirst(cell); + + appendStringInfoString(buf, sep); + get_const_expr(val, context, -1); + sep = ", "; + } + appendStringInfoString(buf, ")"); + + if (!range_spec->lowerinc) + appendStringInfoString(buf, " EXCLUSIVE"); + } + + appendStringInfoString(buf, " END"); + + if (!range_spec->upper) + appendStringInfoString(buf, " UNBOUNDED"); + else + { + appendStringInfoString(buf, " ("); + + sep = ""; + foreach (cell, range_spec->upper) + { + Const *val = lfirst(cell); + + appendStringInfoString(buf, sep); + get_const_expr(val, context, -1); + sep = ", "; + } + appendStringInfoString(buf, ")"); + + if (range_spec->upperinc) + appendStringInfoString(buf, " INCLUSIVE"); + } + } + break; + case T_List: { char *sep; diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 1cbb987..c8e56bd 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -68,6 +68,8 @@ static int numextmembers; static void flagInhTables(TableInfo *tbinfo, int numTables, InhInfo *inhinfo, int numInherits); +static void flagPartitions(TableInfo *tblinfo, int numTables, + PartInfo *partinfo, int numPartitions); static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables); static DumpableObject **buildIndexArray(void *objArray, int numObjs, Size objSize); @@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2); static int ExtensionMemberIdCompare(const void *p1, const void *p2); static void findParentsByOid(TableInfo *self, InhInfo *inhinfo, int numInherits); +static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo, + int numPartitions); static int strInArray(const char *pattern, char **arr, int arr_size); @@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr) NamespaceInfo *nspinfo; ExtensionInfo *extinfo; InhInfo *inhinfo; + PartInfo *partinfo; int numAggregates; int numInherits; + int numPartitions; int numRules; int numProcLangs; int numCasts; @@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr) inhinfo = getInherits(fout, &numInherits); if (g_verbose) + write_msg(NULL, "reading partition information\n"); + partinfo = getPartitions(fout, &numPartitions); + + if (g_verbose) write_msg(NULL, "reading event triggers\n"); getEventTriggers(fout, &numEventTriggers); @@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr) write_msg(NULL, "finding inheritance relationships\n"); flagInhTables(tblinfo, numTables, inhinfo, numInherits); + /* Link tables to partition parents, mark parents as interesting */ + if (g_verbose) + write_msg(NULL, "finding partition relationships\n"); + flagPartitions(tblinfo, numTables, partinfo, numPartitions); + if (g_verbose) write_msg(NULL, "reading column info for interesting tables\n"); getTableAttrs(fout, tblinfo, numTables); @@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables, } } +/* flagPartitions - + * Fill in parent link fields of every target table that is partition, + * and mark parents of partitions as interesting + * + * modifies tblinfo + */ +static void +flagPartitions(TableInfo *tblinfo, int numTables, + PartInfo *partinfo, int numPartitions) +{ + int i; + + for (i = 0; i < numTables; i++) + { + /* Some kinds are never partitions */ + if (tblinfo[i].relkind == RELKIND_SEQUENCE || + tblinfo[i].relkind == RELKIND_VIEW || + tblinfo[i].relkind == RELKIND_MATVIEW) + continue; + + /* Don't bother computing anything for non-target tables, either */ + if (!tblinfo[i].dobj.dump) + continue; + + /* Find the parent TableInfo and save */ + findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions); + + /* Mark the parent as interesting for getTableAttrs */ + if (tblinfo[i].partitionOf) + { + tblinfo[i].partitionOf->interesting = true; + addObjectDependency(&tblinfo[i].dobj, + tblinfo[i].partitionOf->dobj.dumpId); + } + } +} + /* flagInhAttrs - * for each dumpable table in tblinfo, flag its inherited attributes * @@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self, } /* + * findPartitionParentByOid + * find a partition's parent in tblinfo[] + */ +static void +findPartitionParentByOid(TableInfo *self, PartInfo *partinfo, + int numPartitions) +{ + Oid oid = self->dobj.catId.oid; + int i; + + for (i = 0; i < numPartitions; i++) + { + if (partinfo[i].partrelid == oid) + { + TableInfo *parent; + + parent = findTableByOid(partinfo[i].partparent); + if (parent == NULL) + { + write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n", + partinfo[i].partparent, + self->dobj.name, + oid); + exit_nicely(1); + } + self->partitionOf = parent; + + /* While we're at it, also save the partdef */ + self->partitiondef = partinfo[i].partdef; + } + } +} + +/* * parseOidArray * parse a string of numbers delimited by spaces into a character array * diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index c805a84..40ab7cc 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -6134,6 +6134,63 @@ getInherits(Archive *fout, int *numInherits) } /* + * getPartitions + * read all the partition inheritance and partition bound information + * from the system catalogs return them in the PartInfo* structure + * + * numPartitions is set to the number of pairs read in + */ +PartInfo * +getPartitions(Archive *fout, int *numPartitions) +{ + PGresult *res; + int ntups; + int i; + PQExpBuffer query = createPQExpBuffer(); + PartInfo *partinfo; + + int i_partrelid; + int i_partparent; + int i_partbound; + + /* Make sure we are in proper schema */ + selectSourceSchema(fout, "pg_catalog"); + + /* find all the inheritance information */ + + appendPQExpBufferStr(query, + "SELECT inhrelid as partrelid, inhparent AS partparent," + " pg_get_expr(relpartbound, inhrelid) AS partbound" + " FROM pg_class c, pg_inherits" + " WHERE c.oid = inhrelid AND c.relispartition"); + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + *numPartitions = ntups; + + partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo)); + + i_partrelid = PQfnumber(res, "partrelid"); + i_partparent = PQfnumber(res, "partparent"); + i_partbound = PQfnumber(res, "partbound"); + + for (i = 0; i < ntups; i++) + { + partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid)); + partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent)); + partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound)); + } + + PQclear(res); + + destroyPQExpBuffer(query); + + return partinfo; +} + +/* * getIndexes * get information about every index on a dumpable table * @@ -15311,6 +15368,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) if (tbinfo->reloftype && !dopt->binary_upgrade) appendPQExpBuffer(q, " OF %s", tbinfo->reloftype); + if (tbinfo->partitionOf && !dopt->binary_upgrade) + { + TableInfo *parentRel = tbinfo->partitionOf; + + appendPQExpBuffer(q, " PARTITION OF "); + if (parentRel->dobj.namespace != tbinfo->dobj.namespace) + appendPQExpBuffer(q, "%s.", + fmtId(parentRel->dobj.namespace->dobj.name)); + appendPQExpBufferStr(q, fmtId(parentRel->dobj.name)); + } + if (tbinfo->relkind != RELKIND_MATVIEW) { /* Dump the attributes */ @@ -15339,8 +15407,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) (!tbinfo->inhNotNull[j] || dopt->binary_upgrade)); - /* Skip column if fully defined by reloftype */ - if (tbinfo->reloftype && + /* + * Skip column if fully defined by reloftype or the + * partition parent. + */ + if ((tbinfo->reloftype || tbinfo->partitionOf) && !has_default && !has_notnull && !dopt->binary_upgrade) continue; @@ -15369,7 +15440,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) } /* Attribute type */ - if (tbinfo->reloftype && !dopt->binary_upgrade) + if ((tbinfo->reloftype || tbinfo->partitionOf) && + !dopt->binary_upgrade) { appendPQExpBufferStr(q, " WITH OPTIONS"); } @@ -15434,15 +15506,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) if (actual_atts) appendPQExpBufferStr(q, "\n)"); - else if (!(tbinfo->reloftype && !dopt->binary_upgrade)) + else if (!((tbinfo->reloftype || tbinfo->partitionOf) && + !dopt->binary_upgrade)) { /* * We must have a parenthesized attribute list, even though - * empty, when not using the OF TYPE syntax. + * empty, when not using the OF TYPE or PARTITION OF syntax. */ appendPQExpBufferStr(q, " (\n)"); } + if (tbinfo->partitiondef && !dopt->binary_upgrade) + { + appendPQExpBufferStr(q, "\n"); + appendPQExpBufferStr(q, tbinfo->partitiondef); + } + if (numParents > 0 && !dopt->binary_upgrade) { appendPQExpBufferStr(q, "\nINHERITS ("); @@ -15612,6 +15691,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) tbinfo->reloftype); } + if (tbinfo->partitionOf) + { + appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n"); + appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n", + fmtId(tbinfo->partitionOf->dobj.name), + tbinfo->dobj.name, + tbinfo->partitiondef); + } + appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n"); appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n" "SET relfrozenxid = '%u', relminmxid = '%u'\n" diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 0292859..760067a 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -320,6 +320,8 @@ typedef struct _tableInfo struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */ int numTriggers; /* number of triggers for table */ struct _triggerInfo *triggers; /* array of TriggerInfo structs */ + struct _tableInfo *partitionOf; /* TableInfo for the partition parent */ + char *partitiondef; /* partition key definition */ } TableInfo; typedef struct _attrDefInfo @@ -460,6 +462,15 @@ typedef struct _inhInfo Oid inhparent; /* OID of its parent */ } InhInfo; +/* PartInfo isn't a DumpableObject, just temporary state */ +typedef struct _partInfo +{ + Oid partrelid; /* OID of a partition */ + Oid partparent; /* OID of its parent */ + char *partdef; /* partition bound definition */ +} PartInfo; + + typedef struct _prsInfo { DumpableObject dobj; @@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions); extern TableInfo *getTables(Archive *fout, int *numTables); extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables); extern InhInfo *getInherits(Archive *fout, int *numInherits); +extern PartInfo *getPartitions(Archive *fout, int *numPartitions); extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables); extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables); extern RuleInfo *getRules(Archive *fout, int *numRules); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 10d924a..0b763ee 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1798,6 +1798,34 @@ describeOneTableDetails(const char *schemaname, } /* Make footers */ + if (pset.sversion >= 90600) + { + /* Get the partition information */ + PGresult *result; + char *parent_name; + char *partdef; + + 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; + + if (PQntuples(result) > 0) + { + parent_name = PQgetvalue(result, 0, 0); + partdef = PQgetvalue(result, 0, 1); + printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name, + partdef); + printTableAddFooter(&cont, tmpbuf.data); + PQclear(result); + } + } + if (tableinfo.relkind == 'P') { /* Get the partition key information */ @@ -2559,8 +2587,12 @@ describeOneTableDetails(const char *schemaname, PQclear(result); } - /* print inherited tables */ - printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid); + /* print inherited tables (exclude, if parent is a partitioned table) */ + printfPQExpBuffer(&buf, + "SELECT c.oid::pg_catalog.regclass" + " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i" + " WHERE c.oid=i.inhparent AND i.inhrelid = '%s'" + " AND c.relkind != 'P' ORDER BY inhseqno;", oid); result = PSQLexec(buf.data); if (!result) @@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname, PQclear(result); } - /* print child tables */ - if (pset.sversion >= 80300) - printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid); + /* print child tables (with additional info if partitions) */ + if (pset.sversion >= 100000) + printfPQExpBuffer(&buf, + "SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)" + " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i" + " WHERE c.oid=i.inhrelid AND" + " i.inhparent = '%s' AND" + " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')" + " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid); + else if (pset.sversion >= 80300) + printfPQExpBuffer(&buf, + "SELECT c.oid::pg_catalog.regclass" + " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i" + " WHERE c.oid=i.inhrelid AND" + " i.inhparent = '%s' AND" + " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')" + " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid); else printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid); @@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname, /* print the number of child tables, if any */ if (tuples > 0) { - printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples); + if (tableinfo.relkind != 'P') + printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples); + else + printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples); printTableAddFooter(&cont, buf.data); } } else { /* display the list of child tables */ - const char *ct = _("Child tables"); + const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions"); int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding); for (i = 0; i < tuples; i++) { - if (i == 0) - printfPQExpBuffer(&buf, "%s: %s", - ct, PQgetvalue(result, i, 0)); + if (tableinfo.relkind != 'P') + { + if (i == 0) + printfPQExpBuffer(&buf, "%s: %s", + ct, PQgetvalue(result, i, 0)); + else + printfPQExpBuffer(&buf, "%*s %s", + ctw, "", PQgetvalue(result, i, 0)); + } else - printfPQExpBuffer(&buf, "%*s %s", - ctw, "", PQgetvalue(result, i, 0)); + { + if (i == 0) + printfPQExpBuffer(&buf, "%s: %s %s", + ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1)); + else + printfPQExpBuffer(&buf, "%*s %s %s", + ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1)); + } if (i < tuples - 1) appendPQExpBufferChar(&buf, ','); diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 2b12276..732bf80 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -592,6 +592,45 @@ ERROR: column "c" named in partition key does not exist CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (b); -- create a partition of partition CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10); +-- Partition bound in describe output +\d part_b + Table "public.part_b" + Column | Type | Modifiers +--------+---------+--------------------- + a | text | + b | integer | not null default 10 +Partition Of: parted FOR VALUES IN ('b') +Check constraints: + "check_b" CHECK (b > 0) + +-- Both partition bound and partition key in describe output +\d part_c + Table "public.part_c" + Column | Type | Modifiers +--------+---------+-------------------- + a | text | + b | integer | not null default 1 +Partition Of: parted FOR VALUES IN ('c') +Partition Key: RANGE (b) +Check constraints: + "check_b" CHECK (b > 0) +Number of partitions: 1 (Use \d+ to list them.) + +-- Show partition count in the parent's describe output +-- Tempted to include \d+ output listing partitions with bound info but +-- output could vary depending on the order in which partition oids are +-- returned. +\d parted + Table "public.parted" + Column | Type | Modifiers +--------+---------+-------------------- + a | text | + b | integer | not null default 1 +Partition Key: LIST (a) +Check constraints: + "check_b" CHECK (b > 0) +Number of partitions: 3 (Use \d+ to list them.) + -- partition cannot be dropped directly DROP TABLE part_a; ERROR: "part_a" is a partition of "parted" diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index c38312e..8f52a85 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -546,6 +546,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ( -- create a partition of partition CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10); +-- Partition bound in describe output +\d part_b + +-- Both partition bound and partition key in describe output +\d part_c + +-- Show partition count in the parent's describe output +-- Tempted to include \d+ output listing partitions with bound info but +-- output could vary depending on the order in which partition oids are +-- returned. +\d parted + -- partition cannot be dropped directly DROP TABLE part_a; -- 1.7.1