Fix multiple query cache vulnerabilities (CVE-2024-45624).
authorBo Peng <pengbo@sraoss.co.jp>
Sat, 7 Sep 2024 12:30:07 +0000 (21:30 +0900)
committerBo Peng <pengbo@sraoss.co.jp>
Sat, 7 Sep 2024 12:30:07 +0000 (21:30 +0900)
When the query cache feature is enabled, it was possible that a user
can read rows from tables that should not be visible for the user
through query cache.

- If query cache is created for a row security enabled table for user
  A, and then other user B accesses the table via SET ROLE or SET
  SESSION_AUTHORIZATION in the same session, it was possible for the
  user B to retrieve rows which should not be visible from the user B.

- If query cache is created for a table for user A, and then other
  user B accesses the table via SET ROLE or SET SESSION_AUTHORIZATION
  in the same session, it was possible for the user B to retrieve rows
  which should not be visible from the user B.

- If query cache is created for a table for a user, and then the
  access right of the table is revoked from the user by REVOKE
  command, still it was possible for the user to to retrieve the rows
  through the query cache.

Besides the vulnerabilities, there were multiple bugs with the query
cache feature.

- If query cache is created for a row security enabled table for a
  user, and then ALTER DATABASE BYPASSRLS or ALTER ROLE BYPASSRLS
  disable the row security of the table, subsequent SELECT still
  returns the same rows as before through the query cache.

- If query cache is created for a table for a user, and then ALTER
  TABLE SET SCHEMA changes the search path to not allow to access the
  table, subsequent SELECT still returns the rows as before through
  the query cache.

To fix above, following changes are made:

- Do not allow to create query cache/use query cache for row security
  enabled tables (even if the table is included in
  cache_safe_memqcache_table_list).

- Do not allow to create query cache/use query cache if SET ROLE/SET
  AUTHORIZATION is executed in the session (query cache invalidation
  is performed when a table is modified as usual).

- Remove entire query cache if REVOKE/ALTER DATABASE/ALTER TABLE/ALTER
  ROLE is executed. If the command is executed in an explicit
  transaction, do not create query cache/use query cache until the
  transaction gets committed (query cache invalidation is performed
  when a table is modified as usual). If the transaction is aborted,
  do not remove query cache.

Patch is created by Tatsuo Ishii.

Backpatch-through: v4.1

30 files changed:
doc.ja/src/sgml/memcache.sgml
doc/src/sgml/memcache.sgml
src/context/pool_session_context.c
src/include/context/pool_session_context.h
src/include/query_cache/pool_memqcache.h
src/include/utils/pool_select_walker.h
src/protocol/CommandComplete.c
src/protocol/pool_proto_modules.c
src/query_cache/pool_memqcache.c
src/test/regression/tests/006.memqcache/alter_database1.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/alter_database2.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/alter_database3.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/alter_role.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/alter_table1.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/alter_table2.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/alter_table3.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/expected.n [new file with mode: 0644]
src/test/regression/tests/006.memqcache/expected.r [new file with mode: 0644]
src/test/regression/tests/006.memqcache/expected.s [new file with mode: 0644]
src/test/regression/tests/006.memqcache/revoke1.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/revoke2.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/revoke3.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/row_security.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/session_authorization.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/set_role1.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/set_role2.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/set_role3.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/set_role4.data [new file with mode: 0644]
src/test/regression/tests/006.memqcache/test.sh
src/utils/pool_select_walker.c

index 52cbe6bc9d257d9c4f57447df2b2d00b894663ee..a6f690306df37ce3d6d2cedd67182822fcba2fa6 100644 (file)
     /*NO QUERY CACHE*/コメントで始まるSELECT
     システムカタログを使用しているSELECT
     TABLESAMPLEを使っているSELECT
+    行セキュリティが設定されているテーブルを使っているSELECT
    </programlisting>
    ただし、VIEWと unloggedテーブルは、cache_safe_memqcache_table_list に記載することでキャッシュされます。
   </para>
  </note>
+
+ <note>
+  <para>
+   以下のコマンドはクエリキャッシュとデータベース内容の整合性を失わせる可能性があるので、実行されるとクエリキャッシュをすべて削除します。
+   <programlisting>
+    ALTER DATABASE
+    ALTER ROLE
+    ALTER TABLE
+    REVOKE
+   </programlisting>
+   また、SET ROLE、SET SESSION AUTHORIZATIONが実行されると、そのセッション内ではクエリキャッシュは使用されず、また新たにクエリキャッシュが作成されることもありません。
+   <productname>Pgpool-II</productname>はセッションユーザが異なるとクエリキャッシュを違うものと見なしますが、一方で<productname>PostgreSQL</productname>のアクセス権管理はカレントユーザ(current user)を基準に行っており、これらのコマンドはクエリキャッシュとPostgreSQLのアクセス権管理の間の整合性を損なう可能性があるからです。
+   ただし、更新クエリによるクエリキャッシュの削除はこれらのコマンドが使われていない場合と同様に行われます。
+  </para>
+ </note>
+
  <para>
   <!--
   In memory cache saves the pair of SELECT statement
index c6290955af6b3533bbc97d924b3982db34f2b75b..171a4a28ad8bb6849f18f868567c3f5d53a096c2 100644 (file)
@@ -34,6 +34,7 @@
     SELECT starting with "/*NO QUERY CACHE*/" comment
     SELECT including system catalogs
     SELECT uses TABLESAMPLE
+    SELECT uses row security enabled tables
    </programlisting>
    However, VIEWs and SELECTs accessing unlogged tables can be
    cached by specifying in
   </para>
  </note>
 
+ <note>
+  <para>
+   Since consistency between the query cache and database content can
+   be lost by following commands, the query cache is all deleted if
+   they are executed:
+   <programlisting>
+    ALTER DATABASE
+    ALTER ROLE
+    ALTER TABLE
+    REVOKE
+   </programlisting>
+   Moreover, if SET ROLE or SET SESSION AUTHORIZATION are executed,
+   the query cache will not be used and new cache will not be created
+   in the session.
+   <productname>Pgpool-II</productname> assumes that two query cache
+   are different if their session users are different. On the other
+   hand, <productname>PostgreSQL</productname> manages access control
+   depending on current user. Thus these commands may break the
+   consistency between these two access controls.  However, cache
+   invalidation by update commands are performed in the same way in
+   that these commands are not used.
+  </para>
+ </note>
+
  <para>
   On the other hand, it might be slower than the normal path
   in some cases, because it adds some overhead to store cache.
index f87eee07b5071618ed764535dc10e493c7871426..eea8df13601a6d5ea7e794daf1a92bc2b2fc5a9f 100644 (file)
@@ -171,6 +171,10 @@ pool_init_session_context(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * bac
        dml_adaptive_init();
 
        unset_tx_started_by_multi_statement_query();
+
+       unset_query_cache_disabled();
+
+       unset_query_cache_disabled_tx();
 }
 
 /*
@@ -2031,3 +2035,82 @@ unset_tx_started_by_multi_statement_query(void)
 
        session_context->is_tx_started_by_multi_statement = false;
 }
+
+/*
+ * Set query_cache_disabled
+ */
+void
+set_query_cache_disabled(void)
+{
+       if (!session_context)
+               ereport(ERROR,
+                               (errmsg("set_query_cache_disabled: session context is not initialized")));
+
+       session_context->query_cache_disabled = true;
+}
+
+/*
+ * Unset query_cache_disabled
+ */
+void
+unset_query_cache_disabled(void)
+{
+       if (!session_context)
+               ereport(ERROR,
+                               (errmsg("unset_query_cache_disabled: session context is not initialized")));
+
+       session_context->query_cache_disabled = false;
+}
+
+/*
+ * Get query_cache_disabled
+ */
+bool
+query_cache_disabled(void)
+{
+       if (!session_context)
+               ereport(ERROR,
+                               (errmsg("query_cache_disabled: session context is not initialized")));
+
+       return session_context->query_cache_disabled ||
+               session_context->query_cache_disabled_tx;
+}
+
+/*
+ * Set query_cache_disabled in transaction
+ */
+void
+set_query_cache_disabled_tx(void)
+{
+       if (!session_context)
+               ereport(ERROR,
+                               (errmsg("set_query_cache_disabled_tx: session context is not initialized")));
+
+       session_context->query_cache_disabled_tx = true;
+}
+
+/*
+ * Unset query_cache_disabled in transaction
+ */
+void
+unset_query_cache_disabled_tx(void)
+{
+       if (!session_context)
+               ereport(ERROR,
+                               (errmsg("unset_query_cache_disabled_tx: session context is not initialized")));
+
+       session_context->query_cache_disabled_tx = false;
+}
+
+/*
+ * Get query_cache_disabled in transaction
+ */
+bool
+query_cache_disabled_tx(void)
+{
+       if (!session_context)
+               ereport(ERROR,
+                               (errmsg("query_cache_disabled_tx: session context is not initialized")));
+
+       return session_context->query_cache_disabled_tx;
+}
index 192ce2d643ef88b265dd1fbf6d4327aef07f7771..a662aa01cf8409c39e5c5cba7b510117447c7931 100644 (file)
@@ -171,6 +171,7 @@ typedef enum
        SI_SNAPSHOT_PREPARED
 } SI_STATE;
 
+
 /*
  * Per session context:
  */
@@ -320,6 +321,17 @@ typedef struct
                                                                                                         * transaction has been
                                                                                                         * started by a
                                                                                                         * multi-statement-query */
+       /*
+        * True if query cache feature disabled until session ends.
+        * This is set when SET ROLE/SET SESSION AUTHORIZATION executed.
+        */
+       bool    query_cache_disabled;
+       /*
+        * True if query cache feature disabled until current transaction ends.
+        * This is set when REVOKE executed in a transaction.
+        */
+       bool    query_cache_disabled_tx;
+
 }                      POOL_SESSION_CONTEXT;
 
 extern void pool_init_session_context(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * backend);
@@ -408,4 +420,11 @@ extern bool is_tx_started_by_multi_statement_query(void);
 extern void set_tx_started_by_multi_statement_query(void);
 extern void unset_tx_started_by_multi_statement_query(void);
 
+extern void set_query_cache_disabled(void);
+extern void unset_query_cache_disabled(void);
+extern bool query_cache_disabled(void);
+extern void set_query_cache_disabled_tx(void);
+extern void unset_query_cache_disabled_tx(void);
+extern bool query_cache_disabled_tx(void);
+
 #endif                                                 /* POOL_SESSION_CONTEXT_H */
index 5206e2e53a2e311e6bcbddac3143a5446911c8c8..9607fb835d631cf0adf8f0d1c1763235a36202b0 100644 (file)
@@ -6,7 +6,7 @@
  * pgpool: a language independent connection pool server for PostgreSQL
  * written by Tatsuo Ishii
  *
