Hello,
in the related discussions mentioned on TODO list

http://archives.postgresql.org/pgsql-hackers/2009-09/msg00292.php
http://archives.postgresql.org/pgsql-hackers/2009-10/msg00991.php
(The 1st is rather on SQL, I didn't focuss on it yet.)

the implementation is discussed from optimizer/executor's point of view.

I'm wondering why not to address the problem at earlier stage: rewrite the range function to a subquery.

For example:

SELECT *
FROM  a, b, func(a.i, b.j) as c, d
WHERE a.i=b.j and b.j = d.k and c>1

may become

SELECT *
FROM  a, b, <subquery> as c, d
WHERE a.i=b.j and b.j = d.k and c>1

where <subquery> is

SELECT func(a.i, b.j)
FROM a,b
WHERE a.i=b.j

The WHERE clause of the original query is considered a list of ANDed subclauses. Given 'rt_index' is range table index of the function, only those subclauses are used in the substitution subquery having RT index
lower than 'rt_index'.
Even with such a partial qualification the subquery can safely exclude (from function calls) rows that the main query won't need anyway.

Note that

1. This is rather an alternative to the optimizer/executor focused approach that the past discussions covered. I'm aware of questions about SQL conformance.
2. I only propose this for functions, not for general queries.
3. This draft does not deal with record-returning functions (Although I might have some idea how to treat them.).

Is there any obvious reason not to go this way?

Attached is my (experimental) implementation.

Kind regards,
Tony.
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 7f4da92..ac4f620 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -563,25 +563,6 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
 	assign_expr_collations(pstate, funcexpr);
 
 	/*
-	 * The function parameters cannot make use of any variables from other
-	 * FROM items.	(Compare to transformRangeSubselect(); the coding is
-	 * different though because we didn't parse as a sub-select with its own
-	 * level of namespace.)
-	 *
-	 * XXX this will need further work to support SQL99's LATERAL() feature,
-	 * wherein such references would indeed be legal.
-	 */
-	if (pstate->p_relnamespace || pstate->p_varnamespace)
-	{
-		if (contain_vars_of_level(funcexpr, 0))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
-					 errmsg("function expression in FROM cannot refer to other relations of same query level"),
-					 parser_errposition(pstate,
-										locate_var_of_level(funcexpr, 0))));
-	}
-
-	/*
 	 * Disallow aggregate functions in the expression.	(No reason to postpone
 	 * this check until parseCheckAggregates.)
 	 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 04f9622..b7c93c6 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -18,6 +18,8 @@
 #include "commands/trigger.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/var.h"
 #include "parser/analyze.h"
 #include "parser/parse_coerce.h"
 #include "parser/parsetree.h"
@@ -60,6 +62,7 @@ static List *matchLocks(CmdType event, RuleLock *rulelocks,
 		   int varno, Query *parsetree);
 static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
 			 bool forUpdatePushedDown);
+static void substituteRTEFunction(Query *parsetree, RangeTblEntry *rte, int rt_index);
 
 
 /*
@@ -1557,6 +1560,14 @@ fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown)
 			continue;
 		}
 
+		if (rte->rtekind == RTE_FUNCTION)
+		{
+
+			substituteRTEFunction(parsetree, rte, rt_index);
+			continue;
+
+		}
+
 		/*
 		 * Joins and other non-relation RTEs can be ignored completely.
 		 */
@@ -2239,3 +2250,192 @@ QueryRewrite(Query *parsetree)
 
 	return results;
 }
