Fix to get view definition string with correct column names (#26)

Previously, a query string returned from pg_ivm_get_querydef
did not include column names specified when IMMV was defined
by create_immv. This caused failures in maintenance of MIN/MAX
aggregate views whose columns had alias names.

It is fixed by rewriting the result column name in the parse tree
using the view's tuple descriptor prior to calling pg_get_querydef
for PG15 or higher, or specifying the tuple descriptor to
get_query_def for PG14 or earlier.
This commit is contained in:
Yugo Nagata 2022-09-29 22:24:28 +09:00 committed by GitHub
parent 79d4b13ba1
commit 4c6016999d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 81 additions and 45 deletions

View file

@ -383,7 +383,7 @@ SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
ROLLBACK; ROLLBACK;
-- support MIN(), MAX() aggregate functions -- support MIN(), MAX() aggregate functions
BEGIN; BEGIN;
SELECT create_immv('mv_ivm_min_max', 'SELECT i, MIN(j), MAX(j) FROM mv_base_a GROUP BY i'); SELECT create_immv('mv_ivm_min_max(i, min_j, max_j)', 'SELECT i, MIN(j), MAX(j) FROM mv_base_a GROUP BY i');
NOTICE: created index "mv_ivm_min_max_index" on immv "mv_ivm_min_max" NOTICE: created index "mv_ivm_min_max_index" on immv "mv_ivm_min_max"
create_immv create_immv
------------- -------------
@ -391,13 +391,13 @@ NOTICE: created index "mv_ivm_min_max_index" on immv "mv_ivm_min_max"
(1 row) (1 row)
SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3; SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
i | min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__ i | min_j | max_j | __ivm_count_min_j__ | __ivm_count_max_j__ | __ivm_count__
---+-----+-----+-------------------+-------------------+--------------- ---+-------+-------+---------------------+---------------------+---------------
1 | 10 | 10 | 1 | 1 | 1 1 | 10 | 10 | 1 | 1 | 1
2 | 20 | 20 | 1 | 1 | 1 2 | 20 | 20 | 1 | 1 | 1
3 | 30 | 30 | 1 | 1 | 1 3 | 30 | 30 | 1 | 1 | 1
4 | 40 | 40 | 1 | 1 | 1 4 | 40 | 40 | 1 | 1 | 1
5 | 50 | 50 | 1 | 1 | 1 5 | 50 | 50 | 1 | 1 | 1
(5 rows) (5 rows)
INSERT INTO mv_base_a VALUES INSERT INTO mv_base_a VALUES
@ -407,61 +407,61 @@ INSERT INTO mv_base_a VALUES
(4,41), (4,42), (4,41), (4,42),
(5,51), (5,52); (5,51), (5,52);
SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3; SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
i | min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__ i | min_j | max_j | __ivm_count_min_j__ | __ivm_count_max_j__ | __ivm_count__
---+-----+-----+-------------------+-------------------+--------------- ---+-------+-------+---------------------+---------------------+---------------
1 | 10 | 12 | 3 | 3 | 3 1 | 10 | 12 | 3 | 3 | 3
2 | 20 | 22 | 3 | 3 | 3 2 | 20 | 22 | 3 | 3 | 3
3 | 30 | 32 | 3 | 3 | 3 3 | 30 | 32 | 3 | 3 | 3
4 | 40 | 42 | 3 | 3 | 3 4 | 40 | 42 | 3 | 3 | 3
5 | 50 | 52 | 3 | 3 | 3 5 | 50 | 52 | 3 | 3 | 3
(5 rows) (5 rows)
DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32)); DELETE FROM mv_base_a WHERE (i,j) IN ((1,10), (2,21), (3,32));
SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3; SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
i | min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__ i | min_j | max_j | __ivm_count_min_j__ | __ivm_count_max_j__ | __ivm_count__
---+-----+-----+-------------------+-------------------+--------------- ---+-------+-------+---------------------+---------------------+---------------
1 | 11 | 12 | 2 | 2 | 2 1 | 11 | 12 | 2 | 2 | 2
2 | 20 | 22 | 2 | 2 | 2 2 | 20 | 22 | 2 | 2 | 2
3 | 30 | 31 | 2 | 2 | 2 3 | 30 | 31 | 2 | 2 | 2
4 | 40 | 42 | 3 | 3 | 3 4 | 40 | 42 | 3 | 3 | 3
5 | 50 | 52 | 3 | 3 | 3 5 | 50 | 52 | 3 | 3 | 3
(5 rows) (5 rows)
ROLLBACK; ROLLBACK;
-- support MIN(), MAX() aggregate functions without GROUP clause -- support MIN(), MAX() aggregate functions without GROUP clause
BEGIN; BEGIN;
SELECT create_immv('mv_ivm_min_max', 'SELECT MIN(j), MAX(j) FROM mv_base_a'); SELECT create_immv('mv_ivm_min_max(min_j, max_j)', 'SELECT MIN(j), MAX(j) FROM mv_base_a');
create_immv create_immv
------------- -------------
1 1
(1 row) (1 row)
SELECT * FROM mv_ivm_min_max; SELECT * FROM mv_ivm_min_max;
min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__ min_j | max_j | __ivm_count_min_j__ | __ivm_count_max_j__ | __ivm_count__
-----+-----+-------------------+-------------------+--------------- -------+-------+---------------------+---------------------+---------------
10 | 50 | 5 | 5 | 5 10 | 50 | 5 | 5 | 5
(1 row) (1 row)
INSERT INTO mv_base_a VALUES INSERT INTO mv_base_a VALUES
(0,0), (6,60), (7,70); (0,0), (6,60), (7,70);
SELECT * FROM mv_ivm_min_max; SELECT * FROM mv_ivm_min_max;
min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__ min_j | max_j | __ivm_count_min_j__ | __ivm_count_max_j__ | __ivm_count__
-----+-----+-------------------+-------------------+--------------- -------+-------+---------------------+---------------------+---------------
0 | 70 | 8 | 8 | 8 0 | 70 | 8 | 8 | 8
(1 row) (1 row)
DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70)); DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
SELECT * FROM mv_ivm_min_max; SELECT * FROM mv_ivm_min_max;
min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__ min_j | max_j | __ivm_count_min_j__ | __ivm_count_max_j__ | __ivm_count__
-----+-----+-------------------+-------------------+--------------- -------+-------+---------------------+---------------------+---------------
10 | 60 | 6 | 6 | 6 10 | 60 | 6 | 6 | 6
(1 row) (1 row)
DELETE FROM mv_base_a; DELETE FROM mv_base_a;
SELECT * FROM mv_ivm_min_max; SELECT * FROM mv_ivm_min_max;
min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__ min_j | max_j | __ivm_count_min_j__ | __ivm_count_max_j__ | __ivm_count__
-----+-----+-------------------+-------------------+--------------- -------+-------+---------------------+---------------------+---------------
| | 0 | 0 | 0 | | 0 | 0 | 0
(1 row) (1 row)
ROLLBACK; ROLLBACK;