- * Copyright (c) 2003-2023     PgPool Global Development Group
+ * Copyright (c) 2003-2024     PgPool Global Development Group
  *
  * Permission to use, copy, modify, and distribute this software and
  * its documentation for any purpose and without fee is hereby
@@ -302,4 +302,6 @@ extern void InvalidateQueryCache(int tableoid, int dboid);
 
 extern void pool_init_whole_cache_blocks(void);
 
+extern int delete_all_cache_on_memcached(void);
+
 #endif                                                 /* POOL_MEMQCACHE_H */
index 9a0e105a2b7c401a6c8ad9d42bef96d9f9265ee4..0ae7ffe7c01da15f321eecd58a009ed3f561a6ea 100644 (file)
@@ -6,7 +6,7 @@
  * pgpool: a language independent connection pool server for PostgreSQL
  * written by Tatsuo Ishii
  *
- * Copyright (c) 2003-2023     PgPool Global Development Group
+ * Copyright (c) 2003-2024     PgPool Global Development Group
  *
  * Permission to use, copy, modify, and distribute this software and
  * its documentation for any purpose and without fee is hereby
@@ -48,6 +48,7 @@ typedef struct
        bool            has_insertinto_or_locking_clause;       /* True if it has SELECT
                                                                                                         * INTO or FOR
                                                                                                         * SHARE/UPDATE */
+       bool            row_security;   /* true if row security enabled */
        int                     num_oids;               /* number of oids */
        int                     table_oids[POOL_MAX_SELECT_OIDS];       /* table oids */
        char            table_names[POOL_MAX_SELECT_OIDS][NAMEDATALEN]; /* table names */
@@ -61,6 +62,7 @@ extern bool pool_has_temp_table(Node *node);
 extern void discard_temp_table_relcache(void);
 extern bool pool_has_unlogged_table(Node *node);
 extern bool pool_has_view(Node *node);
+extern bool pool_has_row_security(Node *node);
 extern bool pool_has_insertinto_or_locking_clause(Node *node);
 extern bool pool_has_pgpool_regclass(void);
 extern bool pool_has_to_regclass(void);
index a6605ddb77c5adc96ef74b89d974bd7aa77e14cc..92e1e99a9a2a96808ce1ee0ec279dc93cf2965d3 100644 (file)
@@ -5,7 +5,7 @@
  * pgpool: a language independent connection pool server for PostgreSQL
  * written by Tatsuo Ishii
  *
- * Copyright (c) 2003-2023     PgPool Global Development Group
+ * Copyright (c) 2003-2024     PgPool Global Development Group
  *
  * Permission to use, copy, modify, and distribute this software and
  * its documentation for any purpose and without fee is hereby
@@ -44,6 +44,8 @@ static POOL_STATUS handle_mismatch_tuples(POOL_CONNECTION * frontend, POOL_CONNE
 static int     forward_command_complete(POOL_CONNECTION * frontend, char *packet, int packetlen);
 static int     forward_empty_query(POOL_CONNECTION * frontend, char *packet, int packetlen);
 static int     forward_packet_to_frontend(POOL_CONNECTION * frontend, char kind, char *packet, int packetlen);
+static void process_clear_cache(POOL_CONNECTION_POOL * backend);
+static void clear_query_cache(void);
 
 POOL_STATUS
 CommandComplete(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * backend, bool command_complete)
@@ -367,6 +369,21 @@ handle_query_context(POOL_CONNECTION_POOL * backend)
 
                        /* Forget a transaction was started by multi statement query */
                        unset_tx_started_by_multi_statement_query();
+
+                       /*
+                        * Query cache enabled and REVOKE was issued in this transaction?
+                        */
+                       if (pool_config->memory_cache_enabled &&
+                               query_cache_disabled_tx())
+                       {
+                               /*
+                                * Clear all the query cache.
+                                */
+                               clear_query_cache();
+
+                               /* Unset query cache disabled in this transaction */
+                               unset_query_cache_disabled_tx();
+                       }
                }
                else if (stmt->kind == TRANS_STMT_ROLLBACK)
                {
@@ -375,6 +392,9 @@ handle_query_context(POOL_CONNECTION_POOL * backend)
 
                        /* Forget a transaction was started by multi statement query */
                        unset_tx_started_by_multi_statement_query();
+
+                       /* Unset query cache disabled in this transaction */
+                       unset_query_cache_disabled_tx();
                }
        }
        else if (IsA(node, CreateStmt))
@@ -433,6 +453,34 @@ handle_query_context(POOL_CONNECTION_POOL * backend)
                        }
                }
        }
+       else if (IsA(node, VariableSetStmt))
+       {
+               VariableSetStmt *stmt = (VariableSetStmt *) node;
+
+               /* SET ROLE or SESSION AUTHORIZATION command? */
+               if (stmt->kind == VAR_SET_VALUE &&
+                       (!strcmp(stmt->name, "role") ||
+                        !strcmp(stmt->name, "session_authorization")))
+                       /* disable query cache in this session */
+                       set_query_cache_disabled();
+       }
+       else if (IsA(node, GrantStmt))
+       {
+               GrantStmt *stmt = (GrantStmt *) node;
+
+               /* REVOKE? */
+               if (stmt->is_grant)
+                       return;
+
+               /* Clear query cache */
+               process_clear_cache(backend);
+       }
+       else if (IsA(node, AlterTableStmt) || IsA(node, AlterDatabaseStmt) ||
+                        IsA(node, AlterDatabaseSetStmt) || IsA(node, AlterRoleStmt))
+       {
+               /* Clear query cache */
+               process_clear_cache(backend);
+       }
 }
 
 /*
@@ -607,3 +655,103 @@ forward_packet_to_frontend(POOL_CONNECTION * frontend, char kind, char *packet,
 
        return 0;
 }
+
+/*
+ * Process statements that need clearing query cache
+ */
+static void
+process_clear_cache(POOL_CONNECTION_POOL * backend)
+{
+       /* Query cache enabled? */
+       if (!pool_config->memory_cache_enabled)
+               return;
+
+       /* Streaming replication mode? */
+       if (SL_MODE)
+       {
+               /*
+                * Are we inside a transaction?
+                */
+               if (TSTATE(backend, MAIN_NODE_ID ) == 'T')
+               {
+                       /*
+                        * Disable query cache in this transaction.
+                        * All query cache will be cleared at commit.
+                        */
+                       set_query_cache_disabled_tx();
+               }
+               else if (TSTATE(backend, MAIN_NODE_ID ) == 'I') /* outside transaction */
+               {
+                       /*
+                        * Clear all the query cache.
+                        */
+                       clear_query_cache();
+               }
+       }
+       else
+       {
+               /*
+                * Are we inside a transaction?
+                */
+               if (TSTATE(backend, MAIN_NODE_ID ) == 'T')
+               {
+                       /* Inside user started transaction? */
+                       if (!INTERNAL_TRANSACTION_STARTED(backend, MAIN_NODE_ID))
+                       {
+                               /*
+                                * Disable query cache in this transaction.
+                                * All query cache will be cleared at commit.
+                                */
+                               set_query_cache_disabled_tx();
+                       }
+                       else
+                       {
+                               /*
+                                * Clear all the query cache.
+                                */
+                               clear_query_cache();
+                       }
+               }
+               else if (TSTATE(backend, MAIN_NODE_ID ) == 'I') /* outside transaction */
+               {
+                       /*
+                        * Clear all the query cache.
+                        */
+                       clear_query_cache();
+               }
+       }
+}
+
+/*
+ * Clear query cache on shmem or memcached
+ */
+static
+void clear_query_cache(void)
+{
+       /*
+        * Clear all the shared memory cache and oid maps.
+        */
+       if (pool_is_shmem_cache())
+       {
+               pool_clear_memory_cache();
+               ereport(LOG,
+                               (errmsg("all query cache in shared memory deleted")));
+       }
+       else
+#ifdef USE_MEMCACHED
+       {
+               /*
+                * Clear all the memcached cache and oid maps.
+                */
+               delete_all_cache_on_memcached();
+               pool_discard_oid_maps();
+               ereport(LOG,
+                               (errmsg("all query cache in memcached deleted")));
+       }
+#else
+       {
+               ereport(WARNING,
+                               (errmsg("failed to clear cache on memcached, memcached support is not enabled")));
+       }
+#endif
+}
index 71bad97d26acc504be0fe0e30e1e8708ec9fa77f..df8d27e0ca957bf2140ca534fbdc486695d97757 100644 (file)
@@ -262,10 +262,12 @@ SimpleQuery(POOL_CONNECTION * frontend,
         * catalog, which will add significant overhead. Moreover if we are in
         * aborted transaction, commands should be ignored, so we should not use
         * query cache.
+        * Also query cache is disabled, we should not fetch from query cache.
         */
        if (pool_config->memory_cache_enabled && is_likely_select &&
                !pool_is_writing_transaction() &&
-               TSTATE(backend, MAIN_REPLICA ? PRIMARY_NODE_ID : REAL_MAIN_NODE_ID) != 'E')
+               TSTATE(backend, MAIN_REPLICA ? PRIMARY_NODE_ID : REAL_MAIN_NODE_ID) != 'E' &&
+               !query_cache_disabled())
        {
                bool            foundp;
 
@@ -976,7 +978,7 @@ Execute(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * backend,
         */
        if (pool_config->memory_cache_enabled && !pool_is_writing_transaction() &&
                (TSTATE(backend, MAIN_REPLICA ? PRIMARY_NODE_ID : REAL_MAIN_NODE_ID) != 'E')
-               && pool_is_likely_select(query))
+               && pool_is_likely_select(query) && !query_cache_disabled())
        {
                POOL_STATUS status;
                char       *search_query = NULL;
index 24dc27ec39427ff64b8fe9c46df6d412b898b894..84be8a26778fc8e341fbcfa2abd0a9cde81f5322 100644 (file)
@@ -993,6 +993,11 @@ pool_is_allow_to_cache(Node *node, char *query)
                }
        }
 
+       /*
+        * If SELECT uses row security enabled tables, it's not allowed to cache.
+        */
+       if (pool_has_row_security(node))
+               return false;
 
        /*
         * If the table is in the cache_safe_memqcache_table_list, allow to cache
@@ -2142,6 +2147,28 @@ pool_clear_memory_cache(void)
        POOL_SETMASK(&oldmask);
 }
 
+#ifdef USE_MEMCACHED
+/*
+ * delete all query cache on memcached
+ */
+int
+delete_all_cache_on_memcached(void)
+{
+       memcached_return rc;
+
+       rc = memcached_flush(memc, 0);
+
+       /* delete all cache on memcached */
+       if (rc != MEMCACHED_SUCCESS && rc != MEMCACHED_BUFFERED)
+       {
+               ereport(LOG,
+                               (errmsg("failed to delete all cache on memcached, error:\"%s\"", memcached_strerror(memc, rc))));
+               return 0;
+       }
+       return 1;
+}
+#endif
+
 /*
  * Return shared memory cache address
  */