+
+static void
+substituteRTEFunction(Query *parsetree, RangeTblEntry *rte, int rt_index) {
+	bool is_lateral = false;
+	FuncExpr *funcExpr;
+	ListCell *lcell;
+
+	if (!IsA(rte->funcexpr, FuncExpr)) { 
+		elog(ERROR, "unrecognized range function type: %d", (int) nodeTag(rte->funcexpr));
+	}
+
+	funcExpr = (FuncExpr *) rte->funcexpr;
+
+	if (!funcExpr->args) {
+		return;
+	}
+
+	foreach(lcell, funcExpr->args) {
+		Node *arg = (Node *) lfirst(lcell);
+
+		is_lateral = contain_vars_of_level(arg, 0);
+		if (is_lateral) {
+			break;
+		}
+	}
+
+
+	if (is_lateral) {
+		List* from;
+		List *rt_sub = NIL;
+		Relids rt_sub_ids = NULL;
+		Query *subquery = makeNode(Query);
+		List *subquery_from = NIL;
+		TargetEntry *subquery_te = makeTargetEntry((Expr *) funcExpr, 1, NULL, false);
+		Node *subquery_quals = NULL;
+		int i;
+
+
+		Assert(parsetree->jointree != NULL && IsA(parsetree->jointree, FromExpr)); 
+
+
+		from = parsetree->jointree->fromlist;
+
+		/*
+		 * Construct FROM list of the substitution subquery.
+		 */
+		foreach (lcell, from) {
+			Node *fnode = lfirst(lcell);
+			int rt_ind_f = 0;
+				
+			if (IsA(fnode, RangeTblRef)) {
+				rt_ind_f = ((RangeTblRef *) fnode)->rtindex; 
+			} else if (IsA(fnode, JoinExpr)) { 
+				rt_ind_f = ((JoinExpr *) fnode)->rtindex;
+			} else {
+				elog(ERROR, "unrecognized node type: %d", (int) nodeTag(fnode));
+			}
+
+			if (rt_ind_f < rt_index) {
+				subquery_from = lappend(subquery_from, fnode);
+			} else {
+				/* Really found RangeTblRef for the function that we're just replacing?  */
+				Assert(IsA(fnode, RangeTblRef) && rt_ind_f == rt_index);
+
+				break;
+			}
+		}
+		Assert(subquery_from != NIL);
+
+		/*
+		 * Copy the corresponding RTEs into the subquery's range table.
+		 */
+		i = 1;
+		foreach (lcell, parsetree->rtable) {
+			Node *rtnode = lfirst(lcell);
+			
+			if (i < rt_index) {
+				rt_sub = lappend(rt_sub, rtnode);
+				rt_sub_ids = bms_add_member(rt_sub_ids, i); 
+				i++;
+			} else {
+				break;
+			}
+		}
+
+		/*
+		 * Construct qualification (WHERE) of the subquery.
+		 * The result may only reference RTEs located before the function we're rewriting.
+		 */
+		if (parsetree->jointree->quals) {
+			Node *quals = parsetree->jointree->quals;
+			Relids varnos = NULL;
+
+			if (IsA(quals, BoolExpr) && ((BoolExpr *) quals)->boolop == AND_EXPR) {
+				BoolExpr *andExpr = (BoolExpr *) quals;
+				ListCell *arg;
+				List *argsUsable = NIL;
+
+				/*
+				 * Only subclauses referencing members of 'rt_sub' can be added.
+				 * The other can't be applied until result of the subquery is known.
+				 */
+
+				/*
+				 * TODO
+				 * If some subclause is AND operator, flatten it recursively.
+				 * 
+				 * (i.e. do something like
+				 * 	optimizer/utils/clauses.c:simplify_and_arguments()
+				 * )
+				 * 
+				 */
+				foreach (arg, andExpr->args) {
+					Node *andOperand = lfirst(arg);
+
+					varnos = pull_varnos(andOperand);
+					if (bms_is_subset(varnos, rt_sub_ids)) {
+						argsUsable = lappend(argsUsable, andOperand);
+					}
+					bms_free(varnos);
+				}	
+									
+				if (argsUsable != NIL) {
+					if (list_length(argsUsable) == 1) {
+						subquery_quals = (Node *) list_nth(argsUsable, 0);
+						list_free(argsUsable);
+					} else {
+						subquery_quals = (Node *) make_andclause(argsUsable);
+					}
+				}
+			} else {
+				/*
+				 * non-AND operators as well as other expression types are used whole or not at all.
+				 */
+				varnos = pull_varnos(quals);
+
+				if (bms_is_subset(varnos, rt_sub_ids)) {
+					subquery_quals = (Node *) copyObject(quals);
+				}
+				bms_free(varnos);
+			}
+		}
+
+		Assert(rt_sub_ids != NULL);
+		bms_free(rt_sub_ids);
+
+		/*
+		 * TODO
+		 * re-check all attributes
+		 */
+		subquery->commandType = CMD_SELECT;
+		subquery->querySource = QSRC_INSTEAD_RULE;
+		subquery->utilityStmt = NULL;	
+		subquery->resultRelation = 0;
+		subquery->intoClause = NULL;
+		subquery->hasAggs = false;
+		subquery->hasWindowFuncs = false;
+		subquery->hasSubLinks = false;
+		subquery->hasDistinctOn = false;
+		subquery->hasRecursive = false;
+		subquery->hasModifyingCTE = false;
+		subquery->hasForUpdate = false;
+		subquery->returningList = NIL;
+		subquery->groupClause = NIL;
+		subquery->havingQual = NULL;
+		subquery->windowClause = NIL;
+		subquery->distinctClause = NIL;
+		subquery->sortClause = NIL;
+		subquery->limitOffset = NULL;
+		subquery->limitCount = NULL;
+		subquery->rowMarks = NULL;
+		subquery->setOperations = NULL;
+		subquery->constraintDeps = NULL;
+
+		subquery->rtable = rt_sub;
+		subquery->jointree = makeNode(FromExpr);
+		subquery->jointree->fromlist = subquery_from;
+		subquery->jointree->quals = subquery_quals;
+
+		subquery->targetList = lappend(NIL, subquery_te);
+
+
+		rte->rtekind = RTE_SUBQUERY;
+		rte->subquery = subquery;
+		rte->relid = InvalidOid;
+		rte->alias = NULL;
+		rte->inFromCl = false;
+	}
+}
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to