Support UPDATE/DELETE WHERE CURRENT OF cursor_name, per SQL standard.
authorTom Lane
Mon, 11 Jun 2007 01:16:30 +0000 (01:16 +0000)
committerTom Lane
Mon, 11 Jun 2007 01:16:30 +0000 (01:16 +0000)
Along the way, allow FOR UPDATE in non-WITH-HOLD cursors; there may once
have been a reason to disallow that, but it seems to work now, and it's
really rather necessary if you want to select a row via a cursor and then
update it in a concurrent-safe fashion.

Original patch by Arul Shaji, rather heavily editorialized by Tom Lane.

30 files changed:
doc/src/sgml/ref/declare.sgml
doc/src/sgml/ref/delete.sgml
doc/src/sgml/ref/update.sgml
src/backend/executor/Makefile
src/backend/executor/execCurrent.c [new file with mode: 0644]
src/backend/executor/execMain.c
src/backend/executor/execQual.c
src/backend/executor/nodeTidscan.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/path/clausesel.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/path/tidpath.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/prep/prepunion.c
src/backend/optimizer/util/clauses.c
src/backend/optimizer/util/var.c
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/keywords.c
src/backend/parser/parse_expr.c
src/backend/rewrite/rewriteManip.c
src/backend/utils/adt/ruleutils.c
src/include/executor/executor.h
src/include/nodes/nodes.h
src/include/nodes/primnodes.h
src/test/regress/expected/portals.out
src/test/regress/sql/portals.sql

index 4afe2d03b255ec3f1f2fa2605bc1eb158893e053..f823cf77bbea5a31c1e634871d9bd2953d2aa8ee 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -27,7 +27,6 @@ PostgreSQL documentation
 
 DECLARE name [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ]
     CURSOR [ { WITH | WITHOUT } HOLD ] FOR query
-    [ FOR { READ ONLY | UPDATE [ OF column [, ...] ] } ]
 
  
 
