hi based on v29. based on https://stackoverflow.com/a/4014981/1560347: I added a new function append_update_set_caluse, and deleted functions: {append_set_clause_for_count, append_set_clause_for_sum, append_set_clause_for_avg, append_set_clause_for_minmax}
I guess this way is more extensible/generic than yours. replaced the following code with the generic function: append_update_set_caluse. + /* For views with aggregates, we need to build SET clause for updating aggregate + * values. */ + if (query->hasAggs && IsA(tle->expr, Aggref)) + { + Aggref *aggref = (Aggref *) tle->expr; + const char *aggname = get_func_name(aggref->aggfnoid); + + /* + * We can use function names here because it is already checked if these + * can be used in IMMV by its OID at the definition time. + */ + + /* count */ + if (!strcmp(aggname, "count")) + append_set_clause_for_count(resname, aggs_set_old, aggs_set_new, aggs_list_buf); + + /* sum */ + else if (!strcmp(aggname, "sum")) + append_set_clause_for_sum(resname, aggs_set_old, aggs_set_new, aggs_list_buf); + + /* avg */ + else if (!strcmp(aggname, "avg")) + append_set_clause_for_avg(resname, aggs_set_old, aggs_set_new, aggs_list_buf, + format_type_be(aggref->aggtype)); + + else + elog(ERROR, "unsupported aggregate function: %s", aggname); + } ----------------------<<< attached is my refactor. there is some whitespace errors in the patches, you need use git apply --reject --whitespace=fix basedon_v29_matview_c_refactor_update_set_clause.patch Also you patch cannot use git apply, i finally found out bulk apply using gnu patch from https://serverfault.com/questions/102324/apply-multiple-patch-files. previously I just did it manually one by one. I think if you use { for i in $PATCHES/v29*.patch; do patch -p1 < $i; done } GNU patch, it will generate an .orig file for every modified file? -----------------<<<<< src/backend/commands/matview.c 2268: /* For tuple deletion */ maybe "/* For tuple deletion and update*/" is more accurate? -----------------<<<<< currently at here: src/test/regress/sql/incremental_matview.sql 98: -- support SUM(), COUNT() and AVG() aggregate functions 99: BEGIN; 100: CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm_agg AS SELECT i, SUM(j), COUNT(i), AVG(j) FROM mv_base_a GROUP BY i; 101: SELECT * FROM mv_ivm_agg ORDER BY 1,2,3,4; 102: INSERT INTO mv_base_a VALUES(2,100); src/backend/commands/matview.c 2858: if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT) 2859: elog(ERROR, "SPI_exec failed: %s", querybuf.data); then I debug, print out querybuf.data: WITH updt AS (UPDATE public.mv_ivm_agg AS mv SET __ivm_count__ = mv.__ivm_count__ OPERATOR(pg_catalog.+) diff.__ivm_count__ , sum = (CASE WHEN mv.__ivm_count_sum__ OPERATOR(pg_catalog.=) 0 AND diff.__ivm_count_sum__ OPERATOR(pg_catalog.=) 0 THEN NULL WHEN mv.sum IS NULL THEN diff.sum WHEN diff.sum IS NULL THEN mv.sum ELSE (mv.sum OPERATOR(pg_catalog.+) diff.sum) END), __ivm_count_sum__ = (mv.__ivm_count_sum__ OPERATOR(pg_catalog.+) diff.__ivm_count_sum__), count = (mv.count OPERATOR(pg_catalog.+) diff.count), avg = (CASE WHEN mv.__ivm_count_avg__ OPERATOR(pg_catalog.=) 0 AND diff.__ivm_count_avg__ OPERATOR(pg_catalog.=) 0 THEN NULL WHEN mv.__ivm_sum_avg__ IS NULL THEN diff.__ivm_sum_avg__ WHEN diff.__ivm_sum_avg__ IS NULL THEN mv.__ivm_sum_avg__ ELSE (mv.__ivm_sum_avg__ OPERATOR(pg_catalog.+) diff.__ivm_sum_avg__)::numeric END) OPERATOR(pg_catalog./) (mv.__ivm_count_avg__ OPERATOR(pg_catalog.+) diff.__ivm_count_avg__), __ivm_sum_avg__ = (CASE WHEN mv.__ivm_count_avg__ OPERATOR(pg_catalog.=) 0 AND diff.__ivm_count_avg__ OPERATOR(pg_catalog.=) 0 THEN NULL WHEN mv.__ivm_sum_avg__ IS NULL THEN diff.__ivm_sum_avg__ WHEN diff.__ivm_sum_avg__ IS NULL THEN mv.__ivm_sum_avg__ ELSE (mv.__ivm_sum_avg__ OPERATOR(pg_catalog.+) diff.__ivm_sum_avg__) END), __ivm_count_avg__ = (mv.__ivm_count_avg__ OPERATOR(pg_catalog.+) diff.__ivm_count_avg__) FROM new_delta AS diff WHERE (mv.i OPERATOR(pg_catalog.=) diff.i OR (mv.i IS NULL AND diff.i IS NULL)) RETURNING mv.i) INSERT INTO public.mv_ivm_agg (i, sum, count, avg, __ivm_count_sum__, __ivm_count_avg__, __ivm_sum_avg__, __ivm_count__) SELECT i, sum, count, avg, __ivm_count_sum__, __ivm_count_avg__, __ivm_sum_avg__, __ivm_count__ FROM new_delta AS diff WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE (mv.i OPERATOR(pg_catalog.=) diff.i OR (mv.i IS NULL AND diff.i IS NULL))); At this final SPI_exec, we have a update statement with related columns { __ivm_count_sum__, sum, __ivm_count__, count, avg, __ivm_sum_avg__, __ivm_count_avg__}. At this time, my mind stops working, querybuf.data is way too big, but I still feel like there is some logic associated with these columns, maybe we can use it as an assertion to prove that this query (querybuf.len = 1834) is indeed correct. Since the apply delta query is quite complex, I feel like adding some "if debug then print out the final querybuf.data end if" would be a good idea. we add hidden columns somewhere, also to avoid corner cases, so maybe somewhere we should assert total attribute number is sane.
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index eff512d4..33f385cb 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -149,12 +149,39 @@ typedef enum IVM_SUB } IvmOp; +typedef enum +{ + BAD_AGGFUNC, + IVM_COUNT, + IVM_SUM, + IVM_AVG, + IVM_MIN, + IVM_MAX +} IvmAggType; + +typedef struct aggname_entry +{ + const char *agg_name; + int val; +} aggname_entry; + +/* map aggregate name with enum */ +static aggname_entry ivm_agg_lookup[] = +{ + { "count",IVM_COUNT}, + { "sum", IVM_SUM}, + { "avg", IVM_AVG}, + { "min",IVM_MIN}, + { "max",IVM_MAX} +}; + /* ENR name for materialized view delta */ #define NEW_DELTA_ENRNAME "new_delta" #define OLD_DELTA_ENRNAME "old_delta" static int matview_maintenance_depth = 0; +static int get_ivm_aggfunc(const char *agg_name); static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo); static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self); static void transientrel_shutdown(DestReceiver *self); @@ -191,16 +218,7 @@ static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable * static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores, TupleDesc tupdesc_old, TupleDesc tupdesc_new, Query *query, bool use_count, char *count_colname); -static void append_set_clause_for_count(const char *resname, StringInfo buf_old, - StringInfo buf_new,StringInfo aggs_list); -static void append_set_clause_for_sum(const char *resname, StringInfo buf_old, - StringInfo buf_new, StringInfo aggs_list); -static void append_set_clause_for_avg(const char *resname, StringInfo buf_old, - StringInfo buf_new, StringInfo aggs_list, - 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, const char* count_col, const char *castType); static char *get_null_condition_string(IvmOp op, const char *arg1, const char *arg2, @@ -235,6 +253,25 @@ 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, bool is_abort); +static void append_update_set_caluse(const char *aggname, char *resname, StringInfo buf_old, + StringInfo buf_new,StringInfo aggs_list, + char *aggtype); + +int +get_ivm_aggfunc(const char *agg_name) +{ +#define NKEYS (sizeof(ivm_agg_lookup)/sizeof(aggname_entry)) + + int i; + for (i=0; i < NKEYS; i++) + { + aggname_entry *entry = &ivm_agg_lookup[i]; + if (strcmp(entry->agg_name, agg_name) == 0) + return entry->val; + } + return BAD_AGGFUNC; +} + /* * SetMatViewPopulatedState * Mark a materialized view as populated, or not. @@ -2210,38 +2247,23 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n { Aggref *aggref = (Aggref *) tle->expr; const char *aggname = get_func_name(aggref->aggfnoid); + char *aggtype = format_type_be(aggref->aggtype); /* * We can use function names here because it is already checked if these * can be used in IMMV by its OID at the definition time. */ - - /* count */ - if (!strcmp(aggname, "count")) - append_set_clause_for_count(resname, aggs_set_old, aggs_set_new, aggs_list_buf); - - /* sum */ - else if (!strcmp(aggname, "sum")) - append_set_clause_for_sum(resname, aggs_set_old, aggs_set_new, aggs_list_buf); - - /* avg */ - else if (!strcmp(aggname, "avg")) - append_set_clause_for_avg(resname, aggs_set_old, aggs_set_new, aggs_list_buf, - format_type_be(aggref->aggtype)); - - /* min/max */ - else if (!strcmp(aggname, "min") || !strcmp(aggname, "max")) + (void) append_update_set_caluse(aggname, resname, aggs_set_old, + aggs_set_new, aggs_list_buf, aggtype); + + 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 - elog(ERROR, "unsupported aggregate function: %s", aggname); } } @@ -2338,224 +2360,6 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n elog(ERROR, "SPI_finish failed"); } -/* - * append_set_clause_for_count - * - * Append SET clause string for count aggregation to given buffers. - * Also, append resnames required for calculating the aggregate value. - */ -static void -append_set_clause_for_count(const char *resname, StringInfo buf_old, - StringInfo buf_new,StringInfo aggs_list) -{ - /* For tuple deletion */ - if (buf_old) - { - /* resname = mv.resname - t.resname */ - appendStringInfo(buf_old, - ", %s = %s", - quote_qualified_identifier(NULL, resname), - get_operation_string(IVM_SUB, resname, "mv", "t", NULL, NULL)); - } - /* For tuple insertion */ - if (buf_new) - { - /* resname = mv.resname + diff.resname */ - appendStringInfo(buf_new, - ", %s = %s", - quote_qualified_identifier(NULL, resname), - get_operation_string(IVM_ADD, resname, "mv", "diff", NULL, NULL)); - } - - appendStringInfo(aggs_list, ", %s", - quote_qualified_identifier("diff", resname) - ); -} - -/* - * append_set_clause_for_sum - * - * Append SET clause string for sum aggregation to given buffers. - * Also, append resnames required for calculating the aggregate value. - */ -static void -append_set_clause_for_sum(const char *resname, StringInfo buf_old, - StringInfo buf_new, StringInfo aggs_list) -{ - char *count_col = IVM_colname("count", resname); - - /* For tuple deletion */ - if (buf_old) - { - /* sum = mv.sum - t.sum */ - appendStringInfo(buf_old, - ", %s = %s", - quote_qualified_identifier(NULL, resname), - get_operation_string(IVM_SUB, resname, "mv", "t", count_col, NULL) - ); - /* 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) - { - /* sum = mv.sum + diff.sum */ - appendStringInfo(buf_new, - ", %s = %s", - quote_qualified_identifier(NULL, resname), - get_operation_string(IVM_ADD, resname, "mv", "diff", count_col, NULL) - ); - /* 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)) - ); -} - -/* - * append_set_clause_for_avg - * - * Append SET clause string for avg aggregation to given buffers. - * Also, append resnames required for calculating the aggregate value. - */ -static void -append_set_clause_for_avg(const char *resname, StringInfo buf_old, - StringInfo buf_new, StringInfo aggs_list, - const char *aggtype) -{ - char *sum_col = IVM_colname("sum", resname); - char *count_col = IVM_colname("count", resname); - - /* For tuple deletion */ - if (buf_old) - { - /* avg = (mv.sum - t.sum)::aggtype / (mv.count - t.count) */ - appendStringInfo(buf_old, - ", %s = %s OPERATOR(pg_catalog./) %s", - quote_qualified_identifier(NULL, resname), - get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, aggtype), - get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL) - ); - /* sum = mv.sum - t.sum */ - appendStringInfo(buf_old, - ", %s = %s", - quote_qualified_identifier(NULL, sum_col), - get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, NULL) - ); - /* 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) - { - /* avg = (mv.sum + diff.sum)::aggtype / (mv.count + diff.count) */ - appendStringInfo(buf_new, - ", %s = %s OPERATOR(pg_catalog./) %s", - quote_qualified_identifier(NULL, resname), - get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, aggtype), - get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL) - ); - /* sum = mv.sum + diff.sum */ - appendStringInfo(buf_new, - ", %s = %s", - quote_qualified_identifier(NULL, sum_col), - get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, NULL) - ); - /* 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, %s", - quote_qualified_identifier("diff", resname), - quote_qualified_identifier("diff", IVM_colname("sum", resname)), - quote_qualified_identifier("diff", IVM_colname("count", resname)) - ); -} - -/* - * 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 * @@ -3482,3 +3286,228 @@ isIvmName(const char *s) return (strncmp(s, "__ivm_", 6) == 0); return false; } + +void +append_update_set_caluse(const char *aggname, char *resname, StringInfo buf_old, + StringInfo buf_new, StringInfo aggs_list, + char *aggtype) +{ + char *count_col = IVM_colname("count", resname); + char *sum_col = IVM_colname("sum", resname); + + switch (get_ivm_aggfunc(aggname)) + { + case IVM_COUNT: + if (buf_old) + appendStringInfo(buf_old, + ", %s = %s", + quote_qualified_identifier(NULL, resname), + get_operation_string(IVM_SUB, resname, "mv", "t", NULL, NULL)); + + if (buf_new) + appendStringInfo(buf_new, + ", %s = %s", + quote_qualified_identifier(NULL, resname), + get_operation_string(IVM_ADD, resname, "mv", "diff", NULL, NULL)); + + appendStringInfo(aggs_list, ", %s", + quote_qualified_identifier("diff", resname)); + break; + + case IVM_SUM: + if (buf_old) + { + /* sum = mv.sum - t.sum */ + appendStringInfo(buf_old, + ", %s = %s", + quote_qualified_identifier(NULL, resname), + get_operation_string(IVM_SUB, resname, "mv", "t", count_col, NULL) + ); + + /* 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) + ); + } + + if (buf_new) + { + /* sum = mv.sum + diff.sum */ + appendStringInfo(buf_new, + ", %s = %s", + quote_qualified_identifier(NULL, resname), + get_operation_string(IVM_ADD, resname, "mv", "diff", count_col, NULL) + ); + + /* 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))); + break; + + case IVM_AVG: + if (buf_old) + { + /* avg = (mv.sum - t.sum)::aggtype / (mv.count - t.count) */ + appendStringInfo(buf_old, + ", %s = %s OPERATOR(pg_catalog./) %s", + quote_qualified_identifier(NULL, resname), + get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, aggtype), + get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL) + ); + + /* sum = mv.sum - t.sum */ + appendStringInfo(buf_old, + ", %s = %s", + quote_qualified_identifier(NULL, sum_col), + get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, NULL) + ); + + /* 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) + ); + } + + if (buf_new) + { + /* avg = (mv.sum + diff.sum)::aggtype / (mv.count + diff.count) */ + appendStringInfo(buf_new, + ", %s = %s OPERATOR(pg_catalog./) %s", + quote_qualified_identifier(NULL, resname), + get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, aggtype), + get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL) + ); + /* sum = mv.sum + diff.sum */ + appendStringInfo(buf_new, + ", %s = %s", + quote_qualified_identifier(NULL, sum_col), + get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, NULL) + ); + /* 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, %s", + quote_qualified_identifier("diff", resname), + quote_qualified_identifier("diff", IVM_colname("sum", resname)), + quote_qualified_identifier("diff", IVM_colname("count", resname))); + + break; + + case IVM_MIN: + 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) + ); + } + + if (buf_new) + { + /* min = LEAST(mv.min, diff.min) */ + 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), + "LEAST", + 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))); + break; + + case IVM_MAX: + 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) + ); + } + + if (buf_new) + { + /* 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), + "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))); + break; + + case BAD_AGGFUNC: + elog(ERROR, "unsupported aggregate function: %s", aggname); + break; + + default: + elog(ERROR, "unsupported aggregate function: %s", aggname); + break; + } + return; +} \ No newline at end of file