View file

@ -139,7 +139,6 @@ static uint64 refresh_immv_datafill(DestReceiver *dest, Query *query,
static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence); static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence);
static void OpenImmvIncrementalMaintenance(void); static void OpenImmvIncrementalMaintenance(void);
static void CloseImmvIncrementalMaintenance(void); static void CloseImmvIncrementalMaintenance(void);
static Query *get_immv_query(Relation matviewRel);
static Query *rewrite_query_for_preupdate_state(Query *query, List *tables, static Query *rewrite_query_for_preupdate_state(Query *query, List *tables,
TransactionId xid, CommandId cid, TransactionId xid, CommandId cid,
@ -586,7 +585,7 @@ CloseImmvIncrementalMaintenance(void)
/* /*
* get_immv_query - get the Query of IMMV. * get_immv_query - get the Query of IMMV.
*/ */
static Query * Query *
get_immv_query(Relation matviewRel) get_immv_query(Relation matviewRel)
{ {
Relation pgIvmImmv = table_open(PgIvmImmvRelationId(), AccessShareLock); Relation pgIvmImmv = table_open(PgIvmImmvRelationId(), AccessShareLock);
@ -2559,7 +2558,7 @@ get_plan_for_recalc(Relation matviewRel, List *namelist, List *keys, Oid *keyTyp
char *viewdef; char *viewdef;
/* get view definition of matview */ /* get view definition of matview */
viewdef = pg_ivm_get_querydef(get_immv_query(matviewRel), false); viewdef = pg_ivm_get_viewdef(matviewRel, false);
/* /*
* Build a query string for recalculating values. This is like * Build a query string for recalculating values. This is like

View file

@ -46,6 +46,7 @@ extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname,
extern ObjectAddress ExecRefreshImmv(const RangeVar *relation, bool skipData, extern ObjectAddress ExecRefreshImmv(const RangeVar *relation, bool skipData,
const char *queryString, QueryCompletion *qc); const char *queryString, QueryCompletion *qc);
extern bool ImmvIncrementalMaintenanceIsEnabled(void); extern bool ImmvIncrementalMaintenanceIsEnabled(void);
extern Query *get_immv_query(Relation matviewRel);
extern Datum IVM_immediate_before(PG_FUNCTION_ARGS); extern Datum IVM_immediate_before(PG_FUNCTION_ARGS);
extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS); extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
extern void AtAbort_IVM(void); extern void AtAbort_IVM(void);
@ -53,6 +54,6 @@ extern bool isIvmName(const char *s);
/* ruleutils.c */ /* ruleutils.c */
extern char *pg_ivm_get_querydef(Query *query, bool pretty); extern char *pg_ivm_get_viewdef(Relation immvrel, bool pretty);
#endif #endif

View file

@ -15,6 +15,7 @@
#include "postgres.h" #include "postgres.h"
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 150000) #if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 150000)
#include "utils/rel.h"
#include "utils/ruleutils.h" #include "utils/ruleutils.h"
#elif defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 140000) #elif defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 140000)
#include "ruleutils_14.c" #include "ruleutils_14.c"
@ -30,19 +31,49 @@
: PRETTYFLAG_INDENT) : PRETTYFLAG_INDENT)
/* ---------- /* ----------
* pg_get_querydef * pg_ivm_get_viewdef
* *
* Public entry point to deparse one query parsetree. * Public entry point to deparse a view definition query parsetree.
* The pretty flags are determined by GET_PRETTY_FLAGS(pretty). * The pretty flags are determined by GET_PRETTY_FLAGS(pretty).
* *
* The result is a palloc'd C string. * The result is a palloc'd C string.
* ---------- * ----------
*/ */
char * char *
pg_ivm_get_querydef(Query *query, bool pretty) pg_ivm_get_viewdef(Relation immvrel, bool pretty)
{ {
Query *query = get_immv_query(immvrel);
TupleDesc resultDesc = RelationGetDescr(immvrel);
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 150000) #if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 150000)
ListCell *lc;
int colno = 0;
/*
* Rewrite the result column name using the view's tuple
* descriptor.
*
* The column name is usually figured out in get_query_def
* using a tupleDesc specified as an argument, but this
* function is static, so we cannot directly call it.
* Therefore, we rewrite them prior to calling the public
* function pg_get_querydef (for PG15 or higher).
*/
query = copyObject(query);
foreach (lc, query->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(lc);
if (tle->resjunk)
continue; /* ignore junk entries */
colno++;
if (resultDesc && colno <= resultDesc->natts)
tle->resname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname);
}
return pg_get_querydef(query, pretty); return pg_get_querydef(query, pretty);
#else #else
StringInfoData buf; StringInfoData buf;
int prettyFlags; int prettyFlags;
@ -51,7 +82,12 @@ pg_ivm_get_querydef(Query *query, bool pretty)
initStringInfo(&buf); initStringInfo(&buf);
get_query_def(query, &buf, NIL, NULL, true, /*
* For PG14 or earlier, we use get_query_def which is copied
* from the core because any public function for this purpose
* is not available.
*/
get_query_def(query, &buf, NIL, resultDesc, true,
prettyFlags, WRAP_COLUMN_DEFAULT, 0); prettyFlags, WRAP_COLUMN_DEFAULT, 0);
return buf.data; return buf.data;

View file

@ -124,7 +124,7 @@ ROLLBACK;
-- support MIN(), MAX() aggregate functions -- support MIN(), MAX() aggregate functions
BEGIN; BEGIN;
SELECT create_immv('mv_ivm_min_max', 'SELECT i, MIN(j), MAX(j) FROM mv_base_a GROUP BY i'); SELECT create_immv('mv_ivm_min_max(i, min_j, max_j)', 'SELECT i, MIN(j), MAX(j) FROM mv_base_a GROUP BY i');
SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3; SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
INSERT INTO mv_base_a VALUES INSERT INTO mv_base_a VALUES
(1,11), (1,12), (1,11), (1,12),
@ -139,7 +139,7 @@ ROLLBACK;
-- support MIN(), MAX() aggregate functions without GROUP clause -- support MIN(), MAX() aggregate functions without GROUP clause
BEGIN; BEGIN;
SELECT create_immv('mv_ivm_min_max', 'SELECT MIN(j), MAX(j) FROM mv_base_a'); SELECT create_immv('mv_ivm_min_max(min_j, max_j)', 'SELECT MIN(j), MAX(j) FROM mv_base_a');
SELECT * FROM mv_ivm_min_max; SELECT * FROM mv_ivm_min_max;
INSERT INTO mv_base_a VALUES INSERT INTO mv_base_a VALUES
(0,0), (6,60), (7,70); (0,0), (6,60), (7,70);