@@ -37,50 +36,10 @@ DECLARE name [ BINARY ] [ INSENSITI
   
    DECLARE allows a user to create cursors, which
    can be used to retrieve
-   a small number of rows at a time out of a larger query. Cursors can
-   return data either in text or in binary format using
+   a small number of rows at a time out of a larger query.
+   After the cursor is created, rows are fetched from it using
    .
   
-
-  
-   Normal cursors return data in text format, the same as a
-   SELECT would produce.  Since data is stored natively in
-   binary format, the system must do a conversion to produce the text
-   format.  Once the information comes back in text form, the client
-   application might need to convert it to a binary format to manipulate
-   it.  In addition, data in the text format is often larger in size
-   than in the binary format.  Binary cursors return the data in a
-   binary representation that might be more easily manipulated.
-   Nevertheless, if you intend to display the data as text anyway,
-   retrieving it in text form will
-   save you some effort on the client side.
-  
-
-  
-   As an example, if a query returns a value of one from an integer column,
-   you would get a string of 1 with a default cursor
-   whereas with a binary cursor you would get
-   a 4-byte field containing the internal representation of the value
-   (in big-endian byte order).
-  
-
-  
-   Binary cursors should be used carefully.  Many applications,
-   including psql, are not prepared to
-   handle binary cursors and expect data to come back in the text
-   format.
-  
-
-  
-   
-    When the client application uses the extended query protocol
-    to issue a FETCH command, the Bind protocol message
-    specifies whether data is to be retrieved in text or binary format.
-    This choice overrides the way that the cursor is defined.  The concept
-    of a binary cursor as such is thus obsolete when using extended query
-    protocol — any cursor can be treated as either text or binary.
-   
-  
  
 
  
@@ -110,10 +69,10 @@ DECLARE name [ BINARY ] [ INSENSITI
     
      
       Indicates that data retrieved from the cursor should be
-      unaffected by updates to the tables underlying the cursor while
-      the cursor exists.  In PostgreSQL,
-      all cursors are insensitive; this key word currently has no
-      effect and is present for compatibility with the SQL standard.
+      unaffected by updates to the table(s) underlying the cursor that occur
+      after the cursor is created.  In PostgreSQL,
+      this is the default behavior; so this key word has no
+      effect and is only accepted for compatibility with the SQL standard.
      
     
    
@@ -163,34 +122,6 @@ DECLARE name [ BINARY ] [ INSENSITI
      
     
    
-
-   
-    FOR READ ONLY
-    FOR UPDATE
-    
-     
-      FOR READ ONLY indicates that the cursor will
-      be used in a read-only mode.  FOR UPDATE
-      indicates that the cursor will be used to update tables.  Since
-      cursor updates are not currently supported in
-      PostgreSQL, specifying FOR
-      UPDATE will cause an error message and specifying
-      FOR READ ONLY has no effect.
-     
-    
-   
-
-   
-    column
-    
-     
-      Column(s) to be updated by the cursor.  Since cursor updates are
-      not currently supported in
-      PostgreSQL, the FOR
-      UPDATE clause provokes an error message.
-     
-    
-   
   
 
   
@@ -203,6 +134,38 @@ DECLARE name [ BINARY ] [ INSENSITI
  
   Notes
 
+  
+   Normal cursors return data in text format, the same as a
+   SELECT would produce.  The BINARY option
+   specifies that the cursor should return data in binary format.
+   This reduces conversion effort for both the server and client,
+   at the cost of more programmer effort to deal with platform-dependent
+   binary data formats.
+   As an example, if a query returns a value of one from an integer column,
+   you would get a string of 1 with a default cursor,
+   whereas with a binary cursor you would get
+   a 4-byte field containing the internal representation of the value
+   (in big-endian byte order).
+  
+
+  
+   Binary cursors should be used carefully.  Many applications,
+   including psql, are not prepared to
+   handle binary cursors and expect data to come back in the text
+   format.
+  
+
+  
+   
+    When the client application uses the extended query protocol
+    to issue a FETCH command, the Bind protocol message
+    specifies whether data is to be retrieved in text or binary format.
+    This choice overrides the way that the cursor is defined.  The concept
+    of a binary cursor as such is thus obsolete when using extended query
+    protocol — any cursor can be treated as either text or binary.
+   
+  
+
    
     Unless WITH HOLD is specified, the cursor
     created by this command can only be used within the current
@@ -232,6 +195,11 @@ DECLARE name [ BINARY ] [ INSENSITI
     transactions.
    
 
+   
+    WITH HOLD may not be specified when the query
+    includes FOR UPDATE or FOR SHARE.
+   
+
    
     The SCROLL option should be specified when defining a
     cursor that will be used to fetch backwards.  This is required by
@@ -245,6 +213,23 @@ DECLARE name [ BINARY ] [ INSENSITI
     specified, then backward fetches are disallowed in any case.
    
 
+   
+    If the cursor's query includes FOR UPDATE or FOR
+    SHARE, then returned rows are locked at the time they are first
+    fetched, in the same way as for a regular
+     command with
+    these options.
+    In addition, the returned rows will be the most up-to-date versions;
+    therefore these options provide the equivalent of what the SQL standard
+    calls a sensitive cursor.  It is often wise to use FOR
+    UPDATE if the cursor is intended to be used with UPDATE
+    ... WHERE CURRENT OF or DELETE ... WHERE CURRENT OF,
+    since this will prevent other sessions from changing the rows between
+    the time they are fetched and the time they are updated.  Without
+    FOR UPDATE, a subsequent WHERE CURRENT OF command
+    will have no effect if the row was changed meanwhile.
+   
+
    
     The SQL standard only makes provisions for cursors in embedded
     SQL.  The PostgreSQL
@@ -280,14 +265,16 @@ DECLARE liahona CURSOR FOR SELECT * FROM films;
   Compatibility
 
   
-   The SQL standard allows cursors only in embedded
-   SQL and in modules. PostgreSQL
-   permits cursors to be used interactively.
+   The SQL standard specifies that by default, cursors are sensitive to
+   concurrent updates of the underlying data.  In
+   PostgreSQL, cursors are insensitive by default,
+   and can be made sensitive by specifying FOR UPDATE.
   
 
   
-   The SQL standard allows cursors to update table data. All
-   PostgreSQL cursors are read only.
+   The SQL standard allows cursors only in embedded
+   SQL and in modules. PostgreSQL
+   permits cursors to be used interactively.
   
 
   
index f57c56ecf7e1f50bbd859de305bf584889288146..b5c7d97f524bc005793ace4c35254a04695ab2e7 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -22,7 +22,7 @@ PostgreSQL documentation
 
 DELETE FROM [ ONLY ] table [ [ AS ] alias ]
     [ USING usinglist ]
-    [ WHERE condition ]
+    [ WHERE condition | WHERE CURRENT OF cursor_name ]
     [ RETURNING * | output_expression [ AS output_name ] [, ...] ]
 
  
@@ -134,9 +134,23 @@ DELETE FROM [ ONLY ] table [ [ AS ]
     condition
     
      
-      An expression returning a value of type
-      boolean, which determines the rows that are to be
-      deleted.
+      An expression that returns a value of type boolean.
+      Only rows for which this expression returns true
+      will be deleted.
+     
+    
+   
+
+   
+    cursor_name
+    
+     
+      The name of the cursor to use in a WHERE CURRENT OF
+      condition.  The row to be deleted is the one most recently fetched
+      from this cursor.  The cursor must be a simple (non-join, non-aggregate)
+      query on the DELETE's target table.
+      Note that WHERE CURRENT OF cannot be
+      specified together with a boolean condition.
      
     
    
@@ -236,6 +250,14 @@ DELETE FROM films;
    Delete completed tasks, returning full details of the deleted rows:
 
 DELETE FROM tasks WHERE status = 'DONE' RETURNING *;
+      
+  
+
+   
+   Delete the row of tasks on which the cursor
+   c_tasks is currently positioned:
+
+DELETE FROM tasks WHERE CURRENT OF c_tasks;
       
   
  
index df0bec9a1d69452fb0202316951b4aa8f32630e0..2c6a480c4a525a89df6c32621d77b90180b103e6 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -24,7 +24,7 @@ UPDATE [ ONLY ] table [ [ AS ] 
     SET { column = { expression | DEFAULT } |
           ( column [, ...] ) = ( { expression | DEFAULT } [, ...] ) } [, ...]
     [ FROM fromlist ]
-    [ WHERE condition ]
+    [ WHERE condition | WHERE CURRENT OF cursor_name ]
     [ RETURNING * | output_expression [ AS output_name ] [, ...] ]
 
  
@@ -160,6 +160,20 @@ UPDATE [ ONLY ] table [ [ AS ] 
     
    
 
+   
+    cursor_name
+    
+     
+      The name of the cursor to use in a WHERE CURRENT OF
+      condition.  The row to be updated is the one most recently fetched
+      from this cursor.  The cursor must be a simple (non-join, non-aggregate)
+      query on the UPDATE's target table.
+      Note that WHERE CURRENT OF cannot be
+      specified together with a boolean condition.
+     
+    
+   
+
    
     output_expression
     
@@ -309,6 +323,15 @@ UPDATE wines SET stock = stock + 24 WHERE winename = 'Chateau Lafite 2003';
 COMMIT;
 
   
+
+  
+   Change the kind column of the table
+   films in the row on which the cursor
+   c_films is currently positioned:
+
+UPDATE films SET kind = 'Dramatic' WHERE CURRENT OF c_films;
+      
+  
  
 
  
index 55256d9e469629e5989c7fed8fe1da021a374607..cb4ab9dc31a1430226bbe6cb34a3b8baf9d5195d 100644 (file)
@@ -4,7 +4,7 @@
 #    Makefile for executor
 #
 # IDENTIFICATION
-#    $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.25 2007/01/20 17:16:11 petere Exp $
+#    $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.26 2007/06/11 01:16:22 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -12,7 +12,7 @@ subdir = src/backend/executor
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = execAmi.o execGrouping.o execJunk.o execMain.o \
+OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
        execProcnode.o execQual.o execScan.o execTuples.o \
        execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
        nodeBitmapAnd.o nodeBitmapOr.o \
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c
new file mode 100644 (file)
index 0000000..ce95d58
--- /dev/null
@@ -0,0 +1,185 @@
+/*-------------------------------------------------------------------------
+ *
+ * execCurrent.c
+ *   executor support for WHERE CURRENT OF cursor
+ *
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.1 2007/06/11 01:16:22 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "executor/executor.h"
+#include "utils/lsyscache.h"
+#include "utils/portal.h"
+
+
+static ScanState *search_plan_tree(PlanState *node, Oid table_oid);
+
+
+/*
+ * execCurrentOf
+ *
+ * Given the name of a cursor and the OID of a table, determine which row
+ * of the table is currently being scanned by the cursor, and return its
+ * TID into *current_tid.
+ *
+ * Returns TRUE if a row was identified.  Returns FALSE if the cursor is valid
+ * for the table but is not currently scanning a row of the table (this is a
+ * legal situation in inheritance cases).  Raises error if cursor is not a
+ * valid updatable scan of the specified table.
+ */
+bool
+execCurrentOf(char *cursor_name, Oid table_oid,
+             ItemPointer current_tid)
+{
+   char       *table_name;
+   Portal      portal;
+   QueryDesc *queryDesc;
+   ScanState  *scanstate;
+   HeapTuple tup;
+
+   /* Fetch table name for possible use in error messages */
+   table_name = get_rel_name(table_oid);
+   if (table_name == NULL)
+       elog(ERROR, "cache lookup failed for relation %u", table_oid);
+
+   /* Find the cursor's portal */
+   portal = GetPortalByName(cursor_name);
+   if (!PortalIsValid(portal))
+       ereport(ERROR,
+               (errcode(ERRCODE_UNDEFINED_CURSOR),
+                errmsg("cursor \"%s\" does not exist", cursor_name)));
+
+   /*
+    * We have to watch out for non-SELECT queries as well as held cursors,
+    * both of which may have null queryDesc.
+    */
+   if (portal->strategy != PORTAL_ONE_SELECT)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_CURSOR_STATE),
+                errmsg("cursor \"%s\" is not a SELECT query",
+                       cursor_name)));
+   queryDesc = PortalGetQueryDesc(portal);
+   if (queryDesc == NULL)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_CURSOR_STATE),
+                errmsg("cursor \"%s\" is held from a previous transaction",
+                       cursor_name)));
+
+   /*
+    * Dig through the cursor's plan to find the scan node.  Fail if it's
+    * not there or buried underneath aggregation.
+    */
+   scanstate = search_plan_tree(ExecGetActivePlanTree(queryDesc),
+                                table_oid);
+   if (!scanstate)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_CURSOR_STATE),
+                errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
+                       cursor_name, table_name)));
+
+   /*
+    * The cursor must have a current result row: per the SQL spec, it's
+    * an error if not.  We test this at the top level, rather than at
+    * the scan node level, because in inheritance cases any one table
+    * scan could easily not be on a row.  We want to return false, not
+    * raise error, if the passed-in table OID is for one of the inactive
+    * scans.
+    */
+   if (portal->atStart || portal->atEnd)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_CURSOR_STATE),
+                errmsg("cursor \"%s\" is not positioned on a row",
+                       cursor_name)));
+
+   /* Now OK to return false if we found an inactive scan */
+   if (TupIsNull(scanstate->ss_ScanTupleSlot))
+       return false;
+
+   tup = scanstate->ss_ScanTupleSlot->tts_tuple;
+   if (tup == NULL)
+       elog(ERROR, "CURRENT OF applied to non-materialized tuple");
+   Assert(tup->t_tableOid == table_oid);
+
+   *current_tid = tup->t_self;
+
+   return true;
+}
+
+/*
+ * search_plan_tree
+ *
+ * Search through a PlanState tree for a scan node on the specified table.
+ * Return NULL if not found or multiple candidates.
+ */
+static ScanState *
+search_plan_tree(PlanState *node, Oid table_oid)
+{
+   if (node == NULL)
+       return NULL;
+   switch (nodeTag(node))
+   {
+           /*
+            * scan nodes can all be treated alike
+            */
+       case T_SeqScanState:
+       case T_IndexScanState:
+       case T_BitmapHeapScanState:
+       case T_TidScanState:
+       {
+           ScanState *sstate = (ScanState *) node;
+
+           if (RelationGetRelid(sstate->ss_currentRelation) == table_oid)
+               return sstate;
+           break;
+       }
+
+           /*
+            * For Append, we must look through the members; watch out for
+            * multiple matches (possible if it was from UNION ALL)
+            */
+       case T_AppendState:
+       {
+           AppendState *astate = (AppendState *) node;
+           ScanState *result = NULL;
+           int     i;
+
+           for (i = 0; i < astate->as_nplans; i++)
+           {
+               ScanState *elem = search_plan_tree(astate->appendplans[i],
+                                                  table_oid);
+
+               if (!elem)
+                   continue;
+               if (result)
+                   return NULL;                /* multiple matches */
+               result = elem;
+           }
+           return result;
+       }
+
+           /*
+            * Result and Limit can be descended through (these are safe
+            * because they always return their input's current row)
+            */
+       case T_ResultState:
+       case T_LimitState:
+           return search_plan_tree(node->lefttree, table_oid);
+
+           /*
+            * SubqueryScan too, but it keeps the child in a different place
+            */
+       case T_SubqueryScanState:
+           return search_plan_tree(((SubqueryScanState *) node)->subplan,
+                                   table_oid);
+
+       default:
+           /* Otherwise, assume we can't descend through it */
+           break;
+   }
+   return NULL;
+}
index 82b17b19f0112e25275ae026fc8babee129bc336..dfd1a84ab77f90b1e09512a87f985296a6e64de0 100644 (file)
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.294 2007/06/03 17:07:00 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.295 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2368,6 +2368,24 @@ EvalPlanQualStop(evalPlanQual *epq)
    epq->planstate = NULL;
 }
 
