From f00c1295bf7d1a7f48409f8ddfedeffab87dabaa Mon Sep 17 00:00:00 2001 From: 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, 128); #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 */ /* 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/namespace.c index a2510cf80c..4bb9fafe1d 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -853,6 +853,14 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid) (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cannot create relations in temporary schemas of other sessions"))); break; + case RELPERSISTENCE_INVALID: /* persistence not specified by grammar */ + if (isTempOrTempToastNamespace(nspid)) + newRelation->relpersistence = RELPERSISTENCE_TEMP; + else if (isAnyTempNamespace(nspid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("only temporary relations may be created in temporary schemas"))); + break; default: if (isAnyTempNamespace(nspid)) ereport(ERROR, diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 62050f4dc5..2a26fe0b6f 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -238,6 +238,13 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, if (CreateTableAsRelExists(stmt)) return InvalidObjectAddress; + /* + * If the grammar did not specify a relpersistence, assume that the + * relation is permanent. + */ + if (into->rel->relpersistence == RELPERSISTENCE_INVALID) + into->rel->relpersistence = 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/sequence.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) } } + /* + * If the grammar did not specify a relpersistence, assume that the + * relation is permanent. + */ + if (seq->sequence->relpersistence == RELPERSISTENCE_INVALID) + seq->sequence->relpersistence = 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/tablecmds.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 ownerId, inheritOids = lappend_oid(inheritOids, parentOid); } + /* + * 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 == RELPERSISTENCE_INVALID) + stmt->relation->relpersistence = 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 != CMD_SELECT) elog(ERROR, "unexpected parse analysis result"); + /* + * If the grammar did not specify a relpersistence, assume that the + * relation is permanent. + */ + if (stmt->view->relpersistence == RELPERSISTENCE_INVALID) + stmt->view->relpersistence = 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 { $$ = RELPERSISTENCE_TEMP; } $$ = RELPERSISTENCE_TEMP; } | UNLOGGED { $$ = RELPERSISTENCE_UNLOGGED; } - | /*EMPTY*/ { $$ = RELPERSISTENCE_PERMANENT; } + | LOGGED { $$ = RELPERSISTENCE_PERMANENT; } + | /*EMPTY*/ { $$ = RELPERSISTENCE_INVALID; } ; OptTableElementList: @@ -13129,6 +13130,11 @@ OptTempTableName: $$ = $3; $$->relpersistence = RELPERSISTENCE_UNLOGGED; } + | LOGGED opt_table qualified_name + { + $$ = $3; + $$->relpersistence = RELPERSISTENCE_PERMANENT; + } | TABLE qualified_name { $$ = $2; diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/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 seqrelid = 'itest3_a_seq'::regcl 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 +---------+-------+---------+------------+-----------+---------+------- + 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/expected/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, skipping (0 rows) 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 +--------+---------+-----------+----------+--------- + a | integer | | | + +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 +--------+---------+-----------+----------+--------- + a | integer | | | + +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 +--------+---------+-----------+----------+--------- + a | integer | | | + +SELECT 1 AS a INTO LOGGED ctas_logged_tbl; +\d ctas_logged_tbl + Table "public.ctas_logged_tbl" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + +DROP TABLE ctas_logged_tbl, ctas_unlogged_tbl; diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/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? | Cache +--------+-------+---------+---------------------+-----------+---------+------- + 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/identity.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 seqrelid = 'itest3_a_seq'::regcl ALTER TABLE itest3 ALTER COLUMN a TYPE text; -- error +-- 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/select_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/sequence.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(); +-- 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/create_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 -CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] name +CREATE [ { TEMPORARY | TEMP } | LOGGED | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] name [ AS data_type ] [ INCREMENT [ BY ] increment ] [ MINVALUE minvalue | NO MINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE ] @@ -92,6 +92,16 @@ SELECT * FROM name; + + LOGGED + + + If specified, the sequence is created as an permanent sequence. Changes + 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_table.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 -CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name ( [ +CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | LOGGED | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name ( [ { column_name data_type [ STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN | DEFAULT } ] [ COMPRESSION compression_method ] [ COLLATE collation ] [ column_constraint [ ... ] ] | table_constraint | LIKE source_table [ like_option ... ] } @@ -34,7 +34,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] [ TABLESPACE tablespace_name ] -CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name +CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | LOGGED | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name OF type_name [ ( { column_name [ WITH OPTIONS ] [ column_constraint [ ... ] ] | table_constraint } @@ -46,7 +46,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] [ TABLESPACE tablespace_name ] -CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name +CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | LOGGED | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name PARTITION OF parent_table [ ( { column_name [ WITH OPTIONS ] [ column_constraint [ ... ] ] | table_constraint } @@ -203,6 +203,22 @@ WITH ( MODULUS numeric_literal, REM + + LOGGED + + + If specified, the table is created as an permanent table. Data written + to permanent tables is written to the write-ahead log (see + ). + + + + If this is specified, any sequences created together with the permanent + 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/create_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 -CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name +CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | LOGGED | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name [ (column_name [, ...] ) ] [ USING method ] [ WITH ( storage_parameter [= value] [, ... ] ) | WITHOUT OIDS ] @@ -86,6 +86,16 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI + + 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_into.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 + + LOGGED + + + If specified, the table is created as a permanent table. Refer + to for details. + + + + new_table -- 2.43.0