From f29aa802b8b506d59ed938af94fa34a475a320bf Mon Sep 17 00:00:00 2001 From: hoshiai Date: Thu, 26 Jan 2023 11:20:50 +0900 Subject: [PATCH] Support exists_subquery Simple EXISTS clause is supported by this patch. A query is not supported when WHERE clause has OR-condition or JOIN-condition is not contained by targetlist. --- createas.c | 118 +++++++++++++++++++-- expected/pg_ivm.out | 158 ++++++++++++++++++++++++++-- matview.c | 246 +++++++++++++++++++++++++++++++++++++++++++- pg_ivm.h | 2 + sql/pg_ivm.sql | 29 +++++- 5 files changed, 533 insertions(+), 20 deletions(-) diff --git a/createas.c b/createas.c index 937ebc8..e2a0880 100644 --- a/createas.c +++ b/createas.c @@ -66,6 +66,8 @@ typedef struct { bool has_agg; bool has_subquery; + bool in_exists_subquery; /* true, if it is in a exists subquery */ + List *exists_qual_vars; int sublevels_up; } check_ivm_restriction_context; @@ -261,7 +263,7 @@ ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt, CreateIndexOnIMMV(viewQuery, matviewRel, true); /* Create triggers on incremental maintainable materialized view */ - CreateIvmTriggersOnBaseTables(viewQuery, matviewOid, true); + CreateIvmTriggersOnBaseTables(query, matviewOid, true); /* Create triggers to prevent IMMV from beeing changed */ CreateChangePreventTrigger(matviewOid); @@ -278,6 +280,11 @@ ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt, * * count(*) is added for counting distinct tuples in views. * Also, additional hidden columns are added for aggregate values. + * + * EXISTS sublink is rewritten to LATERAL subquery with HAVING + * clause to check count(*) > 0. In addition, a counting column + * referring to count(*) in this subquery is added to the original + * target list. */ Query * rewriteQueryForIMMV(Query *query, List *colNames) @@ -301,6 +308,51 @@ rewriteQueryForIMMV(Query *query, List *colNames) rewritten = copyObject(query); pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET; + /* + * If this query has EXISTS clause, rewrite query and + * add __ivm_exists_count_X__ column. + */ + if (rewritten->hasSubLinks) + { + ListCell *lc; + RangeTblEntry *rte; + int varno = 0; + + /* rewrite EXISTS sublink to LATERAL subquery */ + rewrite_query_for_exists_subquery(rewritten); + + /* Add counting column referring to count(*) in EXISTS clause */ + foreach(lc, rewritten->rtable) + { + char *columnName; + int attnum; + Node *countCol = NULL; + varno++; + + rte = (RangeTblEntry *) lfirst(lc); + if (!rte->subquery || !rte->lateral) + continue; + pstate->p_rtable = rewritten->rtable; + + columnName = getColumnNameStartWith(rte, "__ivm_exists", &attnum); + if (columnName == NULL) + continue; + countCol = (Node *)makeVar(varno, attnum, + INT8OID, -1, InvalidOid, 0); + + + if (countCol != NULL) + { + tle = makeTargetEntry((Expr *) countCol, + list_length(rewritten->targetList) + 1, + pstrdup(columnName), + false); + rewritten->targetList = list_concat(rewritten->targetList, list_make1(tle)); + } + } + } + + /* group keys must be in targetlist */ if (rewritten->groupClause) { @@ -691,7 +743,7 @@ CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock static void check_ivm_restriction(Node *node) { - check_ivm_restriction_context context = {false, false}; + check_ivm_restriction_context context = {false, false, false, NIL, 0}; check_ivm_restriction_walker(node, &context); } @@ -841,6 +893,39 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) query_tree_walker(qry, check_ivm_restriction_walker, (void *) context, QTW_IGNORE_RT_SUBQUERIES); + /* additional restriction checks for exists subquery */ + if (context->exists_qual_vars != NIL && context->sublevels_up == 0) + { + ListCell *lc; + + foreach (lc, context->exists_qual_vars) + { + Var *var = (Var *) lfirst(lc); + ListCell *lc2; + bool found = false; + + foreach(lc2, qry->targetList) + { + TargetEntry *tle = lfirst(lc2); + Var *var2; + + if (!IsA(tle->expr, Var)) + continue; + var2 = (Var *) tle->expr; + if (var->varno == var2->varno && var->varattno == var2->varattno) + { + found = true; + break; + } + } + if (!found) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("this query is not allowed on incrementally maintainable materialized view"), + errhint("targetlist must contain vars that are referred to in EXISTS subquery"))); + } + } + break; } case T_CommonTableExpr: @@ -927,12 +1012,33 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) expression_tree_walker(node, check_ivm_restriction_walker, (void *) context); break; } + case T_Var: + { + Var *variable = (Var *) node; + /* If EXISTS subquery refers to vars of the upper query, collect these vars */ + if (variable->varlevelsup > 0 && context->in_exists_subquery) + context->exists_qual_vars = lappend(context->exists_qual_vars, node); + break; + } case T_SubLink: { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("unsupported subquery on incrementally maintainable materialized view"), - errhint("Only simple subquery in FROM clause is supported."))); + /* Now, EXISTS clause is supported only */ + SubLink *sublink = (SubLink *) node; + if (sublink->subLinkType != EXISTS_SUBLINK) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("this query is not allowed on incrementally maintainable materialized view"), + errhint("subquery in WHERE clause only supports subquery with EXISTS clause"))); + if (context->sublevels_up > 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("nested subquery is not supported on incrementally maintainable materialized view"))); + + context->in_exists_subquery = true; + context->sublevels_up++; + check_ivm_restriction_walker(sublink->subselect, context); + context->sublevels_up--; + context->in_exists_subquery = false; break; } default: diff --git a/expected/pg_ivm.out b/expected/pg_ivm.out index 7f5ded0..f0a8cba 100644 --- a/expected/pg_ivm.out +++ b/expected/pg_ivm.out @@ -661,6 +661,139 @@ SELECT * FROM mv_ri ORDER BY i2; 10 | 10 (2 rows) +ROLLBACK; +-- support subquery for using EXISTS() +BEGIN; +SELECT create_immv('mv_ivm_exists_subquery', 'SELECT a.i, a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i)'); +NOTICE: could not create an index on immv "mv_ivm_exists_subquery" 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 +------------- + 4 +(1 row) + +SELECT create_immv('mv_ivm_exists_subquery2', 'SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) AND a.i > 2'); +NOTICE: could not create an index on immv "mv_ivm_exists_subquery2" 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 +------------- + 2 +(1 row) + +SELECT * FROM mv_ivm_exists_subquery ORDER BY i, j; + i | j | __ivm_exists_count_0__ +---+----+------------------------ + 1 | 10 | 1 + 2 | 20 | 1 + 3 | 30 | 1 + 4 | 40 | 1 +(4 rows) + +SELECT * FROM mv_ivm_exists_subquery2 ORDER BY i, j; + i | j | __ivm_exists_count_0__ +---+----+------------------------ + 3 | 30 | 1 + 4 | 40 | 1 +(2 rows) + +INSERT INTO mv_base_a VALUES(1,10),(6,60),(3,30),(3,300); +SELECT * FROM mv_ivm_exists_subquery ORDER BY i, j; + i | j | __ivm_exists_count_0__ +---+-----+------------------------ + 1 | 10 | 1 + 1 | 10 | 1 + 2 | 20 | 1 + 3 | 30 | 1 + 3 | 30 | 1 + 3 | 300 | 1 + 4 | 40 | 1 +(7 rows) + +SELECT * FROM mv_ivm_exists_subquery2 ORDER BY i, j; + i | j | __ivm_exists_count_0__ +---+-----+------------------------ + 3 | 30 | 1 + 3 | 30 | 1 + 3 | 300 | 1 + 4 | 40 | 1 +(4 rows) + +INSERT INTO mv_base_b VALUES(1,101); +INSERT INTO mv_base_b VALUES(1,111); +INSERT INTO mv_base_b VALUES(2,102); +INSERT INTO mv_base_b VALUES(6,106); +SELECT * FROM mv_ivm_exists_subquery ORDER BY i, j; + i | j | __ivm_exists_count_0__ +---+-----+------------------------ + 1 | 10 | 3 + 1 | 10 | 3 + 2 | 20 | 2 + 3 | 30 | 1 + 3 | 30 | 1 + 3 | 300 | 1 + 4 | 40 | 1 + 6 | 60 | 1 +(8 rows) + +SELECT * FROM mv_ivm_exists_subquery2 ORDER BY i, j; + i | j | __ivm_exists_count_0__ +---+-----+------------------------ + 3 | 30 | 1 + 3 | 30 | 1 + 3 | 300 | 1 + 4 | 40 | 1 + 6 | 60 | 1 +(5 rows) + +UPDATE mv_base_a SET i = 1 WHERE j =60; +UPDATE mv_base_b SET i = 10 WHERE k = 101; +UPDATE mv_base_b SET k = 1002 WHERE k = 102; +SELECT * FROM mv_ivm_exists_subquery ORDER BY i, j; + i | j | __ivm_exists_count_0__ +---+-----+------------------------ + 1 | 10 | 1 + 1 | 10 | 1 + 1 | 60 | 1 + 2 | 20 | 2 + 3 | 30 | 1 + 3 | 30 | 1 + 3 | 300 | 1 + 4 | 40 | 1 +(8 rows) + +SELECT * FROM mv_ivm_exists_subquery2 ORDER BY i, j; + i | j | __ivm_exists_count_0__ +---+-----+------------------------ + 3 | 30 | 1 + 3 | 30 | 1 + 3 | 300 | 1 + 4 | 40 | 1 +(4 rows) + +DELETE FROM mv_base_a WHERE (i,j) = (1,60); +DELETE FROM mv_base_b WHERE i = 2; +SELECT * FROM mv_ivm_exists_subquery ORDER BY i, j; + i | j | __ivm_exists_count_0__ +---+-----+------------------------ + 1 | 10 | 1 + 1 | 10 | 1 + 3 | 30 | 1 + 3 | 30 | 1 + 3 | 300 | 1 + 4 | 40 | 1 +(6 rows) + +SELECT * FROM mv_ivm_exists_subquery2 ORDER BY i, j; + i | j | __ivm_exists_count_0__ +---+-----+------------------------ + 3 | 30 | 1 + 3 | 30 | 1 + 3 | 300 | 1 + 4 | 40 | 1 +(4 rows) + ROLLBACK; -- support simple subquery in FROM clause BEGIN; @@ -688,25 +821,28 @@ SELECT * FROM mv_ivm_subquery ORDER BY i,j; ROLLBACK; -- disallow non-simple subqueries -SELECT create_immv('mv_ivm_exists_subquery', 'SELECT a.i, a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i)'); -ERROR: unsupported subquery on incrementally maintainable materialized view -HINT: Only simple subquery in FROM clause is supported. SELECT create_immv('mv_ivm_subquery', 'SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i'); ERROR: aggregate functions in nested query are not supported on incrementally maintainable materialized view SELECT create_immv('mv_ivm_subquery', 'SELECT a.i,a.j FROM mv_base_a a, (SELECT DISTINCT i FROM mv_base_b) b WHERE a.i = b.i'); ERROR: DISTINCT clause in nested query are not supported on incrementally maintainable materialized view SELECT create_immv('mv_ivm_subquery', 'SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 )'); -ERROR: unsupported subquery on incrementally maintainable materialized view -HINT: Only simple subquery in FROM clause is supported. +ERROR: this query is not allowed on incrementally maintainable materialized view +HINT: subquery in WHERE clause only supports subquery with EXISTS clause SELECT create_immv('mv_ivm_subquery', 'SELECT i,j, (SELECT k FROM mv_base_b LIMIT 1) FROM mv_base_a a'); -ERROR: unsupported subquery on incrementally maintainable materialized view -HINT: Only simple subquery in FROM clause is supported. +ERROR: this query is not allowed on incrementally maintainable materialized view +HINT: subquery in WHERE clause only supports subquery with EXISTS clause SELECT create_immv('mv_ivm_subquery', 'SELECT i,j, (SELECT k FROM mv_base_b LIMIT 1) + 1 FROM mv_base_a a'); -ERROR: unsupported subquery on incrementally maintainable materialized view -HINT: Only simple subquery in FROM clause is supported. +ERROR: this query is not allowed on incrementally maintainable materialized view +HINT: subquery in WHERE clause only supports subquery with EXISTS clause SELECT create_immv('mv_ivm_subquery', 'SELECT * FROM generate_series(1, (SELECT k FROM mv_base_b LIMIT 1)) AS v'); -ERROR: unsupported subquery on incrementally maintainable materialized view -HINT: Only simple subquery in FROM clause is supported. +ERROR: this query is not allowed on incrementally maintainable materialized view +HINT: subquery in WHERE clause only supports subquery with EXISTS clause +SELECT create_immv('mv_ivm_subquery', 'SELECT a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i)'); +ERROR: this query is not allowed on incrementally maintainable materialized view +HINT: targetlist must contain vars that are referred to in EXISTS subquery +SELECT create_immv('mv_ivm_subquery', 'SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 2'); +ERROR: this query is not allowed on incrementally maintainable materialized view +HINT: OR or NOT conditions and EXISTS condition are not used together -- support join subquery in FROM clause BEGIN; SELECT create_immv('mv_ivm_join_subquery', 'SELECT i, j, k FROM ( SELECT i, a.j, b.k FROM mv_base_b b INNER JOIN mv_base_a a USING(i)) tmp'); diff --git a/matview.c b/matview.c index 4a2d3e1..858da7d 100644 --- a/matview.c +++ b/matview.c @@ -29,6 +29,7 @@ #include "executor/tstoreReceiver.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" #include "parser/analyze.h" #include "parser/parse_clause.h" @@ -37,6 +38,7 @@ #include "parser/parser.h" #include "pgstat.h" #include "rewrite/rewriteHandler.h" +#include "rewrite/rewriteManip.h" #include "rewrite/rowsecurity.h" #include "storage/lmgr.h" #include "tcop/tcopprot.h" @@ -973,6 +975,10 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS) i++; } + /* Rewrite for the EXISTS clause */ + if (rewritten->hasSubLinks) + rewrite_query_for_exists_subquery(rewritten); + /* Set all tables in the query to pre-update state */ rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables, pstate, NIL, matviewOid); @@ -1034,15 +1040,40 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS) foreach(lc2, table->rte_paths) { List *rte_path = lfirst(lc2); + int i; + Query *querytree = rewritten; + RangeTblEntry *rte; TupleDesc tupdesc_old; TupleDesc tupdesc_new; bool use_count = false; char *count_colname = NULL; - count_colname = pstrdup("__ivm_count__"); + /* check if the modified table is in EXISTS clause. */ + for (i = 0; i< list_length(rte_path); i++) + { + int index = lfirst_int(list_nth_cell(rte_path, i)); + rte = (RangeTblEntry *)lfirst(list_nth_cell(querytree->rtable, index - 1)); - if (query->hasAggs || query->distinctClause) + if (rte != NULL && rte->rtekind == RTE_SUBQUERY) + { + querytree = rte->subquery; + if (rte->lateral) + { + int attnum; + count_colname = getColumnNameStartWith(rte, "__ivm_exists", &attnum); + if (count_colname) + { + use_count = true; + } + } + } + } + + if (count_colname == NULL && (query->hasAggs || query->distinctClause)) + { + count_colname = pstrdup("__ivm_count__"); use_count = true; + } /* calculate delta tables */ calc_delta(table, rte_path, rewritten, dest_old, dest_new, @@ -1486,6 +1517,8 @@ rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate) TargetEntry *tle_count; FuncCall *fn; Node *node; + int varno = 0; + ListCell *tbl_lc; /* For aggregate views */ if (query->hasAggs) @@ -1504,6 +1537,34 @@ rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate) query->targetList = list_concat(query->targetList, aggs); } + /* Add count(*) used for EXISTS clause */ + foreach(tbl_lc, query->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *)lfirst(tbl_lc); + varno++; + if (rte->subquery) + { + char *columnName; + int attnum; + + /* search ivm_exists_count_X__ column in RangeTblEntry */ + columnName = getColumnNameStartWith(rte, "__ivm_exists", &attnum); + if (columnName == NULL) + continue; + + node = (Node *)makeVar(varno ,attnum, + INT8OID, -1, InvalidOid, 0); + + if (node == NULL) + continue; + tle_count = makeTargetEntry((Expr *) node, + list_length(query->targetList) + 1, + pstrdup(columnName), + false); + query->targetList = lappend(query->targetList, tle_count); + } + } + /* Add count(*) for counting distinct tuples in views */ #if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 140000) fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1); @@ -1526,6 +1587,161 @@ rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate) return query; } +/* + * rewrite_query_for_exists_subquery + * + * Rewrite EXISTS sublink in WHERE to LATERAL subquery + */ +static Query * +rewrite_exists_subquery_walker(Query *query, Node *node, int *count) +{ + /* This can recurse, so check for excessive recursion */ + check_stack_depth(); + + switch (nodeTag(node)) + { + case T_Query: + { + FromExpr *fromexpr; + + /* get subquery in WHERE clause */ + fromexpr = (FromExpr *)query->jointree; + query = rewrite_exists_subquery_walker(query, fromexpr->quals, count); + /* drop subquery in WHERE clause */ + if (IsA(fromexpr->quals, SubLink)) + fromexpr->quals = NULL; + break; + } + case T_BoolExpr: + { + BoolExprType type; + + type = ((BoolExpr *) node)->boolop; + switch (type) + { + ListCell *lc; + case AND_EXPR: + foreach(lc, ((BoolExpr *)node)->args) + { + Node *opnode = (Node *)lfirst(lc); + query = rewrite_exists_subquery_walker(query, opnode, count); + /* overwrite SubLink node if it is contained in AND_EXPR */ + if (IsA(opnode, SubLink)) + lfirst(lc) = makeConst(BOOLOID, -1, InvalidOid, sizeof(bool), BoolGetDatum(true), false, true); + } + break; + case OR_EXPR: + case NOT_EXPR: + if (checkExprHasSubLink(node)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("this query is not allowed on incrementally maintainable materialized view"), + errhint("OR or NOT conditions and EXISTS condition are not used together"))); + break; + } + break; + } + case T_SubLink: + { + char aliasName[NAMEDATALEN]; + char columnName[NAMEDATALEN]; + Query *subselect; + ParseState *pstate; + RangeTblEntry *rte; + RangeTblRef *rtr; + Alias *alias; + Oid opId; + ParseNamespaceItem *nsitem; + + TargetEntry *tle_count; + FuncCall *fn; + Node *fn_node; + Expr *opexpr; + + SubLink *sublink = (SubLink *)node; + /* raise ERROR if there is non-EXISTS sublink */ + if (sublink->subLinkType != EXISTS_SUBLINK) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("this query is not allowed on incrementally maintainable materialized view"), + errhint("subquery in WHERE clause only supports subquery with EXISTS clause"))); + + subselect = (Query *)sublink->subselect; + + /* raise ERROR if the sublink has CTE */ + if (subselect->cteList) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CTE is not supported on incrementally maintainable materialized view"))); + + pstate = make_parsestate(NULL); + pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET; + + /* + * convert EXISTS subquery into LATERAL subquery in FROM clause. + */ + + snprintf(aliasName, sizeof(aliasName), "__ivm_exists_subquery_%d__", *count); + snprintf(columnName, sizeof(columnName), "__ivm_exists_count_%d__", *count); + + /* add COUNT(*) for counting rows that meet exists condition */ + fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1); + fn->agg_star = true; + fn_node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1); + tle_count = makeTargetEntry((Expr *) fn_node, + list_length(subselect->targetList) + 1, + columnName, + false); + /* add __ivm_exists_count__ column */ + subselect->targetList = list_concat(subselect->targetList, list_make1(tle_count)); + subselect->hasAggs = true; + + /* add a sub-query whth LATERAL into from clause */ + alias = makeAlias(aliasName, NIL); + nsitem = addRangeTableEntryForSubquery(pstate, subselect, alias, true, true); + rte = nsitem->p_rte; + query->rtable = lappend(query->rtable, rte); + + /* assume the new RTE is at the end */ + rtr = makeNode(RangeTblRef); + rtr->rtindex = list_length(query->rtable); + ((FromExpr *)query->jointree)->fromlist = lappend(((FromExpr *)query->jointree)->fromlist, rtr); + + /* + * EXISTS condition is converted to HAVING count(*) > 0. + * We use make_opcllause() to get int84gt( '>' operator). We might be able to use make_op(). + */ + opId = OpernameGetOprid(list_make1(makeString(">")), INT8OID, INT4OID); + opexpr = make_opclause(opId, BOOLOID, false, + (Expr *)fn_node, + (Expr *)makeConst(INT4OID, -1, InvalidOid, sizeof(int32), Int32GetDatum(0), false, true), + InvalidOid, InvalidOid); + fix_opfuncids((Node *) opexpr); + query->hasSubLinks = false; + + subselect->havingQual = (Node *)opexpr; + (*count)++; + break; + } + default: + break; + } + return query; +} + +Query * +rewrite_query_for_exists_subquery(Query *query) +{ + int count = 0; + if (query->hasAggs) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("this query is not allowed on incrementally maintainable materialized view"), + errhint("aggregate function and EXISTS condition are not supported at the same time"))); + + return rewrite_exists_subquery_walker(query, (Node *)query, &count); +} + /* * calc_delta * @@ -2948,6 +3164,32 @@ clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry, bool is_abort) hash_search(mv_trigger_info, (void *) &entry->matview_id, HASH_REMOVE, &found); } +/* + * getColumnNameStartWith + * + * Search a column name which starts with the given string from the given RTE, + * and return the first found one or NULL if not found. + */ +char * +getColumnNameStartWith(RangeTblEntry *rte, char *str, int *attnum) +{ + char *colname; + ListCell *lc; + Alias *alias = rte->eref; + + (*attnum) = 0; + foreach(lc, alias->colnames) + { + (*attnum)++; + if (strncmp(strVal(lfirst(lc)), str, strlen(str)) == 0) + { + colname = pstrdup(strVal(lfirst(lc))); + return colname; + } + } + return NULL; +} + /* * isIvmName * diff --git a/pg_ivm.h b/pg_ivm.h index d7d5759..402abc1 100644 --- a/pg_ivm.h +++ b/pg_ivm.h @@ -52,8 +52,10 @@ extern bool ImmvIncrementalMaintenanceIsEnabled(void); extern Query *get_immv_query(Relation matviewRel); extern Datum IVM_immediate_before(PG_FUNCTION_ARGS); extern Datum IVM_immediate_maintenance(PG_FUNCTION_ARGS); +extern Query* rewrite_query_for_exists_subquery(Query *query); extern Datum ivm_visible_in_prestate(PG_FUNCTION_ARGS); extern void AtAbort_IVM(void); +extern char *getColumnNameStartWith(RangeTblEntry *rte, char *str, int *attnum); extern bool isIvmName(const char *s); /* ruleutils.c */ diff --git a/sql/pg_ivm.sql b/sql/pg_ivm.sql index ac558e3..ddc2053 100644 --- a/sql/pg_ivm.sql +++ b/sql/pg_ivm.sql @@ -227,6 +227,32 @@ DELETE FROM ri1 WHERE i=2; SELECT * FROM mv_ri ORDER BY i2; ROLLBACK; +-- support subquery for using EXISTS() +BEGIN; +SELECT create_immv('mv_ivm_exists_subquery', 'SELECT a.i, a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i)'); +SELECT create_immv('mv_ivm_exists_subquery2', 'SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) AND a.i > 2'); +SELECT * FROM mv_ivm_exists_subquery ORDER BY i, j; +SELECT * FROM mv_ivm_exists_subquery2 ORDER BY i, j; +INSERT INTO mv_base_a VALUES(1,10),(6,60),(3,30),(3,300); +SELECT * FROM mv_ivm_exists_subquery ORDER BY i, j; +SELECT * FROM mv_ivm_exists_subquery2 ORDER BY i, j; +INSERT INTO mv_base_b VALUES(1,101); +INSERT INTO mv_base_b VALUES(1,111); +INSERT INTO mv_base_b VALUES(2,102); +INSERT INTO mv_base_b VALUES(6,106); +SELECT * FROM mv_ivm_exists_subquery ORDER BY i, j; +SELECT * FROM mv_ivm_exists_subquery2 ORDER BY i, j; +UPDATE mv_base_a SET i = 1 WHERE j =60; +UPDATE mv_base_b SET i = 10 WHERE k = 101; +UPDATE mv_base_b SET k = 1002 WHERE k = 102; +SELECT * FROM mv_ivm_exists_subquery ORDER BY i, j; +SELECT * FROM mv_ivm_exists_subquery2 ORDER BY i, j; +DELETE FROM mv_base_a WHERE (i,j) = (1,60); +DELETE FROM mv_base_b WHERE i = 2; +SELECT * FROM mv_ivm_exists_subquery ORDER BY i, j; +SELECT * FROM mv_ivm_exists_subquery2 ORDER BY i, j; +ROLLBACK; + -- support simple subquery in FROM clause BEGIN; SELECT create_immv('mv_ivm_subquery', 'SELECT a.i,a.j FROM mv_base_a a,( SELECT * FROM mv_base_b) b WHERE a.i = b.i'); @@ -236,13 +262,14 @@ SELECT * FROM mv_ivm_subquery ORDER BY i,j; ROLLBACK; -- disallow non-simple subqueries -SELECT create_immv('mv_ivm_exists_subquery', 'SELECT a.i, a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i)'); SELECT create_immv('mv_ivm_subquery', 'SELECT a.i,a.j FROM mv_base_a a, (SELECT i, COUNT(*) FROM mv_base_b GROUP BY i) b WHERE a.i = b.i'); SELECT create_immv('mv_ivm_subquery', 'SELECT a.i,a.j FROM mv_base_a a, (SELECT DISTINCT i FROM mv_base_b) b WHERE a.i = b.i'); SELECT create_immv('mv_ivm_subquery', 'SELECT i,j FROM mv_base_a WHERE i IN (SELECT i FROM mv_base_b WHERE k < 103 )'); SELECT create_immv('mv_ivm_subquery', 'SELECT i,j, (SELECT k FROM mv_base_b LIMIT 1) FROM mv_base_a a'); SELECT create_immv('mv_ivm_subquery', 'SELECT i,j, (SELECT k FROM mv_base_b LIMIT 1) + 1 FROM mv_base_a a'); SELECT create_immv('mv_ivm_subquery', 'SELECT * FROM generate_series(1, (SELECT k FROM mv_base_b LIMIT 1)) AS v'); +SELECT create_immv('mv_ivm_subquery', 'SELECT a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i)'); +SELECT create_immv('mv_ivm_subquery', 'SELECT a.i,a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) OR a.i > 2'); -- support join subquery in FROM clause BEGIN;