Fix search_path to a safe value during maintenance operations.
authorJeff Davis <[email protected]>
Tue, 5 Mar 2024 01:31:38 +0000 (17:31 -0800)
committerJeff Davis <[email protected]>
Tue, 5 Mar 2024 01:31:38 +0000 (17:31 -0800)
While executing maintenance operations (ANALYZE, CLUSTER, REFRESH
MATERIALIZED VIEW, REINDEX, or VACUUM), set search_path to
'pg_catalog, pg_temp' to prevent inconsistent behavior.

Functions that are used for functional indexes, in index expressions,
or in materialized views and depend on a different search path must be
declared with CREATE FUNCTION ... SET search_path='...'.

This change was previously committed as 05e1737351, then reverted in
commit 2fcc7ee7af because it was too late in the cycle.

Preparation for the MAINTAIN privilege, which was previously reverted
due to search_path manipulation hazards.

Discussion: https://postgr.es/m/d4ccaf3658cb3c281ec88c851a09733cd9482f22[email protected]
Discussion: https://postgr.es/m/E1q7j7Y-000z1H-Hr%40gemulon.postgresql.org
Discussion: https://postgr.es/m/e44327179e5c9015c8dda67351c04da552066017.camel%40j-davis.com
Reviewed-by: Greg Stark, Nathan Bossart, Noah Misch
30 files changed:
contrib/amcheck/t/004_verify_nbtree_unique.pl
contrib/amcheck/verify_nbtree.c
doc/src/sgml/amcheck.sgml
doc/src/sgml/brin.sgml
doc/src/sgml/ref/analyze.sgml
doc/src/sgml/ref/cluster.sgml
doc/src/sgml/ref/create_index.sgml
doc/src/sgml/ref/refresh_materialized_view.sgml
doc/src/sgml/ref/reindex.sgml
doc/src/sgml/ref/vacuum.sgml
src/backend/access/brin/brin.c
src/backend/catalog/index.c
src/backend/catalog/namespace.c
src/backend/commands/analyze.c
src/backend/commands/cluster.c
src/backend/commands/indexcmds.c
src/backend/commands/matview.c
src/backend/commands/vacuum.c
src/bin/scripts/t/100_vacuumdb.pl
src/include/utils/guc.h
src/test/modules/test_oat_hooks/expected/alter_table.out
src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
src/test/regress/expected/matview.out
src/test/regress/expected/namespace.out
src/test/regress/expected/privileges.out
src/test/regress/expected/vacuum.out
src/test/regress/sql/matview.sql
src/test/regress/sql/namespace.sql
src/test/regress/sql/privileges.sql
src/test/regress/sql/vacuum.sql

