From b3603c776d638a987b2687c74e2226d7f333c68f Mon Sep 17 00:00:00 2001 From: Takuma Hoshiai Date: Fri, 1 Mar 2024 16:48:16 +0900 Subject: [PATCH] Change restriction handling and flags for EXISTS subquery This patch is reported by yugo-n. This is adding a new boolean member allow_context into check_ivm_restriction_context. This member means whether EXISTS subquery is allowed in the current node being examined in check_ivm_restriction_walker or not. --- createas.c | 105 ++++++++++++++++++++++++-------------------- expected/pg_ivm.out | 9 ++-- matview.c | 42 +++++++----------- 3 files changed, 79 insertions(+), 77 deletions(-) diff --git a/createas.c b/createas.c index 39f2807..2530490 100644 --- a/createas.c +++ b/createas.c @@ -65,14 +65,11 @@ typedef struct typedef struct { - bool has_agg; - bool has_sublinks; - 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 */ - bool is_simple_jointree; /* true, if it is in a simple join tree or boolexpr directly under jointree */ - 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, @@ -742,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, 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); } @@ -750,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; @@ -809,8 +816,6 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("recursive query is not supported on incrementally maintainable materialized view"))); - context->has_sublinks |= qry->hasSubLinks; - /* system column restrictions */ vars = pull_vars_of_level((Node *) qry, 0); foreach(lc, vars) @@ -904,10 +909,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--; } } @@ -997,38 +1000,17 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) expression_tree_walker(node, check_ivm_restriction_walker, (void *) context); break; } - case T_List: + case T_FromExpr: { - ListCell *temp; - foreach(temp, (List *) node) - { - check_ivm_restriction_walker(lfirst(temp), context); - } - break; - } - case T_FromExpr: - { - FromExpr *from = (FromExpr *) node; + FromExpr *from = (FromExpr *) node; + + check_ivm_restriction_walker((Node *) from->fromlist, context); - check_ivm_restriction_walker((Node *)from->fromlist, context); /* - * check the sublink restrictions. - * Currently, EXISTS subqueries with condition other than 'AND' is not supported. + * EXIEST is allowed directly under FROM clause */ - if (from->quals == NULL) - break; - if (context->has_sublinks && !context->in_exists_subquery && - !IsA(from->quals, SubLink) && !IsA(from->quals, BoolExpr)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("this query is not allowed on incrementally maintainable materialized view"), - errhint("sublink only supports simple conditions with EXISTS clause in WHERE clause"))); - - context->in_jointree = true; - context->is_simple_jointree = true; + context->allow_exists = true; check_ivm_restriction_walker(from->quals, context); - context->is_simple_jointree = false; - context->in_jointree = false; break; } case T_JoinExpr: @@ -1086,20 +1068,51 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) case T_BoolExpr: { BoolExpr *expr = (BoolExpr *) node; - expression_tree_walker((Node *) expr->args, check_ivm_restriction_walker, (void *) context); + 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 || (context->in_jointree && !context->is_simple_jointree)) + + if (context->sublevels_up > 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("nested sublink is not supported on incrementally maintainable materialized view"))); @@ -1119,8 +1132,6 @@ check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *context) break; } default: - // not a simple query, if a condition don't match these - context->is_simple_jointree = false; expression_tree_walker(node, check_ivm_restriction_walker, (void *) context); break; } diff --git a/expected/pg_ivm.out b/expected/pg_ivm.out index 66a5ba6..33ba313 100644 --- a/expected/pg_ivm.out +++ b/expected/pg_ivm.out @@ -876,7 +876,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'); @@ -884,15 +884,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 simple conditions with EXISTS clause in WHERE clause +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: nested sublink is not supported on incrementally maintainable materialized view +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 d12041e..5ca8526 100644 --- a/matview.c +++ b/matview.c @@ -1614,35 +1614,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; }