diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c index 735e889dbd..81f68119f8 100644 --- a/contrib/tablefunc/tablefunc.c +++ b/contrib/tablefunc/tablefunc.c @@ -853,7 +853,7 @@ get_crosstab_tuplestore(char *sql, MemoryContext SPIcontext; /* initialize our tuplestore */ - tupstore = tuplestore_begin_heap(true, SortMem); + tupstore = tuplestore_begin_heap(true, false, SortMem); /* Connect to SPI manager */ if ((ret = SPI_connect()) < 0) @@ -1124,7 +1124,7 @@ connectby(char *relname, oldcontext = MemoryContextSwitchTo(per_query_ctx); /* initialize our tuplestore */ - tupstore = tuplestore_begin_heap(true, SortMem); + tupstore = tuplestore_begin_heap(true, false, SortMem); MemoryContextSwitchTo(oldcontext); diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml index 8aa5b90a9e..389b455fde 100644 --- a/doc/src/sgml/ref/copy.sgml +++ b/doc/src/sgml/ref/copy.sgml @@ -1,5 +1,5 @@ @@ -133,9 +133,10 @@ COPY table [ ( cursorname [ BINARY ] [ INSENSITIVE ] [ SCROLL ] - CURSOR FOR query +DECLARE cursorname [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ] + CURSOR [ { WITH | WITHOUT } HOLD ] FOR query [ FOR { READ ONLY | UPDATE [ OF column [, ...] ] ] @@ -38,7 +38,8 @@ DECLARE cursorname [ BINARY ] [ INS cursorname - The name of the cursor to be used in subsequent FETCH operations. + The name of the cursor to be used in subsequent + FETCH operations. @@ -57,8 +58,20 @@ DECLARE cursorname [ BINARY ] [ INS SQL92 keyword indicating that data retrieved - from the cursor should be unaffected by updates from other processes or cursors. - By default, all cursors are insensitive. This keyword has no effect. + from the cursor should be unaffected by updates from other + processes or cursors. By default, all cursors are insensitive. + This keyword currently has no effect and is present for + compatibility with the SQL standard. + + + + + + NO SCROLL + + + Specifies that the cursor cannot be used to retrieve rows in a + nonsequential fashion (e.g., backward). @@ -67,8 +80,33 @@ DECLARE cursorname [ BINARY ] [ INS SCROLL - Specifies that the cursor may be used to retrieve rows - in a nonsequential fashion (e.g., backwards). + Specifies that the cursor may be used to retrieve rows in a + nonsequential fashion (e.g., backward). Depending upon the + complexity of the query's execution plan, specifying + SCROLL may impose a slight performance penalty + on the query's execution time. + + + + + + WITHOUT HOLD + + + Specifies that the cursor cannot be used outside of the + transaction that created it. If neither WITHOUT + HOLD nor WITH HOLD is specified, + WITH HOLD is the default. + + + + + + WITH HOLD + + + Specifies that the cursor may be used after the transaction + that creates it successfully commits. @@ -124,7 +162,8 @@ DECLARE cursorname [ BINARY ] [ INS - The BINARY, INSENSITIVE, and SCROLL keywords may appear in any order. + The BINARY, INSENSITIVE, + SCROLL keywords may appear in any order. @@ -144,7 +183,7 @@ DECLARE CURSOR - The message returned if the SELECT is run successfully. + The message returned if the SELECT is run successfully. @@ -155,9 +194,8 @@ WARNING: Closing pre-existing portal "cursorname - This message is reported if the same cursor name was already declared - in the current transaction block. The previous definition is - discarded. + This message is reported if a cursor with the same name already + exists. The previous definition is discarded. @@ -168,7 +206,9 @@ ERROR: DECLARE CURSOR may only be used in begin/end transaction blocks - This error occurs if the cursor is not declared within a transaction block. + This error occurs if the cursor is not declared within a + transaction block, and WITH HOLD is not + specified. @@ -193,16 +233,14 @@ ERROR: DECLARE CURSOR may only be used in begin/end transaction blocks - Normal cursors return data in text format, the same as a SELECT - would produce. Since - data is stored natively in binary format, the system must - do a conversion to produce the text format. In addition, - text formats are often larger in size than the corresponding binary format. - Once the information comes back in text form, the client - application may need to convert it to a binary format to - manipulate it. - BINARY cursors give you back the data in the native binary - representation. + Normal cursors return data in text format, the same as a + SELECT would produce. Since data is stored natively in + binary format, the system must do a conversion to produce the text + format. In addition, text formats are often larger in size than the + corresponding binary format. Once the information comes back in + text form, the client application may need to convert it to a + binary format to manipulate it. BINARY cursors give you back the + data in the native binary representation. @@ -245,7 +283,9 @@ ERROR: DECLARE CURSOR may only be used in begin/end transaction blocks - Cursors are only available within transactions. Use + If WITH HOLD is not specified, the cursor + created by this command can only be used within the current + transaction. Use , and @@ -254,12 +294,25 @@ ERROR: DECLARE CURSOR may only be used in begin/end transaction blocks - The SCROLL option should be specified when defining a cursor - that will be used to fetch backwards. This is required by - SQL92. However, for compatibility with - earlier versions, PostgreSQL will allow - backward fetches without SCROLL, if the cursor's query plan - is simple enough that no extra overhead is needed to support it. + If WITH HOLD is specified and the transaction + that created the cursor successfully commits, the cursor can be + accessed outside the creating transaction. If the creating + transaction is aborted, the cursor is removed. A cursor created + with WITH HOLD is closed when an explicit + CLOSE command is issued on it, or the client + connection is terminated. + + + + The SCROLL option should be specified when defining a + cursor that will be used to fetch backwards. This is required by + SQL92. However, for compatibility with earlier + versions, PostgreSQL will allow + backward fetches without SCROLL, if the cursor's query + plan is simple enough that no extra overhead is needed to support + it. However, application developers are advised not to rely on + using backward fetches from a cursor that has not been created + with SCROLL. @@ -271,7 +324,7 @@ ERROR: DECLARE CURSOR may only be used in begin/end transaction blocks However, ecpg, the embedded SQL preprocessor for PostgreSQL, supports the SQL92 cursor conventions, including those - involving DECLARE and OPEN statements. + involving DECLARE and OPEN statements. @@ -303,13 +356,21 @@ DECLARE liahona CURSOR SQL92 - SQL92 allows cursors only in embedded SQL - and in modules. PostgreSQL permits cursors to be used - interactively. + + SQL92 allows cursors only in embedded + SQL and in modules. PostgreSQL + permits cursors to be used interactively. + + + SQL92 allows embedded or modular cursors to - update database information. - All PostgreSQL cursors are read only. - The BINARY keyword is a PostgreSQL extension. + update database information. All PostgreSQL + cursors are read only. + + + + The BINARY keyword is a + PostgreSQL extension. diff --git a/doc/src/sgml/ref/fetch.sgml b/doc/src/sgml/ref/fetch.sgml index 8f3244eb39..a1f3b13719 100644 --- a/doc/src/sgml/ref/fetch.sgml +++ b/doc/src/sgml/ref/fetch.sgml @@ -1,5 +1,5 @@ @@ -251,8 +251,7 @@ WARNING: PerformPortalFetch: portal "cursor - If cursor is not known. - The cursor must have been declared within the current transaction block. + There is no cursor with the specified name. @@ -326,7 +325,9 @@ WARNING: PerformPortalFetch: portal "cursorFETCH 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. + cursors not declared with SCROLL, but this behavior is best not + relied on. If the cursor is declared with NO SCROLL, no backward + fetches are allowed. @@ -339,16 +340,11 @@ WARNING: PerformPortalFetch: portal "cursor - Updating data via a cursor is not supported by - PostgreSQL, - because mapping cursor updates back to base tables is - not generally possible, as is also the case with VIEW updates. - Consequently, - users must issue explicit UPDATE commands to replace data. - - - - Cursors may only be used inside transaction blocks. + Updating data via a cursor is not supported by + PostgreSQL, because mapping cursor + updates back to base tables is not generally possible, as is also + the case with view updates. Consequently, users must issue + explicit UPDATE commands to replace data. @@ -357,12 +353,6 @@ WARNING: PerformPortalFetch: portal "cursor to change cursor position without retrieving data. - Refer to - , - , - and - - for further information about transactions. @@ -379,7 +369,7 @@ WARNING: PerformPortalFetch: portal "cursor - SQL92 defines FETCH for use in embedded contexts only. - Therefore, it describes placing the results into explicit variables using - an INTO clause, for example: + 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 n @@ -435,16 +426,18 @@ FETCH ABSOLUTE n INTO :variable [, ...] - 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 + 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 + The FETCH forms involving FORWARD and BACKWARD + (including the forms FETCH count and FETCH ALL, in which + FORWARD is implicit) are PostgreSQL extensions. diff --git a/doc/src/sgml/ref/move.sgml b/doc/src/sgml/ref/move.sgml index f01ee9d8a5..cd6d6aca0f 100644 --- a/doc/src/sgml/ref/move.sgml +++ b/doc/src/sgml/ref/move.sgml @@ -1,5 +1,5 @@ @@ -64,12 +64,6 @@ MOVE [ direction { FROM | IN } ] to define a cursor. - Refer to - , - , - and - - for further information about transactions. @@ -83,7 +77,7 @@ MOVE [ direction { FROM | IN } ] BEGIN WORK; -DECLARE liahona CURSOR FOR SELECT * FROM films; +DECLARE liahona CURSOR FOR SELECT * FROM films; -- Skip first 5 rows: MOVE FORWARD 5 IN liahona; diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml index 9e788ff061..89de666802 100644 --- a/doc/src/sgml/sql.sgml +++ b/doc/src/sgml/sql.sgml @@ -1,5 +1,5 @@ @@ -851,7 +851,7 @@ A < B + 3. The most often used command in SQL is the - SELECT statement, + SELECT statement, used to retrieve data. The syntax is: @@ -881,7 +881,7 @@ SELECT [ ALL | DISTINCT [ ON ( expressionSimple Selects - Here are some simple examples using a SELECT statement: + Here are some simple examples using a SELECT statement: Simple Query with Qualification @@ -905,9 +905,10 @@ SELECT * FROM PART - Using * in the SELECT statement will deliver all attributes from - the table. If we want to retrieve only the attributes PNAME and PRICE - from table PART we use the statement: + Using * in the SELECT statement + will deliver all attributes from the table. If we want to retrieve + only the attributes PNAME and PRICE from table PART we use the + statement: SELECT PNAME, PRICE @@ -924,9 +925,9 @@ SELECT PNAME, PRICE Cam | 25 - Note that the SQL SELECT corresponds to the - projection in relational algebra not to the - selection (see SQL SELECT + corresponds to the projection in relational algebra + not to the selection (see for more details). @@ -1252,15 +1253,15 @@ select sname, pname from supplier 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 @@ -1413,11 +1414,12 @@ SELECT S.SNO, S.SNAME, COUNT(SE.PNO) - 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. @@ -1502,16 +1504,18 @@ SELECT * - 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.) @@ -1528,11 +1532,13 @@ 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. + 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. @@ -1670,7 +1676,7 @@ EXCEPT 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 @@ -1786,7 +1792,7 @@ CREATE TABLE SELLS 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 @@ -1808,10 +1814,11 @@ CREATE INDEX I ON SUPPLIER (SNAME); - 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. @@ -1916,7 +1923,7 @@ SELECT * FROM London_Suppliers 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; @@ -1932,7 +1939,7 @@ DROP TABLE SUPPLIER; - The DROP INDEX command is used to destroy an index: + The DROP INDEX command is used to destroy an index: DROP INDEX index_name; @@ -1940,7 +1947,8 @@ 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; @@ -1994,7 +2002,7 @@ INSERT INTO SELLS (SNO, PNO) 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 @@ -2126,7 +2134,7 @@ DELETE FROM SUPPLIER 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. diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 3f172dddfc..598e3c880e 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -8,7 +8,7 @@ * * * 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: @@ -926,7 +926,7 @@ CommitTransaction(void) * 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 */ @@ -1057,7 +1057,7 @@ AbortTransaction(void) * do abort processing */ DeferredTriggerAbortXact(); - AtEOXact_portals(); + AtEOXact_portals(false); lo_commit(false); /* 'false' means it's abort */ AtAbort_Notify(); AtEOXact_UpdatePasswordFile(false); diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index baaf57a70a..978429c87f 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -1,15 +1,14 @@ /*------------------------------------------------------------------------- * * 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 $ * *------------------------------------------------------------------------- */ @@ -50,6 +49,9 @@ #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, @@ -1311,7 +1313,7 @@ GetTypeElement(Oid type) * *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 diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index 1ba72437ad..7eabc58d49 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.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 $ * *------------------------------------------------------------------------- */ @@ -17,18 +17,23 @@ #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); /* @@ -46,8 +51,15 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest) 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 @@ -76,7 +88,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest) /* * 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); @@ -130,6 +142,7 @@ PerformPortalFetch(FetchStmt *stmt, portal = GetPortalByName(stmt->portalname); if (!PortalIsValid(portal)) { + /* FIXME: shouldn't this be an ERROR? */ elog(WARNING, "PerformPortalFetch: portal \"%s\" not found", stmt->portalname); return; @@ -343,6 +356,9 @@ DoRelativeFetch(Portal portal, ScanDirection direction; QueryDesc temp_queryDesc; + if (portal->holdStore) + return DoRelativeStoreFetch(portal, forward, count, dest); + queryDesc = PortalGetQueryDesc(portal); estate = queryDesc->estate; @@ -407,7 +423,7 @@ DoRelativeFetch(Portal portal, } 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"); @@ -452,17 +468,85 @@ DoRelativeFetch(Portal portal, 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; @@ -493,22 +577,25 @@ PerformPortalClose(char *name) /* * 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)) { /* @@ -516,19 +603,30 @@ PreparePortal(char *portalName) * 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 * @@ -545,14 +643,128 @@ PortalCleanup(Portal portal) AssertArg(PortalIsValid(portal)); AssertArg(portal->cleanup == PortalCleanup); - /* - * tell the executor to shutdown the query - */ - ExecutorEnd(PortalGetQueryDesc(portal)); + 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); /* - * This should be unnecessary since the querydesc should be in the - * portal's memory context, but do it anyway for symmetry. + * If we're preserving a holdable portal, we had better be + * inside the transaction that originally created it. */ - FreeQueryDesc(PortalGetQueryDesc(portal)); + 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); + } + } + + /* + * 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. + */ + { + 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; + } + + /* + * We no longer need the portal's short-term memory context. + */ + MemoryContextDelete(PortalGetHeapMemory(portal)); + + PortalGetHeapMemory(portal) = NULL; } diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index 7e3f5d2d2c..cc49bd6d69 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -4,7 +4,7 @@ # 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 $ # #------------------------------------------------------------------------- @@ -18,7 +18,7 @@ OBJS = execAmi.o execGrouping.o execJunk.o execMain.o \ 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 diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 1266895a5f..485f1e03fa 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.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 $ * *------------------------------------------------------------------------- */ @@ -217,8 +217,8 @@ ExecutorRun(QueryDesc *queryDesc, 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 @@ -421,15 +421,6 @@ ExecCheckRTEPerms(RangeTblEntry *rte, CmdType operation) } } - -/* =============================================================== - * =============================================================== - static routines follow - * =============================================================== - * =============================================================== - */ - - static void ExecCheckXactReadOnly(Query *parsetree, CmdType operation) { diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index a1c1fdd8ad..7e084f2830 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -1054,8 +1054,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, 0, false); } - tupstore = tuplestore_begin_heap(true, /* randomAccess */ - SortMem); + tupstore = tuplestore_begin_heap(true, false, SortMem); MemoryContextSwitchTo(oldcontext); rsinfo.setResult = tupstore; rsinfo.setDesc = tupdesc; diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c index 31152a3d85..b338c8961e 100644 --- a/src/backend/executor/nodeHash.c +++ b/src/backend/executor/nodeHash.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -64,7 +64,7 @@ ExecHash(HashState *node) * buffers are palloc'd in regular executor context. */ for (i = 0; i < nbatch; i++) - hashtable->innerBatchFile[i] = BufFileCreateTemp(); + hashtable->innerBatchFile[i] = BufFileCreateTemp(false); } /* diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index 3603fd9b68..4bc1671801 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -138,7 +138,7 @@ ExecHashJoin(HashJoinState *node) * 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; diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c index 2566851dcc..39968c65e0 100644 --- a/src/backend/executor/nodeMaterial.c +++ b/src/backend/executor/nodeMaterial.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -62,8 +62,7 @@ ExecMaterial(MaterialState *node) */ if (tuplestorestate == NULL) { - tuplestorestate = tuplestore_begin_heap(true, /* randomAccess */ - SortMem); + tuplestorestate = tuplestore_begin_heap(true, false, SortMem); node->tuplestorestate = (void *) tuplestorestate; } diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 61eed9b400..7184fbf9ba 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.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 $ * *------------------------------------------------------------------------- */ @@ -866,7 +866,7 @@ SPI_cursor_close(Portal portal) if (!PortalIsValid(portal)) elog(ERROR, "invalid portal in SPI cursor operation"); - PortalDrop(portal); + PortalDrop(portal, false); } /* =================== private functions =================== */ diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 3486a1e010..64cd5603ac 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -2294,6 +2294,13 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) 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); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dc8b06e750..0ee065a9a1 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.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 @@ -246,7 +246,7 @@ static void doNegateFloat(Value *v); %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 @@ -348,7 +348,7 @@ static void doNegateFloat(Value *v); 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 @@ -4230,22 +4230,31 @@ UpdateStmt: UPDATE relation_expr * 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: diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index 83608d95ed..d62f5668d5 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.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 $ * *------------------------------------------------------------------------- */ @@ -143,6 +143,7 @@ static const ScanKeyword ScanKeywords[] = { {"group", GROUP_P}, {"handler", HANDLER}, {"having", HAVING}, + {"hold", HOLD}, {"hour", HOUR_P}, {"ilike", ILIKE}, {"immediate", IMMEDIATE}, diff --git a/src/backend/storage/file/buffile.c b/src/backend/storage/file/buffile.c index 7e78793a40..90b185cd5d 100644 --- a/src/backend/storage/file/buffile.c +++ b/src/backend/storage/file/buffile.c @@ -7,7 +7,7 @@ * 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: * @@ -64,6 +64,7 @@ struct BufFile */ bool isTemp; /* can only add files if this is TRUE */ + bool isInterTxn; /* keep open over transactions? */ bool dirty; /* does buffer need to be written? */ /* @@ -118,7 +119,7 @@ extendBufFile(BufFile *file) File pfile; Assert(file->isTemp); - pfile = OpenTemporaryFile(); + pfile = OpenTemporaryFile(file->isInterTxn); Assert(pfile >= 0); file->files = (File *) repalloc(file->files, @@ -136,16 +137,17 @@ extendBufFile(BufFile *file) * 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; } diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index 83c97fb755..7607d4186c 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -7,7 +7,7 @@ * 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: * @@ -112,14 +112,14 @@ int max_files_per_process = 1000; #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; @@ -750,9 +750,15 @@ PathNameOpenFile(FileName fileName, int fileFlags, int fileMode) * 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; @@ -795,9 +801,13 @@ OpenTemporaryFile(void) 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; } @@ -1114,6 +1124,7 @@ AtEOXact_Files(void) for (i = 1; i < SizeVfdCache; i++) { if ((VfdCache[i].fdstate & FD_TEMPORARY) && + (VfdCache[i].fdstate & FD_TXN_TEMPORARY) && VfdCache[i].fileName != NULL) FileClose(i); } diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c index 516af21a46..ad9d232771 100644 --- a/src/backend/tcop/dest.c +++ b/src/backend/tcop/dest.c @@ -8,7 +8,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -29,6 +29,7 @@ #include "postgres.h" #include "access/printtup.h" +#include "executor/tstoreReceiver.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" @@ -60,9 +61,11 @@ donothingCleanup(DestReceiver *self) static DestReceiver donothingDR = { donothingReceive, donothingSetup, donothingCleanup }; + static DestReceiver debugtupDR = { debugtup, debugSetup, donothingCleanup }; + static DestReceiver spi_printtupDR = { spi_printtup, spi_dest_setup, donothingCleanup }; @@ -98,6 +101,9 @@ DestToFunction(CommandDest dest) case SPI: return &spi_printtupDR; + case Tuplestore: + return tstoreReceiverCreateDR(); + case None: return &donothingDR; } @@ -122,6 +128,7 @@ EndCommand(const char *commandTag, CommandDest dest) case None: case Debug: + case Tuplestore: case SPI: break; } @@ -183,6 +190,7 @@ NullCommand(CommandDest dest) break; case Debug: + case Tuplestore: case None: default: break; @@ -213,6 +221,7 @@ ReadyForQuery(CommandDest dest) break; case Debug: + case Tuplestore: case None: default: break; diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index 48545b1512..729137a1b9 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -14,7 +14,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -36,7 +36,8 @@ 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; diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 66ee72718c..e6486d4b16 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.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 $ * *------------------------------------------------------------------------- */ @@ -20,8 +20,8 @@ * "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 @@ -38,7 +38,6 @@ #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() @@ -131,8 +130,8 @@ EnablePortalManager(void) 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); @@ -157,7 +156,9 @@ GetPortalByName(const char *name) /* * 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, @@ -166,9 +167,25 @@ 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; @@ -179,10 +196,8 @@ PortalSetQuery(Portal portal, * 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) @@ -214,7 +229,11 @@ 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; @@ -228,17 +247,47 @@ CreatePortal(const char *name) /* * 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); @@ -246,8 +295,20 @@ PortalDrop(Portal 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); @@ -255,7 +316,12 @@ PortalDrop(Portal portal) } /* - * 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 @@ -264,13 +330,17 @@ PortalDrop(Portal portal) * 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); + } } diff --git a/src/backend/utils/sort/logtape.c b/src/backend/utils/sort/logtape.c index 1c28028f5f..643b7a7601 100644 --- a/src/backend/utils/sort/logtape.c +++ b/src/backend/utils/sort/logtape.c @@ -64,7 +64,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -470,7 +470,7 @@ LogicalTapeSetCreate(int ntapes) 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)); diff --git a/src/backend/utils/sort/tuplestore.c b/src/backend/utils/sort/tuplestore.c index 65804d5484..89970b2495 100644 --- a/src/backend/utils/sort/tuplestore.c +++ b/src/backend/utils/sort/tuplestore.c @@ -36,7 +36,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -65,6 +65,7 @@ struct Tuplestorestate { 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 */ @@ -190,7 +191,8 @@ struct Tuplestorestate 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); @@ -205,7 +207,7 @@ static void *readtup_heap(Tuplestorestate *state, unsigned int len); */ static Tuplestorestate * -tuplestore_begin_common(bool randomAccess, int maxKBytes) +tuplestore_begin_common(bool randomAccess, bool interTxn, int maxKBytes) { Tuplestorestate *state; @@ -213,6 +215,7 @@ tuplestore_begin_common(bool randomAccess, int maxKBytes) state->status = TSS_INMEM; state->randomAccess = randomAccess; + state->interTxn = interTxn; state->availMem = maxKBytes * 1024L; state->myfile = NULL; @@ -231,10 +234,27 @@ tuplestore_begin_common(bool randomAccess, int maxKBytes) 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; @@ -321,7 +341,7 @@ tuplestore_puttuple(Tuplestorestate *state, void *tuple) /* * Nope; time to switch to tape-based operation. */ - state->myfile = BufFileCreateTemp(); + state->myfile = BufFileCreateTemp(state->interTxn); state->status = TSS_WRITEFILE; dumptuples(state); break; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 49887f450c..3d4b235e56 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.235 2003/03/20 18:52:48 momjian Exp $ + * $Id: parsenodes.h,v 1.236 2003/03/27 16:51:29 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -1210,7 +1210,9 @@ typedef struct CommentStmt */ #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 { diff --git a/src/include/storage/buffile.h b/src/include/storage/buffile.h index 902483a1fb..c456b74d0f 100644 --- a/src/include/storage/buffile.h +++ b/src/include/storage/buffile.h @@ -18,7 +18,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -34,7 +34,7 @@ typedef struct BufFile BufFile; * 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); diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h index a13cec41ea..261e6314e9 100644 --- a/src/include/storage/fd.h +++ b/src/include/storage/fd.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: 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 $ * *------------------------------------------------------------------------- */ @@ -55,7 +55,7 @@ extern int max_files_per_process; /* 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); diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h index bbad436c04..bbf86ef4ca 100644 --- a/src/include/tcop/dest.h +++ b/src/include/tcop/dest.h @@ -44,7 +44,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -70,7 +70,8 @@ typedef enum 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; /* ---------------- diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h index c9ca8547ce..0201b0684c 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.39 2003/03/11 19:40:24 tgl Exp $ + * $Id: portal.h,v 1.40 2003/03/27 16:51:29 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -18,17 +18,45 @@ #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 @@ -58,11 +86,12 @@ typedef struct PortalData 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 */ diff --git a/src/include/utils/tuplestore.h b/src/include/utils/tuplestore.h index 76fe9fb428..dbc47ef29d 100644 --- a/src/include/utils/tuplestore.h +++ b/src/include/utils/tuplestore.h @@ -17,7 +17,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -37,7 +37,8 @@ typedef struct Tuplestorestate Tuplestorestate; */ extern Tuplestorestate *tuplestore_begin_heap(bool randomAccess, - int maxKBytes); + bool interTxn, + int maxKBytes); extern void tuplestore_puttuple(Tuplestorestate *state, void *tuple); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index d105c0e8dc..d2e05f16c2 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3,7 +3,7 @@ * 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. * @@ -1757,7 +1757,7 @@ exec_init_tuple_store(PLpgSQL_execstate * estate) 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; diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out index 23c186cead..7520653924 100644 --- a/src/test/regress/expected/portals.out +++ b/src/test/regress/expected/portals.out @@ -1,30 +1,30 @@ -- --- 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 ---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- @@ -675,4 +675,66 @@ CLOSE foo9; 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 diff --git a/src/test/regress/sql/portals.sql b/src/test/regress/sql/portals.sql index c4e257ef2d..2df820a30a 100644 --- a/src/test/regress/sql/portals.sql +++ b/src/test/regress/sql/portals.sql @@ -1,54 +1,54 @@ -- --- 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; @@ -166,5 +166,55 @@ 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; + +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