+/*
+ * ExecGetActivePlanTree --- get the active PlanState tree from a QueryDesc
+ *
+ * Ordinarily this is just the one mentioned in the QueryDesc, but if we
+ * are looking at a row returned by the EvalPlanQual machinery, we need
+ * to look at the subsidiary state instead.
+ */
+PlanState *
+ExecGetActivePlanTree(QueryDesc *queryDesc)
+{
+   EState     *estate = queryDesc->estate;
+
+   if (estate && estate->es_useEvalPlan && estate->es_evalPlanQual != NULL)
+       return estate->es_evalPlanQual->planstate;
+   else
+       return queryDesc->planstate;
+}
+
 
 /*
  * Support for SELECT INTO (a/k/a CREATE TABLE AS)
index fd54b89f0a5c401d4fdad89a6616a41fe1c76594..5549142e703287324426f3d9c132813b4be8f663 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.218 2007/06/05 21:31:04 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.219 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -151,6 +151,8 @@ static Datum ExecEvalCoerceViaIO(CoerceViaIOState *iostate,
 static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
                                     ExprContext *econtext,
                                     bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
+           bool *isNull, ExprDoneCond *isDone);
 
 
 /* ----------------------------------------------------------------
@@ -3618,6 +3620,41 @@ ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
                     astate->amstate);
 }
 
+/* ----------------------------------------------------------------
+ *     ExecEvalCurrentOfExpr
+ *
+ * Normally, the planner will convert CURRENT OF into a TidScan qualification,
+ * but we have plain execQual support in case it doesn't.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
+                     bool *isNull, ExprDoneCond *isDone)
+{
+   CurrentOfExpr *cexpr = (CurrentOfExpr *) exprstate->expr;
+   bool result;
+   HeapTuple tup;
+   ItemPointerData cursor_tid;
+
+   if (isDone)
+       *isDone = ExprSingleResult;
+   *isNull = false;
+
+   Assert(cexpr->cvarno != INNER);
+   Assert(cexpr->cvarno != OUTER);
+   Assert(!TupIsNull(econtext->ecxt_scantuple));
+   tup = econtext->ecxt_scantuple->tts_tuple;
+   if (tup == NULL)
+       elog(ERROR, "CURRENT OF applied to non-materialized tuple");
+
+   if (execCurrentOf(cexpr->cursor_name, tup->t_tableOid, &cursor_tid))
+       result = ItemPointerEquals(&cursor_tid, &(tup->t_self));
+   else
+       result = false;
+
+   return BoolGetDatum(result);
+}
+
 
 /*
  * ExecEvalExprSwitchContext
@@ -4266,6 +4303,10 @@ ExecInitExpr(Expr *node, PlanState *parent)
                state = (ExprState *) cstate;
            }
            break;
+       case T_CurrentOfExpr:
+           state = (ExprState *) makeNode(ExprState);
+           state->evalfunc = ExecEvalCurrentOfExpr;
+           break;
        case T_TargetEntry:
            {
                TargetEntry *tle = (TargetEntry *) node;
index 9b6724537e70b6e3522509db79bf951143fd30f7..986ff1f3f197edbf84990cf4183250bf34a92669 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/executor/nodeTidscan.c,v 1.53 2007/01/05 22:19:28 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/executor/nodeTidscan.c,v 1.54 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,8 +61,8 @@ TidListCreate(TidScanState *tidstate)
 
    /*
     * We initialize the array with enough slots for the case that all quals
-    * are simple OpExprs.  If there's any ScalarArrayOpExprs, we may have to
-    * enlarge the array.
+    * are simple OpExprs or CurrentOfExprs.  If there are any
+    * ScalarArrayOpExprs, we may have to enlarge the array.
     */
    numAllocTids = list_length(evalList);
    tidList = (ItemPointerData *)
