Support min/max aggregates (#18)
In order to re-calculate min/max values for groups where the min or max value is deleted, we need the view query definition in string form. However, pg_get_viewdef cannot be used for this purpose because IMMV's defenition is in pg_ivm_immv but not pg_rewrite. Therefore, we have to convert query definition in pg_ivm_immv to query definition string. We can use pg_get_querydef in PG15, but we cannot in PG14 or earlier, so we use codes in ruleutil.c copied from PG13 or PG14 depending versions.
This commit is contained in:
parent
790b0d2bd6
commit
6faf0b3baa
10 changed files with 16996 additions and 20 deletions
3
Makefile
3
Makefile
|
|
@ -5,7 +5,8 @@ OBJS = \
|
||||||
$(WIN32RES) \
|
$(WIN32RES) \
|
||||||
createas.o \
|
createas.o \
|
||||||
matview.o \
|
matview.o \
|
||||||
pg_ivm.o
|
pg_ivm.o \
|
||||||
|
ruleutils.o
|
||||||
PGFILEDESC = "pg_ivm - incremental view maintenance on PostgreSQL"
|
PGFILEDESC = "pg_ivm - incremental view maintenance on PostgreSQL"
|
||||||
|
|
||||||
EXTENSION = pg_ivm
|
EXTENSION = pg_ivm
|
||||||
|
|
|
||||||
91
createas.c
91
createas.c
|
|
@ -917,6 +917,51 @@ check_aggregate_supports_ivm(Oid aggfnoid)
|
||||||
case F_AVG_FLOAT8:
|
case F_AVG_FLOAT8:
|
||||||
case F_AVG_INTERVAL:
|
case F_AVG_INTERVAL:
|
||||||
|
|
||||||
|
/* min */
|
||||||
|
case F_MIN_ANYARRAY:
|
||||||
|
case F_MIN_INT8:
|
||||||
|
case F_MIN_INT4:
|
||||||
|
case F_MIN_INT2:
|
||||||
|
case F_MIN_OID:
|
||||||
|
case F_MIN_FLOAT4:
|
||||||
|
case F_MIN_FLOAT8:
|
||||||
|
case F_MIN_DATE:
|
||||||
|
case F_MIN_TIME:
|
||||||
|
case F_MIN_TIMETZ:
|
||||||
|
case F_MIN_MONEY:
|
||||||
|
case F_MIN_TIMESTAMP:
|
||||||
|
case F_MIN_TIMESTAMPTZ:
|
||||||
|
case F_MIN_INTERVAL:
|
||||||
|
case F_MIN_TEXT:
|
||||||
|
case F_MIN_NUMERIC:
|
||||||
|
case F_MIN_BPCHAR:
|
||||||
|
case F_MIN_TID:
|
||||||
|
case F_MIN_ANYENUM:
|
||||||
|
case F_MIN_INET:
|
||||||
|
case F_MIN_PG_LSN:
|
||||||
|
|
||||||
|
/* max */
|
||||||
|
case F_MAX_ANYARRAY:
|
||||||
|
case F_MAX_INT8:
|
||||||
|
case F_MAX_INT4:
|
||||||
|
case F_MAX_INT2:
|
||||||
|
case F_MAX_OID:
|
||||||
|
case F_MAX_FLOAT4:
|
||||||
|
case F_MAX_FLOAT8:
|
||||||
|
case F_MAX_DATE:
|
||||||
|
case F_MAX_TIME:
|
||||||
|
case F_MAX_TIMETZ:
|
||||||
|
case F_MAX_MONEY:
|
||||||
|
case F_MAX_TIMESTAMP:
|
||||||
|
case F_MAX_TIMESTAMPTZ:
|
||||||
|
case F_MAX_INTERVAL:
|
||||||
|
case F_MAX_TEXT:
|
||||||
|
case F_MAX_NUMERIC:
|
||||||
|
case F_MAX_BPCHAR:
|
||||||
|
case F_MAX_TID:
|
||||||
|
case F_MAX_ANYENUM:
|
||||||
|
case F_MAX_INET:
|
||||||
|
case F_MAX_PG_LSN:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -948,6 +993,52 @@ check_aggregate_supports_ivm(Oid aggfnoid)
|
||||||
"avg(float8)",
|
"avg(float8)",
|
||||||
"avg(interval)",
|
"avg(interval)",
|
||||||
|
|
||||||
|
/* min */
|
||||||
|
"min(anyarray)",
|
||||||
|
"min(int8)",
|
||||||
|
"min(int4)",
|
||||||
|
"min(int2)",
|
||||||
|
"min(oid)",
|
||||||
|
"min(float4)",
|
||||||
|
"min(float8)",
|
||||||
|
"min(date)",
|
||||||
|
"min(time without time zone)",
|
||||||
|
"min(time with time zone)",
|
||||||
|
"min(money)",
|
||||||
|
"min(timestamp without time zone)",
|
||||||
|
"min(timestamp with time zone)",
|
||||||
|
"min(interval)",
|
||||||
|
"min(text)",
|
||||||
|
"min(numeric)",
|
||||||
|
"min(character)",
|
||||||
|
"min(tid)",
|
||||||
|
"min(anyenum)",
|
||||||
|
"min(inet)",
|
||||||
|
"min(pg_lsn)",
|
||||||
|
|
||||||
|
/* max */
|
||||||
|
"max(anyarray)",
|
||||||
|
"max(int8)",
|
||||||
|
"max(int4)",
|
||||||
|
"max(int2)",
|
||||||
|
"max(oid)",
|
||||||
|
"max(float4)",
|
||||||
|
"max(float8)",
|
||||||
|
"max(date)",
|
||||||
|
"max(time without time zone)",
|
||||||
|
"max(time with time zone)",
|
||||||
|
"max(money)",
|
||||||
|
"max(timestamp without time zone)",
|
||||||
|
"max(timestamp with time zone)",
|
||||||
|
"max(interval)",
|
||||||
|
"max(text)",
|
||||||
|
"max(numeric)",
|
||||||
|
"max(character)",
|
||||||
|
"max(tid)",
|
||||||
|
"max(anyenum)",
|
||||||
|
"max(inet)",
|
||||||
|
"max(pg_lsn)",
|
||||||
|
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -388,11 +388,93 @@ SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
|
||||||
(5 rows)
|
(5 rows)
|
||||||
|
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
-- not support MIN(), MAX() aggregate functions
|
-- support MIN(), MAX() aggregate functions
|
||||||
SELECT create_immv('mv_ivm_min_max', 'SELECT i, MIN(j) FROM mv_base_a GROUP BY i');
|
BEGIN;
|
||||||
ERROR: aggregate function min(integer) is not supported on incrementally maintainable materialized view
|
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', 'SELECT i, MAX(j) FROM mv_base_a GROUP BY i');
|
NOTICE: created index "mv_ivm_min_max_index" on immv "mv_ivm_min_max"
|
||||||
ERROR: aggregate function max(integer) is not supported on incrementally maintainable materialized view
|
create_immv
|
||||||
|
-------------
|
||||||
|
5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
|
||||||
|
i | min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__
|
||||||
|
---+-----+-----+-------------------+-------------------+---------------
|
||||||
|
1 | 10 | 10 | 1 | 1 | 1
|
||||||
|
2 | 20 | 20 | 1 | 1 | 1
|
||||||
|
3 | 30 | 30 | 1 | 1 | 1
|
||||||
|
4 | 40 | 40 | 1 | 1 | 1
|
||||||
|
5 | 50 | 50 | 1 | 1 | 1
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
|
INSERT INTO mv_base_a VALUES
|
||||||
|
(1,11), (1,12),
|
||||||
|
(2,21), (2,22),
|
||||||
|
(3,31), (3,32),
|
||||||
|
(4,41), (4,42),
|
||||||
|
(5,51), (5,52);
|
||||||
|
SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
|
||||||
|
i | min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__
|
||||||
|
---+-----+-----+-------------------+-------------------+---------------
|
||||||
|
1 | 10 | 12 | 3 | 3 | 3
|
||||||
|
2 | 20 | 22 | 3 | 3 | 3
|
||||||
|
3 | 30 | 32 | 3 | 3 | 3
|
||||||
|
4 | 40 | 42 | 3 | 3 | 3
|
||||||
|
5 | 50 | 52 | 3 | 3 | 3
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
i | min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__
|
||||||
|
---+-----+-----+-------------------+-------------------+---------------
|
||||||
|
1 | 11 | 12 | 2 | 2 | 2
|
||||||
|
2 | 20 | 22 | 2 | 2 | 2
|
||||||
|
3 | 30 | 31 | 2 | 2 | 2
|
||||||
|
4 | 40 | 42 | 3 | 3 | 3
|
||||||
|
5 | 50 | 52 | 3 | 3 | 3
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
|
ROLLBACK;
|
||||||
|
-- support MIN(), MAX() aggregate functions without GROUP clause
|
||||||
|
BEGIN;
|
||||||
|
SELECT create_immv('mv_ivm_min_max', 'SELECT MIN(j), MAX(j) FROM mv_base_a');
|
||||||
|
NOTICE: could not create an index on immv "mv_ivm_min_max" automatically
|
||||||
|
DETAIL: This target list does not have all the primary key columns, or this view does not contain GROUP BY or DISTINCT clause.
|
||||||
|
HINT: Create an index on the immv for efficient incremental maintenance.
|
||||||
|
create_immv
|
||||||
|
-------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM mv_ivm_min_max;
|
||||||
|
min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__
|
||||||
|
-----+-----+-------------------+-------------------+---------------
|
||||||
|
10 | 50 | 5 | 5 | 5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
INSERT INTO mv_base_a VALUES
|
||||||
|
(0,0), (6,60), (7,70);
|
||||||
|
SELECT * FROM mv_ivm_min_max;
|
||||||
|
min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__
|
||||||
|
-----+-----+-------------------+-------------------+---------------
|
||||||
|
0 | 70 | 8 | 8 | 8
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
|
||||||
|
SELECT * FROM mv_ivm_min_max;
|
||||||
|
min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__
|
||||||
|
-----+-----+-------------------+-------------------+---------------
|
||||||
|
10 | 60 | 6 | 6 | 6
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DELETE FROM mv_base_a;
|
||||||
|
SELECT * FROM mv_ivm_min_max;
|
||||||
|
min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__
|
||||||
|
-----+-----+-------------------+-------------------+---------------
|
||||||
|
| | 0 | 0 | 0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
ROLLBACK;
|
||||||
-- support subquery in FROM clause
|
-- support subquery in FROM clause
|
||||||
BEGIN;
|
BEGIN;
|
||||||
SELECT create_immv('mv_ivm_subquery01', 'SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i');
|
SELECT create_immv('mv_ivm_subquery01', 'SELECT a.i,a.j FROM mv_base_a a, (SELECT * FROM mv_base_b) b WHERE a.i = b.i');
|
||||||
|
|
@ -629,6 +711,70 @@ SELECT * FROM mv ORDER BY i;
|
||||||
|
|
||||||
(2 rows)
|
(2 rows)
|
||||||
|
|
||||||
|
ROLLBACK;
|
||||||
|
BEGIN;
|
||||||
|
CREATE TABLE base_t (i int, v int);
|
||||||
|
INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
|
||||||
|
SELECT create_immv('mv', 'SELECT i, sum(v) FROM base_t GROUP BY i');
|
||||||
|
NOTICE: created index "mv_index" on immv "mv"
|
||||||
|
create_immv
|
||||||
|
-------------
|
||||||
|
2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM mv ORDER BY i;
|
||||||
|
i | sum | __ivm_count_sum__ | __ivm_count__
|
||||||
|
---+-----+-------------------+---------------
|
||||||
|
1 | 30 | 2 | 2
|
||||||
|
| 3 | 2 | 2
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
UPDATE base_t SET v = v * 10;
|
||||||
|
SELECT * FROM mv ORDER BY i;
|
||||||
|
i | sum | __ivm_count_sum__ | __ivm_count__
|
||||||
|
---+-----+-------------------+---------------
|
||||||
|
1 | 300 | 2 | 2
|
||||||
|
| 30 | 2 | 2
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
ROLLBACK;
|
||||||
|
BEGIN;
|
||||||
|
CREATE TABLE base_t (i int, v int);
|
||||||
|
INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
|
||||||
|
SELECT create_immv('mv', 'SELECT i, min(v), max(v) FROM base_t GROUP BY i');
|
||||||
|
NOTICE: created index "mv_index" on immv "mv"
|
||||||
|
create_immv
|
||||||
|
-------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM mv ORDER BY i;
|
||||||
|
i | min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__
|
||||||
|
---+-----+-----+-------------------+-------------------+---------------
|
||||||
|
| 1 | 5 | 5 | 5 | 5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DELETE FROM base_t WHERE v = 1;
|
||||||
|
SELECT * FROM mv ORDER BY i;
|
||||||
|
i | min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__
|
||||||
|
---+-----+-----+-------------------+-------------------+---------------
|
||||||
|
| 2 | 5 | 4 | 4 | 4
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DELETE FROM base_t WHERE v = 3;
|
||||||
|
SELECT * FROM mv ORDER BY i;
|
||||||
|
i | min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__
|
||||||
|
---+-----+-----+-------------------+-------------------+---------------
|
||||||
|
| 2 | 5 | 3 | 3 | 3
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DELETE FROM base_t WHERE v = 5;
|
||||||
|
SELECT * FROM mv ORDER BY i;
|
||||||
|
i | min | max | __ivm_count_min__ | __ivm_count_max__ | __ivm_count__
|
||||||
|
---+-----+-----+-------------------+-------------------+---------------
|
||||||
|
| 2 | 4 | 2 | 2 | 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
-- IMMV containing user defined type
|
-- IMMV containing user defined type
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
|
||||||
592
matview.c
592
matview.c
|
|
@ -170,6 +170,9 @@ static void append_set_clause_for_sum(const char *resname, StringInfo buf_old,
|
||||||
static void append_set_clause_for_avg(const char *resname, StringInfo buf_old,
|
static void append_set_clause_for_avg(const char *resname, StringInfo buf_old,
|
||||||
StringInfo buf_new, StringInfo aggs_list,
|
StringInfo buf_new, StringInfo aggs_list,
|
||||||
const char *aggtype);
|
const char *aggtype);
|
||||||
|
static void append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
|
||||||
|
StringInfo buf_new, StringInfo aggs_list,
|
||||||
|
bool is_min);
|
||||||
static char *get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
|
static char *get_operation_string(IvmOp op, const char *col, const char *arg1, const char *arg2,
|
||||||
const char* count_col, const char *castType);
|
const char* count_col, const char *castType);
|
||||||
static char *get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
|
static char *get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
|
||||||
|
|
@ -178,17 +181,29 @@ static void apply_old_delta(const char *matviewname, const char *deltaname_old,
|
||||||
List *keys);
|
List *keys);
|
||||||
static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
|
static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
|
||||||
List *keys, StringInfo aggs_list, StringInfo aggs_set,
|
List *keys, StringInfo aggs_list, StringInfo aggs_set,
|
||||||
const char *count_colname);
|
List *minmax_list, List *is_min_list,
|
||||||
|
const char *count_colname,
|
||||||
|
SPITupleTable **tuptable_recalc, uint64 *num_recalc);
|
||||||
static void apply_new_delta(const char *matviewname, const char *deltaname_new,
|
static void apply_new_delta(const char *matviewname, const char *deltaname_new,
|
||||||
StringInfo target_list);
|
StringInfo target_list);
|
||||||
static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
|
static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
|
||||||
List *keys, StringInfo target_list, StringInfo aggs_set,
|
List *keys, StringInfo target_list, StringInfo aggs_set,
|
||||||
const char* count_colname);
|
const char* count_colname);
|
||||||
static char *get_matching_condition_string(List *keys);
|
static char *get_matching_condition_string(List *keys);
|
||||||
|
static char *get_returning_string(List *minmax_list, List *is_min_list, List *keys);
|
||||||
|
static char *get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list);
|
||||||
|
static char *get_select_for_recalc_string(List *keys);
|
||||||
|
static void recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
|
||||||
|
List *namelist, List *keys, Relation matviewRel);
|
||||||
|
static SPIPlanPtr get_plan_for_recalc(Relation matviewRel, List *namelist, List *keys, Oid *keyTypes);
|
||||||
|
static SPIPlanPtr get_plan_for_set_values(Relation matviewRel, List *namelist, Oid *valTypes);
|
||||||
static void generate_equal(StringInfo querybuf, Oid opttype,
|
static void generate_equal(StringInfo querybuf, Oid opttype,
|
||||||
const char *leftop, const char *rightop);
|
const char *leftop, const char *rightop);
|
||||||
|
|
||||||
static void mv_InitHashTables(void);
|
static void mv_InitHashTables(void);
|
||||||
|
static SPIPlanPtr mv_FetchPreparedPlan(MV_QueryKey *key);
|
||||||
|
static void mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan);
|
||||||
|
static void mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type);
|
||||||
static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
|
static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry);
|
||||||
|
|
||||||
static List *get_securityQuals(Oid relId, int rt_index, Query *query);
|
static List *get_securityQuals(Oid relId, int rt_index, Query *query);
|
||||||
|
|
@ -1551,6 +1566,9 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
int i;
|
int i;
|
||||||
List *keys = NIL;
|
List *keys = NIL;
|
||||||
|
List *minmax_list = NIL;
|
||||||
|
List *is_min_list = NIL;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get names of the materialized view and delta tables
|
* get names of the materialized view and delta tables
|
||||||
|
|
@ -1631,6 +1649,17 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
|
||||||
append_set_clause_for_avg(resname, aggs_set_old, aggs_set_new, aggs_list_buf,
|
append_set_clause_for_avg(resname, aggs_set_old, aggs_set_new, aggs_list_buf,
|
||||||
format_type_be(aggref->aggtype));
|
format_type_be(aggref->aggtype));
|
||||||
|
|
||||||
|
/* min/max */
|
||||||
|
else if (!strcmp(aggname, "min") || !strcmp(aggname, "max"))
|
||||||
|
{
|
||||||
|
bool is_min = (!strcmp(aggname, "min"));
|
||||||
|
|
||||||
|
append_set_clause_for_minmax(resname, aggs_set_old, aggs_set_new, aggs_list_buf, is_min);
|
||||||
|
|
||||||
|
/* make a resname list of min and max aggregates */
|
||||||
|
minmax_list = lappend(minmax_list, resname);
|
||||||
|
is_min_list = lappend_int(is_min_list, is_min);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
elog(ERROR, "unsupported aggregate function: %s", aggname);
|
elog(ERROR, "unsupported aggregate function: %s", aggname);
|
||||||
}
|
}
|
||||||
|
|
@ -1660,6 +1689,8 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
|
||||||
if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
|
if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0)
|
||||||
{
|
{
|
||||||
EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
|
EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData));
|
||||||
|
SPITupleTable *tuptable_recalc = NULL;
|
||||||
|
uint64 num_recalc;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
/* convert tuplestores to ENR, and register for SPI */
|
/* convert tuplestores to ENR, and register for SPI */
|
||||||
|
|
@ -1675,12 +1706,21 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
|
||||||
elog(ERROR, "SPI_register failed");
|
elog(ERROR, "SPI_register failed");
|
||||||
|
|
||||||
if (use_count)
|
if (use_count)
|
||||||
/* apply old delta */
|
/* apply old delta and get rows to be recalculated */
|
||||||
apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
|
apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
|
||||||
keys, aggs_list_buf, aggs_set_old,
|
keys, aggs_list_buf, aggs_set_old,
|
||||||
count_colname);
|
minmax_list, is_min_list,
|
||||||
|
count_colname, &tuptable_recalc, &num_recalc);
|
||||||
else
|
else
|
||||||
apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
|
apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have min or max, we might have to recalculate aggregate values from base tables
|
||||||
|
* on some tuples. TIDs and keys such tuples are returned as a result of the above query.
|
||||||
|
*/
|
||||||
|
if (minmax_list && tuptable_recalc)
|
||||||
|
recalc_and_set_values(tuptable_recalc, num_recalc, minmax_list, keys, matviewRel);
|
||||||
|
|
||||||
}
|
}
|
||||||
/* For tuple insertion */
|
/* For tuple insertion */
|
||||||
if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
|
if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0)
|
||||||
|
|
@ -1872,6 +1912,70 @@ append_set_clause_for_avg(const char *resname, StringInfo buf_old,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* append_set_clause_for_minmax
|
||||||
|
*
|
||||||
|
* Append SET clause string for min or max aggregation to given buffers.
|
||||||
|
* Also, append resnames required for calculating the aggregate value.
|
||||||
|
* is_min is true if this is min, false if not.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
append_set_clause_for_minmax(const char *resname, StringInfo buf_old,
|
||||||
|
StringInfo buf_new, StringInfo aggs_list,
|
||||||
|
bool is_min)
|
||||||
|
{
|
||||||
|
char *count_col = IVM_colname("count", resname);
|
||||||
|
|
||||||
|
/* For tuple deletion */
|
||||||
|
if (buf_old)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If the new value doesn't became NULL then use the value remaining
|
||||||
|
* in the view although this will be recomputated afterwords.
|
||||||
|
*/
|
||||||
|
appendStringInfo(buf_old,
|
||||||
|
", %s = CASE WHEN %s THEN NULL ELSE %s END",
|
||||||
|
quote_qualified_identifier(NULL, resname),
|
||||||
|
get_null_condition_string(IVM_SUB, "mv", "t", count_col),
|
||||||
|
quote_qualified_identifier("mv", resname)
|
||||||
|
);
|
||||||
|
/* count = mv.count - t.count */
|
||||||
|
appendStringInfo(buf_old,
|
||||||
|
", %s = %s",
|
||||||
|
quote_qualified_identifier(NULL, count_col),
|
||||||
|
get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* For tuple insertion */
|
||||||
|
if (buf_new)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* min = LEAST(mv.min, diff.min)
|
||||||
|
* max = GREATEST(mv.max, diff.max)
|
||||||
|
*/
|
||||||
|
appendStringInfo(buf_new,
|
||||||
|
", %s = CASE WHEN %s THEN NULL ELSE %s(%s,%s) END",
|
||||||
|
quote_qualified_identifier(NULL, resname),
|
||||||
|
get_null_condition_string(IVM_ADD, "mv", "diff", count_col),
|
||||||
|
|
||||||
|
is_min ? "LEAST" : "GREATEST",
|
||||||
|
quote_qualified_identifier("mv", resname),
|
||||||
|
quote_qualified_identifier("diff", resname)
|
||||||
|
);
|
||||||
|
/* count = mv.count + diff.count */
|
||||||
|
appendStringInfo(buf_new,
|
||||||
|
", %s = %s",
|
||||||
|
quote_qualified_identifier(NULL, count_col),
|
||||||
|
get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
appendStringInfo(aggs_list, ", %s, %s",
|
||||||
|
quote_qualified_identifier("diff", resname),
|
||||||
|
quote_qualified_identifier("diff", IVM_colname("count", resname))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get_operation_string
|
* get_operation_string
|
||||||
*
|
*
|
||||||
|
|
@ -1975,19 +2079,43 @@ get_null_condition_string(IvmOp op, const char *arg1, const char *arg2,
|
||||||
* list to identify a tuple in the view. If the view has aggregates, this
|
* list to identify a tuple in the view. If the view has aggregates, this
|
||||||
* requires strings representing resnames of aggregates and SET clause for
|
* requires strings representing resnames of aggregates and SET clause for
|
||||||
* updating aggregate values.
|
* updating aggregate values.
|
||||||
|
*
|
||||||
|
* If the view has min or max aggregate, this requires a list of resnames of
|
||||||
|
* min/max aggregates and a list of boolean which represents which entries in
|
||||||
|
* minmax_list is min. These are necessary to check if we need to recalculate
|
||||||
|
* min or max aggregate values. In this case, this query returns TID and keys
|
||||||
|
* of tuples which need to be recalculated. This result and the number of rows
|
||||||
|
* are stored in tuptables and num_recalc repectedly.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
|
apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
|
||||||
List *keys, StringInfo aggs_list, StringInfo aggs_set,
|
List *keys, StringInfo aggs_list, StringInfo aggs_set,
|
||||||
const char *count_colname)
|
List *minmax_list, List *is_min_list,
|
||||||
|
const char *count_colname,
|
||||||
|
SPITupleTable **tuptable_recalc, uint64 *num_recalc)
|
||||||
{
|
{
|
||||||
StringInfoData querybuf;
|
StringInfoData querybuf;
|
||||||
char *match_cond;
|
char *match_cond;
|
||||||
|
char *updt_returning = "";
|
||||||
|
char *select_for_recalc = "SELECT";
|
||||||
bool agg_without_groupby = (list_length(keys) == 0);
|
bool agg_without_groupby = (list_length(keys) == 0);
|
||||||
|
|
||||||
|
Assert(tuptable_recalc != NULL);
|
||||||
|
Assert(num_recalc != NULL);
|
||||||
|
|
||||||
/* build WHERE condition for searching tuples to be deleted */
|
/* build WHERE condition for searching tuples to be deleted */
|
||||||
match_cond = get_matching_condition_string(keys);
|
match_cond = get_matching_condition_string(keys);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We need a special RETURNING clause and SELECT statement for min/max to
|
||||||
|
* check which tuple needs re-calculation from base tables.
|
||||||
|
*/
|
||||||
|
if (minmax_list)
|
||||||
|
{
|
||||||
|
updt_returning = get_returning_string(minmax_list, is_min_list, keys);
|
||||||
|
select_for_recalc = get_select_for_recalc_string(keys);
|
||||||
|
}
|
||||||
|
|
||||||
/* Search for matching tuples from the view and update or delete if found. */
|
/* Search for matching tuples from the view and update or delete if found. */
|
||||||
initStringInfo(&querybuf);
|
initStringInfo(&querybuf);
|
||||||
appendStringInfo(&querybuf,
|
appendStringInfo(&querybuf,
|
||||||
|
|
@ -2002,10 +2130,11 @@ apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
|
||||||
"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
|
"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
|
||||||
"%s" /* SET clauses for aggregates */
|
"%s" /* SET clauses for aggregates */
|
||||||
"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
|
"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
|
||||||
")"
|
"%s" /* RETURNING clause for recalc infomation */
|
||||||
/* delete a tuple if this is to be deleted */
|
"), dlt AS (" /* delete a tuple if this is to be deleted */
|
||||||
"DELETE FROM %s AS mv USING t "
|
"DELETE FROM %s AS mv USING t "
|
||||||
"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt",
|
"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt"
|
||||||
|
") %s", /* SELECT returning which tuples need to be recalculated */
|
||||||
count_colname,
|
count_colname,
|
||||||
count_colname, count_colname, (agg_without_groupby ? "false" : "true"),
|
count_colname, count_colname, (agg_without_groupby ? "false" : "true"),
|
||||||
(aggs_list != NULL ? aggs_list->data : ""),
|
(aggs_list != NULL ? aggs_list->data : ""),
|
||||||
|
|
@ -2013,10 +2142,24 @@ apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
|
||||||
match_cond,
|
match_cond,
|
||||||
matviewname, count_colname, count_colname, count_colname,
|
matviewname, count_colname, count_colname, count_colname,
|
||||||
(aggs_set != NULL ? aggs_set->data : ""),
|
(aggs_set != NULL ? aggs_set->data : ""),
|
||||||
matviewname);
|
updt_returning,
|
||||||
|
matviewname,
|
||||||
|
select_for_recalc);
|
||||||
|
|
||||||
if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
|
if (SPI_exec(querybuf.data, 0) != SPI_OK_SELECT)
|
||||||
elog(ERROR, "SPI_exec failed: %s", querybuf.data);
|
elog(ERROR, "SPI_exec failed: %s", querybuf.data);
|
||||||
|
|
||||||
|
/* Return tuples to be recalculated. */
|
||||||
|
if (minmax_list)
|
||||||
|
{
|
||||||
|
*tuptable_recalc = SPI_tuptable;
|
||||||
|
*num_recalc = SPI_processed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*tuptable_recalc = NULL;
|
||||||
|
*num_recalc = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -2198,6 +2341,342 @@ get_matching_condition_string(List *keys)
|
||||||
return match_cond.data;
|
return match_cond.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_returning_string
|
||||||
|
*
|
||||||
|
* Build a string for RETURNING clause of UPDATE used in apply_old_delta_with_count.
|
||||||
|
* This clause returns ctid and a boolean value that indicates if we need to
|
||||||
|
* recalculate min or max value, for each updated row.
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
get_returning_string(List *minmax_list, List *is_min_list, List *keys)
|
||||||
|
{
|
||||||
|
StringInfoData returning;
|
||||||
|
char *recalc_cond;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
Assert(minmax_list != NIL && is_min_list != NIL);
|
||||||
|
recalc_cond = get_minmax_recalc_condition_string(minmax_list, is_min_list);
|
||||||
|
|
||||||
|
initStringInfo(&returning);
|
||||||
|
|
||||||
|
appendStringInfo(&returning, "RETURNING mv.ctid AS tid, (%s) AS recalc", recalc_cond);
|
||||||
|
foreach (lc, keys)
|
||||||
|
{
|
||||||
|
Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
|
||||||
|
char *resname = NameStr(attr->attname);
|
||||||
|
appendStringInfo(&returning, ", %s", quote_qualified_identifier("mv", resname));
|
||||||
|
}
|
||||||
|
|
||||||
|
return returning.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_minmax_recalc_condition_string
|
||||||
|
*
|
||||||
|
* Build a predicate string for checking if any min/max aggregate
|
||||||
|
* value needs to be recalculated.
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list)
|
||||||
|
{
|
||||||
|
StringInfoData recalc_cond;
|
||||||
|
ListCell *lc1, *lc2;
|
||||||
|
|
||||||
|
initStringInfo(&recalc_cond);
|
||||||
|
|
||||||
|
Assert (list_length(minmax_list) == list_length(is_min_list));
|
||||||
|
|
||||||
|
forboth (lc1, minmax_list, lc2, is_min_list)
|
||||||
|
{
|
||||||
|
char *resname = (char *) lfirst(lc1);
|
||||||
|
bool is_min = (bool) lfirst_int(lc2);
|
||||||
|
char *op_str = (is_min ? ">=" : "<=");
|
||||||
|
|
||||||
|
appendStringInfo(&recalc_cond, "%s OPERATOR(pg_catalog.%s) %s",
|
||||||
|
quote_qualified_identifier("mv", resname),
|
||||||
|
op_str,
|
||||||
|
quote_qualified_identifier("t", resname)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (lnext(minmax_list, lc1))
|
||||||
|
appendStringInfo(&recalc_cond, " OR ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return recalc_cond.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_select_for_recalc_string
|
||||||
|
*
|
||||||
|
* Build a query to return tid and keys of tuples which need
|
||||||
|
* recalculation. This is used as the result of the query
|
||||||
|
* built by apply_old_delta.
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
get_select_for_recalc_string(List *keys)
|
||||||
|
{
|
||||||
|
StringInfoData qry;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
initStringInfo(&qry);
|
||||||
|
|
||||||
|
appendStringInfo(&qry, "SELECT tid");
|
||||||
|
foreach (lc, keys)
|
||||||
|
{
|
||||||
|
Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
|
||||||
|
appendStringInfo(&qry, ", %s", NameStr(attr->attname));
|
||||||
|
}
|
||||||
|
|
||||||
|
appendStringInfo(&qry, " FROM updt WHERE recalc");
|
||||||
|
|
||||||
|
return qry.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* recalc_and_set_values
|
||||||
|
*
|
||||||
|
* Recalculate tuples in a materialized from base tables and update these.
|
||||||
|
* The tuples which needs recalculation are specified by keys, and resnames
|
||||||
|
* of columns to be updated are specified by namelist. TIDs and key values
|
||||||
|
* are given by tuples in tuptable_recalc. Its first attribute must be TID
|
||||||
|
* and key values must be following this.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples,
|
||||||
|
List *namelist, List *keys, Relation matviewRel)
|
||||||
|
{
|
||||||
|
TupleDesc tupdesc_recalc = tuptable_recalc->tupdesc;
|
||||||
|
Oid *keyTypes = NULL, *types = NULL;
|
||||||
|
char *keyNulls = NULL, *nulls = NULL;
|
||||||
|
Datum *keyVals = NULL, *vals = NULL;
|
||||||
|
int num_vals = list_length(namelist);
|
||||||
|
int num_keys = list_length(keys);
|
||||||
|
uint64 i;
|
||||||
|
|
||||||
|
/* If we have keys, initialize arrays for them. */
|
||||||
|
if (keys)
|
||||||
|
{
|
||||||
|
keyTypes = palloc(sizeof(Oid) * num_keys);
|
||||||
|
keyNulls = palloc(sizeof(char) * num_keys);
|
||||||
|
keyVals = palloc(sizeof(Datum) * num_keys);
|
||||||
|
/* a tuple contains keys to be recalculated and ctid to be updated*/
|
||||||
|
Assert(tupdesc_recalc->natts == num_keys + 1);
|
||||||
|
|
||||||
|
/* Types of key attributes */
|
||||||
|
for (i = 0; i < num_keys; i++)
|
||||||
|
keyTypes[i] = TupleDescAttr(tupdesc_recalc, i + 1)->atttypid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* allocate memory for all attribute names and tid */
|
||||||
|
types = palloc(sizeof(Oid) * (num_vals + 1));
|
||||||
|
nulls = palloc(sizeof(char) * (num_vals + 1));
|
||||||
|
vals = palloc(sizeof(Datum) * (num_vals + 1));
|
||||||
|
|
||||||
|
/* For each tuple which needs recalculation */
|
||||||
|
for (i = 0; i < num_tuples; i++)
|
||||||
|
{
|
||||||
|
int j;
|
||||||
|
bool isnull;
|
||||||
|
SPIPlanPtr plan;
|
||||||
|
SPITupleTable *tuptable_newvals;
|
||||||
|
TupleDesc tupdesc_newvals;
|
||||||
|
|
||||||
|
/* Set group key values as parameters if needed. */
|
||||||
|
if (keys)
|
||||||
|
{
|
||||||
|
for (j = 0; j < num_keys; j++)
|
||||||
|
{
|
||||||
|
keyVals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, j + 2, &isnull);
|
||||||
|
if (isnull)
|
||||||
|
keyNulls[j] = 'n';
|
||||||
|
else
|
||||||
|
keyNulls[j] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get recalculated values from base tables. The result must be
|
||||||
|
* only one tuple thich contains the new values for specified keys.
|
||||||
|
*/
|
||||||
|
plan = get_plan_for_recalc(matviewRel, namelist, keys, keyTypes);
|
||||||
|
if (SPI_execute_plan(plan, keyVals, keyNulls, false, 0) != SPI_OK_SELECT)
|
||||||
|
elog(ERROR, "SPI_execute_plan");
|
||||||
|
if (SPI_processed != 1)
|
||||||
|
elog(ERROR, "SPI_execute_plan returned zero or more than one rows");
|
||||||
|
|
||||||
|
tuptable_newvals = SPI_tuptable;
|
||||||
|
tupdesc_newvals = tuptable_newvals->tupdesc;
|
||||||
|
|
||||||
|
Assert(tupdesc_newvals->natts == num_vals);
|
||||||
|
|
||||||
|
/* Set the new values as parameters */
|
||||||
|
for (j = 0; j < tupdesc_newvals->natts; j++)
|
||||||
|
{
|
||||||
|
if (i == 0)
|
||||||
|
types[j] = TupleDescAttr(tupdesc_newvals, j)->atttypid;
|
||||||
|
|
||||||
|
vals[j] = SPI_getbinval(tuptable_newvals->vals[0], tupdesc_newvals, j + 1, &isnull);
|
||||||
|
if (isnull)
|
||||||
|
nulls[j] = 'n';
|
||||||
|
else
|
||||||
|
nulls[j] = ' ';
|
||||||
|
}
|
||||||
|
/* Set TID of the view tuple to be updated as a parameter */
|
||||||
|
types[j] = TIDOID;
|
||||||
|
vals[j] = SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, 1, &isnull);
|
||||||
|
nulls[j] = ' ';
|
||||||
|
|
||||||
|
/* Update the view tuple to the new values */
|
||||||
|
plan = get_plan_for_set_values(matviewRel, namelist, types);
|
||||||
|
if (SPI_execute_plan(plan, vals, nulls, false, 0) != SPI_OK_UPDATE)
|
||||||
|
elog(ERROR, "SPI_execute_plan");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_plan_for_recalc
|
||||||
|
*
|
||||||
|
* Create or fetch a plan for recalculating value in the view's target list
|
||||||
|
* from base tables using the definition query of materialized view specified
|
||||||
|
* by matviewRel. namelist is a list of resnames of values to be recalculated.
|
||||||
|
*
|
||||||
|
* keys is a list of keys to identify tuples to be recalculated if this is not
|
||||||
|
* empty. KeyTypes is an array of types of keys.
|
||||||
|
*/
|
||||||
|
static SPIPlanPtr
|
||||||
|
get_plan_for_recalc(Relation matviewRel, List *namelist, List *keys, Oid *keyTypes)
|
||||||
|
{
|
||||||
|
MV_QueryKey hash_key;
|
||||||
|
SPIPlanPtr plan;
|
||||||
|
|
||||||
|
/* Fetch or prepare a saved plan for the recalculation */
|
||||||
|
mv_BuildQueryKey(&hash_key, RelationGetRelid(matviewRel), MV_PLAN_RECALC);
|
||||||
|
if ((plan = mv_FetchPreparedPlan(&hash_key)) == NULL)
|
||||||
|
{
|
||||||
|
ListCell *lc;
|
||||||
|
StringInfoData str;
|
||||||
|
char *viewdef;
|
||||||
|
|
||||||
|
/* get view definition of matview */
|
||||||
|
viewdef = pg_ivm_get_querydef(get_immv_query(matviewRel), false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build a query string for recalculating values. This is like
|
||||||
|
*
|
||||||
|
* SELECT x1, x2, x3, ... FROM ( ... view definition query ...) mv
|
||||||
|
* WHERE (key1, key2, ...) = ($1, $2, ...);
|
||||||
|
*/
|
||||||
|
|
||||||
|
initStringInfo(&str);
|
||||||
|
appendStringInfo(&str, "SELECT ");
|
||||||
|
foreach (lc, namelist)
|
||||||
|
{
|
||||||
|
appendStringInfo(&str, "%s", (char *) lfirst(lc));
|
||||||
|
if (lnext(namelist, lc))
|
||||||
|
appendStringInfoString(&str, ", ");
|
||||||
|
}
|
||||||
|
appendStringInfo(&str, " FROM (%s) mv", viewdef);
|
||||||
|
|
||||||
|
if (keys)
|
||||||
|
{
|
||||||
|
int i = 1;
|
||||||
|
char paramname[16];
|
||||||
|
|
||||||
|
appendStringInfo(&str, " WHERE (");
|
||||||
|
foreach (lc, keys)
|
||||||
|
{
|
||||||
|
Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
|
||||||
|
char *resname = NameStr(attr->attname);
|
||||||
|
Oid typid = attr->atttypid;
|
||||||
|
|
||||||
|
sprintf(paramname, "$%d", i);
|
||||||
|
appendStringInfo(&str, "(");
|
||||||
|
generate_equal(&str, typid, resname, paramname);
|
||||||
|
appendStringInfo(&str, " OR (%s IS NULL AND %s IS NULL))",
|
||||||
|
resname, paramname);
|
||||||
|
|
||||||
|
if (lnext(keys, lc))
|
||||||
|
appendStringInfoString(&str, " AND ");
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
appendStringInfo(&str, ")");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
keyTypes = NULL;
|
||||||
|
|
||||||
|
plan = SPI_prepare(str.data, list_length(keys), keyTypes);
|
||||||
|
if (plan == NULL)
|
||||||
|
elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
|
||||||
|
|
||||||
|
SPI_keepplan(plan);
|
||||||
|
mv_HashPreparedPlan(&hash_key, plan);
|
||||||
|
}
|
||||||
|
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_plan_for_set_values
|
||||||
|
*
|
||||||
|
* Create or fetch a plan for applying new values calculated by
|
||||||
|
* get_plan_for_recalc to a materialized view specified by matviewRel.
|
||||||
|
* namelist is a list of resnames of attributes to be updated, and
|
||||||
|
* valTypes is an array of types of the
|
||||||
|
* values.
|
||||||
|
*/
|
||||||
|
static SPIPlanPtr
|
||||||
|
get_plan_for_set_values(Relation matviewRel, List *namelist, Oid *valTypes)
|
||||||
|
{
|
||||||
|
MV_QueryKey key;
|
||||||
|
SPIPlanPtr plan;
|
||||||
|
char *matviewname;
|
||||||
|
|
||||||
|
matviewname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
|
||||||
|
RelationGetRelationName(matviewRel));
|
||||||
|
|
||||||
|
/* Fetch or prepare a saved plan for the real check */
|
||||||
|
mv_BuildQueryKey(&key, RelationGetRelid(matviewRel), MV_PLAN_SET_VALUE);
|
||||||
|
if ((plan = mv_FetchPreparedPlan(&key)) == NULL)
|
||||||
|
{
|
||||||
|
ListCell *lc;
|
||||||
|
StringInfoData str;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build a query string for applying min/max values. This is like
|
||||||
|
*
|
||||||
|
* UPDATE matviewname AS mv
|
||||||
|
* SET (x1, x2, x3, x4) = ($1, $2, $3, $4)
|
||||||
|
* WHERE ctid = $5;
|
||||||
|
*/
|
||||||
|
|
||||||
|
initStringInfo(&str);
|
||||||
|
appendStringInfo(&str, "UPDATE %s AS mv SET (", matviewname);
|
||||||
|
foreach (lc, namelist)
|
||||||
|
{
|
||||||
|
appendStringInfo(&str, "%s", (char *) lfirst(lc));
|
||||||
|
if (lnext(namelist, lc))
|
||||||
|
appendStringInfoString(&str, ", ");
|
||||||
|
}
|
||||||
|
appendStringInfo(&str, ") = ROW(");
|
||||||
|
|
||||||
|
for (i = 1; i <= list_length(namelist); i++)
|
||||||
|
appendStringInfo(&str, "%s$%d", (i==1 ? "" : ", "), i);
|
||||||
|
|
||||||
|
appendStringInfo(&str, ") WHERE ctid OPERATOR(pg_catalog.=) $%d", i);
|
||||||
|
|
||||||
|
plan = SPI_prepare(str.data, list_length(namelist) + 1, valTypes);
|
||||||
|
if (plan == NULL)
|
||||||
|
elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), str.data);
|
||||||
|
|
||||||
|
SPI_keepplan(plan);
|
||||||
|
mv_HashPreparedPlan(&key, plan);
|
||||||
|
}
|
||||||
|
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* generate_equal
|
* generate_equal
|
||||||
*
|
*
|
||||||
|
|
@ -2246,6 +2725,99 @@ mv_InitHashTables(void)
|
||||||
&ctl, HASH_ELEM | HASH_BLOBS);
|
&ctl, HASH_ELEM | HASH_BLOBS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mv_FetchPreparedPlan
|
||||||
|
*/
|
||||||
|
static SPIPlanPtr
|
||||||
|
mv_FetchPreparedPlan(MV_QueryKey *key)
|
||||||
|
{
|
||||||
|
MV_QueryHashEntry *entry;
|
||||||
|
SPIPlanPtr plan;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On the first call initialize the hashtable
|
||||||
|
*/
|
||||||
|
if (!mv_query_cache)
|
||||||
|
mv_InitHashTables();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lookup for the key
|
||||||
|
*/
|
||||||
|
entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
|
||||||
|
(void *) key,
|
||||||
|
HASH_FIND, NULL);
|
||||||
|
if (entry == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check whether the plan is still valid. If it isn't, we don't want to
|
||||||
|
* simply rely on plancache.c to regenerate it; rather we should start
|
||||||
|
* from scratch and rebuild the query text too. This is to cover cases
|
||||||
|
* such as table/column renames. We depend on the plancache machinery to
|
||||||
|
* detect possible invalidations, though.
|
||||||
|
*
|
||||||
|
* CAUTION: this check is only trustworthy if the caller has already
|
||||||
|
* locked both materialized views and base tables.
|
||||||
|
*/
|
||||||
|
plan = entry->plan;
|
||||||
|
if (plan && SPI_plan_is_valid(plan))
|
||||||
|
return plan;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Otherwise we might as well flush the cached plan now, to free a little
|
||||||
|
* memory space before we make a new one.
|
||||||
|
*/
|
||||||
|
entry->plan = NULL;
|
||||||
|
if (plan)
|
||||||
|
SPI_freeplan(plan);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mv_HashPreparedPlan
|
||||||
|
*
|
||||||
|
* Add another plan to our private SPI query plan hashtable.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan)
|
||||||
|
{
|
||||||
|
MV_QueryHashEntry *entry;
|
||||||
|
bool found;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On the first call initialize the hashtable
|
||||||
|
*/
|
||||||
|
if (!mv_query_cache)
|
||||||
|
mv_InitHashTables();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the new plan. We might be overwriting an entry previously found
|
||||||
|
* invalid by mv_FetchPreparedPlan.
|
||||||
|
*/
|
||||||
|
entry = (MV_QueryHashEntry *) hash_search(mv_query_cache,
|
||||||
|
(void *) key,
|
||||||
|
HASH_ENTER, &found);
|
||||||
|
Assert(!found || entry->plan == NULL);
|
||||||
|
entry->plan = plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mv_BuildQueryKey
|
||||||
|
*
|
||||||
|
* Construct a hashtable key for a prepared SPI plan for IVM.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* We assume struct MV_QueryKey contains no padding bytes, else we'd need
|
||||||
|
* to use memset to clear them.
|
||||||
|
*/
|
||||||
|
key->matview_id = matview_id;
|
||||||
|
key->query_type = query_type;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* AtAbort_IVM
|
* AtAbort_IVM
|
||||||
*
|
*
|
||||||
|
|
|
||||||
1
pg_ivm.c
1
pg_ivm.c
|
|
@ -28,7 +28,6 @@
|
||||||
#include "utils/varlena.h"
|
#include "utils/varlena.h"
|
||||||
|
|
||||||
#include "pg_ivm.h"
|
#include "pg_ivm.h"
|
||||||
#include "nodes/print.h"
|
|
||||||
|
|
||||||
PG_MODULE_MAGIC;
|
PG_MODULE_MAGIC;
|
||||||
|
|
||||||
|
|
|
||||||
4
pg_ivm.h
4
pg_ivm.h
|
|
@ -51,4 +51,8 @@ extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS);
|
||||||
extern void AtAbort_IVM(void);
|
extern void AtAbort_IVM(void);
|
||||||
extern bool isIvmName(const char *s);
|
extern bool isIvmName(const char *s);
|
||||||
|
|
||||||
|
/* ruleutils.c */
|
||||||
|
|
||||||
|
extern char *pg_ivm_get_querydef(Query *query, bool pretty);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
59
ruleutils.c
Normal file
59
ruleutils.c
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* ruleutils.c
|
||||||
|
* incremental view maintenance extension
|
||||||
|
* Routines for convert stored expressions/querytrees back to
|
||||||
|
* source text
|
||||||
|
*
|
||||||
|
* Portions Copyright (c) 2022, IVM Development Group
|
||||||
|
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
|
||||||
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
|
*
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 150000)
|
||||||
|
#include "utils/ruleutils.h"
|
||||||
|
#elif defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 140000)
|
||||||
|
#include "ruleutils_14.c"
|
||||||
|
#else
|
||||||
|
#include "ruleutils_13.c"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "pg_ivm.h"
|
||||||
|
|
||||||
|
/* Standard conversion of a "bool pretty" option to detailed flags */
|
||||||
|
#define GET_PRETTY_FLAGS(pretty) \
|
||||||
|
((pretty) ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) \
|
||||||
|
: PRETTYFLAG_INDENT)
|
||||||
|
|
||||||
|
/* ----------
|
||||||
|
* pg_get_querydef
|
||||||
|
*
|
||||||
|
* Public entry point to deparse one query parsetree.
|
||||||
|
* The pretty flags are determined by GET_PRETTY_FLAGS(pretty).
|
||||||
|
*
|
||||||
|
* The result is a palloc'd C string.
|
||||||
|
* ----------
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
pg_ivm_get_querydef(Query *query, bool pretty)
|
||||||
|
{
|
||||||
|
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 150000)
|
||||||
|
return pg_get_querydef(query, pretty);
|
||||||
|
#else
|
||||||
|
StringInfoData buf;
|
||||||
|
int prettyFlags;
|
||||||
|
|
||||||
|
prettyFlags = GET_PRETTY_FLAGS(pretty);
|
||||||
|
|
||||||
|
initStringInfo(&buf);
|
||||||
|
|
||||||
|
get_query_def(query, &buf, NIL, NULL, true,
|
||||||
|
prettyFlags, WRAP_COLUMN_DEFAULT, 0);
|
||||||
|
|
||||||
|
return buf.data;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
7844
ruleutils_13.c
Normal file
7844
ruleutils_13.c
Normal file
File diff suppressed because it is too large
Load diff
8214
ruleutils_14.c
Normal file
8214
ruleutils_14.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -123,9 +123,33 @@ DELETE FROM mv_base_a WHERE (i,j) = (2,30);
|
||||||
SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
|
SELECT * FROM mv_ivm_avg_bug ORDER BY 1,2,3;
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
|
|
||||||
-- not support MIN(), MAX() aggregate functions
|
-- support MIN(), MAX() aggregate functions
|
||||||
SELECT create_immv('mv_ivm_min_max', 'SELECT i, MIN(j) FROM mv_base_a GROUP BY i');
|
BEGIN;
|
||||||
SELECT create_immv('mv_ivm_min_max', 'SELECT i, MAX(j) FROM mv_base_a GROUP BY i');
|
SELECT create_immv('mv_ivm_min_max', 'SELECT i, MIN(j), MAX(j) FROM mv_base_a GROUP BY i');
|
||||||
|
SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
|
||||||
|
INSERT INTO mv_base_a VALUES
|
||||||
|
(1,11), (1,12),
|
||||||
|
(2,21), (2,22),
|
||||||
|
(3,31), (3,32),
|
||||||
|
(4,41), (4,42),
|
||||||
|
(5,51), (5,52);
|
||||||
|
SELECT * FROM mv_ivm_min_max ORDER BY 1,2,3;
|
||||||
|
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;
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
-- support MIN(), MAX() aggregate functions without GROUP clause
|
||||||
|
BEGIN;
|
||||||
|
SELECT create_immv('mv_ivm_min_max', 'SELECT MIN(j), MAX(j) FROM mv_base_a');
|
||||||
|
SELECT * FROM mv_ivm_min_max;
|
||||||
|
INSERT INTO mv_base_a VALUES
|
||||||
|
(0,0), (6,60), (7,70);
|
||||||
|
SELECT * FROM mv_ivm_min_max;
|
||||||
|
DELETE FROM mv_base_a WHERE (i,j) IN ((0,0), (7,70));
|
||||||
|
SELECT * FROM mv_ivm_min_max;
|
||||||
|
DELETE FROM mv_base_a;
|
||||||
|
SELECT * FROM mv_ivm_min_max;
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
-- support subquery in FROM clause
|
-- support subquery in FROM clause
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
@ -207,6 +231,28 @@ INSERT INTO base_t VALUES (1),(NULL);
|
||||||
SELECT * FROM mv ORDER BY i;
|
SELECT * FROM mv ORDER BY i;
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
CREATE TABLE base_t (i int, v int);
|
||||||
|
INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (1, 10), (1, 20);
|
||||||
|
SELECT create_immv('mv', 'SELECT i, sum(v) FROM base_t GROUP BY i');
|
||||||
|
SELECT * FROM mv ORDER BY i;
|
||||||
|
UPDATE base_t SET v = v * 10;
|
||||||
|
SELECT * FROM mv ORDER BY i;
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
CREATE TABLE base_t (i int, v int);
|
||||||
|
INSERT INTO base_t VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5);
|
||||||
|
SELECT create_immv('mv', 'SELECT i, min(v), max(v) FROM base_t GROUP BY i');
|
||||||
|
SELECT * FROM mv ORDER BY i;
|
||||||
|
DELETE FROM base_t WHERE v = 1;
|
||||||
|
SELECT * FROM mv ORDER BY i;
|
||||||
|
DELETE FROM base_t WHERE v = 3;
|
||||||
|
SELECT * FROM mv ORDER BY i;
|
||||||
|
DELETE FROM base_t WHERE v = 5;
|
||||||
|
SELECT * FROM mv ORDER BY i;
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
-- IMMV containing user defined type
|
-- IMMV containing user defined type
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue