-----------------------------------
Problem I'm trying to solve:
    For partitioned tables,  make it possible to use RETURNING clause on INSERT 
INTO
           together with DO INSTEAD rule

[  Note  -  wherever I say INSERT I also mean UPDATE and DELETE ]

-----------------------------------
Current behaviour :

    An INSERT which has a RETURNING clause and which is to be rewritten based on
    a rule will be accepted if the rule is an "unconditional DO INSTEAD".
    In general I believe "unconditional" means "no WHERE clause", but in 
practice
    if the rule is of the form 
       CREATE RULE insert_part_history as ON INSERT to history \
         DO INSTEAD SELECT history_insert_partitioned(NEW) returning NEW.id
    this is treated as conditional and the query is rejected.    

    Testcase:
    A table T is partitioned and has two or more columns,  one of which 
    is an id column declared as
         id bigint DEFAULT nextval('history_id_seq'::regclass) NOT NULL
    and the application issues
      "INSERT into history  (column-list which excludes id)  values (....) 
RETURNING id"

    I can get the re-direction of the INSERT *without* RETURNING to work using
    either trigger or rule, in which the trigger/rule invokes a procedure, but
    whichever way I do it, I could not get this RETURNING clause to work.

    For a trigger,
      the INSERT ... RETURNING was accepted but returned no rows, (as I would
      expect), and for the RULE, the INSERT ... RETURNING was rejected with :
 
      ERROR:  cannot perform INSERT RETURNING on relation "history"
      HINT:  You need an unconditional ON INSERT DO INSTEAD rule with a 
RETURNING clause.

      but this hint was not much help,  since :

   For a rule,
     CREATE RULE insert_part_history as ON INSERT to history \
         DO INSTEAD SELECT history_insert_partitioned(NEW) returning NEW.id
    ERROR:  syntax error at or near "returning"
    LINE 1: ...DO INSTEAD SELECT history_insert_partitioned(NEW) returning ...

    Here the function history_insert_partitioned is something like
        CREATE FUNCTION history_insert_partitioned( NEW public.history) RETURNS 
BIGINT AS $$
            DECLARE
               ...
            BEGIN
               ...
                 < acccess NEW fields e.g. timestamp>
                 <  construct partitioned table name>
                  < EXECUTE 'INSERT INTO ' partitioned table
               ...
                RETURN history_id;
            END;
            $$
            LANGUAGE plpgsql

-----------------------------------
Some references to discussion of this requirement :
  .  http://wiki.postgresql.org/wiki/Todo
     item "Make it possible to use RETURNING together with conditional DO 
INSTEAD rules,
           such as for partitioning setups"
  .  http://archives.postgresql.org/pgsql-general/2012-06/msg00377.php
  .  http://archives.postgresql.org/pgsql-general/2010-12/msg00542.php
  .  
http://acodapella.blogspot.it/2011/06/hibernate-postgresql-table-partitioning.html

-----------------------------------
Proposal:
  .  I propose to extend the rule system to recognize cases where the INSERT 
query specifies
     RETURNING and the rule promises to return a row,  and to then permit this 
query to run
     and return the expected row.   In effect,  to widen the definition of 
"unconditional"
     to handle cases such as my testcase.
  .  One comment is that all the infrastructure for returning one row from the 
re-written query
     is already present in the code,  and the non-trivial question is how to 
ensure the
     new design is safe in preventing any rewrite that actually would not 
return a row.
  .  In this patch,   I have chosen to make use of the LIMIT clause  -
     I add a side-effect implication to a LIMIT clause when it occurs in a 
rewrite of an INSERT
     to mean "this rule will return a row".
     So,  with my patch, same testcase,  same function 
history_insert_partitioned and new rule
        CREATE RULE insert_part_history as ON INSERT to history \
               DO INSTEAD SELECT history_insert_partitioned(NEW) LIMIT 1
     the INSERT is accepted and returns the id.
     This use of LIMIT clause is probably contentious but I wished to avoid 
introducing new
     SQL syntax,  and the LIMIT clause does have a connotation of returning 
rows.

-----------------------------------
I attach patch based on clone of postgresql.git as of yesterday (120619-145751 
EST)
I have tested the patch with INSERT and UPDATE  (not tested with DELETE but 
should work).
The patch is not expected to be final but just to show how I did it.

John
                                          