@@ -148,6 +148,25 @@ TidListCreate(TidScanState *tidstate)
            pfree(ipdatums);
            pfree(ipnulls);
        }
+       else if (expr && IsA(expr, CurrentOfExpr))
+       {
+           CurrentOfExpr *cexpr = (CurrentOfExpr *) expr;
+           ItemPointerData cursor_tid;
+
+           if (execCurrentOf(cexpr->cursor_name,
+                         RelationGetRelid(tidstate->ss.ss_currentRelation),
+                             &cursor_tid))
+           {
+               if (numTids >= numAllocTids)
+               {
+                   numAllocTids *= 2;
+                   tidList = (ItemPointerData *)
+                       repalloc(tidList,
+                                numAllocTids * sizeof(ItemPointerData));
+               }
+               tidList[numTids++] = cursor_tid;
+           }
+       }
        else
            elog(ERROR, "could not identify CTID expression");
    }
index 39027b1dbc84706eb2cce718360184efadd52c46..c868ff574d9357aa30d7cf3187ba4fce95d4d478 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.377 2007/06/05 21:31:04 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.378 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1299,6 +1299,20 @@ _copySetToDefault(SetToDefault *from)
    return newnode;
 }
 
+/*
+ * _copyCurrentOfExpr
+ */
+static CurrentOfExpr *
+_copyCurrentOfExpr(CurrentOfExpr *from)
+{
+   CurrentOfExpr *newnode = makeNode(CurrentOfExpr);
+
+   COPY_SCALAR_FIELD(cvarno);
+   COPY_STRING_FIELD(cursor_name);
+
+   return newnode;
+}
+
 /*
  * _copyTargetEntry
  */
@@ -3177,6 +3191,9 @@ copyObject(void *from)
        case T_SetToDefault:
            retval = _copySetToDefault(from);
            break;
+       case T_CurrentOfExpr:
+           retval = _copyCurrentOfExpr(from);
+           break;
        case T_TargetEntry:
            retval = _copyTargetEntry(from);
            break;
index 8a0957c117e5dad4578e5420e93432aed54d6aee..04072c7a65422560271d422496ac86230e40f817 100644 (file)
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.308 2007/06/05 21:31:04 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.309 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -598,6 +598,15 @@ _equalSetToDefault(SetToDefault *a, SetToDefault *b)
    return true;
 }
 
+static bool
+_equalCurrentOfExpr(CurrentOfExpr *a, CurrentOfExpr *b)
+{
+   COMPARE_SCALAR_FIELD(cvarno);
+   COMPARE_STRING_FIELD(cursor_name);
+
+   return true;
+}
+
 static bool
 _equalTargetEntry(TargetEntry *a, TargetEntry *b)
 {
@@ -2124,6 +2133,9 @@ equal(void *a, void *b)
        case T_SetToDefault:
            retval = _equalSetToDefault(a, b);
            break;
+       case T_CurrentOfExpr:
+           retval = _equalCurrentOfExpr(a, b);
+           break;
        case T_TargetEntry:
            retval = _equalTargetEntry(a, b);
            break;
index 5de540642f482fa2b14a1ce96abc368ff3f1850f..869905f0cc548e01b42ecf74f314bbc850b43597 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.309 2007/06/05 21:31:04 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.310 2007/06/11 01:16:22 tgl Exp $
  *
  * NOTES
  *   Every node type that can appear in stored rules' parsetrees *must*
@@ -1058,6 +1058,15 @@ _outSetToDefault(StringInfo str, SetToDefault *node)
    WRITE_INT_FIELD(typeMod);
 }
 
+static void
+_outCurrentOfExpr(StringInfo str, CurrentOfExpr *node)
+{
+   WRITE_NODE_TYPE("CURRENTOFEXPR");
+
+   WRITE_UINT_FIELD(cvarno);
+   WRITE_STRING_FIELD(cursor_name);
+}
+
 static void
 _outTargetEntry(StringInfo str, TargetEntry *node)
 {
@@ -2229,6 +2238,9 @@ _outNode(StringInfo str, void *obj)
            case T_SetToDefault:
                _outSetToDefault(str, obj);
                break;
+           case T_CurrentOfExpr:
+               _outCurrentOfExpr(str, obj);
+               break;
            case T_TargetEntry:
                _outTargetEntry(str, obj);
                break;
index 86c9e911a7c8dfbdb2fa11b49e06c97c159a9af8..e91a6e5b501a92b83593d772ea9b1a05b388da1f 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.207 2007/06/05 21:31:04 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.208 2007/06/11 01:16:22 tgl Exp $
  *
  * NOTES
  *   Path and Plan nodes do not have any readfuncs support, because we
@@ -873,6 +873,20 @@ _readSetToDefault(void)
    READ_DONE();
 }
 
+/*
+ * _readCurrentOfExpr
+ */
+static CurrentOfExpr *
+_readCurrentOfExpr(void)
+{
+   READ_LOCALS(CurrentOfExpr);
+
+   READ_UINT_FIELD(cvarno);
+   READ_STRING_FIELD(cursor_name);
+
+   READ_DONE();
+}
+
 /*
  * _readTargetEntry
  */
@@ -1093,6 +1107,8 @@ parseNodeString(void)
        return_value = _readCoerceToDomainValue();
    else if (MATCH("SETTODEFAULT", 12))
        return_value = _readSetToDefault();
+   else if (MATCH("CURRENTOFEXPR", 13))
+       return_value = _readCurrentOfExpr();
    else if (MATCH("TARGETENTRY", 11))
        return_value = _readTargetEntry();
    else if (MATCH("RANGETBLREF", 11))
index b8bbc29c505bb3c418144644d8d0cac819003d59..4b48ae1e26072fcae607a3db2fe179d98d4ccc26 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.85 2007/04/21 21:01:44 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.86 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,6 +18,7 @@
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/pathnode.h"
 #include "optimizer/plancat.h"
 #include "parser/parsetree.h"
 #include "utils/fmgroids.h"
@@ -712,6 +713,15 @@ clause_selectivity(PlannerInfo *root,
                         varRelid,
                         jointype);
    }