index 3f474a158a0cc6661aadc38acf259f8a4500e603..4b704e6815106a659bee940c94188769781c3f53 100644 (file)
@@ -20,8 +20,11 @@ $node->safe_psql(
    'postgres', q(
    CREATE EXTENSION amcheck;
 
+   CREATE SCHEMA test_amcheck;
+   SET search_path = test_amcheck;
+
    CREATE FUNCTION ok_cmp (int4, int4)
-   RETURNS int LANGUAGE sql AS
+   RETURNS int LANGUAGE sql SET search_path = test_amcheck AS
    $$
        SELECT
            CASE WHEN $1 < $2 THEN -1
@@ -34,7 +37,7 @@ $node->safe_psql(
    --- Check 1: uniqueness violation.
    ---
    CREATE FUNCTION ok_cmp1 (int4, int4)
-   RETURNS int LANGUAGE sql AS
+   RETURNS int LANGUAGE sql SET search_path = test_amcheck AS
    $$
        SELECT ok_cmp($1, $2);
    $$;
@@ -43,7 +46,7 @@ $node->safe_psql(
    --- Make values 768 and 769 look equal.
    ---
    CREATE FUNCTION bad_cmp1 (int4, int4)
-   RETURNS int LANGUAGE sql AS
+   RETURNS int LANGUAGE sql SET search_path = test_amcheck AS
    $$
        SELECT
            CASE WHEN ($1 = 768 AND $2 = 769) OR
@@ -56,13 +59,13 @@ $node->safe_psql(
    --- Check 2: uniqueness violation without deduplication.
    ---
    CREATE FUNCTION ok_cmp2 (int4, int4)
-   RETURNS int LANGUAGE sql AS
+   RETURNS int LANGUAGE sql SET search_path = test_amcheck AS
    $$
        SELECT ok_cmp($1, $2);
    $$;
 
    CREATE FUNCTION bad_cmp2 (int4, int4)
-   RETURNS int LANGUAGE sql AS
+   RETURNS int LANGUAGE sql SET search_path = test_amcheck AS
    $$
        SELECT
            CASE WHEN $1 = $2 AND $1 = 400 THEN -1
@@ -74,13 +77,13 @@ $node->safe_psql(
    --- Check 3: uniqueness violation with deduplication.
    ---
    CREATE FUNCTION ok_cmp3 (int4, int4)
-   RETURNS int LANGUAGE sql AS
+   RETURNS int LANGUAGE sql SET search_path = test_amcheck AS
    $$
        SELECT ok_cmp($1, $2);
    $$;
 
    CREATE FUNCTION bad_cmp3 (int4, int4)
-   RETURNS int LANGUAGE sql AS
+   RETURNS int LANGUAGE sql SET search_path = test_amcheck AS
    $$
        SELECT bad_cmp2($1, $2);
    $$;
@@ -142,7 +145,7 @@ my ($result, $stdout, $stderr);
 # We have not yet broken the index, so we should get no corruption
 $result = $node->safe_psql(
    'postgres', q(
-   SELECT bt_index_check('bttest_unique_idx1', true, true);
+   SELECT bt_index_check('test_amcheck.bttest_unique_idx1', true, true);
 ));
 is($result, '', 'run amcheck on non-broken bttest_unique_idx1');
 
@@ -150,6 +153,7 @@ is($result, '', 'run amcheck on non-broken bttest_unique_idx1');
 # values to be equal.
 $node->safe_psql(
    'postgres', q(
+   SET search_path = test_amcheck;
    UPDATE pg_catalog.pg_amproc SET
           amproc = 'bad_cmp1'::regproc
    WHERE amproc = 'ok_cmp1'::regproc;
@@ -157,7 +161,7 @@ $node->safe_psql(
 
 ($result, $stdout, $stderr) = $node->psql(
    'postgres', q(
-   SELECT bt_index_check('bttest_unique_idx1', true, true);
+   SELECT bt_index_check('test_amcheck.bttest_unique_idx1', true, true);
 ));
 ok( $stderr =~ /index uniqueness is violated for index "bttest_unique_idx1"/,
    'detected uniqueness violation for index "bttest_unique_idx1"');
@@ -175,13 +179,14 @@ ok( $stderr =~ /index uniqueness is violated for index "bttest_unique_idx1"/,
 # but no uniqueness violation.
 ($result, $stdout, $stderr) = $node->psql(
    'postgres', q(
-   SELECT bt_index_check('bttest_unique_idx2', true, true);
+   SELECT bt_index_check('test_amcheck.bttest_unique_idx2', true, true);
 ));
 ok( $stderr =~ /item order invariant violated for index "bttest_unique_idx2"/,
    'detected item order invariant violation for index "bttest_unique_idx2"');
 
 $node->safe_psql(
    'postgres', q(
+   SET search_path = test_amcheck;
    UPDATE pg_catalog.pg_amproc SET
           amproc = 'ok_cmp2'::regproc
    WHERE amproc = 'bad_cmp2'::regproc;
@@ -189,7 +194,7 @@ $node->safe_psql(
 
 ($result, $stdout, $stderr) = $node->psql(
    'postgres', q(
-   SELECT bt_index_check('bttest_unique_idx2', true, true);
+   SELECT bt_index_check('test_amcheck.bttest_unique_idx2', true, true);
 ));
 ok( $stderr =~ /index uniqueness is violated for index "bttest_unique_idx2"/,
    'detected uniqueness violation for index "bttest_unique_idx2"');
@@ -206,7 +211,7 @@ ok( $stderr =~ /index uniqueness is violated for index "bttest_unique_idx2"/,
 # but no uniqueness violation.
 ($result, $stdout, $stderr) = $node->psql(
    'postgres', q(
-   SELECT bt_index_check('bttest_unique_idx3', true, true);
+   SELECT bt_index_check('test_amcheck.bttest_unique_idx3', true, true);
 ));
 ok( $stderr =~ /item order invariant violated for index "bttest_unique_idx3"/,
    'detected item order invariant violation for index "bttest_unique_idx3"');
@@ -215,6 +220,7 @@ ok( $stderr =~ /item order invariant violated for index "bttest_unique_idx3"/,
 # with different visibility.
 $node->safe_psql(
    'postgres', q(
+   SET search_path = test_amcheck;
    DELETE FROM bttest_unique3 WHERE 380 <= i AND i <= 420;
    INSERT INTO bttest_unique3 (SELECT * FROM generate_series(380, 420));
    INSERT INTO bttest_unique3 VALUES (400);
@@ -228,6 +234,7 @@ $node->safe_psql(
 
 $node->safe_psql(
    'postgres', q(
+   SET search_path = test_amcheck;
    UPDATE pg_catalog.pg_amproc SET
           amproc = 'ok_cmp3'::regproc
    WHERE amproc = 'bad_cmp3'::regproc;
@@ -235,7 +242,7 @@ $node->safe_psql(
 
 ($result, $stdout, $stderr) = $node->psql(
    'postgres', q(
-   SELECT bt_index_check('bttest_unique_idx3', true, true);
+   SELECT bt_index_check('test_amcheck.bttest_unique_idx3', true, true);
 ));
 ok( $stderr =~ /index uniqueness is violated for index "bttest_unique_idx3"/,
    'detected uniqueness violation for index "bttest_unique_idx3"');
index 91caa53dd8ba4f1329263f5abb4234a2a7325c80..bff8c61262c88a52e4eb45fa75dd8bb3d67d9629 100644 (file)
@@ -313,6 +313,8 @@ bt_index_check_internal(Oid indrelid, bool parentcheck, bool heapallindexed,
        SetUserIdAndSecContext(heaprel->rd_rel->relowner,
                               save_sec_context | SECURITY_RESTRICTED_OPERATION);
        save_nestlevel = NewGUCNestLevel();
+       SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                       PGC_S_SESSION);
    }
    else
    {
index 780fd05a73b771efe32d82e3ab13fe049ce04723..3af065615bc0dd8d21f29b9ddb822c172446a978 100644 (file)
@@ -24,6 +24,9 @@
   to hold, we can expect binary searches on the affected page to
   incorrectly guide index scans, resulting in wrong answers to SQL
   queries.  If the structure appears to be valid, no error is raised.
+  While these checking functions are run, the <xref
+  linkend="guc-search-path"/> is temporarily changed to <literal>pg_catalog,
+  pg_temp</literal>.
  </para>
  <para>
   Verification is performed using the same procedures as those used by
index 9c5ffcddf848950ceebcd2700b8786effee21dc8..d898cc4720c00c58e797afb74ae6be32e3b9902e 100644 (file)
@@ -95,7 +95,9 @@
   </para>
 
   <para>
-   Lastly, the following functions can be used:
+   Lastly, the following functions can be used (while these functions run,
+   <xref linkend="guc-search-path"/> is temporarily changed to
+   <literal>pg_catalog, pg_temp</literal>):
    <simplelist>
     <member>
      <function>brin_summarize_new_values(regclass)</function>
index 6ed8e7241b4b2ee431e4d785ef06495a7326ec93..bad121a1f19b09c958eecc241af61d034c0bbb1b 100644 (file)
@@ -205,6 +205,12 @@ ANALYZE [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <r
    (This will not be sufficient if there is heavy update activity.)
   </para>
 
+  <para>
+   While <command>ANALYZE</command> is running, the <xref
+   linkend="guc-search-path"/> is temporarily changed to <literal>pg_catalog,
+   pg_temp</literal>.
+  </para>
+
   <para>
    <command>ANALYZE</command>
    requires only a read lock on the target table, so it can run in
index 557a94cea78ad15b6055c34e7f070056b6b108d3..24340e7b9bf6df1666706ed78ef4dda19d3033ca 100644 (file)
@@ -153,6 +153,12 @@ CLUSTER [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <r
     information.
    </para>
 
+   <para>
+    While <command>CLUSTER</command> is running, the <xref
+    linkend="guc-search-path"/> is temporarily changed to <literal>pg_catalog,
+    pg_temp</literal>.
+   </para>
+
    <para>
     When an index scan is used, a temporary copy of the table is created that
     contains the table data in the index order.  Temporary copies of each
index 40986aa502ffd11df4eab97e7688dcc7508aeb69..621bc0e253ceb7b051a8324b8eac5b60cf35564f 100644 (file)
@@ -789,6 +789,12 @@ Indexes:
    the table to generate statistics for these indexes.
   </para>
 
+  <para>
+   While <command>CREATE INDEX</command> is running, the <xref
+   linkend="guc-search-path"/> is temporarily changed to <literal>pg_catalog,
+   pg_temp</literal>.
+  </para>
+
   <para>
    For most index methods, the speed of creating an index is
    dependent on the setting of <xref linkend="guc-maintenance-work-mem"/>.
index 7a019162c38859245e4a5eccf902febe7dd82cce..e4e5145058f9fea23b7a0f7a32ea2f9a1e99cc37 100644 (file)
@@ -98,6 +98,12 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <replaceable class="parameter">name</
    will be ordered that way; but <command>REFRESH MATERIALIZED
    VIEW</command> does not guarantee to preserve that ordering.
   </para>
+
+  <para>
+   While <command>REFRESH MATERIALIZED VIEW</command> is running, the <xref
+   linkend="guc-search-path"/> is temporarily changed to <literal>pg_catalog,
+   pg_temp</literal>.
+  </para>
  </refsect1>
 
  <refsect1>
index 21e2e91d896c479c4614cf34015e46f018a559c2..b214861742be0a1bc96a13803b07e0be16f8aa65 100644 (file)
@@ -291,6 +291,12 @@ REINDEX [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] { DA
    into expensive sequential scans.
   </para>
 
+  <para>
+   While <command>REINDEX</command> is running, the <xref
+   linkend="guc-search-path"/> is temporarily changed to <literal>pg_catalog,
+   pg_temp</literal>.
+  </para>
+
   <para>
    Reindexing a single index or table requires being the owner of that
    index or table.  Reindexing a schema or database requires being the
index a87c2e320e2f9830405fd365f2d5188f70e30418..df0dd71a37081d35ac8949a9e7d6b2d49bf28a94 100644 (file)
@@ -443,6 +443,12 @@ VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <re
     does not have permission to vacuum.
    </para>
 
+   <para>
+    While <command>VACUUM</command> is running, the <xref
+    linkend="guc-search-path"/> is temporarily changed to <literal>pg_catalog,
+    pg_temp</literal>.
+   </para>
+
    <para>
     <command>VACUUM</command> cannot be executed inside a transaction block.
    </para>
index 0574d81d414a644bb340501bacacfe91eda5b38c..9ea6f37f2bee1366f0554a9a2ad555b71dd9dd31 100644 (file)
@@ -1412,6 +1412,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
        SetUserIdAndSecContext(heapRel->rd_rel->relowner,
                               save_sec_context | SECURITY_RESTRICTED_OPERATION);
        save_nestlevel = NewGUCNestLevel();
+       SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                       PGC_S_SESSION);
    }
    else
    {
index dfce1aae45b18dc3e6b1001ae07e66396f1c6d42..1813b73a4fabd8ea2d7b3b8f43da50455bf7c636 100644 (file)
@@ -1464,6 +1464,8 @@ index_concurrently_build(Oid heapRelationId,
    SetUserIdAndSecContext(heapRel->rd_rel->relowner,
                           save_sec_context | SECURITY_RESTRICTED_OPERATION);
    save_nestlevel = NewGUCNestLevel();
+   SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                   PGC_S_SESSION);
 
    indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -3016,6 +3018,9 @@ index_build(Relation heapRelation,
    SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
                           save_sec_context | SECURITY_RESTRICTED_OPERATION);
    save_nestlevel = NewGUCNestLevel();
+   if (!IsBootstrapProcessingMode())
+       SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                       PGC_S_SESSION);
 
    /* Set up initial progress report status */
    {
@@ -3351,6 +3356,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
    SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
                           save_sec_context | SECURITY_RESTRICTED_OPERATION);
    save_nestlevel = NewGUCNestLevel();
+   SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                   PGC_S_SESSION);
 
    indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3612,6 +3619,8 @@ reindex_index(const ReindexStmt *stmt, Oid indexId,
    SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
                           save_sec_context | SECURITY_RESTRICTED_OPERATION);
    save_nestlevel = NewGUCNestLevel();
+   SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                   PGC_S_SESSION);
 
    if (progress)
    {
index e00380156f06b1020f6c0bed112c4db52473f341..4548a917234d09ab7a60b8b0f78c77f6da4ad610 100644 (file)
@@ -4697,6 +4697,9 @@ check_search_path(char **newval, void **extra, GucSource source)
 void
 assign_search_path(const char *newval, void *extra)
 {
+   /* don't access search_path during bootstrap */
+   Assert(!IsBootstrapProcessingMode());
+
    /*
     * We mark the path as needing recomputation, but don't do anything until
     * it's needed.  This avoids trying to do database access during GUC
index 4aee1098cf63d0ebe0785c3e8d20d578e33c10ba..b054ba1815536287afdcd9d9b8459f1d61153ce9 100644 (file)
@@ -339,6 +339,8 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
    SetUserIdAndSecContext(onerel->rd_rel->relowner,
                           save_sec_context | SECURITY_RESTRICTED_OPERATION);
    save_nestlevel = NewGUCNestLevel();
+   SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                   PGC_S_SESSION);
 
    /* measure elapsed time iff autovacuum logging requires it */
    if (AmAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
index 661fdef9b3310c5c6c4340bdb19fda4cca1506e3..2b69dc0558f0ff43023b8360298826e1037ec2cc 100644 (file)
@@ -350,6 +350,8 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
    SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
                           save_sec_context | SECURITY_RESTRICTED_OPERATION);
    save_nestlevel = NewGUCNestLevel();
+   SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                   PGC_S_SESSION);
 
    /*
     * Since we may open a new transaction for each relation, we have to check
index cde1ee7432dc6ac51015e085f5e5e943bc0145ef..d0813278eac10bf156016fd503deb43f4068b5a0 100644 (file)
@@ -585,6 +585,10 @@ DefineIndex(Oid tableId,
 
    root_save_nestlevel = NewGUCNestLevel();
 
+   if (!IsBootstrapProcessingMode())
+       SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                       PGC_S_SESSION);
+
    /*
     * Some callers need us to run with an empty default_tablespace; this is a
     * necessary hack to be able to reproduce catalog state accurately when
@@ -1340,6 +1344,8 @@ DefineIndex(Oid tableId,
                SetUserIdAndSecContext(childrel->rd_rel->relowner,
                                       child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
                child_save_nestlevel = NewGUCNestLevel();
+               SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                               PGC_S_SESSION);
 
                /*
                 * Don't try to create indexes on foreign tables, though. Skip
@@ -3881,6 +3887,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
        SetUserIdAndSecContext(heapRel->rd_rel->relowner,
                               save_sec_context | SECURITY_RESTRICTED_OPERATION);
        save_nestlevel = NewGUCNestLevel();
+       SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                       PGC_S_SESSION);
 
        /* determine safety of this index for set_indexsafe_procflags */
        idx->safe = (indexRel->rd_indexprs == NIL &&
index 2f1d897cf1dc36909ec70b10a5f1ea18acf0d18f..1887cd60ea6b8350e0752f0fbcbb340a01beb6a3 100644 (file)
@@ -173,6 +173,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
    SetUserIdAndSecContext(relowner,
                           save_sec_context | SECURITY_RESTRICTED_OPERATION);
    save_nestlevel = NewGUCNestLevel();
+   SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                   PGC_S_SESSION);
 
    /* Make sure it is a materialized view. */
    if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
index bd2309967e01992ea4d4b05da290bd54ebbae674..02d0dc4aba769397d27f18563c8fba7fc3c329a7 100644 (file)
@@ -2166,6 +2166,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
    SetUserIdAndSecContext(rel->rd_rel->relowner,
                           save_sec_context | SECURITY_RESTRICTED_OPERATION);
    save_nestlevel = NewGUCNestLevel();
+   SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+                   PGC_S_SESSION);
 
    /*
     * If PROCESS_MAIN is set (the default), it's time to vacuum the main
index 4ac021534b712a6782f45fd1f3cce66e178cd7d9..0601fde205f9ab3ba2083fa1f87dfd720a87319f 100644 (file)
@@ -109,7 +109,6 @@ $node->safe_psql(
   CREATE FUNCTION f1(int) RETURNS int LANGUAGE SQL AS 'SELECT f0($1)';
   CREATE TABLE funcidx (x int);
   INSERT INTO funcidx VALUES (0),(1),(2),(3);
-  CREATE INDEX i0 ON funcidx ((f1(x)));
   CREATE SCHEMA "Foo";
   CREATE TABLE "Foo".bar(id int);
   CREATE SCHEMA "Bar";
@@ -117,9 +116,6 @@ $node->safe_psql(
 |);
 $node->command_ok([qw|vacuumdb -Z --table="need""q(uot"(")x") postgres|],
    'column list');
-$node->command_fails(
-   [qw|vacuumdb -Zt funcidx postgres|],
-   'unqualified name via functional index');
 
 $node->command_fails(
    [ 'vacuumdb', '--analyze', '--table', 'vactable(c)', 'postgres' ],
index 471d53da8f0df84efa8754591c98073da3b4eb58..391d8d02120576f660ba857c2282b1309ca1b094 100644 (file)
@@ -203,6 +203,12 @@ typedef enum
 
 #define GUC_QUALIFIER_SEPARATOR '.'
 
+/*
+ * Safe search path when executing code as the table owner, such as during
+ * maintenance operations.
+ */
+#define GUC_SAFE_SEARCH_PATH "pg_catalog, pg_temp"
+
 /*
  * Bit values in "flags" of a GUC variable.  Note that these don't appear
  * on disk, so we can reassign their values freely.
index 19adb28ffb6cdf2452992e7872af853972d0ea36..8cbacca2c9bdebcbda4ee19e19218be64ac7b9a5 100644 (file)
@@ -62,6 +62,8 @@ BEGIN
   END IF;
 END; $$;
 NOTICE:  in process utility: superuser attempting CREATE FUNCTION
+NOTICE:  in object access: superuser attempting namespace search (subId=0x0) [no report on violation, allowed]
+NOTICE:  in object access: superuser finished namespace search (subId=0x0) [no report on violation, allowed]
 NOTICE:  in object access: superuser attempting create (subId=0x0) [explicit]
 NOTICE:  in object access: superuser finished create (subId=0x0) [explicit]
 NOTICE:  in process utility: superuser finished CREATE FUNCTION
index f80373aecc03351dd127e43c15bbfe1f7e0ad9c5..effdc4914583a0c81da642464d6f78d99fed992f 100644 (file)
@@ -89,11 +89,15 @@ NOTICE:  in object access: superuser finished create (subId=0x0) [internal]
 NOTICE:  in process utility: superuser finished CREATE TABLE
 CREATE INDEX regress_test_table_t_idx ON regress_test_table (t);
 NOTICE:  in process utility: superuser attempting CREATE INDEX
+NOTICE:  in object access: superuser attempting namespace search (subId=0x0) [no report on violation, allowed]
+NOTICE:  in object access: superuser finished namespace search (subId=0x0) [no report on violation, allowed]
 NOTICE:  in object access: superuser attempting create (subId=0x0) [explicit]
 NOTICE:  in object access: superuser finished create (subId=0x0) [explicit]
 NOTICE:  in process utility: superuser finished CREATE INDEX
 GRANT SELECT ON Table regress_test_table TO public;
 NOTICE:  in process utility: superuser attempting GRANT
+NOTICE:  in object access: superuser attempting namespace search (subId=0x0) [no report on violation, allowed]
+NOTICE:  in object access: superuser finished namespace search (subId=0x0) [no report on violation, allowed]
 NOTICE:  in process utility: superuser finished GRANT
 CREATE FUNCTION regress_test_func (t text) RETURNS text AS $$
    SELECT $1;
index 67a50bde3d1a92257059e1c51ad7693820a99741..038ab735176ea0adb7a8a93656dae294a406be65 100644 (file)
@@ -574,10 +574,11 @@ DROP OWNED BY regress_user_mvtest CASCADE;
 DROP ROLE regress_user_mvtest;
 -- Concurrent refresh requires a unique index on the materialized
 -- view. Test what happens if it's dropped during the refresh.
+SET search_path = mvtest_mvschema, public;
 CREATE OR REPLACE FUNCTION mvtest_drop_the_index()
   RETURNS bool AS $$
 BEGIN
-  EXECUTE 'DROP INDEX IF EXISTS mvtest_drop_idx';
+  EXECUTE 'DROP INDEX IF EXISTS mvtest_mvschema.mvtest_drop_idx';
   RETURN true;
 END;
 $$ LANGUAGE plpgsql;
@@ -588,6 +589,7 @@ CREATE UNIQUE INDEX mvtest_drop_idx ON drop_idx_matview (i);
 REFRESH MATERIALIZED VIEW CONCURRENTLY drop_idx_matview;
 ERROR:  could not find suitable unique index on materialized view
 DROP MATERIALIZED VIEW drop_idx_matview; -- clean up
+RESET search_path;
 -- make sure that create WITH NO DATA works via SPI
 BEGIN;
 CREATE FUNCTION mvtest_func()
index a62fd8ded015e6ee56784e4341e5ac1a4ed5621f..7d36e9cc0c4b2d09678e28e3ea491cbc293c4c42 100644 (file)
@@ -114,3 +114,47 @@ SELECT COUNT(*) FROM pg_class WHERE relnamespace =
      0
 (1 row)
 
+--
+-- Verify that search_path is set to a safe value during maintenance
+-- commands.
+--
+CREATE SCHEMA test_maint_search_path;
+SET search_path = test_maint_search_path;
+CREATE FUNCTION fn(INT) RETURNS INT IMMUTABLE LANGUAGE plpgsql AS $$
+  BEGIN
+    RAISE NOTICE 'current search_path: %', current_setting('search_path');
+    RETURN $1;
+  END;
+$$;
+CREATE TABLE test_maint(i INT);
+INSERT INTO test_maint VALUES (1), (2);
+CREATE MATERIALIZED VIEW test_maint_mv AS SELECT fn(i) FROM test_maint;
+NOTICE:  current search_path: test_maint_search_path
+NOTICE:  current search_path: test_maint_search_path
+-- the following commands should see search_path as pg_catalog, pg_temp
+CREATE INDEX test_maint_idx ON test_maint_search_path.test_maint (fn(i));
+NOTICE:  current search_path: pg_catalog, pg_temp
+NOTICE:  current search_path: pg_catalog, pg_temp
+REINDEX TABLE test_maint_search_path.test_maint;
+NOTICE:  current search_path: pg_catalog, pg_temp
+NOTICE:  current search_path: pg_catalog, pg_temp
+ANALYZE test_maint_search_path.test_maint;
+NOTICE:  current search_path: pg_catalog, pg_temp
+NOTICE:  current search_path: pg_catalog, pg_temp
+VACUUM FULL test_maint_search_path.test_maint;
+NOTICE:  current search_path: pg_catalog, pg_temp
+NOTICE:  current search_path: pg_catalog, pg_temp
+CLUSTER test_maint_search_path.test_maint USING test_maint_idx;
+NOTICE:  current search_path: pg_catalog, pg_temp
+NOTICE:  current search_path: pg_catalog, pg_temp
+NOTICE:  current search_path: pg_catalog, pg_temp
+NOTICE:  current search_path: pg_catalog, pg_temp
+REFRESH MATERIALIZED VIEW test_maint_search_path.test_maint_mv;
+NOTICE:  current search_path: pg_catalog, pg_temp
+NOTICE:  current search_path: pg_catalog, pg_temp
+RESET search_path;
+DROP SCHEMA test_maint_search_path CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to function test_maint_search_path.fn(integer)
+drop cascades to table test_maint_search_path.test_maint
+drop cascades to materialized view test_maint_search_path.test_maint_mv
index fbb0489a4ffaf918047f4edc5770bdbe7b41f13d..5ae5757bdeef57f16335707a1ebbb17bd8a7c8a4 100644 (file)
@@ -1769,7 +1769,7 @@ SET SESSION AUTHORIZATION regress_sro_user;
 CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS
    'GRANT regress_priv_group2 TO regress_sro_user';
 CREATE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS
-   'DECLARE c CURSOR WITH HOLD FOR SELECT unwanted_grant(); SELECT true';
+   'DECLARE c CURSOR WITH HOLD FOR SELECT public.unwanted_grant(); SELECT true';
 -- REFRESH of this MV will queue a GRANT at end of transaction
 CREATE MATERIALIZED VIEW sro_mv AS SELECT mv_action() WITH NO DATA;
 REFRESH MATERIALIZED VIEW sro_mv;
@@ -1783,12 +1783,12 @@ SET SESSION AUTHORIZATION regress_sro_user;
 -- INSERT to this table will queue a GRANT at end of transaction
 CREATE TABLE sro_trojan_table ();
 CREATE FUNCTION sro_trojan() RETURNS trigger LANGUAGE plpgsql AS
-   'BEGIN PERFORM unwanted_grant(); RETURN NULL; END';
+   'BEGIN PERFORM public.unwanted_grant(); RETURN NULL; END';
 CREATE CONSTRAINT TRIGGER t AFTER INSERT ON sro_trojan_table
     INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE sro_trojan();
 -- Now, REFRESH will issue such an INSERT, queueing the GRANT
 CREATE OR REPLACE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS
-   'INSERT INTO sro_trojan_table DEFAULT VALUES; SELECT true';
+   'INSERT INTO public.sro_trojan_table DEFAULT VALUES; SELECT true';
 REFRESH MATERIALIZED VIEW sro_mv;
 ERROR:  cannot fire deferred trigger within security-restricted operation
 CONTEXT:  SQL function "mv_action" statement 1
@@ -1800,15 +1800,15 @@ BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
 ERROR:  permission denied to grant role "regress_priv_group2"
 DETAIL:  Only roles with the ADMIN option on role "regress_priv_group2" may grant this role.
 CONTEXT:  SQL function "unwanted_grant" statement 1
-SQL statement "SELECT unwanted_grant()"
-PL/pgSQL function sro_trojan() line 1 at PERFORM
+SQL statement "SELECT public.unwanted_grant()"
+PL/pgSQL function public.sro_trojan() line 1 at PERFORM
 SQL function "mv_action" statement 1
 -- REFRESH MATERIALIZED VIEW CONCURRENTLY use of eval_const_expressions()
 SET SESSION AUTHORIZATION regress_sro_user;
 CREATE FUNCTION unwanted_grant_nofail(int) RETURNS int
    IMMUTABLE LANGUAGE plpgsql AS $$
 BEGIN
-   PERFORM unwanted_grant();
+   PERFORM public.unwanted_grant();
    RAISE WARNING 'owned';
    RETURN 1;
 EXCEPTION WHEN OTHERS THEN
index 4def90b80575020a313c86ad4ad3d385afcc31a5..330fcd884c5a1216ca331c4941f9f6895bc9f866 100644 (file)
@@ -64,7 +64,7 @@ CLUSTER vaccluster;
 CREATE FUNCTION do_analyze() RETURNS VOID VOLATILE LANGUAGE SQL
    AS 'ANALYZE pg_am';
 CREATE FUNCTION wrap_do_analyze(c INT) RETURNS INT IMMUTABLE LANGUAGE SQL
-   AS 'SELECT $1 FROM do_analyze()';
+   AS 'SELECT $1 FROM public.do_analyze()';
 CREATE INDEX ON vaccluster(wrap_do_analyze(i));
 INSERT INTO vaccluster VALUES (1), (2);
 ANALYZE vaccluster;
index 235123de1e79ab2ddb0855311d1976873650b145..b74ee305e0f6f1df264b1c7e7265879022dce444 100644 (file)
@@ -233,10 +233,11 @@ DROP ROLE regress_user_mvtest;
 
 -- Concurrent refresh requires a unique index on the materialized
 -- view. Test what happens if it's dropped during the refresh.
+SET search_path = mvtest_mvschema, public;
 CREATE OR REPLACE FUNCTION mvtest_drop_the_index()
   RETURNS bool AS $$
 BEGIN
-  EXECUTE 'DROP INDEX IF EXISTS mvtest_drop_idx';
+  EXECUTE 'DROP INDEX IF EXISTS mvtest_mvschema.mvtest_drop_idx';
   RETURN true;
 END;
 $$ LANGUAGE plpgsql;
@@ -247,6 +248,7 @@ CREATE MATERIALIZED VIEW drop_idx_matview AS
 CREATE UNIQUE INDEX mvtest_drop_idx ON drop_idx_matview (i);
 REFRESH MATERIALIZED VIEW CONCURRENTLY drop_idx_matview;
 DROP MATERIALIZED VIEW drop_idx_matview; -- clean up
+RESET search_path;
 
 -- make sure that create WITH NO DATA works via SPI
 BEGIN;
index 3474f5ecf4215068fd89ec52f9ee8b95ddff36bb..306cdc2d8c6a1cd3d4d7d83686a9046caab730cf 100644 (file)
@@ -66,3 +66,35 @@ DROP SCHEMA test_ns_schema_renamed CASCADE;
 -- verify that the objects were dropped
 SELECT COUNT(*) FROM pg_class WHERE relnamespace =
     (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_renamed');
+
+--
+-- Verify that search_path is set to a safe value during maintenance
+-- commands.
+--
+
+CREATE SCHEMA test_maint_search_path;
+SET search_path = test_maint_search_path;
+
+CREATE FUNCTION fn(INT) RETURNS INT IMMUTABLE LANGUAGE plpgsql AS $$
+  BEGIN
+    RAISE NOTICE 'current search_path: %', current_setting('search_path');
+    RETURN $1;
+  END;
+$$;
+
+CREATE TABLE test_maint(i INT);
+INSERT INTO test_maint VALUES (1), (2);
+CREATE MATERIALIZED VIEW test_maint_mv AS SELECT fn(i) FROM test_maint;
+
+-- the following commands should see search_path as pg_catalog, pg_temp
+
+CREATE INDEX test_maint_idx ON test_maint_search_path.test_maint (fn(i));
+REINDEX TABLE test_maint_search_path.test_maint;
+ANALYZE test_maint_search_path.test_maint;
+VACUUM FULL test_maint_search_path.test_maint;
+CLUSTER test_maint_search_path.test_maint USING test_maint_idx;
+REFRESH MATERIALIZED VIEW test_maint_search_path.test_maint_mv;
+
+RESET search_path;
+
+DROP SCHEMA test_maint_search_path CASCADE;
index 3f68cafcd1ea1607b11b8e4f7bb2715b11649ffd..2ef15a9d8c5ff28218f65a117e7f99f756645d0d 100644 (file)
@@ -1177,7 +1177,7 @@ SET SESSION AUTHORIZATION regress_sro_user;
 CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS
    'GRANT regress_priv_group2 TO regress_sro_user';
 CREATE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS
-   'DECLARE c CURSOR WITH HOLD FOR SELECT unwanted_grant(); SELECT true';
+   'DECLARE c CURSOR WITH HOLD FOR SELECT public.unwanted_grant(); SELECT true';
 -- REFRESH of this MV will queue a GRANT at end of transaction
 CREATE MATERIALIZED VIEW sro_mv AS SELECT mv_action() WITH NO DATA;
 REFRESH MATERIALIZED VIEW sro_mv;
@@ -1188,12 +1188,12 @@ SET SESSION AUTHORIZATION regress_sro_user;
 -- INSERT to this table will queue a GRANT at end of transaction
 CREATE TABLE sro_trojan_table ();
 CREATE FUNCTION sro_trojan() RETURNS trigger LANGUAGE plpgsql AS
-   'BEGIN PERFORM unwanted_grant(); RETURN NULL; END';
+   'BEGIN PERFORM public.unwanted_grant(); RETURN NULL; END';
 CREATE CONSTRAINT TRIGGER t AFTER INSERT ON sro_trojan_table
     INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE sro_trojan();
 -- Now, REFRESH will issue such an INSERT, queueing the GRANT
 CREATE OR REPLACE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS
-   'INSERT INTO sro_trojan_table DEFAULT VALUES; SELECT true';
+   'INSERT INTO public.sro_trojan_table DEFAULT VALUES; SELECT true';
 REFRESH MATERIALIZED VIEW sro_mv;
 \c -
 REFRESH MATERIALIZED VIEW sro_mv;
@@ -1204,7 +1204,7 @@ SET SESSION AUTHORIZATION regress_sro_user;
 CREATE FUNCTION unwanted_grant_nofail(int) RETURNS int
    IMMUTABLE LANGUAGE plpgsql AS $$
 BEGIN
-   PERFORM unwanted_grant();
+   PERFORM public.unwanted_grant();
    RAISE WARNING 'owned';
    RETURN 1;
 EXCEPTION WHEN OTHERS THEN
index 51d7b1fecc71044fd66a27a19f99ebfe8a9125df..0b63ef8dc664cac863486d2f484f630e5c5a88f3 100644 (file)
@@ -49,7 +49,7 @@ CLUSTER vaccluster;
 CREATE FUNCTION do_analyze() RETURNS VOID VOLATILE LANGUAGE SQL
    AS 'ANALYZE pg_am';
 CREATE FUNCTION wrap_do_analyze(c INT) RETURNS INT IMMUTABLE LANGUAGE SQL
-   AS 'SELECT $1 FROM do_analyze()';
+   AS 'SELECT $1 FROM public.do_analyze()';
 CREATE INDEX ON vaccluster(wrap_do_analyze(i));
 INSERT INTO vaccluster VALUES (1), (2);
 ANALYZE vaccluster;