diff --git a/doc/src/sgml/ref/fetch.sgml b/doc/src/sgml/ref/fetch.sgml
index 0452cf0144f..8f3244eb39f 100644
--- a/doc/src/sgml/ref/fetch.sgml
+++ b/doc/src/sgml/ref/fetch.sgml
@@ -1,5 +1,5 @@
@@ -18,17 +18,32 @@ PostgreSQL documentation
- 1999-07-20
+ 2003-03-11
-FETCH [ direction ] [ count ] { IN | FROM } cursor
-FETCH [ FORWARD | BACKWARD | RELATIVE ] [ # | ALL | NEXT | PRIOR ]
- { IN | FROM } cursor
+FETCH [ direction { FROM | IN } ] cursor
+
+where direction can be empty or one of:
+
+ NEXT
+ PRIOR
+ FIRST
+ LAST
+ ABSOLUTE count
+ RELATIVE count
+ count
+ ALL
+ FORWARD
+ FORWARD count
+ FORWARD ALL
+ BACKWARD
+ BACKWARD count
+ BACKWARD ALL
- 1998-09-01
+ 2003-03-11
Inputs
@@ -41,35 +56,151 @@ FETCH [ FORWARD | BACKWARD | RELATIVE ] [ #
direction
- defines the fetch direction. It can be one of
- the following:
+ defines the fetch direction and number of rows to fetch.
+ It can be one of the following:
- FORWARD
+ NEXT
- fetch next row(s). This is the default
+ fetch next row. This is the default
if direction is omitted.
+
+
+ PRIOR
+
+
+ fetch prior row.
+
+
+
+
+
+ FIRST
+
+
+ fetch first row of query (same as ABSOLUTE 1).
+
+
+
+
+
+ LAST
+
+
+ fetch last row of query (same as ABSOLUTE -1).
+
+
+
+
+
+ ABSOLUTE count
+
+
+ fetch the count'th
+ row of query, or the
+ abs(count)'th row
+ from the end if
+ count < 0.
+ Position before first row or after last row
+ if count is out of
+ range; in particular, ABSOLUTE 0 positions before first row.
+
+
+
+
+
+ RELATIVE count
+
+
+ fetch the count'th
+ succeeding row, or the
+ abs(count)'th prior
+ row if count < 0.
+ RELATIVE 0 re-fetches current row, if any.
+
+
+
+
+
+ count
+
+
+ fetch the next count
+ rows (same as FORWARD count).
+
+
+
+
+
+ ALL
+
+
+ fetch all remaining rows (same as FORWARD ALL).
+
+
+
+
+
+ FORWARD
+
+
+ fetch next row (same as NEXT).
+
+
+
+
+
+ FORWARD count
+
+
+ fetch next count
+ rows. FORWARD 0 re-fetches current row.
+
+
+
+
+
+ FORWARD ALL
+
+
+ fetch all remaining rows.
+
+
+
+
BACKWARD
- fetch previous row(s).
+ fetch prior row (same as PRIOR).
+
- RELATIVE
+ BACKWARD count
- Same as FORWARD; provided for SQL92 compatibility.
+ fetch prior count
+ rows (scanning backwards). BACKWARD 0 re-fetches current row.
+
+
+ BACKWARD ALL
+
+
+ fetch all prior rows (scanning backwards).
+
+
+
+
@@ -80,53 +211,11 @@ FETCH [ FORWARD | BACKWARD | RELATIVE ] [ #
count
- determines how many rows to fetch. It can be one of the following:
-
-
-
- #
-
-
- A signed integer constant that specifies how many rows to fetch.
- Note that a negative integer is equivalent to changing the sense of
- FORWARD and BACKWARD. Zero re-fetches the current row, if any.
-
-
-
-
-
-
- ALL
-
-
-
- Retrieve all remaining rows.
-
-
-
-
-
-
- NEXT
-
-
-
- Equivalent to specifying a count of 1.
-
-
-
-
-
-
- PRIOR
-
-
-
- Equivalent to specifying a count of -1.
-
-
-
-
+ is a possibly-signed integer constant, determining the location
+ or number of rows to fetch. For FORWARD and BACKWARD cases,
+ specifying a negative count
+ is equivalent to changing the sense of FORWARD and BACKWARD.
@@ -145,7 +234,7 @@ FETCH [ FORWARD | BACKWARD | RELATIVE ] [ #
- 1998-04-15
+ 2003-03-11
Outputs
@@ -162,25 +251,11 @@ WARNING: PerformPortalFetch: portal "cursor
- If cursor
- is not previously declared.
- The cursor must be declared within a transaction block.
+ If cursor is not known.
+ The cursor must have been declared within the current transaction block.
-
-
-
-WARNING: FETCH/ABSOLUTE not supported, using RELATIVE
-
-
-
- PostgreSQL does not support absolute
- positioning of cursors.
-
-
-
-
@@ -188,75 +263,79 @@ WARNING: FETCH/ABSOLUTE not supported, using RELATIVE
- 1998-04-15
+ 2003-03-11
Description
- FETCH allows a user to retrieve rows using a cursor.
- The number of rows retrieved is specified by
- #.
- If the number of rows remaining in the cursor is less
- than #,
- then only those available are fetched.
- Substituting the keyword ALL in place of a number will
- cause all remaining rows in the cursor to be retrieved.
- Rows may be fetched in both FORWARD and BACKWARD
- directions. The default direction is FORWARD.
+ FETCH retrieves rows using a cursor.
- The cursor position can be before the first row of the query result, or on
- any particular row of the result, or after the last row of the result.
- When created, a cursor is positioned before the first row. After fetching
- some rows, the cursor is positioned on the last row retrieved. A new
- FETCH always steps one row in the specified direction
- (if possible) before beginning to return rows. If the
- FETCH requests more rows than available, the cursor is
- left positioned after the last row of the query result (or before the first
- row, in the case of a backward fetch). This will always be the case after
- FETCH ALL>.
+ A cursor has an associated position> that is used by
+ FETCH>. The cursor position can be before the first row of the
+ query result, or on any particular row of the result, or after the last row
+ of the result. When created, a cursor is positioned before the first row.
+ After fetching some rows, the cursor is positioned on the row most recently
+ retrieved. If FETCH> runs off the end of the available rows
+ then the cursor is left positioned after the last row, or before the first
+ row if fetching backward. FETCH ALL> or FETCH BACKWARD
+ ALL> will always leave the cursor positioned after the last row or before
+ the first row.
+
+
+
+ The SQL-compatible forms (NEXT, PRIOR, FIRST, LAST, ABSOLUTE, RELATIVE)
+ fetch a single row after moving the cursor appropriately. If there is
+ no such row, an empty result is returned, and the cursor is left positioned
+ before the first row or after the last row as appropriate.
+
+
+
+ The forms using FORWARD and BACKWARD are not in the SQL standard, but
+ are PostgreSQL extensions. These forms
+ retrieve the indicated number of rows moving in the forward or backward
+ direction, leaving the cursor positioned on the last-returned row
+ (or after/before all rows, if the count exceeds the number of rows
+ available).
- A zero row count requests fetching the current row without moving the
+ RELATIVE 0, FORWARD 0, and BACKWARD 0 all request
+ fetching the current row without moving the
cursor --- that is, re-fetching the most recently fetched row.
This will succeed unless the cursor is positioned before the
first row or after the last row; in which case, no row is returned.
-
-
- Negative numbers are allowed to be specified for the
- row count. A negative number is equivalent to reversing
- the sense of the FORWARD and BACKWARD keywords. For example,
- FORWARD -1 is the same as BACKWARD 1.
-
-
-
- 1998-04-15
+ 2003-03-11
Notes
- A cursor to be used in backwards fetching should be declared with the
- SCROLL option. In simple cases, PostgreSQL
- will allow backwards fetch from cursors not declared with SCROLL, but
- this behavior is best not relied on.
+ The cursor should be declared with the SCROLL option if one intends to
+ use any variants of FETCH> other than FETCH NEXT>
+ or FETCH FORWARD> with a positive count. For simple queries
+ PostgreSQL will allow backwards fetch from
+ cursors not declared with SCROLL, but this behavior is best not relied on.
- The FORWARD, BACKWARD, and ALL keywords are
- PostgreSQL extensions.
- See below for details on compatibility issues.
+ ABSOLUTE fetches are not any faster than navigating to the desired row
+ with a relative move: the underlying implementation must traverse all
+ the intermediate rows anyway. Negative absolute fetches are even worse:
+ the query must be read to the end to find the last row, and then
+ traversed backward from there. However, rewinding to the start of the
+ query (as with FETCH ABSOLUTE 0) is fast.
@@ -316,7 +395,7 @@ FETCH FORWARD 5 IN liahona;
-- Fetch previous row:
-FETCH BACKWARD 1 IN liahona;
+FETCH PRIOR FROM liahona;
code | title | did | date_prod | kind | len
@@ -339,52 +418,39 @@ COMMIT WORK;
- 1998-09-01
+ 2003-03-11
SQL92
-
-
- The non-embedded use of cursors is a PostgreSQL
- extension. The syntax and usage of cursors is being compared
- against the embedded form of cursors defined in SQL92.
-
-
-
-
-
- SQL92 allows absolute positioning of the cursor for
- FETCH, and allows placing the results into explicit variables:
+ SQL92 defines FETCH for use in embedded contexts only.
+ Therefore, it describes placing the results into explicit variables using
+ an INTO> clause, for example:
-FETCH ABSOLUTE #
+FETCH ABSOLUTE n
FROM cursor
INTO :variable [, ...]
-
-
- ABSOLUTE
-
-
- The cursor should be positioned to the specified absolute
- row number. All row numbers in PostgreSQL
- are relative numbers so this capability is not supported.
-
-
-
-
- :variable
-
-
- Target host variable(s).
-
-
-
-
+ PostgreSQL's use of non-embedded cursors
+ is non-standard, and so is its practice of returning the result data
+ as if it were a SELECT result. Other than this point, FETCH is fully
+ upward-compatible with SQL92.
+
+
+
+ The FETCH forms involving FORWARD and BACKWARD (including the forms
+ FETCH count and FETCH ALL,
+ in which FORWARD is implicit) are PostgreSQL
+ extensions.
+
+
+
+ SQL92 allows only FROM> preceding the
+ cursor name; the option to use IN> is an extension.
diff --git a/doc/src/sgml/ref/move.sgml b/doc/src/sgml/ref/move.sgml
index 928faabc818..f01ee9d8a58 100644
--- a/doc/src/sgml/ref/move.sgml
+++ b/doc/src/sgml/ref/move.sgml
@@ -1,5 +1,5 @@
@@ -21,7 +21,7 @@ PostgreSQL documentation
1999-07-20
-MOVE [ direction ] [ count ] { IN | FROM } cursor
+MOVE [ direction { FROM | IN } ] cursor
@@ -33,9 +33,7 @@ MOVE [ direction ] [
- MOVE allows the user to move the cursor position a
- specified number of rows, or to the beginning or end of the cursor.
- MOVE ALL moves to the end of the cursor.
+ MOVE repositions a cursor without retrieving any data.
MOVE works exactly like the FETCH
command, except it only repositions the cursor and does not return rows.
@@ -54,8 +52,9 @@ MOVE [ direction ] [
- MOVE is a PostgreSQL
- language extension.
+ The count returned in MOVE's status string is the
+ count of the number of rows that would have been returned by the
+ equivalent FETCH command.
@@ -119,9 +118,6 @@ COMMIT WORK;
There is no SQL92 MOVE statement.
- Instead, SQL92 allows
- one to FETCH rows from an absolute cursor position,
- implicitly moving the cursor to the correct position.
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 0621cdd5903..1ba72437ad7 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.9 2003/03/10 03:53:49 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.10 2003/03/11 19:40:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -23,6 +23,11 @@
#include "rewrite/rewriteHandler.h"
+static long DoRelativeFetch(Portal portal,
+ bool forward,
+ long count,
+ CommandDest dest);
+static void DoPortalRewind(Portal portal);
static Portal PreparePortal(char *portalName);
@@ -102,9 +107,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest)
* PerformPortalFetch
* Execute SQL FETCH or MOVE command.
*
- * name: name of portal
- * forward: forward or backward fetch?
- * count: # of tuples to fetch (INT_MAX means "all"; 0 means "refetch")
+ * stmt: parsetree node for command
* dest: where to send results
* completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
* in which to store a command completion status string.
@@ -112,9 +115,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest)
* completionTag may be NULL if caller doesn't want a status string.
*/
void
-PerformPortalFetch(char *name,
- bool forward,
- long count,
+PerformPortalFetch(FetchStmt *stmt,
CommandDest dest,
char *completionTag)
{
@@ -123,48 +124,150 @@ PerformPortalFetch(char *name,
/* initialize completion status in case of early exit */
if (completionTag)
- strcpy(completionTag, (dest == None) ? "MOVE 0" : "FETCH 0");
-
- /* sanity checks */
- if (name == NULL)
- {
- elog(WARNING, "PerformPortalFetch: missing portal name");
- return;
- }
+ strcpy(completionTag, stmt->ismove ? "MOVE 0" : "FETCH 0");
/* get the portal from the portal name */
- portal = GetPortalByName(name);
+ portal = GetPortalByName(stmt->portalname);
if (!PortalIsValid(portal))
{
elog(WARNING, "PerformPortalFetch: portal \"%s\" not found",
- name);
+ stmt->portalname);
return;
}
/* Do it */
- nprocessed = DoPortalFetch(portal, forward, count, dest);
+ nprocessed = DoPortalFetch(portal,
+ stmt->direction,
+ stmt->howMany,
+ stmt->ismove ? None : dest);
/* Return command status if wanted */
if (completionTag)
snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "%s %ld",
- (dest == None) ? "MOVE" : "FETCH",
+ stmt->ismove ? "MOVE" : "FETCH",
nprocessed);
}
/*
* DoPortalFetch
- * Guts of PerformPortalFetch --- shared with SPI cursor operations
+ * Guts of PerformPortalFetch --- shared with SPI cursor operations.
+ * Caller must already have validated the Portal.
*
- * Returns number of rows processed.
+ * Returns number of rows processed (suitable for use in result tag)
*/
long
-DoPortalFetch(Portal portal, bool forward, long count, CommandDest dest)
+DoPortalFetch(Portal portal,
+ FetchDirection fdirection,
+ long count,
+ CommandDest dest)
{
- QueryDesc *queryDesc;
- EState *estate;
- MemoryContext oldcontext;
- ScanDirection direction;
- bool temp_desc = false;
+ bool forward;
+
+ switch (fdirection)
+ {
+ case FETCH_FORWARD:
+ if (count < 0)
+ {
+ fdirection = FETCH_BACKWARD;
+ count = -count;
+ }
+ /* fall out of switch to share code with FETCH_BACKWARD */
+ break;
+ case FETCH_BACKWARD:
+ if (count < 0)
+ {
+ fdirection = FETCH_FORWARD;
+ count = -count;
+ }
+ /* fall out of switch to share code with FETCH_FORWARD */
+ break;
+ case FETCH_ABSOLUTE:
+ if (count > 0)
+ {
+ /*
+ * Definition: Rewind to start, advance count-1 rows, return
+ * next row (if any). In practice, if the goal is less than
+ * halfway back to the start, it's better to scan from where
+ * we are. In any case, we arrange to fetch the target row
+ * going forwards.
+ */
+ if (portal->posOverflow || portal->portalPos == LONG_MAX ||
+ count-1 <= portal->portalPos / 2)
+ {
+ DoPortalRewind(portal);
+ if (count > 1)
+ DoRelativeFetch(portal, true, count-1, None);
+ }
+ else
+ {
+ long pos = portal->portalPos;
+
+ if (portal->atEnd)
+ pos++; /* need one extra fetch if off end */
+ if (count <= pos)
+ DoRelativeFetch(portal, false, pos-count+1, None);
+ else if (count > pos+1)
+ DoRelativeFetch(portal, true, count-pos-1, None);
+ }
+ return DoRelativeFetch(portal, true, 1L, dest);
+ }
+ else if (count < 0)
+ {
+ /*
+ * Definition: Advance to end, back up abs(count)-1 rows,
+ * return prior row (if any). We could optimize this if we
+ * knew in advance where the end was, but typically we won't.
+ * (Is it worth considering case where count > half of size
+ * of query? We could rewind once we know the size ...)
+ */
+ DoRelativeFetch(portal, true, FETCH_ALL, None);
+ if (count < -1)
+ DoRelativeFetch(portal, false, -count-1, None);
+ return DoRelativeFetch(portal, false, 1L, dest);
+ }
+ else /* count == 0 */
+ {
+ /* Rewind to start, return zero rows */
+ DoPortalRewind(portal);
+ return DoRelativeFetch(portal, true, 0L, dest);
+ }
+ break;
+ case FETCH_RELATIVE:
+ if (count > 0)
+ {
+ /*
+ * Definition: advance count-1 rows, return next row (if any).
+ */
+ if (count > 1)
+ DoRelativeFetch(portal, true, count-1, None);
+ return DoRelativeFetch(portal, true, 1L, dest);
+ }
+ else if (count < 0)
+ {
+ /*
+ * Definition: back up abs(count)-1 rows, return prior row
+ * (if any).
+ */
+ if (count < -1)
+ DoRelativeFetch(portal, false, -count-1, None);
+ return DoRelativeFetch(portal, false, 1L, dest);
+ }
+ else /* count == 0 */
+ {
+ /* Same as FETCH FORWARD 0, so fall out of switch */
+ fdirection = FETCH_FORWARD;
+ }
+ break;
+ default:
+ elog(ERROR, "DoPortalFetch: bogus direction");
+ break;
+ }
+
+ /*
+ * Get here with fdirection == FETCH_FORWARD or FETCH_BACKWARD,
+ * and count >= 0.
+ */
+ forward = (fdirection == FETCH_FORWARD);
/*
* Zero count means to re-fetch the current row, if any (per SQL92)
@@ -174,7 +277,7 @@ DoPortalFetch(Portal portal, bool forward, long count, CommandDest dest)
bool on_row;
/* Are we sitting on a row? */
- on_row = (portal->atStart == false && portal->atEnd == false);
+ on_row = (!portal->atStart && !portal->atEnd);
if (dest == None)
{
@@ -187,14 +290,12 @@ DoPortalFetch(Portal portal, bool forward, long count, CommandDest dest)
* If we are sitting on a row, back up one so we can re-fetch it.
* If we are not sitting on a row, we still have to start up and
* shut down the executor so that the destination is initialized
- * and shut down correctly; so keep going. Further down in the
- * routine, count == 0 means we will retrieve no row.
+ * and shut down correctly; so keep going. To DoRelativeFetch,
+ * count == 0 means we will retrieve no row.
*/
if (on_row)
{
- DoPortalFetch(portal,
- false /* backward */, 1L,
- None /* throw away output */);
+ DoRelativeFetch(portal, false, 1L, None);
/* Set up to fetch one row forward */
count = 1;
forward = true;
@@ -203,9 +304,44 @@ DoPortalFetch(Portal portal, bool forward, long count, CommandDest dest)
}
/*
- * switch into the portal context
+ * Optimize MOVE BACKWARD ALL into a Rewind.
*/
- oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+ if (!forward && count == FETCH_ALL && dest == None)
+ {
+ long result = portal->portalPos;
+
+ if (result > 0 && !portal->atEnd)
+ result--;
+ DoPortalRewind(portal);
+ /* result is bogus if pos had overflowed, but it's best we can do */
+ return result;
+ }
+
+ return DoRelativeFetch(portal, forward, count, dest);
+}
+
+/*
+ * DoRelativeFetch
+ * Do fetch for a simple N-rows-forward-or-backward case.
+ *
+ * count <= 0 is interpreted as a no-op: the destination gets started up
+ * and shut down, but nothing else happens. Also, count == FETCH_ALL is
+ * interpreted as "all rows".
+ *
+ * Caller must already have validated the Portal.
+ *
+ * Returns number of rows processed (suitable for use in result tag)
+ */
+static long
+DoRelativeFetch(Portal portal,
+ bool forward,
+ long count,
+ CommandDest dest)
+{
+ QueryDesc *queryDesc;
+ EState *estate;
+ ScanDirection direction;
+ QueryDesc temp_queryDesc;
queryDesc = PortalGetQueryDesc(portal);
estate = queryDesc->estate;
@@ -224,12 +360,9 @@ DoPortalFetch(Portal portal, bool forward, long count, CommandDest dest)
if (dest != queryDesc->dest &&
!(queryDesc->dest == RemoteInternal && dest == Remote))
{
- QueryDesc *qdesc = (QueryDesc *) palloc(sizeof(QueryDesc));
-
- memcpy(qdesc, queryDesc, sizeof(QueryDesc));
- qdesc->dest = dest;
- queryDesc = qdesc;
- temp_desc = true;
+ memcpy(&temp_queryDesc, queryDesc, sizeof(QueryDesc));
+ temp_queryDesc.dest = dest;
+ queryDesc = &temp_queryDesc;
}
/*
@@ -240,67 +373,103 @@ DoPortalFetch(Portal portal, bool forward, long count, CommandDest dest)
* robust about being called again if they've already returned NULL
* once.) Then call the executor (we must not skip this, because the
* destination needs to see a setup and shutdown even if no tuples are
- * available). Finally, update the atStart/atEnd state depending on
+ * available). Finally, update the portal position state depending on
* the number of tuples that were retrieved.
*/
if (forward)
{
- if (portal->atEnd || count == 0)
+ if (portal->atEnd || count <= 0)
direction = NoMovementScanDirection;
else
direction = ForwardScanDirection;
- /* In the executor, zero count processes all portal rows */
- if (count == INT_MAX)
+ /* In the executor, zero count processes all rows */
+ if (count == FETCH_ALL)
count = 0;
ExecutorRun(queryDesc, direction, count);
if (direction != NoMovementScanDirection)
{
+ long oldPos;
+
if (estate->es_processed > 0)
- portal->atStart = false; /* OK to back up now */
- if (count <= 0 || (long) estate->es_processed < count)
+ portal->atStart = false; /* OK to go backward now */
+ if (count == 0 ||
+ (unsigned long) estate->es_processed < (unsigned long) count)
portal->atEnd = true; /* we retrieved 'em all */
+ oldPos = portal->portalPos;
+ portal->portalPos += estate->es_processed;
+ /* portalPos doesn't advance when we fall off the end */
+ if (portal->portalPos < oldPos)
+ portal->posOverflow = true;
}
}
else
{
if (!portal->backwardOK)
- elog(ERROR, "Cursor cannot scan backwards"
+ elog(ERROR, "Cursor can only scan forward"
"\n\tDeclare it with SCROLL option to enable backward scan");
- if (portal->atStart || count == 0)
+ if (portal->atStart || count <= 0)
direction = NoMovementScanDirection;
else
direction = BackwardScanDirection;
- /* In the executor, zero count processes all portal rows */
- if (count == INT_MAX)
+ /* In the executor, zero count processes all rows */
+ if (count == FETCH_ALL)
count = 0;
ExecutorRun(queryDesc, direction, count);
if (direction != NoMovementScanDirection)
{
- if (estate->es_processed > 0)
+ if (estate->es_processed > 0 && portal->atEnd)
+ {
portal->atEnd = false; /* OK to go forward now */
- if (count <= 0 || (long) estate->es_processed < count)
+ portal->portalPos++; /* adjust for endpoint case */
+ }
+ if (count == 0 ||
+ (unsigned long) estate->es_processed < (unsigned long) count)
+ {
portal->atStart = true; /* we retrieved 'em all */
+ portal->portalPos = 0;
+ portal->posOverflow = false;
+ }
+ else
+ {
+ long oldPos;
+
+ oldPos = portal->portalPos;
+ portal->portalPos -= estate->es_processed;
+ if (portal->portalPos > oldPos ||
+ portal->portalPos <= 0)
+ portal->posOverflow = true;
+ }
}
}
- /*
- * Clean up and switch back to old context.
- */
- if (temp_desc)
- pfree(queryDesc);
-
- MemoryContextSwitchTo(oldcontext);
-
return estate->es_processed;
}
+/*
+ * DoPortalRewind - rewind a Portal to starting point
+ */
+static void
+DoPortalRewind(Portal portal)
+{
+ QueryDesc *queryDesc;
+
+ queryDesc = PortalGetQueryDesc(portal);
+
+ ExecutorRewind(queryDesc);
+
+ portal->atStart = true;
+ portal->atEnd = false;
+ portal->portalPos = 0;
+ portal->posOverflow = false;
+}
+
/*
* PerformPortalClose
* Close a cursor.
@@ -310,15 +479,6 @@ PerformPortalClose(char *name)
{
Portal portal;
- /*
- * sanity checks ... why is this case allowed by the grammar, anyway?
- */
- if (name == NULL)
- {
- elog(WARNING, "PerformPortalClose: missing portal name");
- return;
- }
-
/*
* get the portal from the portal name
*/
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index f037e72fd91..638cc61bef4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.201 2003/03/10 03:53:49 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.202 2003/03/11 19:40:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -287,6 +287,42 @@ ExecutorEnd(QueryDesc *queryDesc)
queryDesc->planstate = NULL;
}
+/* ----------------------------------------------------------------
+ * ExecutorRewind
+ *
+ * This routine may be called on an open queryDesc to rewind it
+ * to the start.
+ * ----------------------------------------------------------------
+ */
+void
+ExecutorRewind(QueryDesc *queryDesc)
+{
+ EState *estate;
+ MemoryContext oldcontext;
+
+ /* sanity checks */
+ Assert(queryDesc != NULL);
+
+ estate = queryDesc->estate;
+
+ Assert(estate != NULL);
+
+ /* It's probably not sensible to rescan updating queries */
+ Assert(queryDesc->operation == CMD_SELECT);
+
+ /*
+ * Switch into per-query memory context
+ */
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ /*
+ * rescan plan
+ */
+ ExecReScan(queryDesc->planstate, NULL);
+
+ MemoryContextSwitchTo(oldcontext);
+}
+
/*
* ExecCheckRTPerms
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index e1ccdf08f97..61eed9b4004 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.87 2003/03/10 03:53:49 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.88 2003/03/11 19:40:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1349,8 +1349,11 @@ _SPI_cursor_operation(Portal portal, bool forward, int count,
_SPI_current->tuptable = NULL;
/* Run the cursor */
- _SPI_current->processed = DoPortalFetch(portal, forward, (long) count,
- dest);
+ _SPI_current->processed =
+ DoPortalFetch(portal,
+ forward ? FETCH_FORWARD : FETCH_BACKWARD,
+ (long) count,
+ dest);
if (dest == SPI && _SPI_checktuples())
elog(FATAL, "SPI_fetch: # of processed tuples check failed");
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 045d3cc2ca2..7b28bdc9150 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.405 2003/03/10 03:53:50 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.406 2003/03/11 19:40:23 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -191,7 +191,7 @@ static void doNegateFloat(Value *v);
%type qualified_name OptConstrFromTable
-%type opt_id all_Op MathOp opt_name SpecialRuleRelation
+%type all_Op MathOp opt_name SpecialRuleRelation
%type iso_level opt_encoding
%type grantee
@@ -248,12 +248,10 @@ static void doNegateFloat(Value *v);
%type copy_from
-%type direction reindex_type drop_type
+%type reindex_type drop_type fetch_count
opt_column event comment_type cursor_options
-%type fetch_how_many
-
-%type select_limit_value select_offset_value
+%type fetch_direction select_limit_value select_offset_value
%type OptSeqList
%type OptSeqElem
@@ -345,7 +343,7 @@ static void doNegateFloat(Value *v);
EACH ELSE ENCODING ENCRYPTED END_P ESCAPE EXCEPT
EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
- FALSE_P FETCH FLOAT_P FOR FORCE FOREIGN FORWARD
+ FALSE_P FETCH FIRST_P FLOAT_P FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION
GLOBAL GRANT GROUP_P
@@ -361,7 +359,7 @@ static void doNegateFloat(Value *v);
KEY
- LANCOMPILER LANGUAGE LEADING LEFT LEVEL LIKE LIMIT
+ LANCOMPILER LANGUAGE LAST_P LEADING LEFT LEVEL LIKE LIMIT
LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
LOCK_P
@@ -1239,16 +1237,15 @@ opt_drop_behavior:
;
-
/*****************************************************************************
*
* QUERY :
- * close
+ * close
*
*****************************************************************************/
ClosePortalStmt:
- CLOSE opt_id
+ CLOSE name
{
ClosePortalStmt *n = makeNode(ClosePortalStmt);
n->portalname = $2;
@@ -1256,10 +1253,6 @@ ClosePortalStmt:
}
;
-opt_id: ColId { $$ = $1; }
- | /*EMPTY*/ { $$ = NULL; }
- ;
-
/*****************************************************************************
*
@@ -2583,61 +2576,17 @@ comment_text:
/*****************************************************************************
*
* QUERY:
- * fetch/move [forward | backward] [ # | all ] [ in ]
- * fetch [ forward | backward | absolute | relative ]
- * [ # | all | next | prior ] [ [ in | from ] ]
+ * fetch/move
*
*****************************************************************************/
-FetchStmt: FETCH direction fetch_how_many from_in name
+FetchStmt: FETCH fetch_direction from_in name
{
- FetchStmt *n = makeNode(FetchStmt);
- if ($3 < 0)
- {
- $3 = -$3;
- $2 = (($2 == FETCH_FORWARD) ? FETCH_BACKWARD : FETCH_FORWARD);
- }
- n->direction = $2;
- n->howMany = $3;
- n->portalname = $5;
- n->ismove = FALSE;
- $$ = (Node *)n;
- }
- | FETCH fetch_how_many from_in name
- {
- FetchStmt *n = makeNode(FetchStmt);
- if ($2 < 0)
- {
- n->howMany = -$2;
- n->direction = FETCH_BACKWARD;
- }
- else
- {
- n->direction = FETCH_FORWARD;
- n->howMany = $2;
- }
+ FetchStmt *n = (FetchStmt *) $2;
n->portalname = $4;
n->ismove = FALSE;
$$ = (Node *)n;
}
- | FETCH direction from_in name
- {
- FetchStmt *n = makeNode(FetchStmt);
- n->direction = $2;
- n->howMany = 1;
- n->portalname = $4;
- n->ismove = FALSE;
- $$ = (Node *)n;
- }
- | FETCH from_in name
- {
- FetchStmt *n = makeNode(FetchStmt);
- n->direction = FETCH_FORWARD;
- n->howMany = 1;
- n->portalname = $3;
- n->ismove = FALSE;
- $$ = (Node *)n;
- }
| FETCH name
{
FetchStmt *n = makeNode(FetchStmt);
@@ -2647,55 +2596,13 @@ FetchStmt: FETCH direction fetch_how_many from_in name
n->ismove = FALSE;
$$ = (Node *)n;
}
- | MOVE direction fetch_how_many from_in name
+ | MOVE fetch_direction from_in name
{
- FetchStmt *n = makeNode(FetchStmt);
- if ($3 < 0)
- {
- $3 = -$3;
- $2 = (($2 == FETCH_FORWARD) ? FETCH_BACKWARD : FETCH_FORWARD);
- }
- n->direction = $2;
- n->howMany = $3;
- n->portalname = $5;
- n->ismove = TRUE;
- $$ = (Node *)n;
- }
- | MOVE fetch_how_many from_in name
- {
- FetchStmt *n = makeNode(FetchStmt);
- if ($2 < 0)
- {
- n->howMany = -$2;
- n->direction = FETCH_BACKWARD;
- }
- else
- {
- n->direction = FETCH_FORWARD;
- n->howMany = $2;
- }
+ FetchStmt *n = (FetchStmt *) $2;
n->portalname = $4;
n->ismove = TRUE;
$$ = (Node *)n;
}
- | MOVE direction from_in name
- {
- FetchStmt *n = makeNode(FetchStmt);
- n->direction = $2;
- n->howMany = 1;
- n->portalname = $4;
- n->ismove = TRUE;
- $$ = (Node *)n;
- }
- | MOVE from_in name
- {
- FetchStmt *n = makeNode(FetchStmt);
- n->direction = FETCH_FORWARD;
- n->howMany = 1;
- n->portalname = $3;
- n->ismove = TRUE;
- $$ = (Node *)n;
- }
| MOVE name
{
FetchStmt *n = makeNode(FetchStmt);
@@ -2707,27 +2614,121 @@ FetchStmt: FETCH direction fetch_how_many from_in name
}
;
-direction: FORWARD { $$ = FETCH_FORWARD; }
- | BACKWARD { $$ = FETCH_BACKWARD; }
- | RELATIVE { $$ = FETCH_FORWARD; }
- | ABSOLUTE
+fetch_direction:
+ /*EMPTY*/
{
- elog(NOTICE,
- "FETCH / ABSOLUTE not supported, using RELATIVE");
- $$ = FETCH_FORWARD;
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_FORWARD;
+ n->howMany = 1;
+ $$ = (Node *)n;
+ }
+ | NEXT
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_FORWARD;
+ n->howMany = 1;
+ $$ = (Node *)n;
+ }
+ | PRIOR
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_BACKWARD;
+ n->howMany = 1;
+ $$ = (Node *)n;
+ }
+ | FIRST_P
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_ABSOLUTE;
+ n->howMany = 1;
+ $$ = (Node *)n;
+ }
+ | LAST_P
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_ABSOLUTE;
+ n->howMany = -1;
+ $$ = (Node *)n;
+ }
+ | ABSOLUTE fetch_count
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_ABSOLUTE;
+ n->howMany = $2;
+ $$ = (Node *)n;
+ }
+ | RELATIVE fetch_count
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_RELATIVE;
+ n->howMany = $2;
+ $$ = (Node *)n;
+ }
+ | fetch_count
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_FORWARD;
+ n->howMany = $1;
+ $$ = (Node *)n;
+ }
+ | ALL
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_FORWARD;
+ n->howMany = FETCH_ALL;
+ $$ = (Node *)n;
+ }
+ | FORWARD
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_FORWARD;
+ n->howMany = 1;
+ $$ = (Node *)n;
+ }
+ | FORWARD fetch_count
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_FORWARD;
+ n->howMany = $2;
+ $$ = (Node *)n;
+ }
+ | FORWARD ALL
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_FORWARD;
+ n->howMany = FETCH_ALL;
+ $$ = (Node *)n;
+ }
+ | BACKWARD
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_BACKWARD;
+ n->howMany = 1;
+ $$ = (Node *)n;
+ }
+ | BACKWARD fetch_count
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_BACKWARD;
+ n->howMany = $2;
+ $$ = (Node *)n;
+ }
+ | BACKWARD ALL
+ {
+ FetchStmt *n = makeNode(FetchStmt);
+ n->direction = FETCH_BACKWARD;
+ n->howMany = FETCH_ALL;
+ $$ = (Node *)n;
}
;
-fetch_how_many:
+fetch_count:
Iconst { $$ = $1; }
| '-' Iconst { $$ = - $2; }
- | ALL { $$ = INT_MAX; }
- | NEXT { $$ = 1; }
- | PRIOR { $$ = -1; }
;
-from_in: IN_P {}
- | FROM {}
+from_in: FROM {}
+ | IN_P {}
;
@@ -7093,6 +7094,7 @@ unreserved_keyword:
| EXPLAIN
| EXTERNAL
| FETCH
+ | FIRST_P
| FORCE
| FORWARD
| FUNCTION
@@ -7115,6 +7117,7 @@ unreserved_keyword:
| KEY
| LANCOMPILER
| LANGUAGE
+ | LAST_P
| LEVEL
| LISTEN
| LOAD
@@ -7170,9 +7173,9 @@ unreserved_keyword:
| SCROLL
| SECOND_P
| SECURITY
- | SESSION
| SEQUENCE
| SERIALIZABLE
+ | SESSION
| SET
| SHARE
| SHOW
@@ -7211,8 +7214,8 @@ unreserved_keyword:
| VOLATILE
| WITH
| WITHOUT
- | WRITE
| WORK
+ | WRITE
| YEAR_P
| ZONE
;
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index 727cff3eaf4..49432cb957a 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.134 2003/02/10 04:44:46 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.135 2003/03/11 19:40:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -128,6 +128,7 @@ static const ScanKeyword ScanKeywords[] = {
{"extract", EXTRACT},
{"false", FALSE_P},
{"fetch", FETCH},
+ {"first", FIRST_P},
{"float", FLOAT_P},
{"for", FOR},
{"force", FORCE},
@@ -171,6 +172,7 @@ static const ScanKeyword ScanKeywords[] = {
{"key", KEY},
{"lancompiler", LANCOMPILER},
{"language", LANGUAGE},
+ {"last", LAST_P},
{"leading", LEADING},
{"left", LEFT},
{"level", LEVEL},
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 0fae711a2c3..baf74aa3cc5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.194 2003/03/10 03:53:51 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.195 2003/03/11 19:40:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -321,15 +321,8 @@ ProcessUtility(Node *parsetree,
break;
case T_FetchStmt:
- {
- FetchStmt *stmt = (FetchStmt *) parsetree;
-
- PerformPortalFetch(stmt->portalname,
- stmt->direction == FETCH_FORWARD,
- stmt->howMany,
- (stmt->ismove) ? None : dest,
- completionTag);
- }
+ PerformPortalFetch((FetchStmt *) parsetree, dest,
+ completionTag);
break;
/*
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 654247dd8c5..66ee72718cf 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.52 2003/03/10 03:53:51 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.53 2003/03/11 19:40:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -167,10 +167,12 @@ PortalSetQuery(Portal portal,
AssertArg(PortalIsValid(portal));
portal->queryDesc = queryDesc;
- portal->backwardOK = ExecSupportsBackwardScan(queryDesc->plantree);
- portal->atStart = true; /* Allow fetch forward only, to start */
- portal->atEnd = false;
portal->cleanup = cleanup;
+ portal->backwardOK = ExecSupportsBackwardScan(queryDesc->plantree);
+ portal->atStart = true;
+ portal->atEnd = false; /* allow fetches */
+ portal->portalPos = 0;
+ portal->posOverflow = false;
}
/*
@@ -211,10 +213,12 @@ CreatePortal(const char *name)
/* initialize portal query */
portal->queryDesc = NULL;
- portal->backwardOK = false;
- portal->atStart = true; /* disallow fetches until query is set */
- portal->atEnd = true;
portal->cleanup = NULL;
+ portal->backwardOK = false;
+ portal->atStart = true;
+ portal->atEnd = true; /* disallow fetches until query is set */
+ portal->portalPos = 0;
+ portal->posOverflow = false;
/* put portal in table */
PortalHashTableInsert(portal);
diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h
index 3f2a4221add..74855ddf605 100644
--- a/src/include/commands/portalcmds.h
+++ b/src/include/commands/portalcmds.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: portalcmds.h,v 1.5 2003/03/10 03:53:51 tgl Exp $
+ * $Id: portalcmds.h,v 1.6 2003/03/11 19:40:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -19,10 +19,12 @@
extern void PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest);
-extern void PerformPortalFetch(char *name, bool forward, long count,
- CommandDest dest, char *completionTag);
+extern void PerformPortalFetch(FetchStmt *stmt, CommandDest dest,
+ char *completionTag);
-extern long DoPortalFetch(Portal portal, bool forward, long count,
+extern long DoPortalFetch(Portal portal,
+ FetchDirection fdirection,
+ long count,
CommandDest dest);
extern void PerformPortalClose(char *name);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 785d21718b2..40a696e2976 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: executor.h,v 1.90 2003/03/10 03:53:51 tgl Exp $
+ * $Id: executor.h,v 1.91 2003/03/11 19:40:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -86,6 +86,7 @@ extern void ExecutorStart(QueryDesc *queryDesc);
extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc,
ScanDirection direction, long count);
extern void ExecutorEnd(QueryDesc *queryDesc);
+extern void ExecutorRewind(QueryDesc *queryDesc);
extern void ExecCheckRTPerms(List *rangeTable, CmdType operation);
extern void ExecEndPlan(PlanState *planstate, EState *estate);
extern void ExecConstraints(const char *caller, ResultRelInfo *resultRelInfo,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index c84348ded9e..216ed04c762 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: parsenodes.h,v 1.232 2003/03/10 03:53:51 tgl Exp $
+ * $Id: parsenodes.h,v 1.233 2003/03/11 19:40:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1228,16 +1228,21 @@ typedef struct ClosePortalStmt
*/
typedef enum FetchDirection
{
+ /* for these, howMany is how many rows to fetch; FETCH_ALL means ALL */
FETCH_FORWARD,
- FETCH_BACKWARD
- /* ABSOLUTE someday? */
+ FETCH_BACKWARD,
+ /* for these, howMany indicates a position; only one row is fetched */
+ FETCH_ABSOLUTE,
+ FETCH_RELATIVE
} FetchDirection;
+#define FETCH_ALL LONG_MAX
+
typedef struct FetchStmt
{
NodeTag type;
FetchDirection direction; /* see above */
- long howMany; /* number of rows */
+ long howMany; /* number of rows, or position argument */
char *portalname; /* name of portal (cursor) */
bool ismove; /* TRUE if MOVE */
} FetchStmt;
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index 21469dd52df..c9ca8547ce2 100644
--- a/src/include/utils/portal.h
+++ b/src/include/utils/portal.h
@@ -9,7 +9,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: portal.h,v 1.38 2003/03/10 03:53:52 tgl Exp $
+ * $Id: portal.h,v 1.39 2003/03/11 19:40:24 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -27,10 +27,21 @@ typedef struct PortalData
char *name; /* Portal's name */
MemoryContext heap; /* subsidiary memory */
QueryDesc *queryDesc; /* Info about query associated with portal */
- bool backwardOK; /* is fetch backwards allowed at all? */
- bool atStart; /* T => fetch backwards is not allowed now */
- bool atEnd; /* T => fetch forwards is not allowed now */
void (*cleanup) (Portal); /* Cleanup routine (optional) */
+ bool backwardOK; /* is fetch backwards allowed? */
+ /*
+ * atStart, atEnd and portalPos indicate the current cursor position.
+ * portalPos is zero before the first row, N after fetching N'th row of
+ * query. After we run off the end, portalPos = # of rows in query, and
+ * atEnd is true. If portalPos overflows, set posOverflow (this causes
+ * us to stop relying on its value for navigation). Note that atStart
+ * implies portalPos == 0, but not the reverse (portalPos could have
+ * overflowed).
+ */
+ bool atStart;
+ bool atEnd;
+ bool posOverflow;
+ long portalPos;
} PortalData;
/*