@@ -3599,7 +3626,7 @@ pool_handle_query_cache(POOL_CONNECTION_POOL * backend, char *query, Node *node,
        session_context = pool_get_session_context(true);
 
        /* Ok to cache SELECT result? */
-       if (pool_is_cache_safe())
+       if (pool_is_cache_safe() && !query_cache_disabled())
        {
                SelectContext ctx;
                MemoryContext old_context;
diff --git a/src/test/regression/tests/006.memqcache/alter_database1.data b/src/test/regression/tests/006.memqcache/alter_database1.data
new file mode 100644 (file)
index 0000000..bc0ec47
--- /dev/null
@@ -0,0 +1,53 @@
+# Testing ALTER DATABASE
+# create cache
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# execute ALTER DATABASE
+'P'    ""      "ALTER DATABASE test2 RESET ALL"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# Make sure cache is not used
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# explicit transaction case
+'P'    ""      "BEGIN"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "ALTER DATABASE test2 RESET ALL"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# Make sure cache is not used
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "END"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# Make sure cache is used
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/alter_database2.data b/src/test/regression/tests/006.memqcache/alter_database2.data
new file mode 100644 (file)
index 0000000..4a8fc6f
--- /dev/null
@@ -0,0 +1,18 @@
+# ALTER DATABASE is executed on another session case
+# Make sure to create cache (sync needed)
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "ALTER DATABASE test2 RESET ALL"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/alter_database3.data b/src/test/regression/tests/006.memqcache/alter_database3.data
new file mode 100644 (file)
index 0000000..efba168
--- /dev/null
@@ -0,0 +1,8 @@
+# ALTER DATABASE is executed on another session case
+# Make sure this does not access cache
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/alter_role.data b/src/test/regression/tests/006.memqcache/alter_role.data
new file mode 100644 (file)
index 0000000..601f3d0
--- /dev/null
@@ -0,0 +1,50 @@
+# ALTER ROLE BYPASSRLS case
+# Make sure to create cache (sync needed)
+'P'    ""      "SELECT * FROM users"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "SELECT * FROM users"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "ALTER ROLE foo BYPASSRLS"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "SET ROLE TO foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# expect to ignore cache and result is all rows
+'P'    ""      "SELECT * FROM users"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "RESET ROLE"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "ALTER ROLE foo NOBYPASSRLS"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "SET ROLE TO foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# expect to ignore cache and result is one row
+'P'    ""      "SELECT * FROM users"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/alter_table1.data b/src/test/regression/tests/006.memqcache/alter_table1.data
new file mode 100644 (file)
index 0000000..27e6523
--- /dev/null
@@ -0,0 +1,60 @@
+# ALTER ROLE BYPASSRLS case
+# Make sure to create cache (sync needed)
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'P'    ""      "ALTER TABLE t1 ADD COLUMN j INT"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# Make sure cache is not used
+'P'    ""      "SELECT j FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# explicit transaction case
+'P'    ""      "BEGIN"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "ALTER TABLE t1 DROP COLUMN j"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# Make sure cache is not used (an error expected)
+'P'    ""      "SELECT i,j FROM t1"
+#'B'   ""      ""      0       0       0
+#'E'   ""      0
+'S'
+'Y'
+'P'    ""      "END"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+# Sync is needed to reset query_cache_disabled_tx flag
+'S'
+'Y'
+# create cache
+'P'    ""      "SELECT j FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# Make sure cache is used
+'P'    ""      "SELECT j FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# drop column j for subsequent test
+'Q'    "ALTER TABLE t1 DROP COLUMN j"
+'S'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/alter_table2.data b/src/test/regression/tests/006.memqcache/alter_table2.data
new file mode 100644 (file)
index 0000000..2edce5b
--- /dev/null
@@ -0,0 +1,18 @@
+# ALTER TABLE is executed on another session case
+# Make sure to create cache (sync needed)
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "ALTER TABLE t1 ADD COLUMN j INT"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/alter_table3.data b/src/test/regression/tests/006.memqcache/alter_table3.data
new file mode 100644 (file)
index 0000000..9b2bc64
--- /dev/null
@@ -0,0 +1,8 @@
+# ALTER TABLE is executed on another session case
+# Make sure this does not access cache
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/expected.n b/src/test/regression/tests/006.memqcache/expected.n
new file mode 100644 (file)
index 0000000..00e3bc9
--- /dev/null
@@ -0,0 +1,569 @@
+--
+-- testing an effect on a row level security enabled table and SET ROLE
+--
+CREATE TABLE users (user_name TEXT, data TEXT);
+NOTICE:  DB node id: 0 statement: CREATE TABLE users (user_name TEXT, data TEXT);
+CREATE TABLE
+INSERT INTO users VALUES('foo', 'foodata');
+NOTICE:  DB node id: 0 statement: INSERT INTO users VALUES('foo', 'foodata');
+INSERT 0 1
+INSERT INTO users VALUES('bar', 'bardata');
+NOTICE:  DB node id: 0 statement: INSERT INTO users VALUES('bar', 'bardata');
+INSERT 0 1
+ALTER TABLE users ENABLE ROW LEVEL SECURITY;
+NOTICE:  DB node id: 0 statement: ALTER TABLE users ENABLE ROW LEVEL SECURITY;
+ALTER TABLE
+CREATE POLICY user_policy ON users USING (user_name = CURRENT_USER);
+NOTICE:  DB node id: 0 statement: CREATE POLICY user_policy ON users USING (user_name = CURRENT_USER);
+CREATE POLICY
+GRANT SELECT ON users TO foo;
+NOTICE:  DB node id: 0 statement: GRANT SELECT ON users TO foo;
+GRANT
+GRANT SELECT ON users TO bar;
+NOTICE:  DB node id: 0 statement: GRANT SELECT ON users TO bar;
+GRANT
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- run SELECT as foo. Only user_name = 'foo' data expected.
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ user_name |  data   
+-----------+---------
+ foo       | foodata
+(1 row)
+
+RESET ROLE;
+NOTICE:  DB node id: 0 statement: RESET ROLE;
+RESET
+SET ROLE TO bar;
+NOTICE:  DB node id: 0 statement: SET ROLE TO bar;
+SET
+-- run SELECT as bar. Only user_name = 'bar' data expected.
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ user_name |  data   
+-----------+---------
+ bar       | bardata
+(1 row)
+
+--
+-- testing row security with row_security = off
+--
+SET ROW_SECURITY TO off;
+NOTICE:  DB node id: 0 statement: SET ROW_SECURITY TO off;
+SET
+-- Error expected
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ERROR:  query would be affected by row-level security policy for table "users"
+--
+-- testing SET ROLE
+--
+CREATE TABLE footable(t text);
+NOTICE:  DB node id: 0 statement: CREATE TABLE footable(t text);
+CREATE TABLE
+INSERT INTO footable VALUES('foo');
+NOTICE:  DB node id: 0 statement: INSERT INTO footable VALUES('foo');
+INSERT 0 1
+GRANT SELECT ON footable TO foo;
+NOTICE:  DB node id: 0 statement: GRANT SELECT ON footable TO foo;
+GRANT
+GRANT INSERT ON footable TO foo;
+NOTICE:  DB node id: 0 statement: GRANT INSERT ON footable TO foo;
+GRANT
+GRANT UPDATE ON footable TO foo;
+NOTICE:  DB node id: 0 statement: GRANT UPDATE ON footable TO foo;
+GRANT
+GRANT DELETE ON footable TO foo;
+NOTICE:  DB node id: 0 statement: GRANT DELETE ON footable TO foo;
+GRANT
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t  
+-----
+ foo
+(1 row)
+
+SET ROLE TO bar;
+NOTICE:  DB node id: 0 statement: SET ROLE TO bar;
+SET
+-- run SELECT as bar. Permission denied is expected.
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+ERROR:  permission denied for table footable
+--
+-- testing SESSION AUTHORIZATION
+--
+SET SESSION AUTHORIZATION bar;
+NOTICE:  DB node id: 0 statement: SET SESSION AUTHORIZATION bar;
+SET
+-- run SELECT as bar. Permission denied is expected.
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+ERROR:  permission denied for table footable
+--
+-- testing SET ROLE. Make sure that query cache is not
+-- created.
+--
+-- create cache
+SELECT * FROM footable;
+  t  
+-----
+ foo
+(1 row)
+
+-- change role
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- run SELECT as foo to make sure that cache is not used.
+-- If query cache was created we will NOT see
+-- "NOTICE: DB node id: 1 statement: SELECT ..."
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t  
+-----
+ foo
+(1 row)
+
+-- Modify footable to see cache invalidation works even after SET ROLE.
+INSERT INTO footable VALUES ('foo1');
+NOTICE:  DB node id: 0 statement: INSERT INTO footable VALUES ('foo1');
+INSERT 0 1
+-- restore ROLE
+RESET ROLE;
+NOTICE:  DB node id: 0 statement: RESET ROLE;
+RESET
+-- Make sure cache was invalidated.
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+(2 rows)
+
+--
+-- explicit transaction case
+--
+-- create cache
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+(2 rows)
+
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+(2 rows)
+
+-- change role
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+BEGIN
+-- run SELECT as foo to make sure that cache is not used.
+-- If query cache was created we will NOT see
+-- "NOTICE: DB node id: 1 statement: SELECT ..."
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+(2 rows)
+
+-- Modify footable to see cache invalidation works even after SET ROLE.
+INSERT INTO footable VALUES ('foo2');
+NOTICE:  DB node id: 0 statement: INSERT INTO footable VALUES ('foo2');
+INSERT 0 1
+END;
+NOTICE:  DB node id: 0 statement: END;
+COMMIT
+-- Make sure cache was invalidated.
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+--
+-- explicit transaction abort case
+--
+-- create cache
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+-- change role
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+BEGIN
+-- run SELECT as foo to make sure that cache is not used.
+-- If query cache was created we will NOT see
+-- "NOTICE: DB node id: 0 statement: SELECT ..."
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+-- Modify footable to see cache invalidation works even after SET ROLE.
+INSERT INTO footable VALUES ('foo3');
+NOTICE:  DB node id: 0 statement: INSERT INTO footable VALUES ('foo3');
+INSERT 0 1
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+ foo3
+(4 rows)
+
+ABORT;
+NOTICE:  DB node id: 0 statement: ABORT;
+ROLLBACK
+-- Make sure we don't see 'foo3' row.
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+-- Make sure we don't see 'foo3' row.
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+--
+-- Testing REVOKE
+--
+-- create cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+-- REVOKE
+REVOKE SELECT ON t1 FROM foo;
+NOTICE:  DB node id: 0 statement: REVOKE SELECT ON t1 FROM foo;
+REVOKE
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- Make sure foo cannot SELECT t1
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ERROR:  permission denied for table t1
+RESET ROLE;
+NOTICE:  DB node id: 0 statement: RESET ROLE;
+RESET
+-- GRANT again
+GRANT SELECT ON t1 TO foo;
+NOTICE:  DB node id: 0 statement: GRANT SELECT ON t1 TO foo;
+GRANT
+-- explicit transaction case
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+BEGIN
+-- REVOKE
+REVOKE SELECT ON t1 FROM foo;
+NOTICE:  DB node id: 0 statement: REVOKE SELECT ON t1 FROM foo;
+REVOKE
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- Make sure foo cannot SELECT t1
+-- (thus REVOKE will be rollbacked )
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ERROR:  permission denied for table t1
+END;
+NOTICE:  DB node id: 0 statement: END;
+ROLLBACK
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- because REVOKE is rolled back, foo should be able to access t1
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+--
+-- REVOKE is executed on another session case
+--
+-- Make sure to create cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+-- execute REVOKE
+REVOKE SELECT ON t1 FROM foo
+NOTICE:  DB node id: 0 statement: REVOKE SELECT ON t1 FROM foo
+REVOKE
+-- Make sure this does not access cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+--
+-- ALTER ROLE BYPASSRLS case
+--
+ALTER ROLE foo BYPASSRLS;
+NOTICE:  DB node id: 0 statement: ALTER ROLE foo BYPASSRLS;
+ALTER ROLE
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- expect to ignore cache and result is all rows
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ user_name |  data   
+-----------+---------
+ foo       | foodata
+ bar       | bardata
+(2 rows)
+
+RESET ROLE;
+NOTICE:  DB node id: 0 statement: RESET ROLE;
+RESET
+ALTER ROLE foo NOBYPASSRLS;
+NOTICE:  DB node id: 0 statement: ALTER ROLE foo NOBYPASSRLS;
+ALTER ROLE
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- expect to ignore cache and result is one row
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ user_name |  data   
+-----------+---------
+ foo       | foodata
+(1 row)
+
+--
+-- Testing ALTER TABLE
+--
+-- create cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+ALTER TABLE t1 ADD COLUMN j INT;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 ADD COLUMN j INT;
+ALTER TABLE
+-- Make sure cache is not used
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+-- explicit transaction case
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+BEGIN
+ALTER TABLE t1 DROP COLUMN j;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 DROP COLUMN j;
+ALTER TABLE
+-- Make sure cache is not used
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+END;
+NOTICE:  DB node id: 0 statement: END;
+COMMIT
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+-- Make sure cache is used
+SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+--
+-- ALTER TABLE is executed on another session case
+--
+-- Make sure to create cache
+SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+ALTER TABLE t1 ADD COLUMN j INT;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 ADD COLUMN j INT;
+ALTER TABLE
+-- Make sure this does not access cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+ALTER TABLE t1 DROP COLUMN j;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 DROP COLUMN j;
+ALTER TABLE
+--
+-- Testing ALTER DATABASE
+--
+ALTER TABLE t1 ADD COLUMN j INT;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 ADD COLUMN j INT;
+ALTER TABLE
+-- create taget database
+create database test2;
+NOTICE:  DB node id: 0 statement: create database test2;
+CREATE DATABASE
+-- create cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+ALTER DATABASE test2 RESET ALL;
+NOTICE:  DB node id: 0 statement: ALTER DATABASE test2 RESET ALL;
+ALTER DATABASE
+-- Make sure cache is not used
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+-- explicit transaction case
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+BEGIN
+ALTER DATABASE test2 RESET ALL;
+NOTICE:  DB node id: 0 statement: ALTER DATABASE test2 RESET ALL;
+ALTER DATABASE
+-- Make sure cache is not used
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+END;
+NOTICE:  DB node id: 0 statement: END;
+COMMIT
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+-- Make sure cache is used
+SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+--
+-- ALTER DATABASE is executed on another session case
+--
+-- Make sure to create cache
+SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+ALTER DATABASE test2 RESET ALL;
+NOTICE:  DB node id: 0 statement: ALTER DATABASE test2 RESET ALL;
+ALTER DATABASE
+-- Make sure this does not access cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
diff --git a/src/test/regression/tests/006.memqcache/expected.r b/src/test/regression/tests/006.memqcache/expected.r
new file mode 100644 (file)
index 0000000..fc7f330
--- /dev/null
@@ -0,0 +1,627 @@
+--
+-- testing an effect on a row level security enabled table and SET ROLE
+--
+CREATE TABLE users (user_name TEXT, data TEXT);
+NOTICE:  DB node id: 0 statement: CREATE TABLE users (user_name TEXT, data TEXT);
+NOTICE:  DB node id: 1 statement: CREATE TABLE users (user_name TEXT, data TEXT);
+CREATE TABLE
+INSERT INTO users VALUES('foo', 'foodata');
+NOTICE:  DB node id: 0 statement: INSERT INTO users VALUES('foo', 'foodata');
+NOTICE:  DB node id: 1 statement: INSERT INTO users VALUES('foo', 'foodata');
+INSERT 0 1
+INSERT INTO users VALUES('bar', 'bardata');
+NOTICE:  DB node id: 0 statement: INSERT INTO users VALUES('bar', 'bardata');
+NOTICE:  DB node id: 1 statement: INSERT INTO users VALUES('bar', 'bardata');
+INSERT 0 1
+ALTER TABLE users ENABLE ROW LEVEL SECURITY;
+NOTICE:  DB node id: 0 statement: ALTER TABLE users ENABLE ROW LEVEL SECURITY;
+NOTICE:  DB node id: 1 statement: ALTER TABLE users ENABLE ROW LEVEL SECURITY;
+ALTER TABLE
+CREATE POLICY user_policy ON users USING (user_name = CURRENT_USER);
+NOTICE:  DB node id: 0 statement: CREATE POLICY user_policy ON users USING (user_name = CURRENT_USER);
+NOTICE:  DB node id: 1 statement: CREATE POLICY user_policy ON users USING (user_name = CURRENT_USER);
+CREATE POLICY
+GRANT SELECT ON users TO foo;
+NOTICE:  DB node id: 0 statement: GRANT SELECT ON users TO foo;
+NOTICE:  DB node id: 1 statement: GRANT SELECT ON users TO foo;
+GRANT
+GRANT SELECT ON users TO bar;
+NOTICE:  DB node id: 0 statement: GRANT SELECT ON users TO bar;
+NOTICE:  DB node id: 1 statement: GRANT SELECT ON users TO bar;
+GRANT
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+NOTICE:  DB node id: 1 statement: SET ROLE TO foo;
+SET
+-- run SELECT as foo. Only user_name = 'foo' data expected.
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ user_name |  data   
+-----------+---------
+ foo       | foodata
+(1 row)
+
+RESET ROLE;
+NOTICE:  DB node id: 0 statement: RESET ROLE;
+NOTICE:  DB node id: 1 statement: RESET ROLE;
+RESET
+SET ROLE TO bar;
+NOTICE:  DB node id: 0 statement: SET ROLE TO bar;
+NOTICE:  DB node id: 1 statement: SET ROLE TO bar;
+SET
+-- run SELECT as bar. Only user_name = 'bar' data expected.
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ user_name |  data   
+-----------+---------
+ bar       | bardata
+(1 row)
+
+--
+-- testing row security with row_security = off
+--
+SET ROW_SECURITY TO off;
+NOTICE:  DB node id: 0 statement: SET ROW_SECURITY TO off;
+NOTICE:  DB node id: 1 statement: SET ROW_SECURITY TO off;
+SET
+-- Error expected
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ERROR:  query would be affected by row-level security policy for table "users"
+--
+-- testing SET ROLE
+--
+CREATE TABLE footable(t text);
+NOTICE:  DB node id: 0 statement: CREATE TABLE footable(t text);
+NOTICE:  DB node id: 1 statement: CREATE TABLE footable(t text);
+CREATE TABLE
+INSERT INTO footable VALUES('foo');
+NOTICE:  DB node id: 0 statement: INSERT INTO footable VALUES('foo');
+NOTICE:  DB node id: 1 statement: INSERT INTO footable VALUES('foo');
+INSERT 0 1
+GRANT SELECT ON footable TO foo;
+NOTICE:  DB node id: 0 statement: GRANT SELECT ON footable TO foo;
+NOTICE:  DB node id: 1 statement: GRANT SELECT ON footable TO foo;
+GRANT
+GRANT INSERT ON footable TO foo;
+NOTICE:  DB node id: 0 statement: GRANT INSERT ON footable TO foo;
+NOTICE:  DB node id: 1 statement: GRANT INSERT ON footable TO foo;
+GRANT
+GRANT UPDATE ON footable TO foo;
+NOTICE:  DB node id: 0 statement: GRANT UPDATE ON footable TO foo;
+NOTICE:  DB node id: 1 statement: GRANT UPDATE ON footable TO foo;
+GRANT
+GRANT DELETE ON footable TO foo;
+NOTICE:  DB node id: 0 statement: GRANT DELETE ON footable TO foo;
+NOTICE:  DB node id: 1 statement: GRANT DELETE ON footable TO foo;
+GRANT
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t  
+-----
+ foo
+(1 row)
+
+SET ROLE TO bar;
+NOTICE:  DB node id: 0 statement: SET ROLE TO bar;
+NOTICE:  DB node id: 1 statement: SET ROLE TO bar;
+SET
+-- run SELECT as bar. Permission denied is expected.
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+ERROR:  permission denied for table footable
+--
+-- testing SESSION AUTHORIZATION
+--
+SET SESSION AUTHORIZATION bar;
+NOTICE:  DB node id: 0 statement: SET SESSION AUTHORIZATION bar;
+NOTICE:  DB node id: 1 statement: SET SESSION AUTHORIZATION bar;
+SET
+-- run SELECT as bar. Permission denied is expected.
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+ERROR:  permission denied for table footable
+--
+-- testing SET ROLE. Make sure that query cache is not
+-- created.
+--
+-- create cache
+SELECT * FROM footable;
+  t  
+-----
+ foo
+(1 row)
+
+-- change role
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+NOTICE:  DB node id: 1 statement: SET ROLE TO foo;
+SET
+-- run SELECT as foo to make sure that cache is not used.
+-- If query cache was created we will NOT see
+-- "NOTICE: DB node id: 1 statement: SELECT ..."
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t  
+-----
+ foo
+(1 row)
+
+-- Modify footable to see cache invalidation works even after SET ROLE.
+INSERT INTO footable VALUES ('foo1');
+NOTICE:  DB node id: 0 statement: INSERT INTO footable VALUES ('foo1');
+NOTICE:  DB node id: 1 statement: INSERT INTO footable VALUES ('foo1');
+INSERT 0 1
+-- restore ROLE
+RESET ROLE;
+NOTICE:  DB node id: 0 statement: RESET ROLE;
+NOTICE:  DB node id: 1 statement: RESET ROLE;
+RESET
+-- Make sure cache was invalidated.
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+(2 rows)
+
+--
+-- explicit transaction case
+--
+-- create cache
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+(2 rows)
+
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+(2 rows)
+
+-- change role
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+NOTICE:  DB node id: 1 statement: SET ROLE TO foo;
+SET
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+NOTICE:  DB node id: 1 statement: BEGIN;
+BEGIN
+-- run SELECT as foo to make sure that cache is not used.
+-- If query cache was created we will NOT see
+-- "NOTICE: DB node id: 1 statement: SELECT ..."
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+(2 rows)
+
+-- Modify footable to see cache invalidation works even after SET ROLE.
+INSERT INTO footable VALUES ('foo2');
+NOTICE:  DB node id: 0 statement: INSERT INTO footable VALUES ('foo2');
+NOTICE:  DB node id: 1 statement: INSERT INTO footable VALUES ('foo2');
+INSERT 0 1
+END;
+NOTICE:  DB node id: 1 statement: END;
+NOTICE:  DB node id: 0 statement: END;
+COMMIT
+-- Make sure cache was invalidated.
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+--
+-- explicit transaction abort case
+--
+-- create cache
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+-- change role
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+NOTICE:  DB node id: 1 statement: SET ROLE TO foo;
+SET
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+NOTICE:  DB node id: 1 statement: BEGIN;
+BEGIN
+-- run SELECT as foo to make sure that cache is not used.
+-- If query cache was created we will NOT see
+-- "NOTICE: DB node id: 0 statement: SELECT ..."
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+-- Modify footable to see cache invalidation works even after SET ROLE.
+INSERT INTO footable VALUES ('foo3');
+NOTICE:  DB node id: 0 statement: INSERT INTO footable VALUES ('foo3');
+NOTICE:  DB node id: 1 statement: INSERT INTO footable VALUES ('foo3');
+INSERT 0 1
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+ foo3
+(4 rows)
+
+ABORT;
+NOTICE:  DB node id: 1 statement: ABORT;
+NOTICE:  DB node id: 0 statement: ABORT;
+ROLLBACK
+-- Make sure we don't see 'foo3' row.
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+-- Make sure we don't see 'foo3' row.
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+--
+-- Testing REVOKE
+--
+-- create cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+-- REVOKE
+REVOKE SELECT ON t1 FROM foo;
+NOTICE:  DB node id: 0 statement: REVOKE SELECT ON t1 FROM foo;
+NOTICE:  DB node id: 1 statement: REVOKE SELECT ON t1 FROM foo;
+REVOKE
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+NOTICE:  DB node id: 1 statement: SET ROLE TO foo;
+SET
+-- Make sure foo cannot SELECT t1
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ERROR:  permission denied for table t1
+RESET ROLE;
+NOTICE:  DB node id: 0 statement: RESET ROLE;
+NOTICE:  DB node id: 1 statement: RESET ROLE;
+RESET
+-- GRANT again
+GRANT SELECT ON t1 TO foo;
+NOTICE:  DB node id: 0 statement: GRANT SELECT ON t1 TO foo;
+NOTICE:  DB node id: 1 statement: GRANT SELECT ON t1 TO foo;
+GRANT
+-- explicit transaction case
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+NOTICE:  DB node id: 1 statement: BEGIN;
+BEGIN
+-- REVOKE
+REVOKE SELECT ON t1 FROM foo;
+NOTICE:  DB node id: 0 statement: REVOKE SELECT ON t1 FROM foo;
+NOTICE:  DB node id: 1 statement: REVOKE SELECT ON t1 FROM foo;
+REVOKE
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+NOTICE:  DB node id: 1 statement: SET ROLE TO foo;
+SET
+-- Make sure foo cannot SELECT t1
+-- (thus REVOKE will be rollbacked )
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ERROR:  permission denied for table t1
+END;
+NOTICE:  DB node id: 1 statement: END;
+NOTICE:  DB node id: 0 statement: END;
+ROLLBACK
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+NOTICE:  DB node id: 1 statement: SET ROLE TO foo;
+SET
+-- because REVOKE is rolled back, foo should be able to access t1
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+--
+-- REVOKE is executed on another session case
+--
+-- Make sure to create cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+-- execute REVOKE
+REVOKE SELECT ON t1 FROM foo
+NOTICE:  DB node id: 0 statement: REVOKE SELECT ON t1 FROM foo
+NOTICE:  DB node id: 1 statement: REVOKE SELECT ON t1 FROM foo
+REVOKE
+-- Make sure this does not access cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+--
+-- ALTER ROLE BYPASSRLS case
+--
+ALTER ROLE foo BYPASSRLS;
+NOTICE:  DB node id: 0 statement: ALTER ROLE foo BYPASSRLS;
+NOTICE:  DB node id: 1 statement: ALTER ROLE foo BYPASSRLS;
+ALTER ROLE
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+NOTICE:  DB node id: 1 statement: SET ROLE TO foo;
+SET
+-- expect to ignore cache and result is all rows
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ user_name |  data   
+-----------+---------
+ foo       | foodata
+ bar       | bardata
+(2 rows)
+
+RESET ROLE;
+NOTICE:  DB node id: 0 statement: RESET ROLE;
+NOTICE:  DB node id: 1 statement: RESET ROLE;
+RESET
+ALTER ROLE foo NOBYPASSRLS;
+NOTICE:  DB node id: 0 statement: ALTER ROLE foo NOBYPASSRLS;
+NOTICE:  DB node id: 1 statement: ALTER ROLE foo NOBYPASSRLS;
+ALTER ROLE
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+NOTICE:  DB node id: 1 statement: SET ROLE TO foo;
+SET
+-- expect to ignore cache and result is one row
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ user_name |  data   
+-----------+---------
+ foo       | foodata
+(1 row)
+
+--
+-- Testing ALTER TABLE
+--
+-- create cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+ALTER TABLE t1 ADD COLUMN j INT;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 ADD COLUMN j INT;
+NOTICE:  DB node id: 1 statement: ALTER TABLE t1 ADD COLUMN j INT;
+ALTER TABLE
+-- Make sure cache is not used
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+-- explicit transaction case
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+NOTICE:  DB node id: 1 statement: BEGIN;
+BEGIN
+ALTER TABLE t1 DROP COLUMN j;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 DROP COLUMN j;
+NOTICE:  DB node id: 1 statement: ALTER TABLE t1 DROP COLUMN j;
+ALTER TABLE
+-- Make sure cache is not used
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+END;
+NOTICE:  DB node id: 1 statement: END;
+NOTICE:  DB node id: 0 statement: END;
+COMMIT
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+-- Make sure cache is used
+SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+--
+-- ALTER TABLE is executed on another session case
+--
+-- Make sure to create cache
+SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+ALTER TABLE t1 ADD COLUMN j INT;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 ADD COLUMN j INT;
+NOTICE:  DB node id: 1 statement: ALTER TABLE t1 ADD COLUMN j INT;
+ALTER TABLE
+-- Make sure this does not access cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+ALTER TABLE t1 DROP COLUMN j;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 DROP COLUMN j;
+NOTICE:  DB node id: 1 statement: ALTER TABLE t1 DROP COLUMN j;
+ALTER TABLE
+--
+-- Testing ALTER DATABASE
+--
+ALTER TABLE t1 ADD COLUMN j INT;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 ADD COLUMN j INT;
+NOTICE:  DB node id: 1 statement: ALTER TABLE t1 ADD COLUMN j INT;
+ALTER TABLE
+-- create taget database
+create database test2;
+NOTICE:  DB node id: 0 statement: create database test2;
+NOTICE:  DB node id: 1 statement: create database test2;
+CREATE DATABASE
+-- create cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+ALTER DATABASE test2 RESET ALL;
+NOTICE:  DB node id: 0 statement: ALTER DATABASE test2 RESET ALL;
+NOTICE:  DB node id: 1 statement: ALTER DATABASE test2 RESET ALL;
+ALTER DATABASE
+-- Make sure cache is not used
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+-- explicit transaction case
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+NOTICE:  DB node id: 1 statement: BEGIN;
+BEGIN
+ALTER DATABASE test2 RESET ALL;
+NOTICE:  DB node id: 0 statement: ALTER DATABASE test2 RESET ALL;
+NOTICE:  DB node id: 1 statement: ALTER DATABASE test2 RESET ALL;
+ALTER DATABASE
+-- Make sure cache is not used
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+END;
+NOTICE:  DB node id: 1 statement: END;
+NOTICE:  DB node id: 0 statement: END;
+COMMIT
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+-- Make sure cache is used
+SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+--
+-- ALTER DATABASE is executed on another session case
+--
+-- Make sure to create cache
+SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+ALTER DATABASE test2 RESET ALL;
+NOTICE:  DB node id: 0 statement: ALTER DATABASE test2 RESET ALL;
+NOTICE:  DB node id: 1 statement: ALTER DATABASE test2 RESET ALL;
+ALTER DATABASE
+-- Make sure this does not access cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
diff --git a/src/test/regression/tests/006.memqcache/expected.s b/src/test/regression/tests/006.memqcache/expected.s
new file mode 100644 (file)
index 0000000..00e3bc9
--- /dev/null
@@ -0,0 +1,569 @@
+--
+-- testing an effect on a row level security enabled table and SET ROLE
+--
+CREATE TABLE users (user_name TEXT, data TEXT);
+NOTICE:  DB node id: 0 statement: CREATE TABLE users (user_name TEXT, data TEXT);
+CREATE TABLE
+INSERT INTO users VALUES('foo', 'foodata');
+NOTICE:  DB node id: 0 statement: INSERT INTO users VALUES('foo', 'foodata');
+INSERT 0 1
+INSERT INTO users VALUES('bar', 'bardata');
+NOTICE:  DB node id: 0 statement: INSERT INTO users VALUES('bar', 'bardata');
+INSERT 0 1
+ALTER TABLE users ENABLE ROW LEVEL SECURITY;
+NOTICE:  DB node id: 0 statement: ALTER TABLE users ENABLE ROW LEVEL SECURITY;
+ALTER TABLE
+CREATE POLICY user_policy ON users USING (user_name = CURRENT_USER);
+NOTICE:  DB node id: 0 statement: CREATE POLICY user_policy ON users USING (user_name = CURRENT_USER);
+CREATE POLICY
+GRANT SELECT ON users TO foo;
+NOTICE:  DB node id: 0 statement: GRANT SELECT ON users TO foo;
+GRANT
+GRANT SELECT ON users TO bar;
+NOTICE:  DB node id: 0 statement: GRANT SELECT ON users TO bar;
+GRANT
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- run SELECT as foo. Only user_name = 'foo' data expected.
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ user_name |  data   
+-----------+---------
+ foo       | foodata
+(1 row)
+
+RESET ROLE;
+NOTICE:  DB node id: 0 statement: RESET ROLE;
+RESET
+SET ROLE TO bar;
+NOTICE:  DB node id: 0 statement: SET ROLE TO bar;
+SET
+-- run SELECT as bar. Only user_name = 'bar' data expected.
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ user_name |  data   
+-----------+---------
+ bar       | bardata
+(1 row)
+
+--
+-- testing row security with row_security = off
+--
+SET ROW_SECURITY TO off;
+NOTICE:  DB node id: 0 statement: SET ROW_SECURITY TO off;
+SET
+-- Error expected
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ERROR:  query would be affected by row-level security policy for table "users"
+--
+-- testing SET ROLE
+--
+CREATE TABLE footable(t text);
+NOTICE:  DB node id: 0 statement: CREATE TABLE footable(t text);
+CREATE TABLE
+INSERT INTO footable VALUES('foo');
+NOTICE:  DB node id: 0 statement: INSERT INTO footable VALUES('foo');
+INSERT 0 1
+GRANT SELECT ON footable TO foo;
+NOTICE:  DB node id: 0 statement: GRANT SELECT ON footable TO foo;
+GRANT
+GRANT INSERT ON footable TO foo;
+NOTICE:  DB node id: 0 statement: GRANT INSERT ON footable TO foo;
+GRANT
+GRANT UPDATE ON footable TO foo;
+NOTICE:  DB node id: 0 statement: GRANT UPDATE ON footable TO foo;
+GRANT
+GRANT DELETE ON footable TO foo;
+NOTICE:  DB node id: 0 statement: GRANT DELETE ON footable TO foo;
+GRANT
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t  
+-----
+ foo
+(1 row)
+
+SET ROLE TO bar;
+NOTICE:  DB node id: 0 statement: SET ROLE TO bar;
+SET
+-- run SELECT as bar. Permission denied is expected.
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+ERROR:  permission denied for table footable
+--
+-- testing SESSION AUTHORIZATION
+--
+SET SESSION AUTHORIZATION bar;
+NOTICE:  DB node id: 0 statement: SET SESSION AUTHORIZATION bar;
+SET
+-- run SELECT as bar. Permission denied is expected.
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+ERROR:  permission denied for table footable
+--
+-- testing SET ROLE. Make sure that query cache is not
+-- created.
+--
+-- create cache
+SELECT * FROM footable;
+  t  
+-----
+ foo
+(1 row)
+
+-- change role
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- run SELECT as foo to make sure that cache is not used.
+-- If query cache was created we will NOT see
+-- "NOTICE: DB node id: 1 statement: SELECT ..."
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t  
+-----
+ foo
+(1 row)
+
+-- Modify footable to see cache invalidation works even after SET ROLE.
+INSERT INTO footable VALUES ('foo1');
+NOTICE:  DB node id: 0 statement: INSERT INTO footable VALUES ('foo1');
+INSERT 0 1
+-- restore ROLE
+RESET ROLE;
+NOTICE:  DB node id: 0 statement: RESET ROLE;
+RESET
+-- Make sure cache was invalidated.
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+(2 rows)
+
+--
+-- explicit transaction case
+--
+-- create cache
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+(2 rows)
+
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+(2 rows)
+
+-- change role
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+BEGIN
+-- run SELECT as foo to make sure that cache is not used.
+-- If query cache was created we will NOT see
+-- "NOTICE: DB node id: 1 statement: SELECT ..."
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+(2 rows)
+
+-- Modify footable to see cache invalidation works even after SET ROLE.
+INSERT INTO footable VALUES ('foo2');
+NOTICE:  DB node id: 0 statement: INSERT INTO footable VALUES ('foo2');
+INSERT 0 1
+END;
+NOTICE:  DB node id: 0 statement: END;
+COMMIT
+-- Make sure cache was invalidated.
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+--
+-- explicit transaction abort case
+--
+-- create cache
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+-- change role
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+BEGIN
+-- run SELECT as foo to make sure that cache is not used.
+-- If query cache was created we will NOT see
+-- "NOTICE: DB node id: 0 statement: SELECT ..."
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+-- Modify footable to see cache invalidation works even after SET ROLE.
+INSERT INTO footable VALUES ('foo3');
+NOTICE:  DB node id: 0 statement: INSERT INTO footable VALUES ('foo3');
+INSERT 0 1
+SELECT * FROM footable;
+NOTICE:  DB node id: 0 statement: SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+ foo3
+(4 rows)
+
+ABORT;
+NOTICE:  DB node id: 0 statement: ABORT;
+ROLLBACK
+-- Make sure we don't see 'foo3' row.
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+-- Make sure we don't see 'foo3' row.
+SELECT * FROM footable;
+  t   
+------
+ foo
+ foo1
+ foo2
+(3 rows)
+
+--
+-- Testing REVOKE
+--
+-- create cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+-- REVOKE
+REVOKE SELECT ON t1 FROM foo;
+NOTICE:  DB node id: 0 statement: REVOKE SELECT ON t1 FROM foo;
+REVOKE
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- Make sure foo cannot SELECT t1
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ERROR:  permission denied for table t1
+RESET ROLE;
+NOTICE:  DB node id: 0 statement: RESET ROLE;
+RESET
+-- GRANT again
+GRANT SELECT ON t1 TO foo;
+NOTICE:  DB node id: 0 statement: GRANT SELECT ON t1 TO foo;
+GRANT
+-- explicit transaction case
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+BEGIN
+-- REVOKE
+REVOKE SELECT ON t1 FROM foo;
+NOTICE:  DB node id: 0 statement: REVOKE SELECT ON t1 FROM foo;
+REVOKE
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- Make sure foo cannot SELECT t1
+-- (thus REVOKE will be rollbacked )
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ERROR:  permission denied for table t1
+END;
+NOTICE:  DB node id: 0 statement: END;
+ROLLBACK
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- because REVOKE is rolled back, foo should be able to access t1
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+--
+-- REVOKE is executed on another session case
+--
+-- Make sure to create cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+-- execute REVOKE
+REVOKE SELECT ON t1 FROM foo
+NOTICE:  DB node id: 0 statement: REVOKE SELECT ON t1 FROM foo
+REVOKE
+-- Make sure this does not access cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+--
+-- ALTER ROLE BYPASSRLS case
+--
+ALTER ROLE foo BYPASSRLS;
+NOTICE:  DB node id: 0 statement: ALTER ROLE foo BYPASSRLS;
+ALTER ROLE
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- expect to ignore cache and result is all rows
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ user_name |  data   
+-----------+---------
+ foo       | foodata
+ bar       | bardata
+(2 rows)
+
+RESET ROLE;
+NOTICE:  DB node id: 0 statement: RESET ROLE;
+RESET
+ALTER ROLE foo NOBYPASSRLS;
+NOTICE:  DB node id: 0 statement: ALTER ROLE foo NOBYPASSRLS;
+ALTER ROLE
+SET ROLE TO foo;
+NOTICE:  DB node id: 0 statement: SET ROLE TO foo;
+SET
+-- expect to ignore cache and result is one row
+SELECT * FROM users;
+NOTICE:  DB node id: 0 statement: SELECT * FROM users;
+ user_name |  data   
+-----------+---------
+ foo       | foodata
+(1 row)
+
+--
+-- Testing ALTER TABLE
+--
+-- create cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+ALTER TABLE t1 ADD COLUMN j INT;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 ADD COLUMN j INT;
+ALTER TABLE
+-- Make sure cache is not used
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+-- explicit transaction case
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+BEGIN
+ALTER TABLE t1 DROP COLUMN j;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 DROP COLUMN j;
+ALTER TABLE
+-- Make sure cache is not used
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+END;
+NOTICE:  DB node id: 0 statement: END;
+COMMIT
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+-- Make sure cache is used
+SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+--
+-- ALTER TABLE is executed on another session case
+--
+-- Make sure to create cache
+SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+SELECT * FROM t1;
+ i 
+---
+ 2
+(1 row)
+
+ALTER TABLE t1 ADD COLUMN j INT;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 ADD COLUMN j INT;
+ALTER TABLE
+-- Make sure this does not access cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+ALTER TABLE t1 DROP COLUMN j;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 DROP COLUMN j;
+ALTER TABLE
+--
+-- Testing ALTER DATABASE
+--
+ALTER TABLE t1 ADD COLUMN j INT;
+NOTICE:  DB node id: 0 statement: ALTER TABLE t1 ADD COLUMN j INT;
+ALTER TABLE
+-- create taget database
+create database test2;
+NOTICE:  DB node id: 0 statement: create database test2;
+CREATE DATABASE
+-- create cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+ALTER DATABASE test2 RESET ALL;
+NOTICE:  DB node id: 0 statement: ALTER DATABASE test2 RESET ALL;
+ALTER DATABASE
+-- Make sure cache is not used
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+-- explicit transaction case
+BEGIN;
+NOTICE:  DB node id: 0 statement: BEGIN;
+BEGIN
+ALTER DATABASE test2 RESET ALL;
+NOTICE:  DB node id: 0 statement: ALTER DATABASE test2 RESET ALL;
+ALTER DATABASE
+-- Make sure cache is not used
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+END;
+NOTICE:  DB node id: 0 statement: END;
+COMMIT
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+-- Make sure cache is used
+SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+--
+-- ALTER DATABASE is executed on another session case
+--
+-- Make sure to create cache
+SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
+ALTER DATABASE test2 RESET ALL;
+NOTICE:  DB node id: 0 statement: ALTER DATABASE test2 RESET ALL;
+ALTER DATABASE
+-- Make sure this does not access cache
+SELECT * FROM t1;
+NOTICE:  DB node id: 0 statement: SELECT * FROM t1;
+ i | j 
+---+---
+ 2 |  
+(1 row)
+
diff --git a/src/test/regression/tests/006.memqcache/revoke1.data b/src/test/regression/tests/006.memqcache/revoke1.data
new file mode 100644 (file)
index 0000000..1a2e56d
--- /dev/null
@@ -0,0 +1,73 @@
+# Testing REVOKE
+# create cache
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# revoke
+'P'    ""      "REVOKE SELECT ON t1 FROM foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# change role
+'P'    ""      "SET ROLE TO foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# Make sure foo cannot SELECT t1
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "RESET ROLE"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# GRANT again
+'P'    ""      "GRANT SELECT ON t1 TO foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# explicit transaction case
+'P'    ""      "BEGIN"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# execute REVOKE
+'P'    ""      "REVOKE SELECT ON t1 FROM foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# change role
+'P'    ""      "SET ROLE TO foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# Make sure foo cannot SELECT t1
+# (thus REVOKE will be rollbacked )
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "END"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# because REVOKE was rolled back, foo should be able to access t1
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/revoke2.data b/src/test/regression/tests/006.memqcache/revoke2.data
new file mode 100644 (file)
index 0000000..5c82206
--- /dev/null
@@ -0,0 +1,19 @@
+# Testing REVOKE is executed on another session case
+# Make sure to create cache (sync needed)
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# execute REVOKE
+'P'    ""      "REVOKE SELECT ON t1 FROM foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/revoke3.data b/src/test/regression/tests/006.memqcache/revoke3.data
new file mode 100644 (file)
index 0000000..864e3a1
--- /dev/null
@@ -0,0 +1,8 @@
+# Testing REVOKE is executed on another session case
+# Make sure this does not access cache
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/row_security.data b/src/test/regression/tests/006.memqcache/row_security.data
new file mode 100644 (file)
index 0000000..54ecb31
--- /dev/null
@@ -0,0 +1,42 @@
+# run SELECT as foo. It is expected 0 row returned.
+'P'    ""      "SET ROLE TO foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "SELECT * FROM users WHERE user_name = 'bar'"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "RESET ROLE"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# run SELECT as bar. It is expected 0 row returned.
+'P'    ""      "SET ROLE TO bar"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "SELECT * FROM users WHERE user_name = 'foo'"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+#
+# testing row security with row_security = off
+# Error expected
+#
+'P'    ""      "SET row_security TO off"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "SELECT * FROM users"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/session_authorization.data b/src/test/regression/tests/006.memqcache/session_authorization.data
new file mode 100644 (file)
index 0000000..c9b3d7a
--- /dev/null
@@ -0,0 +1,19 @@
+# create query cache
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# SET SESSION_AUTHORIZATION
+'P'    ""      "SET SESSION AUTHORIZATION bar"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# run SELECT as bar. Permission denied is expected.
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/set_role1.data b/src/test/regression/tests/006.memqcache/set_role1.data
new file mode 100644 (file)
index 0000000..fe049f2
--- /dev/null
@@ -0,0 +1,19 @@
+# create query cache
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# SET ROLE
+'P'    ""      "SET ROLE TO bar"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# run SELECT as bar. Permission denied is expected.
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/set_role2.data b/src/test/regression/tests/006.memqcache/set_role2.data
new file mode 100644 (file)
index 0000000..508fa2e
--- /dev/null
@@ -0,0 +1,39 @@
+# create query cache
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# SET ROLE
+'P'    ""      "SET ROLE TO foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# run SELECT as foo to make sure that cache is not used.
+# If query cache was created we will NOT see
+# "NOTICE: DB node id: 1 statement: SELECT ..."
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# Modify footable to see cache invalidation works even after SET ROLE.
+'P'    ""      "UPDATE footable SET t = 'foo1' WHERE t = 'foo1'"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# restore ROLE
+'P'    ""      "RESET ROLE"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# Make sure cache was invalidated.
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/set_role3.data b/src/test/regression/tests/006.memqcache/set_role3.data
new file mode 100644 (file)
index 0000000..b637bc8
--- /dev/null
@@ -0,0 +1,44 @@
+# explicit transaction case
+# create query cache
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# change role
+'P'    ""      "SET ROLE TO foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "BEGIN"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# run SELECT as foo to make sure that cache is not used.
+# If query cache was created we will NOT see
+# "NOTICE: DB node id: 1 statement: SELECT ..."
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# Modify footable to see cache invalidation works even after SET ROLE.
+'P'    ""      "INSERT INTO footable VALUES ('foo3')"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'P'    ""      "END"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# undo the INSERT to not disturb subsequent tests
+'Q'    "DELETE FROM footable WHERE t = 'foo3'"
+'Y'
+'X'
diff --git a/src/test/regression/tests/006.memqcache/set_role4.data b/src/test/regression/tests/006.memqcache/set_role4.data
new file mode 100644 (file)
index 0000000..e23850f
--- /dev/null
@@ -0,0 +1,90 @@
+# explicit transaction case
+# create cache
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+# change role
+'P'    ""      "SET ROLE TO foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+
+'P'    ""      "BEGIN"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+
+# run SELECT as foo to make sure that cache is not used.
+# If query cache was created we will NOT see
+# "NOTICE: DB node id: 0 statement: SELECT ..."
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+
+# Modify footable to see cache invalidation works even after SET ROLE.
+'P'    ""      "INSERT INTO footable VALUES ('foo3')"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+
+'P'    ""      "ABORT"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+
+# Make sure we don't see 'foo3' row.
+'P'    ""      "SELECT * FROM t1"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+
+# change role
+'P'    ""      "SET ROLE TO foo"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+
+'P'    ""      "BEGIN"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+
+# run SELECT as foo to make sure that cache is not used.
+# If query cache was created we will NOT see
+# "NOTICE: DB node id: 0 statement: SELECT ..."
+'P'    ""      "SELECT * FROM footable"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+
+# Modify footable to see cache invalidation works even after SET ROLE.
+'P'    ""      "INSERT INTO footable VALUES ('foo3')"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+
+'P'    ""      "ABORT"
+'B'    ""      ""      0       0       0
+'E'    ""      0
+'S'
+'Y'
+'X'
index 486b75887c5261c16b6691996478744a55a5a6e5..05f9e26a1496de08e67e5d7391cf6ae56625efd8 100755 (executable)
@@ -9,6 +9,15 @@ source $TESTLIBS
 TESTDIR=testdir
 
 PSQL=$PGBIN/psql
+PGPROTO=$PGPOOL_INSTALL_DIR/bin/pgproto
+
+# remove error/notice details (message and so on) from
+# ErrorResponse or NoticeResponse messages.
+# used for pgproto.
+function del_details_from_error
+{
+    cat|sed -e '/ErrorResponse/s/ F .*//' -e '/NoticeResponse/s/ F .*$//'
+}
 
 for mode in s r n
 do
@@ -114,9 +123,336 @@ EOF
                exit 1
        fi
 
+       ./shutdownall
+       echo "backend_weight1 = 0" >> etc/pgpool.conf
+       echo "notice_per_node_statement = on" >> etc/pgpool.conf
+       ./startall
+       wait_for_pgpool_startup
+
+       createuser foo
+       createuser bar
+
+       $PSQL -a test >> result 2>&1 <<EOF
+--
+-- testing an effect on a row level security enabled table and SET ROLE
+--
+CREATE TABLE users (user_name TEXT, data TEXT);
+INSERT INTO users VALUES('foo', 'foodata');
+INSERT INTO users VALUES('bar', 'bardata');
+ALTER TABLE users ENABLE ROW LEVEL SECURITY;
+CREATE POLICY user_policy ON users USING (user_name = CURRENT_USER);
+GRANT SELECT ON users TO foo;
+GRANT SELECT ON users TO bar;
+SET ROLE TO foo;
+-- run SELECT as foo. Only user_name = 'foo' data expected.
+SELECT * FROM users;
+RESET ROLE;
+SET ROLE TO bar;
+-- run SELECT as bar. Only user_name = 'bar' data expected.
+SELECT * FROM users;
+EOF
+
+#echo '=== extended query test for row security ===' >> result
+#      $PGPROTO -d test -f ../row_security.data |& del_details_from_error >> result
+
+       $PSQL -a -U foo test >> result 2>&1 <<EOF
+--
+-- testing row security with row_security = off
+--
+SET ROW_SECURITY TO off;
+-- Error expected
+SELECT * FROM users;
+EOF
+
+       $PSQL -a test >> result 2>&1 <<EOF
+--
+-- testing SET ROLE
+--
+CREATE TABLE footable(t text);
+INSERT INTO footable VALUES('foo');
+GRANT SELECT ON footable TO foo;
+GRANT INSERT ON footable TO foo;
+GRANT UPDATE ON footable TO foo;
+GRANT DELETE ON footable TO foo;
+SELECT * FROM footable;
+SET ROLE TO bar;
+-- run SELECT as bar. Permission denied is expected.
+SELECT * FROM footable;
+EOF
+
+#echo '==== extended query test for "testing SET ROLE" above ===' >> result
+#      $PGPROTO -d test -f ../set_role1.data |& del_details_from_error >> result
+
+       $PSQL -a test >> result 2>&1 <<EOF
+--
+-- testing SESSION AUTHORIZATION
+--
+SET SESSION AUTHORIZATION bar;
+-- run SELECT as bar. Permission denied is expected.
+SELECT * FROM footable;
+EOF
+
+#echo '=== extended query test for "testing SESSION AUTHORIZATION" above ===' >> result
+#      $PGPROTO -d test -f ../session_authorization.data |& del_details_from_error >> result
+
+       $PSQL -a test >> result 2>&1 <<EOF
+--
+-- testing SET ROLE. Make sure that query cache is not
+-- created.
+--
+-- create cache
+SELECT * FROM footable;
+-- change role
+SET ROLE TO foo;
+-- run SELECT as foo to make sure that cache is not used.
+-- If query cache was created we will NOT see
+-- "NOTICE: DB node id: 1 statement: SELECT ..."
+SELECT * FROM footable;
+-- Modify footable to see cache invalidation works even after SET ROLE.
+INSERT INTO footable VALUES ('foo1');
+-- restore ROLE
+RESET ROLE;
+-- Make sure cache was invalidated.
+SELECT * FROM footable;
+EOF
+
+#echo '=== extended query test for "testing SET ROLE" above ===' >> result
+#      $PGPROTO -d test -f ../set_role2.data |& del_details_from_error >> result
+
+               $PSQL -a test >> result 2>&1 <<EOF
+--
+-- explicit transaction case
+--
+-- create cache
+SELECT * FROM footable;
+SELECT * FROM footable;
+-- change role
+SET ROLE TO foo;
+BEGIN;
+-- run SELECT as foo to make sure that cache is not used.
+-- If query cache was created we will NOT see
+-- "NOTICE: DB node id: 1 statement: SELECT ..."
+SELECT * FROM footable;
+-- Modify footable to see cache invalidation works even after SET ROLE.
+INSERT INTO footable VALUES ('foo2');
+END;
+EOF
+               $PSQL -a test >> result 2>&1 <<EOF
+-- Make sure cache was invalidated.
+SELECT * FROM footable;
+EOF
+
+#echo '=== extended query test for "explicit transaction case" above ===' >> result
+#      $PGPROTO -d test -f ../set_role3.data |& del_details_from_error >> result
+
+#              $PSQL -a test >> result 2>&1 <<EOF
+#-- Make sure cache was invalidated.
+#SELECT * FROM footable;
+#EOF
+
+               $PSQL -a test >> result 2>&1 <<EOF
+--
+-- explicit transaction abort case
+--
+-- create cache
+SELECT * FROM footable;
+SELECT * FROM footable;
+-- change role
+SET ROLE TO foo;
+BEGIN;
+-- run SELECT as foo to make sure that cache is not used.
+-- If query cache was created we will NOT see
+-- "NOTICE: DB node id: 0 statement: SELECT ..."
+SELECT * FROM footable;
+-- Modify footable to see cache invalidation works even after SET ROLE.
+INSERT INTO footable VALUES ('foo3');
+SELECT * FROM footable;
+ABORT;
+EOF
+               $PSQL -a test >> result 2>&1 <<EOF
+-- Make sure we don't see 'foo3' row.
+SELECT * FROM footable;
+EOF
+
+#echo '=== extended query test for "explicit transaction abort case" above ===' >> result
+#      $PGPROTO -d test -f ../set_role4.data |& del_details_from_error >> result
+
+               $PSQL -a test >> result 2>&1 <<EOF
+-- Make sure we don't see 'foo3' row.
+SELECT * FROM footable;
+EOF
+               $PSQL -a test >> result 2>&1 <<EOF
+--
+-- Testing REVOKE
+--
+-- create cache
+SELECT * FROM t1;
+-- REVOKE
+REVOKE SELECT ON t1 FROM foo;
+SET ROLE TO foo;
+-- Make sure foo cannot SELECT t1
+SELECT * FROM t1;
+RESET ROLE;
+-- GRANT again
+GRANT SELECT ON t1 TO foo;
+EOF
+               $PSQL -a test >> result 2>&1 <<EOF
+-- explicit transaction case
+BEGIN;
+-- REVOKE
+REVOKE SELECT ON t1 FROM foo;
+SET ROLE TO foo;
+-- Make sure foo cannot SELECT t1
+-- (thus REVOKE will be rollbacked )
+SELECT * FROM t1;
+END;
+SET ROLE TO foo;
+-- because REVOKE is rolled back, foo should be able to access t1
+SELECT * FROM t1;
+EOF
+
+#echo '=== extended query test for "Tesing REVOKE" and "explicit transaction case" above ===' >> result
+#      $PGPROTO -d test -f ../revoke1.data |& del_details_from_error >> result
+
+               $PSQL -a test >> result 2>&1 <<EOF
+--
+-- REVOKE is executed on another session case
+--
+-- Make sure to create cache
+SELECT * FROM t1;
+SELECT * FROM t1;
+-- execute REVOKE
+REVOKE SELECT ON t1 FROM foo
+EOF
+               $PSQL -a test >> result 2>&1 <<EOF
+-- Make sure this does not access cache
+SELECT * FROM t1;
+EOF
+
+#echo '=== extended query test for "REVOKE is executed on another session case" above ===' >> result
+#      $PGPROTO -d test -f ../revoke2.data |& del_details_from_error >> result
+#      $PGPROTO -d test -f ../revoke3.data |& del_details_from_error >> result
+
+               $PSQL -a test >> result 2>&1 <<EOF
+--
+-- ALTER ROLE BYPASSRLS case
+--
+ALTER ROLE foo BYPASSRLS;
+SET ROLE TO foo;
+-- expect to ignore cache and result is all rows
+SELECT * FROM users;
+RESET ROLE;
+ALTER ROLE foo NOBYPASSRLS;
+SET ROLE TO foo;
+-- expect to ignore cache and result is one row
+SELECT * FROM users;
+EOF
+
+#echo '=== extended query test for "ALTER ROLE BYPASSRLS case" case ===' >> result
+#      $PGPROTO -d test -f ../alter_role.data |& del_details_from_error >> result
+
+               $PSQL -a test >> result 2>&1 <<EOF
+--
+-- Testing ALTER TABLE
+--
+-- create cache
+SELECT * FROM t1;
+ALTER TABLE t1 ADD COLUMN j INT;
+-- Make sure cache is not used
+SELECT * FROM t1;
+EOF
+               $PSQL -a test >> result 2>&1 <<EOF
+-- explicit transaction case
+BEGIN;
+ALTER TABLE t1 DROP COLUMN j;
+-- Make sure cache is not used
+SELECT * FROM t1;
+END;
+SELECT * FROM t1;
+-- Make sure cache is used
+SELECT * FROM t1;
+EOF
+
+#echo '=== extended query test for "Testing ALTER TABLE and explicit transaction" case ===' >> result
+#      $PGPROTO -d test -f ../alter_table1.data |& del_details_from_error >> result
+
+               $PSQL -a test >> result 2>&1 <<EOF
+--
+-- ALTER TABLE is executed on another session case
+--
+-- Make sure to create cache
+SELECT * FROM t1;
+SELECT * FROM t1;
+ALTER TABLE t1 ADD COLUMN j INT;
+EOF
+               $PSQL -a test >> result 2>&1 <<EOF
+-- Make sure this does not access cache
+SELECT * FROM t1;
+ALTER TABLE t1 DROP COLUMN j;
+EOF
+
+#echo '=== extended query test for "ALTER TABLE is executed on another session" case ===' >> result
+#      $PGPROTO -d test -f ../alter_table2.data |& del_details_from_error >> result
+#      $PGPROTO -d test -f ../alter_table3.data |& del_details_from_error >> result
+
+               $PSQL -a test >> result 2>&1 <<EOF
+--
+-- Testing ALTER DATABASE
+--
+ALTER TABLE t1 ADD COLUMN j INT;
+-- create taget database
+create database test2;
+-- create cache
+SELECT * FROM t1;
+ALTER DATABASE test2 RESET ALL;
+-- Make sure cache is not used
+SELECT * FROM t1;
+EOF
+               $PSQL -a test >> result 2>&1 <<EOF
+-- explicit transaction case
+BEGIN;
+ALTER DATABASE test2 RESET ALL;
+-- Make sure cache is not used
+SELECT * FROM t1;
+END;
+SELECT * FROM t1;
+-- Make sure cache is used
+SELECT * FROM t1;
+EOF
+#echo '=== extended query test for "ALTER DATABSE and explicit transaction" case ===' >> result
+#      $PGPROTO -d test -f ../alter_database1.data |& del_details_from_error >> result
+               $PSQL -a test >> result 2>&1 <<EOF
+--
+-- ALTER DATABASE is executed on another session case
+--
+-- Make sure to create cache
+SELECT * FROM t1;
+SELECT * FROM t1;
+ALTER DATABASE test2 RESET ALL;
+EOF
+               $PSQL -a test >> result 2>&1 <<EOF
+-- Make sure this does not access cache
+SELECT * FROM t1;
+EOF
+
+#echo '=== extended query test for "ALTER DATABASE is executed on another session" case ===' >> result
+#      $PGPROTO -d test -f ../alter_database2.data |& del_details_from_error >> result
+#      $PGPROTO -d test -f ../alter_database3.data |& del_details_from_error >> result
+
        ./shutdownall
 
        cd ..
+
+       log=/tmp/diff
+       EXPECTED=expected.$mode
+       diff -c $EXPECTED testdir/result > $log
+       if [ $? != 0 ];then
+           echo "test failed in mode: $mode"
+           cat $log
+           rm $log
+           exit 1
+       fi
+       rm $log
 done
 
 exit 0
index 27e745ba473d19a50f91f92aff17b4eb5c2aa94c..b9c2cef87631fe772ec926d7cff0c5be41a1fe1e 100644 (file)
@@ -3,7 +3,7 @@
  * pgpool: a language independent connection pool server for PostgreSQL
  * written by Tatsuo Ishii
  *
- * Copyright (c) 2003-2023     PgPool Global Development Group
+ * Copyright (c) 2003-2024     PgPool Global Development Group
  *
  * Permission to use, copy, modify, and distribute this software and
  * its documentation for any purpose and without fee is hereby
@@ -46,6 +46,8 @@ static bool is_system_catalog(char *table_name);
 static bool temp_table_walker(Node *node, void *context);
 static bool unlogged_table_walker(Node *node, void *context);
 static bool view_walker(Node *node, void *context);
+static bool row_security_enabled(char *table_name);
+static bool row_security_enabled_walker(Node *node, void *context);
 static bool is_temp_table(char *table_name);
 static bool insertinto_or_locking_clause_walker(Node *node, void *context);
 static bool is_immutable_function(char *fname);
@@ -179,6 +181,25 @@ pool_has_view(Node *node)
        return ctx.has_view;
 }
 
+/*
+ * Return true if this SELECT has a row security enabled table.
+ */
+bool
+pool_has_row_security(Node *node)
+{
+
+       SelectContext ctx;
+
+       if (!IsA(node, SelectStmt))
+               return false;
+
+       ctx.row_security = false;
+
+       raw_expression_tree_walker(node, row_security_enabled_walker, &ctx);
+
+       return ctx.row_security;
+}
+
 /*
  * Return true if this SELECT has INSERT INTO or FOR SHARE or FOR UPDATE.
  */
@@ -552,6 +573,36 @@ view_walker(Node *node, void *context)
        return raw_expression_tree_walker(node, view_walker, context);
 }
 