+   else if (IsA(clause, CurrentOfExpr))
+   {
+       /* CURRENT OF selects at most one row of its table */
+       CurrentOfExpr *cexpr = (CurrentOfExpr *) clause;
+       RelOptInfo *crel = find_base_rel(root, cexpr->cvarno);
+
+       if (crel->tuples > 0)
+           s1 = 1.0 / crel->tuples;
+   }
    else if (IsA(clause, RelabelType))
    {
        /* Not sure this case is needed, but it can't hurt */
index a4d03e9f8f8d6071386736ec0c935b37092a17ed..f76c778998b26c111b362e77bce57a68fa26cbbb 100644 (file)
@@ -54,7 +54,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.184 2007/06/05 21:31:05 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.185 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -770,6 +770,7 @@ cost_tidscan(Path *path, PlannerInfo *root,
    Cost        startup_cost = 0;
    Cost        run_cost = 0;
    Cost        cpu_per_tuple;
+   QualCost    tid_qual_cost;
    int         ntuples;
    ListCell   *l;
 
@@ -799,12 +800,20 @@ cost_tidscan(Path *path, PlannerInfo *root,
        }
    }
 
+   /*
+    * The TID qual expressions will be computed once, any other baserestrict
+    * quals once per retrived tuple.
+    */
+   cost_qual_eval(&tid_qual_cost, tidquals, root);
+
    /* disk costs --- assume each tuple on a different page */
    run_cost += random_page_cost * ntuples;
 
    /* CPU costs */
-   startup_cost += baserel->baserestrictcost.startup;
-   cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
+   startup_cost += baserel->baserestrictcost.startup +
+       tid_qual_cost.per_tuple;
+   cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple -
+       tid_qual_cost.per_tuple;
    run_cost += cpu_per_tuple * ntuples;
 
    path->startup_cost = startup_cost;
@@ -1991,6 +2000,11 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
                cpu_operator_cost;
        }
    }
+   else if (IsA(node, CurrentOfExpr))
+   {
+       /* This is noticeably more expensive than a typical operator */
+       context->total.per_tuple += 100 * cpu_operator_cost;
+   }
    else if (IsA(node, SubLink))
    {
        /* This routine should not be applied to un-planned expressions */
index 2470493708a0fd4bf2049eb0704229eeff11b4dd..84564dde73456153c8308994288c4a0d7585b451 100644 (file)
  * this allows
  *     WHERE ctid IN (tid1, tid2, ...)
  *
+ * We also support "WHERE CURRENT OF cursor" conditions (CurrentOfExpr),
+ * which amount to "CTID = run-time-determined-TID".  These could in
+ * theory be translated to a simple comparison of CTID to the result of
+ * a function, but in practice it works better to keep the special node
+ * representation all the way through to execution.
+ *
  * There is currently no special support for joins involving CTID; in
  * particular nothing corresponding to best_inner_indexscan(). Since it's
  * not very useful to store TIDs of one table in another table, there
@@ -24,7 +30,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/path/tidpath.c,v 1.29 2007/01/05 22:19:31 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/path/tidpath.c,v 1.30 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -174,6 +180,12 @@ TidQualFromExpr(Node *expr, int varno)
        if (IsTidEqualAnyClause((ScalarArrayOpExpr *) expr, varno))
            rlst = list_make1(expr);
    }
+   else if (expr && IsA(expr, CurrentOfExpr))
+   {
+       /* another base case: check for CURRENT OF on this rel */
+       if (((CurrentOfExpr *) expr)->cvarno == varno)
+           rlst = list_make1(expr);
+   }
    else if (and_clause(expr))
    {
        foreach(l, ((BoolExpr *) expr)->args)
index 90a49983ac63798a334993a48031f71cf353eb8d..055b47beec7d6791aa055749eafe65f4299ccc0b 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.135 2007/04/30 00:16:43 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.136 2007/06/11 01:16:23 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -618,6 +618,15 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
            var->varnoold += context->rtoffset;
        return (Node *) var;
    }
+   if (IsA(node, CurrentOfExpr))
+   {
+       CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
+
+       Assert(cexpr->cvarno != INNER);
+       Assert(cexpr->cvarno != OUTER);
+       cexpr->cvarno += context->rtoffset;
+       return (Node *) cexpr;
+   }
    /*
     * Since we update opcode info in-place, this part could possibly
     * scribble on the planner's input data structures, but it's OK.
index 2b273f738a8bf31152cbdf9467886a33428808e4..5e80dc1559a581aba8e7da0c28558c923a16e7db 100644 (file)
@@ -22,7 +22,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.141 2007/04/21 05:56:41 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.142 2007/06/11 01:16:23 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1132,6 +1132,14 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context)
        }
        return (Node *) var;
    }
+   if (IsA(node, CurrentOfExpr))
+   {
+       CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
+
+       if (cexpr->cvarno == context->parent_relid)
+           cexpr->cvarno = context->child_relid;
+       return (Node *) cexpr;
+   }
    if (IsA(node, RangeTblRef))
    {
        RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
index 00f5f6e2c8175fb067bc77058e9470ec29a8d7c1..2cf0ffd28b05e98f77e0f0a8e0b0190390acf37b 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.245 2007/06/05 21:31:05 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.246 2007/06/11 01:16:23 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -3407,6 +3407,7 @@ expression_tree_walker(Node *node,
        case T_CoerceToDomainValue:
        case T_CaseTestExpr:
        case T_SetToDefault:
+       case T_CurrentOfExpr:
        case T_RangeTblRef:
        case T_OuterJoinInfo:
            /* primitive node types with no expression subnodes */
@@ -3873,6 +3874,7 @@ expression_tree_mutator(Node *node,
        case T_CoerceToDomainValue:
        case T_CaseTestExpr:
        case T_SetToDefault:
+       case T_CurrentOfExpr:
        case T_RangeTblRef:
        case T_OuterJoinInfo:
            return (Node *) copyObject(node);
index 13702b7d4656155712e315545fa7a69b475d7dc9..c501c8279228b4974d92d733624a2a81d014cb1b 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.69 2007/01/05 22:19:33 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.70 2007/06/11 01:16:23 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -111,6 +111,14 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
            context->varnos = bms_add_member(context->varnos, var->varno);
        return false;
    }
+   if (IsA(node, CurrentOfExpr))
+   {
+       CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+       if (context->sublevels_up == 0)
+           context->varnos = bms_add_member(context->varnos, cexpr->cvarno);
+       return false;
+   }
    if (IsA(node, Query))
    {
        /* Recurse into RTE subquery or not-yet-planned sublink subquery */
@@ -217,6 +225,8 @@ contain_var_clause_walker(Node *node, void *context)
            return true;        /* abort the tree traversal and return true */
        return false;
    }
+   if (IsA(node, CurrentOfExpr))
+       return true;
    return expression_tree_walker(node, contain_var_clause_walker, context);
 }
 
@@ -249,6 +259,13 @@ contain_vars_of_level_walker(Node *node, int *sublevels_up)
    {
        if (((Var *) node)->varlevelsup == *sublevels_up)
            return true;        /* abort tree traversal and return true */
+       return false;
+   }
+   if (IsA(node, CurrentOfExpr))
+   {
+       if (*sublevels_up == 0)
+           return true;
+       return false;
    }
    if (IsA(node, Query))
    {
@@ -376,6 +393,29 @@ find_minimum_var_level_walker(Node *node,
            }
        }
    }
