diff --git a/createas.c b/createas.c index b177afa..f62070a 100644 --- a/createas.c +++ b/createas.c @@ -65,12 +65,11 @@ typedef struct typedef struct { - bool has_agg; - bool has_subquery; - bool in_exists_subquery; /* true, if it is in a exists subquery */ - bool in_jointree; /* true, if it is in a join tree */ - List *exists_qual_vars; - int sublevels_up; + bool has_agg; /* the query has an aggregate */ + bool allow_exists; /* EXISTS subquery is allowed in the current node */ + bool in_exists_subquery; /* true, if under an EXISTS subquery */ + List *exists_qual_vars; /* Vars used in EXISTS subqueries */ + int sublevels_up; /* (current) nesting depth */ } check_ivm_restriction_context; static void CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid, @@ -466,7 +465,7 @@ makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber * /* Make a Func with a dummy arg, and then override this by the original agg's args. */ node = ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NULL, fn, false, -1); - ((Aggref *)node)->args = aggref->args; + ((Aggref *) node)->args = aggref->args; tle_count = makeTargetEntry((Expr *) node, *next_resno, @@ -502,7 +501,7 @@ makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNumber * /* Make a Func with dummy args, and then override this by the original agg's args. */ node = ParseFuncOrColumn(pstate, fn->funcname, dmy_args, NULL, fn, false, -1); - ((Aggref *)node)->args = aggref->args; + ((Aggref *) node)->args = aggref->args; tle_count = makeTargetEntry((Expr *) node, *next_resno, @@ -573,7 +572,7 @@ CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid, Query *query = (Query *) node; ListCell *lc; - CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *)query->jointree, matviewOid, relids, ex_lock); + CreateIvmTriggersOnBaseTablesRecurse(qry, (Node *) query->jointree, matviewOid, relids, ex_lock); foreach(lc, query->cteList) { CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); @@ -605,7 +604,7 @@ CreateIvmTriggersOnBaseTablesRecurse(Query *qry, Node *node, Oid matviewOid, { Query *subquery = rte->subquery; Assert(rte->subquery != NULL); - CreateIvmTriggersOnBaseTablesRecurse(subquery, (Node *)subquery, matviewOid, relids, ex_lock); + CreateIvmTriggersOnBaseTablesRecurse(subquery, (Node *) subquery, matviewOid, relids, ex_lock); } } break; @@ -740,7 +739,13 @@ 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, false, false, NIL, 0}; + check_ivm_restriction_context context; + + context.has_agg = false; + context.allow_exists = false; + context.in_exists_subquery = false; + context.exists_qual_vars = NIL; + context.sublevels_up = 0; check_ivm_restriction_walker(node, &context); } @@ -748,6 +753,10 @@ check_ivm_restriction(Node *node) static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) { + /* EXISTS is allowed only in this node */ + bool allow_exists = context->allow_exists; + context->allow_exists = false; + if (node == NULL) return false; @@ -758,7 +767,7 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) { case T_Query: { - Query *qry = (Query *)node; + Query *qry = (Query *) node; ListCell *lc; List *vars; @@ -814,7 +823,7 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) if (IsA(lfirst(lc), Var)) { Var *var = (Var *) lfirst(lc); - /* if system column, return error */ + /* if the view has a system column, raise an error */ if (var->varattno < 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -822,7 +831,7 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) } } - /* check if type in the top target list had an equality operator */ + /* check that each type in the target list has an equality operator */ if (context->sublevels_up == 0) { foreach(lc, qry->targetList) @@ -893,6 +902,7 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("VALUES is not supported on incrementally maintainable materialized view"))); + if (rte->relkind == RELKIND_RELATION && isImmv(rte->relid)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -900,10 +910,8 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) if (rte->rtekind == RTE_SUBQUERY) { - context->has_subquery = true; - context->sublevels_up++; - check_ivm_restriction_walker((Node *)rte->subquery, context); + check_ivm_restriction_walker((Node *) rte->subquery, context); context->sublevels_up--; } } @@ -913,9 +921,11 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) /* * additional restriction checks for exists subquery * - * If the query has any EXISTS clauses and columns in them refer to - * columns in tables in the output query, those columns must be - * included in the target list. + * If the query has an EXISTS subquery and columns of a table in + * the outer query are used in the EXISTS subquery, those columns + * must be included in the target list. These columns are required + * to identify tuples in the view to be affected by modification + * of tables in the EXISTS subquery. */ if (context->exists_qual_vars != NIL && context->sublevels_up == 0) { @@ -958,7 +968,7 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("WITH query name %s is not supported on incrementally maintainable materialized view", cte->ctename))); - /* + /* * When a table in a unreferenced CTE is TRUNCATEd, the contents of the * IMMV is not affected so it must not be truncated. For confirming it * at the maintenance time, we have to check if the modified table used @@ -979,12 +989,13 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) } case T_TargetEntry: { - TargetEntry *tle = (TargetEntry *)node; + TargetEntry *tle = (TargetEntry *) node; if (isIvmName(tle->resname)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("column name %s is not supported on incrementally maintainable materialized view", tle->resname))); + if (context->has_agg && !IsA(tle->expr, Aggref) && contain_aggs_of_level((Node *) tle->expr, 0)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -993,19 +1004,22 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) expression_tree_walker(node, check_ivm_restriction_walker, (void *) context); break; } - case T_FromExpr: - { - FromExpr *from = (FromExpr *) node; + case T_FromExpr: + { + FromExpr *from = (FromExpr *) node; - check_ivm_restriction_walker((Node *)from->fromlist, context); - context->in_jointree = true; + check_ivm_restriction_walker((Node *) from->fromlist, context); + + /* + * EXIEST is allowed directly under FROM clause + */ + context->allow_exists = true; check_ivm_restriction_walker(from->quals, context); - context->in_jointree = false; - break; + break; } case T_JoinExpr: { - JoinExpr *joinexpr = (JoinExpr *)node; + JoinExpr *joinexpr = (JoinExpr *) node; if (joinexpr->jointype > JOIN_INNER) ereport(ERROR, @@ -1055,22 +1069,60 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) context->exists_qual_vars = lappend(context->exists_qual_vars, node); break; } + case T_BoolExpr: + { + BoolExpr *expr = (BoolExpr *) node; + BoolExprType type = ((BoolExpr *) node)->boolop; + ListCell *lc; + + switch (type) + { + case AND_EXPR: + foreach(lc, expr->args) + { + Node *opnode = (Node *) lfirst(lc); + + /* + * EXIEST is allowed under AND expression only if it is + * directly under WHERE. + */ + if (allow_exists) + context->allow_exists = true; + check_ivm_restriction_walker(opnode, context); + } + 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 can not be used together"))); + + expression_tree_walker((Node *) expr->args, check_ivm_restriction_walker, (void *) context); + break; + } + break; + } case T_SubLink: { - /* Currently, EXISTS clause is supported only */ Query *subselect; SubLink *sublink = (SubLink *) node; - if (!context->in_jointree || sublink->subLinkType != EXISTS_SUBLINK) + + /* Only EXISTS clause is supported if it is directly under WHERE */ + if (!allow_exists || sublink->subLinkType != EXISTS_SUBLINK) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("this query is not allowed on incrementally maintainable materialized view"), errhint("sublink only supports subquery with EXISTS clause in WHERE clause"))); + if (context->sublevels_up > 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("nested sublink is not supported on incrementally maintainable materialized view"))); - subselect = (Query *)sublink->subselect; + subselect = (Query *) sublink->subselect; + /* raise ERROR if the sublink has CTE */ if (subselect->cteList) ereport(ERROR, @@ -1600,7 +1652,7 @@ get_primary_key_attnos_from_query(Query *query, List **constraintList) i = 1; foreach(lc, key_attnos_list) { - Bitmapset *bms = (Bitmapset *)lfirst(lc); + Bitmapset *bms = (Bitmapset *) lfirst(lc); if (!bms_is_empty(bms) && bms_is_member(i, rels_in_from)) return NULL; i++; diff --git a/expected/pg_ivm.out b/expected/pg_ivm.out index a62dc63..e8798ee 100644 --- a/expected/pg_ivm.out +++ b/expected/pg_ivm.out @@ -906,7 +906,7 @@ ERROR: this query is not allowed on incrementally maintainable materialized vie 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 +HINT: OR or NOT conditions and EXISTS condition can not be used together SELECT create_immv('mv_ivm_subquery', 'SELECT a.j FROM mv_base_a a WHERE EXISTS(SELECT 1 FROM mv_base_a a2 WHERE EXISTS(SELECT 1 FROM mv_base_b b WHERE a2.i = b.i))'); ERROR: nested sublink is not supported on incrementally maintainable materialized view SELECT create_immv('mv_ivm_subquery', 'SELECT EXISTS(SELECT 1 from mv_base_b) FROM mv_base_a a'); @@ -914,10 +914,16 @@ ERROR: this query is not allowed on incrementally maintainable materialized vie HINT: sublink only supports subquery with EXISTS clause in WHERE clause SELECT create_immv('mv_ivm_subquery', 'SELECT false OR EXISTS(SELECT 1 FROM mv_base_a) FROM mv_base_b'); ERROR: this query is not allowed on incrementally maintainable materialized view -HINT: sublink only supports subquery with EXISTS clause in WHERE clause +HINT: OR or NOT conditions and EXISTS condition can not be used together SELECT create_immv('mv_ivm_subquery', 'SELECT * FROM generate_series(1, CASE EXISTS(SELECT 1 FROM mv_base_a) WHEN true THEN 100 ELSE 10 END), mv_base_b'); ERROR: this query is not allowed on incrementally maintainable materialized view HINT: sublink only supports subquery with EXISTS clause in WHERE clause +SELECT create_immv('mv_ivm_subquery', 'SELECT * FROM mv_base_a a WHERE CASE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) WHEN true THEN false ELSE true END'); +ERROR: this query is not allowed on incrementally maintainable materialized view +HINT: sublink only supports subquery with EXISTS clause in WHERE clause +SELECT create_immv('mv_ivm_subquery', 'SELECT * FROM mv_base_a a WHERE true and CASE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) WHEN true THEN false ELSE true END'); +ERROR: this query is not allowed on incrementally maintainable materialized view +HINT: sublink only supports subquery with EXISTS clause in WHERE clause -- 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 6f8525b..e89cb73 100644 --- a/matview.c +++ b/matview.c @@ -1606,7 +1606,8 @@ rewrite_exists_subquery_walker(Query *query, Node *node, int *count) if (fromexpr->quals != NULL) { query = rewrite_exists_subquery_walker(query, fromexpr->quals, count); - /* drop subquery in WHERE clause */ + + /* drop WHERE clause when it has only one EXISTS */ if (IsA(fromexpr->quals, SubLink)) fromexpr->quals = NULL; } @@ -1614,35 +1615,25 @@ rewrite_exists_subquery_walker(Query *query, Node *node, int *count) } case T_BoolExpr: { - BoolExprType type; + BoolExprType type = ((BoolExpr *) node)->boolop; - type = ((BoolExpr *) node)->boolop; - switch (type) + if (type == AND_EXPR) { ListCell *lc; - case AND_EXPR: - foreach(lc, ((BoolExpr *)node)->args) - { - /* If simple EXISTS subquery is used, rewrite LATERAL subquery */ - Node *opnode = (Node *)lfirst(lc); - query = rewrite_exists_subquery_walker(query, opnode, count); - /* - * overwrite SubLink node to true condition if it is contained in AND_EXPR. - * EXISTS clause have already overwritten to LATERAL, so original EXISTS clause - * is not necessory. - */ - 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; + foreach(lc, ((BoolExpr *) node)->args) + { + /* If simple EXISTS subquery is used, rewrite LATERAL subquery */ + Node *opnode = (Node *) lfirst(lc); + query = rewrite_exists_subquery_walker(query, opnode, count); + + /* + * overwrite SubLink node to true condition if it is contained in AND_EXPR. + * EXISTS clause have already overwritten to LATERAL, so original EXISTS clause + * is not necessory. + */ + if (IsA(opnode, SubLink)) + lfirst(lc) = makeConst(BOOLOID, -1, InvalidOid, sizeof(bool), BoolGetDatum(true), false, true); + } } break; } @@ -1701,7 +1692,7 @@ rewrite_exists_subquery_walker(Query *query, Node *node, int *count) /* 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); + ((FromExpr *) query->jointree)->fromlist = lappend(((FromExpr *) query->jointree)->fromlist, rtr); /* * EXISTS condition is converted to HAVING count(*) > 0. @@ -1709,13 +1700,13 @@ rewrite_exists_subquery_walker(Query *query, Node *node, int *count) */ opId = OpernameGetOprid(list_make2(makeString("pg_catalog"), makeString(">")), INT8OID, INT4OID); opexpr = make_opclause(opId, BOOLOID, false, - (Expr *)fn_node, - (Expr *)makeConst(INT4OID, -1, InvalidOid, sizeof(int32), Int32GetDatum(0), false, true), + (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; + subselect->havingQual = (Node *) opexpr; (*count)++; break; } @@ -1738,7 +1729,7 @@ rewrite_exists_subquery_walker(Query *query, Node *node, int *count) * SELECT 1, COUNT(*) AS __ivm_exists_count_0__ * FROM t2 * WHERE t1.key = t2.key - * HAVING __ivm_exists_count_0__ > 0) AS ex + * HAVING COUNT(*) > 0) AS ex */ Query * rewrite_query_for_exists_subquery(Query *query) @@ -1750,7 +1741,7 @@ rewrite_query_for_exists_subquery(Query *query) 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); + return rewrite_exists_subquery_walker(query, (Node *) query, &count); } /* diff --git a/sql/pg_ivm.sql b/sql/pg_ivm.sql index 87adf90..d3c1a01 100644 --- a/sql/pg_ivm.sql +++ b/sql/pg_ivm.sql @@ -298,6 +298,9 @@ SELECT create_immv('mv_ivm_subquery', 'SELECT a.j FROM mv_base_a a WHERE EXISTS( SELECT create_immv('mv_ivm_subquery', 'SELECT EXISTS(SELECT 1 from mv_base_b) FROM mv_base_a a'); SELECT create_immv('mv_ivm_subquery', 'SELECT false OR EXISTS(SELECT 1 FROM mv_base_a) FROM mv_base_b'); SELECT create_immv('mv_ivm_subquery', 'SELECT * FROM generate_series(1, CASE EXISTS(SELECT 1 FROM mv_base_a) WHEN true THEN 100 ELSE 10 END), mv_base_b'); +SELECT create_immv('mv_ivm_subquery', 'SELECT * FROM mv_base_a a WHERE CASE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) WHEN true THEN false ELSE true END'); +SELECT create_immv('mv_ivm_subquery', 'SELECT * FROM mv_base_a a WHERE true and CASE EXISTS(SELECT 1 FROM mv_base_b b WHERE a.i = b.i) WHEN true THEN false ELSE true END'); + -- 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');