*** contrib/tablefunc/tablefunc.c.orig Sun Aug 29 09:54:44 2004
--- contrib/tablefunc/tablefunc.c Sun Sep 12 16:52:39 2004
***************
*** 386,392 ****
elog(ERROR, "crosstab: SPI_connect returned %d", ret);
/* Retrieve the desired rows */
! ret = SPI_exec(sql, 0);
proc = SPI_processed;
/* Check for qualifying tuples */
--- 386,392 ----
elog(ERROR, "crosstab: SPI_connect returned %d", ret);
/* Retrieve the desired rows */
! ret = SPI_execute(sql, true, 0);
proc = SPI_processed;
/* Check for qualifying tuples */
***************
*** 777,783 ****
elog(ERROR, "load_categories_hash: SPI_connect returned %d", ret);
/* Retrieve the category name rows */
! ret = SPI_exec(cats_sql, 0);
num_categories = proc = SPI_processed;
/* Check for qualifying tuples */
--- 777,783 ----
elog(ERROR, "load_categories_hash: SPI_connect returned %d", ret);
/* Retrieve the category name rows */
! ret = SPI_execute(cats_sql, true, 0);
num_categories = proc = SPI_processed;
/* Check for qualifying tuples */
***************
*** 855,861 ****
elog(ERROR, "get_crosstab_tuplestore: SPI_connect returned %d", ret);
/* Now retrieve the crosstab source rows */
! ret = SPI_exec(sql, 0);
proc = SPI_processed;
/* Check for qualifying tuples */
--- 855,861 ----
elog(ERROR, "get_crosstab_tuplestore: SPI_connect returned %d", ret);
/* Now retrieve the crosstab source rows */
! ret = SPI_execute(sql, true, 0);
proc = SPI_processed;
/* Check for qualifying tuples */
***************
*** 1376,1382 ****
}
/* Retrieve the desired rows */
! ret = SPI_exec(sql->data, 0);
proc = SPI_processed;
/* Check for qualifying tuples */
--- 1376,1382 ----
}
/* Retrieve the desired rows */
! ret = SPI_execute(sql->data, true, 0);
proc = SPI_processed;
/* Check for qualifying tuples */
*** doc/src/sgml/plpython.sgml.orig Thu May 20 15:22:46 2004
--- doc/src/sgml/plpython.sgml Sun Sep 12 16:50:25 2004
***************
*** 175,181 ****
row number and column name. It has these additional methods:
nrows which returns the number of rows
returned by the query, and status which is the
! SPI_exec() return value. The result object
can be modified.
--- 175,181 ----
row number and column name. It has these additional methods:
nrows which returns the number of rows
returned by the query, and status which is the
! SPI_execute() return value. The result object
can be modified.
*** doc/src/sgml/spi.sgml.orig Thu Apr 1 16:28:43 2004
--- doc/src/sgml/spi.sgml Sun Sep 12 16:50:25 2004
***************
*** 206,212 ****
SPI_push
! pushes SPI stack to allow recursive SPI calls
SPI_push
--- 206,212 ----
SPI_push
! pushes SPI stack to allow recursive SPI usage
SPI_push
***************
*** 221,228 ****
Description
! SPI_push pushes a new environment on to the
! SPI call stack, allowing recursive calls to use a new environment.
--- 221,244 ----
Description
! SPI_push should be called before executing another
! procedure that might itself wish to use SPI.
! After SPI_push, SPI is no longer in a
! connected> state, and SPI function calls will be rejected unless
! a fresh SPI_connect is done. This ensures a clean
! separation between your procedure's SPI state and that of another procedure
! you call. After the other procedure returns, call
! SPI_pop to restore access to your own SPI state.
!
!
!
! Note that SPI_execute and related functions
! automatically do the equivalent of SPI_push before
! passing control back to the SQL execution engine, so it is not necessary
! for you to worry about this when using those functions.
! Only when you are directly calling arbitrary code that might contain
! SPI_connect do you need to issue
! SPI_push and SPI_pop.
***************
*** 237,243 ****
SPI_pop
! pops SPI stack to allow recursive SPI calls
SPI_pop
--- 253,259 ----
SPI_pop
! pops SPI stack to return from recursive SPI usage
SPI_pop
***************
*** 253,259 ****
SPI_pop pops the previous environment from the
! SPI call stack. For use when returning from recursive SPI calls.
--- 269,275 ----
SPI_pop pops the previous environment from the
! SPI call stack. See SPI_push.
***************
*** 261,281 ****
!
! SPI_exec
! SPI_exec
execute a command
! SPI_exec
! int SPI_exec(const char * command, int count)
--- 277,297 ----
!
! SPI_execute
! SPI_execute
execute a command
! SPI_execute
! int SPI_execute(const char * command, bool read_only, int count)
***************
*** 283,290 ****
Description
! SPI_exec executes the specified SQL command
! for count rows.
--- 299,308 ----
Description
! SPI_execute executes the specified SQL command
! for count rows. If read_only
! is true>, the command must be read-only, and execution overhead
! is somewhat reduced.
***************
*** 295,317 ****
will be executed is restricted (much like a
LIMIT clause). For example,
! SPI_exec("INSERT INTO tab SELECT * FROM tab", 5);
will allow at most 5 rows to be inserted into the table.
You may pass multiple commands in one string, and the command may
! be rewritten by rules. SPI_exec returns the
result for the command executed last.
The actual number of rows for which the (last) command was executed
is returned in the global variable SPI_processed
(unless the return value of the function is
SPI_OK_UTILITY). If the return value of the
! function is SPI_OK_SELECT then you may the use
global pointer SPITupleTable *SPI_tuptable to
access the result rows.
--- 313,366 ----
will be executed is restricted (much like a
LIMIT clause). For example,
! SPI_execute("INSERT INTO tab SELECT * FROM tab", false, 5);
will allow at most 5 rows to be inserted into the table.
You may pass multiple commands in one string, and the command may
! be rewritten by rules. SPI_execute returns the
result for the command executed last.
+ When read_only is false>,
+ SPI_execute computes a new snapshot>
+ before executing each command in the string, and increments the command
+ counter afterwards. The snapshot does not actually change if the current
+ transaction isolation level is SERIALIZABLE>, but in
+ READ COMMITTED> mode the snapshot update allows each command to
+ see the results of newly committed transactions from other sessions.
+ This is essential for consistent behavior when the commands are modifying
+ the database.
+
+
+
+ When read_only is true>,
+ SPI_execute does not update either the snapshot
+ or the command counter, and it allows only plain SELECT>
+ commands to appear in the command string. The commands are executed
+ using the snapshot previously established for the surrounding query.
+ This execution mode is somewhat faster than the read/write mode due
+ to eliminating per-command overhead. It also allows genuinely
+ stable> functions to be built: since successive executions
+ will all use the same snapshot, there will be no change in the results.
+
+
+
+ It is generally unwise to mix read-only and read-write commands within
+ a single function using SPI; that could result in very confusing behavior,
+ since the read-only queries would not see the results of any database
+ updates done by the read-write queries.
+
+
+
The actual number of rows for which the (last) command was executed
is returned in the global variable SPI_processed
(unless the return value of the function is
SPI_OK_UTILITY). If the return value of the
! function is SPI_OK_SELECT then you may use the
global pointer SPITupleTable *SPI_tuptable to
access the result rows.
***************
*** 330,336 ****
} SPITupleTable;
vals> is an array of pointers to rows. (The number
! of valid entries is given by SPI_processed).
tupdesc> is a row descriptor which you may pass to
SPI functions dealing with rows. tuptabcxt>,
alloced>, and free> are internal
--- 379,385 ----
} SPITupleTable;
vals> is an array of pointers to rows. (The number
! of valid entries is given by SPI_processed.)
tupdesc> is a row descriptor which you may pass to
SPI functions dealing with rows. tuptabcxt>,
alloced>, and free> are internal
***************
*** 359,364 ****
--- 408,422 ----
+ bool read_only
+
+
+ true> for read-only execution
+
+
+
+
+
int count
***************
*** 504,517 ****
Notes
! The functions SPI_exec,
! SPI_execp, and
! SPI_prepare change both
SPI_processed and
SPI_tuptable (just the pointer, not the contents
of the structure). Save these two global variables into local
! procedure variables if you need to access the result of
! SPI_exec or SPI_execp
across later calls.
--- 562,576 ----
Notes
! The functions SPI_execute,
! SPI_exec,
! SPI_execute_plan, and
! SPI_execp change both
SPI_processed and
SPI_tuptable (just the pointer, not the contents
of the structure). Save these two global variables into local
! procedure variables if you need to access the result table of
! SPI_execute or a related function
across later calls.
***************
*** 519,524 ****
--- 578,647 ----
+
+
+ SPI_exec
+
+
+
+ SPI_exec
+ execute a read/write command
+
+
+ SPI_exec
+
+
+
+ int SPI_exec(const char * command, int count)
+
+
+
+
+ Description
+
+
+ SPI_exec is the same as
+ SPI_execute, with the latter's
+ read_only parameter always taken as
+ false>.
+
+
+
+
+ Arguments
+
+
+
+ const char * command
+
+
+ string containing command to execute
+
+
+
+
+
+ int count
+
+
+ maximum number of rows to process or return
+
+
+
+
+
+
+
+ Return Value
+
+
+ See SPI_execute.
+
+
+
+
+
+
SPI_prepare
***************
*** 551,564 ****
may be advantageous to perform the planning only once.
SPI_prepare converts a command string into an
execution plan that can be executed repeatedly using
! SPI_execp.
A prepared command can be generalized by writing parameters
($1>, $2>, etc.) in place of what would be
constants in a normal command. The actual values of the parameters
! are then specified when SPI_execp is called.
This allows the prepared command to be used over a wider range of
situations than would be possible without parameters.
--- 674,687 ----
may be advantageous to perform the planning only once.
SPI_prepare converts a command string into an
execution plan that can be executed repeatedly using
! SPI_execute_plan.
A prepared command can be generalized by writing parameters
($1>, $2>, etc.) in place of what would be
constants in a normal command. The actual values of the parameters
! are then specified when SPI_execute_plan is called.
This allows the prepared command to be used over a wider range of
situations than would be possible without parameters.
***************
*** 610,619 ****
Return Value
! SPI_prepare returns non-null pointer to an
! execution plan. On error, NULL will be returned.
! In both cases, SPI_result will be set analogous
! to the value returned by SPI_exec, except that
it is set to SPI_ERROR_ARGUMENT if
command is NULL, or if
nargs> is less than 0, or if nargs> is
--- 733,742 ----
Return Value
! SPI_prepare returns a non-null pointer to an
! execution plan. On error, NULL will be returned,
! and SPI_result will be set to one of the same
! error codes used by SPI_execute, except that
it is set to SPI_ERROR_ARGUMENT if
command is NULL, or if
nargs> is less than 0, or if nargs> is
***************
*** 697,704 ****
SPI_getargtypeid
! returns the expected typeid for the specified argument when
! executing a plan prepared by SPI_prepare
SPI_getargtypeid
--- 820,827 ----
SPI_getargtypeid
! returns the expected typeid for the specified argument of
! a plan prepared by SPI_prepare
SPI_getargtypeid
***************
*** 765,772 ****
SPI_is_cursor_plan
returns true if a plan
! prepared by SPI_prepare can be passed
! as an argument to SPI_cursor_open
SPI_is_cursor_plan
--- 888,895 ----
SPI_is_cursor_plan
returns true if a plan
! prepared by SPI_prepare can be used with
! SPI_cursor_open
SPI_is_cursor_plan
***************
*** 784,790 ****
SPI_is_cursor_plan returns true
if a plan prepared by SPI_prepare can be passed
as an argument to SPI_cursor_open and
! false if that is not the case. The criteria is that the
plan represents one single command and that this
command is a SELECT without an INTO
clause.
--- 907,913 ----
SPI_is_cursor_plan returns true
if a plan prepared by SPI_prepare can be passed
as an argument to SPI_cursor_open and
! false if that is not the case. The criteria are that the
plan represents one single command and that this
command is a SELECT without an INTO
clause.
***************
*** 819,839 ****
!
! SPI_execp
! SPI_execp
executes a plan prepared by SPI_prepare
! SPI_execp
! int SPI_execp(void * plan, Datum * values, const char * nulls, int count)
--- 942,962 ----
!
! SPI_execute_plan
! SPI_execute_plan
executes a plan prepared by SPI_prepare
! SPI_execute_plan
! int SPI_execute_plan(void * plan, Datum * values, const char * nulls, bool read_only, int count)
***************
*** 841,849 ****
Description
! SPI_execp executes a plan prepared by
! SPI_prepare. tcount
! has the same interpretation as in SPI_exec.
--- 964,973 ----
Description
! SPI_execute_plan executes a plan prepared by
! SPI_prepare. read_only and
! count have the same interpretation as in
! SPI_execute.
***************
*** 864,870 ****
Datum *values
! actual parameter values
--- 988,995 ----
Datum *values
! An array of actual parameter values. Must have same length as the
! plan's number of arguments.
***************
*** 873,879 ****
const char * nulls
! An array describing which parameters are null.
n indicates a null value (entry in
values> will be ignored); a space indicates a
nonnull value (entry in values> is valid).
--- 998,1005 ----
const char * nulls
! An array describing which parameters are null. Must have same length as
! the plan's number of arguments.
n indicates a null value (entry in
values> will be ignored); a space indicates a
nonnull value (entry in values> is valid).
***************
*** 881,897 ****
If nulls is NULL then
! SPI_execp assumes that no parameters are
null.
int count
! number of row for which plan is to be executed
--- 1007,1032 ----
If nulls is NULL then
! SPI_execute_plan assumes that no parameters are
null.
+ bool read_only
+
+
+ true> for read-only execution
+
+
+
+
+
int count
! maximum number of rows to process or return
***************
*** 902,909 ****
Return Value
! The return value is the same as for SPI_exec
! or one of the following:
--- 1037,1044 ----
Return Value
! The return value is the same as for SPI_execute,
! with the following additional possible error (negative) results:
***************
*** 931,937 ****
SPI_processed and
SPI_tuptable are set as in
! SPI_exec if successful.
--- 1066,1072 ----
SPI_processed and
SPI_tuptable are set as in
! SPI_execute if successful.
***************
*** 941,947 ****
If one of the objects (a table, function, etc.) referenced by the
prepared plan is dropped during the session then the result of
! SPI_execp for this plan will be unpredictable.
--- 1076,1181 ----
If one of the objects (a table, function, etc.) referenced by the
prepared plan is dropped during the session then the result of
! SPI_execute_plan for this plan will be unpredictable.
!
!
!
!
!
!
!
!
! SPI_execp
!
!
!
! SPI_execp
! executes a plan in read/write mode
!
!
! SPI_execp
!
!
!
! int SPI_execp(void * plan, Datum * values, const char * nulls, int count)
!
!
!
!
! Description
!
!
! SPI_execp is the same as
! SPI_execute_plan, with the latter's
! read_only parameter always taken as
! false>.
!
!
!
!
! Arguments
!
!
!
! void * plan
!
!
! execution plan (returned by SPI_prepare)
!
!
!
!
!
! Datum *values
!
!
! An array of actual parameter values. Must have same length as the
! plan's number of arguments.
!
!
!
!
!
! const char * nulls
!
!
! An array describing which parameters are null. Must have same length as
! the plan's number of arguments.
! n indicates a null value (entry in
! values> will be ignored); a space indicates a
! nonnull value (entry in values> is valid).
!
!
!
! If nulls is NULL then
! SPI_execp assumes that no parameters are
! null.
!
!
!
!
!
! int count
!
!
! maximum number of rows to process or return
!
!
!
!
!
!
!
! Return Value
!
!
! See SPI_execute_plan.
!
!
!
! SPI_processed and
! SPI_tuptable are set as in
! SPI_execute if successful.
***************
*** 1168,1174 ****
SPI_processed and
SPI_tuptable are set as in
! SPI_exec if successful.
--- 1402,1408 ----
SPI_processed and
SPI_tuptable are set as in
! SPI_execute if successful.
***************
*** 1320,1326 ****
your procedure in the current session. You may save the pointer
returned in a local variable. Always check if this pointer is
NULL or not either when preparing a plan or using
! an already prepared plan in SPI_execp.
--- 1554,1560 ----
your procedure in the current session. You may save the pointer
returned in a local variable. Always check if this pointer is
NULL or not either when preparing a plan or using
! an already prepared plan in SPI_execute_plan.
***************
*** 1374,1380 ****
If one of the objects (a table, function, etc.) referenced by the
prepared plan is dropped during the session then the results of
! SPI_execp for this plan will be unpredictable.
--- 1608,1614 ----
If one of the objects (a table, function, etc.) referenced by the
prepared plan is dropped during the session then the results of
! SPI_execute_plan for this plan will be unpredictable.
***************
*** 1386,1392 ****
The functions described here provide an interface for extracting
! information from result sets returned by SPI_exec> and
other SPI functions.
--- 1620,1626 ----
The functions described here provide an interface for extracting
! information from result sets returned by SPI_execute> and
other SPI functions.
***************
*** 2360,2366 ****
const char * Nulls
! which new values are null, if any (see SPI_execp for the format)
--- 2594,2601 ----
const char * Nulls
! which new values are null, if any (see
! SPI_execute_plan for the format)
***************
*** 2466,2472 ****
SPI_freetuptable
! free a row set created by SPI_exec> or a similar function
SPI_freetuptable
--- 2701,2708 ----
SPI_freetuptable
! free a row set created by SPI_execute> or a similar
! function
SPI_freetuptable
***************
*** 2483,2489 ****
SPI_freetuptable frees a row set created by a
prior SPI command execution function, such as
! SPI_exec>. Therefore, this function is usually called
with the global variable SPI_tupletable as
argument.
--- 2719,2725 ----
SPI_freetuptable frees a row set created by a
prior SPI command execution function, such as
! SPI_execute>. Therefore, this function is usually called
with the global variable SPI_tupletable as
argument.
*** src/backend/access/transam/xact.c.orig Fri Sep 10 14:39:55 2004
--- src/backend/access/transam/xact.c Sat Sep 11 19:00:37 2004
***************
*** 401,411 ****
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("cannot have more than 2^32-1 commands in a transaction")));
! /* Propagate new command ID into query snapshots, if set */
! if (QuerySnapshot)
! QuerySnapshot->curcid = s->commandId;
if (SerializableSnapshot)
SerializableSnapshot->curcid = s->commandId;
/*
* make cache changes visible to me.
--- 401,411 ----
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("cannot have more than 2^32-1 commands in a transaction")));
! /* Propagate new command ID into static snapshots, if set */
if (SerializableSnapshot)
SerializableSnapshot->curcid = s->commandId;
+ if (LatestSnapshot)
+ LatestSnapshot->curcid = s->commandId;
/*
* make cache changes visible to me.
*** src/backend/commands/cluster.c.orig Sun Aug 29 09:54:55 2004
--- src/backend/commands/cluster.c Sat Sep 11 19:00:26 2004
***************
*** 202,209 ****
/* Start a new transaction for each relation. */
StartTransactionCommand();
! SetQuerySnapshot(); /* might be needed for functions in
! * indexes */
cluster_rel(rvtc, true);
CommitTransactionCommand();
}
--- 202,209 ----
/* Start a new transaction for each relation. */
StartTransactionCommand();
! /* functions in indexes may want a snapshot set */
! ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
cluster_rel(rvtc, true);
CommitTransactionCommand();
}
*** src/backend/commands/copy.c.orig Fri Sep 10 14:39:56 2004
--- src/backend/commands/copy.c Sat Sep 11 19:10:35 2004
***************
*** 1182,1188 ****
Oid *typioparams;
bool *isvarlena;
char *string;
- Snapshot mySnapshot;
ListCell *cur;
MemoryContext oldcontext;
MemoryContext mycontext;
--- 1182,1187 ----
***************
*** 1260,1268 ****
strlen(null_print));
}
! mySnapshot = CopyQuerySnapshot();
!
! scandesc = heap_beginscan(rel, mySnapshot, 0, NULL);
while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
{
--- 1259,1265 ----
strlen(null_print));
}
! scandesc = heap_beginscan(rel, ActiveSnapshot, 0, NULL);
while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
{
*** src/backend/commands/explain.c.orig Fri Sep 10 14:39:56 2004
--- src/backend/commands/explain.c Sat Sep 11 21:07:54 2004
***************
*** 180,186 ****
plan = planner(query, isCursor, cursorOptions, NULL);
/* Create a QueryDesc requesting no output */
! queryDesc = CreateQueryDesc(query, plan, None_Receiver, NULL,
stmt->analyze);
ExplainOnePlan(queryDesc, stmt, tstate);
--- 180,188 ----
plan = planner(query, isCursor, cursorOptions, NULL);
/* Create a QueryDesc requesting no output */
! queryDesc = CreateQueryDesc(query, plan,
! ActiveSnapshot, InvalidSnapshot,
! None_Receiver, NULL,
stmt->analyze);
ExplainOnePlan(queryDesc, stmt, tstate);
***************
*** 212,218 ****
AfterTriggerBeginQuery();
/* call ExecutorStart to prepare the plan for execution */
! ExecutorStart(queryDesc, false, !stmt->analyze);
/* Execute the plan for statistics if asked for */
if (stmt->analyze)
--- 214,220 ----
AfterTriggerBeginQuery();
/* call ExecutorStart to prepare the plan for execution */
! ExecutorStart(queryDesc, !stmt->analyze);
/* Execute the plan for statistics if asked for */
if (stmt->analyze)
*** src/backend/commands/indexcmds.c.orig Sun Aug 29 09:54:55 2004
--- src/backend/commands/indexcmds.c Sat Sep 11 19:00:27 2004
***************
*** 1060,1067 ****
Oid relid = lfirst_oid(l);
StartTransactionCommand();
! SetQuerySnapshot(); /* might be needed for functions in
! * indexes */
if (reindex_relation(relid, true))
ereport(NOTICE,
(errmsg("table \"%s\" was reindexed",
--- 1060,1067 ----
Oid relid = lfirst_oid(l);
StartTransactionCommand();
! /* functions in indexes may want a snapshot set */
! ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
if (reindex_relation(relid, true))
ereport(NOTICE,
(errmsg("table \"%s\" was reindexed",
*** src/backend/commands/portalcmds.c.orig Fri Sep 10 14:39:56 2004
--- src/backend/commands/portalcmds.c Sat Sep 11 21:06:19 2004
***************
*** 135,141 ****
/*
* Start execution, inserting parameters if any.
*/
! PortalStart(portal, params);
Assert(portal->strategy == PORTAL_ONE_SELECT);
--- 135,141 ----
/*
* Start execution, inserting parameters if any.
*/
! PortalStart(portal, params, ActiveSnapshot);
Assert(portal->strategy == PORTAL_ONE_SELECT);
*** src/backend/commands/prepare.c.orig Sun Aug 29 09:54:56 2004
--- src/backend/commands/prepare.c Sat Sep 11 21:06:58 2004
***************
*** 186,192 ****
/*
* Run the portal to completion.
*/
! PortalStart(portal, paramLI);
(void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag);
--- 186,192 ----
/*
* Run the portal to completion.
*/
! PortalStart(portal, paramLI, ActiveSnapshot);
(void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag);
***************
*** 544,550 ****
}
/* Create a QueryDesc requesting no output */
! qdesc = CreateQueryDesc(query, plan, None_Receiver,
paramLI, stmt->analyze);
ExplainOnePlan(qdesc, stmt, tstate);
--- 544,552 ----
}
/* Create a QueryDesc requesting no output */
! qdesc = CreateQueryDesc(query, plan,
! ActiveSnapshot, InvalidSnapshot,
! None_Receiver,
paramLI, stmt->analyze);
ExplainOnePlan(qdesc, stmt, tstate);
*** src/backend/commands/trigger.c.orig Fri Sep 10 14:39:56 2004
--- src/backend/commands/trigger.c Sat Sep 11 23:27:51 2004
***************
*** 2094,2099 ****
--- 2094,2108 ----
TriggerDesc *trigdesc = NULL;
FmgrInfo *finfo = NULL;
+ /*
+ * Advance the command counter to be sure that the AFTER triggers
+ * can see the results of the just-completed command. Notice that
+ * we do not CCI between triggers though; it's expected that any
+ * read/write trigger will do a CCI after its update (at least if
+ * it thinks other triggers should be able to see the update).
+ */
+ CommandCounterIncrement();
+
/* Make a per-tuple memory context for trigger function calls */
per_tuple_context =
AllocSetContextCreate(CurrentMemoryContext,
*** src/backend/commands/vacuum.c.orig Sun Aug 29 22:57:30 2004
--- src/backend/commands/vacuum.c Sat Sep 11 19:00:29 2004
***************
*** 402,409 ****
if (use_own_xacts)
{
StartTransactionCommand();
! SetQuerySnapshot(); /* might be needed for functions
! * in indexes */
}
else
old_context = MemoryContextSwitchTo(anl_context);
--- 402,409 ----
if (use_own_xacts)
{
StartTransactionCommand();
! /* functions in indexes may want a snapshot set */
! ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
}
else
old_context = MemoryContextSwitchTo(anl_context);
***************
*** 865,872 ****
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
! SetQuerySnapshot(); /* might be needed for functions in
! * indexes */
/*
* Tell the cache replacement strategy that vacuum is causing all
--- 865,872 ----
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
! /* functions in indexes may want a snapshot set */
! ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
/*
* Tell the cache replacement strategy that vacuum is causing all
*** src/backend/executor/execMain.c.orig Sat Sep 11 14:28:34 2004
--- src/backend/executor/execMain.c Sat Sep 11 19:19:08 2004
***************
*** 106,120 ****
* field of the QueryDesc is filled in to describe the tuples that will be
* returned, and the internal fields (estate and planstate) are set up.
*
- * If useCurrentSnapshot is true, run the query with the latest available
- * snapshot, instead of the normal QuerySnapshot. Also, if it's an update
- * or delete query, check that the rows to be updated or deleted would be
- * visible to the normal QuerySnapshot. (This is a special-case behavior
- * needed for referential integrity updates in serializable transactions.
- * We must check all currently-committed rows, but we want to throw a
- * can't-serialize error if any rows that would need updates would not be
- * visible under the normal serializable snapshot.)
- *
* If explainOnly is true, we are not actually intending to run the plan,
* only to set up for EXPLAIN; so skip unwanted side-effects.
*
--- 106,111 ----
***************
*** 123,129 ****
* ----------------------------------------------------------------
*/
void
! ExecutorStart(QueryDesc *queryDesc, bool useCurrentSnapshot, bool explainOnly)
{
EState *estate;
MemoryContext oldcontext;
--- 114,120 ----
* ----------------------------------------------------------------
*/
void
! ExecutorStart(QueryDesc *queryDesc, bool explainOnly)
{
EState *estate;
MemoryContext oldcontext;
***************
*** 156,183 ****
estate->es_param_exec_vals = (ParamExecData *)
palloc0(queryDesc->plantree->nParamExec * sizeof(ParamExecData));
- estate->es_instrument = queryDesc->doInstrument;
-
/*
! * Make our own private copy of the current query snapshot data.
! *
! * This "freezes" our idea of which tuples are good and which are not for
! * the life of this query, even if it outlives the current command and
! * current snapshot.
! */
! if (useCurrentSnapshot)
! {
! /* RI update/delete query --- must use an up-to-date snapshot */
! estate->es_snapshot = CopyCurrentSnapshot();
! /* crosscheck updates/deletes against transaction snapshot */
! estate->es_crosscheck_snapshot = CopyQuerySnapshot();
! }
! else
! {
! /* normal query --- use query snapshot, no crosscheck */
! estate->es_snapshot = CopyQuerySnapshot();
! estate->es_crosscheck_snapshot = InvalidSnapshot;
! }
/*
* Initialize the plan state tree
--- 147,158 ----
estate->es_param_exec_vals = (ParamExecData *)
palloc0(queryDesc->plantree->nParamExec * sizeof(ParamExecData));
/*
! * Copy other important information into the EState
! */
! estate->es_snapshot = queryDesc->snapshot;
! estate->es_crosscheck_snapshot = queryDesc->crosscheck_snapshot;
! estate->es_instrument = queryDesc->doInstrument;
/*
* Initialize the plan state tree
***************
*** 1454,1459 ****
--- 1429,1439 ----
/*
* delete the tuple
+ *
+ * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+ * the row to be deleted is visible to that snapshot, and throw a can't-
+ * serialize error if not. This is a special-case behavior needed for
+ * referential integrity updates in serializable transactions.
*/
ldelete:;
result = heap_delete(resultRelationDesc, tupleid,
***************
*** 1591,1596 ****
--- 1571,1581 ----
/*
* replace the heap tuple
+ *
+ * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+ * the row to be updated is visible to that snapshot, and throw a can't-
+ * serialize error if not. This is a special-case behavior needed for
+ * referential integrity updates in serializable transactions.
*/
result = heap_update(resultRelationDesc, tupleid, tuple,
&ctid,
*** src/backend/executor/functions.c.orig Fri Sep 10 14:39:57 2004
--- src/backend/executor/functions.c Sat Sep 11 23:28:08 2004
***************
*** 65,70 ****
--- 65,71 ----
bool typbyval; /* true if return type is pass by value */
bool returnsTuple; /* true if returning whole tuple result */
bool shutdown_reg; /* true if registered shutdown callback */
+ bool readonly_func; /* true to run in "read only" mode */
ParamListInfo paramLI; /* Param list representing current args */
***************
*** 76,86 ****
/* non-export function prototypes */
! static execution_state *init_execution_state(List *queryTree_list);
static void init_sql_fcache(FmgrInfo *finfo);
static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache);
static TupleTableSlot *postquel_getnext(execution_state *es);
! static void postquel_end(execution_state *es);
static void postquel_sub_params(SQLFunctionCachePtr fcache,
FunctionCallInfo fcinfo);
static Datum postquel_execute(execution_state *es,
--- 77,88 ----
/* non-export function prototypes */
! static execution_state *init_execution_state(List *queryTree_list,
! bool readonly_func);
static void init_sql_fcache(FmgrInfo *finfo);
static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache);
static TupleTableSlot *postquel_getnext(execution_state *es);
! static void postquel_end(execution_state *es, SQLFunctionCachePtr fcache);
static void postquel_sub_params(SQLFunctionCachePtr fcache,
FunctionCallInfo fcinfo);
static Datum postquel_execute(execution_state *es,
***************
*** 91,97 ****
static execution_state *
! init_execution_state(List *queryTree_list)
{
execution_state *firstes = NULL;
execution_state *preves = NULL;
--- 93,99 ----
static execution_state *
! init_execution_state(List *queryTree_list, bool readonly_func)
{
execution_state *firstes = NULL;
execution_state *preves = NULL;
***************
*** 103,108 ****
--- 105,126 ----
Plan *planTree;
execution_state *newes;
+ /* Precheck all commands for validity in a function */
+ if (queryTree->commandType == CMD_UTILITY &&
+ IsA(queryTree->utilityStmt, TransactionStmt))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is a SQL statement name */
+ errmsg("%s is not allowed in a SQL function",
+ CreateQueryTag(queryTree))));
+
+ if (readonly_func && !QueryIsReadOnly(queryTree))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is a SQL statement name */
+ errmsg("%s is not allowed in a non-volatile function",
+ CreateQueryTag(queryTree))));
+
planTree = pg_plan_query(queryTree, NULL);
newes = (execution_state *) palloc(sizeof(execution_state));
***************
*** 172,177 ****
--- 190,199 ----
fcache->rettype = rettype;
+ /* Remember if function is STABLE/IMMUTABLE */
+ fcache->readonly_func =
+ (procedureStruct->provolatile != PROVOLATILE_VOLATILE);
+
/* Now look up the actual result type */
typeTuple = SearchSysCache(TYPEOID,
ObjectIdGetDatum(rettype),
***************
*** 253,259 ****
queryTree_list);
/* Finally, plan the queries */
! fcache->func_state = init_execution_state(queryTree_list);
pfree(src);
--- 275,282 ----
queryTree_list);
/* Finally, plan the queries */
! fcache->func_state = init_execution_state(queryTree_list,
! fcache->readonly_func);
pfree(src);
***************
*** 267,282 ****
static void
postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
{
Assert(es->qd == NULL);
es->qd = CreateQueryDesc(es->query, es->plan,
None_Receiver,
fcache->paramLI, false);
/* Utility commands don't need Executor. */
if (es->qd->operation != CMD_UTILITY)
{
AfterTriggerBeginQuery();
! ExecutorStart(es->qd, false, false);
}
es->status = F_EXEC_RUN;
--- 290,321 ----
static void
postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
{
+ Snapshot snapshot;
+
Assert(es->qd == NULL);
+
+ /*
+ * In a read-only function, use the surrounding query's snapshot;
+ * otherwise take a new snapshot for each query. We copy always
+ * so that postquel_end can unconditionally do FreeSnapshot.
+ */
+ if (fcache->readonly_func)
+ snapshot = CopySnapshot(ActiveSnapshot);
+ else
+ snapshot = CopySnapshot(GetTransactionSnapshot());
+
es->qd = CreateQueryDesc(es->query, es->plan,
+ snapshot, InvalidSnapshot,
None_Receiver,
fcache->paramLI, false);
+ /* We assume we don't need to set up ActiveSnapshot for ExecutorStart */
+
/* Utility commands don't need Executor. */
if (es->qd->operation != CMD_UTILITY)
{
AfterTriggerBeginQuery();
! ExecutorStart(es->qd, false);
}
es->status = F_EXEC_RUN;
***************
*** 285,332 ****
static TupleTableSlot *
postquel_getnext(execution_state *es)
{
long count;
! if (es->qd->operation == CMD_UTILITY)
{
! /* Can't handle starting or committing a transaction */
! if (IsA(es->qd->parsetree->utilityStmt, TransactionStmt))
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot begin/end transactions in SQL functions")));
! ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->params,
! es->qd->dest, NULL);
! return NULL;
}
! /*
! * If it's the function's last command, and it's a SELECT, fetch one
! * row at a time so we can return the results. Otherwise just run it
! * to completion.
! */
! if (LAST_POSTQUEL_COMMAND(es) && es->qd->operation == CMD_SELECT)
! count = 1L;
! else
! count = 0L;
! return ExecutorRun(es->qd, ForwardScanDirection, count);
}
static void
! postquel_end(execution_state *es)
{
/* mark status done to ensure we don't do ExecutorEnd twice */
es->status = F_EXEC_DONE;
/* Utility commands don't need Executor. */
if (es->qd->operation != CMD_UTILITY)
{
! ExecutorEnd(es->qd);
! AfterTriggerEndQuery();
}
FreeQueryDesc(es->qd);
es->qd = NULL;
}
/* Build ParamListInfo array representing current arguments */
--- 324,414 ----
static TupleTableSlot *
postquel_getnext(execution_state *es)
{
+ TupleTableSlot *result;
+ Snapshot saveActiveSnapshot;
long count;
! /* Make our snapshot the active one for any called functions */
! saveActiveSnapshot = ActiveSnapshot;
! PG_TRY();
{
! ActiveSnapshot = es->qd->snapshot;
!
! if (es->qd->operation == CMD_UTILITY)
! {
! ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->params,
! es->qd->dest, NULL);
! result = NULL;
! }
! else
! {
! /*
! * If it's the function's last command, and it's a SELECT, fetch
! * one row at a time so we can return the results. Otherwise just
! * run it to completion. (If we run to completion then
! * ExecutorRun is guaranteed to return NULL.)
! */
! if (LAST_POSTQUEL_COMMAND(es) && es->qd->operation == CMD_SELECT)
! count = 1L;
! else
! count = 0L;
!
! result = ExecutorRun(es->qd, ForwardScanDirection, count);
! }
}
+ PG_CATCH();
+ {
+ /* Restore global vars and propagate error */
+ ActiveSnapshot = saveActiveSnapshot;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
! ActiveSnapshot = saveActiveSnapshot;
! return result;
}
static void
! postquel_end(execution_state *es, SQLFunctionCachePtr fcache)
{
+ Snapshot saveActiveSnapshot;
+
/* mark status done to ensure we don't do ExecutorEnd twice */
es->status = F_EXEC_DONE;
/* Utility commands don't need Executor. */
if (es->qd->operation != CMD_UTILITY)
{
! /* Make our snapshot the active one for any called functions */
! saveActiveSnapshot = ActiveSnapshot;
! PG_TRY();
! {
! ActiveSnapshot = es->qd->snapshot;
!
! ExecutorEnd(es->qd);
! AfterTriggerEndQuery();
! }
! PG_CATCH();
! {
! /* Restore global vars and propagate error */
! ActiveSnapshot = saveActiveSnapshot;
! PG_RE_THROW();
! }
! PG_END_TRY();
! ActiveSnapshot = saveActiveSnapshot;
}
+ FreeSnapshot(es->qd->snapshot);
FreeQueryDesc(es->qd);
es->qd = NULL;
+
+ /*
+ * We must do CommandCounterIncrement after each completed operation,
+ * unless we are in a read-only function.
+ */
+ if (!fcache->readonly_func)
+ CommandCounterIncrement();
}
/* Build ParamListInfo array representing current arguments */
***************
*** 368,373 ****
--- 450,457 ----
SQLFunctionCachePtr fcache)
{
TupleTableSlot *slot;
+ HeapTuple tup;
+ TupleDesc tupDesc;
Datum value;
if (es->status == F_EXEC_START)
***************
*** 377,477 ****
if (TupIsNull(slot))
{
- postquel_end(es);
- fcinfo->isnull = true;
-
/*
! * If this isn't the last command for the function we have to
! * increment the command counter so that subsequent commands can
! * see changes made by previous ones.
*/
! if (!LAST_POSTQUEL_COMMAND(es))
! CommandCounterIncrement();
return (Datum) NULL;
}
! if (LAST_POSTQUEL_COMMAND(es))
{
/*
! * Set up to return the function value.
*/
! HeapTuple tup = slot->val;
! TupleDesc tupDesc = slot->ttc_tupleDescriptor;
!
! if (fcache->returnsTuple)
! {
! /*
! * We are returning the whole tuple, so copy it into current
! * execution context and make sure it is a valid Datum.
! *
! * XXX do we need to remove junk attrs from the result tuple?
! * Probably OK to leave them, as long as they are at the end.
! */
! HeapTupleHeader dtup;
! Oid dtuptype;
! int32 dtuptypmod;
!
! dtup = (HeapTupleHeader) palloc(tup->t_len);
! memcpy((char *) dtup, (char *) tup->t_data, tup->t_len);
! /*
! * Use the declared return type if it's not RECORD; else take
! * the type from the computed result, making sure a typmod has
! * been assigned.
! */
! if (fcache->rettype != RECORDOID)
! {
! /* function has a named composite return type */
! dtuptype = fcache->rettype;
! dtuptypmod = -1;
! }
! else
! {
! /* function is declared to return RECORD */
! if (tupDesc->tdtypeid == RECORDOID &&
! tupDesc->tdtypmod < 0)
! assign_record_type_typmod(tupDesc);
! dtuptype = tupDesc->tdtypeid;
! dtuptypmod = tupDesc->tdtypmod;
! }
!
! HeapTupleHeaderSetDatumLength(dtup, tup->t_len);
! HeapTupleHeaderSetTypeId(dtup, dtuptype);
! HeapTupleHeaderSetTypMod(dtup, dtuptypmod);
! value = PointerGetDatum(dtup);
! fcinfo->isnull = false;
}
else
{
! /*
! * Returning a scalar, which we have to extract from the first
! * column of the SELECT result, and then copy into current
! * execution context if needed.
! */
! value = heap_getattr(tup, 1, tupDesc, &(fcinfo->isnull));
!
! if (!fcinfo->isnull)
! value = datumCopy(value, fcache->typbyval, fcache->typlen);
}
/*
! * If this is a single valued function we have to end the function
! * execution now.
*/
! if (!fcinfo->flinfo->fn_retset)
! postquel_end(es);
! return value;
}
/*
! * If this isn't the last command for the function, we don't return
! * any results, but we have to increment the command counter so that
! * subsequent commands can see changes made by previous ones.
*/
! CommandCounterIncrement();
! return (Datum) NULL;
}
Datum
--- 461,552 ----
if (TupIsNull(slot))
{
/*
! * We fall out here for all cases except where we have obtained
! * a row from a function's final SELECT.
*/
! postquel_end(es, fcache);
! fcinfo->isnull = true;
return (Datum) NULL;
}
! /*
! * If we got a row from a command within the function it has to be
! * the final command. All others shouldn't be returning anything.
! */
! Assert(LAST_POSTQUEL_COMMAND(es));
!
! /*
! * Set up to return the function value.
! */
! tup = slot->val;
! tupDesc = slot->ttc_tupleDescriptor;
!
! if (fcache->returnsTuple)
{
/*
! * We are returning the whole tuple, so copy it into current
! * execution context and make sure it is a valid Datum.
! *
! * XXX do we need to remove junk attrs from the result tuple?
! * Probably OK to leave them, as long as they are at the end.
*/
! HeapTupleHeader dtup;
! Oid dtuptype;
! int32 dtuptypmod;
! dtup = (HeapTupleHeader) palloc(tup->t_len);
! memcpy((char *) dtup, (char *) tup->t_data, tup->t_len);
! /*
! * Use the declared return type if it's not RECORD; else take
! * the type from the computed result, making sure a typmod has
! * been assigned.
! */
! if (fcache->rettype != RECORDOID)
! {
! /* function has a named composite return type */
! dtuptype = fcache->rettype;
! dtuptypmod = -1;
}
else
{
! /* function is declared to return RECORD */
! if (tupDesc->tdtypeid == RECORDOID &&
! tupDesc->tdtypmod < 0)
! assign_record_type_typmod(tupDesc);
! dtuptype = tupDesc->tdtypeid;
! dtuptypmod = tupDesc->tdtypmod;
}
+ HeapTupleHeaderSetDatumLength(dtup, tup->t_len);
+ HeapTupleHeaderSetTypeId(dtup, dtuptype);
+ HeapTupleHeaderSetTypMod(dtup, dtuptypmod);
+
+ value = PointerGetDatum(dtup);
+ fcinfo->isnull = false;
+ }
+ else
+ {
/*
! * Returning a scalar, which we have to extract from the first
! * column of the SELECT result, and then copy into current
! * execution context if needed.
*/
! value = heap_getattr(tup, 1, tupDesc, &(fcinfo->isnull));
! if (!fcinfo->isnull)
! value = datumCopy(value, fcache->typbyval, fcache->typlen);
}
/*
! * If this is a single valued function we have to end the function
! * execution now.
*/
! if (!fcinfo->flinfo->fn_retset)
! postquel_end(es, fcache);
!
! return value;
}
Datum
***************
*** 726,732 ****
{
/* Shut down anything still running */
if (es->status == F_EXEC_RUN)
! postquel_end(es);
/* Reset states to START in case we're called again */
es->status = F_EXEC_START;
es = es->next;
--- 801,807 ----
{
/* Shut down anything still running */
if (es->status == F_EXEC_RUN)
! postquel_end(es, fcache);
/* Reset states to START in case we're called again */
es->status = F_EXEC_START;
es = es->next;
*** src/backend/executor/spi.c.orig Fri Sep 10 14:39:57 2004
--- src/backend/executor/spi.c Sun Sep 12 16:50:11 2004
***************
*** 34,46 ****
static int _SPI_connected = -1;
static int _SPI_curid = -1;
! static int _SPI_execute(const char *src, int tcount, _SPI_plan *plan);
! static int _SPI_pquery(QueryDesc *queryDesc, bool runit,
! bool useCurrentSnapshot, int tcount);
static int _SPI_execute_plan(_SPI_plan *plan,
! Datum *Values, const char *Nulls,
! bool useCurrentSnapshot, int tcount);
static void _SPI_error_callback(void *arg);
--- 34,47 ----
static int _SPI_connected = -1;
static int _SPI_curid = -1;
! static void _SPI_prepare_plan(const char *src, _SPI_plan *plan);
static int _SPI_execute_plan(_SPI_plan *plan,
! Datum *Values, const char *Nulls,
! Snapshot snapshot, Snapshot crosscheck_snapshot,
! bool read_only, int tcount);
!
! static int _SPI_pquery(QueryDesc *queryDesc, int tcount);
static void _SPI_error_callback(void *arg);
***************
*** 252,260 ****
_SPI_curid--;
}
int
! SPI_exec(const char *src, int tcount)
{
int res;
if (src == NULL || tcount < 0)
--- 253,263 ----
_SPI_curid--;
}
+ /* Parse, plan, and execute a querystring */
int
! SPI_execute(const char *src, bool read_only, int tcount)
{
+ _SPI_plan plan;
int res;
if (src == NULL || tcount < 0)
***************
*** 264,277 ****
if (res < 0)
return res;
! res = _SPI_execute(src, tcount, NULL);
_SPI_end_call(true);
return res;
}
int
! SPI_execp(void *plan, Datum *Values, const char *Nulls, int tcount)
{
int res;
--- 267,298 ----
if (res < 0)
return res;
! plan.plancxt = NULL; /* doesn't have own context */
! plan.query = src;
! plan.nargs = 0;
! plan.argtypes = NULL;
!
! _SPI_prepare_plan(src, &plan);
!
! res = _SPI_execute_plan(&plan, NULL, NULL,
! InvalidSnapshot, InvalidSnapshot,
! read_only, tcount);
_SPI_end_call(true);
return res;
}
+ /* Obsolete version of SPI_execute */
int
! SPI_exec(const char *src, int tcount)
! {
! return SPI_execute(src, false, tcount);
! }
!
! /* Execute a previously prepared plan */
! int
! SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
! bool read_only, int tcount)
{
int res;
***************
*** 285,305 ****
if (res < 0)
return res;
! res = _SPI_execute_plan((_SPI_plan *) plan, Values, Nulls, false, tcount);
_SPI_end_call(true);
return res;
}
/*
! * SPI_execp_current -- identical to SPI_execp, except that we expose the
! * Executor option to use a current snapshot instead of the normal
! * QuerySnapshot. This is currently not documented in spi.sgml because
! * it is only intended for use by RI triggers.
*/
! int
! SPI_execp_current(void *plan, Datum *Values, const char *Nulls,
! bool useCurrentSnapshot, int tcount)
{
int res;
--- 306,341 ----
if (res < 0)
return res;
! res = _SPI_execute_plan((_SPI_plan *) plan,
! Values, Nulls,
! InvalidSnapshot, InvalidSnapshot,
! read_only, tcount);
_SPI_end_call(true);
return res;
}
+ /* Obsolete version of SPI_execute_plan */
+ int
+ SPI_execp(void *plan, Datum *Values, const char *Nulls, int tcount)
+ {
+ return SPI_execute_plan(plan, Values, Nulls, false, tcount);
+ }
+
/*
! * SPI_execute_snapshot -- identical to SPI_execute_plan, except that we allow
! * the caller to specify exactly which snapshots to use. This is currently
! * not documented in spi.sgml because it is only intended for use by RI
! * triggers.
! *
! * Passing snapshot == InvalidSnapshot will select the normal behavior of
! * fetching a new snapshot for each query.
*/
! extern int
! SPI_execute_snapshot(void *plan,
! Datum *Values, const char *Nulls,
! Snapshot snapshot, Snapshot crosscheck_snapshot,
! bool read_only, int tcount)
{
int res;
***************
*** 313,320 ****
if (res < 0)
return res;
! res = _SPI_execute_plan((_SPI_plan *) plan, Values, Nulls,
! useCurrentSnapshot, tcount);
_SPI_end_call(true);
return res;
--- 349,358 ----
if (res < 0)
return res;
! res = _SPI_execute_plan((_SPI_plan *) plan,
! Values, Nulls,
! snapshot, crosscheck_snapshot,
! read_only, tcount);
_SPI_end_call(true);
return res;
***************
*** 341,352 ****
plan.nargs = nargs;
plan.argtypes = argtypes;
! SPI_result = _SPI_execute(src, 0, &plan);
! if (SPI_result >= 0) /* copy plan to procedure context */
! result = _SPI_copy_plan(&plan, _SPI_CPLAN_PROCXT);
! else
! result = NULL;
_SPI_end_call(true);
--- 379,388 ----
plan.nargs = nargs;
plan.argtypes = argtypes;
! _SPI_prepare_plan(src, &plan);
! /* copy plan to procedure context */
! result = _SPI_copy_plan(&plan, _SPI_CPLAN_PROCXT);
_SPI_end_call(true);
***************
*** 785,793 ****
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("cannot open SELECT INTO query as cursor")));
- /* Increment CommandCounter to see changes made by now */
- CommandCounterIncrement();
-
/* Reset SPI result */
SPI_processed = 0;
SPI_tuptable = NULL;
--- 821,826 ----
***************
*** 869,875 ****
/*
* Start portal execution.
*/
! PortalStart(portal, paramLI);
Assert(portal->strategy == PORTAL_ONE_SELECT);
--- 902,908 ----
/*
* Start portal execution.
*/
! PortalStart(portal, paramLI, InvalidSnapshot);
Assert(portal->strategy == PORTAL_ONE_SELECT);
***************
*** 1143,1179 ****
*/
/*
! * Plan and optionally execute a querystring.
*
! * If plan != NULL, just prepare plan trees and save them in *plan;
! * else execute immediately.
*/
! static int
! _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
{
List *raw_parsetree_list;
List *query_list_list;
List *plan_list;
ListCell *list_item;
ErrorContextCallback spierrcontext;
! int nargs = 0;
! Oid *argtypes = NULL;
! int res = 0;
!
! if (plan)
! {
! nargs = plan->nargs;
! argtypes = plan->argtypes;
! }
!
! /* Increment CommandCounter to see changes made by now */
! CommandCounterIncrement();
!
! /* Reset state (only needed in case string is empty) */
! SPI_processed = 0;
! SPI_lastoid = InvalidOid;
! SPI_tuptable = NULL;
! _SPI_current->tuptable = NULL;
/*
* Setup error traceback support for ereport()
--- 1176,1197 ----
*/
/*
! * Parse and plan a querystring.
*
! * At entry, plan->argtypes and plan->nargs must be valid.
! *
! * Query and plan lists are stored into *plan.
*/
! static void
! _SPI_prepare_plan(const char *src, _SPI_plan *plan)
{
List *raw_parsetree_list;
List *query_list_list;
List *plan_list;
ListCell *list_item;
ErrorContextCallback spierrcontext;
! Oid *argtypes = plan->argtypes;
! int nargs = plan->nargs;
/*
* Setup error traceback support for ereport()
***************
*** 1191,1199 ****
/*
* Do parse analysis and rule rewrite for each raw parsetree.
*
! * We save the querytrees from each raw parsetree as a separate sublist.
! * This allows _SPI_execute_plan() to know where the boundaries
! * between original queries fall.
*/
query_list_list = NIL;
plan_list = NIL;
--- 1209,1217 ----
/*
* Do parse analysis and rule rewrite for each raw parsetree.
*
! * We save the querytrees from each raw parsetree as a separate
! * sublist. This allows _SPI_execute_plan() to know where the
! * boundaries between original queries fall.
*/
query_list_list = NIL;
plan_list = NIL;
***************
*** 1202,1404 ****
{
Node *parsetree = (Node *) lfirst(list_item);
List *query_list;
- ListCell *query_list_item;
query_list = pg_analyze_and_rewrite(parsetree, argtypes, nargs);
query_list_list = lappend(query_list_list, query_list);
! /* Reset state for each original parsetree */
! /* (at most one of its querytrees will be marked canSetTag) */
! SPI_processed = 0;
! SPI_lastoid = InvalidOid;
! SPI_tuptable = NULL;
! _SPI_current->tuptable = NULL;
!
! foreach(query_list_item, query_list)
! {
! Query *queryTree = (Query *) lfirst(query_list_item);
! Plan *planTree;
! QueryDesc *qdesc;
! DestReceiver *dest;
!
! planTree = pg_plan_query(queryTree, NULL);
! plan_list = lappend(plan_list, planTree);
!
! dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL);
! if (queryTree->commandType == CMD_UTILITY)
! {
! if (IsA(queryTree->utilityStmt, CopyStmt))
! {
! CopyStmt *stmt = (CopyStmt *) queryTree->utilityStmt;
!
! if (stmt->filename == NULL)
! {
! res = SPI_ERROR_COPY;
! goto fail;
! }
! }
! else if (IsA(queryTree->utilityStmt, DeclareCursorStmt) ||
! IsA(queryTree->utilityStmt, ClosePortalStmt) ||
! IsA(queryTree->utilityStmt, FetchStmt))
! {
! res = SPI_ERROR_CURSOR;
! goto fail;
! }
! else if (IsA(queryTree->utilityStmt, TransactionStmt))
! {
! res = SPI_ERROR_TRANSACTION;
! goto fail;
! }
! res = SPI_OK_UTILITY;
! if (plan == NULL)
! {
! ProcessUtility(queryTree->utilityStmt, NULL, dest, NULL);
! CommandCounterIncrement();
! }
! }
! else if (plan == NULL)
! {
! qdesc = CreateQueryDesc(queryTree, planTree, dest,
! NULL, false);
! res = _SPI_pquery(qdesc, true, false,
! queryTree->canSetTag ? tcount : 0);
! if (res < 0)
! goto fail;
! CommandCounterIncrement();
! }
! else
! {
! qdesc = CreateQueryDesc(queryTree, planTree, dest,
! NULL, false);
! res = _SPI_pquery(qdesc, false, false, 0);
! if (res < 0)
! goto fail;
! }
! }
}
! if (plan)
! {
! plan->qtlist = query_list_list;
! plan->ptlist = plan_list;
! }
!
! fail:
/*
* Pop the error context stack
*/
error_context_stack = spierrcontext.previous;
-
- return res;
}
static int
_SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
! bool useCurrentSnapshot, int tcount)
{
! List *query_list_list = plan->qtlist;
! ListCell *plan_list_item = list_head(plan->ptlist);
! ListCell *query_list_list_item;
! ErrorContextCallback spierrcontext;
! int nargs = plan->nargs;
! int res = 0;
! ParamListInfo paramLI;
!
! /* Increment CommandCounter to see changes made by now */
! CommandCounterIncrement();
! /* Convert parameters to form wanted by executor */
! if (nargs > 0)
! {
! int k;
! paramLI = (ParamListInfo)
! palloc0((nargs + 1) * sizeof(ParamListInfoData));
!
! for (k = 0; k < nargs; k++)
{
! paramLI[k].kind = PARAM_NUM;
! paramLI[k].id = k + 1;
! paramLI[k].ptype = plan->argtypes[k];
! paramLI[k].isnull = (Nulls && Nulls[k] == 'n');
! paramLI[k].value = Values[k];
! }
! paramLI[k].kind = PARAM_INVALID;
! }
! else
! paramLI = NULL;
! /* Reset state (only needed in case string is empty) */
! SPI_processed = 0;
! SPI_lastoid = InvalidOid;
! SPI_tuptable = NULL;
! _SPI_current->tuptable = NULL;
!
! /*
! * Setup error traceback support for ereport()
! */
! spierrcontext.callback = _SPI_error_callback;
! spierrcontext.arg = (void *) plan->query;
! spierrcontext.previous = error_context_stack;
! error_context_stack = &spierrcontext;
! foreach(query_list_list_item, query_list_list)
! {
! List *query_list = lfirst(query_list_list_item);
! ListCell *query_list_item;
! /* Reset state for each original parsetree */
! /* (at most one of its querytrees will be marked canSetTag) */
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
_SPI_current->tuptable = NULL;
! foreach(query_list_item, query_list)
{
! Query *queryTree = (Query *) lfirst(query_list_item);
! Plan *planTree;
! QueryDesc *qdesc;
! DestReceiver *dest;
! planTree = lfirst(plan_list_item);
! plan_list_item = lnext(plan_list_item);
! dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL);
! if (queryTree->commandType == CMD_UTILITY)
{
! ProcessUtility(queryTree->utilityStmt, paramLI, dest, NULL);
! res = SPI_OK_UTILITY;
! CommandCounterIncrement();
! }
! else
! {
! qdesc = CreateQueryDesc(queryTree, planTree, dest,
! paramLI, false);
! res = _SPI_pquery(qdesc, true, useCurrentSnapshot,
! queryTree->canSetTag ? tcount : 0);
if (res < 0)
goto fail;
! CommandCounterIncrement();
}
}
- }
fail:
! /*
! * Pop the error context stack
! */
! error_context_stack = spierrcontext.previous;
return res;
}
static int
! _SPI_pquery(QueryDesc *queryDesc, bool runit,
! bool useCurrentSnapshot, int tcount)
{
int operation = queryDesc->operation;
int res;
--- 1220,1417 ----
{
Node *parsetree = (Node *) lfirst(list_item);
List *query_list;
query_list = pg_analyze_and_rewrite(parsetree, argtypes, nargs);
query_list_list = lappend(query_list_list, query_list);
! plan_list = list_concat(plan_list,
! pg_plan_queries(query_list, NULL, false));
}
! plan->qtlist = query_list_list;
! plan->ptlist = plan_list;
/*
* Pop the error context stack
*/
error_context_stack = spierrcontext.previous;
}
+ /*
+ * Execute the given plan with the given parameter values
+ *
+ * snapshot: query snapshot to use, or InvalidSnapshot for the normal
+ * behavior of taking a new snapshot for each query.
+ * crosscheck_snapshot: for RI use, all others pass InvalidSnapshot
+ * read_only: TRUE for read-only execution (no CommandCounterIncrement)
+ * tcount: execution tuple-count limit, or 0 for none
+ */
static int
_SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
! Snapshot snapshot, Snapshot crosscheck_snapshot,
! bool read_only, int tcount)
{
! volatile int res = 0;
! Snapshot saveActiveSnapshot;
! /* Be sure to restore ActiveSnapshot on error exit */
! saveActiveSnapshot = ActiveSnapshot;
! PG_TRY();
! {
! List *query_list_list = plan->qtlist;
! ListCell *plan_list_item = list_head(plan->ptlist);
! ListCell *query_list_list_item;
! ErrorContextCallback spierrcontext;
! int nargs = plan->nargs;
! ParamListInfo paramLI;
! /* Convert parameters to form wanted by executor */
! if (nargs > 0)
{
! int k;
! paramLI = (ParamListInfo)
! palloc0((nargs + 1) * sizeof(ParamListInfoData));
! for (k = 0; k < nargs; k++)
! {
! paramLI[k].kind = PARAM_NUM;
! paramLI[k].id = k + 1;
! paramLI[k].ptype = plan->argtypes[k];
! paramLI[k].isnull = (Nulls && Nulls[k] == 'n');
! paramLI[k].value = Values[k];
! }
! paramLI[k].kind = PARAM_INVALID;
! }
! else
! paramLI = NULL;
! /* Reset state (only needed in case string is empty) */
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
_SPI_current->tuptable = NULL;
! /*
! * Setup error traceback support for ereport()
! */
! spierrcontext.callback = _SPI_error_callback;
! spierrcontext.arg = (void *) plan->query;
! spierrcontext.previous = error_context_stack;
! error_context_stack = &spierrcontext;
!
! foreach(query_list_list_item, query_list_list)
{
! List *query_list = lfirst(query_list_list_item);
! ListCell *query_list_item;
! /* Reset state for each original parsetree */
! /* (at most one of its querytrees will be marked canSetTag) */
! SPI_processed = 0;
! SPI_lastoid = InvalidOid;
! SPI_tuptable = NULL;
! _SPI_current->tuptable = NULL;
! foreach(query_list_item, query_list)
{
! Query *queryTree = (Query *) lfirst(query_list_item);
! Plan *planTree;
! QueryDesc *qdesc;
! DestReceiver *dest;
!
! planTree = lfirst(plan_list_item);
! plan_list_item = lnext(plan_list_item);
!
! if (queryTree->commandType == CMD_UTILITY)
! {
! if (IsA(queryTree->utilityStmt, CopyStmt))
! {
! CopyStmt *stmt = (CopyStmt *) queryTree->utilityStmt;
!
! if (stmt->filename == NULL)
! {
! res = SPI_ERROR_COPY;
! goto fail;
! }
! }
! else if (IsA(queryTree->utilityStmt, DeclareCursorStmt) ||
! IsA(queryTree->utilityStmt, ClosePortalStmt) ||
! IsA(queryTree->utilityStmt, FetchStmt))
! {
! res = SPI_ERROR_CURSOR;
! goto fail;
! }
! else if (IsA(queryTree->utilityStmt, TransactionStmt))
! {
! res = SPI_ERROR_TRANSACTION;
! goto fail;
! }
! }
!
! if (read_only && !QueryIsReadOnly(queryTree))
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! /* translator: %s is a SQL statement name */
! errmsg("%s is not allowed in a non-volatile function",
! CreateQueryTag(queryTree))));
!
! dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None,
! NULL);
! if (snapshot == InvalidSnapshot)
! ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
! else
! ActiveSnapshot = CopySnapshot(snapshot);
!
! if (queryTree->commandType == CMD_UTILITY)
! {
! ProcessUtility(queryTree->utilityStmt, paramLI,
! dest, NULL);
! res = SPI_OK_UTILITY;
! }
! else
! {
! qdesc = CreateQueryDesc(queryTree, planTree,
! ActiveSnapshot,
! crosscheck_snapshot,
! dest,
! paramLI, false);
! res = _SPI_pquery(qdesc,
! queryTree->canSetTag ? tcount : 0);
! FreeQueryDesc(qdesc);
! }
! FreeSnapshot(ActiveSnapshot);
! ActiveSnapshot = NULL;
! /* we know that the receiver doesn't need a destroy call */
if (res < 0)
goto fail;
! if (!read_only)
! CommandCounterIncrement();
}
}
fail:
! /*
! * Pop the error context stack
! */
! error_context_stack = spierrcontext.previous;
! }
! PG_CATCH();
! {
! /* Restore global vars and propagate error */
! ActiveSnapshot = saveActiveSnapshot;
! PG_RE_THROW();
! }
! PG_END_TRY();
!
! ActiveSnapshot = saveActiveSnapshot;
return res;
}
static int
! _SPI_pquery(QueryDesc *queryDesc, int tcount)
{
int operation = queryDesc->operation;
int res;
***************
*** 1427,1435 ****
return SPI_ERROR_OPUNKNOWN;
}
- if (!runit) /* plan preparation, don't execute */
- return res;
-
#ifdef SPI_EXECUTOR_STATS
if (ShowExecutorStats)
ResetUsage();
--- 1440,1445 ----
***************
*** 1437,1443 ****
AfterTriggerBeginQuery();
! ExecutorStart(queryDesc, useCurrentSnapshot, false);
ExecutorRun(queryDesc, ForwardScanDirection, (long) tcount);
--- 1447,1453 ----
AfterTriggerBeginQuery();
! ExecutorStart(queryDesc, false);
ExecutorRun(queryDesc, ForwardScanDirection, (long) tcount);
***************
*** 1466,1473 ****
/* Don't return SPI_OK_SELECT if we discarded the result */
res = SPI_OK_UTILITY;
}
-
- FreeQueryDesc(queryDesc);
#ifdef SPI_EXECUTOR_STATS
if (ShowExecutorStats)
--- 1476,1481 ----
*** src/backend/tcop/fastpath.c.orig Sun Aug 29 09:55:21 2004
--- src/backend/tcop/fastpath.c Sat Sep 11 19:18:05 2004
***************
*** 334,344 ****
get_func_name(fid));
/*
- * Set up a query snapshot in case function needs one.
- */
- SetQuerySnapshot();
-
- /*
* Prepare function call info block and insert arguments.
*/
MemSet(&fcinfo, 0, sizeof(fcinfo));
--- 334,339 ----
*** src/backend/tcop/postgres.c.orig Fri Sep 10 14:39:59 2004
--- src/backend/tcop/postgres.c Sat Sep 11 19:17:49 2004
***************
*** 700,706 ****
{
if (needSnapshot)
{
! SetQuerySnapshot();
needSnapshot = false;
}
plan = pg_plan_query(query, boundParams);
--- 700,706 ----
{
if (needSnapshot)
{
! ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
needSnapshot = false;
}
plan = pg_plan_query(query, boundParams);
***************
*** 883,889 ****
/*
* Start the portal. No parameters here.
*/
! PortalStart(portal, NULL);
/*
* Select the appropriate output format: text unless we are doing
--- 883,889 ----
/*
* Start the portal. No parameters here.
*/
! PortalStart(portal, NULL, InvalidSnapshot);
/*
* Select the appropriate output format: text unless we are doing
***************
*** 1539,1545 ****
pstmt->plan_list,
pstmt->context);
! PortalStart(portal, params);
/*
* Apply the result format requests to the portal.
--- 1539,1545 ----
pstmt->plan_list,
pstmt->context);
! PortalStart(portal, params, InvalidSnapshot);
/*
* Apply the result format requests to the portal.
***************
*** 3026,3031 ****
--- 3026,3034 ----
/* switch back to message context */
MemoryContextSwitchTo(MessageContext);
+
+ /* set snapshot in case function needs one */
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
if (HandleFunctionRequest(&input_message) == EOF)
{
*** src/backend/tcop/pquery.c.orig Fri Sep 10 14:40:00 2004
--- src/backend/tcop/pquery.c Sat Sep 11 21:00:49 2004
***************
*** 32,37 ****
--- 32,42 ----
Portal ActivePortal = NULL;
+ static void ProcessQuery(Query *parsetree,
+ Plan *plan,
+ ParamListInfo params,
+ DestReceiver *dest,
+ char *completionTag);
static uint32 RunFromStore(Portal portal, ScanDirection direction, long count,
DestReceiver *dest);
static long PortalRunSelect(Portal portal, bool forward, long count,
***************
*** 54,59 ****
--- 59,66 ----
QueryDesc *
CreateQueryDesc(Query *parsetree,
Plan *plantree,
+ Snapshot snapshot,
+ Snapshot crosscheck_snapshot,
DestReceiver *dest,
ParamListInfo params,
bool doInstrument)
***************
*** 63,68 ****
--- 70,77 ----
qd->operation = parsetree->commandType; /* operation */
qd->parsetree = parsetree; /* parse tree */
qd->plantree = plantree; /* plan */
+ qd->snapshot = snapshot; /* snapshot */
+ qd->crosscheck_snapshot = crosscheck_snapshot; /* RI check snapshot */
qd->dest = dest; /* output dest */
qd->params = params; /* parameter values passed into query */
qd->doInstrument = doInstrument; /* instrumentation wanted? */
***************
*** 90,96 ****
/*
* ProcessQuery
! * Execute a single query
*
* parsetree: the query tree
* plan: the plan tree for the query
--- 99,105 ----
/*
* ProcessQuery
! * Execute a single plannable query within a PORTAL_MULTI_QUERY portal
*
* parsetree: the query tree
* plan: the plan tree for the query
***************
*** 104,110 ****
* Must be called in a memory context that will be reset or deleted on
* error; otherwise the executor's memory usage will be leaked.
*/
! void
ProcessQuery(Query *parsetree,
Plan *plan,
ParamListInfo params,
--- 113,119 ----
* Must be called in a memory context that will be reset or deleted on
* error; otherwise the executor's memory usage will be leaked.
*/
! static void
ProcessQuery(Query *parsetree,
Plan *plan,
ParamListInfo params,
***************
*** 114,119 ****
--- 123,131 ----
int operation = parsetree->commandType;
QueryDesc *queryDesc;
+ ereport(DEBUG3,
+ (errmsg_internal("ProcessQuery")));
+
/*
* Check for special-case destinations
*/
***************
*** 133,141 ****
}
/*
* Create the QueryDesc object
*/
! queryDesc = CreateQueryDesc(parsetree, plan, dest, params, false);
/*
* Set up to collect AFTER triggers
--- 145,161 ----
}
/*
+ * Must always set snapshot for plannable queries. Note we assume
+ * that caller will take care of restoring ActiveSnapshot on exit/error.
+ */
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
+
+ /*
* Create the QueryDesc object
*/
! queryDesc = CreateQueryDesc(parsetree, plan,
! ActiveSnapshot, InvalidSnapshot,
! dest, params, false);
/*
* Set up to collect AFTER triggers
***************
*** 145,151 ****
/*
* Call ExecStart to prepare the plan for execution
*/
! ExecutorStart(queryDesc, false, false);
/*
* Run the plan to completion.
--- 165,171 ----
/*
* Call ExecStart to prepare the plan for execution
*/
! ExecutorStart(queryDesc, false);
/*
* Run the plan to completion.
***************
*** 195,200 ****
--- 215,223 ----
AfterTriggerEndQuery();
FreeQueryDesc(queryDesc);
+
+ FreeSnapshot(ActiveSnapshot);
+ ActiveSnapshot = NULL;
}
/*
***************
*** 238,250 ****
* the query, they must be passed in here (caller is responsible for
* giving them appropriate lifetime).
*
* On return, portal is ready to accept PortalRun() calls, and the result
* tupdesc (if any) is known.
*/
void
! PortalStart(Portal portal, ParamListInfo params)
{
Portal saveActivePortal;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext oldContext;
--- 261,279 ----
* the query, they must be passed in here (caller is responsible for
* giving them appropriate lifetime).
*
+ * The caller can optionally pass a snapshot to be used; pass InvalidSnapshot
+ * for the normal behavior of setting a new snapshot. This parameter is
+ * presently ignored for non-PORTAL_ONE_SELECT portals (it's only intended
+ * to be used for cursors).
+ *
* On return, portal is ready to accept PortalRun() calls, and the result
* tupdesc (if any) is known.
*/
void
! PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot)
{
Portal saveActivePortal;
+ Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext oldContext;
***************
*** 259,269 ****
--- 288,300 ----
* QueryContext?)
*/
saveActivePortal = ActivePortal;
+ saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
PG_TRY();
{
ActivePortal = portal;
+ ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
***************
*** 285,293 ****
case PORTAL_ONE_SELECT:
/*
! * Must set query snapshot before starting executor.
*/
! SetQuerySnapshot();
/*
* Create QueryDesc in portal's context; for the moment,
--- 316,328 ----
case PORTAL_ONE_SELECT:
/*
! * Must set snapshot before starting executor. Be sure to
! * copy it into the portal's context.
*/
! if (snapshot)
! ActiveSnapshot = CopySnapshot(snapshot);
! else
! ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
/*
* Create QueryDesc in portal's context; for the moment,
***************
*** 295,300 ****
--- 330,337 ----
*/
queryDesc = CreateQueryDesc((Query *) linitial(portal->parseTrees),
(Plan *) linitial(portal->planTrees),
+ ActiveSnapshot,
+ InvalidSnapshot,
None_Receiver,
params,
false);
***************
*** 309,315 ****
/*
* Call ExecStart to prepare the plan for execution
*/
! ExecutorStart(queryDesc, false, false);
/*
* This tells PortalCleanup to shut down the executor
--- 346,352 ----
/*
* Call ExecStart to prepare the plan for execution
*/
! ExecutorStart(queryDesc, false);
/*
* This tells PortalCleanup to shut down the executor
***************
*** 333,340 ****
case PORTAL_UTIL_SELECT:
/*
! * We don't set query snapshot here, because
! * PortalRunUtility will take care of it.
*/
portal->tupDesc =
UtilityTupleDescriptor(((Query *) linitial(portal->parseTrees))->utilityStmt);
--- 370,377 ----
case PORTAL_UTIL_SELECT:
/*
! * We don't set snapshot here, because
! * PortalRunUtility will take care of it if needed.
*/
portal->tupDesc =
UtilityTupleDescriptor(((Query *) linitial(portal->parseTrees))->utilityStmt);
***************
*** 361,366 ****
--- 398,404 ----
/* Restore global vars and propagate error */
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
***************
*** 371,376 ****
--- 409,415 ----
MemoryContextSwitchTo(oldContext);
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
***************
*** 453,458 ****
--- 492,498 ----
{
bool result;
Portal saveActivePortal;
+ Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
***************
*** 485,496 ****
--- 525,538 ----
* Set up global portal context pointers.
*/
saveActivePortal = ActivePortal;
+ saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
saveQueryContext = QueryContext;
PG_TRY();
{
ActivePortal = portal;
+ ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
QueryContext = portal->queryContext;
***************
*** 579,584 ****
--- 621,627 ----
/* Restore global vars and propagate error */
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
***************
*** 590,595 ****
--- 633,639 ----
MemoryContextSwitchTo(oldContext);
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
***************
*** 670,675 ****
--- 714,720 ----
nprocessed = RunFromStore(portal, direction, count, dest);
else
{
+ ActiveSnapshot = queryDesc->snapshot;
ExecutorRun(queryDesc, direction, count);
nprocessed = queryDesc->estate->es_processed;
}
***************
*** 711,716 ****
--- 756,762 ----
nprocessed = RunFromStore(portal, direction, count, dest);
else
{
+ ActiveSnapshot = queryDesc->snapshot;
ExecutorRun(queryDesc, direction, count);
nprocessed = queryDesc->estate->es_processed;
}
***************
*** 834,839 ****
--- 880,888 ----
* the database --- if, say, it has to update an index with
* expressions that invoke user-defined functions, then it had better
* have a snapshot.
+ *
+ * Note we assume that caller will take care of restoring ActiveSnapshot
+ * on exit/error.
*/
if (!(IsA(utilityStmt, TransactionStmt) ||
IsA(utilityStmt, LockStmt) ||
***************
*** 847,853 ****
IsA(utilityStmt, NotifyStmt) ||
IsA(utilityStmt, UnlistenStmt) ||
IsA(utilityStmt, CheckPointStmt)))
! SetQuerySnapshot();
if (query->canSetTag)
{
--- 896,904 ----
IsA(utilityStmt, NotifyStmt) ||
IsA(utilityStmt, UnlistenStmt) ||
IsA(utilityStmt, CheckPointStmt)))
! ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
! else
! ActiveSnapshot = NULL;
if (query->canSetTag)
{
***************
*** 864,869 ****
--- 915,924 ----
/* Some utility statements may change context on us */
MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+
+ if (ActiveSnapshot)
+ FreeSnapshot(ActiveSnapshot);
+ ActiveSnapshot = NULL;
}
/*
***************
*** 924,938 ****
/*
* process a plannable query.
*/
- ereport(DEBUG3,
- (errmsg_internal("ProcessQuery")));
-
- /* Must always set snapshot for plannable queries */
- SetQuerySnapshot();
-
- /*
- * execute the plan
- */
if (log_executor_stats)
ResetUsage();
--- 979,984 ----
***************
*** 1005,1010 ****
--- 1051,1057 ----
{
long result;
Portal saveActivePortal;
+ Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
***************
*** 1025,1036 ****
--- 1072,1085 ----
* Set up global portal context pointers.
*/
saveActivePortal = ActivePortal;
+ saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
saveQueryContext = QueryContext;
PG_TRY();
{
ActivePortal = portal;
+ ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
QueryContext = portal->queryContext;
***************
*** 1056,1061 ****
--- 1105,1111 ----
/* Restore global vars and propagate error */
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
***************
*** 1070,1075 ****
--- 1120,1126 ----
portal->status = PORTAL_READY;
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
*** src/backend/tcop/utility.c.orig Fri Sep 10 14:40:00 2004
--- src/backend/tcop/utility.c Sat Sep 11 22:09:13 2004
***************
*** 222,227 ****
--- 222,267 ----
}
+ /*
+ * QueryIsReadOnly: is an analyzed/rewritten query read-only?
+ *
+ * This is a much stricter test than we apply for XactReadOnly mode;
+ * the query must be *in truth* read-only, because the caller wishes
+ * not to do CommandCounterIncrement afterwards.
+ */
+ bool
+ QueryIsReadOnly(Query *parsetree)
+ {
+ switch (parsetree->commandType)
+ {
+ case CMD_SELECT:
+ if (parsetree->into != NULL)
+ return false; /* SELECT INTO */
+ else if (parsetree->rowMarks != NIL)
+ return false; /* SELECT FOR UPDATE */
+ else
+ return true;
+ case CMD_UPDATE:
+ case CMD_INSERT:
+ case CMD_DELETE:
+ return false;
+ case CMD_UTILITY:
+ /* For now, treat all utility commands as read/write */
+ return false;
+ default:
+ elog(WARNING, "unrecognized commandType: %d",
+ (int) parsetree->commandType);
+ break;
+ }
+ return false;
+ }
+
+ /*
+ * check_xact_readonly: is a utility command read-only?
+ *
+ * Here we use the loose rules of XactReadOnly mode: no permanent effects
+ * on the database are allowed.
+ */
static void
check_xact_readonly(Node *parsetree)
{
***************
*** 299,306 ****
* completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
* in which to store a command completion status string.
*
! * completionTag is only set nonempty if we want to return a nondefault
! * status (currently, only used for MOVE/FETCH).
*
* completionTag may be NULL if caller doesn't want a status string.
*/
--- 339,345 ----
* completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
* in which to store a command completion status string.
*
! * completionTag is only set nonempty if we want to return a nondefault status.
*
* completionTag may be NULL if caller doesn't want a status string.
*/
***************
*** 1580,1585 ****
--- 1619,1672 ----
default:
elog(WARNING, "unrecognized node type: %d",
(int) nodeTag(parsetree));
+ tag = "???";
+ break;
+ }
+
+ return tag;
+ }
+
+ /*
+ * CreateQueryTag
+ * utility to get a string representation of a Query operation.
+ *
+ * This is exactly like CreateCommandTag, except it works on a Query
+ * that has already been through parse analysis (and possibly further).
+ */
+ const char *
+ CreateQueryTag(Query *parsetree)
+ {
+ const char *tag;
+
+ switch (parsetree->commandType)
+ {
+ case CMD_SELECT:
+ /*
+ * We take a little extra care here so that the result will
+ * be useful for complaints about read-only statements
+ */
+ if (parsetree->into != NULL)
+ tag = "SELECT INTO";
+ else if (parsetree->rowMarks != NIL)
+ tag = "SELECT FOR UPDATE";
+ else
+ tag = "SELECT";
+ break;
+ case CMD_UPDATE:
+ tag = "UPDATE";
+ break;
+ case CMD_INSERT:
+ tag = "INSERT";
+ break;
+ case CMD_DELETE:
+ tag = "DELETE";
+ break;
+ case CMD_UTILITY:
+ tag = CreateCommandTag(parsetree->utilityStmt);
+ break;
+ default:
+ elog(WARNING, "unrecognized commandType: %d",
+ (int) parsetree->commandType);
tag = "???";
break;
}
*** src/backend/utils/adt/ri_triggers.c.orig Fri Sep 10 14:40:04 2004
--- src/backend/utils/adt/ri_triggers.c Sun Sep 12 16:49:46 2004
***************
*** 2698,2713 ****
elog(ERROR, "SPI_prepare returned %d for %s", SPI_result, querystr);
/*
! * Run the plan. For safety we force a current query snapshot to be
! * used. (In serializable mode, this arguably violates
! * serializability, but we really haven't got much choice.) We need
! * at most one tuple returned, so pass limit = 1.
! */
! spi_result = SPI_execp_current(qplan, NULL, NULL, true, 1);
/* Check result */
if (spi_result != SPI_OK_SELECT)
! elog(ERROR, "SPI_execp_current returned %d", spi_result);
/* Did we find a tuple violating the constraint? */
if (SPI_processed > 0)
--- 2698,2717 ----
elog(ERROR, "SPI_prepare returned %d for %s", SPI_result, querystr);
/*
! * Run the plan. For safety we force a current snapshot to be used.
! * (In serializable mode, this arguably violates serializability, but we
! * really haven't got much choice.) We need at most one tuple returned,
! * so pass limit = 1.
! */
! spi_result = SPI_execute_snapshot(qplan,
! NULL, NULL,
! CopySnapshot(GetLatestSnapshot()),
! InvalidSnapshot,
! false, 1);
/* Check result */
if (spi_result != SPI_OK_SELECT)
! elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
/* Did we find a tuple violating the constraint? */
if (SPI_processed > 0)
***************
*** 3043,3049 ****
Relation query_rel,
source_rel;
int key_idx;
! bool useCurrentSnapshot;
int limit;
int spi_result;
AclId save_uid;
--- 3047,3054 ----
Relation query_rel,
source_rel;
int key_idx;
! Snapshot test_snapshot;
! Snapshot crosscheck_snapshot;
int limit;
int spi_result;
AclId save_uid;
***************
*** 3094,3114 ****
}
/*
! * In READ COMMITTED mode, we just need to make sure the regular query
! * snapshot is up-to-date, and we will see all rows that could be
! * interesting. In SERIALIZABLE mode, we can't update the regular
! * query snapshot. If the caller passes detectNewRows == false then
! * it's okay to do the query with the transaction snapshot; otherwise
! * we tell the executor to force a current snapshot (and error out if
! * it finds any rows under current snapshot that wouldn't be visible
! * per the transaction snapshot).
*/
! if (IsXactIsoLevelSerializable)
! useCurrentSnapshot = detectNewRows;
else
{
! SetQuerySnapshot();
! useCurrentSnapshot = false;
}
/*
--- 3099,3122 ----
}
/*
! * In READ COMMITTED mode, we just need to use an up-to-date regular
! * snapshot, and we will see all rows that could be interesting.
! * But in SERIALIZABLE mode, we can't change the transaction snapshot.
! * If the caller passes detectNewRows == false then it's okay to do the
! * query with the transaction snapshot; otherwise we use a current
! * snapshot, and tell the executor to error out if it finds any rows under
! * the current snapshot that wouldn't be visible per the transaction
! * snapshot.
*/
! if (IsXactIsoLevelSerializable && detectNewRows)
! {
! test_snapshot = CopySnapshot(GetLatestSnapshot());
! crosscheck_snapshot = CopySnapshot(GetTransactionSnapshot());
! }
else
{
! test_snapshot = CopySnapshot(GetTransactionSnapshot());
! crosscheck_snapshot = InvalidSnapshot;
}
/*
***************
*** 3124,3138 ****
SetUserId(RelationGetForm(query_rel)->relowner);
/* Finally we can run the query. */
! spi_result = SPI_execp_current(qplan, vals, nulls,
! useCurrentSnapshot, limit);
/* Restore UID */
SetUserId(save_uid);
/* Check result */
if (spi_result < 0)
! elog(ERROR, "SPI_execp_current returned %d", spi_result);
if (expect_OK >= 0 && spi_result != expect_OK)
ri_ReportViolation(qkey, constrname ? constrname : "",
--- 3132,3148 ----
SetUserId(RelationGetForm(query_rel)->relowner);
/* Finally we can run the query. */
! spi_result = SPI_execute_snapshot(qplan,
! vals, nulls,
! test_snapshot, crosscheck_snapshot,
! false, limit);
/* Restore UID */
SetUserId(save_uid);
/* Check result */
if (spi_result < 0)
! elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
if (expect_OK >= 0 && spi_result != expect_OK)
ri_ReportViolation(qkey, constrname ? constrname : "",
*** src/backend/utils/adt/ruleutils.c.orig Wed Sep 1 19:58:38 2004
--- src/backend/utils/adt/ruleutils.c Sun Sep 12 16:49:46 2004
***************
*** 290,296 ****
*/
args[0] = ObjectIdGetDatum(ruleoid);
nulls[0] = ' ';
! spirc = SPI_execp(plan_getrulebyoid, args, nulls, 1);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for rule %u", ruleoid);
if (SPI_processed != 1)
--- 290,296 ----
*/
args[0] = ObjectIdGetDatum(ruleoid);
nulls[0] = ' ';
! spirc = SPI_execute_plan(plan_getrulebyoid, args, nulls, true, 1);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for rule %u", ruleoid);
if (SPI_processed != 1)
***************
*** 425,431 ****
args[1] = PointerGetDatum(ViewSelectRuleName);
nulls[0] = ' ';
nulls[1] = ' ';
! spirc = SPI_execp(plan_getviewrule, args, nulls, 2);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for view %u", viewoid);
if (SPI_processed != 1)
--- 425,431 ----
args[1] = PointerGetDatum(ViewSelectRuleName);
nulls[0] = ' ';
nulls[1] = ' ';
! spirc = SPI_execute_plan(plan_getviewrule, args, nulls, true, 2);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for view %u", viewoid);
if (SPI_processed != 1)
*** src/backend/utils/time/tqual.c.orig Sun Aug 29 09:55:38 2004
--- src/backend/utils/time/tqual.c Sat Sep 11 18:59:52 2004
***************
*** 28,45 ****
#include "utils/tqual.h"
/*
! * The SnapshotData structs are static to simplify memory allocation
* (see the hack in GetSnapshotData to avoid repeated malloc/free).
*/
- static SnapshotData QuerySnapshotData;
- static SnapshotData SerializableSnapshotData;
- static SnapshotData CurrentSnapshotData;
static SnapshotData SnapshotDirtyData;
/* Externally visible pointers to valid snapshots: */
- Snapshot QuerySnapshot = NULL;
- Snapshot SerializableSnapshot = NULL;
Snapshot SnapshotDirty = &SnapshotDirtyData;
/* These are updated by GetSnapshotData: */
TransactionId RecentXmin = InvalidTransactionId;
--- 28,51 ----
#include "utils/tqual.h"
/*
! * These SnapshotData structs are static to simplify memory allocation
* (see the hack in GetSnapshotData to avoid repeated malloc/free).
*/
static SnapshotData SnapshotDirtyData;
+ static SnapshotData SerializableSnapshotData;
+ static SnapshotData LatestSnapshotData;
/* Externally visible pointers to valid snapshots: */
Snapshot SnapshotDirty = &SnapshotDirtyData;
+ Snapshot SerializableSnapshot = NULL;
+ Snapshot LatestSnapshot = NULL;
+
+ /*
+ * This pointer is not maintained by this module, but it's convenient
+ * to declare it here anyway. Callers typically assign a copy of
+ * GetTransactionSnapshot's result to ActiveSnapshot.
+ */
+ Snapshot ActiveSnapshot = NULL;
/* These are updated by GetSnapshotData: */
TransactionId RecentXmin = InvalidTransactionId;
***************
*** 1028,1128 ****
/*
! * SetQuerySnapshot
! * Initialize query snapshot for a new query
*
* The SerializableSnapshot is the first one taken in a transaction.
* In serializable mode we just use that one throughout the transaction.
! * In read-committed mode, we take a new snapshot at the start of each query.
*/
! void
! SetQuerySnapshot(void)
{
! /* 1st call in xaction? */
if (SerializableSnapshot == NULL)
{
SerializableSnapshot = GetSnapshotData(&SerializableSnapshotData, true);
! QuerySnapshot = SerializableSnapshot;
! Assert(QuerySnapshot != NULL);
! return;
}
if (IsXactIsoLevelSerializable)
! QuerySnapshot = SerializableSnapshot;
! else
! QuerySnapshot = GetSnapshotData(&QuerySnapshotData, false);
! Assert(QuerySnapshot != NULL);
}
/*
! * CopyQuerySnapshot
! * Copy the current query snapshot.
! *
! * Copying the snapshot is done so that a query is guaranteed to use a
! * consistent snapshot for its entire execution life, even if the command
! * counter is incremented or SetQuerySnapshot() is called while it runs
! * (as could easily happen, due to triggers etc. executing queries).
! *
! * The copy is palloc'd in the current memory context.
*/
Snapshot
! CopyQuerySnapshot(void)
{
! Snapshot snapshot;
!
! if (QuerySnapshot == NULL) /* should be set beforehand */
elog(ERROR, "no snapshot has been set");
! snapshot = (Snapshot) palloc(sizeof(SnapshotData));
! memcpy(snapshot, QuerySnapshot, sizeof(SnapshotData));
! if (snapshot->xcnt > 0)
! {
! snapshot->xip = (TransactionId *)
! palloc(snapshot->xcnt * sizeof(TransactionId));
! memcpy(snapshot->xip, QuerySnapshot->xip,
! snapshot->xcnt * sizeof(TransactionId));
! }
! else
! snapshot->xip = NULL;
! return snapshot;
}
/*
! * CopyCurrentSnapshot
! * Make a snapshot that is up-to-date as of the current instant,
! * and return a copy.
*
* The copy is palloc'd in the current memory context.
*/
Snapshot
! CopyCurrentSnapshot(void)
{
! Snapshot currentSnapshot;
! Snapshot snapshot;
!
! if (QuerySnapshot == NULL) /* should not be first call in xact */
! elog(ERROR, "no snapshot has been set");
! /* Update the static struct */
! currentSnapshot = GetSnapshotData(&CurrentSnapshotData, false);
! currentSnapshot->curcid = GetCurrentCommandId();
!
! /* Make a copy */
! snapshot = (Snapshot) palloc(sizeof(SnapshotData));
! memcpy(snapshot, currentSnapshot, sizeof(SnapshotData));
if (snapshot->xcnt > 0)
{
! snapshot->xip = (TransactionId *)
! palloc(snapshot->xcnt * sizeof(TransactionId));
! memcpy(snapshot->xip, currentSnapshot->xip,
snapshot->xcnt * sizeof(TransactionId));
}
else
! snapshot->xip = NULL;
! return snapshot;
}
/*
--- 1034,1127 ----
/*
! * GetTransactionSnapshot
! * Get the appropriate snapshot for a new query in a transaction.
*
* The SerializableSnapshot is the first one taken in a transaction.
* In serializable mode we just use that one throughout the transaction.
! * In read-committed mode, we take a new snapshot each time we are called.
! *
! * Note that the return value points at static storage that will be modified
! * by future calls and by CommandCounterIncrement(). Callers should copy
! * the result with CopySnapshot() if it is to be used very long.
*/
! Snapshot
! GetTransactionSnapshot(void)
{
! /* First call in transaction? */
if (SerializableSnapshot == NULL)
{
SerializableSnapshot = GetSnapshotData(&SerializableSnapshotData, true);
! return SerializableSnapshot;
}
if (IsXactIsoLevelSerializable)
! return SerializableSnapshot;
! LatestSnapshot = GetSnapshotData(&LatestSnapshotData, false);
!
! return LatestSnapshot;
}
/*
! * GetLatestSnapshot
! * Get a snapshot that is up-to-date as of the current instant,
! * even if we are executing in SERIALIZABLE mode.
*/
Snapshot
! GetLatestSnapshot(void)
{
! /* Should not be first call in transaction */
! if (SerializableSnapshot == NULL)
elog(ERROR, "no snapshot has been set");
! LatestSnapshot = GetSnapshotData(&LatestSnapshotData, false);
! return LatestSnapshot;
}
/*
! * CopySnapshot
! * Copy the given snapshot.
*
* The copy is palloc'd in the current memory context.
+ *
+ * Note that this will not work on "special" snapshots.
*/
Snapshot
! CopySnapshot(Snapshot snapshot)
{
! Snapshot newsnap;
! /* We allocate any XID array needed in the same palloc block. */
! newsnap = (Snapshot) palloc(sizeof(SnapshotData) +
! snapshot->xcnt * sizeof(TransactionId));
! memcpy(newsnap, snapshot, sizeof(SnapshotData));
if (snapshot->xcnt > 0)
{
! newsnap->xip = (TransactionId *) (newsnap + 1);
! memcpy(newsnap->xip, snapshot->xip,
snapshot->xcnt * sizeof(TransactionId));
}
else
! newsnap->xip = NULL;
! return newsnap;
! }
!
! /*
! * FreeSnapshot
! * Free a snapshot previously copied with CopySnapshot.
! *
! * This is currently identical to pfree, but is provided for cleanliness.
! *
! * Do *not* apply this to the results of GetTransactionSnapshot or
! * GetLatestSnapshot.
! */
! void
! FreeSnapshot(Snapshot snapshot)
! {
! pfree(snapshot);
}
/*
***************
*** 1133,1142 ****
FreeXactSnapshot(void)
{
/*
! * We do not free the xip arrays for the snapshot structs; they will
! * be reused soon. So this is now just a state change to prevent
* outside callers from accessing the snapshots.
*/
- QuerySnapshot = NULL;
SerializableSnapshot = NULL;
}
--- 1132,1142 ----
FreeXactSnapshot(void)
{
/*
! * We do not free the xip arrays for the static snapshot structs; they
! * will be reused soon. So this is now just a state change to prevent
* outside callers from accessing the snapshots.
*/
SerializableSnapshot = NULL;
+ LatestSnapshot = NULL;
+ ActiveSnapshot = NULL; /* just for cleanliness */
}
*** src/include/executor/execdesc.h.orig Sun Aug 29 09:56:01 2004
--- src/include/executor/execdesc.h Sat Sep 11 18:59:37 2004
***************
*** 33,38 ****
--- 33,40 ----
CmdType operation; /* CMD_SELECT, CMD_UPDATE, etc. */
Query *parsetree; /* rewritten parsetree */
Plan *plantree; /* planner's output */
+ Snapshot snapshot; /* snapshot to use for query */
+ Snapshot crosscheck_snapshot; /* crosscheck for RI update/delete */
DestReceiver *dest; /* the destination for tuple output */
ParamListInfo params; /* param values being passed in */
bool doInstrument; /* TRUE requests runtime instrumentation */
***************
*** 45,50 ****
--- 47,54 ----
/* in pquery.c */
extern QueryDesc *CreateQueryDesc(Query *parsetree, Plan *plantree,
+ Snapshot snapshot,
+ Snapshot crosscheck_snapshot,
DestReceiver *dest,
ParamListInfo params,
bool doInstrument);
*** src/include/executor/executor.h.orig Sun Aug 29 09:56:01 2004
--- src/include/executor/executor.h Sat Sep 11 19:12:22 2004
***************
*** 95,102 ****
/*
* prototypes from functions in execMain.c
*/
! extern void ExecutorStart(QueryDesc *queryDesc, bool useCurrentSnapshot,
! bool explainOnly);
extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc,
ScanDirection direction, long count);
extern void ExecutorEnd(QueryDesc *queryDesc);
--- 95,101 ----
/*
* prototypes from functions in execMain.c
*/
! extern void ExecutorStart(QueryDesc *queryDesc, bool explainOnly);
extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc,
ScanDirection direction, long count);
extern void ExecutorEnd(QueryDesc *queryDesc);
*** src/include/executor/spi.h.orig Sun Aug 29 09:56:02 2004
--- src/include/executor/spi.h Sun Sep 12 16:49:38 2004
***************
*** 81,91 ****
extern int SPI_finish(void);
extern void SPI_push(void);
extern void SPI_pop(void);
extern int SPI_exec(const char *src, int tcount);
! extern int SPI_execp(void *plan, Datum *values, const char *Nulls,
! int tcount);
! extern int SPI_execp_current(void *plan, Datum *values, const char *Nulls,
! bool useCurrentSnapshot, int tcount);
extern void *SPI_prepare(const char *src, int nargs, Oid *argtypes);
extern void *SPI_saveplan(void *plan);
extern int SPI_freeplan(void *plan);
--- 81,97 ----
extern int SPI_finish(void);
extern void SPI_push(void);
extern void SPI_pop(void);
+ extern int SPI_execute(const char *src, bool read_only, int tcount);
+ extern int SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
+ bool read_only, int tcount);
extern int SPI_exec(const char *src, int tcount);
! extern int SPI_execp(void *plan, Datum *Values, const char *Nulls,
! int tcount);
! extern int SPI_execute_snapshot(void *plan,
! Datum *Values, const char *Nulls,
! Snapshot snapshot,
! Snapshot crosscheck_snapshot,
! bool read_only, int tcount);
extern void *SPI_prepare(const char *src, int nargs, Oid *argtypes);
extern void *SPI_saveplan(void *plan);
extern int SPI_freeplan(void *plan);
*** src/include/tcop/pquery.h.orig Sun Aug 29 09:56:11 2004
--- src/include/tcop/pquery.h Sat Sep 11 18:59:32 2004
***************
*** 20,34 ****
extern DLLIMPORT Portal ActivePortal;
- extern void ProcessQuery(Query *parsetree,
- Plan *plan,
- ParamListInfo params,
- DestReceiver *dest,
- char *completionTag);
-
extern PortalStrategy ChoosePortalStrategy(List *parseTrees);
! extern void PortalStart(Portal portal, ParamListInfo params);
extern void PortalSetResultFormat(Portal portal, int nFormats,
int16 *formats);
--- 20,29 ----
extern DLLIMPORT Portal ActivePortal;
extern PortalStrategy ChoosePortalStrategy(List *parseTrees);
! extern void PortalStart(Portal portal, ParamListInfo params,
! Snapshot snapshot);
extern void PortalSetResultFormat(Portal portal, int nFormats,
int16 *formats);
*** src/include/tcop/utility.h.orig Sun Aug 29 09:56:11 2004
--- src/include/tcop/utility.h Sat Sep 11 22:09:07 2004
***************
*** 26,31 ****
--- 26,35 ----
extern const char *CreateCommandTag(Node *parsetree);
+ extern const char *CreateQueryTag(Query *parsetree);
+
+ extern bool QueryIsReadOnly(Query *parsetree);
+
extern void CheckRelationOwnership(RangeVar *rel, bool noCatalogs);
#endif /* UTILITY_H */
*** src/include/utils/tqual.h.orig Sat Sep 11 14:28:34 2004
--- src/include/utils/tqual.h Sat Sep 11 18:59:27 2004
***************
*** 54,61 ****
#define SnapshotToast ((Snapshot) 0x4)
extern DLLIMPORT Snapshot SnapshotDirty;
! extern DLLIMPORT Snapshot QuerySnapshot;
extern DLLIMPORT Snapshot SerializableSnapshot;
extern TransactionId RecentXmin;
extern TransactionId RecentGlobalXmin;
--- 54,63 ----
#define SnapshotToast ((Snapshot) 0x4)
extern DLLIMPORT Snapshot SnapshotDirty;
!
extern DLLIMPORT Snapshot SerializableSnapshot;
+ extern DLLIMPORT Snapshot LatestSnapshot;
+ extern DLLIMPORT Snapshot ActiveSnapshot;
extern TransactionId RecentXmin;
extern TransactionId RecentGlobalXmin;
***************
*** 121,130 ****
extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTupleHeader tuple,
TransactionId OldestXmin);
! extern Snapshot GetSnapshotData(Snapshot snapshot, bool serializable);
! extern void SetQuerySnapshot(void);
! extern Snapshot CopyQuerySnapshot(void);
! extern Snapshot CopyCurrentSnapshot(void);
extern void FreeXactSnapshot(void);
#endif /* TQUAL_H */
--- 123,135 ----
extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTupleHeader tuple,
TransactionId OldestXmin);
! extern Snapshot GetTransactionSnapshot(void);
! extern Snapshot GetLatestSnapshot(void);
! extern Snapshot CopySnapshot(Snapshot snapshot);
! extern void FreeSnapshot(Snapshot snapshot);
extern void FreeXactSnapshot(void);
+
+ /* in sinval.c; declared here to avoid including tqual.h in sinval.h: */
+ extern Snapshot GetSnapshotData(Snapshot snapshot, bool serializable);
#endif /* TQUAL_H */
*** src/pl/plperl/plperl.c.orig Sun Aug 29 22:58:08 2004
--- src/pl/plperl/plperl.c Sun Sep 12 18:18:38 2004
***************
*** 53,58 ****
--- 53,59 ----
#include "executor/spi.h"
#include "fmgr.h"
#include "tcop/tcopprot.h"
+ #include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
***************
*** 77,82 ****
--- 78,84 ----
char *proname;
TransactionId fn_xmin;
CommandId fn_cmin;
+ bool fn_readonly;
bool lanpltrusted;
bool fn_retistuple; /* true, if function returns tuple */
bool fn_retisset; /* true, if function returns set */
***************
*** 98,108 ****
static bool plperl_safe_init_done = false;
static PerlInterpreter *plperl_interp = NULL;
static HV *plperl_proc_hash = NULL;
- static AV *g_row_keys = NULL;
static AV *g_column_keys = NULL;
static SV *srf_perlret = NULL; /* keep returned value */
static int g_attr_num = 0;
/**********************************************************************
* Forward declarations
**********************************************************************/
--- 100,112 ----
static bool plperl_safe_init_done = false;
static PerlInterpreter *plperl_interp = NULL;
static HV *plperl_proc_hash = NULL;
static AV *g_column_keys = NULL;
static SV *srf_perlret = NULL; /* keep returned value */
static int g_attr_num = 0;
+ /* this is saved and restored by plperl_call_handler */
+ static plperl_proc_desc *plperl_current_prodesc = NULL;
+
/**********************************************************************
* Forward declarations
**********************************************************************/
***************
*** 119,124 ****
--- 123,129 ----
static SV *plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc);
static void plperl_init_shared_libs(pTHX);
+ static HV *plperl_spi_execute_fetch_result(SPITupleTable *, int, int);
/*
***************
*** 435,441 ****
plperl_get_keys(HV *hv)
{
AV *ret;
- SV **svp;
int key_count;
SV *val;
char *key;
--- 440,445 ----
***************
*** 445,451 ****
ret = newAV();
hv_iterinit(hv);
! while (val = hv_iternextsv(hv, (char **) &key, &klen))
{
av_store(ret, key_count, eval_pv(key, TRUE));
key_count++;
--- 449,455 ----
ret = newAV();
hv_iterinit(hv);
! while ((val = hv_iternextsv(hv, (char **) &key, &klen)))
{
av_store(ret, key_count, eval_pv(key, TRUE));
key_count++;
***************
*** 592,617 ****
plperl_call_handler(PG_FUNCTION_ARGS)
{
Datum retval;
! /************************************************************
! * Initialize interpreter
! ************************************************************/
plperl_init_all();
! /************************************************************
! * Connect to SPI manager
! ************************************************************/
! if (SPI_connect() != SPI_OK_CONNECT)
! elog(ERROR, "could not connect to SPI manager");
! /************************************************************
! * Determine if called as function or trigger and
! * call appropriate subhandler
! ************************************************************/
! if (CALLED_AS_TRIGGER(fcinfo))
! retval = PointerGetDatum(plperl_trigger_handler(fcinfo));
! else
! retval = plperl_func_handler(fcinfo);
return retval;
}
--- 596,638 ----
plperl_call_handler(PG_FUNCTION_ARGS)
{
Datum retval;
+ plperl_proc_desc *save_prodesc;
! /*
! * Initialize interpreter if first time through
! */
plperl_init_all();
! /*
! * Ensure that static pointers are saved/restored properly
! */
! save_prodesc = plperl_current_prodesc;
! PG_TRY();
! {
! /************************************************************
! * Connect to SPI manager
! ************************************************************/
! if (SPI_connect() != SPI_OK_CONNECT)
! elog(ERROR, "could not connect to SPI manager");
!
! /************************************************************
! * Determine if called as function or trigger and
! * call appropriate subhandler
! ************************************************************/
! if (CALLED_AS_TRIGGER(fcinfo))
! retval = PointerGetDatum(plperl_trigger_handler(fcinfo));
! else
! retval = plperl_func_handler(fcinfo);
! }
! PG_CATCH();
! {
! plperl_current_prodesc = save_prodesc;
! PG_RE_THROW();
! }
! PG_END_TRY();
!
! plperl_current_prodesc = save_prodesc;
return retval;
}
***************
*** 821,827 ****
SV *retval;
int i;
int count;
- char *ret_test;
ENTER;
SAVETMPS;
--- 842,847 ----
***************
*** 874,879 ****
--- 894,902 ----
/* Find or compile the function */
prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false);
+
+ plperl_current_prodesc = prodesc;
+
/************************************************************
* Call the Perl function if not returning set
************************************************************/
***************
*** 1002,1008 ****
{
HV *row_hv;
SV **svp;
- char *row_key;
svp = av_fetch(ret_av, call_cntr, FALSE);
--- 1025,1030 ----
***************
*** 1052,1058 ****
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
- int i;
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
--- 1074,1079 ----
***************
*** 1067,1073 ****
Datum result;
AV *array;
SV **svp;
- int i;
array = (AV *) SvRV(perlret);
svp = av_fetch(array, funcctx->call_cntr, FALSE);
--- 1088,1093 ----
***************
*** 1158,1163 ****
--- 1178,1185 ----
/* Find or compile the function */
prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true);
+ plperl_current_prodesc = prodesc;
+
/************************************************************
* Call the Perl function
************************************************************/
***************
*** 1323,1328 ****
--- 1345,1354 ----
prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
+ /* Remember if function is STABLE/IMMUTABLE */
+ prodesc->fn_readonly =
+ (procStruct->provolatile != PROVOLATILE_VOLATILE);
+
/************************************************************
* Lookup the pg_language tuple by Oid
************************************************************/
***************
*** 1559,1562 ****
--- 1585,1667 ----
sv_catpv(output, "}");
output = perl_eval_pv(SvPV(output, PL_na), TRUE);
return output;
+ }
+
+
+ HV *
+ plperl_spi_exec(char *query, int limit)
+ {
+ HV *ret_hv;
+ int spi_rv;
+
+ spi_rv = SPI_execute(query, plperl_current_prodesc->fn_readonly, limit);
+ ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed, spi_rv);
+
+ return ret_hv;
+ }
+
+ static HV *
+ plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
+ {
+ int i;
+ char *attname;
+ char *attdata;
+
+ HV *array;
+
+ array = newHV();
+
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ /************************************************************
+ * Get the attribute name
+ ************************************************************/
+ attname = tupdesc->attrs[i]->attname.data;
+
+ /************************************************************
+ * Get the attributes value
+ ************************************************************/
+ attdata = SPI_getvalue(tuple, tupdesc, i + 1);
+ if (attdata)
+ hv_store(array, attname, strlen(attname), newSVpv(attdata, 0), 0);
+ else
+ hv_store(array, attname, strlen(attname), newSVpv("undef", 0), 0);
+ }
+ return array;
+ }
+
+ static HV *
+ plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, int status)
+ {
+ HV *result;
+
+ result = newHV();
+
+ hv_store(result, "status", strlen("status"),
+ newSVpv((char *) SPI_result_code_string(status), 0), 0);
+ hv_store(result, "processed", strlen("processed"),
+ newSViv(processed), 0);
+
+ if (status == SPI_OK_SELECT)
+ {
+ if (processed)
+ {
+ AV *rows;
+ HV *row;
+ int i;
+
+ rows = newAV();
+ for (i = 0; i < processed; i++)
+ {
+ row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc);
+ av_store(rows, i, newRV_noinc((SV *) row));
+ }
+ hv_store(result, "rows", strlen("rows"),
+ newRV_noinc((SV *) rows), 0);
+ }
+ }
+
+ SPI_freetuptable(tuptable);
+
+ return result;
}
*** src/pl/plperl/spi_internal.c.orig Sun Aug 29 09:56:35 2004
--- src/pl/plperl/spi_internal.c Sun Sep 12 18:18:38 2004
***************
*** 1,15 ****
- #include "postgres.h"
- #include "executor/spi.h"
- #include "utils/syscache.h"
/*
* This kludge is necessary because of the conflicting
* definitions of 'DEBUG' between postgres and perl.
* we'll live.
*/
! #include "spi_internal.h"
! static HV *plperl_spi_execute_fetch_result(SPITupleTable *, int, int);
int
--- 1,12 ----
/*
* This kludge is necessary because of the conflicting
* definitions of 'DEBUG' between postgres and perl.
* we'll live.
*/
! #include "postgres.h"
! #include "spi_internal.h"
int
***************
*** 46,127 ****
spi_ERROR(void)
{
return ERROR;
- }
-
- HV *
- plperl_spi_exec(char *query, int limit)
- {
- HV *ret_hv;
- int spi_rv;
-
- spi_rv = SPI_exec(query, limit);
- ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed, spi_rv);
-
- return ret_hv;
- }
-
- static HV *
- plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
- {
- int i;
- char *attname;
- char *attdata;
-
- HV *array;
-
- array = newHV();
-
- for (i = 0; i < tupdesc->natts; i++)
- {
- /************************************************************
- * Get the attribute name
- ************************************************************/
- attname = tupdesc->attrs[i]->attname.data;
-
- /************************************************************
- * Get the attributes value
- ************************************************************/
- attdata = SPI_getvalue(tuple, tupdesc, i + 1);
- if (attdata)
- hv_store(array, attname, strlen(attname), newSVpv(attdata, 0), 0);
- else
- hv_store(array, attname, strlen(attname), newSVpv("undef", 0), 0);
- }
- return array;
- }
-
- static HV *
- plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, int status)
- {
- HV *result;
-
- result = newHV();
-
- hv_store(result, "status", strlen("status"),
- newSVpv((char *) SPI_result_code_string(status), 0), 0);
- hv_store(result, "processed", strlen("processed"),
- newSViv(processed), 0);
-
- if (status == SPI_OK_SELECT)
- {
- if (processed)
- {
- AV *rows;
- HV *row;
- int i;
-
- rows = newAV();
- for (i = 0; i < processed; i++)
- {
- row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc);
- av_store(rows, i, newRV_noinc((SV *) row));
- }
- hv_store(result, "rows", strlen("rows"),
- newRV_noinc((SV *) rows), 0);
- }
- }
-
- SPI_freetuptable(tuptable);
-
- return result;
}
--- 43,46 ----
*** src/pl/plperl/spi_internal.h.orig Sun Aug 29 09:56:35 2004
--- src/pl/plperl/spi_internal.h Sun Sep 12 18:18:39 2004
***************
*** 15,18 ****
--- 15,19 ----
int spi_ERROR(void);
+ /* this is actually in plperl.c */
HV *plperl_spi_exec(char *, int);
*** src/pl/plpgsql/src/pl_comp.c.orig Sun Aug 29 22:58:09 2004
--- src/pl/plpgsql/src/pl_comp.c Sun Sep 12 17:04:30 2004
***************
*** 578,583 ****
--- 578,586 ----
break;
}
+ /* Remember if function is STABLE/IMMUTABLE */
+ function->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
+
/*
* Create the magic FOUND variable.
*/
*** src/pl/plpgsql/src/pl_exec.c.orig Sun Aug 29 22:58:09 2004
--- src/pl/plpgsql/src/pl_exec.c Sun Sep 12 17:04:30 2004
***************
*** 897,902 ****
--- 897,903 ----
* sub-transaction
*/
MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
volatile bool caught = false;
int xrc;
***************
*** 907,918 ****
BeginInternalSubTransaction(NULL);
/* Want to run statements inside function's memory context */
MemoryContextSwitchTo(oldcontext);
if ((xrc = SPI_connect()) != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed: %s",
SPI_result_code_string(xrc));
PG_TRY();
! rc = exec_stmts(estate, block->body);
PG_CATCH();
{
ErrorData *edata;
--- 908,922 ----
BeginInternalSubTransaction(NULL);
/* Want to run statements inside function's memory context */
MemoryContextSwitchTo(oldcontext);
+
if ((xrc = SPI_connect()) != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed: %s",
SPI_result_code_string(xrc));
PG_TRY();
! {
! rc = exec_stmts(estate, block->body);
! }
PG_CATCH();
{
ErrorData *edata;
***************
*** 927,932 ****
--- 931,937 ----
/* Abort the inner transaction (and inner SPI connection) */
RollbackAndReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
SPI_pop();
***************
*** 958,965 ****
--- 963,973 ----
if ((xrc = SPI_finish()) != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed: %s",
SPI_result_code_string(xrc));
+
ReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
SPI_pop();
}
}
***************
*** 1984,1989 ****
--- 1992,1999 ----
estate->retistuple = func->fn_retistuple;
estate->retisset = func->fn_retset;
+ estate->readonly_func = func->fn_readonly;
+
estate->rettupdesc = NULL;
estate->exitlabel = NULL;
***************
*** 2019,2025 ****
static void
exec_eval_cleanup(PLpgSQL_execstate *estate)
{
! /* Clear result of a full SPI_exec */
if (estate->eval_tuptable != NULL)
SPI_freetuptable(estate->eval_tuptable);
estate->eval_tuptable = NULL;
--- 2029,2035 ----
static void
exec_eval_cleanup(PLpgSQL_execstate *estate)
{
! /* Clear result of a full SPI_execute */
if (estate->eval_tuptable != NULL)
SPI_freetuptable(estate->eval_tuptable);
estate->eval_tuptable = NULL;
***************
*** 2120,2126 ****
exec_prepare_plan(estate, expr);
/*
! * Now build up the values and nulls arguments for SPI_execp()
*/
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
nulls = (char *) palloc(expr->nparams * sizeof(char));
--- 2130,2136 ----
exec_prepare_plan(estate, expr);
/*
! * Now build up the values and nulls arguments for SPI_execute_plan()
*/
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
nulls = (char *) palloc(expr->nparams * sizeof(char));
***************
*** 2142,2148 ****
/*
* Execute the plan
*/
! rc = SPI_execp(expr->plan, values, nulls, 0);
switch (rc)
{
case SPI_OK_UTILITY:
--- 2152,2159 ----
/*
* Execute the plan
*/
! rc = SPI_execute_plan(expr->plan, values, nulls,
! estate->readonly_func, 0);
switch (rc)
{
case SPI_OK_UTILITY:
***************
*** 2168,2179 ****
errhint("If you want to discard the results, use PERFORM instead.")));
default:
! elog(ERROR, "SPI_execp failed executing query \"%s\": %s",
expr->query, SPI_result_code_string(rc));
}
/*
! * Release any result tuples from SPI_execp (probably shouldn't be
* any)
*/
SPI_freetuptable(SPI_tuptable);
--- 2179,2190 ----
errhint("If you want to discard the results, use PERFORM instead.")));
default:
! elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
expr->query, SPI_result_code_string(rc));
}
/*
! * Release any result tuples from SPI_execute_plan (probably shouldn't be
* any)
*/
SPI_freetuptable(SPI_tuptable);
***************
*** 2220,2230 ****
exec_eval_cleanup(estate);
/*
! * Call SPI_exec() without preparing a saved plan. The returncode can
* be any standard OK. Note that while a SELECT is allowed, its
* results will be discarded.
*/
! exec_res = SPI_exec(querystr, 0);
switch (exec_res)
{
case SPI_OK_SELECT:
--- 2231,2241 ----
exec_eval_cleanup(estate);
/*
! * Call SPI_execute() without preparing a saved plan. The returncode can
* be any standard OK. Note that while a SELECT is allowed, its
* results will be discarded.
*/
! exec_res = SPI_execute(querystr, estate->readonly_func, 0);
switch (exec_res)
{
case SPI_OK_SELECT:
***************
*** 2249,2255 ****
* behavior is not consistent with SELECT INTO in a normal
* plpgsql context. (We need to reimplement EXECUTE to parse
* the string as a plpgsql command, not just feed it to
! * SPI_exec.) However, CREATE AS should be allowed ... and
* since it produces the same parsetree as SELECT INTO,
* there's no way to tell the difference except to look at the
* source text. Wotta kluge!
--- 2260,2266 ----
* behavior is not consistent with SELECT INTO in a normal
* plpgsql context. (We need to reimplement EXECUTE to parse
* the string as a plpgsql command, not just feed it to
! * SPI_execute.) However, CREATE AS should be allowed ... and
* since it produces the same parsetree as SELECT INTO,
* there's no way to tell the difference except to look at the
* source text. Wotta kluge!
***************
*** 2284,2295 ****
errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
default:
! elog(ERROR, "SPI_exec failed executing query \"%s\": %s",
querystr, SPI_result_code_string(exec_res));
break;
}
! /* Release any result from SPI_exec, as well as the querystring */
SPI_freetuptable(SPI_tuptable);
pfree(querystr);
--- 2295,2306 ----
errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
default:
! elog(ERROR, "SPI_execute failed executing query \"%s\": %s",
querystr, SPI_result_code_string(exec_res));
break;
}
! /* Release any result from SPI_execute, as well as the querystring */
SPI_freetuptable(SPI_tuptable);
pfree(querystr);
***************
*** 3470,3476 ****
exec_prepare_plan(estate, expr);
/*
! * Now build up the values and nulls arguments for SPI_execp()
*/
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
nulls = (char *) palloc(expr->nparams * sizeof(char));
--- 3481,3487 ----
exec_prepare_plan(estate, expr);
/*
! * Now build up the values and nulls arguments for SPI_execute_plan()
*/
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
nulls = (char *) palloc(expr->nparams * sizeof(char));
***************
*** 3506,3512 ****
/*
* Execute the query
*/
! rc = SPI_execp(expr->plan, values, nulls, maxtuples);
if (rc != SPI_OK_SELECT)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
--- 3517,3524 ----
/*
* Execute the query
*/
! rc = SPI_execute_plan(expr->plan, values, nulls,
! estate->readonly_func, maxtuples);
if (rc != SPI_OK_SELECT)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
*** src/pl/plpgsql/src/plpgsql.h.orig Sun Aug 29 22:58:09 2004
--- src/pl/plpgsql/src/plpgsql.h Sun Sep 12 17:04:30 2004
***************
*** 585,590 ****
--- 585,591 ----
Oid fn_rettypioparam;
bool fn_retistuple;
bool fn_retset;
+ bool fn_readonly;
int fn_nargs;
int fn_argvarnos[FUNC_MAX_ARGS];
***************
*** 614,619 ****
--- 615,622 ----
Oid fn_rettype; /* info about declared function rettype */
bool retistuple;
bool retisset;
+
+ bool readonly_func;
TupleDesc rettupdesc;
char *exitlabel;
*** src/pl/plpython/plpython.c.orig Sun Aug 29 22:58:10 2004
--- src/pl/plpython/plpython.c Sun Sep 12 17:57:31 2004
***************
*** 131,136 ****
--- 131,137 ----
char *pyname; /* Python name of procedure */
TransactionId fn_xmin;
CommandId fn_cmin;
+ bool fn_readonly;
PLyTypeInfo result; /* also used to store info for trigger
* tuple type */
PLyTypeInfo args[FUNC_MAX_ARGS];
***************
*** 257,267 ****
static int PLy_first_call = 1;
/*
! * Last function called by postgres backend
! *
! * XXX replace this with errcontext mechanism
*/
! static PLyProcedure *PLy_last_procedure = NULL;
/*
* When a callback from Python into PG incurs an error, we temporarily store
--- 258,266 ----
static int PLy_first_call = 1;
/*
! * Currently active plpython function
*/
! static PLyProcedure *PLy_curr_procedure = NULL;
/*
* When a callback from Python into PG incurs an error, we temporarily store
***************
*** 322,327 ****
--- 321,327 ----
plpython_call_handler(PG_FUNCTION_ARGS)
{
Datum retval;
+ PLyProcedure *save_curr_proc;
PLyProcedure *volatile proc = NULL;
PLy_init_all();
***************
*** 329,334 ****
--- 329,336 ----
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "could not connect to SPI manager");
+ save_curr_proc = PLy_curr_procedure;
+
PG_TRY();
{
if (CALLED_AS_TRIGGER(fcinfo))
***************
*** 338,354 ****
--- 340,359 ----
proc = PLy_procedure_get(fcinfo,
RelationGetRelid(tdata->tg_relation));
+ PLy_curr_procedure = proc;
trv = PLy_trigger_handler(fcinfo, proc);
retval = PointerGetDatum(trv);
}
else
{
proc = PLy_procedure_get(fcinfo, InvalidOid);
+ PLy_curr_procedure = proc;
retval = PLy_function_handler(fcinfo, proc);
}
}
PG_CATCH();
{
+ PLy_curr_procedure = save_curr_proc;
if (proc)
{
/* note: Py_DECREF needs braces around it, as of 2003/08 */
***************
*** 359,364 ****
--- 364,371 ----
}
PG_END_TRY();
+ PLy_curr_procedure = save_curr_proc;
+
Py_DECREF(proc->me);
return retval;
***************
*** 795,808 ****
PLy_procedure_call(PLyProcedure * proc, char *kargs, PyObject * vargs)
{
PyObject *rv;
- PLyProcedure *current;
- current = PLy_last_procedure;
- PLy_last_procedure = proc;
PyDict_SetItemString(proc->globals, kargs, vargs);
rv = PyEval_EvalCode((PyCodeObject *) proc->code,
proc->globals, proc->globals);
- PLy_last_procedure = current;
/*
* If there was an error in a PG callback, propagate that no matter
--- 802,811 ----
***************
*** 1005,1010 ****
--- 1008,1016 ----
strcpy(proc->pyname, procName);
proc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
proc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
+ /* Remember if function is STABLE/IMMUTABLE */
+ proc->fn_readonly =
+ (procStruct->provolatile != PROVOLATILE_VOLATILE);
PLy_typeinfo_init(&proc->result);
for (i = 0; i < FUNC_MAX_ARGS; i++)
PLy_typeinfo_init(&proc->args[i]);
***************
*** 1935,1941 ****
PyErr_SetString(PLy_exc_spi_error,
"Unknown error in PLy_spi_prepare");
/* XXX this oughta be replaced with errcontext mechanism */
! PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_last_procedure));
return NULL;
}
PG_END_TRY();
--- 1941,1948 ----
PyErr_SetString(PLy_exc_spi_error,
"Unknown error in PLy_spi_prepare");
/* XXX this oughta be replaced with errcontext mechanism */
! PLy_elog(WARNING, "in function %s:",
! PLy_procedure_name(PLy_curr_procedure));
return NULL;
}
PG_END_TRY();
***************
*** 2054,2060 ****
}
}
! rv = SPI_execp(plan->plan, plan->values, nulls, limit);
pfree(nulls);
}
--- 2061,2068 ----
}
}
! rv = SPI_execute_plan(plan->plan, plan->values, nulls,
! PLy_curr_procedure->fn_readonly, limit);
pfree(nulls);
}
***************
*** 2080,2086 ****
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_error,
"Unknown error in PLy_spi_execute_plan");
! PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_last_procedure));
return NULL;
}
PG_END_TRY();
--- 2088,2096 ----
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_error,
"Unknown error in PLy_spi_execute_plan");
! /* XXX this oughta be replaced with errcontext mechanism */
! PLy_elog(WARNING, "in function %s:",
! PLy_procedure_name(PLy_curr_procedure));
return NULL;
}
PG_END_TRY();
***************
*** 2098,2104 ****
if (rv < 0)
{
PLy_exception_set(PLy_exc_spi_error,
! "SPI_execp failed: %s",
SPI_result_code_string(rv));
return NULL;
}
--- 2108,2114 ----
if (rv < 0)
{
PLy_exception_set(PLy_exc_spi_error,
! "SPI_execute_plan failed: %s",
SPI_result_code_string(rv));
return NULL;
}
***************
*** 2114,2120 ****
oldcontext = CurrentMemoryContext;
PG_TRY();
! rv = SPI_exec(query, limit);
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
--- 2124,2132 ----
oldcontext = CurrentMemoryContext;
PG_TRY();
! {
! rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit);
! }
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
***************
*** 2123,2129 ****
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_spi_error,
"Unknown error in PLy_spi_execute_query");
! PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_last_procedure));
return NULL;
}
PG_END_TRY();
--- 2135,2143 ----
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_spi_error,
"Unknown error in PLy_spi_execute_query");
! /* XXX this oughta be replaced with errcontext mechanism */
! PLy_elog(WARNING, "in function %s:",
! PLy_procedure_name(PLy_curr_procedure));
return NULL;
}
PG_END_TRY();
***************
*** 2131,2137 ****
if (rv < 0)
{
PLy_exception_set(PLy_exc_spi_error,
! "SPI_exec failed: %s",
SPI_result_code_string(rv));
return NULL;
}
--- 2145,2151 ----
if (rv < 0)
{
PLy_exception_set(PLy_exc_spi_error,
! "SPI_execute failed: %s",
SPI_result_code_string(rv));
return NULL;
}
*** src/pl/tcl/pltcl.c.orig Sun Aug 29 22:58:11 2004
--- src/pl/tcl/pltcl.c Sun Sep 12 17:42:16 2004
***************
*** 87,99 ****
pfree(_pltcl_utf_dst); } while (0)
#define UTF_U2E(x) (_pltcl_utf_dst=utf_u2e(_pltcl_utf_src=(x)))
#define UTF_E2U(x) (_pltcl_utf_dst=utf_e2u(_pltcl_utf_src=(x)))
! #else /* PLTCL_UTF */
#define UTF_BEGIN
#define UTF_END
#define UTF_U2E(x) (x)
#define UTF_E2U(x) (x)
#endif /* PLTCL_UTF */
/**********************************************************************
* The information we cache about loaded procedures
**********************************************************************/
--- 87,103 ----
pfree(_pltcl_utf_dst); } while (0)
#define UTF_U2E(x) (_pltcl_utf_dst=utf_u2e(_pltcl_utf_src=(x)))
#define UTF_E2U(x) (_pltcl_utf_dst=utf_e2u(_pltcl_utf_src=(x)))
!
! #else /* !PLTCL_UTF */
!
#define UTF_BEGIN
#define UTF_END
#define UTF_U2E(x) (x)
#define UTF_E2U(x) (x)
+
#endif /* PLTCL_UTF */
+
/**********************************************************************
* The information we cache about loaded procedures
**********************************************************************/
***************
*** 102,107 ****
--- 106,112 ----
char *proname;
TransactionId fn_xmin;
CommandId fn_cmin;
+ bool fn_readonly;
bool lanpltrusted;
FmgrInfo result_in_func;
Oid result_typioparam;
***************
*** 137,143 ****
--- 142,151 ----
static Tcl_HashTable *pltcl_proc_hash = NULL;
static Tcl_HashTable *pltcl_norm_query_hash = NULL;
static Tcl_HashTable *pltcl_safe_query_hash = NULL;
+
+ /* these are saved and restored by pltcl_call_handler */
static FunctionCallInfo pltcl_current_fcinfo = NULL;
+ static pltcl_proc_desc *pltcl_current_prodesc = NULL;
/*
* When a callback from Tcl into PG incurs an error, we temporarily store
***************
*** 179,189 ****
static int pltcl_returnnull(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
! static int pltcl_SPI_exec(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
static int pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
! static int pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
static int pltcl_SPI_lastoid(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
--- 187,197 ----
static int pltcl_returnnull(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
! static int pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
static int pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
! static int pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
static int pltcl_SPI_lastoid(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
***************
*** 307,317 ****
pltcl_returnnull, NULL, NULL);
Tcl_CreateCommand(interp, "spi_exec",
! pltcl_SPI_exec, NULL, NULL);
Tcl_CreateCommand(interp, "spi_prepare",
pltcl_SPI_prepare, NULL, NULL);
Tcl_CreateCommand(interp, "spi_execp",
! pltcl_SPI_execp, NULL, NULL);
Tcl_CreateCommand(interp, "spi_lastoid",
pltcl_SPI_lastoid, NULL, NULL);
}
--- 315,325 ----
pltcl_returnnull, NULL, NULL);
Tcl_CreateCommand(interp, "spi_exec",
! pltcl_SPI_execute, NULL, NULL);
Tcl_CreateCommand(interp, "spi_prepare",
pltcl_SPI_prepare, NULL, NULL);
Tcl_CreateCommand(interp, "spi_execp",
! pltcl_SPI_execute_plan, NULL, NULL);
Tcl_CreateCommand(interp, "spi_lastoid",
pltcl_SPI_lastoid, NULL, NULL);
}
***************
*** 334,341 ****
/************************************************************
* Check if table pltcl_modules exists
************************************************************/
! spi_rc = SPI_exec("select 1 from pg_catalog.pg_class "
! "where relname = 'pltcl_modules'", 1);
SPI_freetuptable(SPI_tuptable);
if (spi_rc != SPI_OK_SELECT)
elog(ERROR, "select from pg_class failed");
--- 342,350 ----
/************************************************************
* Check if table pltcl_modules exists
************************************************************/
! spi_rc = SPI_execute("select 1 from pg_catalog.pg_class "
! "where relname = 'pltcl_modules'",
! false, 1);
SPI_freetuptable(SPI_tuptable);
if (spi_rc != SPI_OK_SELECT)
elog(ERROR, "select from pg_class failed");
***************
*** 348,356 ****
************************************************************/
Tcl_DStringInit(&unknown_src);
! spi_rc = SPI_exec("select modseq, modsrc from pltcl_modules "
! "where modname = 'unknown' "
! "order by modseq", 0);
if (spi_rc != SPI_OK_SELECT)
elog(ERROR, "select from pltcl_modules failed");
--- 357,366 ----
************************************************************/
Tcl_DStringInit(&unknown_src);
! spi_rc = SPI_execute("select modseq, modsrc from pltcl_modules "
! "where modname = 'unknown' "
! "order by modseq",
! false, 0);
if (spi_rc != SPI_OK_SELECT)
elog(ERROR, "select from pltcl_modules failed");
***************
*** 405,434 ****
{
Datum retval;
FunctionCallInfo save_fcinfo;
! /************************************************************
* Initialize interpreters if first time through
! ************************************************************/
pltcl_init_all();
! /************************************************************
! * Determine if called as function or trigger and
! * call appropriate subhandler
! ************************************************************/
save_fcinfo = pltcl_current_fcinfo;
! if (CALLED_AS_TRIGGER(fcinfo))
{
! pltcl_current_fcinfo = NULL;
! retval = PointerGetDatum(pltcl_trigger_handler(fcinfo));
}
! else
{
! pltcl_current_fcinfo = fcinfo;
! retval = pltcl_func_handler(fcinfo);
}
pltcl_current_fcinfo = save_fcinfo;
return retval;
}
--- 415,460 ----
{
Datum retval;
FunctionCallInfo save_fcinfo;
+ pltcl_proc_desc *save_prodesc;
! /*
* Initialize interpreters if first time through
! */
pltcl_init_all();
! /*
! * Ensure that static pointers are saved/restored properly
! */
save_fcinfo = pltcl_current_fcinfo;
+ save_prodesc = pltcl_current_prodesc;
! PG_TRY();
{
! /*
! * Determine if called as function or trigger and
! * call appropriate subhandler
! */
! if (CALLED_AS_TRIGGER(fcinfo))
! {
! pltcl_current_fcinfo = NULL;
! retval = PointerGetDatum(pltcl_trigger_handler(fcinfo));
! }
! else
! {
! pltcl_current_fcinfo = fcinfo;
! retval = pltcl_func_handler(fcinfo);
! }
}
! PG_CATCH();
{
! pltcl_current_fcinfo = save_fcinfo;
! pltcl_current_prodesc = save_prodesc;
! PG_RE_THROW();
}
+ PG_END_TRY();
pltcl_current_fcinfo = save_fcinfo;
+ pltcl_current_prodesc = save_prodesc;
return retval;
}
***************
*** 467,472 ****
--- 493,500 ----
/* Find or compile the function */
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid);
+ pltcl_current_prodesc = prodesc;
+
if (prodesc->lanpltrusted)
interp = pltcl_safe_interp;
else
***************
*** 643,648 ****
--- 671,678 ----
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
RelationGetRelid(trigdata->tg_relation));
+ pltcl_current_prodesc = prodesc;
+
if (prodesc->lanpltrusted)
interp = pltcl_safe_interp;
else
***************
*** 1030,1035 ****
--- 1060,1069 ----
prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
+ /* Remember if function is STABLE/IMMUTABLE */
+ prodesc->fn_readonly =
+ (procStruct->provolatile != PROVOLATILE_VOLATILE);
+
/************************************************************
* Lookup the pg_language tuple by Oid
************************************************************/
***************
*** 1336,1342 ****
/**********************************************************************
* pltcl_quote() - quote literal strings that are to
! * be used in SPI_exec query strings
**********************************************************************/
static int
pltcl_quote(ClientData cdata, Tcl_Interp *interp,
--- 1370,1376 ----
/**********************************************************************
* pltcl_quote() - quote literal strings that are to
! * be used in SPI_execute query strings
**********************************************************************/
static int
pltcl_quote(ClientData cdata, Tcl_Interp *interp,
***************
*** 1484,1495 ****
/**********************************************************************
! * pltcl_SPI_exec() - The builtin SPI_exec command
* for the Tcl interpreter
**********************************************************************/
static int
! pltcl_SPI_exec(ClientData cdata, Tcl_Interp *interp,
! int argc, CONST84 char *argv[])
{
volatile int my_rc;
int spi_rc;
--- 1518,1529 ----
/**********************************************************************
! * pltcl_SPI_execute() - The builtin SPI_execute command
* for the Tcl interpreter
**********************************************************************/
static int
! pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
! int argc, CONST84 char *argv[])
{
volatile int my_rc;
int spi_rc;
***************
*** 1570,1576 ****
PG_TRY();
{
UTF_BEGIN;
! spi_rc = SPI_exec(UTF_U2E(argv[query_idx]), count);
UTF_END;
}
PG_CATCH();
--- 1604,1611 ----
PG_TRY();
{
UTF_BEGIN;
! spi_rc = SPI_execute(UTF_U2E(argv[query_idx]),
! pltcl_current_prodesc->fn_readonly, count);
UTF_END;
}
PG_CATCH();
***************
*** 1603,1609 ****
break;
default:
! Tcl_AppendResult(interp, "pltcl: SPI_exec failed: ",
SPI_result_code_string(spi_rc), NULL);
SPI_freetuptable(SPI_tuptable);
return TCL_ERROR;
--- 1638,1644 ----
break;
default:
! Tcl_AppendResult(interp, "pltcl: SPI_execute failed: ",
SPI_result_code_string(spi_rc), NULL);
SPI_freetuptable(SPI_tuptable);
return TCL_ERROR;
***************
*** 1840,1850 ****
/**********************************************************************
! * pltcl_SPI_execp() - Execute a prepared plan
**********************************************************************/
static int
! pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
! int argc, CONST84 char *argv[])
{
volatile int my_rc;
int spi_rc;
--- 1875,1885 ----
/**********************************************************************
! * pltcl_SPI_execute_plan() - Execute a prepared plan
**********************************************************************/
static int
! pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
! int argc, CONST84 char *argv[])
{
volatile int my_rc;
int spi_rc;
***************
*** 1992,1998 ****
}
/************************************************************
! * Setup the value array for the SPI_execp() using
* the type specific input functions
************************************************************/
oldcontext = CurrentMemoryContext;
--- 2027,2033 ----
}
/************************************************************
! * Setup the value array for SPI_execute_plan() using
* the type specific input functions
************************************************************/
oldcontext = CurrentMemoryContext;
***************
*** 2046,2052 ****
************************************************************/
oldcontext = CurrentMemoryContext;
PG_TRY();
! spi_rc = SPI_execp(qdesc->plan, argvalues, nulls, count);
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
--- 2081,2090 ----
************************************************************/
oldcontext = CurrentMemoryContext;
PG_TRY();
! {
! spi_rc = SPI_execute_plan(qdesc->plan, argvalues, nulls,
! pltcl_current_prodesc->fn_readonly, count);
! }
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
***************
*** 2058,2064 ****
PG_END_TRY();
/************************************************************
! * Check the return code from SPI_execp()
************************************************************/
switch (spi_rc)
{
--- 2096,2102 ----
PG_END_TRY();
/************************************************************
! * Check the return code from SPI_execute_plan()
************************************************************/
switch (spi_rc)
{
***************
*** 2080,2086 ****
break;
default:
! Tcl_AppendResult(interp, "pltcl: SPI_execp failed: ",
SPI_result_code_string(spi_rc), NULL);
SPI_freetuptable(SPI_tuptable);
return TCL_ERROR;
--- 2118,2124 ----
break;
default:
! Tcl_AppendResult(interp, "pltcl: SPI_execute_plan failed: ",
SPI_result_code_string(spi_rc), NULL);
SPI_freetuptable(SPI_tuptable);
return TCL_ERROR;
*** src/test/regress/expected/transactions.out.orig Mon Sep 6 13:46:34 2004
--- src/test/regress/expected/transactions.out Sun Sep 12 17:27:18 2004
***************
*** 374,379 ****
--- 374,457 ----
FETCH 10 FROM c;
ERROR: portal "c" cannot be run
COMMIT;
+ --
+ -- Check that "stable" functions are really stable. They should not be
+ -- able to see the partial results of the calling query. (Ideally we would
+ -- also check that they don't see commits of concurrent transactions, but
+ -- that's a mite hard to do within the limitations of pg_regress.)
+ --
+ select * from xacttest;
+ a | b
+ -----+---------
+ 56 | 7.8
+ 100 | 99.097
+ 0 | 0.09561
+ 42 | 324.78
+ 777 | 777.777
+ (5 rows)
+
+ create or replace function max_xacttest() returns smallint language sql as
+ 'select max(a) from xacttest' stable;
+ begin;
+ update xacttest set a = max_xacttest() + 10 where a > 0;
+ select * from xacttest;
+ a | b
+ -----+---------
+ 0 | 0.09561
+ 787 | 7.8
+ 787 | 99.097
+ 787 | 324.78
+ 787 | 777.777
+ (5 rows)
+
+ rollback;
+ -- But a volatile function can see the partial results of the calling query
+ create or replace function max_xacttest() returns smallint language sql as
+ 'select max(a) from xacttest' volatile;
+ begin;
+ update xacttest set a = max_xacttest() + 10 where a > 0;
+ select * from xacttest;
+ a | b
+ -----+---------
+ 0 | 0.09561
+ 787 | 7.8
+ 797 | 99.097
+ 807 | 324.78
+ 817 | 777.777
+ (5 rows)
+
+ rollback;
+ -- Now the same test with plpgsql (since it depends on SPI which is different)
+ create or replace function max_xacttest() returns smallint language plpgsql as
+ 'begin return max(a) from xacttest; end' stable;
+ begin;
+ update xacttest set a = max_xacttest() + 10 where a > 0;
+ select * from xacttest;
+ a | b
+ -----+---------
+ 0 | 0.09561
+ 787 | 7.8
+ 787 | 99.097
+ 787 | 324.78
+ 787 | 777.777
+ (5 rows)
+
+ rollback;
+ create or replace function max_xacttest() returns smallint language plpgsql as
+ 'begin return max(a) from xacttest; end' volatile;
+ begin;
+ update xacttest set a = max_xacttest() + 10 where a > 0;
+ select * from xacttest;
+ a | b
+ -----+---------
+ 0 | 0.09561
+ 787 | 7.8
+ 797 | 99.097
+ 807 | 324.78
+ 817 | 777.777
+ (5 rows)
+
+ rollback;
-- test case for problems with dropping an open relation during abort
BEGIN;
savepoint x;
*** src/test/regress/sql/transactions.sql.orig Mon Sep 6 13:44:08 2004
--- src/test/regress/sql/transactions.sql Sun Sep 12 17:25:11 2004
***************
*** 231,236 ****
--- 231,279 ----
FETCH 10 FROM c;
COMMIT;
+ --
+ -- Check that "stable" functions are really stable. They should not be
+ -- able to see the partial results of the calling query. (Ideally we would
+ -- also check that they don't see commits of concurrent transactions, but
+ -- that's a mite hard to do within the limitations of pg_regress.)
+ --
+ select * from xacttest;
+
+ create or replace function max_xacttest() returns smallint language sql as
+ 'select max(a) from xacttest' stable;
+
+ begin;
+ update xacttest set a = max_xacttest() + 10 where a > 0;
+ select * from xacttest;
+ rollback;
+
+ -- But a volatile function can see the partial results of the calling query
+ create or replace function max_xacttest() returns smallint language sql as
+ 'select max(a) from xacttest' volatile;
+
+ begin;
+ update xacttest set a = max_xacttest() + 10 where a > 0;
+ select * from xacttest;
+ rollback;
+
+ -- Now the same test with plpgsql (since it depends on SPI which is different)
+ create or replace function max_xacttest() returns smallint language plpgsql as
+ 'begin return max(a) from xacttest; end' stable;
+
+ begin;
+ update xacttest set a = max_xacttest() + 10 where a > 0;
+ select * from xacttest;
+ rollback;
+
+ create or replace function max_xacttest() returns smallint language plpgsql as
+ 'begin return max(a) from xacttest; end' volatile;
+
+ begin;
+ update xacttest set a = max_xacttest() + 10 where a > 0;
+ select * from xacttest;
+ rollback;
+
+
-- test case for problems with dropping an open relation during abort
BEGIN;
savepoint x;