+   if (IsA(node, CurrentOfExpr))
+   {
+       int         varlevelsup = 0;
+
+       /* convert levelsup to frame of reference of original query */
+       varlevelsup -= context->sublevels_up;
+       /* ignore local vars of subqueries */
+       if (varlevelsup >= 0)
+       {
+           if (context->min_varlevel < 0 ||
+               context->min_varlevel > varlevelsup)
+           {
+               context->min_varlevel = varlevelsup;
+
+               /*
+                * As soon as we find a local variable, we can abort the tree
+                * traversal, since min_varlevel is then certainly 0.
+                */
+               if (varlevelsup == 0)
+                   return true;
+           }
+       }
+   }
 
    /*
     * An Aggref must be treated like a Var of its level.  Normally we'd get
index 68475387be367779f91683b0588779bcebd0d9f0..3ac6f000d857a00a45b00bbf2f897708dfdb362a 100644 (file)
@@ -20,7 +20,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.363 2007/04/27 22:05:48 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.364 2007/06/11 01:16:24 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -3178,12 +3178,12 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
                (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
                 errmsg("DECLARE CURSOR cannot specify INTO")));
 
-   /* Implementation restriction (might go away someday) */
-   if (result->rowMarks != NIL)
+   /* FOR UPDATE and WITH HOLD are not compatible */
+   if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD))
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-             errmsg("DECLARE CURSOR ... FOR UPDATE/SHARE is not supported"),
-                errdetail("Cursors must be READ ONLY.")));
+             errmsg("DECLARE CURSOR WITH HOLD ... FOR UPDATE/SHARE is not supported"),
+                errdetail("Holdable cursors must be READ ONLY.")));
 
    /* We won't need the raw querytree any more */
    stmt->query = NULL;
index 8884da228921cad401ebb63ffb634f546bdeb1e4..b50be6bd739f7c32a7b957d94124d5604627bb42 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.591 2007/04/27 22:05:48 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.592 2007/06/11 01:16:25 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -296,7 +296,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args)
 %type    TableElement ConstraintElem TableFuncElement
 %type    columnDef
 %type  def_elem old_aggr_elem
-%type    def_arg columnElem where_clause
+%type    def_arg columnElem where_clause where_or_current_clause
                a_expr b_expr c_expr func_expr AexprConst indirection_el
                columnref in_expr having_clause func_table array_expr
 %type    row type_list array_expr_list
@@ -377,8 +377,8 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args)
    CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
    COMMITTED CONCURRENTLY CONNECTION CONSTRAINT CONSTRAINTS
    CONTENT_P CONVERSION_P CONVERT COPY COST CREATE CREATEDB
-   CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME
-   CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
+   CREATEROLE CREATEUSER CROSS CSV CURRENT_P CURRENT_DATE CURRENT_ROLE
+   CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
    DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
    DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS
@@ -5715,7 +5715,7 @@ returning_clause:
  *****************************************************************************/
 
 DeleteStmt: DELETE_P FROM relation_expr_opt_alias
-           using_clause where_clause returning_clause
+           using_clause where_or_current_clause returning_clause
                {
                    DeleteStmt *n = makeNode(DeleteStmt);
                    n->relation = $3;
@@ -5771,7 +5771,7 @@ opt_nowait:   NOWAIT                          { $$ = TRUE; }
 UpdateStmt: UPDATE relation_expr_opt_alias
            SET set_clause_list
            from_clause
-           where_clause
+           where_or_current_clause
            returning_clause
                {
                    UpdateStmt *n = makeNode(UpdateStmt);
@@ -6562,6 +6562,18 @@ where_clause:
            | /*EMPTY*/                             { $$ = NULL; }
        ;
 
+/* variant for UPDATE and DELETE */
+where_or_current_clause:
+           WHERE a_expr                            { $$ = $2; }
+           | WHERE CURRENT_P OF name
+               {
+                   CurrentOfExpr *n = makeNode(CurrentOfExpr);
+                   n->cursor_name = $4;
+                   $$ = (Node *) n;
+               }
+           | /*EMPTY*/                             { $$ = NULL; }
+       ;
+
 
 TableFuncElementList:
            TableFuncElement
@@ -8818,6 +8830,7 @@ unreserved_keyword:
            | CREATEROLE
            | CREATEUSER
            | CSV
+           | CURRENT_P
            | CURSOR
            | CYCLE
            | DATABASE
index 5c8ef10a214a4646a61a71f94c61bd3fd06700b2..b48a0c79583d8a0adc45587d21634060f7a123be 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.187 2007/04/26 16:13:12 neilc Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.188 2007/06/11 01:16:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -101,6 +101,7 @@ static const ScanKeyword ScanKeywords[] = {
    {"createuser", CREATEUSER},
    {"cross", CROSS},
    {"csv", CSV},
+   {"current", CURRENT_P},
    {"current_date", CURRENT_DATE},
    {"current_role", CURRENT_ROLE},
    {"current_time", CURRENT_TIME},
index 45107e43acea9abb4d4e1f8552211a80a92e9979..6601bfe40ee4d8a5df0fd1b1f4cca07c895a8c93 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.218 2007/06/05 21:31:05 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.219 2007/06/11 01:16:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -243,6 +243,21 @@ transformExpr(ParseState *pstate, Node *expr)
            result = transformBooleanTest(pstate, (BooleanTest *) expr);
            break;
 
+       case T_CurrentOfExpr:
+           {
+               CurrentOfExpr *c = (CurrentOfExpr *) expr;
+               int     sublevels_up;
+
+               /* CURRENT OF can only appear at top level of UPDATE/DELETE */
+               Assert(pstate->p_target_rangetblentry != NULL);
+               c->cvarno = RTERangeTablePosn(pstate,
+                                             pstate->p_target_rangetblentry,
+                                             &sublevels_up);
+               Assert(sublevels_up == 0);
+               result = expr;
+               break;
+           }
+
            /*********************************************
             * Quietly accept node types that may be presented when we are
             * called on an already-transformed tree.
@@ -1863,6 +1878,9 @@ exprType(Node *expr)
        case T_SetToDefault:
            type = ((SetToDefault *) expr)->typeId;
            break;
+       case T_CurrentOfExpr:
+           type = BOOLOID;
+           break;
        default:
            elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
            type = InvalidOid;  /* keep compiler quiet */
index 8ea9ac103c65fdf655d72807a4556128f806261e..19b566389051d899647cda96970147caddec805c 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.103 2007/01/05 22:19:36 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.104 2007/06/11 01:16:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -151,6 +151,14 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
        }
        return false;
    }
+   if (IsA(node, CurrentOfExpr))
+   {
+       CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+       if (context->sublevels_up == 0)
+           cexpr->cvarno += context->offset;
+       return false;
+   }
    if (IsA(node, RangeTblRef))
    {
        RangeTblRef *rtr = (RangeTblRef *) node;
@@ -302,6 +310,15 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
        }
        return false;
    }
