1998-08-22 14:38:39 +02:00
|
|
|
/**********************************************************************
|
|
|
|
* pl_exec.c - Executor for the PL/pgSQL
|
|
|
|
* procedural language
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2004-09-13 22:10:13 +02:00
|
|
|
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.119 2004/09/13 20:09:20 tgl Exp $
|
1998-08-22 14:38:39 +02:00
|
|
|
*
|
1998-09-01 06:40:42 +02:00
|
|
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
1998-08-22 14:38:39 +02:00
|
|
|
*
|
1998-09-01 06:40:42 +02:00
|
|
|
* The author hereby grants permission to use, copy, modify,
|
|
|
|
* distribute, and license this software and its documentation
|
|
|
|
* for any purpose, provided that existing copyright notices are
|
|
|
|
* retained in all copies and that this notice is included
|
|
|
|
* verbatim in any distributions. No written agreement, license,
|
|
|
|
* or royalty fee is required for any of the authorized uses.
|
|
|
|
* Modifications to this software may be copyrighted by their
|
|
|
|
* author and need not follow the licensing terms described
|
|
|
|
* here, provided that the new terms are clearly indicated on
|
|
|
|
* the first page of each file where they apply.
|
1998-08-22 14:38:39 +02:00
|
|
|
*
|
1998-09-01 06:40:42 +02:00
|
|
|
* IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY
|
|
|
|
* PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
|
|
|
|
* CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
|
|
|
|
* SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
|
|
|
|
* IF THE AUTHOR HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
|
|
|
* DAMAGE.
|
1998-08-22 14:38:39 +02:00
|
|
|
*
|
1998-09-01 06:40:42 +02:00
|
|
|
* THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY
|
|
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
|
|
* PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON
|
|
|
|
* AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAVE NO
|
|
|
|
* OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
|
|
|
|
* ENHANCEMENTS, OR MODIFICATIONS.
|
1998-08-22 14:38:39 +02:00
|
|
|
*
|
|
|
|
**********************************************************************/
|
|
|
|
|
|
|
|
#include "plpgsql.h"
|
|
|
|
#include "pl.tab.h"
|
|
|
|
|
2002-09-05 02:43:07 +02:00
|
|
|
#include <ctype.h>
|
|
|
|
|
2000-07-12 04:37:39 +02:00
|
|
|
#include "access/heapam.h"
|
|
|
|
#include "catalog/pg_proc.h"
|
|
|
|
#include "catalog/pg_type.h"
|
1999-01-27 17:15:22 +01:00
|
|
|
#include "executor/spi_priv.h"
|
2002-08-30 02:28:41 +02:00
|
|
|
#include "funcapi.h"
|
2000-12-01 21:43:59 +01:00
|
|
|
#include "optimizer/clauses.h"
|
2000-08-13 04:50:35 +02:00
|
|
|
#include "parser/parse_expr.h"
|
1999-04-20 04:19:59 +02:00
|
|
|
#include "tcop/tcopprot.h"
|
2003-03-25 04:16:41 +01:00
|
|
|
#include "utils/array.h"
|
2000-07-12 04:37:39 +02:00
|
|
|
#include "utils/builtins.h"
|
|
|
|
#include "utils/lsyscache.h"
|
2004-04-01 23:28:47 +02:00
|
|
|
#include "utils/typcache.h"
|
1998-08-22 14:38:39 +02:00
|
|
|
|
|
|
|
|
2003-08-04 02:43:34 +02:00
|
|
|
static const char *const raise_skip_msg = "RAISE";
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2004-07-31 09:39:21 +02:00
|
|
|
|
2003-09-29 01:37:45 +02:00
|
|
|
/*
|
|
|
|
* All plpgsql function executions within a single transaction share
|
|
|
|
* the same executor EState for evaluating "simple" expressions. Each
|
|
|
|
* function call creates its own "eval_econtext" ExprContext within this
|
2004-08-29 07:07:03 +02:00
|
|
|
* estate. We destroy the estate at transaction shutdown to ensure there
|
2003-09-29 01:37:45 +02:00
|
|
|
* is no permanent leakage of memory (especially for xact abort case).
|
|
|
|
*
|
|
|
|
* If a simple PLpgSQL_expr has been used in the current xact, it is
|
|
|
|
* linked into the active_simple_exprs list.
|
|
|
|
*/
|
|
|
|
static EState *simple_eval_estate = NULL;
|
|
|
|
static PLpgSQL_expr *active_simple_exprs = NULL;
|
|
|
|
|
1998-08-22 14:38:39 +02:00
|
|
|
/************************************************************
|
|
|
|
* Local function forward declarations
|
|
|
|
************************************************************/
|
2003-04-24 23:16:45 +02:00
|
|
|
static void plpgsql_exec_error_callback(void *arg);
|
2004-08-30 04:54:42 +02:00
|
|
|
static PLpgSQL_var *copy_var(PLpgSQL_var *var);
|
|
|
|
static PLpgSQL_rec *copy_rec(PLpgSQL_rec *rec);
|
|
|
|
|
|
|
|
static int exec_stmt_block(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_block *block);
|
|
|
|
static int exec_stmts(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmts *stmts);
|
|
|
|
static int exec_stmt(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt *stmt);
|
|
|
|
static int exec_stmt_assign(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_assign *stmt);
|
|
|
|
static int exec_stmt_perform(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_perform *stmt);
|
|
|
|
static int exec_stmt_getdiag(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_getdiag *stmt);
|
|
|
|
static int exec_stmt_if(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_if *stmt);
|
|
|
|
static int exec_stmt_loop(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_loop *stmt);
|
|
|
|
static int exec_stmt_while(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_while *stmt);
|
|
|
|
static int exec_stmt_fori(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_fori *stmt);
|
|
|
|
static int exec_stmt_fors(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_fors *stmt);
|
|
|
|
static int exec_stmt_select(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_select *stmt);
|
|
|
|
static int exec_stmt_open(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_open *stmt);
|
|
|
|
static int exec_stmt_fetch(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_fetch *stmt);
|
|
|
|
static int exec_stmt_close(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_close *stmt);
|
|
|
|
static int exec_stmt_exit(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_exit *stmt);
|
|
|
|
static int exec_stmt_return(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_return *stmt);
|
|
|
|
static int exec_stmt_return_next(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_return_next *stmt);
|
|
|
|
static int exec_stmt_raise(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_raise *stmt);
|
|
|
|
static int exec_stmt_execsql(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_execsql *stmt);
|
|
|
|
static int exec_stmt_dynexecute(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_dynexecute *stmt);
|
|
|
|
static int exec_stmt_dynfors(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_dynfors *stmt);
|
|
|
|
|
|
|
|
static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_function *func,
|
2002-09-04 22:31:48 +02:00
|
|
|
ReturnSetInfo *rsi);
|
2004-08-30 04:54:42 +02:00
|
|
|
static void exec_eval_cleanup(PLpgSQL_execstate *estate);
|
2001-08-02 23:31:23 +02:00
|
|
|
|
2004-08-30 04:54:42 +02:00
|
|
|
static void exec_prepare_plan(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_expr *expr);
|
1999-05-25 18:15:34 +02:00
|
|
|
static bool exec_simple_check_node(Node *node);
|
2004-08-30 04:54:42 +02:00
|
|
|
static void exec_simple_check_plan(PLpgSQL_expr *expr);
|
|
|
|
static Datum exec_eval_simple_expr(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_expr *expr,
|
1999-05-25 18:15:34 +02:00
|
|
|
bool *isNull,
|
|
|
|
Oid *rettype);
|
1999-01-27 17:15:22 +01:00
|
|
|
|
2004-08-30 04:54:42 +02:00
|
|
|
static void exec_assign_expr(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_datum *target,
|
|
|
|
PLpgSQL_expr *expr);
|
|
|
|
static void exec_assign_value(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_datum *target,
|
1998-09-01 06:40:42 +02:00
|
|
|
Datum value, Oid valtype, bool *isNull);
|
2004-08-30 04:54:42 +02:00
|
|
|
static void exec_eval_datum(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_datum *datum,
|
2003-08-04 02:43:34 +02:00
|
|
|
Oid expectedtypeid,
|
|
|
|
Oid *typeid,
|
|
|
|
Datum *value,
|
|
|
|
bool *isnull);
|
2004-08-30 04:54:42 +02:00
|
|
|
static int exec_eval_integer(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_expr *expr,
|
2004-08-29 07:07:03 +02:00
|
|
|
bool *isNull);
|
2004-08-30 04:54:42 +02:00
|
|
|
static bool exec_eval_boolean(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_expr *expr,
|
2004-08-29 07:07:03 +02:00
|
|
|
bool *isNull);
|
2004-08-30 04:54:42 +02:00
|
|
|
static Datum exec_eval_expr(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_expr *expr,
|
1998-09-01 06:40:42 +02:00
|
|
|
bool *isNull,
|
|
|
|
Oid *rettype);
|
2004-08-30 04:54:42 +02:00
|
|
|
static int exec_run_select(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_expr *expr, int maxtuples, Portal *portalP);
|
|
|
|
static void exec_move_row(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_rec *rec,
|
|
|
|
PLpgSQL_row *row,
|
1998-09-01 06:40:42 +02:00
|
|
|
HeapTuple tup, TupleDesc tupdesc);
|
2004-08-30 04:54:42 +02:00
|
|
|
static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_row *row,
|
2004-08-29 07:07:03 +02:00
|
|
|
TupleDesc tupdesc);
|
2003-10-01 23:47:42 +02:00
|
|
|
static char *convert_value_to_string(Datum value, Oid valtype);
|
1998-09-01 06:40:42 +02:00
|
|
|
static Datum exec_cast_value(Datum value, Oid valtype,
|
|
|
|
Oid reqtype,
|
1998-08-22 14:38:39 +02:00
|
|
|
FmgrInfo *reqinput,
|
2004-06-06 02:41:28 +02:00
|
|
|
Oid reqtypioparam,
|
2000-01-15 23:43:25 +01:00
|
|
|
int32 reqtypmod,
|
1998-08-22 14:38:39 +02:00
|
|
|
bool *isnull);
|
2003-03-25 04:16:41 +01:00
|
|
|
static Datum exec_simple_cast_value(Datum value, Oid valtype,
|
2003-08-04 02:43:34 +02:00
|
|
|
Oid reqtype, int32 reqtypmod,
|
|
|
|
bool *isnull);
|
2004-08-30 04:54:42 +02:00
|
|
|
static void exec_init_tuple_store(PLpgSQL_execstate *estate);
|
2002-08-31 01:59:46 +02:00
|
|
|
static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
|
2004-08-30 04:54:42 +02:00
|
|
|
static void exec_set_found(PLpgSQL_execstate *estate, bool state);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* plpgsql_exec_function Called by the call handler for
|
|
|
|
* function execution.
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
Datum
|
2004-08-30 04:54:42 +02:00
|
|
|
plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
PLpgSQL_execstate estate;
|
2003-04-24 23:16:45 +02:00
|
|
|
ErrorContextCallback plerrcontext;
|
1998-09-01 06:40:42 +02:00
|
|
|
int i;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-04-24 23:16:45 +02:00
|
|
|
* Setup the execution state
|
1998-08-22 14:38:39 +02:00
|
|
|
*/
|
2003-04-24 23:16:45 +02:00
|
|
|
plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo);
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-04-24 23:16:45 +02:00
|
|
|
* Setup error traceback support for ereport()
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2003-04-24 23:16:45 +02:00
|
|
|
plerrcontext.callback = plpgsql_exec_error_callback;
|
|
|
|
plerrcontext.arg = &estate;
|
|
|
|
plerrcontext.previous = error_context_stack;
|
|
|
|
error_context_stack = &plerrcontext;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Make local execution copies of all the datums
|
|
|
|
*/
|
2003-07-27 20:38:26 +02:00
|
|
|
estate.err_text = gettext_noop("during initialization of execution state");
|
1998-09-01 06:40:42 +02:00
|
|
|
for (i = 0; i < func->ndatums; i++)
|
|
|
|
{
|
|
|
|
switch (func->datums[i]->dtype)
|
|
|
|
{
|
|
|
|
case PLPGSQL_DTYPE_VAR:
|
|
|
|
estate.datums[i] = (PLpgSQL_datum *)
|
|
|
|
copy_var((PLpgSQL_var *) (func->datums[i]));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_REC:
|
|
|
|
estate.datums[i] = (PLpgSQL_datum *)
|
|
|
|
copy_rec((PLpgSQL_rec *) (func->datums[i]));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_ROW:
|
|
|
|
case PLPGSQL_DTYPE_RECFIELD:
|
2003-03-25 04:16:41 +01:00
|
|
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
1998-09-01 06:40:42 +02:00
|
|
|
estate.datums[i] = func->datums[i];
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-07-27 20:38:26 +02:00
|
|
|
* Store the actual call argument values into the variables
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2003-07-27 20:38:26 +02:00
|
|
|
estate.err_text = gettext_noop("while storing call arguments into local variables");
|
1998-09-01 06:40:42 +02:00
|
|
|
for (i = 0; i < func->fn_nargs; i++)
|
|
|
|
{
|
|
|
|
int n = func->fn_argvarnos[i];
|
|
|
|
|
|
|
|
switch (estate.datums[n]->dtype)
|
|
|
|
{
|
|
|
|
case PLPGSQL_DTYPE_VAR:
|
|
|
|
{
|
|
|
|
PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[n];
|
|
|
|
|
2000-05-28 19:56:29 +02:00
|
|
|
var->value = fcinfo->arg[i];
|
|
|
|
var->isnull = fcinfo->argnull[i];
|
2001-05-21 16:22:19 +02:00
|
|
|
var->freeval = false;
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_ROW:
|
|
|
|
{
|
2000-05-28 19:56:29 +02:00
|
|
|
PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n];
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2004-02-24 02:44:33 +01:00
|
|
|
if (!fcinfo->argnull[i])
|
|
|
|
{
|
2004-04-01 23:28:47 +02:00
|
|
|
HeapTupleHeader td;
|
|
|
|
Oid tupType;
|
|
|
|
int32 tupTypmod;
|
|
|
|
TupleDesc tupdesc;
|
|
|
|
HeapTupleData tmptup;
|
|
|
|
|
|
|
|
td = DatumGetHeapTupleHeader(fcinfo->arg[i]);
|
|
|
|
/* Extract rowtype info and find a tupdesc */
|
|
|
|
tupType = HeapTupleHeaderGetTypeId(td);
|
|
|
|
tupTypmod = HeapTupleHeaderGetTypMod(td);
|
|
|
|
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
|
|
|
|
/* Build a temporary HeapTuple control structure */
|
|
|
|
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
|
|
|
|
ItemPointerSetInvalid(&(tmptup.t_self));
|
|
|
|
tmptup.t_tableOid = InvalidOid;
|
|
|
|
tmptup.t_data = td;
|
|
|
|
exec_move_row(&estate, NULL, row, &tmptup, tupdesc);
|
2004-02-24 02:44:33 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* If arg is null, treat it as an empty row */
|
|
|
|
exec_move_row(&estate, NULL, row, NULL, NULL);
|
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* Initialize the other variables to NULL values for now. The default
|
|
|
|
* values are set when the blocks are entered.
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2003-07-27 20:38:26 +02:00
|
|
|
estate.err_text = gettext_noop("while initializing local variables to NULL");
|
1998-09-01 06:40:42 +02:00
|
|
|
for (i = estate.found_varno; i < estate.ndatums; i++)
|
|
|
|
{
|
|
|
|
switch (estate.datums[i]->dtype)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_DTYPE_VAR:
|
|
|
|
{
|
|
|
|
PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[i];
|
|
|
|
|
|
|
|
var->value = 0;
|
|
|
|
var->isnull = true;
|
2001-05-21 16:22:19 +02:00
|
|
|
var->freeval = false;
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_ROW:
|
|
|
|
case PLPGSQL_DTYPE_REC:
|
|
|
|
case PLPGSQL_DTYPE_RECFIELD:
|
2003-03-25 04:16:41 +01:00
|
|
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
1998-09-01 06:40:42 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Set the magic variable FOUND to false
|
|
|
|
*/
|
|
|
|
exec_set_found(&estate, false);
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Now call the toplevel block of statements
|
|
|
|
*/
|
2003-04-24 23:16:45 +02:00
|
|
|
estate.err_text = NULL;
|
|
|
|
estate.err_stmt = (PLpgSQL_stmt *) (func->action);
|
1998-09-01 06:40:42 +02:00
|
|
|
if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN)
|
|
|
|
{
|
2003-04-24 23:16:45 +02:00
|
|
|
estate.err_stmt = NULL;
|
2003-07-27 20:38:26 +02:00
|
|
|
estate.err_text = NULL;
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
2003-08-04 02:43:34 +02:00
|
|
|
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
|
|
|
|
errmsg("control reached end of function without RETURN")));
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* We got a return value - process it
|
|
|
|
*/
|
2003-04-24 23:16:45 +02:00
|
|
|
estate.err_stmt = NULL;
|
2003-07-27 20:38:26 +02:00
|
|
|
estate.err_text = gettext_noop("while casting return value to function's return type");
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2000-05-28 19:56:29 +02:00
|
|
|
fcinfo->isnull = estate.retisnull;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2002-08-30 02:28:41 +02:00
|
|
|
if (estate.retisset)
|
|
|
|
{
|
|
|
|
ReturnSetInfo *rsi = estate.rsi;
|
|
|
|
|
|
|
|
/* Check caller can handle a set result */
|
|
|
|
if (!rsi || !IsA(rsi, ReturnSetInfo) ||
|
|
|
|
(rsi->allowedModes & SFRM_Materialize) == 0)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("set-valued function called in context that cannot accept a set")));
|
2002-08-30 02:28:41 +02:00
|
|
|
rsi->returnMode = SFRM_Materialize;
|
|
|
|
|
|
|
|
/* If we produced any tuples, send back the result */
|
|
|
|
if (estate.tuple_store)
|
|
|
|
{
|
|
|
|
rsi->setResult = estate.tuple_store;
|
|
|
|
if (estate.rettupdesc)
|
2003-03-09 03:19:13 +01:00
|
|
|
{
|
|
|
|
MemoryContext oldcxt;
|
|
|
|
|
|
|
|
oldcxt = MemoryContextSwitchTo(estate.tuple_store_cxt);
|
2002-08-30 02:28:41 +02:00
|
|
|
rsi->setDesc = CreateTupleDescCopy(estate.rettupdesc);
|
2003-03-09 03:19:13 +01:00
|
|
|
MemoryContextSwitchTo(oldcxt);
|
|
|
|
}
|
2002-08-30 02:28:41 +02:00
|
|
|
}
|
|
|
|
estate.retval = (Datum) 0;
|
|
|
|
fcinfo->isnull = true;
|
|
|
|
}
|
|
|
|
else if (!estate.retisnull)
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
2001-11-05 20:41:56 +01:00
|
|
|
if (estate.retistuple)
|
|
|
|
{
|
2004-04-01 23:28:47 +02:00
|
|
|
/* Copy tuple to upper executor memory, as a tuple Datum */
|
|
|
|
estate.retval =
|
|
|
|
PointerGetDatum(SPI_returntuple((HeapTuple) (estate.retval),
|
|
|
|
estate.rettupdesc));
|
2001-11-05 20:41:56 +01:00
|
|
|
}
|
|
|
|
else
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
2001-11-05 20:41:56 +01:00
|
|
|
/* Cast value to proper type */
|
|
|
|
estate.retval = exec_cast_value(estate.retval, estate.rettype,
|
|
|
|
func->fn_rettype,
|
|
|
|
&(func->fn_retinput),
|
2004-06-06 02:41:28 +02:00
|
|
|
func->fn_rettypioparam,
|
2001-11-05 20:41:56 +01:00
|
|
|
-1,
|
|
|
|
&fcinfo->isnull);
|
2001-11-08 21:37:52 +01:00
|
|
|
|
2001-11-05 20:41:56 +01:00
|
|
|
/*
|
|
|
|
* If the functions return type isn't by value, copy the value
|
|
|
|
* into upper executor memory context.
|
|
|
|
*/
|
|
|
|
if (!fcinfo->isnull && !func->fn_retbyval)
|
|
|
|
{
|
2002-08-24 17:00:47 +02:00
|
|
|
Size len;
|
|
|
|
void *tmp;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2002-08-24 17:00:47 +02:00
|
|
|
len = datumGetSize(estate.retval, false, func->fn_rettyplen);
|
|
|
|
tmp = (void *) SPI_palloc(len);
|
|
|
|
memcpy(tmp, DatumGetPointer(estate.retval), len);
|
|
|
|
estate.retval = PointerGetDatum(tmp);
|
2001-11-05 20:41:56 +01:00
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
/* Clean up any leftover temporary memory */
|
2003-09-29 01:37:45 +02:00
|
|
|
if (estate.eval_econtext != NULL)
|
|
|
|
FreeExprContext(estate.eval_econtext);
|
|
|
|
estate.eval_econtext = NULL;
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_eval_cleanup(&estate);
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-04-24 23:16:45 +02:00
|
|
|
* Pop the error context stack
|
1998-08-22 14:38:39 +02:00
|
|
|
*/
|
2003-04-24 23:16:45 +02:00
|
|
|
error_context_stack = plerrcontext.previous;
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Return the functions result
|
|
|
|
*/
|
|
|
|
return estate.retval;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* plpgsql_exec_trigger Called by the call handler for
|
|
|
|
* trigger execution.
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
HeapTuple
|
2004-08-30 04:54:42 +02:00
|
|
|
plpgsql_exec_trigger(PLpgSQL_function *func,
|
1998-09-01 06:40:42 +02:00
|
|
|
TriggerData *trigdata)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
PLpgSQL_execstate estate;
|
2003-04-24 23:16:45 +02:00
|
|
|
ErrorContextCallback plerrcontext;
|
1998-09-01 06:40:42 +02:00
|
|
|
int i;
|
|
|
|
PLpgSQL_var *var;
|
2002-11-23 04:59:09 +01:00
|
|
|
PLpgSQL_rec *rec_new,
|
2003-08-04 02:43:34 +02:00
|
|
|
*rec_old;
|
1998-09-01 06:40:42 +02:00
|
|
|
HeapTuple rettup;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-04-24 23:16:45 +02:00
|
|
|
* Setup the execution state
|
1998-08-22 14:38:39 +02:00
|
|
|
*/
|
2003-04-24 23:16:45 +02:00
|
|
|
plpgsql_estate_setup(&estate, func, NULL);
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-04-24 23:16:45 +02:00
|
|
|
* Setup error traceback support for ereport()
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2003-04-24 23:16:45 +02:00
|
|
|
plerrcontext.callback = plpgsql_exec_error_callback;
|
|
|
|
plerrcontext.arg = &estate;
|
|
|
|
plerrcontext.previous = error_context_stack;
|
|
|
|
error_context_stack = &plerrcontext;
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Make local execution copies of all the datums
|
|
|
|
*/
|
2003-07-27 20:38:26 +02:00
|
|
|
estate.err_text = gettext_noop("during initialization of execution state");
|
1998-09-01 06:40:42 +02:00
|
|
|
for (i = 0; i < func->ndatums; i++)
|
|
|
|
{
|
|
|
|
switch (func->datums[i]->dtype)
|
|
|
|
{
|
|
|
|
case PLPGSQL_DTYPE_VAR:
|
|
|
|
estate.datums[i] = (PLpgSQL_datum *)
|
|
|
|
copy_var((PLpgSQL_var *) (func->datums[i]));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_REC:
|
|
|
|
estate.datums[i] = (PLpgSQL_datum *)
|
|
|
|
copy_rec((PLpgSQL_rec *) (func->datums[i]));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_ROW:
|
|
|
|
case PLPGSQL_DTYPE_RECFIELD:
|
2003-03-25 04:16:41 +01:00
|
|
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_DTYPE_TRIGARG:
|
|
|
|
estate.datums[i] = func->datums[i];
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2002-11-23 04:59:09 +01:00
|
|
|
* Put the OLD and NEW tuples into record variables
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
|
|
|
rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
|
2001-08-02 23:31:23 +02:00
|
|
|
rec_new->freetup = false;
|
|
|
|
rec_new->freetupdesc = false;
|
1998-09-01 06:40:42 +02:00
|
|
|
rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
|
2001-08-02 23:31:23 +02:00
|
|
|
rec_old->freetup = false;
|
|
|
|
rec_old->freetupdesc = false;
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2002-11-23 04:59:09 +01:00
|
|
|
if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Per-statement triggers don't use OLD/NEW variables
|
|
|
|
*/
|
|
|
|
rec_new->tup = NULL;
|
|
|
|
rec_new->tupdesc = NULL;
|
|
|
|
rec_old->tup = NULL;
|
|
|
|
rec_old->tupdesc = NULL;
|
|
|
|
}
|
|
|
|
else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
|
|
|
rec_new->tup = trigdata->tg_trigtuple;
|
|
|
|
rec_new->tupdesc = trigdata->tg_relation->rd_att;
|
|
|
|
rec_old->tup = NULL;
|
|
|
|
rec_old->tupdesc = NULL;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
|
|
|
|
{
|
|
|
|
rec_new->tup = trigdata->tg_newtuple;
|
|
|
|
rec_new->tupdesc = trigdata->tg_relation->rd_att;
|
|
|
|
rec_old->tup = trigdata->tg_trigtuple;
|
|
|
|
rec_old->tupdesc = trigdata->tg_relation->rd_att;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
|
|
|
|
{
|
|
|
|
rec_new->tup = NULL;
|
|
|
|
rec_new->tupdesc = NULL;
|
|
|
|
rec_old->tup = trigdata->tg_trigtuple;
|
|
|
|
rec_old->tupdesc = trigdata->tg_relation->rd_att;
|
|
|
|
}
|
|
|
|
else
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2002-11-23 04:59:09 +01:00
|
|
|
* Assign the special tg_ variables
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2002-11-23 04:59:09 +01:00
|
|
|
|
|
|
|
var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]);
|
|
|
|
var->isnull = false;
|
|
|
|
var->freeval = false;
|
|
|
|
|
|
|
|
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
|
|
|
|
var->value = DirectFunctionCall1(textin, CStringGetDatum("INSERT"));
|
|
|
|
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
|
|
|
|
var->value = DirectFunctionCall1(textin, CStringGetDatum("UPDATE"));
|
|
|
|
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
|
|
|
|
var->value = DirectFunctionCall1(textin, CStringGetDatum("DELETE"));
|
|
|
|
else
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
|
2002-11-23 04:59:09 +01:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]);
|
|
|
|
var->isnull = false;
|
2001-08-02 23:31:23 +02:00
|
|
|
var->freeval = true;
|
2000-08-03 18:35:08 +02:00
|
|
|
var->value = DirectFunctionCall1(namein,
|
2001-03-22 05:01:46 +01:00
|
|
|
CStringGetDatum(trigdata->tg_trigger->tgname));
|
1998-09-01 06:40:42 +02:00
|
|
|
|
|
|
|
var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]);
|
|
|
|
var->isnull = false;
|
2001-08-02 23:31:23 +02:00
|
|
|
var->freeval = true;
|
1998-09-01 06:40:42 +02:00
|
|
|
if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
|
2000-07-06 01:12:09 +02:00
|
|
|
var->value = DirectFunctionCall1(textin, CStringGetDatum("BEFORE"));
|
1998-09-01 06:40:42 +02:00
|
|
|
else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
|
2000-07-06 01:12:09 +02:00
|
|
|
var->value = DirectFunctionCall1(textin, CStringGetDatum("AFTER"));
|
1998-09-01 06:40:42 +02:00
|
|
|
else
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized trigger execution time: not BEFORE or AFTER");
|
1998-09-01 06:40:42 +02:00
|
|
|
|
|
|
|
var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]);
|
|
|
|
var->isnull = false;
|
2001-08-02 23:31:23 +02:00
|
|
|
var->freeval = true;
|
1998-09-01 06:40:42 +02:00
|
|
|
if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
|
2000-07-06 01:12:09 +02:00
|
|
|
var->value = DirectFunctionCall1(textin, CStringGetDatum("ROW"));
|
1998-09-01 06:40:42 +02:00
|
|
|
else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
|
2000-07-06 01:12:09 +02:00
|
|
|
var->value = DirectFunctionCall1(textin, CStringGetDatum("STATEMENT"));
|
1998-09-01 06:40:42 +02:00
|
|
|
else
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT");
|
1998-09-01 06:40:42 +02:00
|
|
|
|
|
|
|
var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]);
|
|
|
|
var->isnull = false;
|
2001-05-21 16:22:19 +02:00
|
|
|
var->freeval = false;
|
2001-08-02 23:31:23 +02:00
|
|
|
var->value = ObjectIdGetDatum(trigdata->tg_relation->rd_id);
|
1998-09-01 06:40:42 +02:00
|
|
|
|
|
|
|
var = (PLpgSQL_var *) (estate.datums[func->tg_relname_varno]);
|
|
|
|
var->isnull = false;
|
2001-08-02 23:31:23 +02:00
|
|
|
var->freeval = true;
|
2000-08-03 18:35:08 +02:00
|
|
|
var->value = DirectFunctionCall1(namein,
|
2001-03-22 05:01:46 +01:00
|
|
|
CStringGetDatum(RelationGetRelationName(trigdata->tg_relation)));
|
1998-09-01 06:40:42 +02:00
|
|
|
|
|
|
|
var = (PLpgSQL_var *) (estate.datums[func->tg_nargs_varno]);
|
|
|
|
var->isnull = false;
|
2001-05-21 16:22:19 +02:00
|
|
|
var->freeval = false;
|
2001-08-02 23:31:23 +02:00
|
|
|
var->value = Int16GetDatum(trigdata->tg_trigger->tgnargs);
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-07-27 20:38:26 +02:00
|
|
|
* Store the actual call argument values into the special execution
|
2001-03-22 07:16:21 +01:00
|
|
|
* state variables
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2003-07-27 20:38:26 +02:00
|
|
|
estate.err_text = gettext_noop("while storing call arguments into local variables");
|
1998-09-01 06:40:42 +02:00
|
|
|
estate.trig_nargs = trigdata->tg_trigger->tgnargs;
|
|
|
|
if (estate.trig_nargs == 0)
|
|
|
|
estate.trig_argv = NULL;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
estate.trig_argv = palloc(sizeof(Datum) * estate.trig_nargs);
|
|
|
|
for (i = 0; i < trigdata->tg_trigger->tgnargs; i++)
|
2000-07-06 01:12:09 +02:00
|
|
|
estate.trig_argv[i] = DirectFunctionCall1(textin,
|
2001-03-22 05:01:46 +01:00
|
|
|
CStringGetDatum(trigdata->tg_trigger->tgargs[i]));
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* Initialize the other variables to NULL values for now. The default
|
|
|
|
* values are set when the blocks are entered.
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2003-07-27 20:38:26 +02:00
|
|
|
estate.err_text = gettext_noop("while initializing local variables to NULL");
|
1998-09-01 06:40:42 +02:00
|
|
|
for (i = estate.found_varno; i < estate.ndatums; i++)
|
|
|
|
{
|
|
|
|
switch (estate.datums[i]->dtype)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_DTYPE_VAR:
|
|
|
|
{
|
|
|
|
PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[i];
|
|
|
|
|
|
|
|
var->value = 0;
|
|
|
|
var->isnull = true;
|
2001-05-21 16:22:19 +02:00
|
|
|
var->freeval = false;
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_ROW:
|
|
|
|
case PLPGSQL_DTYPE_REC:
|
|
|
|
case PLPGSQL_DTYPE_RECFIELD:
|
2003-03-25 04:16:41 +01:00
|
|
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_DTYPE_TRIGARG:
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Set the magic variable FOUND to false
|
|
|
|
*/
|
|
|
|
exec_set_found(&estate, false);
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Now call the toplevel block of statements
|
|
|
|
*/
|
2003-04-24 23:16:45 +02:00
|
|
|
estate.err_text = NULL;
|
|
|
|
estate.err_stmt = (PLpgSQL_stmt *) (func->action);
|
1998-09-01 06:40:42 +02:00
|
|
|
if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN)
|
|
|
|
{
|
2003-04-24 23:16:45 +02:00
|
|
|
estate.err_stmt = NULL;
|
2003-07-27 20:38:26 +02:00
|
|
|
estate.err_text = NULL;
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
2003-08-04 02:43:34 +02:00
|
|
|
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
|
|
|
|
errmsg("control reached end of trigger procedure without RETURN")));
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
|
2002-08-30 02:28:41 +02:00
|
|
|
if (estate.retisset)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
|
|
errmsg("trigger procedure cannot return a set")));
|
2002-08-30 02:28:41 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Check that the returned tuple structure has the same attributes,
|
2002-11-23 04:59:09 +01:00
|
|
|
* the relation that fired the trigger has. A per-statement trigger
|
|
|
|
* always needs to return NULL, so we ignore any return value the
|
|
|
|
* function itself produces (XXX: is this a good idea?)
|
1998-09-01 06:40:42 +02:00
|
|
|
*
|
2001-03-22 07:16:21 +01:00
|
|
|
* XXX This way it is possible, that the trigger returns a tuple where
|
|
|
|
* attributes don't have the correct atttypmod's length. It's up to
|
|
|
|
* the trigger's programmer to ensure that this doesn't happen. Jan
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2002-11-23 04:59:09 +01:00
|
|
|
if (estate.retisnull || TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
|
1998-09-01 06:40:42 +02:00
|
|
|
rettup = NULL;
|
|
|
|
else
|
|
|
|
{
|
2002-08-31 01:59:46 +02:00
|
|
|
if (!compatible_tupdesc(estate.rettupdesc,
|
|
|
|
trigdata->tg_relation->rd_att))
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
|
|
errmsg("returned tuple structure does not match table of trigger event")));
|
2001-08-02 23:31:23 +02:00
|
|
|
/* Copy tuple to upper executor memory */
|
1998-09-01 06:40:42 +02:00
|
|
|
rettup = SPI_copytuple((HeapTuple) (estate.retval));
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
/* Clean up any leftover temporary memory */
|
2003-09-29 01:37:45 +02:00
|
|
|
if (estate.eval_econtext != NULL)
|
|
|
|
FreeExprContext(estate.eval_econtext);
|
|
|
|
estate.eval_econtext = NULL;
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_eval_cleanup(&estate);
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-04-24 23:16:45 +02:00
|
|
|
* Pop the error context stack
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2003-04-24 23:16:45 +02:00
|
|
|
error_context_stack = plerrcontext.previous;
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Return the triggers result
|
|
|
|
*/
|
|
|
|
return rettup;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-04-24 23:16:45 +02:00
|
|
|
/*
|
|
|
|
* error context callback to let us supply a call-stack traceback
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
plpgsql_exec_error_callback(void *arg)
|
|
|
|
{
|
|
|
|
PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
|
|
|
|
|
|
|
|
/* safety check, shouldn't happen */
|
|
|
|
if (estate->err_func == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* if we are doing RAISE, don't report its location */
|
|
|
|
if (estate->err_text == raise_skip_msg)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (estate->err_stmt != NULL)
|
2003-07-27 20:38:26 +02:00
|
|
|
{
|
|
|
|
/* translator: last %s is a plpgsql statement type name */
|
|
|
|
errcontext("PL/pgSQL function \"%s\" line %d at %s",
|
2003-04-24 23:16:45 +02:00
|
|
|
estate->err_func->fn_name,
|
|
|
|
estate->err_stmt->lineno,
|
|
|
|
plpgsql_stmt_typename(estate->err_stmt));
|
2003-07-27 20:38:26 +02:00
|
|
|
}
|
2003-04-24 23:16:45 +02:00
|
|
|
else if (estate->err_text != NULL)
|
2003-07-27 20:38:26 +02:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We don't expend the cycles to run gettext() on err_text unless
|
2003-08-04 02:43:34 +02:00
|
|
|
* we actually need it. Therefore, places that set up err_text
|
|
|
|
* should use gettext_noop() to ensure the strings get recorded in
|
|
|
|
* the message dictionary.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* translator: last %s is a phrase such as "while storing call
|
|
|
|
* arguments into local variables"
|
2003-07-27 20:38:26 +02:00
|
|
|
*/
|
|
|
|
errcontext("PL/pgSQL function \"%s\" %s",
|
2003-04-24 23:16:45 +02:00
|
|
|
estate->err_func->fn_name,
|
2003-07-27 20:38:26 +02:00
|
|
|
gettext(estate->err_text));
|
|
|
|
}
|
2003-04-24 23:16:45 +02:00
|
|
|
else
|
2003-07-27 20:38:26 +02:00
|
|
|
errcontext("PL/pgSQL function \"%s\"",
|
2003-04-24 23:16:45 +02:00
|
|
|
estate->err_func->fn_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
1998-08-22 14:38:39 +02:00
|
|
|
/* ----------
|
|
|
|
* Support functions for copying local execution variables
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static PLpgSQL_var *
|
2004-08-30 04:54:42 +02:00
|
|
|
copy_var(PLpgSQL_var *var)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));
|
|
|
|
|
|
|
|
memcpy(new, var, sizeof(PLpgSQL_var));
|
2001-05-21 16:22:19 +02:00
|
|
|
new->freeval = false;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
return new;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
static PLpgSQL_rec *
|
2004-08-30 04:54:42 +02:00
|
|
|
copy_rec(PLpgSQL_rec *rec)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
memcpy(new, rec, sizeof(PLpgSQL_rec));
|
2001-05-21 16:22:19 +02:00
|
|
|
new->tup = NULL;
|
|
|
|
new->tupdesc = NULL;
|
|
|
|
new->freetup = false;
|
|
|
|
new->freetupdesc = false;
|
1998-09-01 06:40:42 +02:00
|
|
|
|
|
|
|
return new;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-07-31 09:39:21 +02:00
|
|
|
static bool
|
2004-08-30 04:54:42 +02:00
|
|
|
exception_matches_conditions(ErrorData *edata, PLpgSQL_condition *cond)
|
2004-07-31 09:39:21 +02:00
|
|
|
{
|
2004-08-01 01:04:58 +02:00
|
|
|
for (; cond != NULL; cond = cond->next)
|
2004-07-31 09:39:21 +02:00
|
|
|
{
|
2004-08-21 00:00:14 +02:00
|
|
|
int sqlerrstate = cond->sqlerrstate;
|
2004-08-01 01:04:58 +02:00
|
|
|
|
|
|
|
/*
|
2004-08-29 07:07:03 +02:00
|
|
|
* OTHERS matches everything *except* query-canceled; if you're
|
|
|
|
* foolish enough, you can match that explicitly.
|
2004-08-01 01:04:58 +02:00
|
|
|
*/
|
2004-08-21 00:00:14 +02:00
|
|
|
if (sqlerrstate == 0)
|
2004-08-01 01:04:58 +02:00
|
|
|
{
|
2004-08-21 00:00:14 +02:00
|
|
|
if (edata->sqlerrcode != ERRCODE_QUERY_CANCELED)
|
2004-08-01 01:04:58 +02:00
|
|
|
return true;
|
|
|
|
}
|
2004-08-21 00:00:14 +02:00
|
|
|
/* Exact match? */
|
|
|
|
else if (edata->sqlerrcode == sqlerrstate)
|
|
|
|
return true;
|
|
|
|
/* Category match? */
|
|
|
|
else if (ERRCODE_IS_CATEGORY(sqlerrstate) &&
|
|
|
|
ERRCODE_TO_CATEGORY(edata->sqlerrcode) == sqlerrstate)
|
|
|
|
return true;
|
2004-07-31 09:39:21 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
1998-08-22 14:38:39 +02:00
|
|
|
/* ----------
|
|
|
|
* exec_stmt_block Execute a block of statements
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
2004-07-31 09:39:21 +02:00
|
|
|
volatile int rc = -1;
|
1998-09-01 06:40:42 +02:00
|
|
|
int i;
|
|
|
|
int n;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* First initialize all variables declared in this block
|
|
|
|
*/
|
|
|
|
for (i = 0; i < block->n_initvars; i++)
|
|
|
|
{
|
|
|
|
n = block->initvarnos[i];
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
switch (estate->datums[n]->dtype)
|
|
|
|
{
|
|
|
|
case PLPGSQL_DTYPE_VAR:
|
|
|
|
{
|
|
|
|
PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]);
|
|
|
|
|
2001-05-21 16:22:19 +02:00
|
|
|
if (var->freeval)
|
|
|
|
{
|
2001-10-25 07:50:21 +02:00
|
|
|
pfree((void *) (var->value));
|
2001-05-21 16:22:19 +02:00
|
|
|
var->freeval = false;
|
|
|
|
}
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
if (!var->isconst || var->isnull)
|
|
|
|
{
|
|
|
|
if (var->default_val == NULL)
|
|
|
|
{
|
|
|
|
var->value = (Datum) 0;
|
|
|
|
var->isnull = true;
|
|
|
|
if (var->notnull)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
2003-08-04 02:43:34 +02:00
|
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
|
|
errmsg("variable \"%s\" declared NOT NULL cannot default to NULL",
|
|
|
|
var->refname)));
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
exec_assign_expr(estate, (PLpgSQL_datum *) var,
|
|
|
|
var->default_val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_REC:
|
|
|
|
{
|
|
|
|
PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[n]);
|
|
|
|
|
2001-05-21 16:22:19 +02:00
|
|
|
if (rec->freetup)
|
|
|
|
{
|
|
|
|
heap_freetuple(rec->tup);
|
|
|
|
FreeTupleDesc(rec->tupdesc);
|
|
|
|
rec->freetup = false;
|
|
|
|
}
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
rec->tup = NULL;
|
|
|
|
rec->tupdesc = NULL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_RECFIELD:
|
2003-03-25 04:16:41 +01:00
|
|
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
1998-09-01 06:40:42 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized dtype: %d",
|
|
|
|
estate->datums[n]->dtype);
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-07-31 09:39:21 +02:00
|
|
|
if (block->exceptions)
|
|
|
|
{
|
|
|
|
/*
|
2004-08-29 07:07:03 +02:00
|
|
|
* Execute the statements in the block's body inside a
|
|
|
|
* sub-transaction
|
2004-07-31 09:39:21 +02:00
|
|
|
*/
|
2004-08-29 07:07:03 +02:00
|
|
|
MemoryContext oldcontext = CurrentMemoryContext;
|
2004-09-13 22:10:13 +02:00
|
|
|
ResourceOwner oldowner = CurrentResourceOwner;
|
2004-08-29 07:07:03 +02:00
|
|
|
volatile bool caught = false;
|
|
|
|
int xrc;
|
2004-07-31 09:39:21 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Start a subtransaction, and re-connect to SPI within it
|
|
|
|
*/
|
|
|
|
SPI_push();
|
|
|
|
BeginInternalSubTransaction(NULL);
|
|
|
|
/* Want to run statements inside function's memory context */
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
2004-09-13 22:10:13 +02:00
|
|
|
|
2004-07-31 22:55:45 +02:00
|
|
|
if ((xrc = SPI_connect()) != SPI_OK_CONNECT)
|
|
|
|
elog(ERROR, "SPI_connect failed: %s",
|
|
|
|
SPI_result_code_string(xrc));
|
2004-07-31 09:39:21 +02:00
|
|
|
|
|
|
|
PG_TRY();
|
2004-09-13 22:10:13 +02:00
|
|
|
{
|
|
|
|
rc = exec_stmts(estate, block->body);
|
|
|
|
}
|
2004-07-31 09:39:21 +02:00
|
|
|
PG_CATCH();
|
|
|
|
{
|
2004-08-29 07:07:03 +02:00
|
|
|
ErrorData *edata;
|
2004-07-31 09:39:21 +02:00
|
|
|
PLpgSQL_exceptions *exceptions;
|
|
|
|
int j;
|
|
|
|
|
|
|
|
/* Save error info */
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
edata = CopyErrorData();
|
|
|
|
FlushErrorState();
|
|
|
|
|
|
|
|
/* Abort the inner transaction (and inner SPI connection) */
|
|
|
|
RollbackAndReleaseCurrentSubTransaction();
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
2004-09-13 22:10:13 +02:00
|
|
|
CurrentResourceOwner = oldowner;
|
2004-07-31 09:39:21 +02:00
|
|
|
|
|
|
|
SPI_pop();
|
|
|
|
|
|
|
|
/* Look for a matching exception handler */
|
|
|
|
exceptions = block->exceptions;
|
|
|
|
for (j = 0; j < exceptions->exceptions_used; j++)
|
|
|
|
{
|
|
|
|
PLpgSQL_exception *exception = exceptions->exceptions[j];
|
|
|
|
|
2004-08-01 01:04:58 +02:00
|
|
|
if (exception_matches_conditions(edata, exception->conditions))
|
2004-07-31 09:39:21 +02:00
|
|
|
{
|
|
|
|
rc = exec_stmts(estate, exception->action);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If no match found, re-throw the error */
|
|
|
|
if (j >= exceptions->exceptions_used)
|
|
|
|
ReThrowError(edata);
|
|
|
|
else
|
|
|
|
FreeErrorData(edata);
|
|
|
|
caught = true;
|
|
|
|
}
|
|
|
|
PG_END_TRY();
|
|
|
|
|
|
|
|
/* Commit the inner transaction, return to outer xact context */
|
|
|
|
if (!caught)
|
|
|
|
{
|
2004-07-31 22:55:45 +02:00
|
|
|
if ((xrc = SPI_finish()) != SPI_OK_FINISH)
|
|
|
|
elog(ERROR, "SPI_finish failed: %s",
|
|
|
|
SPI_result_code_string(xrc));
|
2004-09-13 22:10:13 +02:00
|
|
|
|
2004-07-31 09:39:21 +02:00
|
|
|
ReleaseCurrentSubTransaction();
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
2004-09-13 22:10:13 +02:00
|
|
|
CurrentResourceOwner = oldowner;
|
|
|
|
|
2004-07-31 09:39:21 +02:00
|
|
|
SPI_pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Just execute the statements in the block's body
|
|
|
|
*/
|
|
|
|
rc = exec_stmts(estate, block->body);
|
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Handle the return code.
|
|
|
|
*/
|
|
|
|
switch (rc)
|
|
|
|
{
|
|
|
|
case PLPGSQL_RC_OK:
|
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
|
|
|
|
case PLPGSQL_RC_EXIT:
|
|
|
|
if (estate->exitlabel == NULL)
|
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
if (block->label == NULL)
|
|
|
|
return PLPGSQL_RC_EXIT;
|
|
|
|
if (strcmp(block->label, estate->exitlabel))
|
|
|
|
return PLPGSQL_RC_EXIT;
|
|
|
|
estate->exitlabel = NULL;
|
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
|
|
|
|
case PLPGSQL_RC_RETURN:
|
|
|
|
return PLPGSQL_RC_RETURN;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
default:
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized rc: %d", rc);
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
return PLPGSQL_RC_OK;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmts Iterate over a list of statements
|
|
|
|
* as long as their return code is OK
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmts(PLpgSQL_execstate *estate, PLpgSQL_stmts *stmts)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
int rc;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < stmts->stmts_used; i++)
|
|
|
|
{
|
2004-07-31 09:39:21 +02:00
|
|
|
rc = exec_stmt(estate, stmts->stmts[i]);
|
1998-09-01 06:40:42 +02:00
|
|
|
if (rc != PLPGSQL_RC_OK)
|
|
|
|
return rc;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
return PLPGSQL_RC_OK;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt Distribute one statement to the statements
|
|
|
|
* type specific execution function.
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
PLpgSQL_stmt *save_estmt;
|
|
|
|
int rc = -1;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2003-04-24 23:16:45 +02:00
|
|
|
save_estmt = estate->err_stmt;
|
|
|
|
estate->err_stmt = stmt;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2002-08-30 02:28:41 +02:00
|
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
switch (stmt->cmd_type)
|
|
|
|
{
|
|
|
|
case PLPGSQL_STMT_BLOCK:
|
|
|
|
rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *) stmt);
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_STMT_ASSIGN:
|
|
|
|
rc = exec_stmt_assign(estate, (PLpgSQL_stmt_assign *) stmt);
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2002-11-10 01:35:58 +01:00
|
|
|
case PLPGSQL_STMT_PERFORM:
|
|
|
|
rc = exec_stmt_perform(estate, (PLpgSQL_stmt_perform *) stmt);
|
|
|
|
break;
|
|
|
|
|
2000-09-05 11:02:18 +02:00
|
|
|
case PLPGSQL_STMT_GETDIAG:
|
|
|
|
rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) stmt);
|
|
|
|
break;
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_STMT_IF:
|
|
|
|
rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt);
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_STMT_LOOP:
|
|
|
|
rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *) stmt);
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_STMT_WHILE:
|
|
|
|
rc = exec_stmt_while(estate, (PLpgSQL_stmt_while *) stmt);
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_STMT_FORI:
|
|
|
|
rc = exec_stmt_fori(estate, (PLpgSQL_stmt_fori *) stmt);
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_STMT_FORS:
|
|
|
|
rc = exec_stmt_fors(estate, (PLpgSQL_stmt_fors *) stmt);
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_STMT_SELECT:
|
|
|
|
rc = exec_stmt_select(estate, (PLpgSQL_stmt_select *) stmt);
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_STMT_EXIT:
|
|
|
|
rc = exec_stmt_exit(estate, (PLpgSQL_stmt_exit *) stmt);
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_STMT_RETURN:
|
|
|
|
rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *) stmt);
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2002-08-30 02:28:41 +02:00
|
|
|
case PLPGSQL_STMT_RETURN_NEXT:
|
|
|
|
rc = exec_stmt_return_next(estate, (PLpgSQL_stmt_return_next *) stmt);
|
|
|
|
break;
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_STMT_RAISE:
|
|
|
|
rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt);
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
case PLPGSQL_STMT_EXECSQL:
|
|
|
|
rc = exec_stmt_execsql(estate, (PLpgSQL_stmt_execsql *) stmt);
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2000-08-31 15:26:16 +02:00
|
|
|
case PLPGSQL_STMT_DYNEXECUTE:
|
|
|
|
rc = exec_stmt_dynexecute(estate, (PLpgSQL_stmt_dynexecute *) stmt);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_STMT_DYNFORS:
|
|
|
|
rc = exec_stmt_dynfors(estate, (PLpgSQL_stmt_dynfors *) stmt);
|
|
|
|
break;
|
|
|
|
|
2001-05-21 16:22:19 +02:00
|
|
|
case PLPGSQL_STMT_OPEN:
|
|
|
|
rc = exec_stmt_open(estate, (PLpgSQL_stmt_open *) stmt);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_STMT_FETCH:
|
|
|
|
rc = exec_stmt_fetch(estate, (PLpgSQL_stmt_fetch *) stmt);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_STMT_CLOSE:
|
|
|
|
rc = exec_stmt_close(estate, (PLpgSQL_stmt_close *) stmt);
|
|
|
|
break;
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
default:
|
2003-04-24 23:16:45 +02:00
|
|
|
estate->err_stmt = save_estmt;
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized cmdtype: %d", stmt->cmd_type);
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2003-04-24 23:16:45 +02:00
|
|
|
estate->err_stmt = save_estmt;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
return rc;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_assign Evaluate an expression and
|
|
|
|
* put the result into a variable.
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_assign(PLpgSQL_execstate *estate, PLpgSQL_stmt_assign *stmt)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
2002-11-10 01:35:58 +01:00
|
|
|
Assert(stmt->varno >= 0);
|
2001-05-28 21:33:24 +02:00
|
|
|
|
2002-11-10 01:35:58 +01:00
|
|
|
exec_assign_expr(estate, estate->datums[stmt->varno], stmt->expr);
|
2001-05-28 21:33:24 +02:00
|
|
|
|
2002-11-10 01:35:58 +01:00
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_perform Evaluate query and discard result (but set
|
|
|
|
* FOUND depending on whether at least one row
|
|
|
|
* was returned).
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_perform(PLpgSQL_execstate *estate, PLpgSQL_stmt_perform *stmt)
|
2002-11-10 01:35:58 +01:00
|
|
|
{
|
|
|
|
PLpgSQL_expr *expr = stmt->expr;
|
|
|
|
int rc;
|
2001-05-28 21:33:24 +02:00
|
|
|
|
2002-11-10 01:35:58 +01:00
|
|
|
/*
|
|
|
|
* If not already done create a plan for this expression
|
|
|
|
*/
|
|
|
|
if (expr->plan == NULL)
|
|
|
|
exec_prepare_plan(estate, expr);
|
2002-11-13 01:39:48 +01:00
|
|
|
|
2002-11-10 01:35:58 +01:00
|
|
|
rc = exec_run_select(estate, expr, 0, NULL);
|
|
|
|
if (rc != SPI_OK_SELECT)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
2003-08-04 02:43:34 +02:00
|
|
|
errmsg("query \"%s\" did not return data", expr->query)));
|
2001-05-28 21:33:24 +02:00
|
|
|
|
2002-11-10 01:35:58 +01:00
|
|
|
exec_set_found(estate, (estate->eval_processed != 0));
|
2002-06-25 01:12:06 +02:00
|
|
|
|
2002-11-10 01:35:58 +01:00
|
|
|
exec_eval_cleanup(estate);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
return PLPGSQL_RC_OK;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2000-09-05 11:02:18 +02:00
|
|
|
/* ----------
|
2001-03-22 05:01:46 +01:00
|
|
|
* exec_stmt_getdiag Put internal PG information into
|
|
|
|
* specified variables.
|
2000-09-05 11:02:18 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
|
2000-09-05 11:02:18 +02:00
|
|
|
{
|
2001-03-22 05:01:46 +01:00
|
|
|
int i;
|
|
|
|
PLpgSQL_datum *var;
|
|
|
|
bool isnull = false;
|
2000-09-05 11:02:18 +02:00
|
|
|
|
2001-03-22 05:01:46 +01:00
|
|
|
for (i = 0; i < stmt->ndtitems; i++)
|
2000-09-05 11:02:18 +02:00
|
|
|
{
|
2001-03-22 05:01:46 +01:00
|
|
|
PLpgSQL_diag_item *dtitem = &stmt->dtitems[i];
|
2001-02-19 20:49:53 +01:00
|
|
|
|
|
|
|
if (dtitem->target <= 0)
|
|
|
|
continue;
|
2001-03-22 05:01:46 +01:00
|
|
|
|
2001-02-19 20:49:53 +01:00
|
|
|
var = (estate->datums[dtitem->target]);
|
2000-09-05 11:02:18 +02:00
|
|
|
|
|
|
|
if (var == NULL)
|
2001-02-19 20:49:53 +01:00
|
|
|
continue;
|
2000-09-05 11:02:18 +02:00
|
|
|
|
2001-02-19 20:49:53 +01:00
|
|
|
switch (dtitem->item)
|
2000-09-05 11:02:18 +02:00
|
|
|
{
|
2001-02-19 20:49:53 +01:00
|
|
|
case PLPGSQL_GETDIAG_ROW_COUNT:
|
2000-09-05 11:02:18 +02:00
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_assign_value(estate, var,
|
|
|
|
UInt32GetDatum(estate->eval_processed),
|
2001-02-19 20:49:53 +01:00
|
|
|
INT4OID, &isnull);
|
|
|
|
break;
|
2000-09-05 11:02:18 +02:00
|
|
|
|
2001-02-19 20:49:53 +01:00
|
|
|
case PLPGSQL_GETDIAG_RESULT_OID:
|
2000-09-05 11:02:18 +02:00
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_assign_value(estate, var,
|
|
|
|
ObjectIdGetDatum(estate->eval_lastoid),
|
2001-02-19 20:49:53 +01:00
|
|
|
OIDOID, &isnull);
|
|
|
|
break;
|
2000-09-05 11:02:18 +02:00
|
|
|
|
|
|
|
default:
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized attribute request: %d",
|
2001-02-19 20:49:53 +01:00
|
|
|
dtitem->item);
|
|
|
|
}
|
|
|
|
}
|
2001-03-22 05:01:46 +01:00
|
|
|
|
2001-02-19 20:49:53 +01:00
|
|
|
return PLPGSQL_RC_OK;
|
2000-09-05 11:02:18 +02:00
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_if Evaluate a bool expression and
|
|
|
|
* execute the true or false body
|
|
|
|
* conditionally.
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_if(PLpgSQL_execstate *estate, PLpgSQL_stmt_if *stmt)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
2003-10-01 23:47:42 +02:00
|
|
|
bool value;
|
1998-09-01 06:40:42 +02:00
|
|
|
bool isnull = false;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2003-10-01 23:47:42 +02:00
|
|
|
value = exec_eval_boolean(estate, stmt->cond, &isnull);
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_eval_cleanup(estate);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2003-10-01 23:47:42 +02:00
|
|
|
if (!isnull && value)
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
|
|
|
if (stmt->true_body != NULL)
|
|
|
|
return exec_stmts(estate, stmt->true_body);
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
if (stmt->false_body != NULL)
|
|
|
|
return exec_stmts(estate, stmt->false_body);
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
return PLPGSQL_RC_OK;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_loop Loop over statements until
|
|
|
|
* an exit occurs.
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
int rc;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
rc = exec_stmts(estate, stmt->body);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
switch (rc)
|
|
|
|
{
|
|
|
|
case PLPGSQL_RC_OK:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_RC_EXIT:
|
|
|
|
if (estate->exitlabel == NULL)
|
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
if (stmt->label == NULL)
|
|
|
|
return PLPGSQL_RC_EXIT;
|
|
|
|
if (strcmp(stmt->label, estate->exitlabel))
|
|
|
|
return PLPGSQL_RC_EXIT;
|
|
|
|
estate->exitlabel = NULL;
|
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
|
|
|
|
case PLPGSQL_RC_RETURN:
|
|
|
|
return PLPGSQL_RC_RETURN;
|
|
|
|
|
|
|
|
default:
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized rc: %d", rc);
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
return PLPGSQL_RC_OK;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_while Loop over statements as long
|
|
|
|
* as an expression evaluates to
|
|
|
|
* true or an exit occurs.
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
2003-10-01 23:47:42 +02:00
|
|
|
bool value;
|
1998-09-01 06:40:42 +02:00
|
|
|
bool isnull = false;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
2003-10-01 23:47:42 +02:00
|
|
|
value = exec_eval_boolean(estate, stmt->cond, &isnull);
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_eval_cleanup(estate);
|
2003-10-01 23:47:42 +02:00
|
|
|
|
|
|
|
if (isnull || !value)
|
1998-09-01 06:40:42 +02:00
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
rc = exec_stmts(estate, stmt->body);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
switch (rc)
|
|
|
|
{
|
|
|
|
case PLPGSQL_RC_OK:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PLPGSQL_RC_EXIT:
|
|
|
|
if (estate->exitlabel == NULL)
|
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
if (stmt->label == NULL)
|
|
|
|
return PLPGSQL_RC_EXIT;
|
|
|
|
if (strcmp(stmt->label, estate->exitlabel))
|
|
|
|
return PLPGSQL_RC_EXIT;
|
|
|
|
estate->exitlabel = NULL;
|
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
|
|
|
|
case PLPGSQL_RC_RETURN:
|
|
|
|
return PLPGSQL_RC_RETURN;
|
|
|
|
|
|
|
|
default:
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized rc: %d", rc);
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
return PLPGSQL_RC_OK;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_fori Iterate an integer variable
|
|
|
|
* from a lower to an upper value.
|
|
|
|
* Loop can be left with exit.
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
PLpgSQL_var *var;
|
|
|
|
Datum value;
|
|
|
|
Oid valtype;
|
|
|
|
bool isnull = false;
|
2002-08-20 07:28:24 +02:00
|
|
|
bool found = false;
|
|
|
|
int rc = PLPGSQL_RC_OK;
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
var = (PLpgSQL_var *) (estate->datums[stmt->var->varno]);
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Get the value of the lower bound into the loop var
|
1998-08-22 14:38:39 +02:00
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
value = exec_eval_expr(estate, stmt->lower, &isnull, &valtype);
|
|
|
|
value = exec_cast_value(value, valtype, var->datatype->typoid,
|
|
|
|
&(var->datatype->typinput),
|
2004-06-06 02:41:28 +02:00
|
|
|
var->datatype->typioparam,
|
1998-09-01 06:40:42 +02:00
|
|
|
var->datatype->atttypmod, &isnull);
|
|
|
|
if (isnull)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
|
|
errmsg("lower bound of FOR loop cannot be NULL")));
|
1998-09-01 06:40:42 +02:00
|
|
|
var->value = value;
|
|
|
|
var->isnull = false;
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_eval_cleanup(estate);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Get the value of the upper bound
|
1998-08-22 14:38:39 +02:00
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
value = exec_eval_expr(estate, stmt->upper, &isnull, &valtype);
|
|
|
|
value = exec_cast_value(value, valtype, var->datatype->typoid,
|
|
|
|
&(var->datatype->typinput),
|
2004-06-06 02:41:28 +02:00
|
|
|
var->datatype->typioparam,
|
1998-09-01 06:40:42 +02:00
|
|
|
var->datatype->atttypmod, &isnull);
|
|
|
|
if (isnull)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
|
|
errmsg("upper bound of FOR loop cannot be NULL")));
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_eval_cleanup(estate);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Now do the loop
|
1998-08-22 14:38:39 +02:00
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
for (;;)
|
|
|
|
{
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Check bounds
|
|
|
|
*/
|
|
|
|
if (stmt->reverse)
|
|
|
|
{
|
|
|
|
if ((int4) (var->value) < (int4) value)
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
if ((int4) (var->value) > (int4) value)
|
|
|
|
break;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
2002-08-20 07:28:24 +02:00
|
|
|
|
2002-09-04 22:31:48 +02:00
|
|
|
found = true; /* looped at least once */
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Execute the statements
|
|
|
|
*/
|
|
|
|
rc = exec_stmts(estate, stmt->body);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
if (rc == PLPGSQL_RC_RETURN)
|
2002-09-04 22:31:48 +02:00
|
|
|
break; /* return from function */
|
2002-08-20 07:28:24 +02:00
|
|
|
else if (rc == PLPGSQL_RC_EXIT)
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
2002-08-20 07:28:24 +02:00
|
|
|
if (estate->exitlabel == NULL)
|
|
|
|
/* unlabelled exit, finish the current loop */
|
|
|
|
rc = PLPGSQL_RC_OK;
|
|
|
|
else if (stmt->label != NULL &&
|
|
|
|
strcmp(stmt->label, estate->exitlabel) == 0)
|
|
|
|
{
|
|
|
|
/* labelled exit, matches the current stmt's label */
|
1998-09-01 06:40:42 +02:00
|
|
|
estate->exitlabel = NULL;
|
2002-08-20 07:28:24 +02:00
|
|
|
rc = PLPGSQL_RC_OK;
|
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
/*
|
2002-09-04 22:31:48 +02:00
|
|
|
* otherwise, we processed a labelled exit that does not match
|
|
|
|
* the current statement's label, if any: return RC_EXIT so
|
|
|
|
* that the EXIT continues to recurse upward.
|
2002-08-20 07:28:24 +02:00
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
break;
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Increase/decrease loop var
|
|
|
|
*/
|
|
|
|
if (stmt->reverse)
|
1999-01-17 22:53:32 +01:00
|
|
|
var->value--;
|
1998-09-01 06:40:42 +02:00
|
|
|
else
|
1999-01-17 22:53:32 +01:00
|
|
|
var->value++;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
/*
|
2002-09-04 22:31:48 +02:00
|
|
|
* Set the FOUND variable to indicate the result of executing the loop
|
|
|
|
* (namely, whether we looped one or more times). This must be set
|
|
|
|
* here so that it does not interfere with the value of the FOUND
|
|
|
|
* variable inside the loop processing itself.
|
2002-08-20 07:28:24 +02:00
|
|
|
*/
|
|
|
|
exec_set_found(estate, found);
|
|
|
|
|
|
|
|
return rc;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_fors Execute a query, assign each
|
|
|
|
* tuple to a record or row and
|
|
|
|
* execute a group of statements
|
|
|
|
* for it.
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_fors(PLpgSQL_execstate *estate, PLpgSQL_stmt_fors *stmt)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
PLpgSQL_rec *rec = NULL;
|
|
|
|
PLpgSQL_row *row = NULL;
|
|
|
|
SPITupleTable *tuptab;
|
2001-05-21 16:22:19 +02:00
|
|
|
Portal portal;
|
2002-08-20 07:28:24 +02:00
|
|
|
bool found = false;
|
|
|
|
int rc = PLPGSQL_RC_OK;
|
1998-09-01 06:40:42 +02:00
|
|
|
int i;
|
|
|
|
int n;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Determine if we assign to a record or a row
|
1998-08-22 14:38:39 +02:00
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
if (stmt->rec != NULL)
|
|
|
|
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
2002-08-30 02:28:41 +02:00
|
|
|
else if (stmt->row != NULL)
|
|
|
|
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
1998-09-01 06:40:42 +02:00
|
|
|
else
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unsupported target");
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2001-10-25 07:50:21 +02:00
|
|
|
* Open the implicit cursor for the statement and fetch the initial 10
|
|
|
|
* rows.
|
1998-08-22 14:38:39 +02:00
|
|
|
*/
|
2001-05-21 16:22:19 +02:00
|
|
|
exec_run_select(estate, stmt->query, 0, &portal);
|
2001-08-02 23:31:23 +02:00
|
|
|
|
2001-05-21 16:22:19 +02:00
|
|
|
SPI_cursor_fetch(portal, true, 10);
|
2001-08-02 23:31:23 +02:00
|
|
|
tuptab = SPI_tuptable;
|
2003-01-21 23:06:12 +01:00
|
|
|
n = SPI_processed;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2002-08-20 07:28:24 +02:00
|
|
|
* If the query didn't return any rows, set the target to NULL and
|
|
|
|
* return with FOUND = false.
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
|
|
|
if (n == 0)
|
2003-01-21 23:06:12 +01:00
|
|
|
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
|
2002-08-20 07:28:24 +02:00
|
|
|
else
|
2002-09-04 22:31:48 +02:00
|
|
|
found = true; /* processed at least one tuple */
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Now do the loop
|
|
|
|
*/
|
2002-08-20 07:28:24 +02:00
|
|
|
while (n > 0)
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
2001-05-21 16:22:19 +02:00
|
|
|
for (i = 0; i < n; i++)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Assign the tuple to the target
|
|
|
|
*/
|
|
|
|
exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-05-21 16:22:19 +02:00
|
|
|
/*
|
|
|
|
* Execute the statements
|
|
|
|
*/
|
|
|
|
rc = exec_stmts(estate, stmt->body);
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
if (rc != PLPGSQL_RC_OK)
|
2001-05-21 16:22:19 +02:00
|
|
|
{
|
2002-08-20 07:28:24 +02:00
|
|
|
/*
|
2003-03-02 21:45:47 +01:00
|
|
|
* We're aborting the loop, so cleanup and set FOUND.
|
|
|
|
* (This code should match the code after the loop.)
|
2002-08-20 07:28:24 +02:00
|
|
|
*/
|
|
|
|
SPI_freetuptable(tuptab);
|
|
|
|
SPI_cursor_close(portal);
|
2003-03-02 21:45:47 +01:00
|
|
|
exec_set_found(estate, found);
|
2002-08-20 07:28:24 +02:00
|
|
|
|
|
|
|
if (rc == PLPGSQL_RC_EXIT)
|
|
|
|
{
|
2001-05-21 16:22:19 +02:00
|
|
|
if (estate->exitlabel == NULL)
|
2002-08-20 07:28:24 +02:00
|
|
|
/* unlabelled exit, finish the current loop */
|
|
|
|
rc = PLPGSQL_RC_OK;
|
|
|
|
else if (stmt->label != NULL &&
|
|
|
|
strcmp(stmt->label, estate->exitlabel) == 0)
|
|
|
|
{
|
|
|
|
/* labelled exit, matches the current stmt's label */
|
|
|
|
estate->exitlabel = NULL;
|
|
|
|
rc = PLPGSQL_RC_OK;
|
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
/*
|
2002-09-04 22:31:48 +02:00
|
|
|
* otherwise, we processed a labelled exit that does
|
|
|
|
* not match the current statement's label, if any:
|
|
|
|
* return RC_EXIT so that the EXIT continues to
|
|
|
|
* recurse upward.
|
2002-08-20 07:28:24 +02:00
|
|
|
*/
|
|
|
|
}
|
2001-05-21 16:22:19 +02:00
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
return rc;
|
2001-05-21 16:22:19 +02:00
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
SPI_freetuptable(tuptab);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Fetch the next 50 tuples
|
|
|
|
*/
|
|
|
|
SPI_cursor_fetch(portal, true, 50);
|
2001-08-02 23:31:23 +02:00
|
|
|
n = SPI_processed;
|
|
|
|
tuptab = SPI_tuptable;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2003-03-02 21:45:47 +01:00
|
|
|
/*
|
|
|
|
* Release last group of tuples
|
|
|
|
*/
|
|
|
|
SPI_freetuptable(tuptab);
|
|
|
|
|
2001-05-21 16:22:19 +02:00
|
|
|
/*
|
|
|
|
* Close the implicit cursor
|
|
|
|
*/
|
|
|
|
SPI_cursor_close(portal);
|
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
/*
|
2002-09-04 22:31:48 +02:00
|
|
|
* Set the FOUND variable to indicate the result of executing the loop
|
|
|
|
* (namely, whether we looped one or more times). This must be set
|
|
|
|
* here so that it does not interfere with the value of the FOUND
|
|
|
|
* variable inside the loop processing itself.
|
2002-08-20 07:28:24 +02:00
|
|
|
*/
|
|
|
|
exec_set_found(estate, found);
|
|
|
|
|
|
|
|
return rc;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_select Run a query and assign the first
|
|
|
|
* row to a record or rowtype.
|
2002-08-20 07:28:24 +02:00
|
|
|
* ----------
|
1998-08-22 14:38:39 +02:00
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_select(PLpgSQL_execstate *estate, PLpgSQL_stmt_select *stmt)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
PLpgSQL_rec *rec = NULL;
|
|
|
|
PLpgSQL_row *row = NULL;
|
|
|
|
SPITupleTable *tuptab;
|
2001-08-02 23:31:23 +02:00
|
|
|
uint32 n;
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Initialize the global found variable to false
|
|
|
|
*/
|
|
|
|
exec_set_found(estate, false);
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Determine if we assign to a record or a row
|
|
|
|
*/
|
|
|
|
if (stmt->rec != NULL)
|
|
|
|
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
2001-08-02 23:31:23 +02:00
|
|
|
else if (stmt->row != NULL)
|
|
|
|
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
1998-09-01 06:40:42 +02:00
|
|
|
else
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unsupported target");
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Run the query
|
|
|
|
*/
|
2001-05-21 16:22:19 +02:00
|
|
|
exec_run_select(estate, stmt->query, 1, NULL);
|
2003-01-21 23:06:12 +01:00
|
|
|
tuptab = estate->eval_tuptable;
|
2001-08-02 23:31:23 +02:00
|
|
|
n = estate->eval_processed;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* If the query didn't return any row, set the target to NULL and
|
|
|
|
* return.
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
|
|
|
if (n == 0)
|
|
|
|
{
|
2003-01-21 23:06:12 +01:00
|
|
|
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_eval_cleanup(estate);
|
1998-09-01 06:40:42 +02:00
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Put the result into the target and set found to true
|
|
|
|
*/
|
|
|
|
exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
|
|
|
|
exec_set_found(estate, true);
|
2001-08-02 23:31:23 +02:00
|
|
|
|
|
|
|
exec_eval_cleanup(estate);
|
1998-09-01 06:40:42 +02:00
|
|
|
|
|
|
|
return PLPGSQL_RC_OK;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_exit Start exiting loop(s) or blocks
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_exit(PLpgSQL_execstate *estate, PLpgSQL_stmt_exit *stmt)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* If the exit has a condition, check that it's true
|
|
|
|
*/
|
|
|
|
if (stmt->cond != NULL)
|
|
|
|
{
|
2003-10-01 23:47:42 +02:00
|
|
|
bool value;
|
|
|
|
bool isnull = false;
|
|
|
|
|
|
|
|
value = exec_eval_boolean(estate, stmt->cond, &isnull);
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_eval_cleanup(estate);
|
2003-10-01 23:47:42 +02:00
|
|
|
if (isnull || !value)
|
1998-09-01 06:40:42 +02:00
|
|
|
return PLPGSQL_RC_OK;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
estate->exitlabel = stmt->label;
|
|
|
|
return PLPGSQL_RC_EXIT;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_return Evaluate an expression and start
|
|
|
|
* returning from the function.
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
2002-08-30 02:28:41 +02:00
|
|
|
/*
|
|
|
|
* If processing a set-returning PL/PgSQL function, the final RETURN
|
|
|
|
* indicates that the function is finished producing tuples. The rest
|
|
|
|
* of the work will be done at the top level.
|
|
|
|
*/
|
|
|
|
if (estate->retisset)
|
|
|
|
return PLPGSQL_RC_RETURN;
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
if (estate->retistuple)
|
|
|
|
{
|
2001-08-02 23:31:23 +02:00
|
|
|
/* initialize for null result tuple */
|
|
|
|
estate->retval = (Datum) 0;
|
|
|
|
estate->rettupdesc = NULL;
|
|
|
|
estate->retisnull = true;
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
if (stmt->retrecno >= 0)
|
|
|
|
{
|
|
|
|
PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->retrecno]);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
if (HeapTupleIsValid(rec->tup))
|
|
|
|
{
|
2001-11-05 20:41:56 +01:00
|
|
|
estate->retval = (Datum) rec->tup;
|
|
|
|
estate->rettupdesc = rec->tupdesc;
|
2001-08-02 23:31:23 +02:00
|
|
|
estate->retisnull = false;
|
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
return PLPGSQL_RC_RETURN;
|
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2003-09-26 01:02:12 +02:00
|
|
|
if (stmt->retrowno >= 0)
|
|
|
|
{
|
|
|
|
PLpgSQL_row *row = (PLpgSQL_row *) (estate->datums[stmt->retrowno]);
|
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
if (row->rowtupdesc) /* should always be true here */
|
2003-09-26 01:02:12 +02:00
|
|
|
{
|
|
|
|
estate->retval = (Datum) make_tuple_from_row(estate, row,
|
2004-08-29 07:07:03 +02:00
|
|
|
row->rowtupdesc);
|
|
|
|
if (estate->retval == (Datum) NULL) /* should not happen */
|
2003-09-26 01:02:12 +02:00
|
|
|
elog(ERROR, "row not compatible with its own tupdesc");
|
|
|
|
estate->rettupdesc = row->rowtupdesc;
|
|
|
|
estate->retisnull = false;
|
|
|
|
}
|
|
|
|
return PLPGSQL_RC_RETURN;
|
|
|
|
}
|
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
if (stmt->expr != NULL)
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
2001-05-21 16:22:19 +02:00
|
|
|
exec_run_select(estate, stmt->expr, 1, NULL);
|
2001-08-02 23:31:23 +02:00
|
|
|
if (estate->eval_processed > 0)
|
|
|
|
{
|
2001-11-05 20:41:56 +01:00
|
|
|
estate->retval = (Datum) estate->eval_tuptable->vals[0];
|
|
|
|
estate->rettupdesc = estate->eval_tuptable->tupdesc;
|
2001-08-02 23:31:23 +02:00
|
|
|
estate->retisnull = false;
|
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
return PLPGSQL_RC_RETURN;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2002-08-30 02:28:41 +02:00
|
|
|
if (estate->fn_rettype == VOIDOID)
|
|
|
|
{
|
|
|
|
/* Special hack for function returning VOID */
|
|
|
|
estate->retval = (Datum) 0;
|
|
|
|
estate->retisnull = false;
|
|
|
|
estate->rettype = VOIDOID;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Normal case for scalar results */
|
|
|
|
estate->retval = exec_eval_expr(estate, stmt->expr,
|
|
|
|
&(estate->retisnull),
|
|
|
|
&(estate->rettype));
|
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
return PLPGSQL_RC_RETURN;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2002-11-10 01:35:58 +01:00
|
|
|
/* ----------
|
|
|
|
* exec_stmt_return_next Evaluate an expression and add it to the
|
|
|
|
* list of tuples returned by the current
|
|
|
|
* SRF.
|
|
|
|
* ----------
|
2002-08-30 02:28:41 +02:00
|
|
|
*/
|
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_return_next(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_return_next *stmt)
|
2002-08-30 02:28:41 +02:00
|
|
|
{
|
2002-09-01 18:28:06 +02:00
|
|
|
TupleDesc tupdesc;
|
|
|
|
int natts;
|
|
|
|
HeapTuple tuple;
|
2002-08-30 02:28:41 +02:00
|
|
|
bool free_tuple = false;
|
|
|
|
|
|
|
|
if (!estate->retisset)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
2003-08-04 02:43:34 +02:00
|
|
|
errmsg("cannot use RETURN NEXT in a non-SETOF function")));
|
2002-08-30 02:28:41 +02:00
|
|
|
|
|
|
|
if (estate->tuple_store == NULL)
|
|
|
|
exec_init_tuple_store(estate);
|
|
|
|
|
2002-08-31 01:59:46 +02:00
|
|
|
/* rettupdesc will be filled by exec_init_tuple_store */
|
|
|
|
tupdesc = estate->rettupdesc;
|
2002-09-04 22:31:48 +02:00
|
|
|
natts = tupdesc->natts;
|
2002-08-31 01:59:46 +02:00
|
|
|
|
2002-08-30 02:28:41 +02:00
|
|
|
if (stmt->rec)
|
|
|
|
{
|
|
|
|
PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
2002-08-31 01:59:46 +02:00
|
|
|
|
2003-01-21 23:06:12 +01:00
|
|
|
if (!HeapTupleIsValid(rec->tup))
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
errmsg("record \"%s\" is not assigned yet",
|
|
|
|
rec->refname),
|
|
|
|
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
|
2002-08-31 01:59:46 +02:00
|
|
|
if (!compatible_tupdesc(tupdesc, rec->tupdesc))
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
2003-08-04 02:43:34 +02:00
|
|
|
errmsg("wrong record type supplied in RETURN NEXT")));
|
2002-08-30 02:28:41 +02:00
|
|
|
tuple = rec->tup;
|
|
|
|
}
|
|
|
|
else if (stmt->row)
|
|
|
|
{
|
2003-09-26 01:02:12 +02:00
|
|
|
tuple = make_tuple_from_row(estate, stmt->row, tupdesc);
|
|
|
|
if (tuple == NULL)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
2004-08-29 07:07:03 +02:00
|
|
|
errmsg("wrong record type supplied in RETURN NEXT")));
|
2002-08-30 02:28:41 +02:00
|
|
|
free_tuple = true;
|
|
|
|
}
|
|
|
|
else if (stmt->expr)
|
|
|
|
{
|
2002-09-04 22:31:48 +02:00
|
|
|
Datum retval;
|
|
|
|
bool isNull;
|
|
|
|
Oid rettype;
|
|
|
|
char nullflag;
|
2002-08-30 02:28:41 +02:00
|
|
|
|
2002-08-31 01:59:46 +02:00
|
|
|
if (natts != 1)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
2003-08-04 02:43:34 +02:00
|
|
|
errmsg("wrong result type supplied in RETURN NEXT")));
|
2002-08-30 02:28:41 +02:00
|
|
|
|
|
|
|
retval = exec_eval_expr(estate,
|
|
|
|
stmt->expr,
|
|
|
|
&isNull,
|
2002-08-31 01:59:46 +02:00
|
|
|
&rettype);
|
|
|
|
|
|
|
|
/* coerce type if needed */
|
2003-03-25 04:16:41 +01:00
|
|
|
retval = exec_simple_cast_value(retval,
|
|
|
|
rettype,
|
|
|
|
tupdesc->attrs[0]->atttypid,
|
|
|
|
tupdesc->attrs[0]->atttypmod,
|
|
|
|
&isNull);
|
2002-08-30 02:28:41 +02:00
|
|
|
|
|
|
|
nullflag = isNull ? 'n' : ' ';
|
|
|
|
|
2002-08-31 01:59:46 +02:00
|
|
|
tuple = heap_formtuple(tupdesc, &retval, &nullflag);
|
2002-08-30 02:28:41 +02:00
|
|
|
|
|
|
|
free_tuple = true;
|
|
|
|
|
|
|
|
exec_eval_cleanup(estate);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
|
|
errmsg("RETURN NEXT must have a parameter")));
|
2002-08-30 02:28:41 +02:00
|
|
|
tuple = NULL; /* keep compiler quiet */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HeapTupleIsValid(tuple))
|
|
|
|
{
|
|
|
|
MemoryContext oldcxt;
|
|
|
|
|
|
|
|
oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
|
|
|
|
tuplestore_puttuple(estate->tuple_store, tuple);
|
|
|
|
MemoryContextSwitchTo(oldcxt);
|
|
|
|
|
|
|
|
if (free_tuple)
|
|
|
|
heap_freetuple(tuple);
|
|
|
|
}
|
|
|
|
|
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_init_tuple_store(PLpgSQL_execstate *estate)
|
2002-08-30 02:28:41 +02:00
|
|
|
{
|
|
|
|
ReturnSetInfo *rsi = estate->rsi;
|
|
|
|
MemoryContext oldcxt;
|
|
|
|
|
2002-08-31 01:59:46 +02:00
|
|
|
/*
|
|
|
|
* Check caller can handle a set result in the way we want
|
|
|
|
*/
|
2002-08-30 02:28:41 +02:00
|
|
|
if (!rsi || !IsA(rsi, ReturnSetInfo) ||
|
2002-08-31 01:59:46 +02:00
|
|
|
(rsi->allowedModes & SFRM_Materialize) == 0 ||
|
|
|
|
rsi->expectedDesc == NULL)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("set-valued function called in context that cannot accept a set")));
|
2002-08-30 02:28:41 +02:00
|
|
|
|
|
|
|
estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
|
|
|
|
|
|
|
|
oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
|
2004-02-03 18:34:04 +01:00
|
|
|
estate->tuple_store = tuplestore_begin_heap(true, false, work_mem);
|
2002-08-30 02:28:41 +02:00
|
|
|
MemoryContextSwitchTo(oldcxt);
|
2002-08-31 01:59:46 +02:00
|
|
|
|
|
|
|
estate->rettupdesc = rsi->expectedDesc;
|
2002-08-30 02:28:41 +02:00
|
|
|
}
|
|
|
|
|
1998-08-22 14:38:39 +02:00
|
|
|
/* ----------
|
2003-07-26 01:37:31 +02:00
|
|
|
* exec_stmt_raise Build a message and throw it with elog()
|
1998-08-22 14:38:39 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
2003-03-25 01:34:24 +01:00
|
|
|
Oid paramtypeid;
|
|
|
|
Datum paramvalue;
|
|
|
|
bool paramisnull;
|
1998-09-01 06:40:42 +02:00
|
|
|
char *extval;
|
|
|
|
int pidx = 0;
|
|
|
|
char c[2] = {0, 0};
|
|
|
|
char *cp;
|
|
|
|
PLpgSQL_dstring ds;
|
|
|
|
|
|
|
|
plpgsql_dstring_init(&ds);
|
|
|
|
|
|
|
|
for (cp = stmt->message; *cp; cp++)
|
|
|
|
{
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2004-02-25 19:10:51 +01:00
|
|
|
* Occurrences of a single % are replaced by the next argument's
|
2003-07-26 01:37:31 +02:00
|
|
|
* external representation. Double %'s are converted to one %.
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
|
|
|
if ((c[0] = *cp) == '%')
|
|
|
|
{
|
|
|
|
cp++;
|
|
|
|
if (*cp == '%')
|
|
|
|
{
|
|
|
|
plpgsql_dstring_append(&ds, c);
|
|
|
|
continue;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
cp--;
|
|
|
|
if (pidx >= stmt->nparams)
|
|
|
|
{
|
|
|
|
plpgsql_dstring_append(&ds, c);
|
|
|
|
continue;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
2003-03-25 01:34:24 +01:00
|
|
|
exec_eval_datum(estate, estate->datums[stmt->params[pidx]],
|
|
|
|
InvalidOid,
|
|
|
|
¶mtypeid, ¶mvalue, ¶misnull);
|
|
|
|
if (paramisnull)
|
|
|
|
extval = "<NULL>";
|
|
|
|
else
|
2003-10-01 23:47:42 +02:00
|
|
|
extval = convert_value_to_string(paramvalue, paramtypeid);
|
2003-03-25 01:34:24 +01:00
|
|
|
plpgsql_dstring_append(&ds, extval);
|
1998-09-01 06:40:42 +02:00
|
|
|
pidx++;
|
|
|
|
continue;
|
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
plpgsql_dstring_append(&ds, c);
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-04-24 23:16:45 +02:00
|
|
|
* Throw the error (may or may not come back)
|
1998-08-22 14:38:39 +02:00
|
|
|
*/
|
2003-08-04 02:43:34 +02:00
|
|
|
estate->err_text = raise_skip_msg; /* suppress traceback of raise */
|
2003-04-24 23:16:45 +02:00
|
|
|
|
|
|
|
ereport(stmt->elog_level,
|
2004-08-29 07:07:03 +02:00
|
|
|
((stmt->elog_level >= ERROR) ? errcode(ERRCODE_RAISE_EXCEPTION) : 0,
|
|
|
|
errmsg_internal("%s", plpgsql_dstring_get(&ds))));
|
2003-04-24 23:16:45 +02:00
|
|
|
|
|
|
|
estate->err_text = NULL; /* un-suppress... */
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
plpgsql_dstring_free(&ds);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
return PLPGSQL_RC_OK;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
/* ----------
|
2002-08-30 02:28:41 +02:00
|
|
|
* Initialize a mostly empty execution state
|
2001-08-02 23:31:23 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static void
|
2004-08-30 04:54:42 +02:00
|
|
|
plpgsql_estate_setup(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_function *func,
|
2002-08-30 02:28:41 +02:00
|
|
|
ReturnSetInfo *rsi)
|
2001-08-02 23:31:23 +02:00
|
|
|
{
|
|
|
|
estate->retval = (Datum) 0;
|
|
|
|
estate->retisnull = true;
|
|
|
|
estate->rettype = InvalidOid;
|
2002-08-30 02:28:41 +02:00
|
|
|
|
|
|
|
estate->fn_rettype = func->fn_rettype;
|
2001-08-02 23:31:23 +02:00
|
|
|
estate->retistuple = func->fn_retistuple;
|
|
|
|
estate->retisset = func->fn_retset;
|
2002-08-30 02:28:41 +02:00
|
|
|
|
2004-09-13 22:10:13 +02:00
|
|
|
estate->readonly_func = func->fn_readonly;
|
|
|
|
|
2002-08-30 02:28:41 +02:00
|
|
|
estate->rettupdesc = NULL;
|
2001-08-02 23:31:23 +02:00
|
|
|
estate->exitlabel = NULL;
|
|
|
|
|
2002-08-30 02:28:41 +02:00
|
|
|
estate->tuple_store = NULL;
|
|
|
|
estate->tuple_store_cxt = NULL;
|
|
|
|
estate->rsi = rsi;
|
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
estate->trig_nargs = 0;
|
|
|
|
estate->trig_argv = NULL;
|
|
|
|
|
|
|
|
estate->found_varno = func->found_varno;
|
|
|
|
estate->ndatums = func->ndatums;
|
|
|
|
estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
|
|
|
|
/* caller is expected to fill the datums array */
|
|
|
|
|
|
|
|
estate->eval_tuptable = NULL;
|
|
|
|
estate->eval_processed = 0;
|
|
|
|
estate->eval_lastoid = InvalidOid;
|
|
|
|
estate->eval_econtext = NULL;
|
2003-04-24 23:16:45 +02:00
|
|
|
|
|
|
|
estate->err_func = func;
|
|
|
|
estate->err_stmt = NULL;
|
|
|
|
estate->err_text = NULL;
|
2001-08-02 23:31:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Release temporary memory used by expression/subselect evaluation
|
|
|
|
*
|
|
|
|
* NB: the result of the evaluation is no longer valid after this is done,
|
|
|
|
* unless it is a pass-by-value datatype.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static void
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_eval_cleanup(PLpgSQL_execstate *estate)
|
2001-08-02 23:31:23 +02:00
|
|
|
{
|
2004-09-13 22:10:13 +02:00
|
|
|
/* Clear result of a full SPI_execute */
|
2001-08-02 23:31:23 +02:00
|
|
|
if (estate->eval_tuptable != NULL)
|
|
|
|
SPI_freetuptable(estate->eval_tuptable);
|
|
|
|
estate->eval_tuptable = NULL;
|
|
|
|
|
2003-09-29 01:37:45 +02:00
|
|
|
/* Clear result of exec_eval_simple_expr (but keep the econtext) */
|
2001-08-02 23:31:23 +02:00
|
|
|
if (estate->eval_econtext != NULL)
|
2003-09-29 01:37:45 +02:00
|
|
|
ResetExprContext(estate->eval_econtext);
|
2001-08-02 23:31:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
1999-01-27 17:15:22 +01:00
|
|
|
/* ----------
|
|
|
|
* Generate a prepared plan
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static void
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_prepare_plan(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_expr *expr)
|
1999-01-27 17:15:22 +01:00
|
|
|
{
|
|
|
|
int i;
|
2002-12-13 20:46:01 +01:00
|
|
|
_SPI_plan *spi_plan;
|
1999-01-27 17:15:22 +01:00
|
|
|
void *plan;
|
|
|
|
Oid *argtypes;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2001-10-25 07:50:21 +02:00
|
|
|
* We need a temporary argtypes array to load with data. (The finished
|
|
|
|
* plan structure will contain a copy of it.)
|
1999-01-27 17:15:22 +01:00
|
|
|
*/
|
2004-06-05 21:48:09 +02:00
|
|
|
argtypes = (Oid *) palloc(expr->nparams * sizeof(Oid));
|
1999-01-27 17:15:22 +01:00
|
|
|
|
|
|
|
for (i = 0; i < expr->nparams; i++)
|
|
|
|
{
|
2003-08-04 02:43:34 +02:00
|
|
|
Datum paramval;
|
|
|
|
bool paramisnull;
|
1999-01-27 17:15:22 +01:00
|
|
|
|
2003-03-25 01:34:24 +01:00
|
|
|
exec_eval_datum(estate, estate->datums[expr->params[i]],
|
|
|
|
InvalidOid,
|
|
|
|
&argtypes[i], ¶mval, ¶misnull);
|
1999-01-27 17:15:22 +01:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-01-27 17:15:22 +01:00
|
|
|
* Generate and save the plan
|
|
|
|
*/
|
|
|
|
plan = SPI_prepare(expr->query, expr->nparams, argtypes);
|
|
|
|
if (plan == NULL)
|
2004-08-13 20:47:56 +02:00
|
|
|
{
|
|
|
|
/* Some SPI errors deserve specific error messages */
|
|
|
|
switch (SPI_result)
|
|
|
|
{
|
|
|
|
case SPI_ERROR_COPY:
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2004-08-29 07:07:03 +02:00
|
|
|
errmsg("cannot COPY to/from client in PL/pgSQL")));
|
2004-08-13 20:47:56 +02:00
|
|
|
case SPI_ERROR_CURSOR:
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2004-08-29 07:07:03 +02:00
|
|
|
errmsg("cannot manipulate cursors directly in PL/pgSQL"),
|
|
|
|
errhint("Use PL/pgSQL's cursor features instead.")));
|
2004-08-13 20:47:56 +02:00
|
|
|
case SPI_ERROR_TRANSACTION:
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2004-08-29 07:07:03 +02:00
|
|
|
errmsg("cannot begin/end transactions in PL/pgSQL"),
|
2004-08-13 20:47:56 +02:00
|
|
|
errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
|
|
|
|
default:
|
|
|
|
elog(ERROR, "SPI_prepare failed for \"%s\": %s",
|
|
|
|
expr->query, SPI_result_code_string(SPI_result));
|
|
|
|
}
|
|
|
|
}
|
1999-01-27 17:15:22 +01:00
|
|
|
expr->plan = SPI_saveplan(plan);
|
2002-12-13 20:46:01 +01:00
|
|
|
spi_plan = (_SPI_plan *) expr->plan;
|
|
|
|
expr->plan_argtypes = spi_plan->argtypes;
|
2003-09-29 01:37:45 +02:00
|
|
|
expr->expr_simple_expr = NULL;
|
1999-01-27 17:15:22 +01:00
|
|
|
exec_simple_check_plan(expr);
|
2001-08-02 23:31:23 +02:00
|
|
|
|
2002-10-20 00:10:58 +02:00
|
|
|
SPI_freeplan(plan);
|
2001-08-02 23:31:23 +02:00
|
|
|
pfree(argtypes);
|
1999-01-27 17:15:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
1998-08-22 14:38:39 +02:00
|
|
|
/* ----------
|
|
|
|
* exec_stmt_execsql Execute an SQL statement not
|
|
|
|
* returning any data.
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_execsql(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_execsql *stmt)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
int i;
|
|
|
|
Datum *values;
|
|
|
|
char *nulls;
|
|
|
|
int rc;
|
|
|
|
PLpgSQL_expr *expr = stmt->sqlstmt;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* On the first call for this expression generate the plan
|
|
|
|
*/
|
|
|
|
if (expr->plan == NULL)
|
1999-01-27 17:15:22 +01:00
|
|
|
exec_prepare_plan(estate, expr);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2004-09-13 22:10:13 +02:00
|
|
|
* Now build up the values and nulls arguments for SPI_execute_plan()
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2004-06-05 21:48:09 +02:00
|
|
|
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
|
|
|
|
nulls = (char *) palloc(expr->nparams * sizeof(char));
|
1998-09-01 06:40:42 +02:00
|
|
|
|
|
|
|
for (i = 0; i < expr->nparams; i++)
|
|
|
|
{
|
2003-03-25 01:34:24 +01:00
|
|
|
PLpgSQL_datum *datum = estate->datums[expr->params[i]];
|
|
|
|
Oid paramtypeid;
|
|
|
|
bool paramisnull;
|
|
|
|
|
|
|
|
exec_eval_datum(estate, datum, expr->plan_argtypes[i],
|
|
|
|
¶mtypeid, &values[i], ¶misnull);
|
|
|
|
if (paramisnull)
|
|
|
|
nulls[i] = 'n';
|
|
|
|
else
|
|
|
|
nulls[i] = ' ';
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Execute the plan
|
|
|
|
*/
|
2004-09-13 22:10:13 +02:00
|
|
|
rc = SPI_execute_plan(expr->plan, values, nulls,
|
|
|
|
estate->readonly_func, 0);
|
1998-09-01 06:40:42 +02:00
|
|
|
switch (rc)
|
|
|
|
{
|
|
|
|
case SPI_OK_UTILITY:
|
|
|
|
case SPI_OK_SELINTO:
|
2002-08-20 07:28:24 +02:00
|
|
|
break;
|
|
|
|
|
2002-08-29 06:12:03 +02:00
|
|
|
case SPI_OK_INSERT:
|
|
|
|
case SPI_OK_DELETE:
|
|
|
|
case SPI_OK_UPDATE:
|
2002-09-04 22:31:48 +02:00
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
/*
|
|
|
|
* If the INSERT, DELETE, or UPDATE query affected at least
|
|
|
|
* one tuple, set the magic 'FOUND' variable to true. This
|
|
|
|
* conforms with the behavior of PL/SQL.
|
|
|
|
*/
|
2002-08-29 06:12:03 +02:00
|
|
|
exec_set_found(estate, (SPI_processed != 0));
|
1998-09-01 06:40:42 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SPI_OK_SELECT:
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
2003-08-04 02:43:34 +02:00
|
|
|
errmsg("SELECT query has no destination for result data"),
|
2003-07-26 01:37:31 +02:00
|
|
|
errhint("If you want to discard the results, use PERFORM instead.")));
|
1998-09-01 06:40:42 +02:00
|
|
|
|
|
|
|
default:
|
2004-09-13 22:10:13 +02:00
|
|
|
elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
|
2004-07-31 22:55:45 +02:00
|
|
|
expr->query, SPI_result_code_string(rc));
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
2001-08-02 23:31:23 +02:00
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
/*
|
2004-09-13 22:10:13 +02:00
|
|
|
* Release any result tuples from SPI_execute_plan (probably shouldn't be
|
2001-10-25 07:50:21 +02:00
|
|
|
* any)
|
|
|
|
*/
|
2001-08-02 23:31:23 +02:00
|
|
|
SPI_freetuptable(SPI_tuptable);
|
|
|
|
|
|
|
|
/* Save result info for GET DIAGNOSTICS */
|
|
|
|
estate->eval_processed = SPI_processed;
|
|
|
|
estate->eval_lastoid = SPI_lastoid;
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
pfree(values);
|
|
|
|
pfree(nulls);
|
|
|
|
|
|
|
|
return PLPGSQL_RC_OK;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2000-08-31 15:26:16 +02:00
|
|
|
/* ----------
|
|
|
|
* exec_stmt_dynexecute Execute a dynamic SQL query not
|
|
|
|
* returning any data.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_dynexecute(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_stmt_dynexecute *stmt)
|
2000-08-31 15:26:16 +02:00
|
|
|
{
|
|
|
|
Datum query;
|
|
|
|
bool isnull = false;
|
|
|
|
Oid restype;
|
|
|
|
char *querystr;
|
2001-01-04 03:38:02 +01:00
|
|
|
int exec_res;
|
2000-08-31 15:26:16 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* First we evaluate the string expression after the EXECUTE keyword.
|
|
|
|
* It's result is the querystring we have to execute.
|
2000-08-31 15:26:16 +02:00
|
|
|
*/
|
|
|
|
query = exec_eval_expr(estate, stmt->query, &isnull, &restype);
|
|
|
|
if (isnull)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
|
|
errmsg("cannot EXECUTE a null querystring")));
|
2000-08-31 15:26:16 +02:00
|
|
|
|
2003-10-01 23:47:42 +02:00
|
|
|
/* Get the C-String representation */
|
|
|
|
querystr = convert_value_to_string(query, restype);
|
2000-08-31 15:26:16 +02:00
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_eval_cleanup(estate);
|
2000-11-16 23:30:52 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2004-09-13 22:10:13 +02:00
|
|
|
* Call SPI_execute() without preparing a saved plan. The returncode can
|
2001-03-22 07:16:21 +01:00
|
|
|
* be any standard OK. Note that while a SELECT is allowed, its
|
|
|
|
* results will be discarded.
|
2000-08-31 15:26:16 +02:00
|
|
|
*/
|
2004-09-13 22:10:13 +02:00
|
|
|
exec_res = SPI_execute(querystr, estate->readonly_func, 0);
|
2001-01-04 03:38:02 +01:00
|
|
|
switch (exec_res)
|
2000-08-31 15:26:16 +02:00
|
|
|
{
|
2001-01-04 03:38:02 +01:00
|
|
|
case SPI_OK_SELECT:
|
2000-08-31 15:26:16 +02:00
|
|
|
case SPI_OK_INSERT:
|
|
|
|
case SPI_OK_UPDATE:
|
|
|
|
case SPI_OK_DELETE:
|
2001-01-04 03:38:02 +01:00
|
|
|
case SPI_OK_UTILITY:
|
2000-08-31 15:26:16 +02:00
|
|
|
break;
|
|
|
|
|
2001-01-04 03:38:02 +01:00
|
|
|
case 0:
|
2001-03-22 05:01:46 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Also allow a zero return, which implies the querystring
|
2001-01-04 03:38:02 +01:00
|
|
|
* contained no commands.
|
|
|
|
*/
|
2000-08-31 15:26:16 +02:00
|
|
|
break;
|
|
|
|
|
2001-02-09 01:14:26 +01:00
|
|
|
case SPI_OK_SELINTO:
|
2001-03-22 05:01:46 +01:00
|
|
|
|
2001-02-09 01:14:26 +01:00
|
|
|
/*
|
2002-09-04 22:31:48 +02:00
|
|
|
* We want to disallow SELECT INTO for now, because its
|
|
|
|
* 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
|
2004-09-13 22:10:13 +02:00
|
|
|
* SPI_execute.) However, CREATE AS should be allowed ... and
|
2002-09-04 22:31:48 +02:00
|
|
|
* 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!
|
2001-02-09 01:14:26 +01:00
|
|
|
*/
|
2002-09-04 22:31:48 +02:00
|
|
|
{
|
|
|
|
char *ptr;
|
2002-03-25 08:41:10 +01:00
|
|
|
|
2002-09-04 22:31:48 +02:00
|
|
|
for (ptr = querystr; *ptr; ptr++)
|
|
|
|
if (!isspace((unsigned char) *ptr))
|
|
|
|
break;
|
|
|
|
if (*ptr == 'S' || *ptr == 's')
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("EXECUTE of SELECT ... INTO is not implemented yet")));
|
2002-09-04 22:31:48 +02:00
|
|
|
break;
|
|
|
|
}
|
2001-02-09 01:14:26 +01:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/* Some SPI errors deserve specific error messages */
|
2004-08-13 20:47:56 +02:00
|
|
|
case SPI_ERROR_COPY:
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("cannot COPY to/from client in PL/pgSQL")));
|
|
|
|
case SPI_ERROR_CURSOR:
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2004-08-29 07:07:03 +02:00
|
|
|
errmsg("cannot manipulate cursors directly in PL/pgSQL"),
|
2004-08-13 20:47:56 +02:00
|
|
|
errhint("Use PL/pgSQL's cursor features instead.")));
|
|
|
|
case SPI_ERROR_TRANSACTION:
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("cannot begin/end transactions in PL/pgSQL"),
|
|
|
|
errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
|
|
|
|
|
2000-08-31 15:26:16 +02:00
|
|
|
default:
|
2004-09-13 22:10:13 +02:00
|
|
|
elog(ERROR, "SPI_execute failed executing query \"%s\": %s",
|
2004-07-31 22:55:45 +02:00
|
|
|
querystr, SPI_result_code_string(exec_res));
|
2000-08-31 15:26:16 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2004-09-13 22:10:13 +02:00
|
|
|
/* Release any result from SPI_execute, as well as the querystring */
|
2001-08-02 23:31:23 +02:00
|
|
|
SPI_freetuptable(SPI_tuptable);
|
2000-08-31 15:26:16 +02:00
|
|
|
pfree(querystr);
|
2001-08-02 23:31:23 +02:00
|
|
|
|
|
|
|
/* Save result info for GET DIAGNOSTICS */
|
|
|
|
estate->eval_processed = SPI_processed;
|
|
|
|
estate->eval_lastoid = SPI_lastoid;
|
|
|
|
|
2000-08-31 15:26:16 +02:00
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_dynfors Execute a dynamic query, assign each
|
|
|
|
* tuple to a record or row and
|
|
|
|
* execute a group of statements
|
|
|
|
* for it.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
|
2000-08-31 15:26:16 +02:00
|
|
|
{
|
|
|
|
Datum query;
|
|
|
|
bool isnull = false;
|
|
|
|
Oid restype;
|
|
|
|
char *querystr;
|
|
|
|
PLpgSQL_rec *rec = NULL;
|
|
|
|
PLpgSQL_row *row = NULL;
|
|
|
|
SPITupleTable *tuptab;
|
2002-08-20 07:28:24 +02:00
|
|
|
int rc = PLPGSQL_RC_OK;
|
2000-08-31 15:26:16 +02:00
|
|
|
int i;
|
|
|
|
int n;
|
2001-10-25 07:50:21 +02:00
|
|
|
void *plan;
|
2001-05-21 16:22:19 +02:00
|
|
|
Portal portal;
|
2002-08-20 07:28:24 +02:00
|
|
|
bool found = false;
|
2000-08-31 15:26:16 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-08-31 15:26:16 +02:00
|
|
|
* Determine if we assign to a record or a row
|
|
|
|
*/
|
|
|
|
if (stmt->rec != NULL)
|
|
|
|
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
2002-08-30 02:28:41 +02:00
|
|
|
else if (stmt->row != NULL)
|
|
|
|
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
2000-08-31 15:26:16 +02:00
|
|
|
else
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unsupported target");
|
2000-08-31 15:26:16 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* Evaluate the string expression after the EXECUTE keyword. It's
|
|
|
|
* result is the querystring we have to execute.
|
2000-08-31 15:26:16 +02:00
|
|
|
*/
|
|
|
|
query = exec_eval_expr(estate, stmt->query, &isnull, &restype);
|
|
|
|
if (isnull)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
|
|
errmsg("cannot EXECUTE a null querystring")));
|
2000-08-31 15:26:16 +02:00
|
|
|
|
2003-10-01 23:47:42 +02:00
|
|
|
/* Get the C-String representation */
|
|
|
|
querystr = convert_value_to_string(query, restype);
|
2000-08-31 15:26:16 +02:00
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_eval_cleanup(estate);
|
2000-11-16 23:30:52 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2001-05-21 16:22:19 +02:00
|
|
|
* Prepare a plan and open an implicit cursor for the query
|
2000-08-31 15:26:16 +02:00
|
|
|
*/
|
2001-05-21 16:22:19 +02:00
|
|
|
plan = SPI_prepare(querystr, 0, NULL);
|
|
|
|
if (plan == NULL)
|
2004-07-31 22:55:45 +02:00
|
|
|
elog(ERROR, "SPI_prepare failed for \"%s\": %s",
|
|
|
|
querystr, SPI_result_code_string(SPI_result));
|
2004-09-13 22:10:13 +02:00
|
|
|
portal = SPI_cursor_open(NULL, plan, NULL, NULL,
|
|
|
|
estate->readonly_func);
|
2001-05-21 16:22:19 +02:00
|
|
|
if (portal == NULL)
|
2004-07-31 22:55:45 +02:00
|
|
|
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
|
|
|
|
querystr, SPI_result_code_string(SPI_result));
|
2000-08-31 15:26:16 +02:00
|
|
|
pfree(querystr);
|
2001-05-21 16:22:19 +02:00
|
|
|
SPI_freeplan(plan);
|
2000-08-31 15:26:16 +02:00
|
|
|
|
2001-05-21 16:22:19 +02:00
|
|
|
/*
|
|
|
|
* Fetch the initial 10 tuples
|
|
|
|
*/
|
|
|
|
SPI_cursor_fetch(portal, true, 10);
|
2001-08-02 23:31:23 +02:00
|
|
|
tuptab = SPI_tuptable;
|
2003-01-21 23:06:12 +01:00
|
|
|
n = SPI_processed;
|
2000-08-31 15:26:16 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2002-08-20 07:28:24 +02:00
|
|
|
* If the query didn't return any rows, set the target to NULL and
|
|
|
|
* return with FOUND = false.
|
2000-08-31 15:26:16 +02:00
|
|
|
*/
|
|
|
|
if (n == 0)
|
2003-01-21 23:06:12 +01:00
|
|
|
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
|
2002-08-20 07:28:24 +02:00
|
|
|
else
|
2003-03-02 21:45:47 +01:00
|
|
|
found = true; /* processed at least one tuple */
|
2000-08-31 15:26:16 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-08-31 15:26:16 +02:00
|
|
|
* Now do the loop
|
|
|
|
*/
|
2002-08-20 07:28:24 +02:00
|
|
|
while (n > 0)
|
2000-08-31 15:26:16 +02:00
|
|
|
{
|
2001-05-21 16:22:19 +02:00
|
|
|
for (i = 0; i < n; i++)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Assign the tuple to the target
|
|
|
|
*/
|
|
|
|
exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Execute the statements
|
|
|
|
*/
|
|
|
|
rc = exec_stmts(estate, stmt->body);
|
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
if (rc != PLPGSQL_RC_OK)
|
2001-05-21 16:22:19 +02:00
|
|
|
{
|
2003-03-02 21:45:47 +01:00
|
|
|
/*
|
|
|
|
* We're aborting the loop, so cleanup and set FOUND.
|
|
|
|
* (This code should match the code after the loop.)
|
|
|
|
*/
|
2002-08-20 07:28:24 +02:00
|
|
|
SPI_freetuptable(tuptab);
|
|
|
|
SPI_cursor_close(portal);
|
2003-03-02 21:45:47 +01:00
|
|
|
exec_set_found(estate, found);
|
2001-05-21 16:22:19 +02:00
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
if (rc == PLPGSQL_RC_EXIT)
|
|
|
|
{
|
2001-05-21 16:22:19 +02:00
|
|
|
if (estate->exitlabel == NULL)
|
2002-08-20 07:28:24 +02:00
|
|
|
/* unlabelled exit, finish the current loop */
|
|
|
|
rc = PLPGSQL_RC_OK;
|
|
|
|
else if (stmt->label != NULL &&
|
|
|
|
strcmp(stmt->label, estate->exitlabel) == 0)
|
|
|
|
{
|
|
|
|
/* labelled exit, matches the current stmt's label */
|
|
|
|
estate->exitlabel = NULL;
|
|
|
|
rc = PLPGSQL_RC_OK;
|
|
|
|
}
|
2001-05-21 16:22:19 +02:00
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
/*
|
2002-09-04 22:31:48 +02:00
|
|
|
* otherwise, we processed a labelled exit that does
|
|
|
|
* not match the current statement's label, if any:
|
|
|
|
* return RC_EXIT so that the EXIT continues to
|
|
|
|
* recurse upward.
|
2002-08-20 07:28:24 +02:00
|
|
|
*/
|
|
|
|
}
|
2001-05-21 16:22:19 +02:00
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
return rc;
|
2001-05-21 16:22:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SPI_freetuptable(tuptab);
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2001-05-21 16:22:19 +02:00
|
|
|
* Fetch the next 50 tuples
|
2000-08-31 15:26:16 +02:00
|
|
|
*/
|
2001-05-21 16:22:19 +02:00
|
|
|
SPI_cursor_fetch(portal, true, 50);
|
2001-08-02 23:31:23 +02:00
|
|
|
n = SPI_processed;
|
|
|
|
tuptab = SPI_tuptable;
|
2001-05-21 16:22:19 +02:00
|
|
|
}
|
2000-08-31 15:26:16 +02:00
|
|
|
|
2001-05-21 16:22:19 +02:00
|
|
|
/*
|
2003-03-02 21:45:47 +01:00
|
|
|
* Release last group of tuples
|
|
|
|
*/
|
|
|
|
SPI_freetuptable(tuptab);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Close the implicit cursor
|
2001-05-21 16:22:19 +02:00
|
|
|
*/
|
|
|
|
SPI_cursor_close(portal);
|
|
|
|
|
2002-08-20 07:28:24 +02:00
|
|
|
/*
|
2002-09-04 22:31:48 +02:00
|
|
|
* Set the FOUND variable to indicate the result of executing the loop
|
|
|
|
* (namely, whether we looped one or more times). This must be set
|
|
|
|
* here so that it does not interfere with the value of the FOUND
|
|
|
|
* variable inside the loop processing itself.
|
2002-08-20 07:28:24 +02:00
|
|
|
*/
|
|
|
|
exec_set_found(estate, found);
|
|
|
|
|
2001-05-21 16:22:19 +02:00
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_open Execute an OPEN cursor statement
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
|
2001-05-21 16:22:19 +02:00
|
|
|
{
|
|
|
|
PLpgSQL_var *curvar = NULL;
|
2001-10-25 07:50:21 +02:00
|
|
|
char *curname = NULL;
|
2001-05-21 16:22:19 +02:00
|
|
|
PLpgSQL_expr *query = NULL;
|
|
|
|
Portal portal;
|
|
|
|
int i;
|
|
|
|
Datum *values;
|
|
|
|
char *nulls;
|
|
|
|
bool isnull;
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Get the cursor variable and if it has an assigned name, check
|
|
|
|
* that it's not in use currently.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
|
|
|
|
if (!curvar->isnull)
|
|
|
|
{
|
|
|
|
curname = DatumGetCString(DirectFunctionCall1(textout, curvar->value));
|
|
|
|
if (SPI_cursor_find(curname) != NULL)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DUPLICATE_CURSOR),
|
|
|
|
errmsg("cursor \"%s\" already in use", curname)));
|
2001-05-21 16:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Process the OPEN according to it's type.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
if (stmt->query != NULL)
|
|
|
|
{
|
|
|
|
/* ----------
|
|
|
|
* This is an OPEN refcursor FOR SELECT ...
|
|
|
|
*
|
|
|
|
* We just make sure the query is planned. The real work is
|
|
|
|
* done downstairs.
|
|
|
|
* ----------
|
2000-08-31 15:26:16 +02:00
|
|
|
*/
|
2001-10-25 07:50:21 +02:00
|
|
|
query = stmt->query;
|
2001-05-21 16:22:19 +02:00
|
|
|
if (query->plan == NULL)
|
|
|
|
exec_prepare_plan(estate, query);
|
|
|
|
}
|
|
|
|
else if (stmt->dynquery != NULL)
|
|
|
|
{
|
|
|
|
/* ----------
|
|
|
|
* This is an OPEN refcursor FOR EXECUTE ...
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
Datum queryD;
|
|
|
|
Oid restype;
|
|
|
|
char *querystr;
|
2001-11-16 00:31:09 +01:00
|
|
|
void *curplan;
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* We evaluate the string expression after the
|
|
|
|
* EXECUTE keyword. It's result is the querystring we have
|
|
|
|
* to execute.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
queryD = exec_eval_expr(estate, stmt->dynquery, &isnull, &restype);
|
|
|
|
if (isnull)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
|
|
errmsg("cannot EXECUTE a null querystring")));
|
2000-08-31 15:26:16 +02:00
|
|
|
|
2003-10-01 23:47:42 +02:00
|
|
|
/* Get the C-String representation */
|
|
|
|
querystr = convert_value_to_string(queryD, restype);
|
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_eval_cleanup(estate);
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Now we prepare a query plan for it and open a cursor
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
curplan = SPI_prepare(querystr, 0, NULL);
|
2002-10-20 00:10:58 +02:00
|
|
|
if (curplan == NULL)
|
2004-07-31 22:55:45 +02:00
|
|
|
elog(ERROR, "SPI_prepare failed for \"%s\": %s",
|
|
|
|
querystr, SPI_result_code_string(SPI_result));
|
2004-09-13 22:10:13 +02:00
|
|
|
portal = SPI_cursor_open(curname, curplan, NULL, NULL,
|
|
|
|
estate->readonly_func);
|
2001-05-21 16:22:19 +02:00
|
|
|
if (portal == NULL)
|
2004-07-31 22:55:45 +02:00
|
|
|
elog(ERROR, "could not open cursor for query \"%s\": %s",
|
|
|
|
querystr, SPI_result_code_string(SPI_result));
|
2001-05-21 16:22:19 +02:00
|
|
|
pfree(querystr);
|
2002-10-20 00:10:58 +02:00
|
|
|
SPI_freeplan(curplan);
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Store the eventually assigned cursor name in the cursor variable
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
if (curvar->freeval)
|
2001-10-25 07:50:21 +02:00
|
|
|
pfree((void *) (curvar->value));
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
|
|
|
|
curvar->isnull = false;
|
|
|
|
curvar->freeval = true;
|
|
|
|
|
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* ----------
|
|
|
|
* This is an OPEN cursor
|
2001-11-16 00:31:09 +01:00
|
|
|
*
|
|
|
|
* Note: parser should already have checked that statement supplies
|
|
|
|
* args iff cursor needs them, but we check again to be safe.
|
2001-05-21 16:22:19 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
if (stmt->argquery != NULL)
|
2000-08-31 15:26:16 +02:00
|
|
|
{
|
2001-05-21 16:22:19 +02:00
|
|
|
/* ----------
|
|
|
|
* Er - OPEN CURSOR (args). We fake a SELECT ... INTO ...
|
|
|
|
* statement to evaluate the args and put 'em into the
|
|
|
|
* internal row.
|
|
|
|
* ----------
|
|
|
|
*/
|
2001-10-25 07:50:21 +02:00
|
|
|
PLpgSQL_stmt_select set_args;
|
2001-05-21 16:22:19 +02:00
|
|
|
|
2001-11-16 00:31:09 +01:00
|
|
|
if (curvar->cursor_explicit_argrow < 0)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
2003-08-04 02:43:34 +02:00
|
|
|
errmsg("arguments given for cursor without arguments")));
|
2001-11-16 00:31:09 +01:00
|
|
|
|
2001-05-21 16:22:19 +02:00
|
|
|
memset(&set_args, 0, sizeof(set_args));
|
2001-10-25 07:50:21 +02:00
|
|
|
set_args.cmd_type = PLPGSQL_STMT_SELECT;
|
|
|
|
set_args.lineno = stmt->lineno;
|
|
|
|
set_args.row = (PLpgSQL_row *)
|
|
|
|
(estate->datums[curvar->cursor_explicit_argrow]);
|
|
|
|
set_args.query = stmt->argquery;
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
if (exec_stmt_select(estate, &set_args) != PLPGSQL_RC_OK)
|
|
|
|
elog(ERROR, "open cursor failed during argument processing");
|
|
|
|
}
|
2001-11-16 00:31:09 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
if (curvar->cursor_explicit_argrow >= 0)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
|
|
errmsg("arguments required for cursor")));
|
2001-11-16 00:31:09 +01:00
|
|
|
}
|
2001-05-21 16:22:19 +02:00
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
query = curvar->cursor_explicit_expr;
|
2001-05-21 16:22:19 +02:00
|
|
|
if (query->plan == NULL)
|
|
|
|
exec_prepare_plan(estate, query);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Here we go if we have a saved plan where we have to put
|
|
|
|
* values into, either from an explicit cursor or from a
|
|
|
|
* refcursor opened with OPEN ... FOR SELECT ...;
|
|
|
|
* ----------
|
|
|
|
*/
|
2004-06-05 21:48:09 +02:00
|
|
|
values = (Datum *) palloc(query->nparams * sizeof(Datum));
|
|
|
|
nulls = (char *) palloc(query->nparams * sizeof(char));
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
for (i = 0; i < query->nparams; i++)
|
|
|
|
{
|
2003-03-25 01:34:24 +01:00
|
|
|
PLpgSQL_datum *datum = estate->datums[query->params[i]];
|
|
|
|
Oid paramtypeid;
|
|
|
|
bool paramisnull;
|
|
|
|
|
|
|
|
exec_eval_datum(estate, datum, query->plan_argtypes[i],
|
|
|
|
¶mtypeid, &values[i], ¶misnull);
|
|
|
|
if (paramisnull)
|
|
|
|
nulls[i] = 'n';
|
|
|
|
else
|
|
|
|
nulls[i] = ' ';
|
2000-08-31 15:26:16 +02:00
|
|
|
}
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Open the cursor
|
|
|
|
* ----------
|
|
|
|
*/
|
2004-09-13 22:10:13 +02:00
|
|
|
portal = SPI_cursor_open(curname, query->plan, values, nulls,
|
|
|
|
estate->readonly_func);
|
2001-05-21 16:22:19 +02:00
|
|
|
if (portal == NULL)
|
2004-07-31 22:55:45 +02:00
|
|
|
elog(ERROR, "could not open cursor: %s",
|
|
|
|
SPI_result_code_string(SPI_result));
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
pfree(values);
|
|
|
|
pfree(nulls);
|
2001-08-02 23:31:23 +02:00
|
|
|
if (curname)
|
|
|
|
pfree(curname);
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Store the eventually assigned portal name in the cursor variable
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
if (curvar->freeval)
|
2001-10-25 07:50:21 +02:00
|
|
|
pfree((void *) (curvar->value));
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
|
|
|
|
curvar->isnull = false;
|
|
|
|
curvar->freeval = true;
|
|
|
|
|
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_fetch Fetch from a cursor into a target
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
|
2001-05-21 16:22:19 +02:00
|
|
|
{
|
|
|
|
PLpgSQL_var *curvar = NULL;
|
|
|
|
PLpgSQL_rec *rec = NULL;
|
|
|
|
PLpgSQL_row *row = NULL;
|
2001-08-02 23:31:23 +02:00
|
|
|
SPITupleTable *tuptab;
|
2001-05-21 16:22:19 +02:00
|
|
|
Portal portal;
|
2001-10-25 07:50:21 +02:00
|
|
|
char *curname;
|
2001-05-21 16:22:19 +02:00
|
|
|
int n;
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Get the portal of the cursor by name
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
|
|
|
|
if (curvar->isnull)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
2003-08-04 02:43:34 +02:00
|
|
|
errmsg("cursor variable \"%s\" is NULL", curvar->refname)));
|
2001-05-21 16:22:19 +02:00
|
|
|
curname = DatumGetCString(DirectFunctionCall1(textout, curvar->value));
|
|
|
|
|
|
|
|
portal = SPI_cursor_find(curname);
|
|
|
|
if (portal == NULL)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_CURSOR),
|
|
|
|
errmsg("cursor \"%s\" does not exist", curname)));
|
2001-05-21 16:22:19 +02:00
|
|
|
pfree(curname);
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Determine if we fetch into a record or a row
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
if (stmt->rec != NULL)
|
|
|
|
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
2003-03-02 21:45:47 +01:00
|
|
|
else if (stmt->row != NULL)
|
|
|
|
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
2001-05-21 16:22:19 +02:00
|
|
|
else
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unsupported target");
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Fetch 1 tuple from the cursor
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
SPI_cursor_fetch(portal, true, 1);
|
2001-08-02 23:31:23 +02:00
|
|
|
tuptab = SPI_tuptable;
|
2003-01-21 23:06:12 +01:00
|
|
|
n = SPI_processed;
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
/* ----------
|
2003-03-02 21:45:47 +01:00
|
|
|
* Set the target and the global FOUND variable appropriately.
|
2001-05-21 16:22:19 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
if (n == 0)
|
|
|
|
{
|
2003-01-21 23:06:12 +01:00
|
|
|
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
|
2003-03-02 21:45:47 +01:00
|
|
|
exec_set_found(estate, false);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
|
|
|
|
exec_set_found(estate, true);
|
2001-05-21 16:22:19 +02:00
|
|
|
}
|
|
|
|
|
2001-08-02 23:31:23 +02:00
|
|
|
SPI_freetuptable(tuptab);
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_stmt_close Close a cursor
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt)
|
2001-05-21 16:22:19 +02:00
|
|
|
{
|
|
|
|
PLpgSQL_var *curvar = NULL;
|
|
|
|
Portal portal;
|
2001-10-25 07:50:21 +02:00
|
|
|
char *curname;
|
2001-05-21 16:22:19 +02:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Get the portal of the cursor by name
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
|
|
|
|
if (curvar->isnull)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
2003-08-04 02:43:34 +02:00
|
|
|
errmsg("cursor variable \"%s\" is NULL", curvar->refname)));
|
2001-05-21 16:22:19 +02:00
|
|
|
curname = DatumGetCString(DirectFunctionCall1(textout, curvar->value));
|
|
|
|
|
|
|
|
portal = SPI_cursor_find(curname);
|
|
|
|
if (portal == NULL)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_CURSOR),
|
|
|
|
errmsg("cursor \"%s\" does not exist", curname)));
|
2001-05-21 16:22:19 +02:00
|
|
|
pfree(curname);
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* And close it.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
SPI_cursor_close(portal);
|
2000-08-31 15:26:16 +02:00
|
|
|
|
|
|
|
return PLPGSQL_RC_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
1998-08-22 14:38:39 +02:00
|
|
|
/* ----------
|
2002-08-20 07:28:24 +02:00
|
|
|
* exec_assign_expr Put an expression's result into
|
1998-08-22 14:38:39 +02:00
|
|
|
* a variable.
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static void
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
|
|
|
|
PLpgSQL_expr *expr)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
Datum value;
|
|
|
|
Oid valtype;
|
|
|
|
bool isnull = false;
|
|
|
|
|
|
|
|
value = exec_eval_expr(estate, expr, &isnull, &valtype);
|
2001-05-28 21:33:24 +02:00
|
|
|
exec_assign_value(estate, target, value, valtype, &isnull);
|
2001-08-02 23:31:23 +02:00
|
|
|
exec_eval_cleanup(estate);
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_assign_value Put a value into a target field
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static void
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_assign_value(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_datum *target,
|
1998-09-01 06:40:42 +02:00
|
|
|
Datum value, Oid valtype, bool *isNull)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
switch (target->dtype)
|
|
|
|
{
|
|
|
|
case PLPGSQL_DTYPE_VAR:
|
2004-08-29 07:07:03 +02:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Target is a variable
|
|
|
|
*/
|
|
|
|
PLpgSQL_var *var = (PLpgSQL_var *) target;
|
|
|
|
Datum newvalue;
|
2001-05-21 16:22:19 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
newvalue = exec_cast_value(value, valtype, var->datatype->typoid,
|
|
|
|
&(var->datatype->typinput),
|
|
|
|
var->datatype->typioparam,
|
|
|
|
var->datatype->atttypmod,
|
|
|
|
isNull);
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
if (*isNull && var->notnull)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
|
|
errmsg("NULL cannot be assigned to variable \"%s\" declared NOT NULL",
|
|
|
|
var->refname)));
|
2001-08-02 23:31:23 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
if (var->freeval)
|
|
|
|
{
|
|
|
|
pfree(DatumGetPointer(var->value));
|
|
|
|
var->freeval = false;
|
|
|
|
}
|
2004-06-04 04:37:06 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* If type is by-reference, make sure we have a freshly
|
|
|
|
* palloc'd copy; the originally passed value may not live
|
|
|
|
* as long as the variable! But we don't need to re-copy
|
|
|
|
* if exec_cast_value performed a conversion; its output
|
|
|
|
* must already be palloc'd.
|
|
|
|
*/
|
|
|
|
if (!var->datatype->typbyval && !*isNull)
|
|
|
|
{
|
|
|
|
if (newvalue == value)
|
|
|
|
var->value = datumCopy(newvalue,
|
|
|
|
false,
|
|
|
|
var->datatype->typlen);
|
|
|
|
else
|
|
|
|
var->value = newvalue;
|
|
|
|
var->freeval = true;
|
|
|
|
}
|
2001-05-21 16:22:19 +02:00
|
|
|
else
|
2001-08-02 23:31:23 +02:00
|
|
|
var->value = newvalue;
|
2004-08-29 07:07:03 +02:00
|
|
|
var->isnull = *isNull;
|
|
|
|
break;
|
2001-05-21 16:22:19 +02:00
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2004-06-04 04:37:06 +02:00
|
|
|
case PLPGSQL_DTYPE_ROW:
|
|
|
|
{
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Target is a row variable
|
|
|
|
*/
|
|
|
|
PLpgSQL_row *row = (PLpgSQL_row *) target;
|
|
|
|
|
|
|
|
/* Source must be of RECORD or composite type */
|
|
|
|
if (!(valtype == RECORDOID ||
|
|
|
|
get_typtype(valtype) == 'c'))
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
|
|
errmsg("cannot assign non-composite value to a row variable")));
|
|
|
|
if (*isNull)
|
|
|
|
{
|
|
|
|
/* If source is null, just assign nulls to the row */
|
|
|
|
exec_move_row(estate, NULL, row, NULL, NULL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
HeapTupleHeader td;
|
|
|
|
Oid tupType;
|
|
|
|
int32 tupTypmod;
|
|
|
|
TupleDesc tupdesc;
|
|
|
|
HeapTupleData tmptup;
|
|
|
|
|
|
|
|
/* Else source is a tuple Datum, safe to do this: */
|
|
|
|
td = DatumGetHeapTupleHeader(value);
|
|
|
|
/* Extract rowtype info and find a tupdesc */
|
|
|
|
tupType = HeapTupleHeaderGetTypeId(td);
|
|
|
|
tupTypmod = HeapTupleHeaderGetTypMod(td);
|
|
|
|
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
|
|
|
|
/* Build a temporary HeapTuple control structure */
|
|
|
|
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
|
|
|
|
ItemPointerSetInvalid(&(tmptup.t_self));
|
|
|
|
tmptup.t_tableOid = InvalidOid;
|
|
|
|
tmptup.t_data = td;
|
|
|
|
exec_move_row(estate, NULL, row, &tmptup, tupdesc);
|
|
|
|
}
|
|
|
|
break;
|
2004-06-04 04:37:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_REC:
|
|
|
|
{
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Target is a record variable
|
|
|
|
*/
|
|
|
|
PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
|
|
|
|
|
|
|
|
/* Source must be of RECORD or composite type */
|
|
|
|
if (!(valtype == RECORDOID ||
|
|
|
|
get_typtype(valtype) == 'c'))
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
|
|
errmsg("cannot assign non-composite value to a record variable")));
|
|
|
|
if (*isNull)
|
|
|
|
{
|
|
|
|
/* If source is null, just assign nulls to the record */
|
|
|
|
exec_move_row(estate, rec, NULL, NULL, NULL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
HeapTupleHeader td;
|
|
|
|
Oid tupType;
|
|
|
|
int32 tupTypmod;
|
|
|
|
TupleDesc tupdesc;
|
|
|
|
HeapTupleData tmptup;
|
|
|
|
|
|
|
|
/* Else source is a tuple Datum, safe to do this: */
|
|
|
|
td = DatumGetHeapTupleHeader(value);
|
|
|
|
/* Extract rowtype info and find a tupdesc */
|
|
|
|
tupType = HeapTupleHeaderGetTypeId(td);
|
|
|
|
tupTypmod = HeapTupleHeaderGetTypMod(td);
|
|
|
|
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
|
|
|
|
/* Build a temporary HeapTuple control structure */
|
|
|
|
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
|
|
|
|
ItemPointerSetInvalid(&(tmptup.t_self));
|
|
|
|
tmptup.t_tableOid = InvalidOid;
|
|
|
|
tmptup.t_data = td;
|
|
|
|
exec_move_row(estate, rec, NULL, &tmptup, tupdesc);
|
|
|
|
}
|
|
|
|
break;
|
2004-06-04 04:37:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_RECFIELD:
|
2004-08-29 07:07:03 +02:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Target is a field of a record
|
|
|
|
*/
|
|
|
|
PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
|
|
|
|
PLpgSQL_rec *rec;
|
|
|
|
int fno;
|
|
|
|
HeapTuple newtup;
|
|
|
|
int natts;
|
|
|
|
int i;
|
|
|
|
Datum *values;
|
|
|
|
char *nulls;
|
|
|
|
void *mustfree;
|
|
|
|
bool attisnull;
|
|
|
|
Oid atttype;
|
|
|
|
int32 atttypmod;
|
|
|
|
|
|
|
|
rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Check that there is already a tuple in the record. We
|
|
|
|
* need that because records don't have any predefined
|
|
|
|
* field structure.
|
|
|
|
*/
|
|
|
|
if (!HeapTupleIsValid(rec->tup))
|
|
|
|
ereport(ERROR,
|
2003-08-04 02:43:34 +02:00
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
errmsg("record \"%s\" is not assigned yet",
|
|
|
|
rec->refname),
|
|
|
|
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Get the number of the records field to change and the
|
|
|
|
* number of attributes in the tuple.
|
|
|
|
*/
|
|
|
|
fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
|
|
|
|
if (fno == SPI_ERROR_NOATTRIBUTE)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
|
|
|
errmsg("record \"%s\" has no field \"%s\"",
|
|
|
|
rec->refname, recfield->fieldname)));
|
|
|
|
fno--;
|
|
|
|
natts = rec->tupdesc->natts;
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Set up values/datums arrays for heap_formtuple. For
|
|
|
|
* all the attributes except the one we want to replace,
|
|
|
|
* use the value that's in the old tuple.
|
|
|
|
*/
|
|
|
|
values = palloc(sizeof(Datum) * natts);
|
|
|
|
nulls = palloc(natts);
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
for (i = 0; i < natts; i++)
|
|
|
|
{
|
|
|
|
if (i == fno)
|
|
|
|
continue;
|
|
|
|
values[i] = SPI_getbinval(rec->tup, rec->tupdesc,
|
|
|
|
i + 1, &attisnull);
|
|
|
|
if (attisnull)
|
|
|
|
nulls[i] = 'n';
|
|
|
|
else
|
|
|
|
nulls[i] = ' ';
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now insert the new value, being careful to cast it to
|
|
|
|
* the right type.
|
|
|
|
*/
|
|
|
|
atttype = SPI_gettypeid(rec->tupdesc, fno + 1);
|
|
|
|
atttypmod = rec->tupdesc->attrs[fno]->atttypmod;
|
|
|
|
attisnull = *isNull;
|
|
|
|
values[fno] = exec_simple_cast_value(value,
|
|
|
|
valtype,
|
|
|
|
atttype,
|
|
|
|
atttypmod,
|
|
|
|
&attisnull);
|
1998-09-01 06:40:42 +02:00
|
|
|
if (attisnull)
|
2004-08-29 07:07:03 +02:00
|
|
|
nulls[fno] = 'n';
|
1998-09-01 06:40:42 +02:00
|
|
|
else
|
2004-08-29 07:07:03 +02:00
|
|
|
nulls[fno] = ' ';
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Avoid leaking the result of exec_simple_cast_value, if
|
|
|
|
* it performed a conversion to a pass-by-ref type.
|
|
|
|
*/
|
|
|
|
if (!attisnull && values[fno] != value && !get_typbyval(atttype))
|
|
|
|
mustfree = DatumGetPointer(values[fno]);
|
|
|
|
else
|
|
|
|
mustfree = NULL;
|
2001-08-02 23:31:23 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Now call heap_formtuple() to create a new tuple that
|
|
|
|
* replaces the old one in the record.
|
|
|
|
*/
|
|
|
|
newtup = heap_formtuple(rec->tupdesc, values, nulls);
|
2001-05-21 16:22:19 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
if (rec->freetup)
|
|
|
|
heap_freetuple(rec->tup);
|
2001-05-21 16:22:19 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
rec->tup = newtup;
|
|
|
|
rec->freetup = true;
|
2001-05-21 16:22:19 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
pfree(values);
|
|
|
|
pfree(nulls);
|
|
|
|
if (mustfree)
|
|
|
|
pfree(mustfree);
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
break;
|
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2003-03-25 04:16:41 +01:00
|
|
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
2003-08-04 02:43:34 +02:00
|
|
|
{
|
2004-08-29 07:07:03 +02:00
|
|
|
int nsubscripts;
|
|
|
|
int i;
|
|
|
|
PLpgSQL_expr *subscripts[MAXDIM];
|
|
|
|
int subscriptvals[MAXDIM];
|
|
|
|
bool havenullsubscript,
|
|
|
|
oldarrayisnull;
|
|
|
|
Oid arraytypeid,
|
|
|
|
arrayelemtypeid;
|
|
|
|
int16 elemtyplen;
|
|
|
|
bool elemtypbyval;
|
|
|
|
char elemtypalign;
|
|
|
|
Datum oldarrayval,
|
|
|
|
coerced_value;
|
|
|
|
ArrayType *newarrayval;
|
2003-03-25 04:16:41 +01:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Target is an element of an array
|
|
|
|
*
|
|
|
|
* To handle constructs like x[1][2] := something, we have to
|
|
|
|
* be prepared to deal with a chain of arrayelem datums.
|
|
|
|
* Chase back to find the base array datum, and save the
|
|
|
|
* subscript expressions as we go. (We are scanning right
|
|
|
|
* to left here, but want to evaluate the subscripts
|
|
|
|
* left-to-right to minimize surprises.)
|
|
|
|
*/
|
|
|
|
nsubscripts = 0;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
PLpgSQL_arrayelem *arrayelem = (PLpgSQL_arrayelem *) target;
|
|
|
|
|
|
|
|
if (nsubscripts >= MAXDIM)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
|
|
errmsg("number of array dimensions exceeds the maximum allowed, %d",
|
|
|
|
MAXDIM)));
|
|
|
|
subscripts[nsubscripts++] = arrayelem->subscript;
|
|
|
|
target = estate->datums[arrayelem->arrayparentno];
|
|
|
|
} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
|
|
|
|
|
|
|
|
/* Fetch current value of array datum */
|
|
|
|
exec_eval_datum(estate, target, InvalidOid,
|
2003-03-25 04:16:41 +01:00
|
|
|
&arraytypeid, &oldarrayval, &oldarrayisnull);
|
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
arrayelemtypeid = get_element_type(arraytypeid);
|
|
|
|
if (!OidIsValid(arrayelemtypeid))
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
|
|
errmsg("subscripted object is not an array")));
|
2003-03-25 04:16:41 +01:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Evaluate the subscripts, switch into left-to-right
|
|
|
|
* order
|
|
|
|
*/
|
|
|
|
havenullsubscript = false;
|
|
|
|
for (i = 0; i < nsubscripts; i++)
|
|
|
|
{
|
|
|
|
bool subisnull;
|
2003-03-25 04:16:41 +01:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
subscriptvals[i] =
|
|
|
|
exec_eval_integer(estate,
|
|
|
|
subscripts[nsubscripts - 1 - i],
|
|
|
|
&subisnull);
|
|
|
|
havenullsubscript |= subisnull;
|
|
|
|
}
|
2003-03-25 04:16:41 +01:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Skip the assignment if we have any nulls, either in the
|
|
|
|
* original array value, the subscripts, or the righthand
|
|
|
|
* side. This is pretty bogus but it corresponds to the
|
|
|
|
* current behavior of ExecEvalArrayRef().
|
|
|
|
*/
|
|
|
|
if (oldarrayisnull || havenullsubscript || *isNull)
|
|
|
|
return;
|
2003-03-25 04:16:41 +01:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/* Coerce source value to match array element type. */
|
|
|
|
coerced_value = exec_simple_cast_value(value,
|
|
|
|
valtype,
|
|
|
|
arrayelemtypeid,
|
|
|
|
-1,
|
|
|
|
isNull);
|
2003-03-25 04:16:41 +01:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Build the modified array value.
|
|
|
|
*/
|
|
|
|
get_typlenbyvalalign(arrayelemtypeid,
|
|
|
|
&elemtyplen,
|
|
|
|
&elemtypbyval,
|
|
|
|
&elemtypalign);
|
|
|
|
|
|
|
|
newarrayval = array_set((ArrayType *) DatumGetPointer(oldarrayval),
|
|
|
|
nsubscripts,
|
|
|
|
subscriptvals,
|
|
|
|
coerced_value,
|
|
|
|
get_typlen(arraytypeid),
|
|
|
|
elemtyplen,
|
|
|
|
elemtypbyval,
|
|
|
|
elemtypalign,
|
|
|
|
isNull);
|
2003-03-25 04:16:41 +01:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Assign it to the base variable.
|
|
|
|
*/
|
|
|
|
exec_assign_value(estate, target,
|
|
|
|
PointerGetDatum(newarrayval),
|
|
|
|
arraytypeid, isNull);
|
2003-03-25 04:16:41 +01:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Avoid leaking the result of exec_simple_cast_value, if
|
|
|
|
* it performed a conversion to a pass-by-ref type.
|
|
|
|
*/
|
|
|
|
if (!*isNull && coerced_value != value && !elemtypbyval)
|
|
|
|
pfree(DatumGetPointer(coerced_value));
|
2003-03-25 04:16:41 +01:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
/*
|
|
|
|
* Avoid leaking the modified array value, too.
|
|
|
|
*/
|
|
|
|
pfree(newarrayval);
|
|
|
|
break;
|
|
|
|
}
|
2003-03-25 04:16:41 +01:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
default:
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized dtype: %d", target->dtype);
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2003-03-25 01:34:24 +01:00
|
|
|
/*
|
|
|
|
* exec_eval_datum Get current value of a PLpgSQL_datum
|
|
|
|
*
|
|
|
|
* The type oid, value in Datum format, and null flag are returned.
|
|
|
|
*
|
|
|
|
* If expectedtypeid isn't InvalidOid, it is checked against the actual type.
|
|
|
|
*
|
|
|
|
* This obviously only handles scalar datums (not whole records or rows);
|
|
|
|
* at present it doesn't need to handle PLpgSQL_expr datums, either.
|
|
|
|
*
|
|
|
|
* NOTE: caller must not modify the returned value, since it points right
|
|
|
|
* at the stored value in the case of pass-by-reference datatypes.
|
|
|
|
*/
|
|
|
|
static void
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_eval_datum(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_datum *datum,
|
2003-03-25 01:34:24 +01:00
|
|
|
Oid expectedtypeid,
|
|
|
|
Oid *typeid,
|
|
|
|
Datum *value,
|
|
|
|
bool *isnull)
|
|
|
|
{
|
|
|
|
switch (datum->dtype)
|
|
|
|
{
|
|
|
|
case PLPGSQL_DTYPE_VAR:
|
2004-08-29 07:07:03 +02:00
|
|
|
{
|
|
|
|
PLpgSQL_var *var = (PLpgSQL_var *) datum;
|
2004-06-04 02:07:52 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
*typeid = var->datatype->typoid;
|
|
|
|
*value = var->value;
|
|
|
|
*isnull = var->isnull;
|
|
|
|
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
|
|
errmsg("type of \"%s\" does not match that when preparing the plan",
|
|
|
|
var->refname)));
|
|
|
|
break;
|
|
|
|
}
|
2004-06-04 02:07:52 +02:00
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_ROW:
|
2004-08-29 07:07:03 +02:00
|
|
|
{
|
|
|
|
PLpgSQL_row *row = (PLpgSQL_row *) datum;
|
|
|
|
HeapTuple tup;
|
|
|
|
|
|
|
|
if (!row->rowtupdesc) /* should not happen */
|
|
|
|
elog(ERROR, "row variable has no tupdesc");
|
|
|
|
/* Make sure we have a valid type/typmod setting */
|
|
|
|
BlessTupleDesc(row->rowtupdesc);
|
|
|
|
tup = make_tuple_from_row(estate, row, row->rowtupdesc);
|
|
|
|
if (tup == NULL) /* should not happen */
|
|
|
|
elog(ERROR, "row not compatible with its own tupdesc");
|
|
|
|
*typeid = row->rowtupdesc->tdtypeid;
|
|
|
|
*value = HeapTupleGetDatum(tup);
|
|
|
|
*isnull = false;
|
|
|
|
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
|
|
errmsg("type of \"%s\" does not match that when preparing the plan",
|
|
|
|
row->refname)));
|
|
|
|
break;
|
|
|
|
}
|
2004-06-04 02:07:52 +02:00
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_REC:
|
2004-08-29 07:07:03 +02:00
|
|
|
{
|
|
|
|
PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
|
|
|
|
HeapTupleData worktup;
|
2004-06-04 02:07:52 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
if (!HeapTupleIsValid(rec->tup))
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
errmsg("record \"%s\" is not assigned yet",
|
|
|
|
rec->refname),
|
|
|
|
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
|
|
|
|
Assert(rec->tupdesc != NULL);
|
|
|
|
/* Make sure we have a valid type/typmod setting */
|
|
|
|
BlessTupleDesc(rec->tupdesc);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In a trigger, the NEW and OLD parameters are likely to
|
|
|
|
* be on-disk tuples that don't have the desired Datum
|
|
|
|
* fields. Copy the tuple body and insert the right
|
|
|
|
* values.
|
|
|
|
*/
|
|
|
|
heap_copytuple_with_tuple(rec->tup, &worktup);
|
|
|
|
HeapTupleHeaderSetDatumLength(worktup.t_data, worktup.t_len);
|
|
|
|
HeapTupleHeaderSetTypeId(worktup.t_data, rec->tupdesc->tdtypeid);
|
|
|
|
HeapTupleHeaderSetTypMod(worktup.t_data, rec->tupdesc->tdtypmod);
|
|
|
|
*typeid = rec->tupdesc->tdtypeid;
|
|
|
|
*value = HeapTupleGetDatum(&worktup);
|
|
|
|
*isnull = false;
|
|
|
|
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
|
|
errmsg("type of \"%s\" does not match that when preparing the plan",
|
|
|
|
rec->refname)));
|
|
|
|
break;
|
|
|
|
}
|
2003-03-25 01:34:24 +01:00
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_RECFIELD:
|
2004-08-29 07:07:03 +02:00
|
|
|
{
|
|
|
|
PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
|
|
|
|
PLpgSQL_rec *rec;
|
|
|
|
int fno;
|
2004-06-04 02:07:52 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
|
|
|
|
if (!HeapTupleIsValid(rec->tup))
|
|
|
|
ereport(ERROR,
|
2003-08-04 02:43:34 +02:00
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
errmsg("record \"%s\" is not assigned yet",
|
|
|
|
rec->refname),
|
|
|
|
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
|
2004-08-29 07:07:03 +02:00
|
|
|
fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
|
|
|
|
if (fno == SPI_ERROR_NOATTRIBUTE)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
|
|
|
errmsg("record \"%s\" has no field \"%s\"",
|
|
|
|
rec->refname, recfield->fieldname)));
|
|
|
|
*typeid = SPI_gettypeid(rec->tupdesc, fno);
|
|
|
|
*value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
|
|
|
|
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
|
|
errmsg("type of \"%s.%s\" does not match that when preparing the plan",
|
|
|
|
rec->refname, recfield->fieldname)));
|
|
|
|
break;
|
|
|
|
}
|
2003-03-25 01:34:24 +01:00
|
|
|
|
|
|
|
case PLPGSQL_DTYPE_TRIGARG:
|
|
|
|
{
|
2004-08-29 07:07:03 +02:00
|
|
|
PLpgSQL_trigarg *trigarg = (PLpgSQL_trigarg *) datum;
|
|
|
|
int tgargno;
|
|
|
|
|
|
|
|
*typeid = TEXTOID;
|
|
|
|
tgargno = exec_eval_integer(estate, trigarg->argnum, isnull);
|
|
|
|
if (*isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
|
|
|
|
{
|
|
|
|
*value = (Datum) 0;
|
|
|
|
*isnull = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*value = estate->trig_argv[tgargno];
|
|
|
|
*isnull = false;
|
|
|
|
}
|
|
|
|
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
|
|
errmsg("type of tgargv[%d] does not match that when preparing the plan",
|
|
|
|
tgargno)));
|
|
|
|
break;
|
2003-03-25 01:34:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
|
2003-03-25 01:34:24 +01:00
|
|
|
}
|
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2003-03-25 04:16:41 +01:00
|
|
|
/* ----------
|
2003-09-29 01:37:45 +02:00
|
|
|
* exec_eval_integer Evaluate an expression, coerce result to int4
|
2003-03-25 04:16:41 +01:00
|
|
|
*
|
2003-09-29 01:37:45 +02:00
|
|
|
* Note we do not do exec_eval_cleanup here; the caller must do it at
|
|
|
|
* some later point. (We do this because the caller may be holding the
|
|
|
|
* results of other, pass-by-reference, expression evaluations, such as
|
|
|
|
* an array value to be subscripted. Also see notes in exec_eval_simple_expr
|
|
|
|
* about allocation of the parameter array.)
|
2003-03-25 04:16:41 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_eval_integer(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_expr *expr,
|
2003-09-29 01:37:45 +02:00
|
|
|
bool *isNull)
|
2003-03-25 04:16:41 +01:00
|
|
|
{
|
2003-09-29 01:37:45 +02:00
|
|
|
Datum exprdatum;
|
|
|
|
Oid exprtypeid;
|
2003-03-25 04:16:41 +01:00
|
|
|
|
2003-09-29 01:37:45 +02:00
|
|
|
exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid);
|
|
|
|
exprdatum = exec_simple_cast_value(exprdatum, exprtypeid,
|
|
|
|
INT4OID, -1,
|
|
|
|
isNull);
|
|
|
|
return DatumGetInt32(exprdatum);
|
2003-03-25 04:16:41 +01:00
|
|
|
}
|
|
|
|
|
2003-10-01 23:47:42 +02:00
|
|
|
/* ----------
|
|
|
|
* exec_eval_boolean Evaluate an expression, coerce result to bool
|
|
|
|
*
|
|
|
|
* Note we do not do exec_eval_cleanup here; the caller must do it at
|
|
|
|
* some later point.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static bool
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_eval_boolean(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_expr *expr,
|
2003-10-01 23:47:42 +02:00
|
|
|
bool *isNull)
|
|
|
|
{
|
|
|
|
Datum exprdatum;
|
|
|
|
Oid exprtypeid;
|
|
|
|
|
|
|
|
exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid);
|
|
|
|
exprdatum = exec_simple_cast_value(exprdatum, exprtypeid,
|
|
|
|
BOOLOID, -1,
|
|
|
|
isNull);
|
|
|
|
return DatumGetBool(exprdatum);
|
|
|
|
}
|
|
|
|
|
1998-08-22 14:38:39 +02:00
|
|
|
/* ----------
|
|
|
|
* exec_eval_expr Evaluate an expression and return
|
|
|
|
* the result Datum.
|
2001-08-02 23:31:23 +02:00
|
|
|
*
|
|
|
|
* NOTE: caller must do exec_eval_cleanup when done with the Datum.
|
1998-08-22 14:38:39 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static Datum
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_eval_expr(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_expr *expr,
|
1998-09-01 06:40:42 +02:00
|
|
|
bool *isNull,
|
|
|
|
Oid *rettype)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
int rc;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-01-27 17:15:22 +01:00
|
|
|
* If not already done create a plan for this expression
|
|
|
|
*/
|
|
|
|
if (expr->plan == NULL)
|
|
|
|
exec_prepare_plan(estate, expr);
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* If this is a simple expression, bypass SPI and use the executor
|
|
|
|
* directly
|
1999-01-27 17:15:22 +01:00
|
|
|
*/
|
2003-09-29 01:37:45 +02:00
|
|
|
if (expr->expr_simple_expr != NULL)
|
1999-01-27 17:15:22 +01:00
|
|
|
return exec_eval_simple_expr(estate, expr, isNull, rettype);
|
|
|
|
|
2001-05-21 16:22:19 +02:00
|
|
|
rc = exec_run_select(estate, expr, 2, NULL);
|
1998-09-01 06:40:42 +02:00
|
|
|
if (rc != SPI_OK_SELECT)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
2003-08-04 02:43:34 +02:00
|
|
|
errmsg("query \"%s\" did not return data", expr->query)));
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* If there are no rows selected, the result is NULL.
|
|
|
|
*/
|
2001-08-02 23:31:23 +02:00
|
|
|
if (estate->eval_processed == 0)
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
|
|
|
*isNull = true;
|
|
|
|
return (Datum) 0;
|
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* Check that the expression returned one single Datum
|
|
|
|
*/
|
2001-08-02 23:31:23 +02:00
|
|
|
if (estate->eval_processed > 1)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_CARDINALITY_VIOLATION),
|
|
|
|
errmsg("query \"%s\" returned more than one row",
|
|
|
|
expr->query)));
|
2001-08-02 23:31:23 +02:00
|
|
|
if (estate->eval_tuptable->tupdesc->natts != 1)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
|
|
errmsg("query \"%s\" returned %d columns", expr->query,
|
|
|
|
estate->eval_tuptable->tupdesc->natts)));
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-01-05 19:23:54 +01:00
|
|
|
* Return the result and its type
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2001-08-02 23:31:23 +02:00
|
|
|
*rettype = SPI_gettypeid(estate->eval_tuptable->tupdesc, 1);
|
|
|
|
return SPI_getbinval(estate->eval_tuptable->vals[0],
|
|
|
|
estate->eval_tuptable->tupdesc, 1, isNull);
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_run_select Execute a select query
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static int
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_run_select(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_expr *expr, int maxtuples, Portal *portalP)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
int i;
|
|
|
|
Datum *values;
|
|
|
|
char *nulls;
|
|
|
|
int rc;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1998-09-01 06:40:42 +02:00
|
|
|
* On the first call for this expression generate the plan
|
1998-08-22 14:38:39 +02:00
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
if (expr->plan == NULL)
|
1999-01-27 17:15:22 +01:00
|
|
|
exec_prepare_plan(estate, expr);
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2004-09-13 22:10:13 +02:00
|
|
|
* Now build up the values and nulls arguments for SPI_execute_plan()
|
1998-08-22 14:38:39 +02:00
|
|
|
*/
|
2004-06-05 21:48:09 +02:00
|
|
|
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
|
|
|
|
nulls = (char *) palloc(expr->nparams * sizeof(char));
|
1998-09-01 06:40:42 +02:00
|
|
|
|
|
|
|
for (i = 0; i < expr->nparams; i++)
|
|
|
|
{
|
2003-03-25 01:34:24 +01:00
|
|
|
PLpgSQL_datum *datum = estate->datums[expr->params[i]];
|
|
|
|
Oid paramtypeid;
|
|
|
|
bool paramisnull;
|
|
|
|
|
|
|
|
exec_eval_datum(estate, datum, expr->plan_argtypes[i],
|
|
|
|
¶mtypeid, &values[i], ¶misnull);
|
|
|
|
if (paramisnull)
|
|
|
|
nulls[i] = 'n';
|
|
|
|
else
|
|
|
|
nulls[i] = ' ';
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2001-08-02 23:31:23 +02:00
|
|
|
* If a portal was requested, put the query into the portal
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2001-05-21 16:22:19 +02:00
|
|
|
if (portalP != NULL)
|
|
|
|
{
|
2004-09-13 22:10:13 +02:00
|
|
|
*portalP = SPI_cursor_open(NULL, expr->plan, values, nulls,
|
|
|
|
estate->readonly_func);
|
2001-05-21 16:22:19 +02:00
|
|
|
if (*portalP == NULL)
|
2004-07-31 22:55:45 +02:00
|
|
|
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
|
|
|
|
expr->query, SPI_result_code_string(SPI_result));
|
2001-05-21 16:22:19 +02:00
|
|
|
pfree(values);
|
|
|
|
pfree(nulls);
|
|
|
|
return SPI_OK_CURSOR;
|
|
|
|
}
|
2001-08-02 23:31:23 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Execute the query
|
|
|
|
*/
|
2004-09-13 22:10:13 +02:00
|
|
|
rc = SPI_execute_plan(expr->plan, values, nulls,
|
|
|
|
estate->readonly_func, maxtuples);
|
1998-09-01 06:40:42 +02:00
|
|
|
if (rc != SPI_OK_SELECT)
|
2003-07-26 01:37:31 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
|
|
errmsg("query \"%s\" is not a SELECT", expr->query)));
|
2001-08-02 23:31:23 +02:00
|
|
|
|
|
|
|
/* Save query results for eventual cleanup */
|
|
|
|
Assert(estate->eval_tuptable == NULL);
|
|
|
|
estate->eval_tuptable = SPI_tuptable;
|
|
|
|
estate->eval_processed = SPI_processed;
|
|
|
|
estate->eval_lastoid = SPI_lastoid;
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
pfree(values);
|
|
|
|
pfree(nulls);
|
|
|
|
|
|
|
|
return rc;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
1999-01-27 17:15:22 +01:00
|
|
|
/* ----------
|
|
|
|
* exec_eval_simple_expr - Evaluate a simple expression returning
|
|
|
|
* a Datum by directly calling ExecEvalExpr().
|
2003-09-29 01:37:45 +02:00
|
|
|
*
|
|
|
|
* Note: if pass-by-reference, the result is in the eval_econtext's
|
|
|
|
* temporary memory context. It will be freed when exec_eval_cleanup
|
|
|
|
* is done.
|
1999-01-27 17:15:22 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static Datum
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_eval_simple_expr(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_expr *expr,
|
1999-05-25 18:15:34 +02:00
|
|
|
bool *isNull,
|
|
|
|
Oid *rettype)
|
1999-01-27 17:15:22 +01:00
|
|
|
{
|
|
|
|
Datum retval;
|
1999-05-25 18:15:34 +02:00
|
|
|
ExprContext *econtext;
|
1999-01-27 17:15:22 +01:00
|
|
|
ParamListInfo paramLI;
|
2003-09-29 01:37:45 +02:00
|
|
|
int i;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Pass back previously-determined result type.
|
|
|
|
*/
|
|
|
|
*rettype = expr->expr_simple_type;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create an EState for evaluation of simple expressions, if there's
|
2004-08-29 07:07:03 +02:00
|
|
|
* not one already in the current transaction. The EState is made a
|
2003-09-29 01:37:45 +02:00
|
|
|
* child of TopTransactionContext so it will have the right lifespan.
|
|
|
|
*/
|
|
|
|
if (simple_eval_estate == NULL)
|
|
|
|
{
|
|
|
|
MemoryContext oldcontext;
|
|
|
|
|
|
|
|
oldcontext = MemoryContextSwitchTo(TopTransactionContext);
|
|
|
|
simple_eval_estate = CreateExecutorState();
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Prepare the expression for execution, if it's not been done already
|
|
|
|
* in the current transaction.
|
|
|
|
*/
|
|
|
|
if (expr->expr_simple_state == NULL)
|
|
|
|
{
|
|
|
|
expr->expr_simple_state = ExecPrepareExpr(expr->expr_simple_expr,
|
|
|
|
simple_eval_estate);
|
|
|
|
/* Add it to list for cleanup */
|
|
|
|
expr->expr_simple_next = active_simple_exprs;
|
|
|
|
active_simple_exprs = expr;
|
|
|
|
}
|
1999-01-27 17:15:22 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2004-08-29 07:07:03 +02:00
|
|
|
* Create an expression context for simple expressions, if there's not
|
|
|
|
* one already in the current function call. This must be a child of
|
|
|
|
* simple_eval_estate.
|
2001-08-02 23:31:23 +02:00
|
|
|
*/
|
2003-09-29 01:37:45 +02:00
|
|
|
econtext = estate->eval_econtext;
|
|
|
|
if (econtext == NULL)
|
|
|
|
{
|
|
|
|
econtext = CreateExprContext(simple_eval_estate);
|
|
|
|
estate->eval_econtext = econtext;
|
|
|
|
}
|
2001-08-02 23:31:23 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Param list can live in econtext's temporary memory context.
|
2003-09-29 01:37:45 +02:00
|
|
|
*
|
2004-08-29 07:07:03 +02:00
|
|
|
* XXX think about avoiding repeated palloc's for param lists? Beware
|
|
|
|
* however that this routine is re-entrant: exec_eval_datum() can call
|
|
|
|
* it back for subscript evaluation, and so there can be a need to
|
|
|
|
* have more than one active param list.
|
2001-08-02 23:31:23 +02:00
|
|
|
*/
|
2001-10-25 07:50:21 +02:00
|
|
|
paramLI = (ParamListInfo)
|
2004-08-02 03:30:51 +02:00
|
|
|
MemoryContextAllocZero(econtext->ecxt_per_tuple_memory,
|
2001-10-25 07:50:21 +02:00
|
|
|
(expr->nparams + 1) * sizeof(ParamListInfoData));
|
1999-01-27 17:15:22 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-09-29 01:37:45 +02:00
|
|
|
* Put the parameter values into the parameter list entries.
|
1999-01-27 17:15:22 +01:00
|
|
|
*/
|
2003-09-29 01:37:45 +02:00
|
|
|
for (i = 0; i < expr->nparams; i++)
|
1999-01-27 17:15:22 +01:00
|
|
|
{
|
2003-03-25 01:34:24 +01:00
|
|
|
PLpgSQL_datum *datum = estate->datums[expr->params[i]];
|
|
|
|
|
2003-09-29 01:37:45 +02:00
|
|
|
paramLI[i].kind = PARAM_NUM;
|
|
|
|
paramLI[i].id = i + 1;
|
2003-03-25 01:34:24 +01:00
|
|
|
exec_eval_datum(estate, datum, expr->plan_argtypes[i],
|
2004-08-02 03:30:51 +02:00
|
|
|
¶mLI[i].ptype,
|
2003-09-29 01:37:45 +02:00
|
|
|
¶mLI[i].value, ¶mLI[i].isnull);
|
1999-01-27 17:15:22 +01:00
|
|
|
}
|
2003-09-29 01:37:45 +02:00
|
|
|
paramLI[i].kind = PARAM_INVALID;
|
1999-01-27 17:15:22 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-09-29 01:37:45 +02:00
|
|
|
* Now we can safely make the econtext point to the param list.
|
1999-01-27 17:15:22 +01:00
|
|
|
*/
|
2003-09-29 01:37:45 +02:00
|
|
|
econtext->ecxt_param_list_info = paramLI;
|
1999-01-27 17:15:22 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-01-27 17:15:22 +01:00
|
|
|
* Now call the executor to evaluate the expression
|
|
|
|
*/
|
|
|
|
SPI_push();
|
2003-09-29 01:37:45 +02:00
|
|
|
retval = ExecEvalExprSwitchContext(expr->expr_simple_state,
|
2000-07-12 04:37:39 +02:00
|
|
|
econtext,
|
|
|
|
isNull,
|
2000-08-24 05:29:15 +02:00
|
|
|
NULL);
|
1999-01-27 17:15:22 +01:00
|
|
|
SPI_pop();
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-01-27 17:15:22 +01:00
|
|
|
* That's it.
|
|
|
|
*/
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
1998-08-22 14:38:39 +02:00
|
|
|
/* ----------
|
2001-04-30 22:05:40 +02:00
|
|
|
* exec_move_row Move one tuple's values into a record or row
|
1998-08-22 14:38:39 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static void
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_move_row(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_rec *rec,
|
|
|
|
PLpgSQL_row *row,
|
1998-09-01 06:40:42 +02:00
|
|
|
HeapTuple tup, TupleDesc tupdesc)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-01-21 23:06:12 +01:00
|
|
|
* Record is simple - just copy the tuple and its descriptor into the
|
|
|
|
* record variable
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
|
|
|
if (rec != NULL)
|
|
|
|
{
|
2001-05-21 16:22:19 +02:00
|
|
|
if (rec->freetup)
|
|
|
|
{
|
|
|
|
heap_freetuple(rec->tup);
|
|
|
|
rec->freetup = false;
|
|
|
|
}
|
|
|
|
if (rec->freetupdesc)
|
|
|
|
{
|
|
|
|
FreeTupleDesc(rec->tupdesc);
|
|
|
|
rec->freetupdesc = false;
|
|
|
|
}
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
if (HeapTupleIsValid(tup))
|
|
|
|
{
|
2001-05-21 16:22:19 +02:00
|
|
|
rec->tup = heap_copytuple(tup);
|
|
|
|
rec->freetup = true;
|
2003-01-21 23:06:12 +01:00
|
|
|
}
|
|
|
|
else if (tupdesc)
|
|
|
|
{
|
|
|
|
/* If we have a tupdesc but no data, form an all-nulls tuple */
|
2003-08-04 02:43:34 +02:00
|
|
|
char *nulls;
|
2003-01-21 23:06:12 +01:00
|
|
|
|
2004-06-05 21:48:09 +02:00
|
|
|
nulls = (char *) palloc(tupdesc->natts * sizeof(char));
|
2003-01-21 23:06:12 +01:00
|
|
|
memset(nulls, 'n', tupdesc->natts * sizeof(char));
|
|
|
|
|
|
|
|
rec->tup = heap_formtuple(tupdesc, NULL, nulls);
|
|
|
|
rec->freetup = true;
|
|
|
|
|
|
|
|
pfree(nulls);
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
rec->tup = NULL;
|
2003-01-21 23:06:12 +01:00
|
|
|
|
|
|
|
if (tupdesc)
|
|
|
|
{
|
|
|
|
rec->tupdesc = CreateTupleDescCopy(tupdesc);
|
|
|
|
rec->freetupdesc = true;
|
|
|
|
}
|
|
|
|
else
|
1998-09-01 06:40:42 +02:00
|
|
|
rec->tupdesc = NULL;
|
|
|
|
|
|
|
|
return;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2001-04-30 22:05:40 +02:00
|
|
|
* Row is a bit more complicated in that we assign the individual
|
|
|
|
* attributes of the tuple to the variables the row points to.
|
|
|
|
*
|
|
|
|
* NOTE: this code used to demand row->nfields == tup->t_data->t_natts,
|
2001-10-25 07:50:21 +02:00
|
|
|
* but that's wrong. The tuple might have more fields than we
|
|
|
|
* expected if it's from an inheritance-child table of the current
|
|
|
|
* table, or it might have fewer if the table has had columns added by
|
|
|
|
* ALTER TABLE. Ignore extra columns and assume NULL for missing
|
2004-08-29 07:07:03 +02:00
|
|
|
* columns, the same as heap_getattr would do. We also have to skip
|
2003-09-26 01:02:12 +02:00
|
|
|
* over dropped columns in either the source or destination.
|
2003-01-21 23:06:12 +01:00
|
|
|
*
|
2003-08-04 02:43:34 +02:00
|
|
|
* If we have no tuple data at all, we'll assign NULL to all columns of
|
|
|
|
* the row variable.
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
|
|
|
if (row != NULL)
|
|
|
|
{
|
2001-04-30 22:05:40 +02:00
|
|
|
int t_natts;
|
2003-09-26 01:02:12 +02:00
|
|
|
int fnum;
|
|
|
|
int anum;
|
2001-04-30 22:05:40 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
if (HeapTupleIsValid(tup))
|
2001-04-30 22:05:40 +02:00
|
|
|
t_natts = tup->t_data->t_natts;
|
|
|
|
else
|
|
|
|
t_natts = 0;
|
|
|
|
|
2003-09-26 01:02:12 +02:00
|
|
|
anum = 0;
|
|
|
|
for (fnum = 0; fnum < row->nfields; fnum++)
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
2001-04-30 22:05:40 +02:00
|
|
|
PLpgSQL_var *var;
|
|
|
|
Datum value;
|
|
|
|
bool isnull;
|
|
|
|
Oid valtype;
|
1998-09-01 06:40:42 +02:00
|
|
|
|
2003-09-26 01:02:12 +02:00
|
|
|
if (row->varnos[fnum] < 0)
|
|
|
|
continue; /* skip dropped column in row struct */
|
|
|
|
|
|
|
|
var = (PLpgSQL_var *) (estate->datums[row->varnos[fnum]]);
|
|
|
|
|
|
|
|
while (anum < t_natts && tupdesc->attrs[anum]->attisdropped)
|
|
|
|
anum++; /* skip dropped column in tuple */
|
|
|
|
|
|
|
|
if (anum < t_natts)
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
2003-09-26 01:02:12 +02:00
|
|
|
value = SPI_getbinval(tup, tupdesc, anum + 1, &isnull);
|
|
|
|
valtype = SPI_gettypeid(tupdesc, anum + 1);
|
|
|
|
anum++;
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
2001-04-30 22:05:40 +02:00
|
|
|
else
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
2001-04-30 22:05:40 +02:00
|
|
|
value = (Datum) 0;
|
|
|
|
isnull = true;
|
2001-05-08 03:00:53 +02:00
|
|
|
valtype = InvalidOid;
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
2001-04-30 22:05:40 +02:00
|
|
|
|
2003-09-26 01:02:12 +02:00
|
|
|
exec_assign_value(estate, (PLpgSQL_datum *) var,
|
2001-04-30 22:05:40 +02:00
|
|
|
value, valtype, &isnull);
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2003-07-26 01:37:31 +02:00
|
|
|
elog(ERROR, "unsupported target");
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2003-09-26 01:02:12 +02:00
|
|
|
/* ----------
|
|
|
|
* make_tuple_from_row Make a tuple from the values of a row object
|
|
|
|
*
|
|
|
|
* A NULL return indicates rowtype mismatch; caller must raise suitable error
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static HeapTuple
|
2004-08-30 04:54:42 +02:00
|
|
|
make_tuple_from_row(PLpgSQL_execstate *estate,
|
|
|
|
PLpgSQL_row *row,
|
2003-09-26 01:02:12 +02:00
|
|
|
TupleDesc tupdesc)
|
|
|
|
{
|
|
|
|
int natts = tupdesc->natts;
|
|
|
|
HeapTuple tuple;
|
|
|
|
Datum *dvalues;
|
|
|
|
char *nulls;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (natts != row->nfields)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
dvalues = (Datum *) palloc0(natts * sizeof(Datum));
|
|
|
|
nulls = (char *) palloc(natts * sizeof(char));
|
|
|
|
MemSet(nulls, 'n', natts);
|
|
|
|
|
|
|
|
for (i = 0; i < natts; i++)
|
|
|
|
{
|
|
|
|
PLpgSQL_var *var;
|
|
|
|
|
|
|
|
if (tupdesc->attrs[i]->attisdropped)
|
2004-08-29 07:07:03 +02:00
|
|
|
continue; /* leave the column as null */
|
2003-09-26 01:02:12 +02:00
|
|
|
if (row->varnos[i] < 0) /* should not happen */
|
|
|
|
elog(ERROR, "dropped rowtype entry for non-dropped column");
|
|
|
|
|
|
|
|
var = (PLpgSQL_var *) (estate->datums[row->varnos[i]]);
|
|
|
|
if (var->datatype->typoid != tupdesc->attrs[i]->atttypid)
|
|
|
|
return NULL;
|
|
|
|
dvalues[i] = var->value;
|
|
|
|
if (!var->isnull)
|
|
|
|
nulls[i] = ' ';
|
|
|
|
}
|
|
|
|
|
|
|
|
tuple = heap_formtuple(tupdesc, dvalues, nulls);
|
|
|
|
|
|
|
|
pfree(dvalues);
|
|
|
|
pfree(nulls);
|
|
|
|
|
|
|
|
return tuple;
|
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
|
2003-10-01 23:47:42 +02:00
|
|
|
/* ----------
|
|
|
|
* convert_value_to_string Convert a non-null Datum to C string
|
|
|
|
*
|
|
|
|
* Note: callers generally assume that the result is a palloc'd string and
|
|
|
|
* should be pfree'd. This is not all that safe an assumption ...
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static char *
|
|
|
|
convert_value_to_string(Datum value, Oid valtype)
|
|
|
|
{
|
2004-06-06 02:41:28 +02:00
|
|
|
Oid typoutput;
|
|
|
|
Oid typioparam;
|
2003-10-01 23:47:42 +02:00
|
|
|
bool typIsVarlena;
|
|
|
|
|
2004-06-06 02:41:28 +02:00
|
|
|
getTypeOutputInfo(valtype, &typoutput, &typioparam, &typIsVarlena);
|
2003-10-01 23:47:42 +02:00
|
|
|
|
2004-06-06 02:41:28 +02:00
|
|
|
return DatumGetCString(OidFunctionCall3(typoutput,
|
|
|
|
value,
|
|
|
|
ObjectIdGetDatum(typioparam),
|
|
|
|
Int32GetDatum(-1)));
|
2003-10-01 23:47:42 +02:00
|
|
|
}
|
|
|
|
|
1998-08-22 14:38:39 +02:00
|
|
|
/* ----------
|
|
|
|
* exec_cast_value Cast a value if required
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static Datum
|
|
|
|
exec_cast_value(Datum value, Oid valtype,
|
|
|
|
Oid reqtype,
|
1998-08-22 14:38:39 +02:00
|
|
|
FmgrInfo *reqinput,
|
2004-06-06 02:41:28 +02:00
|
|
|
Oid reqtypioparam,
|
2000-01-15 23:43:25 +01:00
|
|
|
int32 reqtypmod,
|
1998-08-22 14:38:39 +02:00
|
|
|
bool *isnull)
|
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
if (!*isnull)
|
|
|
|
{
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* If the type of the queries return value isn't that of the
|
|
|
|
* variable, convert it.
|
1998-09-01 06:40:42 +02:00
|
|
|
*/
|
2000-01-15 23:43:25 +01:00
|
|
|
if (valtype != reqtype || reqtypmod != -1)
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
|
|
|
char *extval;
|
|
|
|
|
2003-10-01 23:47:42 +02:00
|
|
|
extval = convert_value_to_string(value, valtype);
|
2000-05-30 06:25:00 +02:00
|
|
|
value = FunctionCall3(reqinput,
|
|
|
|
CStringGetDatum(extval),
|
2004-06-06 02:41:28 +02:00
|
|
|
ObjectIdGetDatum(reqtypioparam),
|
2000-05-30 06:25:00 +02:00
|
|
|
Int32GetDatum(reqtypmod));
|
2000-01-15 23:43:25 +01:00
|
|
|
pfree(extval);
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
return value;
|
1998-08-22 14:38:39 +02:00
|
|
|
}
|
|
|
|
|
2003-03-25 04:16:41 +01:00
|
|
|
/* ----------
|
|
|
|
* exec_simple_cast_value Cast a value if required
|
|
|
|
*
|
|
|
|
* As above, but need not supply details about target type. Note that this
|
|
|
|
* is slower than exec_cast_value with cached type info, and so should be
|
|
|
|
* avoided in heavily used code paths.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static Datum
|
|
|
|
exec_simple_cast_value(Datum value, Oid valtype,
|
|
|
|
Oid reqtype, int32 reqtypmod,
|
|
|
|
bool *isnull)
|
|
|
|
{
|
|
|
|
if (!*isnull)
|
|
|
|
{
|
|
|
|
if (valtype != reqtype || reqtypmod != -1)
|
|
|
|
{
|
2004-06-06 02:41:28 +02:00
|
|
|
Oid typinput;
|
|
|
|
Oid typioparam;
|
2003-03-25 04:16:41 +01:00
|
|
|
FmgrInfo finfo_input;
|
|
|
|
|
2004-06-06 02:41:28 +02:00
|
|
|
getTypeInputInfo(reqtype, &typinput, &typioparam);
|
2003-10-01 23:47:42 +02:00
|
|
|
|
2004-06-06 02:41:28 +02:00
|
|
|
fmgr_info(typinput, &finfo_input);
|
2003-03-25 04:16:41 +01:00
|
|
|
|
|
|
|
value = exec_cast_value(value,
|
|
|
|
valtype,
|
|
|
|
reqtype,
|
|
|
|
&finfo_input,
|
2004-06-06 02:41:28 +02:00
|
|
|
typioparam,
|
2003-03-25 04:16:41 +01:00
|
|
|
reqtypmod,
|
|
|
|
isnull);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1999-01-27 17:15:22 +01:00
|
|
|
/* ----------
|
|
|
|
* exec_simple_check_node - Recursively check if an expression
|
|
|
|
* is made only of simple things we can
|
|
|
|
* hand out directly to ExecEvalExpr()
|
|
|
|
* instead of calling SPI.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static bool
|
1999-05-25 18:15:34 +02:00
|
|
|
exec_simple_check_node(Node *node)
|
1999-01-27 17:15:22 +01:00
|
|
|
{
|
2002-12-12 16:49:42 +01:00
|
|
|
if (node == NULL)
|
|
|
|
return TRUE;
|
|
|
|
|
1999-01-27 17:15:22 +01:00
|
|
|
switch (nodeTag(node))
|
|
|
|
{
|
2002-12-12 16:49:42 +01:00
|
|
|
case T_Const:
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case T_Param:
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case T_ArrayRef:
|
1999-05-25 18:15:34 +02:00
|
|
|
{
|
2002-12-12 16:49:42 +01:00
|
|
|
ArrayRef *expr = (ArrayRef *) node;
|
1999-05-25 18:15:34 +02:00
|
|
|
|
2002-12-12 16:49:42 +01:00
|
|
|
if (!exec_simple_check_node((Node *) expr->refupperindexpr))
|
|
|
|
return FALSE;
|
|
|
|
if (!exec_simple_check_node((Node *) expr->reflowerindexpr))
|
|
|
|
return FALSE;
|
|
|
|
if (!exec_simple_check_node((Node *) expr->refexpr))
|
|
|
|
return FALSE;
|
|
|
|
if (!exec_simple_check_node((Node *) expr->refassgnexpr))
|
|
|
|
return FALSE;
|
1999-05-25 18:15:34 +02:00
|
|
|
|
2002-12-12 16:49:42 +01:00
|
|
|
return TRUE;
|
|
|
|
}
|
1999-01-27 17:15:22 +01:00
|
|
|
|
2002-12-12 16:49:42 +01:00
|
|
|
case T_FuncExpr:
|
|
|
|
{
|
|
|
|
FuncExpr *expr = (FuncExpr *) node;
|
|
|
|
|
|
|
|
if (expr->funcretset)
|
|
|
|
return FALSE;
|
|
|
|
if (!exec_simple_check_node((Node *) expr->args))
|
|
|
|
return FALSE;
|
1999-01-27 17:15:22 +01:00
|
|
|
|
1999-05-25 18:15:34 +02:00
|
|
|
return TRUE;
|
|
|
|
}
|
1999-01-27 17:15:22 +01:00
|
|
|
|
2002-12-12 16:49:42 +01:00
|
|
|
case T_OpExpr:
|
|
|
|
{
|
|
|
|
OpExpr *expr = (OpExpr *) node;
|
1999-05-25 18:15:34 +02:00
|
|
|
|
2002-12-12 16:49:42 +01:00
|
|
|
if (expr->opretset)
|
|
|
|
return FALSE;
|
|
|
|
if (!exec_simple_check_node((Node *) expr->args))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
case T_DistinctExpr:
|
|
|
|
{
|
|
|
|
DistinctExpr *expr = (DistinctExpr *) node;
|
|
|
|
|
|
|
|
if (expr->opretset)
|
|
|
|
return FALSE;
|
|
|
|
if (!exec_simple_check_node((Node *) expr->args))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2003-06-29 02:33:44 +02:00
|
|
|
case T_ScalarArrayOpExpr:
|
|
|
|
{
|
|
|
|
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
|
|
|
|
|
|
|
|
if (!exec_simple_check_node((Node *) expr->args))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2002-12-12 16:49:42 +01:00
|
|
|
case T_BoolExpr:
|
|
|
|
{
|
|
|
|
BoolExpr *expr = (BoolExpr *) node;
|
|
|
|
|
|
|
|
if (!exec_simple_check_node((Node *) expr->args))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
case T_FieldSelect:
|
|
|
|
return exec_simple_check_node((Node *) ((FieldSelect *) node)->arg);
|
1999-05-25 18:15:34 +02:00
|
|
|
|
2004-06-09 21:08:20 +02:00
|
|
|
case T_FieldStore:
|
|
|
|
{
|
2004-08-29 07:07:03 +02:00
|
|
|
FieldStore *expr = (FieldStore *) node;
|
2004-06-09 21:08:20 +02:00
|
|
|
|
|
|
|
if (!exec_simple_check_node((Node *) expr->arg))
|
|
|
|
return FALSE;
|
|
|
|
if (!exec_simple_check_node((Node *) expr->newvals))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2000-08-13 04:50:35 +02:00
|
|
|
case T_RelabelType:
|
2002-12-12 16:49:42 +01:00
|
|
|
return exec_simple_check_node((Node *) ((RelabelType *) node)->arg);
|
|
|
|
|
|
|
|
case T_CaseExpr:
|
|
|
|
{
|
|
|
|
CaseExpr *expr = (CaseExpr *) node;
|
|
|
|
|
|
|
|
if (!exec_simple_check_node((Node *) expr->arg))
|
|
|
|
return FALSE;
|
|
|
|
if (!exec_simple_check_node((Node *) expr->args))
|
|
|
|
return FALSE;
|
|
|
|
if (!exec_simple_check_node((Node *) expr->defresult))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
case T_CaseWhen:
|
|
|
|
{
|
|
|
|
CaseWhen *when = (CaseWhen *) node;
|
|
|
|
|
|
|
|
if (!exec_simple_check_node((Node *) when->expr))
|
|
|
|
return FALSE;
|
|
|
|
if (!exec_simple_check_node((Node *) when->result))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2004-03-17 21:48:43 +01:00
|
|
|
case T_CaseTestExpr:
|
|
|
|
return TRUE;
|
|
|
|
|
2003-04-09 01:20:04 +02:00
|
|
|
case T_ArrayExpr:
|
|
|
|
{
|
2003-08-04 02:43:34 +02:00
|
|
|
ArrayExpr *expr = (ArrayExpr *) node;
|
2003-04-09 01:20:04 +02:00
|
|
|
|
|
|
|
if (!exec_simple_check_node((Node *) expr->elements))
|
|
|
|
return FALSE;
|
2004-05-11 00:44:49 +02:00
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
case T_RowExpr:
|
|
|
|
{
|
2004-08-29 07:07:03 +02:00
|
|
|
RowExpr *expr = (RowExpr *) node;
|
2004-05-11 00:44:49 +02:00
|
|
|
|
|
|
|
if (!exec_simple_check_node((Node *) expr->args))
|
|
|
|
return FALSE;
|
2003-04-09 01:20:04 +02:00
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2003-02-16 03:30:39 +01:00
|
|
|
case T_CoalesceExpr:
|
|
|
|
{
|
2003-08-04 02:43:34 +02:00
|
|
|
CoalesceExpr *expr = (CoalesceExpr *) node;
|
2003-02-16 03:30:39 +01:00
|
|
|
|
|
|
|
if (!exec_simple_check_node((Node *) expr->args))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
case T_NullIfExpr:
|
|
|
|
{
|
|
|
|
NullIfExpr *expr = (NullIfExpr *) node;
|
|
|
|
|
|
|
|
if (expr->opretset)
|
|
|
|
return FALSE;
|
|
|
|
if (!exec_simple_check_node((Node *) expr->args))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2002-12-12 16:49:42 +01:00
|
|
|
case T_NullTest:
|
|
|
|
return exec_simple_check_node((Node *) ((NullTest *) node)->arg);
|
|
|
|
|
|
|
|
case T_BooleanTest:
|
|
|
|
return exec_simple_check_node((Node *) ((BooleanTest *) node)->arg);
|
|
|
|
|
2003-02-03 22:15:45 +01:00
|
|
|
case T_CoerceToDomain:
|
|
|
|
return exec_simple_check_node((Node *) ((CoerceToDomain *) node)->arg);
|
|
|
|
|
2004-03-17 21:48:43 +01:00
|
|
|
case T_CoerceToDomainValue:
|
|
|
|
return TRUE;
|
|
|
|
|
2002-12-12 16:49:42 +01:00
|
|
|
case T_List:
|
|
|
|
{
|
|
|
|
List *expr = (List *) node;
|
2004-05-26 06:41:50 +02:00
|
|
|
ListCell *l;
|
2002-12-12 16:49:42 +01:00
|
|
|
|
|
|
|
foreach(l, expr)
|
|
|
|
{
|
|
|
|
if (!exec_simple_check_node(lfirst(l)))
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
2000-08-13 04:50:35 +02:00
|
|
|
|
1999-05-25 18:15:34 +02:00
|
|
|
default:
|
|
|
|
return FALSE;
|
1999-01-27 17:15:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* exec_simple_check_plan - Check if a plan is simple enough to
|
|
|
|
* be evaluated by ExecEvalExpr() instead
|
|
|
|
* of SPI.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static void
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_simple_check_plan(PLpgSQL_expr *expr)
|
1999-01-27 17:15:22 +01:00
|
|
|
{
|
1999-05-25 18:15:34 +02:00
|
|
|
_SPI_plan *spi_plan = (_SPI_plan *) expr->plan;
|
|
|
|
Plan *plan;
|
|
|
|
TargetEntry *tle;
|
1999-01-27 17:15:22 +01:00
|
|
|
|
2003-09-29 01:37:45 +02:00
|
|
|
expr->expr_simple_expr = NULL;
|
1999-01-27 17:15:22 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-01-27 17:15:22 +01:00
|
|
|
* 1. We can only evaluate queries that resulted in one single
|
2001-03-22 07:16:21 +01:00
|
|
|
* execution plan
|
1999-01-27 17:15:22 +01:00
|
|
|
*/
|
2004-05-31 01:40:41 +02:00
|
|
|
if (list_length(spi_plan->ptlist) != 1)
|
1999-01-27 17:15:22 +01:00
|
|
|
return;
|
|
|
|
|
2004-05-26 06:41:50 +02:00
|
|
|
plan = (Plan *) linitial(spi_plan->ptlist);
|
1999-01-27 17:15:22 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-01-27 17:15:22 +01:00
|
|
|
* 2. It must be a RESULT plan --> no scan's required
|
|
|
|
*/
|
1999-07-04 03:03:01 +02:00
|
|
|
if (plan == NULL) /* utility statement produces this */
|
|
|
|
return;
|
|
|
|
|
2000-04-12 19:17:23 +02:00
|
|
|
if (!IsA(plan, Result))
|
1999-01-27 17:15:22 +01:00
|
|
|
return;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-03-11 07:19:00 +01:00
|
|
|
* 3. Can't have any subplan or qual clause, either
|
1999-01-27 17:15:22 +01:00
|
|
|
*/
|
2000-03-11 07:19:00 +01:00
|
|
|
if (plan->lefttree != NULL ||
|
|
|
|
plan->righttree != NULL ||
|
|
|
|
plan->initPlan != NULL ||
|
|
|
|
plan->qual != NULL ||
|
|
|
|
((Result *) plan)->resconstantqual != NULL)
|
1999-01-27 17:15:22 +01:00
|
|
|
return;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-03-11 07:19:00 +01:00
|
|
|
* 4. The plan must have a single attribute as result
|
1999-01-27 17:15:22 +01:00
|
|
|
*/
|
2004-05-31 01:40:41 +02:00
|
|
|
if (list_length(plan->targetlist) != 1)
|
1999-01-27 17:15:22 +01:00
|
|
|
return;
|
|
|
|
|
2004-05-26 06:41:50 +02:00
|
|
|
tle = (TargetEntry *) linitial(plan->targetlist);
|
2000-03-11 07:19:00 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2002-12-12 16:49:42 +01:00
|
|
|
* 5. Check that all the nodes in the expression are non-scary.
|
1999-01-27 17:15:22 +01:00
|
|
|
*/
|
2002-12-12 16:49:42 +01:00
|
|
|
if (!exec_simple_check_node((Node *) tle->expr))
|
1999-01-27 17:15:22 +01:00
|
|
|
return;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2003-09-29 01:37:45 +02:00
|
|
|
* Yes - this is a simple expression. Mark it as such, and initialize
|
|
|
|
* state to "not executing".
|
1999-01-27 17:15:22 +01:00
|
|
|
*/
|
2003-09-29 01:37:45 +02:00
|
|
|
expr->expr_simple_expr = tle->expr;
|
|
|
|
expr->expr_simple_state = NULL;
|
|
|
|
expr->expr_simple_next = NULL;
|
2002-12-15 17:17:59 +01:00
|
|
|
/* Also stash away the expression result type */
|
2003-09-29 01:37:45 +02:00
|
|
|
expr->expr_simple_type = exprType((Node *) tle->expr);
|
1999-01-27 17:15:22 +01:00
|
|
|
}
|
|
|
|
|
2002-08-31 01:59:46 +02:00
|
|
|
/*
|
|
|
|
* Check two tupledescs have matching number and types of attributes
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
compatible_tupdesc(TupleDesc td1, TupleDesc td2)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (td1->natts != td2->natts)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (i = 0; i < td1->natts; i++)
|
|
|
|
{
|
|
|
|
if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
1998-08-22 14:38:39 +02:00
|
|
|
/* ----------
|
|
|
|
* exec_set_found Set the global found variable
|
|
|
|
* to true/false
|
|
|
|
* ----------
|
|
|
|
*/
|
1998-09-01 06:40:42 +02:00
|
|
|
static void
|
2004-08-30 04:54:42 +02:00
|
|
|
exec_set_found(PLpgSQL_execstate *estate, bool state)
|
1998-08-22 14:38:39 +02:00
|
|
|
{
|
1998-09-01 06:40:42 +02:00
|
|
|
PLpgSQL_var *var;
|
1998-08-22 14:38:39 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
var = (PLpgSQL_var *) (estate->datums[estate->found_varno]);
|
|
|
|
var->value = (Datum) state;
|
|
|
|
var->isnull = false;
|
|
|
|
}
|
2003-09-29 01:37:45 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* plpgsql_eoxact --- post-transaction-commit-or-abort cleanup
|
|
|
|
*
|
|
|
|
* If a simple_eval_estate was created in the current transaction,
|
|
|
|
* it has to be cleaned up, and we have to mark all active PLpgSQL_expr
|
|
|
|
* structs that are using it as no longer active.
|
|
|
|
*/
|
|
|
|
void
|
2004-08-01 19:32:22 +02:00
|
|
|
plpgsql_xact_cb(XactEvent event, TransactionId parentXid, void *arg)
|
2003-09-29 01:37:45 +02:00
|
|
|
{
|
|
|
|
PLpgSQL_expr *expr;
|
|
|
|
PLpgSQL_expr *enext;
|
|
|
|
|
2004-08-01 19:32:22 +02:00
|
|
|
switch (event)
|
2003-09-29 01:37:45 +02:00
|
|
|
{
|
2004-08-01 19:32:22 +02:00
|
|
|
/*
|
|
|
|
* Nothing to do at subtransaction events
|
|
|
|
*
|
2004-08-29 07:07:03 +02:00
|
|
|
* XXX really? Maybe subtransactions need to have their own
|
|
|
|
* simple_eval_estate? It would get a lot messier, so for now
|
2004-08-01 19:32:22 +02:00
|
|
|
* let's assume we don't need that.
|
|
|
|
*/
|
|
|
|
case XACT_EVENT_START_SUB:
|
|
|
|
case XACT_EVENT_ABORT_SUB:
|
|
|
|
case XACT_EVENT_COMMIT_SUB:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XACT_EVENT_ABORT:
|
|
|
|
case XACT_EVENT_COMMIT:
|
|
|
|
/* Mark all active exprs as inactive */
|
|
|
|
for (expr = active_simple_exprs; expr; expr = enext)
|
|
|
|
{
|
|
|
|
enext = expr->expr_simple_next;
|
|
|
|
expr->expr_simple_state = NULL;
|
|
|
|
expr->expr_simple_next = NULL;
|
|
|
|
}
|
|
|
|
active_simple_exprs = NULL;
|
2004-08-29 07:07:03 +02:00
|
|
|
|
2004-08-01 19:32:22 +02:00
|
|
|
/*
|
2004-08-29 07:07:03 +02:00
|
|
|
* If we are doing a clean transaction shutdown, free the
|
|
|
|
* EState (so that any remaining resources will be released
|
|
|
|
* correctly). In an abort, we expect the regular abort
|
|
|
|
* recovery procedures to release everything of interest.
|
2004-08-01 19:32:22 +02:00
|
|
|
*/
|
|
|
|
if (event == XACT_EVENT_COMMIT && simple_eval_estate)
|
|
|
|
FreeExecutorState(simple_eval_estate);
|
|
|
|
simple_eval_estate = NULL;
|
|
|
|
break;
|
2003-09-29 01:37:45 +02:00
|
|
|
}
|
|
|
|
}
|