+/*
+ * Walker function to find a row security enabled table.
+ */
+static bool
+row_security_enabled_walker(Node *node, void *context)
+{
+       SelectContext *ctx = (SelectContext *) context;
+       char       *relname;
+
+       if (node == NULL)
+               return false;
+
+       if (IsA(node, RangeVar))
+       {
+               RangeVar   *rgv = (RangeVar *) node;
+
+               relname = make_table_name_from_rangevar(rgv);
+
+               ereport(DEBUG1,
+                               (errmsg("row secuirty walker. checking relation \"%s\"", relname)));
+
+               if (row_security_enabled(relname))
+               {
+                       ctx->row_security = true;
+                       return false;
+               }
+       }
+       return raw_expression_tree_walker(node, row_security_enabled_walker, context);
+}
+
 /*
  * Determine whether table_name is a system catalog or not.
  */
@@ -909,6 +960,60 @@ is_view(char *table_name)
        return result;
 }
 
+/*
+ * Returns true if table_name enables row security.
+ */
+static bool
+row_security_enabled(char *table_name)
+{
+/*
+ * Query to know if the target table enables row security. This is valid for
+ * PostgreSQL 9.5 or later. Remember that to_regclass() is available in
+ * PostgreSQL 9.4 or later and we can use to_regclass() unconditionally.
+ */
+#define ISROWSECURITYQUERY "SELECT count(*) FROM pg_catalog.pg_class AS c WHERE c.oid = pg_catalog.to_regclass('%s') AND c.relrowsecurity"
+
+       static POOL_RELCACHE * relcache;
+       POOL_CONNECTION_POOL *backend;
+       bool            result;
+       char       *query;
+
+       if (table_name == NULL)
+               return false;
+
+       backend = pool_get_session_context(false)->backend;
+
+       /*
+        * Check backend version. PostgreSQL 9.5 or later have relrowsecurity
+        * column.
+        */
+       if (Pgversion(backend)->major < 95)
+               return false;
+
+       query = ISROWSECURITYQUERY;
+
+       if (!relcache)
+       {
+               relcache = pool_create_relcache(pool_config->relcache_size, query,
+                                                                               int_register_func, int_unregister_func,
+                                                                               false);
+               if (relcache == NULL)
+               {
+                       ereport(WARNING,
+                                       (errmsg("unable to create relcache, while checking for row security")));
+                       return false;
+               }
+
+       }
+
+       /*
+        * Search relcache.
+        */
+       result = pool_search_relcache(relcache, backend, table_name) == 0 ? false : true;
+       return result;
+}
+
+
 /*
  * Judge if we have pgpool_regclass or not.
  */