+   if (IsA(node, CurrentOfExpr))
+   {
+       CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+       if (context->sublevels_up == 0 &&
+           cexpr->cvarno == context->rt_index)
+           cexpr->cvarno = context->new_index;
+       return false;
+   }
    if (IsA(node, RangeTblRef))
    {
        RangeTblRef *rtr = (RangeTblRef *) node;
@@ -466,6 +483,13 @@ IncrementVarSublevelsUp_walker(Node *node,
            var->varlevelsup += context->delta_sublevels_up;
        return false;           /* done here */
    }
+   if (IsA(node, CurrentOfExpr))
+   {
+       /* this should not happen */
+       if (context->min_sublevels_up == 0)
+           elog(ERROR, "cannot push down CurrentOfExpr");
+       return false;
+   }
    if (IsA(node, Aggref))
    {
        Aggref     *agg = (Aggref *) node;
@@ -536,6 +560,15 @@ rangeTableEntry_used_walker(Node *node,
            return true;
        return false;
    }
+   if (IsA(node, CurrentOfExpr))
+   {
+       CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+       if (context->sublevels_up == 0 &&
+           cexpr->cvarno == context->rt_index)
+           return true;
+       return false;
+   }
    if (IsA(node, RangeTblRef))
    {
        RangeTblRef *rtr = (RangeTblRef *) node;
@@ -932,8 +965,27 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
        }
        /* otherwise fall through to copy the var normally */
    }
+   else if (IsA(node, CurrentOfExpr))
+   {
+       CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+       int         this_varno = (int) cexpr->cvarno;
 
-   if (IsA(node, Query))
+       if (this_varno == context->target_varno &&
+           context->sublevels_up == 0)
+       {
+           /*
+            * We get here if a WHERE CURRENT OF expression turns out to
+            * apply to a view.  Someday we might be able to translate
+            * the expression to apply to an underlying table of the view,
+            * but right now it's not implemented.
+            */
+           ereport(ERROR,
+                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                    errmsg("WHERE CURRENT OF on a view is not implemented")));
+       }
+       /* otherwise fall through to copy the expr normally */
+   }
+   else if (IsA(node, Query))
    {
        /* Recurse into RTE subquery or not-yet-planned sublink subquery */
        Query      *newnode;
index 78be35ef36da901bf22009750b2333dde13f9e79..c6f6b88248756b4bf6a95cc454b13c2ac7f94f2a 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.259 2007/06/05 21:31:06 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.260 2007/06/11 01:16:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -3086,6 +3086,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
        case T_Param:
        case T_CoerceToDomainValue:
        case T_SetToDefault:
+       case T_CurrentOfExpr:
            /* single words: always simple */
            return true;
 
@@ -4134,6 +4135,11 @@ get_rule_expr(Node *node, deparse_context *context,
            appendStringInfo(buf, "DEFAULT");
            break;
 
+       case T_CurrentOfExpr:
+           appendStringInfo(buf, "CURRENT OF %s",
+                   quote_identifier(((CurrentOfExpr *) node)->cursor_name));
+           break;
+
        case T_List:
            {
                char       *sep;
index f9d8d107b36d4eae81158777578465bce37926d1..408519c1e35704da3e94353098ff4621e934aae2 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.139 2007/02/27 01:11:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.140 2007/06/11 01:16:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -70,6 +70,12 @@ extern bool ExecSupportsMarkRestore(NodeTag plantype);
 extern bool ExecSupportsBackwardScan(Plan *node);
 extern bool ExecMayReturnRawTuples(PlanState *node);
 
+/*
+ * prototypes from functions in execCurrent.c
+ */
+extern bool execCurrentOf(char *cursor_name, Oid table_oid,
+                         ItemPointer current_tid);
+
 /*
  * prototypes from functions in execGrouping.c
  */
@@ -135,6 +141,7 @@ extern void ExecConstraints(ResultRelInfo *resultRelInfo,
                TupleTableSlot *slot, EState *estate);
 extern TupleTableSlot *EvalPlanQual(EState *estate, Index rti,
             ItemPointer tid, TransactionId priorXmax, CommandId curCid);
+extern PlanState *ExecGetActivePlanTree(QueryDesc *queryDesc);
 extern DestReceiver *CreateIntoRelDestReceiver(void);
 
 /*
index 31186930d20c3381b4edfbaa152fb03c8f40193e..c54a1a4522c41f7041ca2e6450e1ceaaff4a56ae 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.200 2007/06/05 21:31:08 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.201 2007/06/11 01:16:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -139,6 +139,7 @@ typedef enum NodeTag
    T_CoerceToDomain,
    T_CoerceToDomainValue,
    T_SetToDefault,
+   T_CurrentOfExpr,
    T_TargetEntry,
    T_RangeTblRef,
    T_JoinExpr,
index a567a8e26d49f5edd2a7a94caedb3ab914a69c5e..9a3e09b77ec6c3dc3cb991b945e91ae8dbe5de09 100644 (file)
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.130 2007/06/05 21:31:08 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.131 2007/06/11 01:16:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -915,6 +915,21 @@ typedef struct SetToDefault
    int32       typeMod;        /* typemod for substituted value */
 } SetToDefault;
 
+/*
+ * Node representing [WHERE] CURRENT OF cursor_name
+ *
+ * CURRENT OF is a bit like a Var, in that it carries the rangetable index
+ * of the target relation being constrained; this aids placing the expression
+ * correctly during planning.  We can assume however that its "levelsup" is
+ * always zero, due to the syntactic constraints on where it can appear.
+ */
+typedef struct CurrentOfExpr
+{
+   Expr        xpr;
+   Index       cvarno;         /* RT index of target relation */
+   char       *cursor_name;    /* name of referenced cursor */
+} CurrentOfExpr;
+
 /*--------------------
  * TargetEntry -
  *    a target entry (used in query target lists)
index 9e618dbc5fb8d7acf86c4fa884be241a8a155352..3638664b1bbc1cc3aca427bb12a6443f4ce6c883 100644 (file)
@@ -899,3 +899,176 @@ SELECT name FROM pg_cursors ORDER BY 1;
 (0 rows)
 
 COMMIT;
+--
+-- Tests for updatable cursors
+--
+CREATE TEMP TABLE uctest(f1 int, f2 text);
+INSERT INTO uctest VALUES (1, 'one'), (2, 'two'), (3, 'three');
+SELECT * FROM uctest;
+ f1 |  f2   
+----+-------
+  1 | one
+  2 | two
+  3 | three
+(3 rows)
+
+-- Check DELETE WHERE CURRENT
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+FETCH 2 FROM c1;
+ f1 | f2  
+----+-----
+  1 | one
+  2 | two
+(2 rows)
+
+DELETE FROM uctest WHERE CURRENT OF c1;
+-- should show deletion
+SELECT * FROM uctest;
+ f1 |  f2   
+----+-------
+  1 | one
+  3 | three
+(2 rows)
+
+-- cursor did not move
+FETCH ALL FROM c1;
+ f1 |  f2   
+----+-------
+  3 | three
+(1 row)
+
+-- cursor is insensitive
+MOVE BACKWARD ALL IN c1;
+FETCH ALL FROM c1;
+ f1 |  f2   
+----+-------
+  1 | one
+  2 | two
+  3 | three
+(3 rows)
+
+COMMIT;
+-- should still see deletion
+SELECT * FROM uctest;
+ f1 |  f2   
+----+-------
+  1 | one
+  3 | three
+(2 rows)
+
+-- Check UPDATE WHERE CURRENT; this time use FOR UPDATE
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE;
+FETCH c1;
+ f1 | f2  
+----+-----
+  1 | one
+(1 row)
+
+UPDATE uctest SET f1 = 8 WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+ f1 |  f2   
+----+-------
+  3 | three
+  8 | one
+(2 rows)
+
+COMMIT;
+SELECT * FROM uctest;
+ f1 |  f2   
+----+-------
+  3 | three
+  8 | one
+(2 rows)
+
+-- Check inheritance cases
+CREATE TEMP TABLE ucchild () inherits (uctest);
+INSERT INTO ucchild values(100, 'hundred');
+SELECT * FROM uctest;
+ f1  |   f2    
+-----+---------
+   3 | three
+   8 | one
+ 100 | hundred
+(3 rows)
+
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+FETCH 1 FROM c1;
+ f1 |  f2   
+----+-------
+  3 | three
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+ f1 | f2  
+----+-----
+  8 | one
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+ f1  |   f2    
+-----+---------
+ 100 | hundred
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+ f1 | f2 
+----+----
+(0 rows)
+
+COMMIT;
+SELECT * FROM uctest;
+ f1  |   f2    
+-----+---------
+  13 | three
+  18 | one
+ 110 | hundred
+(3 rows)
+
+-- Check various error cases
+DELETE FROM uctest WHERE CURRENT OF c1;  -- fail, no such cursor
+ERROR:  cursor "c1" does not exist
+DECLARE cx CURSOR WITH HOLD FOR SELECT * FROM uctest;
+DELETE FROM uctest WHERE CURRENT OF cx;  -- fail, can't use held cursor
+ERROR:  cursor "cx" is held from a previous transaction
+BEGIN;
+DECLARE c CURSOR FOR SELECT * FROM tenk2;
+DELETE FROM uctest WHERE CURRENT OF c;  -- fail, cursor on wrong table
+ERROR:  cursor "c" is not a simply updatable scan of table "uctest"
+ROLLBACK;
+BEGIN;
+DECLARE c CURSOR FOR SELECT * FROM tenk1 JOIN tenk2 USING (unique1);
+DELETE FROM tenk1 WHERE CURRENT OF c;  -- fail, cursor is on a join
+ERROR:  cursor "c" is not a simply updatable scan of table "tenk1"
+ROLLBACK;
+BEGIN;
+DECLARE c CURSOR FOR SELECT f1,count(*) FROM uctest GROUP BY f1;
+DELETE FROM uctest WHERE CURRENT OF c;  -- fail, cursor is on aggregation
+ERROR:  cursor "c" is not a simply updatable scan of table "uctest"
+ROLLBACK;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no current row
+ERROR:  cursor "c1" is not positioned on a row
+ROLLBACK;
+-- WHERE CURRENT OF may someday work with views, but today is not that day.
+-- For now, just make sure it errors out cleanly.
+CREATE TEMP VIEW ucview AS SELECT * FROM uctest;
+CREATE RULE ucrule AS ON DELETE TO ucview DO INSTEAD
+  DELETE FROM uctest WHERE f1 = OLD.f1;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM ucview;
+FETCH FROM c1;
+ f1 |  f2   
+----+-------
+ 13 | three
+(1 row)
+
+DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported
+ERROR:  WHERE CURRENT OF on a view is not implemented
+ROLLBACK;
index 18f803b4391690e0bc5b8ef4e6a73f0f42cf2017..382a28c4e30bfa156de6f5c0a0d442c5851a56af 100644 (file)
@@ -316,5 +316,85 @@ CLOSE ALL;
 SELECT name FROM pg_cursors ORDER BY 1;
 COMMIT;
 
+--
+-- Tests for updatable cursors
+--
+
+CREATE TEMP TABLE uctest(f1 int, f2 text);
+INSERT INTO uctest VALUES (1, 'one'), (2, 'two'), (3, 'three');
+SELECT * FROM uctest;
+
+-- Check DELETE WHERE CURRENT
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+FETCH 2 FROM c1;
+DELETE FROM uctest WHERE CURRENT OF c1;
+-- should show deletion
+SELECT * FROM uctest;
+-- cursor did not move
+FETCH ALL FROM c1;
+-- cursor is insensitive
+MOVE BACKWARD ALL IN c1;
+FETCH ALL FROM c1;
+COMMIT;
+-- should still see deletion
+SELECT * FROM uctest;
+
+-- Check UPDATE WHERE CURRENT; this time use FOR UPDATE
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE;
+FETCH c1;
+UPDATE uctest SET f1 = 8 WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+COMMIT;
+SELECT * FROM uctest;
+
+-- Check inheritance cases
+CREATE TEMP TABLE ucchild () inherits (uctest);
+INSERT INTO ucchild values(100, 'hundred');
+SELECT * FROM uctest;
+
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+FETCH 1 FROM c1;
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+COMMIT;
+SELECT * FROM uctest;
+
+-- Check various error cases
 
+DELETE FROM uctest WHERE CURRENT OF c1;  -- fail, no such cursor
+DECLARE cx CURSOR WITH HOLD FOR SELECT * FROM uctest;
+DELETE FROM uctest WHERE CURRENT OF cx;  -- fail, can't use held cursor
+BEGIN;
+DECLARE c CURSOR FOR SELECT * FROM tenk2;
+DELETE FROM uctest WHERE CURRENT OF c;  -- fail, cursor on wrong table
+ROLLBACK;
+BEGIN;
+DECLARE c CURSOR FOR SELECT * FROM tenk1 JOIN tenk2 USING (unique1);
+DELETE FROM tenk1 WHERE CURRENT OF c;  -- fail, cursor is on a join
+ROLLBACK;
+BEGIN;
+DECLARE c CURSOR FOR SELECT f1,count(*) FROM uctest GROUP BY f1;
+DELETE FROM uctest WHERE CURRENT OF c;  -- fail, cursor is on aggregation
+ROLLBACK;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no current row
+ROLLBACK;
 
+-- WHERE CURRENT OF may someday work with views, but today is not that day.
+-- For now, just make sure it errors out cleanly.
+CREATE TEMP VIEW ucview AS SELECT * FROM uctest;
+CREATE RULE ucrule AS ON DELETE TO ucview DO INSTEAD
+  DELETE FROM uctest WHERE f1 = OLD.f1;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM ucview;
+FETCH FROM c1;
+DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported
+ROLLBACK;