diff options
-rw-r--r-- | contrib/postgres_fdw/expected/postgres_fdw.out | 5 | ||||
-rw-r--r-- | contrib/postgres_fdw/sql/postgres_fdw.sql | 3 | ||||
-rw-r--r-- | doc/src/sgml/ref/copy.sgml | 11 | ||||
-rw-r--r-- | doc/src/sgml/ref/pg_checksums.sgml | 4 | ||||
-rw-r--r-- | src/backend/commands/copy.c | 6 | ||||
-rw-r--r-- | src/backend/commands/copyto.c | 153 | ||||
-rw-r--r-- | src/bin/pg_checksums/pg_checksums.c | 17 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_dump.c | 7 | ||||
-rw-r--r-- | src/port/win32env.c | 7 | ||||
-rw-r--r-- | src/test/regress/expected/copy.out | 20 | ||||
-rw-r--r-- | src/test/regress/expected/rowsecurity.out | 21 | ||||
-rw-r--r-- | src/test/regress/sql/copy.sql | 14 | ||||
-rw-r--r-- | src/test/regress/sql/rowsecurity.sql | 3 | ||||
-rw-r--r-- | src/test/ssl/t/001_ssltests.pl | 8 |
14 files changed, 233 insertions, 46 deletions
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 91bbd0d8c73..cd28126049d 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -11599,6 +11599,11 @@ SELECT * FROM result_tbl ORDER BY a; (3 rows) DELETE FROM result_tbl; +-- Test COPY TO when foreign table is partition +COPY async_pt TO stdout; --error +ERROR: cannot copy from foreign table "async_p1" +DETAIL: Partition "async_p1" is a foreign table in partitioned table "async_pt" +HINT: Try the COPY (SELECT ...) TO variant. DROP FOREIGN TABLE async_p3; DROP TABLE base_tbl3; -- Check case where the partitioned table has local/remote partitions diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 3b7da128519..9a8f9e28135 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -3941,6 +3941,9 @@ INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; SELECT * FROM result_tbl ORDER BY a; DELETE FROM result_tbl; +-- Test COPY TO when foreign table is partition +COPY async_pt TO stdout; --error + DROP FOREIGN TABLE async_p3; DROP TABLE base_tbl3; diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml index c2d1fbc1fbe..fdc24b36bb8 100644 --- a/doc/src/sgml/ref/copy.sgml +++ b/doc/src/sgml/ref/copy.sgml @@ -539,13 +539,14 @@ COPY <replaceable class="parameter">count</replaceable> <para> <command>COPY TO</command> can be used with plain - tables and populated materialized views. - For example, - <literal>COPY <replaceable class="parameter">table</replaceable> - TO</literal> copies the same rows as + tables, populated materialized views, and partitioned tables. + For non-partitioned tables, COPY <replaceable class="parameter">table</replaceable> + copies the same rows as <literal>SELECT * FROM ONLY <replaceable class="parameter">table</replaceable></literal>. + For partitioned tables, it copies the same rows as + <literal>SELECT * FROM <replaceable class="parameter">table</replaceable></literal>. However it doesn't directly support other relation types, - such as partitioned tables, inheritance child tables, or views. + such as inheritance child tables, or views. To copy all rows from such relations, use <literal>COPY (SELECT * FROM <replaceable class="parameter">table</replaceable>) TO</literal>. </para> diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml index 95043aa329c..e9e393495df 100644 --- a/doc/src/sgml/ref/pg_checksums.sgml +++ b/doc/src/sgml/ref/pg_checksums.sgml @@ -247,5 +247,9 @@ PostgreSQL documentation remains unchanged, and <application>pg_checksums</application> can be re-run to perform the same operation. </para> + <para> + The target cluster must have the same major version as + <application>pg_checksums</application>. + </para> </refsect1> </refentry> diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index fae9c41db65..44020d0ae80 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -251,11 +251,15 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, * relation which we have opened and locked. Use "ONLY" so that * COPY retrieves rows from only the target table not any * inheritance children, the same as when RLS doesn't apply. + * + * However, when copying data from a partitioned table, we don't + * use "ONLY", since we need to retrieve rows from its descendant + * tables too. */ from = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)), pstrdup(RelationGetRelationName(rel)), -1); - from->inh = false; /* apply ONLY */ + from->inh = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); /* Build query */ select = makeNode(SelectStmt); diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index e5781155cdf..a1919c6db43 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -18,7 +18,9 @@ #include <unistd.h> #include <sys/stat.h> +#include "access/table.h" #include "access/tableam.h" +#include "catalog/pg_inherits.h" #include "commands/copyapi.h" #include "commands/progress.h" #include "executor/execdesc.h" @@ -86,6 +88,7 @@ typedef struct CopyToStateData CopyFormatOptions opts; Node *whereClause; /* WHERE condition (or NULL) */ + List *partitions; /* OID list of partitions to copy data from */ /* * Working state @@ -116,6 +119,8 @@ static void CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot); static void CopyAttributeOutText(CopyToState cstate, const char *string); static void CopyAttributeOutCSV(CopyToState cstate, const char *string, bool use_quote); +static void CopyRelationTo(CopyToState cstate, Relation rel, Relation root_rel, + uint64 *processed); /* built-in format-specific routines */ static void CopyToTextLikeStart(CopyToState cstate, TupleDesc tupDesc); @@ -602,6 +607,10 @@ EndCopy(CopyToState cstate) pgstat_progress_end_command(); MemoryContextDelete(cstate->copycontext); + + if (cstate->partitions) + list_free(cstate->partitions); + pfree(cstate); } @@ -643,6 +652,7 @@ BeginCopyTo(ParseState *pstate, PROGRESS_COPY_COMMAND_TO, 0 }; + List *children = NIL; if (rel != NULL && rel->rd_rel->relkind != RELKIND_RELATION) { @@ -673,11 +683,34 @@ BeginCopyTo(ParseState *pstate, errmsg("cannot copy from sequence \"%s\"", RelationGetRelationName(rel)))); else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot copy from partitioned table \"%s\"", - RelationGetRelationName(rel)), - errhint("Try the COPY (SELECT ...) TO variant."))); + { + /* + * Collect OIDs of relation containing data, so that later + * DoCopyTo can copy the data from them. + */ + children = find_all_inheritors(RelationGetRelid(rel), AccessShareLock, NULL); + + foreach_oid(child, children) + { + char relkind = get_rel_relkind(child); + + if (relkind == RELKIND_FOREIGN_TABLE) + { + char *relation_name = get_rel_name(child); + + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot copy from foreign table \"%s\"", relation_name), + errdetail("Partition \"%s\" is a foreign table in partitioned table \"%s\"", + relation_name, RelationGetRelationName(rel)), + errhint("Try the COPY (SELECT ...) TO variant.")); + } + + /* Exclude tables with no data */ + if (RELKIND_HAS_PARTITIONS(relkind)) + children = foreach_delete_current(children, child); + } + } else ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -713,6 +746,7 @@ BeginCopyTo(ParseState *pstate, cstate->rel = rel; tupDesc = RelationGetDescr(cstate->rel); + cstate->partitions = children; } else { @@ -722,6 +756,7 @@ BeginCopyTo(ParseState *pstate, DestReceiver *dest; cstate->rel = NULL; + cstate->partitions = NIL; /* * Run parse analysis and rewrite. Note this also acquires sufficient @@ -1030,7 +1065,7 @@ DoCopyTo(CopyToState cstate) TupleDesc tupDesc; int num_phys_attrs; ListCell *cur; - uint64 processed; + uint64 processed = 0; if (fe_copy) SendCopyBegin(cstate); @@ -1070,33 +1105,24 @@ DoCopyTo(CopyToState cstate) if (cstate->rel) { - TupleTableSlot *slot; - TableScanDesc scandesc; - - scandesc = table_beginscan(cstate->rel, GetActiveSnapshot(), 0, NULL); - slot = table_slot_create(cstate->rel, NULL); - - processed = 0; - while (table_scan_getnextslot(scandesc, ForwardScanDirection, slot)) + /* + * If COPY TO source table is a partitioned table, then open each + * partition and process each individual partition. + */ + if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { - CHECK_FOR_INTERRUPTS(); - - /* Deconstruct the tuple ... */ - slot_getallattrs(slot); - - /* Format and send the data */ - CopyOneRowTo(cstate, slot); + foreach_oid(child, cstate->partitions) + { + Relation scan_rel; - /* - * Increment the number of processed tuples, and report the - * progress. - */ - pgstat_progress_update_param(PROGRESS_COPY_TUPLES_PROCESSED, - ++processed); + /* We already got the lock in BeginCopyTo */ + scan_rel = table_open(child, NoLock); + CopyRelationTo(cstate, scan_rel, cstate->rel, &processed); + table_close(scan_rel, NoLock); + } } - - ExecDropSingleTupleTableSlot(slot); - table_endscan(scandesc); + else + CopyRelationTo(cstate, cstate->rel, NULL, &processed); } else { @@ -1116,6 +1142,73 @@ DoCopyTo(CopyToState cstate) } /* + * Scans a single table and exports its rows to the COPY destination. + * + * root_rel can be set to the root table of rel if rel is a partition + * table so that we can send tuples in root_rel's rowtype, which might + * differ from individual partitions. +*/ +static void +CopyRelationTo(CopyToState cstate, Relation rel, Relation root_rel, uint64 *processed) +{ + TupleTableSlot *slot; + TableScanDesc scandesc; + AttrMap *map = NULL; + TupleTableSlot *root_slot = NULL; + + scandesc = table_beginscan(rel, GetActiveSnapshot(), 0, NULL); + slot = table_slot_create(rel, NULL); + + /* + * If we are exporting partition data here, we check if converting tuples + * to the root table's rowtype, because a partition might have column + * order different than its root table. + */ + if (root_rel != NULL) + { + root_slot = table_slot_create(root_rel, NULL); + map = build_attrmap_by_name_if_req(RelationGetDescr(root_rel), + RelationGetDescr(rel), + false); + } + + while (table_scan_getnextslot(scandesc, ForwardScanDirection, slot)) + { + TupleTableSlot *copyslot; + + CHECK_FOR_INTERRUPTS(); + + if (map != NULL) + copyslot = execute_attr_map_slot(map, slot, root_slot); + else + { + /* Deconstruct the tuple */ + slot_getallattrs(slot); + copyslot = slot; + } + + /* Format and send the data */ + CopyOneRowTo(cstate, copyslot); + + /* + * Increment the number of processed tuples, and report the progress. + */ + pgstat_progress_update_param(PROGRESS_COPY_TUPLES_PROCESSED, + ++(*processed)); + } + + ExecDropSingleTupleTableSlot(slot); + + if (root_slot != NULL) + ExecDropSingleTupleTableSlot(root_slot); + + if (map != NULL) + free_attrmap(map); + + table_endscan(scandesc); +} + +/* * Emit one row during DoCopyTo(). */ static inline void diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c index f20be82862a..46cb2f36efa 100644 --- a/src/bin/pg_checksums/pg_checksums.c +++ b/src/bin/pg_checksums/pg_checksums.c @@ -25,6 +25,7 @@ #include "common/logging.h" #include "common/relpath.h" #include "fe_utils/option_utils.h" +#include "fe_utils/version.h" #include "getopt_long.h" #include "pg_getopt.h" #include "storage/bufpage.h" @@ -448,6 +449,8 @@ main(int argc, char *argv[]) int c; int option_index; bool crc_ok; + uint32 major_version; + char *version_str; pg_logging_init(argv[0]); set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_checksums")); @@ -543,6 +546,20 @@ main(int argc, char *argv[]) exit(1); } + /* + * Retrieve the contents of this cluster's PG_VERSION. We require + * compatibility with the same major version as the one this tool is + * compiled with. + */ + major_version = GET_PG_MAJORVERSION_NUM(get_pg_version(DataDir, &version_str)); + if (major_version != PG_MAJORVERSION_NUM) + { + pg_log_error("data directory is of wrong version"); + pg_log_error_detail("File \"%s\" contains \"%s\", which is not compatible with this program's version \"%s\".", + "PG_VERSION", version_str, PG_MAJORVERSION); + exit(1); + } + /* Read the control file and check compatibility */ ControlFile = get_controlfile(DataDir, &crc_ok); if (!crc_ok) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 4b8cd49df09..47913178a93 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -12023,17 +12023,12 @@ dumpExtension(Archive *fout, const ExtensionInfo *extinfo) .createStmt = q->data, .dropStmt = delq->data)); - /* Dump Extension Comments and Security Labels */ + /* Dump Extension Comments */ if (extinfo->dobj.dump & DUMP_COMPONENT_COMMENT) dumpComment(fout, "EXTENSION", qextname, NULL, "", extinfo->dobj.catId, 0, extinfo->dobj.dumpId); - if (extinfo->dobj.dump & DUMP_COMPONENT_SECLABEL) - dumpSecLabel(fout, "EXTENSION", qextname, - NULL, "", - extinfo->dobj.catId, 0, extinfo->dobj.dumpId); - free(qextname); destroyPQExpBuffer(q); diff --git a/src/port/win32env.c b/src/port/win32env.c index b22fbafde40..e1cee683dbf 100644 --- a/src/port/win32env.c +++ b/src/port/win32env.c @@ -152,6 +152,13 @@ pgwin32_unsetenv(const char *name) int res; char *envbuf; + /* Error conditions, per POSIX */ + if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL) + { + errno = EINVAL; + return -1; + } + envbuf = (char *) malloc(strlen(name) + 2); if (!envbuf) return -1; diff --git a/src/test/regress/expected/copy.out b/src/test/regress/expected/copy.out index ac66eb55aee..24e0f472f14 100644 --- a/src/test/regress/expected/copy.out +++ b/src/test/regress/expected/copy.out @@ -373,3 +373,23 @@ COPY copytest_mv(id) TO stdout WITH (header); id 1 DROP MATERIALIZED VIEW copytest_mv; +-- Tests for COPY TO with partitioned tables. +-- The child table pp_2 has a different column order than the root table pp. +-- Check if COPY TO exports tuples as the root table's column order. +CREATE TABLE pp (id int,val int) PARTITION BY RANGE (id); +CREATE TABLE pp_1 (val int, id int) PARTITION BY RANGE (id); +CREATE TABLE pp_2 (id int, val int) PARTITION BY RANGE (id); +ALTER TABLE pp ATTACH PARTITION pp_1 FOR VALUES FROM (1) TO (5); +ALTER TABLE pp ATTACH PARTITION pp_2 FOR VALUES FROM (5) TO (10); +CREATE TABLE pp_15 PARTITION OF pp_1 FOR VALUES FROM (1) TO (5); +CREATE TABLE pp_510 PARTITION OF pp_2 FOR VALUES FROM (5) TO (10); +INSERT INTO pp SELECT g, 10 + g FROM generate_series(1,6) g; +COPY pp TO stdout(header); +id val +1 11 +2 12 +3 13 +4 14 +5 15 +6 16 +DROP TABLE PP; diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 5a172c5d91c..42b78a24603 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -986,6 +986,11 @@ NOTICE: f_leak => my first satire 9 | 11 | 1 | regress_rls_dave | awesome science fiction (4 rows) +COPY part_document TO stdout WITH (DELIMITER ','); +1,11,1,regress_rls_bob,my first novel +6,11,1,regress_rls_carol,great science fiction +9,11,1,regress_rls_dave,awesome science fiction +4,55,1,regress_rls_bob,my first satire EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); QUERY PLAN ------------------------------------------------------------------------- @@ -1028,6 +1033,17 @@ NOTICE: f_leak => awesome technology book 10 | 99 | 2 | regress_rls_dave | awesome technology book (10 rows) +COPY part_document TO stdout WITH (DELIMITER ','); +1,11,1,regress_rls_bob,my first novel +2,11,2,regress_rls_bob,my second novel +6,11,1,regress_rls_carol,great science fiction +9,11,1,regress_rls_dave,awesome science fiction +4,55,1,regress_rls_bob,my first satire +8,55,2,regress_rls_carol,great satire +3,99,2,regress_rls_bob,my science textbook +5,99,2,regress_rls_bob,my history book +7,99,2,regress_rls_carol,great technology book +10,99,2,regress_rls_dave,awesome technology book EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); QUERY PLAN ------------------------------------------------------------------------- @@ -1058,6 +1074,11 @@ NOTICE: f_leak => awesome science fiction 9 | 11 | 1 | regress_rls_dave | awesome science fiction (4 rows) +COPY part_document TO stdout WITH (DELIMITER ','); +1,11,1,regress_rls_bob,my first novel +2,11,2,regress_rls_bob,my second novel +6,11,1,regress_rls_carol,great science fiction +9,11,1,regress_rls_dave,awesome science fiction EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); QUERY PLAN ---------------------------------------------------------------------------------- diff --git a/src/test/regress/sql/copy.sql b/src/test/regress/sql/copy.sql index a1316c73bac..676a8b342b5 100644 --- a/src/test/regress/sql/copy.sql +++ b/src/test/regress/sql/copy.sql @@ -405,3 +405,17 @@ COPY copytest_mv(id) TO stdout WITH (header); REFRESH MATERIALIZED VIEW copytest_mv; COPY copytest_mv(id) TO stdout WITH (header); DROP MATERIALIZED VIEW copytest_mv; + +-- Tests for COPY TO with partitioned tables. +-- The child table pp_2 has a different column order than the root table pp. +-- Check if COPY TO exports tuples as the root table's column order. +CREATE TABLE pp (id int,val int) PARTITION BY RANGE (id); +CREATE TABLE pp_1 (val int, id int) PARTITION BY RANGE (id); +CREATE TABLE pp_2 (id int, val int) PARTITION BY RANGE (id); +ALTER TABLE pp ATTACH PARTITION pp_1 FOR VALUES FROM (1) TO (5); +ALTER TABLE pp ATTACH PARTITION pp_2 FOR VALUES FROM (5) TO (10); +CREATE TABLE pp_15 PARTITION OF pp_1 FOR VALUES FROM (1) TO (5); +CREATE TABLE pp_510 PARTITION OF pp_2 FOR VALUES FROM (5) TO (10); +INSERT INTO pp SELECT g, 10 + g FROM generate_series(1,6) g; +COPY pp TO stdout(header); +DROP TABLE PP; diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql index 21ac0ca51ee..2d1be543391 100644 --- a/src/test/regress/sql/rowsecurity.sql +++ b/src/test/regress/sql/rowsecurity.sql @@ -362,16 +362,19 @@ SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename SET SESSION AUTHORIZATION regress_rls_bob; SET row_security TO ON; SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +COPY part_document TO stdout WITH (DELIMITER ','); EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); -- viewpoint from regress_rls_carol SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +COPY part_document TO stdout WITH (DELIMITER ','); EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); -- viewpoint from regress_rls_dave SET SESSION AUTHORIZATION regress_rls_dave; SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +COPY part_document TO stdout WITH (DELIMITER ','); EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); -- pp1 ERROR diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl index eaee88d027e..310d70a4c08 100644 --- a/src/test/ssl/t/001_ssltests.pl +++ b/src/test/ssl/t/001_ssltests.pl @@ -754,7 +754,7 @@ my $serialno = '\d+'; if ($ENV{OPENSSL} ne '') { - $serialno = `$ENV{OPENSSL} x509 -serial -noout -in ssl/client.crt`; + my $serialstr = `$ENV{OPENSSL} x509 -serial -noout -in ssl/client.crt`; if ($? == 0) { # OpenSSL prints serial numbers in hexadecimal and converting the serial @@ -765,9 +765,9 @@ if ($ENV{OPENSSL} ne '') { no warnings qw(portable); - $serialno =~ s/^serial=//; - $serialno =~ s/\s+//g; - $serialno = hex($serialno); + $serialstr =~ s/^serial=//; + $serialstr =~ s/\s+//g; + $serialno = hex($serialstr); } } } |