--- src/backend/optimizer/plan/planner.c.orig	2012-06-19 14:59:21.264574275 -0400
+++ src/backend/optimizer/plan/planner.c	2012-06-19 15:10:54.776590758 -0400
@@ -226,7 +226,8 @@ standard_planner(Query *parse, int curso
 
 	result->commandType = parse->commandType;
 	result->queryId = parse->queryId;
-	result->hasReturning = (parse->returningList != NIL);
+	result->returning_one = parse->returning_one;
+	result->hasReturning = ( (parse->returningList != NIL) || parse->returning_one );
 	result->hasModifyingCTE = parse->hasModifyingCTE;
 	result->canSetTag = parse->canSetTag;
 	result->transientPlan = glob->transientPlan;
--- src/backend/rewrite/rewriteHandler.c.orig	2012-06-19 14:59:21.324574277 -0400
+++ src/backend/rewrite/rewriteHandler.c	2012-06-19 15:14:44.080596207 -0400
@@ -1734,6 +1734,8 @@ CopyAndAddInvertedQual(Query *parsetree,
  *					(must be initialized to FALSE)
  *	*qual_product - filled with modified original query if any qualified
  *					INSTEAD rule is found (must be initialized to NULL)
+ *	*limit_rows_ptr  -  hint that this rule will return rows :
+ *					any value in range [ 1 , 23 ] indicates yes
  * Return value:
  *	list of rule actions adjusted for use with this query
  *
@@ -1753,11 +1755,16 @@ fireRules(Query *parsetree,
 		  List *locks,
 		  bool *instead_flag,
 		  bool *returning_flag,
-		  Query **qual_product)
+		  Query **qual_product
+		 ,int64 *limit_rows_ptr
+	 )
 {
 	List	   *results = NIL;
 	ListCell   *l;
 
+	if (limit_rows_ptr)
+		*limit_rows_ptr = -1;
+
 	foreach(l, locks)
 	{
 		RewriteRule *rule_lock = (RewriteRule *) lfirst(l);
@@ -1810,6 +1817,22 @@ fireRules(Query *parsetree,
 		{
 			Query	   *rule_action = lfirst(r);
 
+			/*    check for rule which is a SELECT with LIMIT clause -
+			**    if so,  inform caller of the value (if const) or type of the limit
+			*/
+			if (	(limit_rows_ptr)
+			     && (rule_action->commandType == CMD_SELECT)
+			     && (rule_action->limitCount)
+			   )
+			{
+				if (nodeTag(rule_action->limitCount) == T_Const) {
+					*limit_rows_ptr = DatumGetInt64(((Const *)(rule_action->limitCount))->constvalue);
+				}
+				else if (nodeTag(rule_action->limitCount) == T_FuncExpr) {
+					*limit_rows_ptr = ((int64)((FuncExpr *)(rule_action->limitCount))->funcresulttype);
+				}
+			}
+
 			if (rule_action->commandType == CMD_NOTHING)
 				continue;
 
@@ -1850,6 +1873,7 @@ RewriteQuery(Query *parsetree, List *rew
 	 * clauses.  (We have to do this first because the WITH clauses may get
 	 * copied into rule actions below.)
 	 */
+	parsetree->returning_one = false;
 	foreach(lc1, parsetree->cteList)
 	{
 		CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc1);
@@ -1922,7 +1946,10 @@ RewriteQuery(Query *parsetree, List *rew
 		RangeTblEntry *rt_entry;
 		Relation	rt_entry_relation;
 		List	   *locks;
+		int64 limit_rows;  /*  has either the const value or the datatype of a limit value */
+				   /*  which is a hint that this action will return specified number of rows */
 
+		limit_rows = -1;
 		result_relation = parsetree->resultRelation;
 		Assert(result_relation != 0);
 		rt_entry = rt_fetch(result_relation, parsetree->rtable);
@@ -2002,7 +2029,9 @@ RewriteQuery(Query *parsetree, List *rew
 										locks,
 										&instead,
 										&returning,
-										&qual_product);
+										&qual_product
+									       ,&limit_rows
+						   );
 
 			/*
 			 * If we got any product queries, recursively rewrite them --- but
@@ -2044,14 +2073,28 @@ RewriteQuery(Query *parsetree, List *rew
 
 		/*
 		 * If there is an INSTEAD, and the original query has a RETURNING, we
-		 * have to have found a RETURNING in the rule(s), else fail. (Because
-		 * DefineQueryRewrite only allows RETURNING in unconditional INSTEAD
-		 * rules, there's no need to worry whether the substituted RETURNING
-		 * will actually be executed --- it must be.)
-		 */
-		if ((instead || qual_product != NULL) &&
-			parsetree->returningList &&
-			!returning)
+		 * have to have found in the rule(s) :
+		 *    EITHER  a RETURNING
+		 *    OR      a hint that this action will return specified number of rows
+		 * else fail.
+		 * (Because DefineQueryRewrite only allows RETURNING in unconditional
+		 * INSTEAD rules, there's no need to worry whether the substituted
+		 * RETURNING will actually be executed --- it must be.)
+		 */
+		if (      (instead || qual_product != NULL)
+		      &&  (parsetree->returningList)
+		      &&  (!returning)
+		   )
+		{
+		    /*  a value of limit_rows in the range 1 - 23 is a good hint
+		    **  that query will return specified number of rows
+		    */
+		    if (    (limit_rows >= 1)
+		         && (limit_rows <= 23)
+		       )
+		    {
+			parsetree->returning_one = true;
+		    }   else
 		{
 			switch (event)
 			{
@@ -2082,6 +2125,7 @@ RewriteQuery(Query *parsetree, List *rew
 					break;
 			}
 		}
+		}
 
 		heap_close(rt_entry_relation, NoLock);
 	}
@@ -2165,6 +2209,7 @@ QueryRewrite(Query *parsetree)
 	bool		foundOriginalQuery;
 	Query	   *lastInstead;
 
+	parsetree->returning_one = false;
 	/*
 	 * This function is only applied to top-level original queries
 	 */
@@ -2184,6 +2229,7 @@ QueryRewrite(Query *parsetree)
 	 * Apply all the RIR rules on each query
 	 *
 	 * This is also a handy place to mark each query with the original queryId
+	 * and with returning_one
 	 */
 	results = NIL;
 	foreach(l, querylist)
@@ -2193,6 +2239,7 @@ QueryRewrite(Query *parsetree)
 		query = fireRIRrules(query, NIL, false);
 
 		query->queryId = input_query_id;
+		query->returning_one = parsetree->returning_one;
 
 		results = lappend(results, query);
 	}
@@ -2233,9 +2280,13 @@ QueryRewrite(Query *parsetree)
 		else
 		{
 			Assert(!query->canSetTag);
-			if (query->commandType == origCmdType &&
-				(query->querySource == QSRC_INSTEAD_RULE ||
-				 query->querySource == QSRC_QUAL_INSTEAD_RULE))
+			if (    (     (query->commandType == origCmdType)
+				  ||  query->returning_one
+				)
+			     &&	(     (query->querySource == QSRC_INSTEAD_RULE)
+				  || (query->querySource == QSRC_QUAL_INSTEAD_RULE)
+				)
+			   )
 				lastInstead = query;
 		}
 	}
--- src/backend/nodes/copyfuncs.c.orig	2012-06-19 14:59:21.256574276 -0400
+++ src/backend/nodes/copyfuncs.c	2012-06-19 15:10:54.776590758 -0400
@@ -93,6 +93,7 @@ _copyPlannedStmt(const PlannedStmt *from
 	COPY_NODE_FIELD(relationOids);
 	COPY_NODE_FIELD(invalItems);
 	COPY_SCALAR_FIELD(nParamExec);
+	COPY_SCALAR_FIELD(returning_one);
 
 	return newnode;
 }
@@ -2434,6 +2435,7 @@ _copyQuery(const Query *from)
 	COPY_NODE_FIELD(rowMarks);
 	COPY_NODE_FIELD(setOperations);
 	COPY_NODE_FIELD(constraintDeps);
+	COPY_SCALAR_FIELD(returning_one);
 
 	return newnode;
 }
--- src/include/nodes/plannodes.h.orig	2012-06-19 14:59:21.496574281 -0400
+++ src/include/nodes/plannodes.h	2012-06-19 15:10:54.812590759 -0400
@@ -43,6 +43,8 @@ typedef struct PlannedStmt
 
 	bool		hasModifyingCTE;	/* has insert|update|delete in WITH? */
 
+	bool		returning_one;		/* inform portal that any intermediate result is to be returned -
+							 when a RETURNING was rewritten */
 	bool		canSetTag;		/* do I set the command result tag? */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
--- src/include/nodes/parsenodes.h.orig	2012-06-19 14:59:21.492574281 -0400
+++ src/include/nodes/parsenodes.h	2012-06-19 15:10:54.816590759 -0400
@@ -105,6 +105,8 @@ typedef struct Query
 
 	uint32		queryId;		/* query identifier (can be set by plugins) */
 
+	bool		returning_one;		/* inform portal that any intermediate result is to be returned -
+							when a RETURNING was rewritten */
 	bool		canSetTag;		/* do I set the command result tag? */
 
 	Node	   *utilityStmt;	/* non-null if this is DECLARE CURSOR or a
-- 
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