Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1s2PbE-002l5w-1J for pgsql-hackers@arkaria.postgresql.org; Thu, 02 May 2024 06:07:21 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1s2PbB-00Fzsh-Da for pgsql-hackers@arkaria.postgresql.org; Thu, 02 May 2024 06:07:18 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1s2Pb9-00FzsY-Cf for pgsql-hackers@lists.postgresql.org; Thu, 02 May 2024 06:07:18 +0000 Received: from fout4-smtp.messagingengine.com ([103.168.172.147]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1s2Pb4-0017it-TW for pgsql-hackers@lists.postgresql.org; Thu, 02 May 2024 06:07:15 +0000 Received: from compute5.internal (compute5.nyi.internal [10.202.2.45]) by mailfout.nyi.internal (Postfix) with ESMTP id 294011380375; Thu, 2 May 2024 02:07:08 -0400 (EDT) Received: from mailfrontend1 ([10.202.2.162]) by compute5.internal (MEProxy); Thu, 02 May 2024 02:07:08 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=paquier.xyz; h= cc:cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm3; t=1714630028; x=1714716428; bh=RoQx/W3Rqh tlZbhlmxc/zDO3eLEa8XzEY+8bSHROVhA=; b=Ygxt9PrPvyROEMZjNtQr7pt392 89AcHEDZkW6civ9EWHjj844ZrZA6an7GySmHN/PyFuZHBepI3Qp6YFgAGYQuHqLW rJoYYX77lhe2iy+H6aeirTEXblXnn6WZi/b1s8ik6tceJz+dLRIG9h4xAvlgusqV JO0kADwL8VjwUObnEArTK2JM7vxIQ7CRvJ0+2zpCA5BGU0th2//AhZ/QVLtdSapC PaidDs2RpxzikuLwKs/NhPGhF5SxdTCNcOnWbMzN8w31zu4bdU4UXgqlL/5yJDaT P4ZlsKkrNsOFYaTF0EtLtA267jzTLZxINrAbMlhtPSoE+H5j8KodWqfyHCXA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm3; t=1714630028; x=1714716428; bh=RoQx/W3RqhtlZbhlmxc/zDO3eLEa 8XzEY+8bSHROVhA=; b=XNE+Wpo+vQwsbrKm4BKOiBVax1zsekqJ2e+KTRXCUsuf m3FvvkP6VwdOMYVufb0OIuVlGlBxtzhsthgxVKFo4NPh2aMb3XeIPGIVriMpfzh5 +oRE18J/wTtpvRMG43N9cUm3EqQ33sCytTVDLtbwkoJcT1Xt24lY73r+np6w8FOq i0R1/kqE6LU9MwfSm6PMssd1K71wloVL3h+SlTZPfTTKMM9m9LsmP+JcO3sfWQV/ 0bPs+1zl7cVyXoDdNcvl5LD4lTNTKdMm+gJvYvRy+MN40gpn0ex6nvsuB5wpn0/1 EQgFSB3sj2nF6y8nRn4n2Envq2PXfEowdjjSDhoLyQ== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvledrvddujedguddtvdcutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfgh necuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmd enfghrlhcuvffnffculdejtddmnecujfgurhepfffhvfevuffkfhggtggujgesghdtreer tddtvdenucfhrhhomhepofhitghhrggvlhcurfgrqhhuihgvrhcuoehmihgthhgrvghlse hprghquhhivghrrdighiiiqeenucggtffrrghtthgvrhhnpeetleeifedufffhhfdtteel geeggeffhfekueevteeigfduudevudetgfegiedvjeenucevlhhushhtvghrufhiiigvpe dtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehmihgthhgrvghlsehprghquhhivghrrdig hiii X-ME-Proxy: Feedback-ID: i0fe9450f:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Thu, 2 May 2024 02:07:06 -0400 (EDT) Date: Thu, 2 May 2024 15:06:51 +0900 From: Michael Paquier To: "David G. Johnston" Cc: Nathan Bossart , Postgres hackers Subject: Re: Partitioned tables and [un]loggedness Message-ID: References: <20240424202640.GB861150@nathanxps13> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="jC5zdQ53xUCbWX1A" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --jC5zdQ53xUCbWX1A Content-Type: multipart/mixed; boundary="4gi4+Uv5FY2QD4TM" Content-Disposition: inline --4gi4+Uv5FY2QD4TM Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Thu, Apr 25, 2024 at 08:55:27AM +0900, Michael Paquier wrote: > On Wed, Apr 24, 2024 at 04:43:58PM -0700, David G. Johnston wrote: >> My point is that if you feel that treating logged as a copy-able property >> is OK then doing the following should also just work: >>=20 >> postgres=3D# create temp table parentt ( id integer ) partition by range= (id); >> CREATE TABLE >> postgres=3D# create table child10t partition of parentt for values from = (0) >> to (9); >> ERROR: cannot create a permanent relation as partition of temporary >> relation "parentt" >>=20 >> i.e., child10t should be created as a temporary partition under parentt. >=20 > Ah, indeed, I've missed your point here. Lifting the error and > inheriting temporary in this case would make sense. The case of a temporary persistence is actually *very* tricky. The namespace, where the relation is created, is guessed and locked with permission checks done based on the RangeVar when the CreateStmt is transformed, which is before we try to look at its inheritance tree to find its partitioned parent. So we would somewhat need to shortcut the existing RangeVar lookup and include the parents in the loop to find out the correct namespace. And this is much earlier than now. The code complexity is not trivial, so I am getting cold feet when trying to support this case in a robust fashion. For now, I have discarded this case and focused on the main problem with SET LOGGED and UNLOGGED. Switching between logged <-> unlogged does not have such complications, because the namespace where the relation is created is going to be the same. So we won't lock or perform permission checks on an incorrect namespace. The addition of LOGGED makes the logic deciding how the loggedness of a partition table based on its partitioned table or the query quite easy to follow, but this needs some safety nets in the sequence, view and CTAS code paths to handle with the case where the query specifies no relpersistence. I have also looked at support for ONLY, and I've been surprised that it is not that complicated. tablecmds.c has a ATSimpleRecursion() that is smart enough to do an inheritance tree lookup and apply the rewrites where they should happen in the step 3 of ALTER TABLE, while handling ONLY on its own. The relpersistence of partitioned tables is updated in step 2, with the catalog changes. Attached is a new patch series: - 0001 refactors some code around ATPrepChangePersistence() that I found confusing after applying the operation to partitioned tables. - 0002 adds support for a LOGGED keyword. - 0003 expands ALTER TABLE SET [UN]LOGGED to partitioned tables, without recursion to partitions. - 0004 adds the recursion logic, expanding regression tests to show the difference. 0003 and 0004 should be merged together, I think. Still, splitting them makes reviews a bit easier. -- Michael --4gi4+Uv5FY2QD4TM Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v2-0001-Refactor-some-code-of-ALTER-TABLE-SET-LOGGED-UNLO.patch" Content-Transfer-Encoding: quoted-printable =46rom 30d572fac2aa58ce2d62ba929c30fded9b020e0b Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Thu, 2 May 2024 08:16:33 +0900 Subject: [PATCH v2 1/4] Refactor some code of ALTER TABLE SET LOGGED/UNLOGG= ED This is in preparation for an upcoming patch set to extend the possibilities in this area, making the code more consistent with the surroundings related to access methods and tablespaces. --- src/backend/commands/tablecmds.c | 38 +++++++++++++------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablec= mds.c index 08c87e6029..baabbf82e7 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -602,7 +602,8 @@ static ObjectAddress ATExecClusterOn(Relation rel, cons= t char *indexName, static void ATExecDropCluster(Relation rel, LOCKMODE lockmode); static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, con= st char *amname); static void ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMeth= od); -static bool ATPrepChangePersistence(Relation rel, bool toLogged); +static void ATPrepSetPersistence(AlteredTableInfo *tab, Relation rel, + bool toLogged); static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacename, LOCKMODE lockmode); static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE = lockmode); @@ -5037,13 +5038,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd= *cmd, ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot change persistence setting twice"))); - tab->chgPersistence =3D ATPrepChangePersistence(rel, true); - /* force rewrite if necessary; see comment in ATRewriteTables */ - if (tab->chgPersistence) - { - tab->rewrite |=3D AT_REWRITE_ALTER_PERSISTENCE; - tab->newrelpersistence =3D RELPERSISTENCE_PERMANENT; - } + ATPrepSetPersistence(tab, rel, true); pass =3D AT_PASS_MISC; break; case AT_SetUnLogged: /* SET UNLOGGED */ @@ -5052,13 +5047,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd= *cmd, ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot change persistence setting twice"))); - tab->chgPersistence =3D ATPrepChangePersistence(rel, false); - /* force rewrite if necessary; see comment in ATRewriteTables */ - if (tab->chgPersistence) - { - tab->rewrite |=3D AT_REWRITE_ALTER_PERSISTENCE; - tab->newrelpersistence =3D RELPERSISTENCE_UNLOGGED; - } + ATPrepSetPersistence(tab, rel, false); pass =3D AT_PASS_MISC; break; case AT_DropOids: /* SET WITHOUT OIDS */ @@ -17775,12 +17764,9 @@ ATExecSetCompression(Relation rel, * This verifies that we're not trying to change a temp table. Also, * existing foreign key constraints are checked to avoid ending up with * permanent tables referencing unlogged tables. - * - * Return value is false if the operation is a no-op (in which case the - * checks are skipped), otherwise true. */ -static bool -ATPrepChangePersistence(Relation rel, bool toLogged) +static void +ATPrepSetPersistence(AlteredTableInfo *tab, Relation rel, bool toLogged) { Relation pg_constraint; HeapTuple tuple; @@ -17804,12 +17790,12 @@ ATPrepChangePersistence(Relation rel, bool toLogg= ed) case RELPERSISTENCE_PERMANENT: if (toLogged) /* nothing to do */ - return false; + return; break; case RELPERSISTENCE_UNLOGGED: if (!toLogged) /* nothing to do */ - return false; + return; break; } =20 @@ -17892,7 +17878,13 @@ ATPrepChangePersistence(Relation rel, bool toLogge= d) =20 table_close(pg_constraint, AccessShareLock); =20 - return true; + /* force rewrite if necessary; see comment in ATRewriteTables */ + tab->rewrite |=3D AT_REWRITE_ALTER_PERSISTENCE; + if (toLogged) + tab->newrelpersistence =3D RELPERSISTENCE_PERMANENT; + else + tab->newrelpersistence =3D RELPERSISTENCE_UNLOGGED; + tab->chgPersistence =3D true; } =20 /* --=20 2.43.0 --4gi4+Uv5FY2QD4TM Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v2-0002-Add-support-for-LOGGED-keyword-similar-to-UNLOGGE.patch" Content-Transfer-Encoding: quoted-printable =46rom f00c1295bf7d1a7f48409f8ddfedeffab87dabaa Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Thu, 2 May 2024 09:29:48 +0900 Subject: [PATCH v2 2/4] Add support for LOGGED keyword, similar to UNLOGGED but for permanent This extends the following statements with a new keyword called LOGGED, to be able to force a relation to be permanent: - CREATE SEQUENCE - CREATE TABLE AS - SELECT INTO - CREATE TABLE The implementation is done here with the introduction of a RELPERSISTENCE_INVALID, which is set by the grammar when neither TEMP, UNLOGGED or LOGGED are specified. This is handy for an upcoming patch that aims to introduce relpersistence inheritance for partitions, based on its partitioned table. --- src/include/catalog/pg_class.h | 1 + src/backend/catalog/namespace.c | 8 ++++++ src/backend/commands/createas.c | 7 +++++ src/backend/commands/sequence.c | 7 +++++ src/backend/commands/tablecmds.c | 9 +++++++ src/backend/commands/view.c | 7 +++++ src/backend/parser/gram.y | 8 +++++- src/test/regress/expected/identity.out | 11 ++++++++ src/test/regress/expected/select_into.out | 32 +++++++++++++++++++++++ src/test/regress/expected/sequence.out | 9 +++++++ src/test/regress/sql/identity.sql | 6 +++++ src/test/regress/sql/select_into.sql | 13 +++++++++ src/test/regress/sql/sequence.sql | 5 ++++ doc/src/sgml/ref/create_sequence.sgml | 12 ++++++++- doc/src/sgml/ref/create_table.sgml | 22 +++++++++++++--- doc/src/sgml/ref/create_table_as.sgml | 12 ++++++++- doc/src/sgml/ref/select_into.sgml | 12 ++++++++- 17 files changed, 174 insertions(+), 7 deletions(-) diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 0fc2c093b0..1b3fd060ca 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -175,6 +175,7 @@ MAKE_SYSCACHE(RELNAMENSP, pg_class_relname_nsp_index, 1= 28); #define RELPERSISTENCE_PERMANENT 'p' /* regular table */ #define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */ #define RELPERSISTENCE_TEMP 't' /* temporary table */ +#define RELPERSISTENCE_INVALID '\0' /* persistence not allowed */ =20 /* default selection for replica identity (primary key or nothing) */ #define REPLICA_IDENTITY_DEFAULT 'd' diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespac= e.c index a2510cf80c..4bb9fafe1d 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -853,6 +853,14 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelatio= n, Oid nspid) (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cannot create relations in temporary schemas of other sessi= ons"))); break; + case RELPERSISTENCE_INVALID: /* persistence not specified by grammar */ + if (isTempOrTempToastNamespace(nspid)) + newRelation->relpersistence =3D RELPERSISTENCE_TEMP; + else if (isAnyTempNamespace(nspid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("only temporary relations may be created in temporary schema= s"))); + break; default: if (isAnyTempNamespace(nspid)) ereport(ERROR, diff --git a/src/backend/commands/createas.c b/src/backend/commands/createa= s.c index 62050f4dc5..2a26fe0b6f 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -238,6 +238,13 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStm= t *stmt, if (CreateTableAsRelExists(stmt)) return InvalidObjectAddress; =20 + /* + * If the grammar did not specify a relpersistence, assume that the + * relation is permanent. + */ + if (into->rel->relpersistence =3D=3D RELPERSISTENCE_INVALID) + into->rel->relpersistence =3D RELPERSISTENCE_PERMANENT; + /* * Create the tuple receiver object and insert info it will need */ diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequenc= e.c index 46103561c3..3388e59f56 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -162,6 +162,13 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) } } =20 + /* + * If the grammar did not specify a relpersistence, assume that the + * relation is permanent. + */ + if (seq->sequence->relpersistence =3D=3D RELPERSISTENCE_INVALID) + seq->sequence->relpersistence =3D RELPERSISTENCE_PERMANENT; + /* Check and set all option values */ init_params(pstate, seq->options, seq->for_identity, true, &seqform, &seqdataform, diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablec= mds.c index baabbf82e7..97ba34f0d5 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -810,6 +810,15 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid own= erId, inheritOids =3D lappend_oid(inheritOids, parentOid); } =20 + /* + * If the grammar did not specify a relpersistence, assume that the + * relation is permanent. Note that this is done before selecting + * the relation's tablespace, as this change may impact the tablespace + * location depending on the persistence set here. + */ + if (stmt->relation->relpersistence =3D=3D RELPERSISTENCE_INVALID) + stmt->relation->relpersistence =3D RELPERSISTENCE_PERMANENT; + /* * Select tablespace to use: an explicitly indicated one, or (in the case * of a partitioned table) the parent's, if it has one. diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index fdad833832..6e691525d7 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -388,6 +388,13 @@ DefineView(ViewStmt *stmt, const char *queryString, if (viewParse->commandType !=3D CMD_SELECT) elog(ERROR, "unexpected parse analysis result"); =20 + /* + * If the grammar did not specify a relpersistence, assume that the + * relation is permanent. + */ + if (stmt->view->relpersistence =3D=3D RELPERSISTENCE_INVALID) + stmt->view->relpersistence =3D RELPERSISTENCE_PERMANENT; + /* * Check for unsupported cases. These tests are redundant with ones in * DefineQueryRewrite(), but that function will complain about a bogus ON diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e8b619926e..85ef8dd372 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -3775,7 +3775,8 @@ OptTemp: TEMPORARY { $$ =3D RELPERSISTENCE_TEMP; } $$ =3D RELPERSISTENCE_TEMP; } | UNLOGGED { $$ =3D RELPERSISTENCE_UNLOGGED; } - | /*EMPTY*/ { $$ =3D RELPERSISTENCE_PERMANENT; } + | LOGGED { $$ =3D RELPERSISTENCE_PERMANENT; } + | /*EMPTY*/ { $$ =3D RELPERSISTENCE_INVALID; } ; =20 OptTableElementList: @@ -13129,6 +13130,11 @@ OptTempTableName: $$ =3D $3; $$->relpersistence =3D RELPERSISTENCE_UNLOGGED; } + | LOGGED opt_table qualified_name + { + $$ =3D $3; + $$->relpersistence =3D RELPERSISTENCE_PERMANENT; + } | TABLE qualified_name { $$ =3D $2; diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expe= cted/identity.out index f357b9b63b..373df20661 100644 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -365,6 +365,17 @@ SELECT seqtypid::regtype FROM pg_sequence WHERE seqrel= id =3D 'itest3_a_seq'::regcl =20 ALTER TABLE itest3 ALTER COLUMN a TYPE text; -- error ERROR: identity column type must be smallint, integer, or bigint +-- check that LOGGED propagates to sequence (for grammar) +CREATE LOGGED TABLE itest16 (a int NOT NULL, b text); +ALTER TABLE itest16 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; +\d itest16_a_seq + Sequence "public.itest16_a_seq" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache=20 +---------+-------+---------+------------+-----------+---------+------- + integer | 1 | 1 | 2147483647 | 1 | no | 1 +Sequence for identity column: public.itest16.a + +DROP TABLE itest16; -- check that unlogged propagates to sequence CREATE UNLOGGED TABLE itest17 (a int NOT NULL, b text); ALTER TABLE itest17 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; diff --git a/src/test/regress/expected/select_into.out b/src/test/regress/e= xpected/select_into.out index b79fe9a1c0..fb9c3eaa2a 100644 --- a/src/test/regress/expected/select_into.out +++ b/src/test/regress/expected/select_into.out @@ -220,3 +220,35 @@ NOTICE: relation "ctas_ine_tbl" already exists, skipp= ing (0 rows) =20 DROP TABLE ctas_ine_tbl; +-- CREATE TABLE AS with LOGGED and UNLOGGED. +CREATE UNLOGGED TABLE ctas_unlogged_tbl AS SELECT 1 AS a; +\d ctas_unlogged_tbl + Unlogged table "public.ctas_unlogged_tbl" + Column | Type | Collation | Nullable | Default=20 +--------+---------+-----------+----------+--------- + a | integer | | |=20 + +CREATE LOGGED TABLE ctas_logged_tbl AS SELECT 1 AS a; +\d ctas_logged_tbl + Table "public.ctas_logged_tbl" + Column | Type | Collation | Nullable | Default=20 +--------+---------+-----------+----------+--------- + a | integer | | |=20 + +DROP TABLE ctas_logged_tbl, ctas_unlogged_tbl; +-- SELECT INTO with LOGGED and UNLOGGED. +SELECT 1 AS a INTO UNLOGGED ctas_unlogged_tbl; +\d ctas_unlogged_tbl + Unlogged table "public.ctas_unlogged_tbl" + Column | Type | Collation | Nullable | Default=20 +--------+---------+-----------+----------+--------- + a | integer | | |=20 + +SELECT 1 AS a INTO LOGGED ctas_logged_tbl; +\d ctas_logged_tbl + Table "public.ctas_logged_tbl" + Column | Type | Collation | Nullable | Default=20 +--------+---------+-----------+----------+--------- + a | integer | | |=20 + +DROP TABLE ctas_logged_tbl, ctas_unlogged_tbl; diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expe= cted/sequence.out index 2b47b7796b..653428ddf6 100644 --- a/src/test/regress/expected/sequence.out +++ b/src/test/regress/expected/sequence.out @@ -599,6 +599,15 @@ DROP SEQUENCE seq2; -- should fail SELECT lastval(); ERROR: lastval is not yet defined in this session +-- logged sequences (for grammar) +CREATE LOGGED SEQUENCE sequence_test_logged; +\d sequence_test_logged + Sequence "public.sequence_test_logged" + Type | Start | Minimum | Maximum | Increment | Cycles? | Ca= che=20 +--------+-------+---------+---------------------+-----------+---------+---= ---- + bigint | 1 | 1 | 9223372036854775807 | 1 | no | = 1 + +DROP SEQUENCE sequence_test_logged; -- unlogged sequences -- (more tests in src/test/recovery/) CREATE UNLOGGED SEQUENCE sequence_test_unlogged; diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/ident= ity.sql index 7b0800226c..833c6f49fb 100644 --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -214,6 +214,12 @@ SELECT seqtypid::regtype FROM pg_sequence WHERE seqrel= id =3D 'itest3_a_seq'::regcl =20 ALTER TABLE itest3 ALTER COLUMN a TYPE text; -- error =20 +-- check that LOGGED propagates to sequence (for grammar) +CREATE LOGGED TABLE itest16 (a int NOT NULL, b text); +ALTER TABLE itest16 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; +\d itest16_a_seq +DROP TABLE itest16; + -- check that unlogged propagates to sequence CREATE UNLOGGED TABLE itest17 (a int NOT NULL, b text); ALTER TABLE itest17 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; diff --git a/src/test/regress/sql/select_into.sql b/src/test/regress/sql/se= lect_into.sql index 689c448cc2..55ffb25c12 100644 --- a/src/test/regress/sql/select_into.sql +++ b/src/test/regress/sql/select_into.sql @@ -136,3 +136,16 @@ EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS EXECUTE ctas_ine_query; -- ok DROP TABLE ctas_ine_tbl; + +-- CREATE TABLE AS with LOGGED and UNLOGGED. +CREATE UNLOGGED TABLE ctas_unlogged_tbl AS SELECT 1 AS a; +\d ctas_unlogged_tbl +CREATE LOGGED TABLE ctas_logged_tbl AS SELECT 1 AS a; +\d ctas_logged_tbl +DROP TABLE ctas_logged_tbl, ctas_unlogged_tbl; +-- SELECT INTO with LOGGED and UNLOGGED. +SELECT 1 AS a INTO UNLOGGED ctas_unlogged_tbl; +\d ctas_unlogged_tbl +SELECT 1 AS a INTO LOGGED ctas_logged_tbl; +\d ctas_logged_tbl +DROP TABLE ctas_logged_tbl, ctas_unlogged_tbl; diff --git a/src/test/regress/sql/sequence.sql b/src/test/regress/sql/seque= nce.sql index 674f5f1f66..189112fef7 100644 --- a/src/test/regress/sql/sequence.sql +++ b/src/test/regress/sql/sequence.sql @@ -271,6 +271,11 @@ DROP SEQUENCE seq2; -- should fail SELECT lastval(); =20 +-- logged sequences (for grammar) +CREATE LOGGED SEQUENCE sequence_test_logged; +\d sequence_test_logged +DROP SEQUENCE sequence_test_logged; + -- unlogged sequences -- (more tests in src/test/recovery/) CREATE UNLOGGED SEQUENCE sequence_test_unlogged; diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/creat= e_sequence.sgml index 34e9084b5c..ef8654cee5 100644 --- a/doc/src/sgml/ref/create_sequence.sgml +++ b/doc/src/sgml/ref/create_sequence.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation =20 -CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] name +CREATE [ { TEMPORARY | TEMP } | LOGGED | UNLOGGED ] SEQUENCE [ IF NOT EXIS= TS ] name [ AS data_type ] [ INCREMENT [ BY ] increment ] [ MINVALUE minvalue | N= O MINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE ] @@ -92,6 +92,16 @@ SELECT * FROM name; =20 + + LOGGED + + + If specified, the sequence is created as an permanent sequence. Cha= nges + to permanent sequences are written to the write-ahead log. + + + + UNLOGGED diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_t= able.sgml index 02f31d2d6f..29dfd68dc8 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation =20 -CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF N= OT EXISTS ] table_name ( [ +CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | LOGGED | UNLOGGED ] TAB= LE [ IF NOT EXISTS ] table_name ( [ { column_name data_type [ STORAGE { PLAIN | EXTERNAL = | EXTENDED | MAIN | DEFAULT } ] [ COMPRESSION compression_meth= od ] [ COLLATE collation ] [ column_constraint [ ... ] ] | table_constraint | LIKE source_table [ like_opt= ion ... ] } @@ -34,7 +34,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGG= ED ] TABLE [ IF NOT EXI [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] [ TABLESPACE tablespace_name ] =20 -CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF N= OT EXISTS ] table_name +CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | LOGGED | UNLOGGED ] TAB= LE [ IF NOT EXISTS ] table_name OF type_name [ ( { column_name [ WITH OPTI= ONS ] [ column_constraint [ = =2E.. ] ] | table_constraint } @@ -46,7 +46,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGG= ED ] TABLE [ IF NOT EXI [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] [ TABLESPACE tablespace_name ] =20 -CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF N= OT EXISTS ] table_name +CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | LOGGED | UNLOGGED ] TAB= LE [ IF NOT EXISTS ] table_name PARTITION OF parent_table [ ( { column_name [ WITH OPTI= ONS ] [ column_constraint [ = =2E.. ] ] | table_constraint } @@ -203,6 +203,22 @@ WITH ( MODULUS numeri= c_literal, REM =20 + + LOGGED + + + If specified, the table is created as an permanent table. Data writ= ten + to permanent tables is written to the write-ahead log (see + ). + + + + If this is specified, any sequences created together with the perman= ent + table (for identity or serial columns) are also created as permanent. + + + + UNLOGGED diff --git a/doc/src/sgml/ref/create_table_as.sgml b/doc/src/sgml/ref/creat= e_table_as.sgml index 8429333e3a..4f250f059c 100644 --- a/doc/src/sgml/ref/create_table_as.sgml +++ b/doc/src/sgml/ref/create_table_as.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation =20 -CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF N= OT EXISTS ] table_name +CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | LOGGED | UNLOGGED ] TAB= LE [ IF NOT EXISTS ] table_name [ (column_name [, ...] ) ] [ USING method ] [ WITH ( storage_parameter [=3D value] [, ... ] ) |= WITHOUT OIDS ] @@ -86,6 +86,16 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOG= GED ] TABLE [ IF NOT EXI =20 + + LOGGED + + + If specified, the table is created as a permanent table. + Refer to for details. + + + + UNLOGGED diff --git a/doc/src/sgml/ref/select_into.sgml b/doc/src/sgml/ref/select_in= to.sgml index 82a77784b9..d818c620fc 100644 --- a/doc/src/sgml/ref/select_into.sgml +++ b/doc/src/sgml/ref/select_into.sgml @@ -24,7 +24,7 @@ PostgreSQL documentation [ WITH [ RECURSIVE ] with_query [, ...] ] SELECT [ ALL | DISTINCT [ ON ( expression= [, ...] ) ] ] * | expression [ [ AS ]= output_name ] [, ...] - INTO [ TEMPORARY | TEMP | UNLOGGED ] [ TABLE ] new_table + INTO [ TEMPORARY | TEMP | LOGGED | UNLOGGED ] [ TABLE ] new_table [ FROM from_item [, ...= ] ] [ WHERE condition ] [ GROUP BY expression [= , ...] ] @@ -75,6 +75,16 @@ SELECT [ ALL | DISTINCT [ ON ( expression =20 + + LOGGED + + + If specified, the table is created as a permanent table. Refer + to for details. + + + + new_table --=20 2.43.0 --4gi4+Uv5FY2QD4TM Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v2-0003-Support-LOGGED-UNLOGGED-for-partitioned-tables.patch" Content-Transfer-Encoding: quoted-printable =46rom de44be520f8c157dc558f63dd4059d80b6ad00e9 Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Thu, 2 May 2024 13:45:18 +0900 Subject: [PATCH v2 3/4] Support LOGGED/UNLOGGED for partitioned tables When using ALTER TABLE SET LOGGED/UNLOGGED, indexes and sequences that are owned by the partitioned table changed need to have their relpersistence also changed. This patch does not apply recursion when using ALTER TABLE on a partitioned table: only new partitions inherit the loggedness of the parent if the query does not give a persistence (either LOGGED or UNLOGGED). --- src/backend/commands/tablecmds.c | 118 +++++++++++++++++++++- src/test/regress/expected/alter_table.out | 88 ++++++++++++++++ src/test/regress/sql/alter_table.sql | 37 +++++++ doc/src/sgml/ref/alter_table.sgml | 6 ++ doc/src/sgml/ref/create_table.sgml | 5 + 5 files changed, 249 insertions(+), 5 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablec= mds.c index 97ba34f0d5..8854e31f14 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -602,6 +602,7 @@ static ObjectAddress ATExecClusterOn(Relation rel, cons= t char *indexName, static void ATExecDropCluster(Relation rel, LOCKMODE lockmode); static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, con= st char *amname); static void ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMeth= od); +static void ATExecSetPersistenceNoStorage(Relation rel, char newrelpersist= ence); static void ATPrepSetPersistence(AlteredTableInfo *tab, Relation rel, bool toLogged); static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, @@ -811,13 +812,39 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ow= nerId, } =20 /* - * If the grammar did not specify a relpersistence, assume that the - * relation is permanent. Note that this is done before selecting - * the relation's tablespace, as this change may impact the tablespace - * location depending on the persistence set here. + * If the grammar did not specify a relpersistence, determine which one + * to use depending on the relation to create. Note that this is done + * before selecting the relation's tablespace, as this change may impact + * the tablespace location depending on the persistence set here. + * + * If the relation is not partitioned, assume that it is permanent. + * + * A partition inherits the persistence of its partitioned table, it the + * latter is unlogged or logged as the namespace where the relation will + * be created is known. This property cannot be enforced for temporary + * partitioned tables because the namespace of the relation is locked + * before it is possible to know the inheritance tree of this new + * relation, when its RangeVar is locked earlier when transforming the + * CreateStmt query. */ if (stmt->relation->relpersistence =3D=3D RELPERSISTENCE_INVALID) - stmt->relation->relpersistence =3D RELPERSISTENCE_PERMANENT; + { + if (stmt->partbound) + { + Oid parentOid =3D linitial_oid(inheritOids); + char relpersistence =3D get_rel_persistence(parentOid); + + Assert(list_length(inheritOids) =3D=3D 1); + /* + * The parent's persistence is logged or unlogged, so rely on + * it when creating the new relation. + */ + if (relpersistence !=3D RELPERSISTENCE_TEMP) + stmt->relation->relpersistence =3D relpersistence; + } + else + stmt->relation->relpersistence =3D RELPERSISTENCE_PERMANENT; + } =20 /* * Select tablespace to use: an explicitly indicated one, or (in the case @@ -5426,6 +5453,14 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, break; case AT_SetLogged: /* SET LOGGED */ case AT_SetUnLogged: /* SET UNLOGGED */ + + /* + * Only do this for partitioned tables, for which this is just a + * catalog change. Tables with storage are handled by Phase 3. + */ + if (rel->rd_rel->relkind =3D=3D RELKIND_PARTITIONED_TABLE && + tab->chgPersistence) + ATExecSetPersistenceNoStorage(rel, tab->newrelpersistence); break; case AT_DropOids: /* SET WITHOUT OIDS */ /* nothing to do here, oid columns don't exist anymore */ @@ -15542,6 +15577,79 @@ ATExecSetAccessMethodNoStorage(Relation rel, Oid n= ewAccessMethodId) table_close(pg_class, RowExclusiveLock); } =20 +/* + * Special handling of ALTER TABLE SET LOGGED/UNLOGGED for relations with = no + * storage that have an interest in changing their persistence. + * + * Since these have no storage, setting the persistence to permanent or + * unlogged is a catalog-only operation. This needs to switch the + * persistence of all sequences and indexes related to this relation. + */ +static void +ATExecSetPersistenceNoStorage(Relation rel, char newrelpersistence) +{ + Relation pg_class; + HeapTuple tuple; + List *reloids; /* for indexes and sequences */ + ListCell *elt; + Form_pg_class rd_rel; + Oid reloid =3D RelationGetRelid(rel); + + /* + * Shouldn't be called on relations having storage; these are processed in + * phase 3. + */ + Assert(!RELKIND_HAS_STORAGE(rel->rd_rel->relkind)); + + /* Get a modifiable copy of the relation's pg_class row. */ + pg_class =3D table_open(RelationRelationId, RowExclusiveLock); + + tuple =3D SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", reloid); + rd_rel =3D (Form_pg_class) GETSTRUCT(tuple); + + /* Leave if no update required */ + if (rd_rel->relpersistence =3D=3D newrelpersistence) + { + heap_freetuple(tuple); + table_close(pg_class, RowExclusiveLock); + return; + } + + /* Update the pg_class row. */ + rd_rel->relpersistence =3D newrelpersistence; + CatalogTupleUpdate(pg_class, &tuple->t_self, tuple); + + InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0); + + heap_freetuple(tuple); + + /* Update the per-sequence and per-index relpersistence */ + reloids =3D getOwnedSequences(reloid); + reloids =3D list_union_oid(reloids, RelationGetIndexList(rel)); + foreach(elt, reloids) + { + Oid classoid =3D lfirst_oid(elt); + + tuple =3D SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(classoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", classoid); + rd_rel =3D (Form_pg_class) GETSTRUCT(tuple); + + rd_rel->relpersistence =3D newrelpersistence; + CatalogTupleUpdate(pg_class, &tuple->t_self, tuple); + InvokeObjectPostAlterHook(RelationRelationId, classoid, 0); + + heap_freetuple(tuple); + } + + /* Make sure the persistence changes are visible */ + CommandCounterIncrement(); + + table_close(pg_class, RowExclusiveLock); +} + /* * ALTER TABLE SET TABLESPACE */ diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/e= xpected/alter_table.out index 7666c76238..5071e7d963 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -3597,6 +3597,94 @@ ALTER TABLE logged1 SET UNLOGGED; -- silently do not= hing DROP TABLE logged3; DROP TABLE logged2; DROP TABLE logged1; +-- SET LOGGED/UNLOGGED with partitioned tables +CREATE TABLE logged_part_1(f1 SERIAL PRIMARY KEY) + PARTITION BY LIST (f1); -- has sequence, index +CREATE TABLE logged_part_2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES lo= gged_part_1) + PARTITION BY LIST (f1); -- foreign key +CREATE TABLE logged_part_3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES lo= gged_part_3) + PARTITION BY LIST (f1); -- self-referencing foreign key +-- Check relpersistence of all the objects created. +SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part' + ORDER BY relname; + relname | relpersistence=20 +----------------------+---------------- + logged_part_1 | p + logged_part_1_f1_seq | p + logged_part_1_pkey | p + logged_part_2 | p + logged_part_2_f1_seq | p + logged_part_2_pkey | p + logged_part_3 | p + logged_part_3_f1_seq | p + logged_part_3_pkey | p +(9 rows) + +ALTER TABLE logged_part_1 SET UNLOGGED; -- fails as a foreign-key exists +ERROR: could not change table "logged_part_1" to unlogged because it refe= rences logged table "logged_part_2" +ALTER TABLE logged_part_2 SET UNLOGGED; +ALTER TABLE logged_part_3 SET UNLOGGED; -- skip self-referencing foreign k= ey +-- Re-check relpersistence of all the objects created. +SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part' + ORDER BY relname; + relname | relpersistence=20 +----------------------+---------------- + logged_part_1 | p + logged_part_1_f1_seq | p + logged_part_1_pkey | p + logged_part_2 | u + logged_part_2_f1_seq | u + logged_part_2_pkey | u + logged_part_3 | u + logged_part_3_f1_seq | u + logged_part_3_pkey | u +(9 rows) + +ALTER TABLE logged_part_1 SET LOGGED; -- no-op +ALTER TABLE logged_part_2 SET LOGGED; +ALTER TABLE logged_part_3 SET LOGGED; -- skip self-referencing foreign key +SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part= _[2|3]' + ORDER BY relname; + relname | relpersistence=20 +----------------------+---------------- + logged_part_2 | p + logged_part_2_f1_seq | p + logged_part_2_pkey | p + logged_part_3 | p + logged_part_3_f1_seq | p + logged_part_3_pkey | p +(6 rows) + +-- Partitions +CREATE TABLE logged_part_2_1 PARTITION OF logged_part_2 + FOR VALUES IN (1); -- permanent, inherited +CREATE UNLOGGED TABLE logged_part_2_2 PARTITION OF logged_part_2 + FOR VALUES IN (2); -- unlogged, not inherited +ALTER TABLE logged_part_2 SET UNLOGGED; +CREATE TABLE logged_part_2_3 PARTITION OF logged_part_2 + FOR VALUES IN (3); -- unlogged, inherited +CREATE LOGGED TABLE logged_part_2_4 PARTITION OF logged_part_2 + FOR VALUES IN (4); -- logged, not inherited +SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part= _2' + ORDER BY relname; + relname | relpersistence=20 +----------------------+---------------- + logged_part_2 | u + logged_part_2_1 | p + logged_part_2_1_pkey | p + logged_part_2_2 | u + logged_part_2_2_pkey | u + logged_part_2_3 | u + logged_part_2_3_pkey | u + logged_part_2_4 | p + logged_part_2_4_pkey | p + logged_part_2_f1_seq | u + logged_part_2_pkey | u +(11 rows) + +DROP TABLE logged_part_3; +DROP TABLE logged_part_2; +DROP TABLE logged_part_1; -- test ADD COLUMN IF NOT EXISTS CREATE TABLE test_add_column(c1 integer); \d test_add_column diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/al= ter_table.sql index 9df5a63bdf..30aa62d256 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -2259,6 +2259,43 @@ DROP TABLE logged3; DROP TABLE logged2; DROP TABLE logged1; =20 +-- SET LOGGED/UNLOGGED with partitioned tables +CREATE TABLE logged_part_1(f1 SERIAL PRIMARY KEY) + PARTITION BY LIST (f1); -- has sequence, index +CREATE TABLE logged_part_2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES lo= gged_part_1) + PARTITION BY LIST (f1); -- foreign key +CREATE TABLE logged_part_3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES lo= gged_part_3) + PARTITION BY LIST (f1); -- self-referencing foreign key +-- Check relpersistence of all the objects created. +SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part' + ORDER BY relname; +ALTER TABLE logged_part_1 SET UNLOGGED; -- fails as a foreign-key exists +ALTER TABLE logged_part_2 SET UNLOGGED; +ALTER TABLE logged_part_3 SET UNLOGGED; -- skip self-referencing foreign k= ey +-- Re-check relpersistence of all the objects created. +SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part' + ORDER BY relname; +ALTER TABLE logged_part_1 SET LOGGED; -- no-op +ALTER TABLE logged_part_2 SET LOGGED; +ALTER TABLE logged_part_3 SET LOGGED; -- skip self-referencing foreign key +SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part= _[2|3]' + ORDER BY relname; +-- Partitions +CREATE TABLE logged_part_2_1 PARTITION OF logged_part_2 + FOR VALUES IN (1); -- permanent, inherited +CREATE UNLOGGED TABLE logged_part_2_2 PARTITION OF logged_part_2 + FOR VALUES IN (2); -- unlogged, not inherited +ALTER TABLE logged_part_2 SET UNLOGGED; +CREATE TABLE logged_part_2_3 PARTITION OF logged_part_2 + FOR VALUES IN (3); -- unlogged, inherited +CREATE LOGGED TABLE logged_part_2_4 PARTITION OF logged_part_2 + FOR VALUES IN (4); -- logged, not inherited +SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part= _2' + ORDER BY relname; +DROP TABLE logged_part_3; +DROP TABLE logged_part_2; +DROP TABLE logged_part_1; + -- test ADD COLUMN IF NOT EXISTS CREATE TABLE test_add_column(c1 integer); \d test_add_column diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_tab= le.sgml index ebd8c62038..7937194462 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -803,6 +803,12 @@ WITH ( MODULUS numeri= c_literal, REM (for identity or serial columns). However, it is also possible to change the persistence of such sequences separately. + + + Setting this property for a partitioned table has no direct effect, + because such tables have no storage of their own, but the configured + value will be inherited by newly-created partitions. + =20 diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_t= able.sgml index 29dfd68dc8..f65c6d14c7 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -216,6 +216,11 @@ WITH ( MODULUS numeri= c_literal, REM If this is specified, any sequences created together with the perman= ent table (for identity or serial columns) are also created as permanent. + + + When applied to a partitioned table, newly-created partitions and + their objects (sequences and indexes) will inherit this property. + =20 --=20 2.43.0 --4gi4+Uv5FY2QD4TM Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v2-0004-Recurse-ALTER-TABLE-SET-LOGGED-UNLOGGED-for-parti.patch" Content-Transfer-Encoding: quoted-printable =46rom 0f2d517d28042a16d3ebd89d18327f0cfc7525c5 Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Thu, 2 May 2024 14:43:56 +0900 Subject: [PATCH v2 4/4] Recurse ALTER TABLE SET LOGGED/UNLOGGED for partitioned tables This commit recurses the command of $subject to apply on all the partitions of a partitioned table, except if ONLY is used. Regression tests are expanded for both cases, with multiple levels of partitioning. --- src/backend/commands/tablecmds.c | 2 + src/test/regress/expected/alter_table.out | 91 +++++++++++++++++++---- src/test/regress/sql/alter_table.sql | 15 ++++ doc/src/sgml/ref/alter_table.sgml | 6 +- 4 files changed, 97 insertions(+), 17 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablec= mds.c index 8854e31f14..8f18b7aa39 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -5075,6 +5075,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd = *cmd, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot change persistence setting twice"))); ATPrepSetPersistence(tab, rel, true); + ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); pass =3D AT_PASS_MISC; break; case AT_SetUnLogged: /* SET UNLOGGED */ @@ -5084,6 +5085,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd = *cmd, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot change persistence setting twice"))); ATPrepSetPersistence(tab, rel, false); + ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); pass =3D AT_PASS_MISC; break; case AT_DropOids: /* SET WITHOUT OIDS */ diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/e= xpected/alter_table.out index 5071e7d963..8ec5c668c8 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -3665,22 +3665,85 @@ CREATE TABLE logged_part_2_3 PARTITION OF logged_pa= rt_2 FOR VALUES IN (3); -- unlogged, inherited CREATE LOGGED TABLE logged_part_2_4 PARTITION OF logged_part_2 FOR VALUES IN (4); -- logged, not inherited +-- Partitions of partitions +CREATE TABLE logged_part_2_56 PARTITION OF logged_part_2 + FOR VALUES IN (5, 6) PARTITION BY LIST(f1); +CREATE TABLE logged_part_2_5 PARTITION OF logged_part_2_56 + FOR VALUES IN (5); -- unlogged, inherited +CREATE LOGGED TABLE logged_part_2_6 PARTITION OF logged_part_2_56 + FOR VALUES IN (6); -- logged, not inherited SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part= _2' ORDER BY relname; - relname | relpersistence=20 -----------------------+---------------- - logged_part_2 | u - logged_part_2_1 | p - logged_part_2_1_pkey | p - logged_part_2_2 | u - logged_part_2_2_pkey | u - logged_part_2_3 | u - logged_part_2_3_pkey | u - logged_part_2_4 | p - logged_part_2_4_pkey | p - logged_part_2_f1_seq | u - logged_part_2_pkey | u -(11 rows) + relname | relpersistence=20 +-----------------------+---------------- + logged_part_2 | u + logged_part_2_1 | u + logged_part_2_1_pkey | u + logged_part_2_2 | u + logged_part_2_2_pkey | u + logged_part_2_3 | u + logged_part_2_3_pkey | u + logged_part_2_4 | p + logged_part_2_4_pkey | p + logged_part_2_5 | u + logged_part_2_56 | u + logged_part_2_56_pkey | u + logged_part_2_5_pkey | u + logged_part_2_6 | p + logged_part_2_6_pkey | p + logged_part_2_f1_seq | u + logged_part_2_pkey | u +(17 rows) + +-- All partitions are logged. +ALTER TABLE logged_part_2 SET LOGGED; +SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part= _2' + ORDER BY relname; + relname | relpersistence=20 +-----------------------+---------------- + logged_part_2 | p + logged_part_2_1 | p + logged_part_2_1_pkey | p + logged_part_2_2 | p + logged_part_2_2_pkey | p + logged_part_2_3 | p + logged_part_2_3_pkey | p + logged_part_2_4 | p + logged_part_2_4_pkey | p + logged_part_2_5 | p + logged_part_2_56 | p + logged_part_2_56_pkey | p + logged_part_2_5_pkey | p + logged_part_2_6 | p + logged_part_2_6_pkey | p + logged_part_2_f1_seq | p + logged_part_2_pkey | p +(17 rows) + +-- Only the partitioned partition is unlogged. +ALTER TABLE ONLY logged_part_2_56 SET UNLOGGED; +SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part= _2' + ORDER BY relname; + relname | relpersistence=20 +-----------------------+---------------- + logged_part_2 | p + logged_part_2_1 | p + logged_part_2_1_pkey | p + logged_part_2_2 | p + logged_part_2_2_pkey | p + logged_part_2_3 | p + logged_part_2_3_pkey | p + logged_part_2_4 | p + logged_part_2_4_pkey | p + logged_part_2_5 | p + logged_part_2_56 | u + logged_part_2_56_pkey | u + logged_part_2_5_pkey | p + logged_part_2_6 | p + logged_part_2_6_pkey | p + logged_part_2_f1_seq | p + logged_part_2_pkey | p +(17 rows) =20 DROP TABLE logged_part_3; DROP TABLE logged_part_2; diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/al= ter_table.sql index 30aa62d256..3393c67b9c 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -2290,6 +2290,21 @@ CREATE TABLE logged_part_2_3 PARTITION OF logged_par= t_2 FOR VALUES IN (3); -- unlogged, inherited CREATE LOGGED TABLE logged_part_2_4 PARTITION OF logged_part_2 FOR VALUES IN (4); -- logged, not inherited +-- Partitions of partitions +CREATE TABLE logged_part_2_56 PARTITION OF logged_part_2 + FOR VALUES IN (5, 6) PARTITION BY LIST(f1); +CREATE TABLE logged_part_2_5 PARTITION OF logged_part_2_56 + FOR VALUES IN (5); -- unlogged, inherited +CREATE LOGGED TABLE logged_part_2_6 PARTITION OF logged_part_2_56 + FOR VALUES IN (6); -- logged, not inherited +SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part= _2' + ORDER BY relname; +-- All partitions are logged. +ALTER TABLE logged_part_2 SET LOGGED; +SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part= _2' + ORDER BY relname; +-- Only the partitioned partition is unlogged. +ALTER TABLE ONLY logged_part_2_56 SET UNLOGGED; SELECT relname, relpersistence FROM pg_class WHERE relname ~ '^logged_part= _2' ORDER BY relname; DROP TABLE logged_part_3; diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_tab= le.sgml index 7937194462..79827796cc 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -805,9 +805,9 @@ WITH ( MODULUS numeric= _literal, REM =20 - Setting this property for a partitioned table has no direct effect, - because such tables have no storage of their own, but the configured - value will be inherited by newly-created partitions. + Setting this property on a partitioned table updates any partitions + attached to it, unless ONLY is specified. The + configured value is inherited by newly-created partitions. --=20 2.43.0 --4gi4+Uv5FY2QD4TM-- --jC5zdQ53xUCbWX1A Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEG72nH6vTowiyblFKnvQgOdbyQH0FAmYzLXsACgkQnvQgOdby QH1hiw//fxyRfEVyujFEXtq8G412x95UKldaeCXcrJ2fv8bYcQJWtvjYNn4zn5KH 9OA/FuqiLw4gEdNWN16HtyevIn716txOeMxAt7+qOPskHSp+TEn4Yui1JLMdEHJG kuX3bnO2Om6xGjcu9ur46GrAfykHDTU/y9MySfDTpnxW+Dp9GcZo78s38+f68Xx5 M9DtwzAFsZYyT97yQ8cG3KHhpVXg47ButC4R4YPMiLblyL7kfrPTSErBnSY131cQ 3MWqjxDjFm4kKL4tWc9mMM5mDntSsy/KD0R4TqygcxFBrTzdxh9JcjkoevI3zco4 V1bUZqTLsQvPzXrmbxyrZKukM0JABFhU9nSimDx/Wg7rfT5D3wNrMrKW5v/+03hR NR0SaKRTMnV3dz4DGiMFpBfI+C1zVoF6qnuRuV6u+p63PcqyIRItGhbDM18vi6pT 29vJHF/0axJ0QnF9SBKQZ+xVJ/JxiVGWyrW8GyYqKdKylujMO/82DUWHfD/ICxxy 5wZXuV9y1d7oIEveUNEuED+RXQRplBZfaMqJUI0/JfwqnH/NGvG7FDskyYnVKUEu lHQbvvtP1pzSk9ZwzBhj+FDvweewH4rro/itclAd1XduJLwsohZSgHxyAqSUE5r0 LTwbLFkuNBoz2Wj2AGrSaqmk4551pj2hO6C9Ir3pghMmGCyySck= =0UYf -----END PGP SIGNATURE----- --jC5zdQ53xUCbWX1A--