+ Note that the
SQL SELECT
+ corresponds to the projection
in relational algebra
+
not to the selection
(see
endterm="rel-alg"> for more details).
Aggregate Operators
-
SQL provides aggregate operators
- (e.g. AVG, COUNT, SUM, MIN, MAX) that
- take an expression as argument. The expression is evaluated at
- each row that satisfies the WHERE clause, and the aggregate operator
- is calculated over this set of input values. Normally, an aggregate
- delivers a single result for a whole SELECT statement. But if
- grouping is specified in the query, then a separate calculation is done
- over the rows of each group, and an aggregate result is delivered per
- group (see next section).
+
SQL provides aggregate operators
(e.g. AVG,
+ COUNT, SUM, MIN, MAX) that take an expression as argument. The
+ expression is evaluated at each row that satisfies the WHERE
+ clause, and the aggregate operator is calculated over this set
+ of input values. Normally, an aggregate delivers a single
+ result for a whole SELECT statement. But if
+ grouping is specified in the query, then a separate calculation
+ is done over the rows of each group, and an aggregate result is
+ delivered per group (see next section).
Aggregates
- Also observe that it makes no sense to ask for an aggregate of an
- aggregate, e.g., AVG(MAX(sno)), because a SELECT only does one pass
- of grouping and aggregation. You can get a result of this kind by
- using a temporary table or a sub-SELECT in the FROM clause to
- do the first level of aggregation.
+ Also observe that it makes no sense to ask for an aggregate of
+ an aggregate, e.g., AVG(MAX(sno)), because a
+ SELECT only does one pass of grouping and
+ aggregation. You can get a result of this kind by using a
+ temporary table or a sub-SELECT in the FROM clause to do the
+ first level of aggregation.
- When we look at the above query we can see
- the keyword SELECT two times. The first one at the beginning of the
- query - we will refer to it as outer SELECT - and the one in the WHERE
- clause which begins a nested query - we will refer to it as inner
- SELECT. For every tuple of the outer SELECT the inner SELECT has to be
- evaluated. After every evaluation we know the price of the tuple named
- 'Screw' and we can check if the price of the actual tuple is
- greater. (Actually, in this example the inner query need only be
- evaluated once, since it does not depend on the state of the outer
- query.)
+ When we look at the above query we can see the keyword
+ SELECT two times. The first one at the
+ beginning of the query - we will refer to it as outer
+ SELECT - and the one in the WHERE clause which
+ begins a nested query - we will refer to it as inner
+ SELECT. For every tuple of the outer
+ SELECT the inner SELECT has
+ to be evaluated. After every evaluation we know the price of the
+ tuple named 'Screw' and we can check if the price of the actual
+ tuple is greater. (Actually, in this example the inner query need
+ only be evaluated once, since it does not depend on the state of
+ the outer query.)
- In our example the result will be empty because every supplier sells
- at least one part. Note that we use S.SNO from the outer SELECT within
- the WHERE clause of the inner SELECT. Here the subquery must be
- evaluated afresh for each tuple from the outer query, i.e. the value for
- S.SNO is always taken from the current tuple of the outer SELECT.
+ In our example the result will be empty because every supplier
+ sells at least one part. Note that we use S.SNO from the outer
+ SELECT within the WHERE clause of the inner
+ SELECT. Here the subquery must be evaluated
+ afresh for each tuple from the outer query, i.e. the value for
+ S.SNO is always taken from the current tuple of the outer
+ SELECT.
The most fundamental command for data definition is the
one that creates a new relation (a new table). The syntax of the
- CREATE TABLE command is:
+ CREATE TABLE command is:
CREATE TABLE table_name
To create an index in
SQL
- the CREATE INDEX command is used. The syntax is:
+ the CREATE INDEX command is used. The syntax is:
CREATE INDEX index_name
- The created index is maintained automatically, i.e. whenever a new tuple
- is inserted into the relation SUPPLIER the index I is adapted. Note
- that the only changes a user can perceive when an index is present
- are increased speed for SELECT and decreases in speed of updates.
+ The created index is maintained automatically, i.e. whenever a new
+ tuple is inserted into the relation SUPPLIER the index I is
+ adapted. Note that the only changes a user can perceive when an
+ index is present are increased speed for SELECT
+ and decreases in speed of updates.
To destroy a table (including all tuples stored in that table) the
- DROP TABLE command is used:
+ DROP TABLE command is used:
DROP TABLE table_name;
- The DROP INDEX command is used to destroy an index:
+ The DROP INDEX command is used to destroy an index:
DROP INDEX index_name;
- Finally to destroy a given view use the command DROP VIEW:
+ Finally to destroy a given view use the command DROP
+ VIEW:
DROP VIEW view_name;
To change one or more attribute values of tuples in a relation the
- UPDATE command is used. The syntax is:
+ UPDATE command is used. The syntax is:
UPDATE table_name
need a mechanism to access every single tuple of the set of tuples
returned by a SELECT statement. This mechanism can be provided by
declaring a cursor.
- After that we can use the FETCH command to
+ After that we can use the FETCH command to
retrieve a tuple and set the cursor to the next tuple.
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.144 2003/03/21 04:33:15 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.145 2003/03/27 16:51:27 momjian Exp $
*
* NOTES
* Transaction aborts can now occur two ways:
* access, and in fact could still cause an error...)
*/
- AtEOXact_portals();
+ AtEOXact_portals(true);
/* handle commit for large objects [ PA, 7/17/98 ] */
/* XXX probably this does not belong here */
* do abort processing
*/
DeferredTriggerAbortXact();
- AtEOXact_portals();
+ AtEOXact_portals(false);
lo_commit(false); /* 'false' means it's abort */
AtAbort_Notify();
AtEOXact_UpdatePasswordFile(false);
/*-------------------------------------------------------------------------
*
* copy.c
- * COPY command.
- *
+ * Implements the COPY utility command.
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.189 2003/02/03 21:15:43 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.190 2003/03/27 16:51:27 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#define ISOCTAL(c) (((c) >= '0') && ((c) <= '7'))
#define OCTVALUE(c) ((c) - '0')
+/*
+ * Represents the type of data returned by CopyReadAttribute()
+ */
typedef enum CopyReadResult
{
NORMAL_ATTR,
* *result is set to indicate what terminated the read:
* NORMAL_ATTR: column delimiter
* END_OF_LINE: newline
- * END_OF_FILE: EOF indication
+ * END_OF_FILE: EOF indicator
* In all cases, the string read up to the terminator is returned.
*
* Note: This function does not care about SQL NULL values -- it
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.10 2003/03/11 19:40:22 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.11 2003/03/27 16:51:27 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include
+#include "miscadmin.h"
#include "commands/portalcmds.h"
#include "executor/executor.h"
#include "optimizer/planner.h"
#include "rewrite/rewriteHandler.h"
-
+#include "utils/memutils.h"
static long DoRelativeFetch(Portal portal,
bool forward,
long count,
CommandDest dest);
+static long DoRelativeStoreFetch(Portal portal,
+ bool forward,
+ long count,
+ CommandDest dest);
static void DoPortalRewind(Portal portal);
-static Portal PreparePortal(char *portalName);
+static Portal PreparePortal(DeclareCursorStmt *stmt);
/*
char *cursorName;
QueryDesc *queryDesc;
- /* Check for invalid context (must be in transaction block) */
- RequireTransactionChain((void *) stmt, "DECLARE CURSOR");
+ /*
+ * If this is a non-holdable cursor, we ensure that this statement
+ * has been executed inside a transaction block (or else, it would
+ * have no user-visible effect).
+ *
+ * XXX: surely there is a better way to check this?
+ */
+ if (!(stmt->options & CURSOR_OPT_HOLD))
+ RequireTransactionChain((void *) stmt, "DECLARE CURSOR");
/*
* The query has been through parse analysis, but not rewriting or
/*
* Create a portal and copy the query and plan into its memory context.
*/
- portal = PreparePortal(stmt->portalname);
+ portal = PreparePortal(stmt);
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
query = copyObject(query);
portal = GetPortalByName(stmt->portalname);
if (!PortalIsValid(portal))
{
+ /* FIXME: shouldn't this be an ERROR? */
elog(WARNING, "PerformPortalFetch: portal \"%s\" not found",
stmt->portalname);
return;
ScanDirection direction;
QueryDesc temp_queryDesc;
+ if (portal->holdStore)
+ return DoRelativeStoreFetch(portal, forward, count, dest);
+
queryDesc = PortalGetQueryDesc(portal);
estate = queryDesc->estate;
}
else
{
- if (!portal->backwardOK)
+ if (portal->scrollType == DISABLE_SCROLL)
elog(ERROR, "Cursor can only scan forward"
"\n\tDeclare it with SCROLL option to enable backward scan");
return estate->es_processed;
}
+/*
+ * DoRelativeStoreFetch
+ * Do fetch for a simple N-rows-forward-or-backward case, getting
+ * the results from the portal's tuple store.
+ */
+static long
+DoRelativeStoreFetch(Portal portal,
+ bool forward,
+ long count,
+ CommandDest dest)
+{
+ DestReceiver *destfunc;
+ QueryDesc *queryDesc = portal->queryDesc;
+ long rows_fetched = 0;
+
+ if (!forward && portal->scrollType == DISABLE_SCROLL)
+ elog(ERROR, "Cursor can only scan forward"
+ "\n\tDeclare it with SCROLL option to enable backward scan");
+
+ destfunc = DestToFunction(dest);
+ (*destfunc->setup) (destfunc, queryDesc->operation,
+ portal->name, queryDesc->tupDesc);
+
+ for (;;)
+ {
+ HeapTuple tup;
+ bool should_free;
+
+ if (rows_fetched >= count)
+ break;
+ if (portal->atEnd && forward)
+ break;
+ if (portal->atStart && !forward)
+ break;
+
+ tup = tuplestore_getheaptuple(portal->holdStore, forward, &should_free);
+
+ if (tup == NULL)
+ {
+ if (forward)
+ portal->atEnd = true;
+ else
+ portal->atStart = true;
+
+ break;
+ }
+
+ (*destfunc->receiveTuple) (tup, queryDesc->tupDesc, destfunc);
+
+ rows_fetched++;
+ if (forward)
+ portal->portalPos++;
+ else
+ portal->portalPos--;
+
+ if (forward && portal->atStart)
+ portal->atStart = false;
+ if (!forward && portal->atEnd)
+ portal->atEnd = false;
+
+ if (should_free)
+ pfree(tup);
+ }
+
+ (*destfunc->cleanup) (destfunc);
+
+ return rows_fetched;
+}
+
/*
* DoPortalRewind - rewind a Portal to starting point
*/
static void
DoPortalRewind(Portal portal)
{
- QueryDesc *queryDesc;
-
- queryDesc = PortalGetQueryDesc(portal);
-
- ExecutorRewind(queryDesc);
+ if (portal->holdStore)
+ tuplestore_rescan(portal->holdStore);
+ else
+ ExecutorRewind(PortalGetQueryDesc(portal));
portal->atStart = true;
portal->atEnd = false;
/*
* Note: PortalCleanup is called as a side-effect
*/
- PortalDrop(portal);
+ PortalDrop(portal, false);
}
-
/*
* PreparePortal
+ * Given a DECLARE CURSOR statement, returns the Portal data
+ * structure based on that statement that is used to manage the
+ * Portal internally. If a portal with specified name already
+ * exists, it is replaced.
*/
static Portal
-PreparePortal(char *portalName)
+PreparePortal(DeclareCursorStmt *stmt)
{
Portal portal;
/*
* Check for already-in-use portal name.
*/
- portal = GetPortalByName(portalName);
+ portal = GetPortalByName(stmt->portalname);
if (PortalIsValid(portal))
{
/*
* portal?
*/
elog(WARNING, "Closing pre-existing portal \"%s\"",
- portalName);
- PortalDrop(portal);
+ stmt->portalname);
+ PortalDrop(portal, false);
}
/*
* Create the new portal.
*/
- portal = CreatePortal(portalName);
+ portal = CreatePortal(stmt->portalname);
+
+ /*
+ * Modify the newly created portal based on the options specified in
+ * the DECLARE CURSOR statement.
+ */
+ if (stmt->options & CURSOR_OPT_SCROLL)
+ portal->scrollType = ENABLE_SCROLL;
+ else if (stmt->options & CURSOR_OPT_NO_SCROLL)
+ portal->scrollType = DISABLE_SCROLL;
+
+ if (stmt->options & CURSOR_OPT_HOLD)
+ portal->holdOpen = true;
return portal;
}
-
/*
* PortalCleanup
*
AssertArg(PortalIsValid(portal));
AssertArg(portal->cleanup == PortalCleanup);
+ if (portal->holdStore)
+ tuplestore_end(portal->holdStore);
+ else
+ ExecutorEnd(PortalGetQueryDesc(portal));
+
+}
+
+/*
+ * PersistHoldablePortal
+ *
+ * Prepare the specified Portal for access outside of the current
+ * transaction. When this function returns, all future accesses to the
+ * portal must be done via the Tuplestore (not by invoking the
+ * executor).
+ */
+void
+PersistHoldablePortal(Portal portal)
+{
+ MemoryContext oldcxt;
+ QueryDesc *queryDesc = PortalGetQueryDesc(portal);
+
+ /*
+ * If we're preserving a holdable portal, we had better be
+ * inside the transaction that originally created it.
+ */
+ Assert(portal->createXact == GetCurrentTransactionId());
+ Assert(portal->holdStore == NULL);
+
+ /*
+ * This context is used to store portal data that needs to persist
+ * between transactions.
+ */
+ oldcxt = MemoryContextSwitchTo(portal->holdContext);
+
+ /* XXX: Should SortMem be used for this? */
+ portal->holdStore = tuplestore_begin_heap(true, true, SortMem);
+
+ /* Set the destination to output to the tuplestore */
+ queryDesc->dest = Tuplestore;
+
+ /*
+ * Rewind the executor: we need to store the entire result set in
+ * the tuplestore, so that subsequent backward FETCHs can be
+ * processed.
+ */
+ ExecutorRewind(queryDesc);
+
+ /* Fetch the result set into the tuplestore */
+ ExecutorRun(queryDesc, ForwardScanDirection, 0);
+
+ /*
+ * Reset the position in the result set: ideally, this could be
+ * implemented by just skipping straight to the tuple # that we need
+ * to be at, but the tuplestore API doesn't support that. So we
+ * start at the beginning of the tuplestore and iterate through it
+ * until we reach where we need to be.
+ */
+ if (!portal->atEnd)
+ {
+ int store_pos = 0;
+ bool should_free;
+
+ tuplestore_rescan(portal->holdStore);
+
+ while (store_pos < portal->portalPos)
+ {
+ HeapTuple tmp = tuplestore_gettuple(portal->holdStore,
+ true, &should_free);
+
+ if (tmp == NULL)
+ elog(ERROR,
+ "PersistHoldablePortal: unexpected end of tuple stream");
+
+ store_pos++;
+
+ /*
+ * This could probably be optimized by creating and then
+ * deleting a separate memory context for this series of
+ * operations.
+ */
+ if (should_free)
+ pfree(tmp);
+ }
+ }
+
/*
- * tell the executor to shutdown the query
+ * The current Portal structure contains some data that will be
+ * needed by the holdable cursor, but it has been allocated in a
+ * memory context that is not sufficiently long-lived: we need to
+ * copy it into the portal's long-term memory context.
*/
- ExecutorEnd(PortalGetQueryDesc(portal));
+ {
+ TupleDesc tupDescCopy;
+ QueryDesc *queryDescCopy;
+
+ /*
+ * We need to use this order as ExecutorEnd invalidates the
+ * queryDesc's tuple descriptor
+ */
+ tupDescCopy = CreateTupleDescCopy(queryDesc->tupDesc);
+
+ ExecutorEnd(queryDesc);
+
+ queryDescCopy = palloc(sizeof(*queryDescCopy));
+
+ /*
+ * This doesn't copy all the dependant data in the QueryDesc,
+ * but that's okay -- the only complex field we need to keep is
+ * the query's tupledesc, which we've copied ourselves.
+ */
+ memcpy(queryDescCopy, queryDesc, sizeof(*queryDesc));
+
+ FreeQueryDesc(queryDesc);
+
+ queryDescCopy->tupDesc = tupDescCopy;
+ portal->queryDesc = queryDescCopy;
+ }
/*
- * This should be unnecessary since the querydesc should be in the
- * portal's memory context, but do it anyway for symmetry.
+ * We no longer need the portal's short-term memory context.
*/
- FreeQueryDesc(PortalGetQueryDesc(portal));
+ MemoryContextDelete(PortalGetHeapMemory(portal));
+
+ PortalGetHeapMemory(portal) = NULL;
}
# Makefile for executor
#
# IDENTIFICATION
-# $Header: /cvsroot/pgsql/src/backend/executor/Makefile,v 1.20 2003/01/10 23:54:24 tgl Exp $
+# $Header: /cvsroot/pgsql/src/backend/executor/Makefile,v 1.21 2003/03/27 16:51:27 momjian Exp $
#
#-------------------------------------------------------------------------
nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \
nodeNestloop.o nodeFunctionscan.o nodeResult.o nodeSeqscan.o \
nodeSetOp.o nodeSort.o nodeUnique.o nodeLimit.o nodeGroup.o \
- nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o spi.o
+ nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o tstoreReceiver.o spi.o
all: SUBSYS.o
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.204 2003/03/27 14:33:11 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.205 2003/03/27 16:51:27 momjian Exp $
*
*-------------------------------------------------------------------------
*/
estate->es_lastoid = InvalidOid;
destfunc = DestToFunction(dest);
- (*destfunc->setup) (destfunc, (int) operation,
- queryDesc->portalName, queryDesc->tupDesc);
+ (*destfunc->setup) (destfunc, operation, queryDesc->portalName,
+ queryDesc->tupDesc);
/*
* run plan
}
}
-
-/* ===============================================================
- * ===============================================================
- static routines follow
- * ===============================================================
- * ===============================================================
- */
-
-
static void
ExecCheckXactReadOnly(Query *parsetree, CmdType operation)
{
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.126 2003/03/09 02:19:13 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.127 2003/03/27 16:51:27 momjian Exp $
*
*-------------------------------------------------------------------------
*/
0,
false);
}
- tupstore = tuplestore_begin_heap(true, /* randomAccess */
- SortMem);
+ tupstore = tuplestore_begin_heap(true, false, SortMem);
MemoryContextSwitchTo(oldcontext);
rsinfo.setResult = tupstore;
rsinfo.setDesc = tupdesc;
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/nodeHash.c,v 1.74 2003/01/10 23:54:24 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/nodeHash.c,v 1.75 2003/03/27 16:51:27 momjian Exp $
*
*-------------------------------------------------------------------------
*/
* buffers are palloc'd in regular executor context.
*/
for (i = 0; i < nbatch; i++)
- hashtable->innerBatchFile[i] = BufFileCreateTemp();
+ hashtable->innerBatchFile[i] = BufFileCreateTemp(false);
}
/*
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.48 2003/01/27 20:51:48 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.49 2003/03/27 16:51:27 momjian Exp $
*
*-------------------------------------------------------------------------
*/
* buffers are palloc'd in regular executor context.
*/
for (i = 0; i < hashtable->nbatch; i++)
- hashtable->outerBatchFile[i] = BufFileCreateTemp();
+ hashtable->outerBatchFile[i] = BufFileCreateTemp(false);
}
else if (hashtable == NULL)
return NULL;
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/nodeMaterial.c,v 1.41 2003/03/09 02:19:13 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/nodeMaterial.c,v 1.42 2003/03/27 16:51:27 momjian Exp $
*
*-------------------------------------------------------------------------
*/
*/
if (tuplestorestate == NULL)
{
- tuplestorestate = tuplestore_begin_heap(true, /* randomAccess */
- SortMem);
+ tuplestorestate = tuplestore_begin_heap(true, false, SortMem);
node->tuplestorestate = (void *) tuplestorestate;
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.88 2003/03/11 19:40:22 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.89 2003/03/27 16:51:28 momjian Exp $
*
*-------------------------------------------------------------------------
*/
if (!PortalIsValid(portal))
elog(ERROR, "invalid portal in SPI cursor operation");
- PortalDrop(portal);
+ PortalDrop(portal, false);
}
/* =================== private functions =================== */
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.265 2003/03/10 03:53:50 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.266 2003/03/27 16:51:28 momjian Exp $
*
*-------------------------------------------------------------------------
*/
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *) stmt;
+ /*
+ * Don't allow both SCROLL and NO SCROLL to be specified
+ */
+ if ((stmt->options & CURSOR_OPT_SCROLL) &&
+ (stmt->options & CURSOR_OPT_NO_SCROLL))
+ elog(ERROR, "Both SCROLL and NO SCROLL cannot be specified.");
+
stmt->query = (Node *) transformStmt(pstate, stmt->query,
&extras_before, &extras_after);
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.408 2003/03/20 18:52:47 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.409 2003/03/27 16:51:28 momjian Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
%type opt_freeze opt_default opt_recheck
%type opt_binary opt_oids copy_delimiter
-%type copy_from
+%type copy_from opt_hold
%type reindex_type drop_type fetch_count
opt_column event comment_type cursor_options
GLOBAL GRANT GROUP_P
- HANDLER HAVING HOUR_P
+ HANDLER HAVING HOLD HOUR_P
ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCREMENT
INDEX INHERITS INITIALLY INNER_P INOUT INPUT
* CURSOR STATEMENTS
*
*****************************************************************************/
-DeclareCursorStmt: DECLARE name cursor_options CURSOR FOR SelectStmt
+DeclareCursorStmt: DECLARE name cursor_options CURSOR opt_hold FOR SelectStmt
{
DeclareCursorStmt *n = makeNode(DeclareCursorStmt);
n->portalname = $2;
n->options = $3;
- n->query = $6;
+ n->query = $7;
+
+ if ($5)
+ n->options |= CURSOR_OPT_HOLD;
+
$$ = (Node *)n;
}
;
cursor_options: /*EMPTY*/ { $$ = 0; }
- | cursor_options BINARY { $$ = $1 | CURSOR_OPT_BINARY; }
+ | cursor_options NO SCROLL { $$ = $1 | CURSOR_OPT_NO_SCROLL; }
| cursor_options SCROLL { $$ = $1 | CURSOR_OPT_SCROLL; }
+ | cursor_options BINARY { $$ = $1 | CURSOR_OPT_BINARY; }
| cursor_options INSENSITIVE { $$ = $1 | CURSOR_OPT_INSENSITIVE; }
;
+opt_hold: /* EMPTY */ { $$ = FALSE; }
+ | WITH HOLD { $$ = TRUE; }
+ | WITHOUT HOLD { $$ = FALSE; }
+
/*****************************************************************************
*
* QUERY:
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.136 2003/03/20 07:02:10 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.137 2003/03/27 16:51:28 momjian Exp $
*
*-------------------------------------------------------------------------
*/
{"group", GROUP_P},
{"handler", HANDLER},
{"having", HAVING},
+ {"hold", HOLD},
{"hour", HOUR_P},
{"ilike", ILIKE},
{"immediate", IMMEDIATE},
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/storage/file/buffile.c,v 1.14 2002/09/05 00:43:07 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/storage/file/buffile.c,v 1.15 2003/03/27 16:51:29 momjian Exp $
*
* NOTES:
*
*/
bool isTemp; /* can only add files if this is TRUE */
+ bool isInterTxn; /* keep open over transactions? */
bool dirty; /* does buffer need to be written? */
/*
File pfile;
Assert(file->isTemp);
- pfile = OpenTemporaryFile();
+ pfile = OpenTemporaryFile(file->isInterTxn);
Assert(pfile >= 0);
file->files = (File *) repalloc(file->files,
* written to it).
*/
BufFile *
-BufFileCreateTemp(void)
+BufFileCreateTemp(bool interTxn)
{
BufFile *file;
File pfile;
- pfile = OpenTemporaryFile();
+ pfile = OpenTemporaryFile(interTxn);
Assert(pfile >= 0);
file = makeBufFile(pfile);
file->isTemp = true;
+ file->isInterTxn = interTxn;
return file;
}
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/storage/file/fd.c,v 1.95 2002/09/02 06:11:42 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/storage/file/fd.c,v 1.96 2003/03/27 16:51:29 momjian Exp $
*
* NOTES:
*
#define FileUnknownPos (-1L)
+/* these are the assigned bits in fdstate below: */
+#define FD_TEMPORARY (1 << 0)
+#define FD_TXN_TEMPORARY (1 << 1)
+
typedef struct vfd
{
signed short fd; /* current FD, or VFD_CLOSED if none */
unsigned short fdstate; /* bitflags for VFD's state */
-
-/* these are the assigned bits in fdstate: */
-#define FD_TEMPORARY (1 << 0) /* should be unlinked when closed */
-
File nextFree; /* link to next free VFD, if in freelist */
File lruMoreRecently; /* doubly linked recency-of-use list */
File lruLessRecently;
* This routine takes care of generating an appropriate tempfile name.
* There's no need to pass in fileFlags or fileMode either, since only
* one setting makes any sense for a temp file.
+ *
+ * keepOverTxn: if true, don't close the file at end-of-transaction. In
+ * most cases, you don't want temporary files to outlive the transaction
+ * that created them, so this should be false -- but if you need
+ * "somewhat" temporary storage, this might be useful. In either case,
+ * the file is removed when the File is explicitely closed.
*/
File
-OpenTemporaryFile(void)
+OpenTemporaryFile(bool keepOverTxn)
{
char tempfilepath[128];
File file;
elog(ERROR, "Failed to create temporary file %s", tempfilepath);
}
- /* Mark it for deletion at close or EOXact */
+ /* Mark it for deletion at close */
VfdCache[file].fdstate |= FD_TEMPORARY;
+ /* Mark it for deletion at EOXact */
+ if (!keepOverTxn)
+ VfdCache[file].fdstate |= FD_TXN_TEMPORARY;
+
return file;
}
for (i = 1; i < SizeVfdCache; i++)
{
if ((VfdCache[i].fdstate & FD_TEMPORARY) &&
+ (VfdCache[i].fdstate & FD_TXN_TEMPORARY) &&
VfdCache[i].fileName != NULL)
FileClose(i);
}
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.50 2003/01/21 22:06:12 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.51 2003/03/27 16:51:29 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/printtup.h"
+#include "executor/tstoreReceiver.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
static DestReceiver donothingDR = {
donothingReceive, donothingSetup, donothingCleanup
};
+
static DestReceiver debugtupDR = {
debugtup, debugSetup, donothingCleanup
};
+
static DestReceiver spi_printtupDR = {
spi_printtup, spi_dest_setup, donothingCleanup
};
case SPI:
return &spi_printtupDR;
+ case Tuplestore:
+ return tstoreReceiverCreateDR();
+
case None:
return &donothingDR;
}
case None:
case Debug:
+ case Tuplestore:
case SPI:
break;
}
break;
case Debug:
+ case Tuplestore:
case None:
default:
break;
break;
case Debug:
+ case Tuplestore:
case None:
default:
break;
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/mmgr/mcxt.c,v 1.38 2002/12/16 16:22:46 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/mmgr/mcxt.c,v 1.39 2003/03/27 16:51:29 momjian Exp $
*
*-------------------------------------------------------------------------
*/
MemoryContext CurrentMemoryContext = NULL;
/*
- * Standard top-level contexts
+ * Standard top-level contexts. For a description of the purpose of each
+ * of these contexts, refer to src/backend/utils/mmgr/README
*/
MemoryContext TopMemoryContext = NULL;
MemoryContext ErrorContext = NULL;
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.53 2003/03/11 19:40:23 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.54 2003/03/27 16:51:29 momjian Exp $
*
*-------------------------------------------------------------------------
*/
* "PortalData" structure, plans the query and then stores the query
* in the portal without executing it. Later, when the backend
* sees a
- * fetch 1 from FOO
- * the system looks up the portal named "FOO" in the portal table,
+ * fetch 1 from foo
+ * the system looks up the portal named "foo" in the portal table,
* gets the planned query and then calls the executor with a count
* of 1. The executor then runs the query and returns a single
* tuple. The problem is that we have to hold onto the state of the
#include "utils/memutils.h"
#include "utils/portal.h"
-
/*
* estimate of the maximum number of open portals a user would have,
* used in initially sizing the PortalHashTable in EnablePortalManager()
ctl.entrysize = sizeof(PortalHashEnt);
/*
- * use PORTALS_PER_USER, defined in utils/portal.h as a guess of how
- * many hash table entries to create, initially
+ * use PORTALS_PER_USER as a guess of how many hash table entries to
+ * create, initially
*/
PortalHashTable = hash_create("Portal hash", PORTALS_PER_USER,
&ctl, HASH_ELEM);
/*
* PortalSetQuery
- * Attaches a "query" to portal.
+ * Attaches a "query" to the specified portal. Note that in the
+ * case of DECLARE CURSOR, some Portal options have already been
+ * set based upon the parsetree of the original DECLARE statement.
*/
void
PortalSetQuery(Portal portal,
{
AssertArg(PortalIsValid(portal));
+ /*
+ * If the user didn't specify a SCROLL type, allow or disallow
+ * scrolling based on whether it would require any additional
+ * runtime overhead to do so.
+ */
+ if (portal->scrollType == DEFAULT_SCROLL)
+ {
+ bool backwardPlan;
+
+ backwardPlan = ExecSupportsBackwardScan(queryDesc->plantree);
+
+ if (backwardPlan)
+ portal->scrollType = ENABLE_SCROLL;
+ else
+ portal->scrollType = DISABLE_SCROLL;
+ }
+
portal->queryDesc = queryDesc;
portal->cleanup = cleanup;
- portal->backwardOK = ExecSupportsBackwardScan(queryDesc->plantree);
portal->atStart = true;
portal->atEnd = false; /* allow fetches */
portal->portalPos = 0;
* CreatePortal
* Returns a new portal given a name.
*
- * Exceptions:
- * BadState if called when disabled.
- * BadArg if portal name is invalid.
- * "WARNING" if portal name is in use (existing portal is returned!)
+ * An elog(WARNING) is emitted if portal name is in use (existing
+ * portal is returned!)
*/
Portal
CreatePortal(const char *name)
/* initialize portal query */
portal->queryDesc = NULL;
portal->cleanup = NULL;
- portal->backwardOK = false;
+ portal->scrollType = DEFAULT_SCROLL;
+ portal->holdOpen = false;
+ portal->holdStore = NULL;
+ portal->holdContext = NULL;
+ portal->createXact = GetCurrentTransactionId();
portal->atStart = true;
portal->atEnd = true; /* disallow fetches until query is set */
portal->portalPos = 0;
/*
* PortalDrop
- * Destroys portal.
+ * Destroy the portal.
*
- * Exceptions:
- * BadState if called when disabled.
- * BadArg if portal is invalid.
+ * keepHoldable: if true, holdable portals should not be removed by
+ * this function. More specifically, invoking this function with
+ * keepHoldable = true on a holdable portal prepares the portal for
+ * access outside of its creating transaction.
*/
void
-PortalDrop(Portal portal)
+PortalDrop(Portal portal, bool persistHoldable)
{
AssertArg(PortalIsValid(portal));
+ if (portal->holdOpen && persistHoldable)
+ {
+ /*
+ * We're "dropping" a holdable portal, but what we really need
+ * to do is prepare the portal for access outside of its
+ * creating transaction.
+ */
+
+ /*
+ * Create the memory context that is used for storage of
+ * long-term (cross transaction) data needed by the holdable
+ * portal.
+ */
+ portal->holdContext =
+ AllocSetContextCreate(PortalMemory,
+ "PortalHeapMemory",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ /*
+ * Note that PersistHoldablePortal() releases any resources used
+ * by the portal that are local to the creating txn.
+ */
+ PersistHoldablePortal(portal);
+
+ return;
+ }
+
/* remove portal from hash table */
PortalHashTableDelete(portal);
if (PointerIsValid(portal->cleanup))
(*portal->cleanup) (portal);
- /* release subsidiary storage */
- MemoryContextDelete(PortalGetHeapMemory(portal));
+ /*
+ * delete short-term memory context; in the case of a holdable
+ * portal, this has already been done
+ */
+ if (PortalGetHeapMemory(portal))
+ MemoryContextDelete(PortalGetHeapMemory(portal));
+
+ /*
+ * delete long-term memory context; in the case of a non-holdable
+ * portal, this context has never been created, so we don't need to
+ * do anything
+ */
+ if (portal->holdContext)
+ MemoryContextDelete(portal->holdContext);
/* release name and portal data (both are in PortalMemory) */
pfree(portal->name);
}
/*
- * Destroy all portals created in the current transaction (ie, all of them).
+ * Cleanup the portals created in the current transaction. If the
+ * transaction was aborted, all the portals created in this transaction
+ * should be removed. If the transaction was successfully committed, any
+ * holdable cursors created in this transaction need to be kept
+ * open. Only cursors created in the current transaction should be
+ * removed in this fashion.
*
* XXX This assumes that portals can be deleted in a random order, ie,
* no portal has a reference to any other (at least not one that will be
* references...
*/
void
-AtEOXact_portals(void)
+AtEOXact_portals(bool isCommit)
{
HASH_SEQ_STATUS status;
PortalHashEnt *hentry;
+ TransactionId xact = GetCurrentTransactionId();
hash_seq_init(&status, PortalHashTable);
while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
- PortalDrop(hentry->portal);
+ {
+ if (hentry->portal->createXact == xact)
+ PortalDrop(hentry->portal, isCommit);
+ }
}
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/sort/logtape.c,v 1.8 2002/06/20 20:29:40 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/sort/logtape.c,v 1.9 2003/03/27 16:51:29 momjian Exp $
*
*-------------------------------------------------------------------------
*/
Assert(ntapes > 0);
lts = (LogicalTapeSet *) palloc(sizeof(LogicalTapeSet) +
(ntapes - 1) *sizeof(LogicalTape *));
- lts->pfile = BufFileCreateTemp();
+ lts->pfile = BufFileCreateTemp(false);
lts->nFileBlocks = 0L;
lts->freeBlocksLen = 32; /* reasonable initial guess */
lts->freeBlocks = (long *) palloc(lts->freeBlocksLen * sizeof(long));
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/sort/tuplestore.c,v 1.11 2003/03/09 02:19:13 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/sort/tuplestore.c,v 1.12 2003/03/27 16:51:29 momjian Exp $
*
*-------------------------------------------------------------------------
*/
{
TupStoreStatus status; /* enumerated value as shown above */
bool randomAccess; /* did caller request random access? */
+ bool interTxn; /* keep open through transactions? */
long availMem; /* remaining memory available, in bytes */
BufFile *myfile; /* underlying file, or NULL if none */
static Tuplestorestate *tuplestore_begin_common(bool randomAccess,
- int maxKBytes);
+ bool interTxn,
+ int maxKBytes);
static void dumptuples(Tuplestorestate *state);
static unsigned int getlen(Tuplestorestate *state, bool eofOK);
static void *copytup_heap(Tuplestorestate *state, void *tup);
*/
static Tuplestorestate *
-tuplestore_begin_common(bool randomAccess, int maxKBytes)
+tuplestore_begin_common(bool randomAccess, bool interTxn, int maxKBytes)
{
Tuplestorestate *state;
state->status = TSS_INMEM;
state->randomAccess = randomAccess;
+ state->interTxn = interTxn;
state->availMem = maxKBytes * 1024L;
state->myfile = NULL;
return state;
}
+/*
+ * tuplestore_begin_heap
+ *
+ * Create a new tuplestore; other types of tuple stores (other than
+ * "heap" tuple stores, for heap tuples) are possible, but not presently
+ * implemented.
+ *
+ * randomAccess: if true, both forward and backward accesses to the
+ * tuple store are allowed.
+ *
+ * interTxn: if true, the files used by on-disk storage persist beyond
+ * the end of the current transaction.
+ *
+ * maxKBytes: how much data to store in memory (any data beyond this
+ * amount is paged to disk).
+ */
Tuplestorestate *
-tuplestore_begin_heap(bool randomAccess, int maxKBytes)
+tuplestore_begin_heap(bool randomAccess, bool interTxn, int maxKBytes)
{
- Tuplestorestate *state = tuplestore_begin_common(randomAccess, maxKBytes);
+ Tuplestorestate *state = tuplestore_begin_common(randomAccess,
+ interTxn, maxKBytes);
state->copytup = copytup_heap;
state->writetup = writetup_heap;
/*
* Nope; time to switch to tape-based operation.
*/
- state->myfile = BufFileCreateTemp();
+ state->myfile = BufFileCreateTemp(state->interTxn);
state->status = TSS_WRITEFILE;
dumptuples(state);
break;
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: parsenodes.h,v 1.235 2003/03/20 18:52:48 momjian Exp $
+ * $Id: parsenodes.h,v 1.236 2003/03/27 16:51:29 momjian Exp $
*
*-------------------------------------------------------------------------
*/
*/
#define CURSOR_OPT_BINARY 0x0001
#define CURSOR_OPT_SCROLL 0x0002
-#define CURSOR_OPT_INSENSITIVE 0x0004
+#define CURSOR_OPT_NO_SCROLL 0x0004
+#define CURSOR_OPT_INSENSITIVE 0x0008
+#define CURSOR_OPT_HOLD 0x0010
typedef struct DeclareCursorStmt
{
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: buffile.h,v 1.12 2002/06/20 20:29:52 momjian Exp $
+ * $Id: buffile.h,v 1.13 2003/03/27 16:51:29 momjian Exp $
*
*-------------------------------------------------------------------------
*/
* prototypes for functions in buffile.c
*/
-extern BufFile *BufFileCreateTemp(void);
+extern BufFile *BufFileCreateTemp(bool interTxn);
extern void BufFileClose(BufFile *file);
extern size_t BufFileRead(BufFile *file, void *ptr, size_t size);
extern size_t BufFileWrite(BufFile *file, void *ptr, size_t size);
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: fd.h,v 1.36 2002/08/06 02:36:35 tgl Exp $
+ * $Id: fd.h,v 1.37 2003/03/27 16:51:29 momjian Exp $
*
*-------------------------------------------------------------------------
*/
/* Operations on virtual Files --- equivalent to Unix kernel file ops */
extern File FileNameOpenFile(FileName fileName, int fileFlags, int fileMode);
extern File PathNameOpenFile(FileName fileName, int fileFlags, int fileMode);
-extern File OpenTemporaryFile(void);
+extern File OpenTemporaryFile(bool keepOverTxn);
extern void FileClose(File file);
extern void FileUnlink(File file);
extern int FileRead(File file, char *buffer, int amount);
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: dest.h,v 1.32 2002/09/04 20:31:45 momjian Exp $
+ * $Id: dest.h,v 1.33 2003/03/27 16:51:29 momjian Exp $
*
*-------------------------------------------------------------------------
*/
Remote, /* results sent to frontend process */
RemoteInternal, /* results sent to frontend process in
* internal (binary) form */
- SPI /* results sent to SPI manager */
+ SPI, /* results sent to SPI manager */
+ Tuplestore /* results sent to Tuplestore */
} CommandDest;
/* ----------------
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: portal.h,v 1.39 2003/03/11 19:40:24 tgl Exp $
+ * $Id: portal.h,v 1.40 2003/03/27 16:51:29 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include "executor/execdesc.h"
#include "nodes/memnodes.h"
+#include "utils/tuplestore.h"
+/*
+ * We support three kinds of scroll behavior:
+ *
+ * (1) Neither NO SCROLL nor SCROLL was specified: to remain backward
+ * compatible, we allow backward fetches here, unless it would
+ * impose additional runtime overhead to do so.
+ *
+ * (2) NO SCROLL was specified: don't allow any backward fetches.
+ *
+ * (3) SCROLL was specified: allow all kinds of backward fetches, even
+ * if we need to take a slight performance hit to do so.
+ *
+ * Case #1 is converted to #2 or #3 by looking at the query itself and
+ * determining if scrollability can be supported without additional
+ * overhead.
+ */
+typedef enum
+{
+ DEFAULT_SCROLL,
+ DISABLE_SCROLL,
+ ENABLE_SCROLL
+} ScrollType;
typedef struct PortalData *Portal;
typedef struct PortalData
{
char *name; /* Portal's name */
- MemoryContext heap; /* subsidiary memory */
+ MemoryContext heap; /* memory for storing short-term data */
QueryDesc *queryDesc; /* Info about query associated with portal */
void (*cleanup) (Portal); /* Cleanup routine (optional) */
- bool backwardOK; /* is fetch backwards allowed? */
+ ScrollType scrollType; /* Allow backward fetches? */
+ bool holdOpen; /* hold open after txn ends? */
+ TransactionId createXact; /* the xid of the creating txn */
+ Tuplestorestate *holdStore; /* store for holdable cursors */
+ MemoryContext holdContext; /* memory for long-term data */
+
/*
* atStart, atEnd and portalPos indicate the current cursor position.
* portalPos is zero before the first row, N after fetching N'th row of
extern void EnablePortalManager(void);
-extern void AtEOXact_portals(void);
+extern void AtEOXact_portals(bool isCommit);
extern Portal CreatePortal(const char *name);
-extern void PortalDrop(Portal portal);
+extern void PortalDrop(Portal portal, bool persistHoldable);
extern Portal GetPortalByName(const char *name);
extern void PortalSetQuery(Portal portal, QueryDesc *queryDesc,
void (*cleanup) (Portal portal));
+extern void PersistHoldablePortal(Portal portal);
#endif /* PORTAL_H */
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: tuplestore.h,v 1.9 2003/03/09 03:34:10 tgl Exp $
+ * $Id: tuplestore.h,v 1.10 2003/03/27 16:51:29 momjian Exp $
*
*-------------------------------------------------------------------------
*/
*/
extern Tuplestorestate *tuplestore_begin_heap(bool randomAccess,
- int maxKBytes);
+ bool interTxn,
+ int maxKBytes);
extern void tuplestore_puttuple(Tuplestorestate *state, void *tuple);
* procedural language
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.83 2003/03/25 03:16:40 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.84 2003/03/27 16:51:29 momjian Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
- estate->tuple_store = tuplestore_begin_heap(true, SortMem);
+ estate->tuple_store = tuplestore_begin_heap(true, false, SortMem);
MemoryContextSwitchTo(oldcxt);
estate->rettupdesc = rsi->expectedDesc;
--
--- PORTALS
+-- Cursor regression tests
--
BEGIN;
-DECLARE foo1 CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo2 CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo3 CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo4 CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo5 CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo6 CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo7 CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo8 CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo9 CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo10 CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo11 CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo12 CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo13 CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo14 CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo15 CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo16 CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo17 CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo18 CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo19 CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo20 CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo21 CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo22 CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo23 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo1 SCROLL CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo2 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo3 SCROLL CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo4 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo5 SCROLL CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo6 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo7 SCROLL CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo8 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo9 SCROLL CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo10 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo11 SCROLL CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo12 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo13 SCROLL CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo14 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo15 SCROLL CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo16 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo17 SCROLL CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo18 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo19 SCROLL CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo20 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo21 SCROLL CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo22 SCROLL CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo23 SCROLL CURSOR FOR SELECT * FROM tenk1;
FETCH 1 in foo1;
unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
CLOSE foo10;
CLOSE foo11;
CLOSE foo12;
-end;
+-- is there a reason why we don't close the rest of the open cursors?
+END;
+--
+-- NO SCROLL disallows backward fetching
+--
+BEGIN;
+DECLARE foo24 NO SCROLL CURSOR FOR SELECT * FROM tenk1;
+FETCH 1 FROM foo24;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx
+(1 row)
+
+FETCH BACKWARD 1 FROM foo24; -- should fail
+ERROR: Cursor can only scan forward
+ Declare it with SCROLL option to enable backward scan
+END;
+--
+-- Cursors outside transaction blocks
+--
+BEGIN;
+DECLARE foo25 SCROLL CURSOR WITH HOLD FOR SELECT * FROM tenk2;
+FETCH FROM foo25;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx
+(1 row)
+
+FETCH FROM foo25;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx
+(1 row)
+
+COMMIT;
+FETCH FROM foo25;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx
+(1 row)
+
+FETCH BACKWARD FROM foo25;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx
+(1 row)
+
+FETCH ABSOLUTE -1 FROM foo25;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 2968 | 9999 | 0 | 0 | 8 | 8 | 68 | 968 | 968 | 2968 | 2968 | 136 | 137 | EKAAAA | PUOAAA | VVVVxx
+(1 row)
+
+CLOSE foo25;
+--
+-- ROLLBACK should close holdable cursors
+--
+BEGIN;
+DECLARE foo26 CURSOR WITH HOLD FOR SELECT * FROM tenk1;
+ROLLBACK;
+-- should fail
+FETCH FROM foo26;
+WARNING: PerformPortalFetch: portal "foo26" not found
--
--- PORTALS
+-- Cursor regression tests
--
BEGIN;
-DECLARE foo1 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo1 SCROLL CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo2 CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo2 SCROLL CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo3 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo3 SCROLL CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo4 CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo4 SCROLL CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo5 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo5 SCROLL CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo6 CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo6 SCROLL CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo7 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo7 SCROLL CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo8 CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo8 SCROLL CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo9 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo9 SCROLL CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo10 CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo10 SCROLL CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo11 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo11 SCROLL CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo12 CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo12 SCROLL CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo13 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo13 SCROLL CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo14 CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo14 SCROLL CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo15 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo15 SCROLL CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo16 CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo16 SCROLL CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo17 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo17 SCROLL CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo18 CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo18 SCROLL CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo19 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo19 SCROLL CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo20 CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo20 SCROLL CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo21 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo21 SCROLL CURSOR FOR SELECT * FROM tenk1;
-DECLARE foo22 CURSOR FOR SELECT * FROM tenk2;
+DECLARE foo22 SCROLL CURSOR FOR SELECT * FROM tenk2;
-DECLARE foo23 CURSOR FOR SELECT * FROM tenk1;
+DECLARE foo23 SCROLL CURSOR FOR SELECT * FROM tenk1;
FETCH 1 in foo1;
CLOSE foo12;
-end;
+-- is there a reason why we don't close the rest of the open cursors?
+END;
+
+--
+-- NO SCROLL disallows backward fetching
+--
+
+BEGIN;
+
+DECLARE foo24 NO SCROLL CURSOR FOR SELECT * FROM tenk1;
+
+FETCH 1 FROM foo24;
+
+FETCH BACKWARD 1 FROM foo24; -- should fail
+
+END;
+
+--
+-- Cursors outside transaction blocks
+--
+
+BEGIN;
+
+DECLARE foo25 SCROLL CURSOR WITH HOLD FOR SELECT * FROM tenk2;
+
+FETCH FROM foo25;
+
+FETCH FROM foo25;
+
+COMMIT;
+
+FETCH FROM foo25;
+
+FETCH BACKWARD FROM foo25;
+
+FETCH ABSOLUTE -1 FROM foo25;
+
+CLOSE foo25;
+
+--
+-- ROLLBACK should close holdable cursors
+--
+
+BEGIN;
+
+DECLARE foo26 CURSOR WITH HOLD FOR SELECT * FROM tenk1;
+
+ROLLBACK;
+
+-- should fail
+FETCH FROM foo26;
\ No newline at end of file