agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH v5] Allow specifying access method for partitioned tables 3+ messages / 2 participants [nested] [flat]
* [PATCH v5] Allow specifying access method for partitioned tables @ 2024-03-08 04:24 Michael Paquier <michael@paquier.xyz> 0 siblings, 0 replies; 3+ messages in thread From: Michael Paquier @ 2024-03-08 04:24 UTC (permalink / raw) Partitioned tables gain support for CREATE TABLE .. USING, where specifying an access method can serve as a hint when creating partitions on it. This includes support for ALTER TABLE .. SET ACCESS METHOD, where specifying DEFAULT is equivalent to resetting the partitioned table's relam to 0. Specifying a non-DEFAULT access method will set its relam to not be 0. The historical default of using pg_class.relam as InvalidOid is preserved, corresponding to the case where USING clause is not used. In this case, like previous releases, a partition is created with the access method set by default_table_access_method if its CREATE TABLE query does not specify an access method with a USING clause. The relcache of partitioned tables is not changed: rd_tableam is not set, even if a partitioned table has a relam set. Authors: Justin Pryzby, Soumyadeep Chakraborty Reviewed-by: Michael Paquier, and a few others.. Discussion: https://postgr.es/m/ --- doc/src/sgml/catalogs.sgml | 8 +- doc/src/sgml/ref/alter_table.sgml | 8 ++ doc/src/sgml/ref/create_table.sgml | 4 + src/backend/commands/tablecmds.c | 165 +++++++++++++++++++----- src/backend/utils/cache/lsyscache.c | 22 ++++ src/backend/utils/cache/relcache.c | 7 + src/bin/pg_dump/pg_dump.c | 3 +- src/bin/pg_dump/t/002_pg_dump.pl | 35 +++++ src/include/catalog/pg_class.h | 4 +- src/include/utils/lsyscache.h | 1 + src/test/regress/expected/create_am.out | 158 ++++++++++++++++++++++- src/test/regress/sql/create_am.sql | 91 ++++++++++++- 12 files changed, 462 insertions(+), 44 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index b7980eb499..03815e10e8 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1989,8 +1989,12 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para> <para> If this is a table or an index, the access method used (heap, - B-tree, hash, etc.); otherwise zero (zero occurs for sequences, - as well as relations without storage, such as views) + B-tree, hash, etc.); for partitioned tables, the access method + for newly created partitions, when one is not specified in the + creation command, or zero to make creation fall back to + <varname>default_table_access_method</varname>. + Zero for sequences, as well as other relations without + storage, such as views. </para></entry> </row> diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 96e3d77605..b6e794e6ce 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -737,6 +737,14 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM <literal>DEFAULT</literal> changes the access method of the table to <xref linkend="guc-default-table-access-method"/>. </para> + <para> + When applied to a partitioned table, there is no data to rewrite, but any + partitions created afterwards will use that access method unless + overridden by a <literal>USING</literal> clause. Using + <varname>DEFAULT</varname> switches the partitioned table to use + <varname>default_table_access_method</varname> when new partitions are + created. + </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 4cbaaccaf7..b79081a5ec 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1330,6 +1330,10 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM method is chosen for the new table. See <xref linkend="guc-default-table-access-method"/> for more information. </para> + <para> + When creating a partition, the table access method is the access method + of its partitioned table, if set. + </para> </listitem> </varlistentry> diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6c0c899210..9e36aae425 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -183,7 +183,9 @@ typedef struct AlteredTableInfo List *afterStmts; /* List of utility command parsetrees */ bool verify_new_notnull; /* T if we should recheck NOT NULL */ int rewrite; /* Reason for forced rewrite, if any */ - Oid newAccessMethod; /* new access method; 0 means no change */ + bool chgAccessMethod; /* T if SET ACCESS METHOD is used */ + Oid newAccessMethod; /* new access method; 0 means no change, + * if above is true */ Oid newTableSpace; /* new tablespace; 0 means no change */ bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */ char newrelpersistence; /* if above is true */ @@ -589,6 +591,7 @@ static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode); static void ATExecDropCluster(Relation rel, LOCKMODE lockmode); static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname); +static void ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethod); static bool ATPrepChangePersistence(Relation rel, bool toLogged); static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacename, LOCKMODE lockmode); @@ -703,7 +706,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; - const char *accessMethod = NULL; Oid accessMethodId = InvalidOid; /* @@ -948,24 +950,22 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * If the statement hasn't specified an access method, but we're defining - * a type of relation that needs one, use the default. + * Select access method to use: an explicitly indicated one, or (in the + * case of a partitioned table) the parent's, if it has one. */ if (stmt->accessMethod != NULL) + accessMethodId = get_table_am_oid(stmt->accessMethod, false); + else if (stmt->partbound) { - accessMethod = stmt->accessMethod; - - if (partitioned) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("specifying a table access method is not supported on a partitioned table"))); + Assert(list_length(inheritOids) == 1); + accessMethodId = get_rel_relam(linitial_oid(inheritOids)); } - else if (RELKIND_HAS_TABLE_AM(relkind)) - accessMethod = default_table_access_method; + else + accessMethodId = InvalidOid; - /* look up the access method, verify it is for a table */ - if (accessMethod != NULL) - accessMethodId = get_table_am_oid(accessMethod, false); + /* still nothing? use the default */ + if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) + accessMethodId = get_table_am_oid(default_table_access_method, false); /* * Create the relation. Inherited defaults and constraints are passed in @@ -5041,14 +5041,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_SetAccessMethod: /* SET ACCESS METHOD */ ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW); - /* partitioned tables don't have an access method */ - if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change access method of a partitioned table"))); - /* check if another access method change was already requested */ - if (OidIsValid(tab->newAccessMethod)) + if (tab->chgAccessMethod) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot have multiple SET ACCESS METHOD subcommands"))); @@ -5402,7 +5396,14 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, /* nothing to do here, oid columns don't exist anymore */ break; case AT_SetAccessMethod: /* SET ACCESS METHOD */ - /* handled specially in Phase 3 */ + + /* + * 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 == RELKIND_PARTITIONED_TABLE && + tab->chgAccessMethod) + ATExecSetAccessMethodNoStorage(rel, tab->newAccessMethod); break; case AT_SetTableSpace: /* SET TABLESPACE */ @@ -5808,7 +5809,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, * Select destination access method (same as original unless user * requested a change) */ - if (OidIsValid(tab->newAccessMethod)) + if (tab->chgAccessMethod) NewAccessMethod = tab->newAccessMethod; else NewAccessMethod = OldHeap->rd_rel->relam; @@ -6395,6 +6396,7 @@ ATGetQueueEntry(List **wqueue, Relation rel) tab->relkind = rel->rd_rel->relkind; tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel)); tab->newAccessMethod = InvalidOid; + tab->chgAccessMethod = false; tab->newTableSpace = InvalidOid; tab->newrelpersistence = RELPERSISTENCE_PERMANENT; tab->chgPersistence = false; @@ -15197,25 +15199,128 @@ ATExecDropCluster(Relation rel, LOCKMODE lockmode) /* * Preparation phase for SET ACCESS METHOD * - * Check that access method exists. If it is the same as the table's current - * access method, it is a no-op. Otherwise, a table rewrite is necessary. - * If amname is NULL, select default_table_access_method as access method. + * Check that the access method exists and determine whether a change is + * actually needed. */ static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname) { Oid amoid; - /* Check that the table access method exists */ - amoid = get_table_am_oid(amname ? amname : default_table_access_method, - false); + /* + * Look up the access method name and check that it differs from the + * table's current AM. If DEFAULT was specified for a partitioned table + * (amname is NULL), set it to InvalidOid to reset the catalogued AM. + */ + if (amname != NULL) + amoid = get_table_am_oid(amname, false); + else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + amoid = InvalidOid; + else + amoid = get_table_am_oid(default_table_access_method, false); + /* if it's a match, phase 3 doesn't need to do anything */ if (rel->rd_rel->relam == amoid) return; /* Save info for Phase 3 to do the real work */ tab->rewrite |= AT_REWRITE_ACCESS_METHOD; tab->newAccessMethod = amoid; + tab->chgAccessMethod = true; +} + +/* + * Special handling of ALTER TABLE SET ACCESS METHOD for relations with no + * storage that have an interest in preserving AM. + * + * Since these have no storage, setting the access method is a catalog only + * operation. + */ +static void +ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId) +{ + Relation pg_class; + Oid oldAccessMethodId; + HeapTuple tuple; + Form_pg_class rd_rel; + Oid reloid = 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 = table_open(RelationRelationId, RowExclusiveLock); + + tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", reloid); + rd_rel = (Form_pg_class) GETSTRUCT(tuple); + + /* Update the pg_class row. */ + oldAccessMethodId = rd_rel->relam; + rd_rel->relam = newAccessMethodId; + + /* Leave if no update required */ + if (rd_rel->relam == oldAccessMethodId) + { + heap_freetuple(tuple); + table_close(pg_class, RowExclusiveLock); + return; + } + + CatalogTupleUpdate(pg_class, &tuple->t_self, tuple); + + /* + * Update the dependency on the new access method. No dependency is added + * if the new access method is InvalidOid (default case). Be very careful + * that this has to compare the previous value stored in pg_class with the + * new one. + */ + if (!OidIsValid(oldAccessMethodId) && OidIsValid(rd_rel->relam)) + { + ObjectAddress relobj, + referenced; + + /* + * New access method is defined and there was no dependency + * previously, so record a new one. + */ + ObjectAddressSet(relobj, RelationRelationId, reloid); + ObjectAddressSet(referenced, AccessMethodRelationId, rd_rel->relam); + recordDependencyOn(&relobj, &referenced, DEPENDENCY_NORMAL); + } + else if (OidIsValid(oldAccessMethodId) && + !OidIsValid(rd_rel->relam)) + { + /* + * There was an access method defined, and no new one, so just remove + * the existing dependency. + */ + deleteDependencyRecordsForClass(RelationRelationId, reloid, + AccessMethodRelationId, + DEPENDENCY_NORMAL); + } + else + { + Assert(OidIsValid(oldAccessMethodId) && + OidIsValid(rd_rel->relam)); + + /* Both are valid, so update the dependency */ + changeDependencyFor(RelationRelationId, reloid, + AccessMethodRelationId, + oldAccessMethodId, rd_rel->relam); + } + + /* make the relam and dependency changes visible */ + CommandCounterIncrement(); + + InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0); + + heap_freetuple(tuple); + table_close(pg_class, RowExclusiveLock); } /* diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 6418d1c6eb..26368ffcc9 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -2069,6 +2069,28 @@ get_rel_persistence(Oid relid) return result; } +/* + * get_rel_relam + * + * Returns the relam associated with a given relation. + */ +Oid +get_rel_relam(Oid relid) +{ + HeapTuple tp; + Form_pg_class reltup; + Oid result; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for relation %u", relid); + reltup = (Form_pg_class) GETSTRUCT(tp); + result = reltup->relam; + ReleaseSysCache(tp); + + return result; +} + /* ---------- TRANSFORM CACHE ---------- */ diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 2cd19d603f..404861856f 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -1208,6 +1208,13 @@ retry: else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE) RelationInitTableAccessMethod(relation); + else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + /* + * Do nothing: access methods are a setting that partitions can + * inherit. + */ + } else Assert(relation->rd_rel->relam == InvalidOid); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index d275b31605..b1c4c3ec7f 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -16656,7 +16656,8 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) if (RELKIND_HAS_TABLESPACE(tbinfo->relkind)) tablespace = tbinfo->reltablespace; - if (RELKIND_HAS_TABLE_AM(tbinfo->relkind)) + if (RELKIND_HAS_TABLE_AM(tbinfo->relkind) || + tbinfo->relkind == RELKIND_PARTITIONED_TABLE) tableam = tbinfo->amname; ArchiveEntry(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId, diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index c8b489d94e..f0410ce6a1 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -4587,6 +4587,41 @@ my %tests = ( no_table_access_method => 1, only_dump_measurement => 1, }, + }, + + # CREATE TABLE with partitioned table and various AMs. One + # partition uses the same default as the parent, and a second + # uses its own AM. + 'CREATE TABLE regress_pg_dump_table_part' => { + create_order => 19, + create_sql => ' + CREATE TABLE dump_test.regress_pg_dump_table_am_parent (id int) PARTITION BY LIST (id); + ALTER TABLE dump_test.regress_pg_dump_table_am_parent SET ACCESS METHOD regress_table_am; + CREATE TABLE dump_test.regress_pg_dump_table_am_child_1 + PARTITION OF dump_test.regress_pg_dump_table_am_parent FOR VALUES IN (1) USING heap; + CREATE TABLE dump_test.regress_pg_dump_table_am_child_2 + PARTITION OF dump_test.regress_pg_dump_table_am_parent FOR VALUES IN (2);', + regexp => qr/^ + \QSET default_table_access_method = regress_table_am;\E + (\n(?!SET[^;]+;)[^\n]*)* + \n\QCREATE TABLE dump_test.regress_pg_dump_table_am_parent (\E + (.*\n)* + \QSET default_table_access_method = heap;\E + (\n(?!SET[^;]+;)[^\n]*)* + \n\QCREATE TABLE dump_test.regress_pg_dump_table_am_child_1 (\E + (.*\n)* + \QSET default_table_access_method = regress_table_am;\E + (\n(?!SET[^;]+;)[^\n]*)* + \n\QCREATE TABLE dump_test.regress_pg_dump_table_am_child_2 (\E + (.*\n)*/xm, + like => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_table_access_method => 1, + only_dump_measurement => 1, + }, }); ######################################### diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 3b7533e7bb..0fc2c093b0 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -219,7 +219,9 @@ MAKE_SYSCACHE(RELNAMENSP, pg_class_relname_nsp_index, 128); /* * Relation kinds with a table access method (rd_tableam). Although sequences * use the heap table AM, they are enough of a special case in most uses that - * they are not included here. + * they are not included here. Likewise, partitioned tables can have an access + * method defined so that their partitions can inherit it, but they do not set + * rd_tableam; hence, this is handled specially outside of this macro. */ #define RELKIND_HAS_TABLE_AM(relkind) \ ((relkind) == RELKIND_RELATION || \ diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index e4a200b00e..35a8dec2b9 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -139,6 +139,7 @@ extern char get_rel_relkind(Oid relid); extern bool get_rel_relispartition(Oid relid); extern Oid get_rel_tablespace(Oid relid); extern char get_rel_persistence(Oid relid); +extern Oid get_rel_relam(Oid relid); extern Oid get_transform_fromsql(Oid typid, Oid langid, List *trftypes); extern Oid get_transform_tosql(Oid typid, Oid langid, List *trftypes); extern bool get_typisdefined(Oid typid); diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out index 8d73e21356..a27805a8f5 100644 --- a/src/test/regress/expected/create_am.out +++ b/src/test/regress/expected/create_am.out @@ -176,9 +176,16 @@ SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1; 1 (1 row) --- CREATE TABLE .. PARTITION BY doesn't not support USING +-- CREATE TABLE .. PARTITION BY supports USING. CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a) USING heap2; -ERROR: specifying a table access method is not supported on a partitioned table +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'tableam_parted_heap2' AND a.oid = c.relam; + amname +-------- + heap2 +(1 row) + +DROP TABLE tableam_parted_heap2; CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a); -- new partitions will inherit from the current default, rather the partition root SET default_table_access_method = 'heap'; @@ -336,12 +343,151 @@ ALTER MATERIALIZED VIEW heapmv SET ACCESS METHOD heap, SET ACCESS METHOD heap2; ERROR: cannot have multiple SET ACCESS METHOD subcommands DROP MATERIALIZED VIEW heapmv; DROP TABLE heaptable; --- No support for partitioned tables. -CREATE TABLE am_partitioned(x INT, y INT) - PARTITION BY hash (x); +-- Partition hierarchies with access methods +BEGIN; +SET LOCAL default_table_access_method = 'heap'; +CREATE TABLE am_partitioned(x INT, y INT) PARTITION BY hash (x); +-- pg_class.relam is 0, no dependency recorded between the AM and the +-- partitioned table. +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; + relam +------- + 0 +(1 row) + +SELECT pg_describe_object(classid, objid, objsubid) AS obj, + pg_describe_object(refclassid, refobjid, refobjsubid) as refobj + FROM pg_depend, pg_am + WHERE pg_depend.refclassid = 'pg_am'::regclass + AND pg_am.oid = pg_depend.refobjid + AND pg_depend.objid = 'am_partitioned'::regclass; + obj | refobj +-----+-------- +(0 rows) + +-- New default is set, with dependency added. ALTER TABLE am_partitioned SET ACCESS METHOD heap2; -ERROR: cannot change access method of a partitioned table +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'am_partitioned' AND a.oid = c.relam; + amname +-------- + heap2 +(1 row) + +SELECT pg_describe_object(classid, objid, objsubid) AS obj, + pg_describe_object(refclassid, refobjid, refobjsubid) as refobj + FROM pg_depend, pg_am + WHERE pg_depend.refclassid = 'pg_am'::regclass + AND pg_am.oid = pg_depend.refobjid + AND pg_depend.objid = 'am_partitioned'::regclass; + obj | refobj +----------------------+--------------------- + table am_partitioned | access method heap2 +(1 row) + +-- Default is set, with dependency updated. +SET LOCAL default_table_access_method = 'heap2'; +ALTER TABLE am_partitioned SET ACCESS METHOD heap; +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'am_partitioned' AND a.oid = c.relam; + amname +-------- + heap +(1 row) + +-- Dependency pinned, hence removed. +SELECT pg_describe_object(classid, objid, objsubid) AS obj, + pg_describe_object(refclassid, refobjid, refobjsubid) as refobj + FROM pg_depend, pg_am + WHERE pg_depend.refclassid = 'pg_am'::regclass + AND pg_am.oid = pg_depend.refobjid + AND pg_depend.objid = 'am_partitioned'::regclass; + obj | refobj +-----+-------- +(0 rows) + +-- Default and AM set in the clause are the same, relam should be set. +SET LOCAL default_table_access_method = 'heap2'; +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'am_partitioned' AND a.oid = c.relam; + amname +-------- + heap2 +(1 row) + +-- Reset to default +ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT; +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; + relam +------- + 0 +(1 row) + +-- Upon ALTER TABLE SET ACCESS METHOD on a partitioned table, new partitions +-- will inherit the AM set. Existing partitioned are unchanged. +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; + relam +------- + 0 +(1 row) + +SET LOCAL default_table_access_method = 'heap'; +CREATE TABLE am_partitioned_0 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 0); +SET LOCAL default_table_access_method = 'heap2'; +CREATE TABLE am_partitioned_1 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 1); +SET LOCAL default_table_access_method = 'heap'; +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +CREATE TABLE am_partitioned_2 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 2); +ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT; +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; + relam +------- + 0 +(1 row) + +CREATE TABLE am_partitioned_3 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 3); +-- Partitioned table with relam at 0 +ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT; +CREATE TABLE am_partitioned_5p PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 5) PARTITION BY hash(y); +-- Partitions of this partitioned table inherit default AM at creation +-- time. +CREATE TABLE am_partitioned_5p1 PARTITION OF am_partitioned_5p + FOR VALUES WITH (MODULUS 10, REMAINDER 1); +-- Partitioned table with relam set. +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +CREATE TABLE am_partitioned_6p PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 6) PARTITION BY hash(y); +-- Partitions of this partitioned table inherit its AM. +CREATE TABLE am_partitioned_6p1 PARTITION OF am_partitioned_6p + FOR VALUES WITH (MODULUS 10, REMAINDER 1); +SELECT c.relname, a.amname FROM pg_class c, pg_am a + WHERE c.relam = a.oid AND + c.relname LIKE 'am_partitioned%' +UNION ALL +SELECT c.relname, 'default' FROM pg_class c + WHERE c.relam = 0 + AND c.relname LIKE 'am_partitioned%' ORDER BY 1; + relname | amname +--------------------+--------- + am_partitioned | heap2 + am_partitioned_0 | heap + am_partitioned_1 | heap2 + am_partitioned_2 | heap2 + am_partitioned_3 | heap + am_partitioned_5p | default + am_partitioned_5p1 | heap + am_partitioned_6p | heap2 + am_partitioned_6p1 | heap2 +(9 rows) + DROP TABLE am_partitioned; +COMMIT; -- Second, create objects in the new AM by changing the default AM BEGIN; SET LOCAL default_table_access_method = 'heap2'; diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 606ee4cb24..adff0079f9 100644 --- a/src/test/regress/sql/create_am.sql +++ b/src/test/regress/sql/create_am.sql @@ -124,8 +124,11 @@ CREATE SEQUENCE tableam_seq_heap2 USING heap2; CREATE MATERIALIZED VIEW tableam_tblmv_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2; SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1; --- CREATE TABLE .. PARTITION BY doesn't not support USING +-- CREATE TABLE .. PARTITION BY supports USING. CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a) USING heap2; +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'tableam_parted_heap2' AND a.oid = c.relam; +DROP TABLE tableam_parted_heap2; CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a); -- new partitions will inherit from the current default, rather the partition root @@ -213,11 +216,91 @@ ALTER TABLE heaptable SET ACCESS METHOD DEFAULT, SET ACCESS METHOD heap2; ALTER MATERIALIZED VIEW heapmv SET ACCESS METHOD heap, SET ACCESS METHOD heap2; DROP MATERIALIZED VIEW heapmv; DROP TABLE heaptable; --- No support for partitioned tables. -CREATE TABLE am_partitioned(x INT, y INT) - PARTITION BY hash (x); + +-- Partition hierarchies with access methods +BEGIN; +SET LOCAL default_table_access_method = 'heap'; +CREATE TABLE am_partitioned(x INT, y INT) PARTITION BY hash (x); +-- pg_class.relam is 0, no dependency recorded between the AM and the +-- partitioned table. +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; +SELECT pg_describe_object(classid, objid, objsubid) AS obj, + pg_describe_object(refclassid, refobjid, refobjsubid) as refobj + FROM pg_depend, pg_am + WHERE pg_depend.refclassid = 'pg_am'::regclass + AND pg_am.oid = pg_depend.refobjid + AND pg_depend.objid = 'am_partitioned'::regclass; +-- New default is set, with dependency added. ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'am_partitioned' AND a.oid = c.relam; +SELECT pg_describe_object(classid, objid, objsubid) AS obj, + pg_describe_object(refclassid, refobjid, refobjsubid) as refobj + FROM pg_depend, pg_am + WHERE pg_depend.refclassid = 'pg_am'::regclass + AND pg_am.oid = pg_depend.refobjid + AND pg_depend.objid = 'am_partitioned'::regclass; +-- Default is set, with dependency updated. +SET LOCAL default_table_access_method = 'heap2'; +ALTER TABLE am_partitioned SET ACCESS METHOD heap; +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'am_partitioned' AND a.oid = c.relam; +-- Dependency pinned, hence removed. +SELECT pg_describe_object(classid, objid, objsubid) AS obj, + pg_describe_object(refclassid, refobjid, refobjsubid) as refobj + FROM pg_depend, pg_am + WHERE pg_depend.refclassid = 'pg_am'::regclass + AND pg_am.oid = pg_depend.refobjid + AND pg_depend.objid = 'am_partitioned'::regclass; +-- Default and AM set in the clause are the same, relam should be set. +SET LOCAL default_table_access_method = 'heap2'; +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'am_partitioned' AND a.oid = c.relam; +-- Reset to default +ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT; +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; +-- Upon ALTER TABLE SET ACCESS METHOD on a partitioned table, new partitions +-- will inherit the AM set. Existing partitioned are unchanged. +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; +SET LOCAL default_table_access_method = 'heap'; +CREATE TABLE am_partitioned_0 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 0); +SET LOCAL default_table_access_method = 'heap2'; +CREATE TABLE am_partitioned_1 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 1); +SET LOCAL default_table_access_method = 'heap'; +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +CREATE TABLE am_partitioned_2 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 2); +ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT; +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; +CREATE TABLE am_partitioned_3 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 3); +-- Partitioned table with relam at 0 +ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT; +CREATE TABLE am_partitioned_5p PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 5) PARTITION BY hash(y); +-- Partitions of this partitioned table inherit default AM at creation +-- time. +CREATE TABLE am_partitioned_5p1 PARTITION OF am_partitioned_5p + FOR VALUES WITH (MODULUS 10, REMAINDER 1); +-- Partitioned table with relam set. +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +CREATE TABLE am_partitioned_6p PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 6) PARTITION BY hash(y); +-- Partitions of this partitioned table inherit its AM. +CREATE TABLE am_partitioned_6p1 PARTITION OF am_partitioned_6p + FOR VALUES WITH (MODULUS 10, REMAINDER 1); +SELECT c.relname, a.amname FROM pg_class c, pg_am a + WHERE c.relam = a.oid AND + c.relname LIKE 'am_partitioned%' +UNION ALL +SELECT c.relname, 'default' FROM pg_class c + WHERE c.relam = 0 + AND c.relname LIKE 'am_partitioned%' ORDER BY 1; DROP TABLE am_partitioned; +COMMIT; -- Second, create objects in the new AM by changing the default AM BEGIN; -- 2.39.2 --bmc3sziemswrdp7y-- ^ permalink raw reply [nested|flat] 3+ messages in thread
* [PATCH v5] Allow specifying access method for partitioned tables @ 2024-03-08 04:24 Michael Paquier <michael@paquier.xyz> 0 siblings, 0 replies; 3+ messages in thread From: Michael Paquier @ 2024-03-08 04:24 UTC (permalink / raw) Partitioned tables gain support for CREATE TABLE .. USING, where specifying an access method can serve as a hint when creating partitions on it. This includes support for ALTER TABLE .. SET ACCESS METHOD, where specifying DEFAULT is equivalent to resetting the partitioned table's relam to 0. Specifying a non-DEFAULT access method will set its relam to not be 0. The historical default of using pg_class.relam as InvalidOid is preserved, corresponding to the case where USING clause is not used. In this case, like previous releases, a partition is created with the access method set by default_table_access_method if its CREATE TABLE query does not specify an access method with a USING clause. The relcache of partitioned tables is not changed: rd_tableam is not set, even if a partitioned table has a relam set. Authors: Justin Pryzby, Soumyadeep Chakraborty Reviewed-by: Michael Paquier, and a few others.. Discussion: https://postgr.es/m/ --- doc/src/sgml/catalogs.sgml | 8 +- doc/src/sgml/ref/alter_table.sgml | 8 ++ doc/src/sgml/ref/create_table.sgml | 4 + src/backend/commands/tablecmds.c | 165 +++++++++++++++++++----- src/backend/utils/cache/lsyscache.c | 22 ++++ src/backend/utils/cache/relcache.c | 7 + src/bin/pg_dump/pg_dump.c | 3 +- src/bin/pg_dump/t/002_pg_dump.pl | 35 +++++ src/include/catalog/pg_class.h | 4 +- src/include/utils/lsyscache.h | 1 + src/test/regress/expected/create_am.out | 158 ++++++++++++++++++++++- src/test/regress/sql/create_am.sql | 91 ++++++++++++- 12 files changed, 462 insertions(+), 44 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index b7980eb499..03815e10e8 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1989,8 +1989,12 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para> <para> If this is a table or an index, the access method used (heap, - B-tree, hash, etc.); otherwise zero (zero occurs for sequences, - as well as relations without storage, such as views) + B-tree, hash, etc.); for partitioned tables, the access method + for newly created partitions, when one is not specified in the + creation command, or zero to make creation fall back to + <varname>default_table_access_method</varname>. + Zero for sequences, as well as other relations without + storage, such as views. </para></entry> </row> diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 96e3d77605..b6e794e6ce 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -737,6 +737,14 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM <literal>DEFAULT</literal> changes the access method of the table to <xref linkend="guc-default-table-access-method"/>. </para> + <para> + When applied to a partitioned table, there is no data to rewrite, but any + partitions created afterwards will use that access method unless + overridden by a <literal>USING</literal> clause. Using + <varname>DEFAULT</varname> switches the partitioned table to use + <varname>default_table_access_method</varname> when new partitions are + created. + </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 4cbaaccaf7..b79081a5ec 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1330,6 +1330,10 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM method is chosen for the new table. See <xref linkend="guc-default-table-access-method"/> for more information. </para> + <para> + When creating a partition, the table access method is the access method + of its partitioned table, if set. + </para> </listitem> </varlistentry> diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6c0c899210..9e36aae425 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -183,7 +183,9 @@ typedef struct AlteredTableInfo List *afterStmts; /* List of utility command parsetrees */ bool verify_new_notnull; /* T if we should recheck NOT NULL */ int rewrite; /* Reason for forced rewrite, if any */ - Oid newAccessMethod; /* new access method; 0 means no change */ + bool chgAccessMethod; /* T if SET ACCESS METHOD is used */ + Oid newAccessMethod; /* new access method; 0 means no change, + * if above is true */ Oid newTableSpace; /* new tablespace; 0 means no change */ bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */ char newrelpersistence; /* if above is true */ @@ -589,6 +591,7 @@ static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode); static void ATExecDropCluster(Relation rel, LOCKMODE lockmode); static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname); +static void ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethod); static bool ATPrepChangePersistence(Relation rel, bool toLogged); static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacename, LOCKMODE lockmode); @@ -703,7 +706,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, Oid ofTypeId; ObjectAddress address; LOCKMODE parentLockmode; - const char *accessMethod = NULL; Oid accessMethodId = InvalidOid; /* @@ -948,24 +950,22 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * If the statement hasn't specified an access method, but we're defining - * a type of relation that needs one, use the default. + * Select access method to use: an explicitly indicated one, or (in the + * case of a partitioned table) the parent's, if it has one. */ if (stmt->accessMethod != NULL) + accessMethodId = get_table_am_oid(stmt->accessMethod, false); + else if (stmt->partbound) { - accessMethod = stmt->accessMethod; - - if (partitioned) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("specifying a table access method is not supported on a partitioned table"))); + Assert(list_length(inheritOids) == 1); + accessMethodId = get_rel_relam(linitial_oid(inheritOids)); } - else if (RELKIND_HAS_TABLE_AM(relkind)) - accessMethod = default_table_access_method; + else + accessMethodId = InvalidOid; - /* look up the access method, verify it is for a table */ - if (accessMethod != NULL) - accessMethodId = get_table_am_oid(accessMethod, false); + /* still nothing? use the default */ + if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) + accessMethodId = get_table_am_oid(default_table_access_method, false); /* * Create the relation. Inherited defaults and constraints are passed in @@ -5041,14 +5041,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_SetAccessMethod: /* SET ACCESS METHOD */ ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW); - /* partitioned tables don't have an access method */ - if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change access method of a partitioned table"))); - /* check if another access method change was already requested */ - if (OidIsValid(tab->newAccessMethod)) + if (tab->chgAccessMethod) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot have multiple SET ACCESS METHOD subcommands"))); @@ -5402,7 +5396,14 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, /* nothing to do here, oid columns don't exist anymore */ break; case AT_SetAccessMethod: /* SET ACCESS METHOD */ - /* handled specially in Phase 3 */ + + /* + * 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 == RELKIND_PARTITIONED_TABLE && + tab->chgAccessMethod) + ATExecSetAccessMethodNoStorage(rel, tab->newAccessMethod); break; case AT_SetTableSpace: /* SET TABLESPACE */ @@ -5808,7 +5809,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, * Select destination access method (same as original unless user * requested a change) */ - if (OidIsValid(tab->newAccessMethod)) + if (tab->chgAccessMethod) NewAccessMethod = tab->newAccessMethod; else NewAccessMethod = OldHeap->rd_rel->relam; @@ -6395,6 +6396,7 @@ ATGetQueueEntry(List **wqueue, Relation rel) tab->relkind = rel->rd_rel->relkind; tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel)); tab->newAccessMethod = InvalidOid; + tab->chgAccessMethod = false; tab->newTableSpace = InvalidOid; tab->newrelpersistence = RELPERSISTENCE_PERMANENT; tab->chgPersistence = false; @@ -15197,25 +15199,128 @@ ATExecDropCluster(Relation rel, LOCKMODE lockmode) /* * Preparation phase for SET ACCESS METHOD * - * Check that access method exists. If it is the same as the table's current - * access method, it is a no-op. Otherwise, a table rewrite is necessary. - * If amname is NULL, select default_table_access_method as access method. + * Check that the access method exists and determine whether a change is + * actually needed. */ static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname) { Oid amoid; - /* Check that the table access method exists */ - amoid = get_table_am_oid(amname ? amname : default_table_access_method, - false); + /* + * Look up the access method name and check that it differs from the + * table's current AM. If DEFAULT was specified for a partitioned table + * (amname is NULL), set it to InvalidOid to reset the catalogued AM. + */ + if (amname != NULL) + amoid = get_table_am_oid(amname, false); + else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + amoid = InvalidOid; + else + amoid = get_table_am_oid(default_table_access_method, false); + /* if it's a match, phase 3 doesn't need to do anything */ if (rel->rd_rel->relam == amoid) return; /* Save info for Phase 3 to do the real work */ tab->rewrite |= AT_REWRITE_ACCESS_METHOD; tab->newAccessMethod = amoid; + tab->chgAccessMethod = true; +} + +/* + * Special handling of ALTER TABLE SET ACCESS METHOD for relations with no + * storage that have an interest in preserving AM. + * + * Since these have no storage, setting the access method is a catalog only + * operation. + */ +static void +ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId) +{ + Relation pg_class; + Oid oldAccessMethodId; + HeapTuple tuple; + Form_pg_class rd_rel; + Oid reloid = 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 = table_open(RelationRelationId, RowExclusiveLock); + + tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", reloid); + rd_rel = (Form_pg_class) GETSTRUCT(tuple); + + /* Update the pg_class row. */ + oldAccessMethodId = rd_rel->relam; + rd_rel->relam = newAccessMethodId; + + /* Leave if no update required */ + if (rd_rel->relam == oldAccessMethodId) + { + heap_freetuple(tuple); + table_close(pg_class, RowExclusiveLock); + return; + } + + CatalogTupleUpdate(pg_class, &tuple->t_self, tuple); + + /* + * Update the dependency on the new access method. No dependency is added + * if the new access method is InvalidOid (default case). Be very careful + * that this has to compare the previous value stored in pg_class with the + * new one. + */ + if (!OidIsValid(oldAccessMethodId) && OidIsValid(rd_rel->relam)) + { + ObjectAddress relobj, + referenced; + + /* + * New access method is defined and there was no dependency + * previously, so record a new one. + */ + ObjectAddressSet(relobj, RelationRelationId, reloid); + ObjectAddressSet(referenced, AccessMethodRelationId, rd_rel->relam); + recordDependencyOn(&relobj, &referenced, DEPENDENCY_NORMAL); + } + else if (OidIsValid(oldAccessMethodId) && + !OidIsValid(rd_rel->relam)) + { + /* + * There was an access method defined, and no new one, so just remove + * the existing dependency. + */ + deleteDependencyRecordsForClass(RelationRelationId, reloid, + AccessMethodRelationId, + DEPENDENCY_NORMAL); + } + else + { + Assert(OidIsValid(oldAccessMethodId) && + OidIsValid(rd_rel->relam)); + + /* Both are valid, so update the dependency */ + changeDependencyFor(RelationRelationId, reloid, + AccessMethodRelationId, + oldAccessMethodId, rd_rel->relam); + } + + /* make the relam and dependency changes visible */ + CommandCounterIncrement(); + + InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0); + + heap_freetuple(tuple); + table_close(pg_class, RowExclusiveLock); } /* diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 6418d1c6eb..26368ffcc9 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -2069,6 +2069,28 @@ get_rel_persistence(Oid relid) return result; } +/* + * get_rel_relam + * + * Returns the relam associated with a given relation. + */ +Oid +get_rel_relam(Oid relid) +{ + HeapTuple tp; + Form_pg_class reltup; + Oid result; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for relation %u", relid); + reltup = (Form_pg_class) GETSTRUCT(tp); + result = reltup->relam; + ReleaseSysCache(tp); + + return result; +} + /* ---------- TRANSFORM CACHE ---------- */ diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 2cd19d603f..404861856f 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -1208,6 +1208,13 @@ retry: else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE) RelationInitTableAccessMethod(relation); + else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + /* + * Do nothing: access methods are a setting that partitions can + * inherit. + */ + } else Assert(relation->rd_rel->relam == InvalidOid); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index d275b31605..b1c4c3ec7f 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -16656,7 +16656,8 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) if (RELKIND_HAS_TABLESPACE(tbinfo->relkind)) tablespace = tbinfo->reltablespace; - if (RELKIND_HAS_TABLE_AM(tbinfo->relkind)) + if (RELKIND_HAS_TABLE_AM(tbinfo->relkind) || + tbinfo->relkind == RELKIND_PARTITIONED_TABLE) tableam = tbinfo->amname; ArchiveEntry(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId, diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index c8b489d94e..f0410ce6a1 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -4587,6 +4587,41 @@ my %tests = ( no_table_access_method => 1, only_dump_measurement => 1, }, + }, + + # CREATE TABLE with partitioned table and various AMs. One + # partition uses the same default as the parent, and a second + # uses its own AM. + 'CREATE TABLE regress_pg_dump_table_part' => { + create_order => 19, + create_sql => ' + CREATE TABLE dump_test.regress_pg_dump_table_am_parent (id int) PARTITION BY LIST (id); + ALTER TABLE dump_test.regress_pg_dump_table_am_parent SET ACCESS METHOD regress_table_am; + CREATE TABLE dump_test.regress_pg_dump_table_am_child_1 + PARTITION OF dump_test.regress_pg_dump_table_am_parent FOR VALUES IN (1) USING heap; + CREATE TABLE dump_test.regress_pg_dump_table_am_child_2 + PARTITION OF dump_test.regress_pg_dump_table_am_parent FOR VALUES IN (2);', + regexp => qr/^ + \QSET default_table_access_method = regress_table_am;\E + (\n(?!SET[^;]+;)[^\n]*)* + \n\QCREATE TABLE dump_test.regress_pg_dump_table_am_parent (\E + (.*\n)* + \QSET default_table_access_method = heap;\E + (\n(?!SET[^;]+;)[^\n]*)* + \n\QCREATE TABLE dump_test.regress_pg_dump_table_am_child_1 (\E + (.*\n)* + \QSET default_table_access_method = regress_table_am;\E + (\n(?!SET[^;]+;)[^\n]*)* + \n\QCREATE TABLE dump_test.regress_pg_dump_table_am_child_2 (\E + (.*\n)*/xm, + like => { + %full_runs, %dump_test_schema_runs, section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_table_access_method => 1, + only_dump_measurement => 1, + }, }); ######################################### diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 3b7533e7bb..0fc2c093b0 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -219,7 +219,9 @@ MAKE_SYSCACHE(RELNAMENSP, pg_class_relname_nsp_index, 128); /* * Relation kinds with a table access method (rd_tableam). Although sequences * use the heap table AM, they are enough of a special case in most uses that - * they are not included here. + * they are not included here. Likewise, partitioned tables can have an access + * method defined so that their partitions can inherit it, but they do not set + * rd_tableam; hence, this is handled specially outside of this macro. */ #define RELKIND_HAS_TABLE_AM(relkind) \ ((relkind) == RELKIND_RELATION || \ diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index e4a200b00e..35a8dec2b9 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -139,6 +139,7 @@ extern char get_rel_relkind(Oid relid); extern bool get_rel_relispartition(Oid relid); extern Oid get_rel_tablespace(Oid relid); extern char get_rel_persistence(Oid relid); +extern Oid get_rel_relam(Oid relid); extern Oid get_transform_fromsql(Oid typid, Oid langid, List *trftypes); extern Oid get_transform_tosql(Oid typid, Oid langid, List *trftypes); extern bool get_typisdefined(Oid typid); diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out index 8d73e21356..a27805a8f5 100644 --- a/src/test/regress/expected/create_am.out +++ b/src/test/regress/expected/create_am.out @@ -176,9 +176,16 @@ SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1; 1 (1 row) --- CREATE TABLE .. PARTITION BY doesn't not support USING +-- CREATE TABLE .. PARTITION BY supports USING. CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a) USING heap2; -ERROR: specifying a table access method is not supported on a partitioned table +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'tableam_parted_heap2' AND a.oid = c.relam; + amname +-------- + heap2 +(1 row) + +DROP TABLE tableam_parted_heap2; CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a); -- new partitions will inherit from the current default, rather the partition root SET default_table_access_method = 'heap'; @@ -336,12 +343,151 @@ ALTER MATERIALIZED VIEW heapmv SET ACCESS METHOD heap, SET ACCESS METHOD heap2; ERROR: cannot have multiple SET ACCESS METHOD subcommands DROP MATERIALIZED VIEW heapmv; DROP TABLE heaptable; --- No support for partitioned tables. -CREATE TABLE am_partitioned(x INT, y INT) - PARTITION BY hash (x); +-- Partition hierarchies with access methods +BEGIN; +SET LOCAL default_table_access_method = 'heap'; +CREATE TABLE am_partitioned(x INT, y INT) PARTITION BY hash (x); +-- pg_class.relam is 0, no dependency recorded between the AM and the +-- partitioned table. +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; + relam +------- + 0 +(1 row) + +SELECT pg_describe_object(classid, objid, objsubid) AS obj, + pg_describe_object(refclassid, refobjid, refobjsubid) as refobj + FROM pg_depend, pg_am + WHERE pg_depend.refclassid = 'pg_am'::regclass + AND pg_am.oid = pg_depend.refobjid + AND pg_depend.objid = 'am_partitioned'::regclass; + obj | refobj +-----+-------- +(0 rows) + +-- New default is set, with dependency added. ALTER TABLE am_partitioned SET ACCESS METHOD heap2; -ERROR: cannot change access method of a partitioned table +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'am_partitioned' AND a.oid = c.relam; + amname +-------- + heap2 +(1 row) + +SELECT pg_describe_object(classid, objid, objsubid) AS obj, + pg_describe_object(refclassid, refobjid, refobjsubid) as refobj + FROM pg_depend, pg_am + WHERE pg_depend.refclassid = 'pg_am'::regclass + AND pg_am.oid = pg_depend.refobjid + AND pg_depend.objid = 'am_partitioned'::regclass; + obj | refobj +----------------------+--------------------- + table am_partitioned | access method heap2 +(1 row) + +-- Default is set, with dependency updated. +SET LOCAL default_table_access_method = 'heap2'; +ALTER TABLE am_partitioned SET ACCESS METHOD heap; +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'am_partitioned' AND a.oid = c.relam; + amname +-------- + heap +(1 row) + +-- Dependency pinned, hence removed. +SELECT pg_describe_object(classid, objid, objsubid) AS obj, + pg_describe_object(refclassid, refobjid, refobjsubid) as refobj + FROM pg_depend, pg_am + WHERE pg_depend.refclassid = 'pg_am'::regclass + AND pg_am.oid = pg_depend.refobjid + AND pg_depend.objid = 'am_partitioned'::regclass; + obj | refobj +-----+-------- +(0 rows) + +-- Default and AM set in the clause are the same, relam should be set. +SET LOCAL default_table_access_method = 'heap2'; +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'am_partitioned' AND a.oid = c.relam; + amname +-------- + heap2 +(1 row) + +-- Reset to default +ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT; +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; + relam +------- + 0 +(1 row) + +-- Upon ALTER TABLE SET ACCESS METHOD on a partitioned table, new partitions +-- will inherit the AM set. Existing partitioned are unchanged. +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; + relam +------- + 0 +(1 row) + +SET LOCAL default_table_access_method = 'heap'; +CREATE TABLE am_partitioned_0 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 0); +SET LOCAL default_table_access_method = 'heap2'; +CREATE TABLE am_partitioned_1 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 1); +SET LOCAL default_table_access_method = 'heap'; +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +CREATE TABLE am_partitioned_2 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 2); +ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT; +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; + relam +------- + 0 +(1 row) + +CREATE TABLE am_partitioned_3 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 3); +-- Partitioned table with relam at 0 +ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT; +CREATE TABLE am_partitioned_5p PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 5) PARTITION BY hash(y); +-- Partitions of this partitioned table inherit default AM at creation +-- time. +CREATE TABLE am_partitioned_5p1 PARTITION OF am_partitioned_5p + FOR VALUES WITH (MODULUS 10, REMAINDER 1); +-- Partitioned table with relam set. +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +CREATE TABLE am_partitioned_6p PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 6) PARTITION BY hash(y); +-- Partitions of this partitioned table inherit its AM. +CREATE TABLE am_partitioned_6p1 PARTITION OF am_partitioned_6p + FOR VALUES WITH (MODULUS 10, REMAINDER 1); +SELECT c.relname, a.amname FROM pg_class c, pg_am a + WHERE c.relam = a.oid AND + c.relname LIKE 'am_partitioned%' +UNION ALL +SELECT c.relname, 'default' FROM pg_class c + WHERE c.relam = 0 + AND c.relname LIKE 'am_partitioned%' ORDER BY 1; + relname | amname +--------------------+--------- + am_partitioned | heap2 + am_partitioned_0 | heap + am_partitioned_1 | heap2 + am_partitioned_2 | heap2 + am_partitioned_3 | heap + am_partitioned_5p | default + am_partitioned_5p1 | heap + am_partitioned_6p | heap2 + am_partitioned_6p1 | heap2 +(9 rows) + DROP TABLE am_partitioned; +COMMIT; -- Second, create objects in the new AM by changing the default AM BEGIN; SET LOCAL default_table_access_method = 'heap2'; diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 606ee4cb24..adff0079f9 100644 --- a/src/test/regress/sql/create_am.sql +++ b/src/test/regress/sql/create_am.sql @@ -124,8 +124,11 @@ CREATE SEQUENCE tableam_seq_heap2 USING heap2; CREATE MATERIALIZED VIEW tableam_tblmv_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2; SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1; --- CREATE TABLE .. PARTITION BY doesn't not support USING +-- CREATE TABLE .. PARTITION BY supports USING. CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a) USING heap2; +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'tableam_parted_heap2' AND a.oid = c.relam; +DROP TABLE tableam_parted_heap2; CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a); -- new partitions will inherit from the current default, rather the partition root @@ -213,11 +216,91 @@ ALTER TABLE heaptable SET ACCESS METHOD DEFAULT, SET ACCESS METHOD heap2; ALTER MATERIALIZED VIEW heapmv SET ACCESS METHOD heap, SET ACCESS METHOD heap2; DROP MATERIALIZED VIEW heapmv; DROP TABLE heaptable; --- No support for partitioned tables. -CREATE TABLE am_partitioned(x INT, y INT) - PARTITION BY hash (x); + +-- Partition hierarchies with access methods +BEGIN; +SET LOCAL default_table_access_method = 'heap'; +CREATE TABLE am_partitioned(x INT, y INT) PARTITION BY hash (x); +-- pg_class.relam is 0, no dependency recorded between the AM and the +-- partitioned table. +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; +SELECT pg_describe_object(classid, objid, objsubid) AS obj, + pg_describe_object(refclassid, refobjid, refobjsubid) as refobj + FROM pg_depend, pg_am + WHERE pg_depend.refclassid = 'pg_am'::regclass + AND pg_am.oid = pg_depend.refobjid + AND pg_depend.objid = 'am_partitioned'::regclass; +-- New default is set, with dependency added. ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'am_partitioned' AND a.oid = c.relam; +SELECT pg_describe_object(classid, objid, objsubid) AS obj, + pg_describe_object(refclassid, refobjid, refobjsubid) as refobj + FROM pg_depend, pg_am + WHERE pg_depend.refclassid = 'pg_am'::regclass + AND pg_am.oid = pg_depend.refobjid + AND pg_depend.objid = 'am_partitioned'::regclass; +-- Default is set, with dependency updated. +SET LOCAL default_table_access_method = 'heap2'; +ALTER TABLE am_partitioned SET ACCESS METHOD heap; +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'am_partitioned' AND a.oid = c.relam; +-- Dependency pinned, hence removed. +SELECT pg_describe_object(classid, objid, objsubid) AS obj, + pg_describe_object(refclassid, refobjid, refobjsubid) as refobj + FROM pg_depend, pg_am + WHERE pg_depend.refclassid = 'pg_am'::regclass + AND pg_am.oid = pg_depend.refobjid + AND pg_depend.objid = 'am_partitioned'::regclass; +-- Default and AM set in the clause are the same, relam should be set. +SET LOCAL default_table_access_method = 'heap2'; +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +SELECT a.amname FROM pg_class c, pg_am a + WHERE c.relname = 'am_partitioned' AND a.oid = c.relam; +-- Reset to default +ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT; +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; +-- Upon ALTER TABLE SET ACCESS METHOD on a partitioned table, new partitions +-- will inherit the AM set. Existing partitioned are unchanged. +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; +SET LOCAL default_table_access_method = 'heap'; +CREATE TABLE am_partitioned_0 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 0); +SET LOCAL default_table_access_method = 'heap2'; +CREATE TABLE am_partitioned_1 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 1); +SET LOCAL default_table_access_method = 'heap'; +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +CREATE TABLE am_partitioned_2 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 2); +ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT; +SELECT relam FROM pg_class WHERE relname = 'am_partitioned'; +CREATE TABLE am_partitioned_3 PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 3); +-- Partitioned table with relam at 0 +ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT; +CREATE TABLE am_partitioned_5p PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 5) PARTITION BY hash(y); +-- Partitions of this partitioned table inherit default AM at creation +-- time. +CREATE TABLE am_partitioned_5p1 PARTITION OF am_partitioned_5p + FOR VALUES WITH (MODULUS 10, REMAINDER 1); +-- Partitioned table with relam set. +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +CREATE TABLE am_partitioned_6p PARTITION OF am_partitioned + FOR VALUES WITH (MODULUS 10, REMAINDER 6) PARTITION BY hash(y); +-- Partitions of this partitioned table inherit its AM. +CREATE TABLE am_partitioned_6p1 PARTITION OF am_partitioned_6p + FOR VALUES WITH (MODULUS 10, REMAINDER 1); +SELECT c.relname, a.amname FROM pg_class c, pg_am a + WHERE c.relam = a.oid AND + c.relname LIKE 'am_partitioned%' +UNION ALL +SELECT c.relname, 'default' FROM pg_class c + WHERE c.relam = 0 + AND c.relname LIKE 'am_partitioned%' ORDER BY 1; DROP TABLE am_partitioned; +COMMIT; -- Second, create objects in the new AM by changing the default AM BEGIN; -- 2.39.2 --bmc3sziemswrdp7y-- ^ permalink raw reply [nested|flat] 3+ messages in thread
* [PATCH] Allow progress tracking of sub-commands. @ 2026-05-04 09:32 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 3+ messages in thread From: Antonin Houska @ 2026-05-04 09:32 UTC (permalink / raw) Some commands that support progress reporting run sub-commands, which also report their progress. The typical case is that REPACK builds indexes. Instead of disabling the progress tracking of the sub-commands, we can allow both the "parent" command and the sub-command to report their progress at the same time. --- contrib/file_fdw/file_fdw.c | 44 +++++++++++ src/backend/commands/indexcmds.c | 10 ++- src/backend/commands/repack.c | 14 +++- src/backend/utils/activity/backend_progress.c | 76 ++++++++++++++++--- src/backend/utils/activity/backend_status.c | 1 + src/backend/utils/adt/pgstatfuncs.c | 9 ++- src/include/utils/backend_status.h | 7 ++ 7 files changed, 142 insertions(+), 19 deletions(-) diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c index 33a37d832ce..a60fb226320 100644 --- a/contrib/file_fdw/file_fdw.c +++ b/contrib/file_fdw/file_fdw.c @@ -36,6 +36,7 @@ #include "optimizer/pathnode.h" #include "optimizer/planmain.h" #include "optimizer/restrictinfo.h" +#include "utils/backend_status.h" #include "utils/acl.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -119,6 +120,16 @@ typedef struct FileFdwExecutionState CopyFromState cstate; /* COPY execution state */ } FileFdwExecutionState; +/* + * Since progress tracking of multiple COPY commands is not supported, the + * first file_fdw node of the plan needs to set pgstat_track_activities to + * false during startup, and the last active node needs to restore the + * original value during shutdown. + */ +static bool save_pgstat_track_activities = false; +static int fdw_nodes = 0; +static int active_fdw_nodes = 0; + /* * SQL functions */ @@ -616,6 +627,12 @@ fileGetForeignPlan(PlannerInfo *root, { Index scan_relid = baserel->relid; + /* + * This seems to be the appropriate place to count file_fdw nodes in the + * plan. + */ + fdw_nodes++; + /* * We have no native ability to evaluate restriction clauses, so we just * put all the scan_clauses into the plan node's qual list for the @@ -695,6 +712,18 @@ fileBeginForeignScan(ForeignScanState *node, int eflags) /* Add any options from the plan (currently only convert_selectively) */ options = list_concat(options, plan->fdw_private); + /* + * Save the value of pgstat_track_activities if this is the first file_fdw + * node of a plan containing multiple file_fdw nodes, and disable the + * progress tracking. The monitoring infrastructure currently does not + * support monitoring of multiple COPY commands. + */ + if (fdw_nodes > 1 && active_fdw_nodes++ == 0) + { + save_pgstat_track_activities = pgstat_track_activities; + pgstat_track_activities = false; + } + /* * Create CopyState from FDW options. We always acquire all columns, so * as to match the expected ScanTupleSlot signature. @@ -861,6 +890,21 @@ fileEndForeignScan(ForeignScanState *node) festate->cstate->num_errors)); EndCopyFrom(festate->cstate); + + + /* + * Restore the value of pgstat_track_activities if this is the last + * file_fdw node of a plan containing multiple file_fdw nodes, and enable + * progress tracking if we disabled it earlier. + */ + if (active_fdw_nodes > 0) + { + if (--active_fdw_nodes == 0) + { + pgstat_track_activities = save_pgstat_track_activities; + fdw_nodes = 0; + } + } } /* diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 9ab74c8df0a..7280e64d118 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -3918,6 +3918,12 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein * more detailed comments. */ + /* + * XXX Is there a reason not to start progress reporting here? If it's ok, + * then INDEX_CREATE_SUPPRESS_PROGRESS below is probably not needed. + */ + pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, relationOid); + foreach(lc, indexIds) { char *concurrentName; @@ -3966,8 +3972,6 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein if (indexRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) elog(ERROR, "cannot reindex a temporary table concurrently"); - pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, idx->tableId); - progress_vals[0] = PROGRESS_CREATEIDX_COMMAND_REINDEX_CONCURRENTLY; progress_vals[1] = 0; /* initializing */ progress_vals[2] = idx->indexId; @@ -4144,7 +4148,6 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein * Update progress for the index to build, with the correct parent * table involved. */ - pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, newidx->tableId); progress_vals[0] = PROGRESS_CREATEIDX_COMMAND_REINDEX_CONCURRENTLY; progress_vals[1] = PROGRESS_CREATEIDX_PHASE_BUILD; progress_vals[2] = newidx->indexId; @@ -4208,7 +4211,6 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein * Update progress for the index to build, with the correct parent * table involved. */ - pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, newidx->tableId); progress_vals[0] = PROGRESS_CREATEIDX_COMMAND_REINDEX_CONCURRENTLY; progress_vals[1] = PROGRESS_CREATEIDX_PHASE_VALIDATE_IDXSCAN; progress_vals[2] = newidx->indexId; diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 9d162957bc3..f360f37a9da 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1937,6 +1937,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, pgstat_progress_update_param(PROGRESS_REPACK_PHASE, PROGRESS_REPACK_PHASE_REBUILD_INDEX); + reindex_params.options |= REINDEXOPT_REPORT_PROGRESS; reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params); } @@ -3204,9 +3205,20 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes) "repacknew", get_rel_namespace(ind->rd_index->indrelid), false); - newindex = index_create_copy(NewHeap, INDEX_CREATE_SUPPRESS_PROGRESS, + + /* + * We build the index on the new heap, but after the swap phase it'll + * become an index on the old heap. It makes more sense to report the + * progress this way. (The reporting API expects that both command and + * subcommand deal with the same target.) + */ + pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, + RelationGetRelid(OldHeap)); + newindex = index_create_copy(NewHeap, 0, oldindex, ind->rd_rel->reltablespace, newName); + pgstat_progress_end_command(); + copy_index_constraints(ind, newindex, RelationGetRelid(NewHeap)); result = lappend_oid(result, newindex); diff --git a/src/backend/utils/activity/backend_progress.c b/src/backend/utils/activity/backend_progress.c index b0359771de5..97965a6973c 100644 --- a/src/backend/utils/activity/backend_progress.c +++ b/src/backend/utils/activity/backend_progress.c @@ -22,6 +22,10 @@ * * Set st_progress_command (and st_progress_command_target) in own backend * entry. Also, zero-initialize st_progress_param array. + * + * If command has already been started, start a sub-command. Only parameters + * of the sub-command are updated until pgstat_progress_end_command() is + * called. (Target relation must be the same for both commands.) *----------- */ void @@ -33,9 +37,30 @@ pgstat_progress_start_command(ProgressCommandType cmdtype, Oid relid) return; PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); - beentry->st_progress_command = cmdtype; - beentry->st_progress_command_target = relid; - MemSet(&beentry->st_progress_param, 0, sizeof(beentry->st_progress_param)); + /* Sub-command should not be started w/o parent command. */ + if (beentry->st_progress_command == PROGRESS_COMMAND_INVALID) + { + Assert(beentry->st_progress_command2 == PROGRESS_COMMAND_INVALID); + + beentry->st_progress_command = cmdtype; + beentry->st_progress_command_target = relid; + MemSet(&beentry->st_progress_param, 0, + sizeof(beentry->st_progress_param)); + } + else if (beentry->st_progress_command2 == PROGRESS_COMMAND_INVALID) + { + Assert(beentry->st_progress_command != PROGRESS_COMMAND_INVALID); + Assert(beentry->st_progress_command_target == relid); + + beentry->st_progress_command2 = cmdtype; + MemSet(&beentry->st_progress_param2, 0, + sizeof(beentry->st_progress_param2)); + } + else + { + /* Only one level of nesting is supported. */ + Assert(false); + } PGSTAT_END_WRITE_ACTIVITY(beentry); } @@ -49,14 +74,20 @@ void pgstat_progress_update_param(int index, int64 val) { volatile PgBackendStatus *beentry = MyBEEntry; + volatile int64 *params; Assert(index >= 0 && index < PGSTAT_NUM_PROGRESS_PARAM); + Assert(beentry->st_progress_command != PROGRESS_COMMAND_INVALID || + beentry->st_progress_command2 != PROGRESS_COMMAND_INVALID); if (!beentry || !pgstat_track_activities) return; + params = (beentry->st_progress_command2 == PROGRESS_COMMAND_INVALID) ? + beentry->st_progress_param : beentry->st_progress_param2; + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); - beentry->st_progress_param[index] = val; + params[index] = val; PGSTAT_END_WRITE_ACTIVITY(beentry); } @@ -70,14 +101,20 @@ void pgstat_progress_incr_param(int index, int64 incr) { volatile PgBackendStatus *beentry = MyBEEntry; + volatile int64 *params; Assert(index >= 0 && index < PGSTAT_NUM_PROGRESS_PARAM); + Assert(beentry->st_progress_command != PROGRESS_COMMAND_INVALID || + beentry->st_progress_command2 != PROGRESS_COMMAND_INVALID); if (!beentry || !pgstat_track_activities) return; + params = (beentry->st_progress_command2 == PROGRESS_COMMAND_INVALID) ? + beentry->st_progress_param : beentry->st_progress_param2; + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); - beentry->st_progress_param[index] += incr; + params[index] += incr; PGSTAT_END_WRITE_ACTIVITY(beentry); } @@ -124,17 +161,24 @@ pgstat_progress_update_multi_param(int nparam, const int *index, { volatile PgBackendStatus *beentry = MyBEEntry; int i; + volatile int64 *params; if (!beentry || !pgstat_track_activities || nparam == 0) return; + Assert(beentry->st_progress_command != PROGRESS_COMMAND_INVALID || + beentry->st_progress_command2 != PROGRESS_COMMAND_INVALID); + + params = (beentry->st_progress_command2 == PROGRESS_COMMAND_INVALID) ? + beentry->st_progress_param : beentry->st_progress_param2; + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); for (i = 0; i < nparam; ++i) { Assert(index[i] >= 0 && index[i] < PGSTAT_NUM_PROGRESS_PARAM); - beentry->st_progress_param[index[i]] = val[i]; + params[index[i]] = val[i]; } PGSTAT_END_WRITE_ACTIVITY(beentry); @@ -144,7 +188,7 @@ pgstat_progress_update_multi_param(int nparam, const int *index, * pgstat_progress_end_command() - * * Reset st_progress_command (and st_progress_command_target) in own backend - * entry. This signals the end of the command. + * entry. This signals the end of the command (or a sub-command). *----------- */ void @@ -155,11 +199,19 @@ pgstat_progress_end_command(void) if (!beentry || !pgstat_track_activities) return; - if (beentry->st_progress_command == PROGRESS_COMMAND_INVALID) - return; - PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); - beentry->st_progress_command = PROGRESS_COMMAND_INVALID; - beentry->st_progress_command_target = InvalidOid; + + if (beentry->st_progress_command2 != PROGRESS_COMMAND_INVALID) + { + Assert(beentry->st_progress_command != PROGRESS_COMMAND_INVALID); + + beentry->st_progress_command2 = PROGRESS_COMMAND_INVALID; + } + else + { + beentry->st_progress_command = PROGRESS_COMMAND_INVALID; + beentry->st_progress_command_target = InvalidOid; + } + PGSTAT_END_WRITE_ACTIVITY(beentry); } diff --git a/src/backend/utils/activity/backend_status.c b/src/backend/utils/activity/backend_status.c index d685fc5cd87..15675992415 100644 --- a/src/backend/utils/activity/backend_status.c +++ b/src/backend/utils/activity/backend_status.c @@ -284,6 +284,7 @@ pgstat_bestart_initial(void) lbeentry.st_state = STATE_STARTING; lbeentry.st_progress_command = PROGRESS_COMMAND_INVALID; + lbeentry.st_progress_command2 = PROGRESS_COMMAND_INVALID; lbeentry.st_progress_command_target = InvalidOid; lbeentry.st_query_id = INT64CONST(0); lbeentry.st_plan_id = INT64CONST(0); diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 7a9dfa9ba3b..c2257d900af 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -314,6 +314,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS) Datum values[PG_STAT_GET_PROGRESS_COLS] = {0}; bool nulls[PG_STAT_GET_PROGRESS_COLS] = {0}; int i; + volatile int64 *params; local_beentry = pgstat_get_local_beentry_by_index(curr_backend); beentry = &local_beentry->backendStatus; @@ -322,7 +323,11 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS) * Report values for only those backends which are running the given * command. */ - if (beentry->st_progress_command != cmdtype) + if (beentry->st_progress_command == cmdtype) + params = beentry->st_progress_param; + else if (beentry->st_progress_command2 == cmdtype) + params = beentry->st_progress_param2; + else continue; /* Value available to all callers */ @@ -334,7 +339,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS) { values[2] = ObjectIdGetDatum(beentry->st_progress_command_target); for (i = 0; i < PGSTAT_NUM_PROGRESS_PARAM; i++) - values[i + 3] = Int64GetDatum(beentry->st_progress_param[i]); + values[i + 3] = Int64GetDatum(params[i]); } else { diff --git a/src/include/utils/backend_status.h b/src/include/utils/backend_status.h index a334e096e4a..f528f7abeec 100644 --- a/src/include/utils/backend_status.h +++ b/src/include/utils/backend_status.h @@ -169,6 +169,13 @@ typedef struct PgBackendStatus Oid st_progress_command_target; int64 st_progress_param[PGSTAT_NUM_PROGRESS_PARAM]; + /* + * Some commands have a sub-command, e.g. REPACK (re)builds indexes. The + * subcommands are supposed to have the same target. + */ + ProgressCommandType st_progress_command2; + int64 st_progress_param2[PGSTAT_NUM_PROGRESS_PARAM]; + /* query identifier, optionally computed using post_parse_analyze_hook */ int64 st_query_id; -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 3+ messages in thread
end of thread, other threads:[~2026-05-04 09:32 UTC | newest] Thread overview: 3+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2024-03-08 04:24 [PATCH v5] Allow specifying access method for partitioned tables Michael Paquier <michael@paquier.xyz> 2024-03-08 04:24 [PATCH v5] Allow specifying access method for partitioned tables Michael Paquier <michael@paquier.xyz> 2026-05-04 09:32 [PATCH] Allow progress tracking of sub-commands. Antonin Houska <ah@cybertec.at>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox