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.
This commit is contained in:
hoshiai 2023-01-26 11:20:50 +09:00
parent 0587e78651
commit f29aa802b8
5 changed files with 533 additions and 20 deletions

View file

@ -66,6 +66,8 @@ typedef struct
{ {
bool has_agg; bool has_agg;
bool has_subquery; bool has_subquery;
bool in_exists_subquery; /* true, if it is in a exists subquery */
List *exists_qual_vars;
int sublevels_up; int sublevels_up;
} check_ivm_restriction_context; } check_ivm_restriction_context;
@ -261,7 +263,7 @@ ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt,
CreateIndexOnIMMV(viewQuery, matviewRel, true); CreateIndexOnIMMV(viewQuery, matviewRel, true);
/* Create triggers on incremental maintainable materialized view */ /* Create triggers on incremental maintainable materialized view */
CreateIvmTriggersOnBaseTables(viewQuery, matviewOid, true); CreateIvmTriggersOnBaseTables(query, matviewOid, true);
/* Create triggers to prevent IMMV from beeing changed */ /* Create triggers to prevent IMMV from beeing changed */
CreateChangePreventTrigger(matviewOid); CreateChangePreventTrigger(matviewOid);
@ -278,6 +280,11 @@ ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt,
* *
* count(*) is added for counting distinct tuples in views. * count(*) is added for counting distinct tuples in views.
* Also, additional hidden columns are added for aggregate values. * 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 * Query *
rewriteQueryForIMMV(Query *query, List *colNames) rewriteQueryForIMMV(Query *query, List *colNames)
@ -301,6 +308,51 @@ rewriteQueryForIMMV(Query *query, List *colNames)
rewritten = copyObject(query); rewritten = copyObject(query);
pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET; 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 */ /* group keys must be in targetlist */
if (rewritten->groupClause) if (rewritten->groupClause)
{ {
@ -691,7 +743,7 @@ CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 timing, bool ex_lock
static void static void
check_ivm_restriction(Node *node) 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); 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); 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; break;
} }
case T_CommonTableExpr: 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); expression_tree_walker(node, check_ivm_restriction_walker, (void *) context);
break; 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: case T_SubLink:
{ {
/* Now, EXISTS clause is supported only */
SubLink *sublink = (SubLink *) node;
if (sublink->subLinkType != EXISTS_SUBLINK)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported subquery on incrementally maintainable materialized view"), errmsg("this query is not allowed on incrementally maintainable materialized view"),
errhint("Only simple subquery in FROM clause is supported."))); 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; break;
} }
default: default:

View file

@ -661,6 +661,139 @@ SELECT * FROM mv_ri ORDER BY i2;
10 | 10 10 | 10
(2 rows) (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; ROLLBACK;
-- support simple subquery in FROM clause -- support simple subquery in FROM clause
BEGIN; BEGIN;
@ -688,25 +821,28 @@ SELECT * FROM mv_ivm_subquery ORDER BY i,j;
ROLLBACK; ROLLBACK;
-- disallow non-simple subqueries -- 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'); 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 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'); 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 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 )'); 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 ERROR: this query is not allowed on incrementally maintainable materialized view
HINT: Only simple subquery in FROM clause is supported. 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'); 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 ERROR: this query is not allowed on incrementally maintainable materialized view
HINT: Only simple subquery in FROM clause is supported. 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'); 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 ERROR: this query is not allowed on incrementally maintainable materialized view
HINT: Only simple subquery in FROM clause is supported. 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'); 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 ERROR: this query is not allowed on incrementally maintainable materialized view
HINT: Only simple subquery in FROM clause is supported. 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 -- support join subquery in FROM clause
BEGIN; 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'); 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');

246
matview.c
View file

@ -29,6 +29,7 @@
#include "executor/tstoreReceiver.h" #include "executor/tstoreReceiver.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h" #include "optimizer/optimizer.h"
#include "parser/analyze.h" #include "parser/analyze.h"
#include "parser/parse_clause.h" #include "parser/parse_clause.h"
@ -37,6 +38,7 @@
#include "parser/parser.h" #include "parser/parser.h"
#include "pgstat.h" #include "pgstat.h"
#include "rewrite/rewriteHandler.h" #include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rowsecurity.h" #include "rewrite/rowsecurity.h"
#include "storage/lmgr.h" #include "storage/lmgr.h"
#include "tcop/tcopprot.h" #include "tcop/tcopprot.h"
@ -973,6 +975,10 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
i++; 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 */ /* Set all tables in the query to pre-update state */
rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables, rewritten = rewrite_query_for_preupdate_state(rewritten, entry->tables,
pstate, NIL, matviewOid); pstate, NIL, matviewOid);
@ -1034,15 +1040,40 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
foreach(lc2, table->rte_paths) foreach(lc2, table->rte_paths)
{ {
List *rte_path = lfirst(lc2); List *rte_path = lfirst(lc2);
int i;
Query *querytree = rewritten;
RangeTblEntry *rte;
TupleDesc tupdesc_old; TupleDesc tupdesc_old;
TupleDesc tupdesc_new; TupleDesc tupdesc_new;
bool use_count = false; bool use_count = false;
char *count_colname = NULL; 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; use_count = true;
}
}
}
}
if (count_colname == NULL && (query->hasAggs || query->distinctClause))
{
count_colname = pstrdup("__ivm_count__");
use_count = true;
}
/* calculate delta tables */ /* calculate delta tables */
calc_delta(table, rte_path, rewritten, dest_old, dest_new, 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; TargetEntry *tle_count;
FuncCall *fn; FuncCall *fn;
Node *node; Node *node;
int varno = 0;
ListCell *tbl_lc;
/* For aggregate views */ /* For aggregate views */
if (query->hasAggs) if (query->hasAggs)
@ -1504,6 +1537,34 @@ rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate)
query->targetList = list_concat(query->targetList, aggs); 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 */ /* Add count(*) for counting distinct tuples in views */
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 140000) #if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 140000)
fn = makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT_CALL, -1); 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; 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 * 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); 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 * isIvmName
* *

View file

@ -52,8 +52,10 @@ extern bool ImmvIncrementalMaintenanceIsEnabled(void);
extern Query *get_immv_query(Relation matviewRel); extern Query *get_immv_query(Relation matviewRel);
extern Datum IVM_immediate_before(PG_FUNCTION_ARGS); extern Datum IVM_immediate_before(PG_FUNCTION_ARGS);
extern Datum IVM_immediate_maintenance(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 Datum ivm_visible_in_prestate(PG_FUNCTION_ARGS);
extern void AtAbort_IVM(void); extern void AtAbort_IVM(void);
extern char *getColumnNameStartWith(RangeTblEntry *rte, char *str, int *attnum);
extern bool isIvmName(const char *s); extern bool isIvmName(const char *s);
/* ruleutils.c */ /* ruleutils.c */

View file

@ -227,6 +227,32 @@ DELETE FROM ri1 WHERE i=2;
SELECT * FROM mv_ri ORDER BY i2; SELECT * FROM mv_ri ORDER BY i2;
ROLLBACK; 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 -- support simple subquery in FROM clause
BEGIN; 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'); 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; ROLLBACK;
-- disallow non-simple subqueries -- 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 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 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 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) 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 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 * 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 -- support join subquery in FROM clause
BEGIN; BEGIN;