Add new SCHEMA option to REINDEX and reindexdb.
Sawada Masahiko
Reviewed by Michael Paquier and FabrÃzio de Royes Mello
<refsynopsisdiv>
<synopsis>
-REINDEX { INDEX | TABLE | DATABASE | SYSTEM } <replaceable class="PARAMETER">name</replaceable> [ FORCE ]
+REINDEX { INDEX | TABLE | SCHEMA | DATABASE | SYSTEM } <replaceable class="PARAMETER">name</replaceable> [ FORCE ]
</synopsis>
</refsynopsisdiv>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>SCHEMA</literal></term>
+ <listitem>
+ <para>
+ Recreate all indexes of the specified schema. If a table of this
+ schema has a secondary <quote>TOAST</> table, that is reindexed as
+ well. Indexes on shared system catalogs are also processed.
+ This form of <command>REINDEX</command> cannot be executed inside a
+ transaction block.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DATABASE</literal></term>
<listitem>
<command>reindexdb</command>
<arg rep="repeat"><replaceable>connection-option</replaceable></arg>
+ <arg choice="plain" rep="repeat">
+ <arg choice="opt">
+ <group choice="plain">
+ <arg choice="plain"><option>--schema</option></arg>
+ <arg choice="plain"><option>-S</option></arg>
+ </group>
+ <replaceable>table</replaceable>
+ </arg>
+ </arg>
+
<arg choice="plain" rep="repeat">
<arg choice="opt">
<group choice="plain">
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-S <replaceable class="parameter">schema</replaceable></></term>
+ <term><option>--schema=<replaceable class="parameter">schema</replaceable></></term>
+ <listitem>
+ <para>
+ Reindex <replaceable class="parameter">schema</replaceable> only.
+ Multiple schemas can be reindexed by writing multiple
+ <option>-S</> switches.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-t <replaceable class="parameter">table</replaceable></></term>
<term><option>--table=<replaceable class="parameter">table</replaceable></></term>
}
/*
- * ReindexDatabase
- * Recreate indexes of a database.
+ * ReindexObject
+ * Recreate indexes of object whose type is defined by objectKind.
*
* To reduce the probability of deadlocks, each table is reindexed in a
* separate transaction, so we can release the lock on it right away.
* That means this must not be called within a user transaction block!
*/
Oid
-ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
+ReindexObject(const char *objectName, ReindexObjectType objectKind)
{
+ Oid objectOid;
Relation relationRelation;
HeapScanDesc scan;
+ ScanKeyData *scan_keys = NULL;
HeapTuple tuple;
MemoryContext private_context;
MemoryContext old;
List *relids = NIL;
ListCell *l;
+ int num_keys;
- AssertArg(databaseName);
+ AssertArg(objectName);
+ Assert(objectKind == REINDEX_OBJECT_SCHEMA ||
+ objectKind == REINDEX_OBJECT_SYSTEM ||
+ objectKind == REINDEX_OBJECT_DATABASE);
- if (strcmp(databaseName, get_database_name(MyDatabaseId)) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("can only reindex the currently open database")));
+ /*
+ * Get OID of object to reindex, being the database currently being
+ * used by session for a database or for system catalogs, or the schema
+ * defined by caller. At the same time do permission checks that need
+ * different processing depending on the object type.
+ */
+ if (objectKind == REINDEX_OBJECT_SCHEMA)
+ {
+ objectOid = get_namespace_oid(objectName, false);
- if (!pg_database_ownercheck(MyDatabaseId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
- databaseName);
+ if (!pg_namespace_ownercheck(objectOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE,
+ objectName);
+ }
+ else
+ {
+ objectOid = MyDatabaseId;
+
+ if (strcmp(objectName, get_database_name(MyDatabaseId)) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("can only reindex the currently open database")));
+ if (!pg_database_ownercheck(MyDatabaseId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
+ objectName);
+ }
/*
* Create a memory context that will survive forced transaction commits we
* abort cleanup logic.
*/
private_context = AllocSetContextCreate(PortalContext,
- "ReindexDatabase",
+ (objectKind == REINDEX_OBJECT_SCHEMA) ?
+ "ReindexSchema" : "ReindexDatabase",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/*
- * We always want to reindex pg_class first. This ensures that if there
- * is any corruption in pg_class' indexes, they will be fixed before we
- * process any other tables. This is critical because reindexing itself
- * will try to update pg_class.
+ * We always want to reindex pg_class first when reindexing system
+ * catalogs or a database. This ensures that if there is any corruption
+ * in pg_class' indexes, they will be fixed before we process any other
+ * tables. This is critical because reindexing itself will try to
+ * update pg_class.
*/
- if (do_system)
+ if (objectKind == REINDEX_OBJECT_DATABASE ||
+ objectKind == REINDEX_OBJECT_SYSTEM ||
+ (objectKind == REINDEX_OBJECT_SCHEMA &&
+ IsSystemNamespace(objectOid)))
{
old = MemoryContextSwitchTo(private_context);
relids = lappend_oid(relids, RelationRelationId);
MemoryContextSwitchTo(old);
}
+ /*
+ * Define the search keys to find the objects to reindex. For a schema,
+ * we search target relations using relnamespace and relkind, something
+ * not necessary for a database-wide operation.
+ */
+ if (objectKind == REINDEX_OBJECT_SCHEMA)
+ {
+ scan_keys = palloc(sizeof(ScanKeyData) * 2);
+ ScanKeyInit(&scan_keys[0],
+ Anum_pg_class_relnamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(objectOid));
+ ScanKeyInit(&scan_keys[1],
+ Anum_pg_class_relkind,
+ BTEqualStrategyNumber, F_CHAREQ,
+ 'r');
+ num_keys = 2;
+ }
+ else
+ num_keys = 0;
+
/*
* Scan pg_class to build a list of the relations we need to reindex.
*
* rels will be processed indirectly by reindex_relation).
*/
relationRelation = heap_open(RelationRelationId, AccessShareLock);
- scan = heap_beginscan_catalog(relationRelation, 0, NULL);
+ scan = heap_beginscan_catalog(relationRelation, num_keys, scan_keys);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
continue;
/* Check user/system classification, and optionally skip */
- if (IsSystemClass(relid, classtuple))
- {
- if (!do_system)
- continue;
- }
- else
- {
- if (!do_user)
- continue;
- }
+ if (!IsSystemClass(relid, classtuple) &&
+ objectKind == REINDEX_OBJECT_SYSTEM)
+ continue;
+ /*
+ * Already have it in the case of system catalogs being all
+ * reindexed, of a database or of a system catalog being reindexed
+ * as a schema.
+ */
if (HeapTupleGetOid(tuple) == RelationRelationId)
- continue; /* got it already */
+ continue;
old = MemoryContextSwitchTo(private_context);
relids = lappend_oid(relids, relid);
StartTransactionCommand();
MemoryContextDelete(private_context);
+ if (scan_keys)
+ pfree(scan_keys);
- return MyDatabaseId;
+ return objectOid;
}
%type <boolean> copy_from opt_program
%type <ival> opt_column event cursor_options opt_hold opt_set_data
-%type <objtype> reindex_type drop_type comment_type security_label_type
+%type <objtype> drop_type comment_type security_label_type
%type <node> fetch_args limit_clause select_limit_value
offset_clause select_offset_value
*****************************************************************************/
ReindexStmt:
- REINDEX reindex_type qualified_name opt_force
+ REINDEX INDEX qualified_name opt_force
{
ReindexStmt *n = makeNode(ReindexStmt);
- n->kind = $2;
+ n->kind = REINDEX_OBJECT_INDEX;
n->relation = $3;
n->name = NULL;
$$ = (Node *)n;
}
+ | REINDEX TABLE qualified_name opt_force
+ {
+ ReindexStmt *n = makeNode(ReindexStmt);
+ n->kind = REINDEX_OBJECT_TABLE;
+ n->relation = $3;
+ n->name = NULL;
+ $$ = (Node *)n;
+ }
+ | REINDEX SCHEMA name opt_force
+ {
+ ReindexStmt *n = makeNode(ReindexStmt);
+ n->kind = REINDEX_OBJECT_SCHEMA;
+ n->name = $3;
+ n->relation = NULL;
+ $$ = (Node *)n;
+ }
| REINDEX SYSTEM_P name opt_force
{
ReindexStmt *n = makeNode(ReindexStmt);
- n->kind = OBJECT_DATABASE;
+ n->kind = REINDEX_OBJECT_SYSTEM;
n->name = $3;
n->relation = NULL;
- n->do_system = true;
- n->do_user = false;
$$ = (Node *)n;
}
| REINDEX DATABASE name opt_force
{
ReindexStmt *n = makeNode(ReindexStmt);
- n->kind = OBJECT_DATABASE;
+ n->kind = REINDEX_OBJECT_DATABASE;
n->name = $3;
n->relation = NULL;
- n->do_system = true;
- n->do_user = true;
$$ = (Node *)n;
}
;
-reindex_type:
- INDEX { $$ = OBJECT_INDEX; }
- | TABLE { $$ = OBJECT_TABLE; }
- ;
-
opt_force: FORCE { $$ = TRUE; }
| /* EMPTY */ { $$ = FALSE; }
;
PreventCommandDuringRecovery("REINDEX");
switch (stmt->kind)
{
- case OBJECT_INDEX:
+ case REINDEX_OBJECT_INDEX:
ReindexIndex(stmt->relation);
break;
- case OBJECT_TABLE:
- case OBJECT_MATVIEW:
+ case REINDEX_OBJECT_TABLE:
ReindexTable(stmt->relation);
break;
- case OBJECT_DATABASE:
+ case REINDEX_OBJECT_SCHEMA:
+ case REINDEX_OBJECT_SYSTEM:
+ case REINDEX_OBJECT_DATABASE:
/*
* This cannot run inside a user transaction block; if
* intended effect!
*/
PreventTransactionChain(isTopLevel,
- "REINDEX DATABASE");
- ReindexDatabase(stmt->name,
- stmt->do_system, stmt->do_user);
+ (stmt->kind == REINDEX_OBJECT_SCHEMA) ?
+ "REINDEX SCHEMA" : "REINDEX DATABASE");
+ ReindexObject(stmt->name, stmt->kind);
break;
default:
elog(ERROR, "unrecognized object type: %d",
else if (pg_strcasecmp(prev_wd, "REINDEX") == 0)
{
static const char *const list_REINDEX[] =
- {"TABLE", "INDEX", "SYSTEM", "DATABASE", NULL};
+ {"TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE", NULL};
COMPLETE_WITH_LIST(list_REINDEX);
}
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+ else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0 )
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
pg_strcasecmp(prev_wd, "DATABASE") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_databases);
{"password", no_argument, NULL, 'W'},
{"echo", no_argument, NULL, 'e'},
{"quiet", no_argument, NULL, 'q'},
+ {"schema", required_argument, NULL, 'S'},
{"dbname", required_argument, NULL, 'd'},
{"all", no_argument, NULL, 'a'},
{"system", no_argument, NULL, 's'},
bool quiet = false;
SimpleStringList indexes = {NULL, NULL};
SimpleStringList tables = {NULL, NULL};
+ SimpleStringList schemas = {NULL, NULL};
progname = get_progname(argv[0]);
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
handle_help_version_opts(argc, argv, "reindexdb", help);
/* process command-line options */
- while ((c = getopt_long(argc, argv, "h:p:U:wWeqd:ast:i:", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeqS:d:ast:i:", long_options, &optindex)) != -1)
{
switch (c)
{
case 'q':
quiet = true;
break;
+ case 'S':
+ simple_string_list_append(&schemas, optarg);
+ break;
case 'd':
dbname = pg_strdup(optarg);
break;
fprintf(stderr, _("%s: cannot reindex all databases and system catalogs at the same time\n"), progname);
exit(1);
}
+ if (schemas.head != NULL)
+ {
+ fprintf(stderr, _("%s: cannot reindex specific schema(s) in all databases\n"), progname);
+ exit(1);
+ }
if (tables.head != NULL)
{
fprintf(stderr, _("%s: cannot reindex specific table(s) in all databases\n"), progname);
}
else if (syscatalog)
{
+ if (schemas.head != NULL)
+ {
+ fprintf(stderr, _("%s: cannot reindex specific schema(s) and system catalogs at the same time\n"), progname);
+ exit(1);
+ }
if (tables.head != NULL)
{
fprintf(stderr, _("%s: cannot reindex specific table(s) and system catalogs at the same time\n"), progname);
dbname = get_user_name_or_exit(progname);
}
+ if (schemas.head != NULL)
+ {
+ SimpleStringListCell *cell;
+
+ for (cell = schemas.head; cell; cell = cell->next)
+ {
+ reindex_one_database(cell->val, dbname, "SCHEMA", host, port,
+ username, prompt_password, progname, echo);
+ }
+ }
+
if (indexes.head != NULL)
{
SimpleStringListCell *cell;
username, prompt_password, progname, echo);
}
}
- /* reindex database only if neither index nor table is specified */
- if (indexes.head == NULL && tables.head == NULL)
+ /* reindex database only if neither index nor table nor schema is specified */
+ if (indexes.head == NULL && tables.head == NULL && schemas.head == NULL)
reindex_one_database(dbname, dbname, "DATABASE", host, port,
username, prompt_password, progname, echo);
}
appendPQExpBuffer(&sql, " TABLE %s", name);
else if (strcmp(type, "INDEX") == 0)
appendPQExpBuffer(&sql, " INDEX %s", name);
+ else if (strcmp(type, "SCHEMA") == 0)
+ appendPQExpBuffer(&sql, " SCHEMA %s", name);
else if (strcmp(type, "DATABASE") == 0)
appendPQExpBuffer(&sql, " DATABASE %s", fmtId(name));
appendPQExpBufferStr(&sql, ";");
if (strcmp(type, "INDEX") == 0)
fprintf(stderr, _("%s: reindexing of index \"%s\" in database \"%s\" failed: %s"),
progname, name, dbname, PQerrorMessage(conn));
+ if (strcmp(type, "SCHEMA") == 0)
+ fprintf(stderr, _("%s: reindexing of schema \"%s\" in database \"%s\" failed: %s"),
+ progname, name, dbname, PQerrorMessage(conn));
else
fprintf(stderr, _("%s: reindexing of database \"%s\" failed: %s"),
progname, dbname, PQerrorMessage(conn));
printf(_(" -i, --index=INDEX recreate specific index(es) only\n"));
printf(_(" -q, --quiet don't write any messages\n"));
printf(_(" -s, --system reindex system catalogs\n"));
+ printf(_(" -S, --schema=SCHEMA recreate specific schema(s) only\n"));
printf(_(" -t, --table=TABLE reindex specific table(s) only\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" -?, --help show this help, then exit\n"));
use strict;
use warnings;
use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 17;
program_help_ok('reindexdb');
program_version_ok('reindexdb');
[ 'reindexdb', 'postgres', '-i', 'test1x' ],
qr/statement: REINDEX INDEX test1x;/,
'reindex specific index');
-
+issues_sql_like(
+ [ 'reindexdb', 'postgres', '-S', 'pg_catalog' ],
+ qr/statement: REINDEX SCHEMA pg_catalog;/,
+ 'reindex specific schema');
issues_sql_like(
[ 'reindexdb', 'postgres', '-s' ],
qr/statement: REINDEX SYSTEM postgres;/,
bool quiet);
extern Oid ReindexIndex(RangeVar *indexRelation);
extern Oid ReindexTable(RangeVar *relation);
-extern Oid ReindexDatabase(const char *databaseName,
- bool do_system, bool do_user);
+extern Oid ReindexObject(const char *databaseName, ReindexObjectType kind);
extern char *makeObjectName(const char *name1, const char *name2,
const char *label);
extern char *ChooseRelationName(const char *name1, const char *name2,
* REINDEX Statement
* ----------------------
*/
+typedef enum ReindexObjectType
+{
+ REINDEX_OBJECT_INDEX, /* index */
+ REINDEX_OBJECT_TABLE, /* table or materialized view */
+ REINDEX_OBJECT_SCHEMA, /* schema */
+ REINDEX_OBJECT_SYSTEM, /* system catalogs */
+ REINDEX_OBJECT_DATABASE /* database */
+} ReindexObjectType;
+
typedef struct ReindexStmt
{
NodeTag type;
- ObjectType kind; /* OBJECT_INDEX, OBJECT_TABLE, etc. */
+ ReindexObjectType kind; /* REINDEX_OBJECT_INDEX, REINDEX_OBJECT_TABLE, etc. */
RangeVar *relation; /* Table or index to reindex */
const char *name; /* name of database to reindex */
bool do_system; /* include system tables in database case */
Index Cond: ((thousand = 1) AND (tenthous = 1001))
(2 rows)
+--
+-- REINDEX SCHEMA
+--
+REINDEX SCHEMA schema_to_reindex; -- failure, schema does not exist
+ERROR: schema "schema_to_reindex" does not exist
+CREATE SCHEMA schema_to_reindex;
+CREATE TABLE schema_to_reindex.table1(col1 SERIAL PRIMARY KEY);
+CREATE TABLE schema_to_reindex.table2(col1 SERIAL PRIMARY KEY, col2 VARCHAR(100) NOT NULL);
+CREATE INDEX ON schema_to_reindex.table2(col2);
+REINDEX SCHEMA schema_to_reindex;
+NOTICE: table "schema_to_reindex.table1" was reindexed
+NOTICE: table "schema_to_reindex.table2" was reindexed
+BEGIN;
+REINDEX SCHEMA schema_to_reindex; -- failure, cannot run in a transaction
+ERROR: REINDEX SCHEMA cannot run inside a transaction block
+END;
+-- Failure for unauthorized user
+CREATE ROLE reindexuser login;
+SET SESSION ROLE user_reindex;
+ERROR: role "user_reindex" does not exist
+REINDEX SCHEMA schema_to_reindex;
+NOTICE: table "schema_to_reindex.table1" was reindexed
+NOTICE: table "schema_to_reindex.table2" was reindexed
+-- Clean up
+RESET ROLE;
+DROP ROLE user_reindex;
+ERROR: role "user_reindex" does not exist
+DROP SCHEMA schema_to_reindex CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table schema_to_reindex.table1
+drop cascades to table schema_to_reindex.table2
explain (costs off)
select * from tenk1 where (thousand, tenthous) in ((1,1001), (null,null));
+
+--
+-- REINDEX SCHEMA
+--
+REINDEX SCHEMA schema_to_reindex; -- failure, schema does not exist
+CREATE SCHEMA schema_to_reindex;
+CREATE TABLE schema_to_reindex.table1(col1 SERIAL PRIMARY KEY);
+CREATE TABLE schema_to_reindex.table2(col1 SERIAL PRIMARY KEY, col2 VARCHAR(100) NOT NULL);
+CREATE INDEX ON schema_to_reindex.table2(col2);
+REINDEX SCHEMA schema_to_reindex;
+BEGIN;
+REINDEX SCHEMA schema_to_reindex; -- failure, cannot run in a transaction
+END;
+
+-- Failure for unauthorized user
+CREATE ROLE reindexuser login;
+SET SESSION ROLE user_reindex;
+REINDEX SCHEMA schema_to_reindex;
+
+-- Clean up
+RESET ROLE;
+DROP ROLE user_reindex;
+DROP SCHEMA schema_to_reindex CASCADE;