prep_stmt.patch

text/plain

Filename: prep_stmt.patch
Type: text/plain
Part: 0
Message: prepareable statements

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: context
File+
src/backend/commands/Makefile 2 2
src/backend/commands/prepare.c 387 0
src/backend/parser/analyze.c 129 1
src/backend/parser/gram.y 89 4
src/backend/parser/keywords.c 2 0
src/backend/parser/parse_type.c 13 0
src/backend/tcop/postgres.c 12 0
src/backend/tcop/utility.c 19 0
src/include/commands/prepare.h 26 0
src/include/nodes/nodes.h 3 0
src/include/nodes/parsenodes.h 39 0
src/include/parser/gramparse.h 1 0
src/include/parser/parse_type.h 1 0
src/test/regress/expected/prepare.out 107 0
src/test/regress/parallel_schedule 1 1
src/test/regress/serial_schedule 1 0
src/test/regress/sql/prepare.sql 45 0
Index: src/backend/commands/Makefile
===================================================================
RCS file: /var/lib/cvs/pgsql/src/backend/commands/Makefile,v
retrieving revision 1.28
diff -c -r1.28 Makefile
*** src/backend/commands/Makefile	15 Apr 2002 05:22:03 -0000	1.28
--- src/backend/commands/Makefile	28 Jun 2002 17:20:06 -0000
***************
*** 13,20 ****
  include $(top_builddir)/src/Makefile.global
  
  OBJS = aggregatecmds.o analyze.o async.o cluster.o comment.o copy.o \
! 	dbcommands.o define.o explain.o functioncmds.o \
! 	indexcmds.o lockcmds.o operatorcmds.o portalcmds.o proclang.o \
  	schemacmds.o sequence.o tablecmds.o trigger.o typecmds.o user.o \
  	vacuum.o vacuumlazy.o variable.o view.o
  
--- 13,20 ----
  include $(top_builddir)/src/Makefile.global
  
  OBJS = aggregatecmds.o analyze.o async.o cluster.o comment.o copy.o \
! 	dbcommands.o define.o explain.o functioncmds.o indexcmds.o \
! 	lockcmds.o operatorcmds.o portalcmds.o prepare.o proclang.o \
  	schemacmds.o sequence.o tablecmds.o trigger.o typecmds.o user.o \
  	vacuum.o vacuumlazy.o variable.o view.o
  
Index: src/backend/commands/prepare.c
===================================================================
RCS file: src/backend/commands/prepare.c
diff -N src/backend/commands/prepare.c
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- src/backend/commands/prepare.c	28 Jun 2002 17:20:06 -0000
***************
*** 0 ****
--- 1,387 ----
+ /*-------------------------------------------------------------------------
+  *
+  * prepare.c
+  *	  Prepareable SQL statements via PREPARE, EXECUTE and DEALLOCATE
+  *
+  * Copyright (c) 2002, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *	  $Header$
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres.h"
+ 
+ #include "commands/prepare.h"
+ #include "executor/executor.h"
+ #include "utils/guc.h"
+ #include "optimizer/planner.h"
+ #include "optimizer/planmain.h"
+ #include "rewrite/rewriteHandler.h"
+ #include "tcop/pquery.h"
+ #include "tcop/tcopprot.h"
+ #include "tcop/utility.h"
+ #include "utils/hsearch.h"
+ #include "utils/memutils.h"
+ 
+ #define HASH_KEY_LEN NAMEDATALEN
+ 
+ typedef struct
+ {
+ 	char		 key[HASH_KEY_LEN];
+ 	QueryData	*data;
+ } HashEntry;
+ 
+ /*
+  * The hash table in which prepared queries are stored. This is
+  * per-backend: query plans are not shared between backends.
+  * The keys for this hash table are the arguments to PREPARE
+  * and EXECUTE ("plan names"); the values are QueryData structs.
+  */
+ static HTAB *prepared_queries = NULL;
+ 
+ static void StoreQuery(const char *plan_name, QueryData *query_data);
+ static void InitHashTable(void);
+ static void RunQuery(QueryDesc *qdesc, EState *state);
+ 
+ void
+ PrepareQuery(PrepareStmt *stmt)
+ {
+ 	QueryData query_data;
+ 	List *plan_list = NIL;
+ 	List *query_list,
+ 		 *query_list_item;
+ 
+ 	if (!stmt->name)
+ 		elog(ERROR, "No statement name given.");
+ 
+ 	if (stmt->query->commandType == CMD_UTILITY)
+ 		elog(ERROR, "Utility statements cannot be prepared.");
+ 
+ 	/* Rewrite the query. The result could be 0, 1, or many queries. */
+ 	query_list = QueryRewrite(stmt->query);
+ 
+ 	foreach(query_list_item, query_list)
+ 	{
+ 		Plan  *plan;
+ 		Query *query = (Query *) lfirst(query_list_item);
+ 
+ 		/* We can't generate plans for utility statements. */
+ 		if (query->commandType == CMD_UTILITY)
+ 			continue;
+ 
+ 		/* Call the query planner to generate a plan. */
+ 		plan = planner(query);
+ 
+ 		plan_list = lappend(plan_list, plan);
+ 	}
+ 
+ 	query_data.plan_list = plan_list;
+ 	query_data.query_list = query_list;
+ 	query_data.nargs = stmt->nargs;
+ 	query_data.argtypes = stmt->argtoids;
+ 
+ 	StoreQuery(stmt->name, &query_data);
+ }
+ 
+ /*
+  * Store all the data pertaining to the query in the hash table using
+  * the specified key. A copy of the data is made before storage, so the
+  * caller can dispose of their copy.
+  */
+ static void
+ StoreQuery(const char *stmt_name, QueryData *query_data)
+ {
+ 	bool found;
+ 	HashEntry *entry;
+ 	QueryData *data;
+ 	MemoryContext oldcxt,
+ 				  entrycxt;
+ 	char key[HASH_KEY_LEN];
+ 
+ 	MemSet(key, 0, sizeof(key));
+ 	strncpy(key, stmt_name, sizeof(key));
+ 
+ 	/* Initialize the hash table, if necessary */
+ 	if (!prepared_queries)
+ 		InitHashTable();
+ 
+ 	hash_search(prepared_queries, key, HASH_FIND, &found);
+ 
+ 	if (found)
+ 		elog(ERROR, "Prepared statement with name \"%s\" already exists.", stmt_name);
+ 
+ 	entrycxt = AllocSetContextCreate(TopMemoryContext,
+ 									 stmt_name,
+ 									 1024,
+ 									 1024,
+ 									 ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 	oldcxt = MemoryContextSwitchTo(entrycxt);
+ 
+ 	/*
+ 	 * Create the hash table entry. We need to copy the data so that
+ 	 * it is stored in the correct memory context.
+ 	 */
+ 	data = (QueryData *) palloc(sizeof(QueryData));
+ 
+ 	data->context = entrycxt;
+ 	data->nargs = query_data->nargs;
+ 	data->plan_list = (List *) copyObject(query_data->plan_list);
+ 	data->query_list = (List *) copyObject(query_data->query_list);
+ 
+ 	if (data->nargs)
+ 	{
+ 		int mem_size = sizeof(Oid) * data->nargs;
+ 		data->argtypes = (Oid *) palloc(mem_size);
+ 		memcpy(data->argtypes, query_data->argtypes, mem_size);
+ 	}
+ 	else
+ 		data->argtypes = NULL;
+ 
+ 	/* Add entry to hash table */
+ 	MemoryContextSwitchTo(TopMemoryContext);
+ 
+ 	entry = (HashEntry *) hash_search(prepared_queries,
+ 									  key,
+ 									  HASH_ENTER,
+ 									  NULL);
+ 
+ 	if (!entry)
+ 		elog(ERROR, "Unable to store prepared statement \"%s\"!", stmt_name);
+ 
+ 	data->key = entry->key;
+ 	entry->data = data;
+ 
+ 	MemoryContextSwitchTo(oldcxt);
+ }
+ 
+ static void
+ InitHashTable(void)
+ {
+ 	HASHCTL hash_ctl;
+ 
+ 	MemSet(&hash_ctl, 0, sizeof(hash_ctl));
+ 
+ 	hash_ctl.keysize = HASH_KEY_LEN;
+ 	hash_ctl.entrysize = sizeof(HashEntry);
+ 
+ 	prepared_queries = hash_create("Prepared Queries",
+ 								   8,
+ 								   &hash_ctl,
+ 								   HASH_ELEM);
+ 
+ 	if (!prepared_queries)
+ 		elog(ERROR, "InitHashTable(): unable to create hash table.");
+ }
+ 
+ /*
+  * Implements the 'EXECUTE' utility statement.
+  */
+ void
+ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
+ {
+ 	ParamListInfo paramLI = NULL;
+ 	QueryData	*qdata;
+ 	List		*l,
+ 				*query_list,
+ 				*plan_list;
+ 
+ 	qdata = FetchQuery(stmt->name);
+ 
+ 	if (qdata->nargs)
+ 	{
+ 		List *l;
+ 		bool isNull;
+ 		int i = 0;
+ 
+ 		ExprContext *econtext = MakeExprContext(NULL, CurrentMemoryContext);
+ 
+ 		paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) * qdata->nargs);
+ 
+ 		foreach (l, stmt->params)
+ 		{
+ 			Node *n = lfirst(l);
+ 			fix_opids(n);
+ 
+ 			paramLI[i].value = ExecEvalExprSwitchContext(n, econtext, &isNull, NULL);
+ 			paramLI[i].kind = PARAM_NUM;
+ 			paramLI[i].id = i + 1;
+ 			paramLI[i].isnull = isNull;
+ 
+ 			i++;
+ 		}
+ 
+ 		pfree(econtext);
+ 	}
+ 
+ 	query_list = qdata->query_list;
+ 	plan_list = qdata->plan_list;
+ 
+ 	Assert(length(query_list) == length(plan_list));
+ 
+ 	foreach(l, query_list)
+ 	{
+ 		CommandDest dest;
+ 		bool is_last_query;
+ 		Query *query = lfirst(l);
+ 		Plan *plan = lfirst(plan_list);
+ 		plan_list = lnext(plan_list);
+ 
+ 		is_last_query = (plan_list == NIL);
+ 
+ 		/*
+ 		 * We only send the output to the final destination for the final
+ 		 * query we execute.
+ 		 */
+ 		dest = (is_last_query ? outputDest : None);
+ 
+ 		if (query->commandType == CMD_UTILITY)
+ 			ProcessUtility(query->utilityStmt, dest, NULL);
+ 		else
+ 		{
+ 			if (Show_executor_stats)
+ 				ResetUsage();
+ 
+ 			QueryDesc *qdesc = CreateQueryDesc(query, plan, dest, NULL);
+ 			EState *state = CreateExecutorState();
+ 
+ 			state->es_param_list_info = qdata->nargs ? paramLI : NULL;
+ 
+ 			if (stmt->into)
+ 			{
+ 				if (qdesc->operation != CMD_SELECT)
+ 					elog(ERROR, "INTO clause specified for non-SELECT query.");
+ 
+ 				query->into = stmt->into;
+ 				qdesc->dest = None;
+ 			}
+ 
+ 			RunQuery(qdesc, state);
+ 
+ 			if (Show_executor_stats)
+ 				ShowUsage("EXECUTOR STATISTICS");
+ 		}
+ 
+ 		/*
+ 		 * If we're processing multiple queries, we need to increment
+ 		 * the command counter between them. For the last query,
+ 		 * there's no need to do this, it's done automatically.
+ 		 */
+ 		if (! is_last_query)
+ 			CommandCounterIncrement();
+ 	}
+ 
+ 	if (paramLI)
+ 		pfree(paramLI);
+ 	pfree(qdata);
+ }
+ 
+ /*
+  * Fetch all data on the given prepared statement from the hash table in
+  * which it is stored.
+  */
+ QueryData *
+ FetchQuery(const char *plan_name)
+ {
+ 	QueryData *result;
+ 	HashEntry *entry;
+ 
+ 	/* See notes in DeallocateQuery() */
+ 	char key[HASH_KEY_LEN];
+ 
+ 	MemSet(key, 0, HASH_KEY_LEN);
+ 	strncpy(key, plan_name, HASH_KEY_LEN);
+ 
+ 	/*
+ 	 * If the hash table hasn't been initialized, it can't be storing
+ 	 * anything, therefore it couldn't possibly store our plan.
+ 	 */
+ 	if (!prepared_queries)
+ 		elog(ERROR, "Prepared statement with name \"%s\" does not exist", plan_name);
+ 
+ 	entry = (HashEntry *) hash_search(prepared_queries,
+ 									  key,
+ 									  HASH_FIND,
+ 									  NULL);
+ 
+ 	if (!entry)
+ 		elog(ERROR, "Prepared statement with name \"%s\" does not exist", plan_name);
+ 
+ 	result = palloc(sizeof(QueryData));
+ 
+ 	result->key = entry->data->key;
+ 	result->nargs = entry->data->nargs;
+ 	result->plan_list = (List *) copyObject(entry->data->plan_list);
+ 	result->query_list = (List *) copyObject(entry->data->query_list);
+ 	result->context = entry->data->context;
+ 
+ 	if (result->nargs)
+ 	{
+ 		int mem_size = sizeof(Oid) * result->nargs;
+ 		result->argtypes = (Oid *) palloc(mem_size);
+ 		memcpy(result->argtypes, entry->data->argtypes, mem_size);
+ 	}
+ 	else
+ 		result->argtypes = NULL;
+ 
+ 	return result;
+ }
+ 
+ /*
+  * Actually execute a prepared query. We can't use any of the existing
+  * routines in tcop/postgres.c because we need to manipulate part of the
+  * Executor (e.g. to inform it of parameters to execute) -- in any case,
+  * not much code is duplicated.
+  */
+ static void
+ RunQuery(QueryDesc *qdesc, EState *state)
+ {
+ 	TupleDesc tupdesc;
+ 
+ 	tupdesc = ExecutorStart(qdesc, state);
+ 
+ 	BeginCommand(NULL, qdesc->dest);
+ 
+ 	ExecutorRun(qdesc, state, state->es_direction, 0L);
+ 
+ 	ExecutorEnd(qdesc, state);
+ }
+ 
+ /*
+  * Implements the 'DEALLOCATE' utility statement: deletes the
+  * specified plan from storage.
+  */
+ void
+ DeallocateQuery(DeallocateStmt *stmt)
+ {
+ 	/*
+ 	 * We can't just use the statement name as supplied by the user: the
+ 	 * hash package is picky enough that it needs to be NULL-padded out
+ 	 * to the appropriate length to work correctly.
+ 	 */
+ 	char key[HASH_KEY_LEN];
+ 	HashEntry *entry;
+ 
+ 	MemSet(key, 0, HASH_KEY_LEN);
+ 	strncpy(key, stmt->name, HASH_KEY_LEN);
+ 
+ 	/*
+ 	 * First lookup the entry, so we can release some of the memory
+ 	 * it has allocated (when it's removed, hash_search() will return
+ 	 * a dangling pointer, so it needs to be done prior to HASH_REMOVE).
+ 	 * This requires an extra hash-table lookup, but DEALLOCATE
+ 	 * isn't exactly a performance bottleneck.
+ 	 */
+ 	entry = hash_search(prepared_queries, key, HASH_FIND, NULL);
+ 
+ 	if (!entry)
+ 		elog(ERROR, "No plan found with name \"%s\"", stmt->name);
+ 
+ 	if (entry->data && MemoryContextIsValid(entry->data->context))
+ 		MemoryContextDelete(entry->data->context);
+ 
+ 	/* Okay, now we can remove the hash table entry */
+ 	hash_search(prepared_queries, key, HASH_REMOVE, NULL);
+ }
Index: src/backend/parser/analyze.c
===================================================================
RCS file: /var/lib/cvs/pgsql/src/backend/parser/analyze.c,v
retrieving revision 1.237
diff -c -r1.237 analyze.c
*** src/backend/parser/analyze.c	20 Jun 2002 20:29:31 -0000	1.237
--- src/backend/parser/analyze.c	28 Jun 2002 17:20:06 -0000
***************
*** 20,25 ****
--- 20,26 ----
  #include "catalog/namespace.h"
  #include "catalog/pg_index.h"
  #include "catalog/pg_type.h"
+ #include "commands/prepare.h"
  #include "nodes/makefuncs.h"
  #include "parser/analyze.h"
  #include "parser/gramparse.h"
***************
*** 43,49 ****
  #include "mb/pg_wchar.h"
  #endif
  
- 
  /* State shared by transformCreateSchemaStmt and its subroutines */
  typedef struct
  {
--- 44,49 ----
***************
*** 94,99 ****
--- 94,101 ----
  static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
  static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt);
  static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
+ static Query *transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt);
+ static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt);
  static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
  						List **extras_before, List **extras_after);
  static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
***************
*** 280,285 ****
--- 282,295 ----
  										extras_before, extras_after);
  			break;
  
+ 		case T_PrepareStmt:
+ 			result = transformPrepareStmt(pstate, (PrepareStmt *) parseTree);
+ 			break;
+ 
+ 		case T_ExecuteStmt:
+ 			result = transformExecuteStmt(pstate, (ExecuteStmt *) parseTree);
+ 			break;
+ 
  			/*
  			 * Optimizable statements
  			 */
***************
*** 2651,2656 ****
--- 2661,2784 ----
  	qry->utilityStmt = (Node *) stmt;
  
  	return qry;
+ }
+ 
+ static Query *
+ transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt)
+ {
+ 	Query	*result = makeNode(Query);
+ 	List	*extras_before	= NIL,
+ 			*extras_after	= NIL;
+ 	Oid		*argtoids = NULL;
+ 
+ 	stmt->nargs = length(stmt->argtypes);
+ 
+ 	if (stmt->nargs)
+ 	{
+ 		List *l;
+ 		int i = 0;
+ 
+ 		argtoids = palloc(sizeof(*argtoids) * stmt->nargs);
+ 
+ 		foreach (l, stmt->argtypes)
+ 		{
+ 			TypeName *tn = lfirst(l);
+ 			Oid toid = typenameTypeId(tn);
+ 
+ 			if (!OidIsValid(toid))
+ 			{
+ 				elog(ERROR, "Argument $%d has invalid type \"%s\"",
+ 					 i + 1, TypeNameToString(tn));
+ 			}
+ 
+ 			argtoids[i++] = toid;
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * We need to adjust the number of parameters expected by the
+ 	 * rest of the system, so that $1, ... $n are parsed properly.
+ 	 * This is somewhat of a hack; however, the existing interfaces
+ 	 * only allow parameters to be specified when working with a
+ 	 * raw query string (parser(), pg_parse_query(), etc.), which
+ 	 * can't be used here.
+ 	 */
+ 	parser_param_init(argtoids, stmt->nargs);
+ 
+ 	stmt->argtoids = argtoids;
+ 
+ 	stmt->query = transformStmt(pstate, (Node *) stmt->query,
+ 								&extras_before, &extras_after);
+ 
+ 	result->commandType = CMD_UTILITY;
+ 	result->utilityStmt = (Node *) stmt;
+ 
+ 	return result;
+ }
+ 
+ static Query *
+ transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt)
+ {
+ 	Query *result = makeNode(Query);
+ 	QueryData *qdata;
+ 
+ 	result->commandType = CMD_UTILITY;
+ 	result->utilityStmt = (Node *) stmt;
+ 
+ 	qdata = FetchQuery(stmt->name);
+ 
+ 	if (!qdata)
+ 		elog(ERROR, "No such prepared statement: %s", stmt->name);
+ 
+ 	if (stmt->params || qdata->nargs)
+ 	{
+ 		List *l;
+ 		Oid expected_type_id,
+ 			given_type_id;
+ 		int i = 0;
+ 		int nparams = length(stmt->params);
+ 
+ 		if (nparams != qdata->nargs)
+ 		{
+ 			elog(ERROR, "Wrong number of parameters, expected %d but got %d",
+ 				 qdata->nargs, nparams);
+ 		}
+ 
+ 		foreach (l, stmt->params)
+ 		{
+ 			Node *expr = lfirst(l);
+ 
+ 			expr = transformExpr(pstate, expr);
+ 
+ 			given_type_id = exprType(expr);
+ 			expected_type_id = qdata->argtypes[i];
+ 
+ 			if (given_type_id != expected_type_id)
+ 			{
+ 				expr = CoerceTargetExpr(pstate,
+ 										expr,
+ 										given_type_id,
+ 										expected_type_id,
+ 										-1,
+ 										false);
+ 
+ 				if (!expr)
+ 				{
+ 					elog(ERROR, "Parameter $%d of type \"%s\" cannot be "
+ 								"coerced into the expected type (\"%s\").\n\t"
+ 								"You will need to rewrite or cast the expression.",
+ 						 i + 1,
+ 						 typeidToString(given_type_id),
+ 						 typeidToString(expected_type_id));
+ 				}
+ 			}
+ 
+ 			lfirst(l) = expr;
+ 			i++;
+ 		}
+ 	}
+ 
+ 	return result;
  }
  
  /* exported so planner can check again after rewriting, query pullup, etc */
Index: src/backend/parser/gram.y
===================================================================
RCS file: /var/lib/cvs/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.334
diff -c -r2.334 gram.y
*** src/backend/parser/gram.y	22 Jun 2002 02:04:45 -0000	2.334
--- src/backend/parser/gram.y	28 Jun 2002 17:20:06 -0000
***************
*** 152,158 ****
  		SelectStmt, TransactionStmt, TruncateStmt,
  		UnlistenStmt, UpdateStmt, VacuumStmt,
  		VariableResetStmt, VariableSetStmt, VariableShowStmt,
! 		ViewStmt, CheckPointStmt
  
  %type <node>	select_no_parens, select_with_parens, select_clause,
  				simple_select
--- 152,159 ----
  		SelectStmt, TransactionStmt, TruncateStmt,
  		UnlistenStmt, UpdateStmt, VacuumStmt,
  		VariableResetStmt, VariableSetStmt, VariableShowStmt,
! 		ViewStmt, CheckPointStmt, DeallocateStmt, ExecuteStmt,
! 		PrepareStmt
  
  %type <node>	select_no_parens, select_with_parens, select_clause,
  				simple_select
***************
*** 217,223 ****
  				target_list, update_target_list, insert_column_list,
  				insert_target_list, def_list, opt_indirection,
  				group_clause, TriggerFuncArgs, select_limit,
! 				opt_select_limit
  
  %type <range>	into_clause, OptTempTableName
  
--- 218,225 ----
  				target_list, update_target_list, insert_column_list,
  				insert_target_list, def_list, opt_indirection,
  				group_clause, TriggerFuncArgs, select_limit,
! 				opt_select_limit, execute_using, prepare_type_clause,
! 				prepare_type_list
  
  %type <range>	into_clause, OptTempTableName
  
***************
*** 333,339 ****
  	CREATEUSER, CROSS, CURRENT_DATE, CURRENT_TIME,
  	CURRENT_TIMESTAMP, CURRENT_USER, CURSOR, CYCLE,
  
! 	DATABASE, DAY_P, DEC, DECIMAL, DECLARE, DEFAULT,
  	DEFERRABLE, DEFERRED, DEFINER, DELETE_P, DELIMITER, DELIMITERS,
      DESC, DISTINCT, DO, DOMAIN_P, DOUBLE, DROP,
  
--- 335,341 ----
  	CREATEUSER, CROSS, CURRENT_DATE, CURRENT_TIME,
  	CURRENT_TIMESTAMP, CURRENT_USER, CURSOR, CYCLE,
  
! 	DATABASE, DAY_P, DEALLOCATE, DEC, DECIMAL, DECLARE, DEFAULT,
  	DEFERRABLE, DEFERRED, DEFINER, DELETE_P, DELIMITER, DELIMITERS,
      DESC, DISTINCT, DO, DOMAIN_P, DOUBLE, DROP,
  
***************
*** 369,375 ****
  	ORDER, OUT_P, OUTER_P, OVERLAPS, OVERLAY, OWNER,
  
  	PARTIAL, PASSWORD, PATH_P, PENDANT, PLACING, POSITION,
! 	PRECISION, PRIMARY, PRIOR, PRIVILEGES, PROCEDURE,
  	PROCEDURAL,
  
  	READ, REAL, REFERENCES, REINDEX, RELATIVE, RENAME, REPLACE,
--- 371,377 ----
  	ORDER, OUT_P, OUTER_P, OVERLAPS, OVERLAY, OWNER,
  
  	PARTIAL, PASSWORD, PATH_P, PENDANT, PLACING, POSITION,
! 	PRECISION, PREPARE, PRIMARY, PRIOR, PRIVILEGES, PROCEDURE,
  	PROCEDURAL,
  
  	READ, REAL, REFERENCES, REINDEX, RELATIVE, RENAME, REPLACE,
***************
*** 492,503 ****
--- 494,507 ----
  			| DropSchemaStmt
  			| TruncateStmt
  			| CommentStmt
+ 			| DeallocateStmt
  			| DropGroupStmt
  			| DropPLangStmt
  			| DropAssertStmt
  			| DropTrigStmt
  			| DropRuleStmt
  			| DropUserStmt
+ 			| ExecuteStmt
  			| ExplainStmt
  			| FetchStmt
  			| GrantStmt
***************
*** 526,531 ****
--- 530,536 ----
  			| VariableResetStmt
  			| ConstraintsSetStmt
  			| CheckPointStmt
+ 			| PrepareStmt
  			| /*EMPTY*/
  				{ $$ = (Node *)NULL; }
  		;
***************
*** 3803,3808 ****
--- 3808,3882 ----
  				}
  		;
  
+ /*****************************************************************************
+  *
+  *		QUERY:
+  *				PREPARE <plan_name> FROM <query>
+  *
+  *****************************************************************************/
+ 
+ PrepareStmt: PREPARE name prepare_type_clause FROM OptimizableStmt
+ 				{
+ 					PrepareStmt *n = makeNode(PrepareStmt);
+ 					n->name = $2;
+ 					n->argtypes = $3;
+ 					n->query = (Query *) $5;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ prepare_type_clause:
+ 				 '(' prepare_type_list ')'	{ $$ = $2; }
+ 				| /* EMPTY */				{ $$ = NIL; }
+ 		;
+ 
+ prepare_type_list:
+ 				  Typename				{ $$ = makeList1($1); }
+ 				| prepare_type_list ',' Typename
+ 										{ $$ = lappend($1, $3); }
+ 				| /* EMPTY */			{ $$ = NIL; }
+ 		;
+ 
+ /*****************************************************************************
+  *
+  *		QUERY:
+  *				EXECUTE <plan_name> [INTO ...] [USING expr, ...]
+  *
+  *****************************************************************************/
+ 
+ ExecuteStmt: EXECUTE name into_clause execute_using
+ 				{
+ 					ExecuteStmt *n = makeNode(ExecuteStmt);
+ 					n->name = $2;
+ 					n->into = $3;
+ 					n->params = $4;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ execute_using: USING expr_list		{ $$ = $2; }
+ 		| /* EMPTY */				{ $$ = NULL; }
+ 		;
+ 
+ /*****************************************************************************
+  *
+  *		QUERY:
+  *				DEALLOCATE <plan_name>
+  *
+  *****************************************************************************/
+ 
+ DeallocateStmt: DEALLOCATE opt_prepare name
+ 					{
+ 						DeallocateStmt *n = makeNode(DeallocateStmt);
+ 						n->name = $3;
+ 						$$ = (Node *) n;
+ 					}
+ 		;
+ 
+ /* Ignored, for SQL92 spec compliance */
+ opt_prepare: PREPARE		{}
+ 		| /* EMPTY */		{}
+ 		;
  
  /*****************************************************************************
   *																			 *
***************
*** 7176,7181 ****
--- 7250,7266 ----
  	 * Keep enough information around to fill out the type of param nodes
  	 * used in postquel functions
  	 */
+ 	parser_param_init(typev, nargs);
+ }
+ 
+ /*
+  * Do the initialization required for processing parameters. This
+  * is also called by transformPrepareStmt() to update the parser's
+  * view of the parameters in a prepared statement.
+  */
+ void
+ parser_param_init(Oid *typev, int nargs)
+ {
  	param_type_info = typev;
  	pfunc_num_args = nargs;
  }
Index: src/backend/parser/keywords.c
===================================================================
RCS file: /var/lib/cvs/pgsql/src/backend/parser/keywords.c,v
retrieving revision 1.117
diff -c -r1.117 keywords.c
*** src/backend/parser/keywords.c	22 Jun 2002 02:04:45 -0000	1.117
--- src/backend/parser/keywords.c	28 Jun 2002 17:20:06 -0000
***************
*** 91,96 ****
--- 91,97 ----
  	{"cycle", CYCLE},
  	{"database", DATABASE},
  	{"day", DAY_P},
+ 	{"deallocate", DEALLOCATE},
  	{"dec", DEC},
  	{"decimal", DECIMAL},
  	{"declare", DECLARE},
***************
*** 225,230 ****
--- 226,232 ----
  	{"placing", PLACING},
  	{"position", POSITION},
  	{"precision", PRECISION},
+ 	{"prepare", PREPARE},
  	{"primary", PRIMARY},
  	{"prior", PRIOR},
  	{"privileges", PRIVILEGES},
Index: src/backend/parser/parse_type.c
===================================================================
RCS file: /var/lib/cvs/pgsql/src/backend/parser/parse_type.c,v
retrieving revision 1.43
diff -c -r1.43 parse_type.c
*** src/backend/parser/parse_type.c	20 Jun 2002 20:29:33 -0000	1.43
--- src/backend/parser/parse_type.c	28 Jun 2002 17:20:06 -0000
***************
*** 277,282 ****
--- 277,295 ----
  	return (Type) tup;
  }
  
+ /* given a Type structure, return the name of the type in string form */
+ char *
+ typeidToString(Oid id)
+ {
+ 	Type type = typeidType(id);
+ 
+ 	char *name = typeTypeName(type);
+ 
+ 	ReleaseSysCache((HeapTuple) type);
+ 
+ 	return name;
+ }
+ 
  /* given type (as type struct), return the type OID */
  Oid
  typeTypeId(Type tp)
Index: src/backend/tcop/postgres.c
===================================================================
RCS file: /var/lib/cvs/pgsql/src/backend/tcop/postgres.c,v
retrieving revision 1.268
diff -c -r1.268 postgres.c
*** src/backend/tcop/postgres.c	20 Jun 2002 20:29:36 -0000	1.268
--- src/backend/tcop/postgres.c	28 Jun 2002 17:20:06 -0000
***************
*** 2426,2431 ****
--- 2426,2443 ----
  			tag = "REINDEX";
  			break;
  
+ 		case T_ExecuteStmt:
+ 			tag = "EXECUTE";
+ 			break;
+ 
+ 		case T_PrepareStmt:
+ 			tag = "PREPARE";
+ 			break;
+ 
+ 		case T_DeallocateStmt:
+ 			tag = "DEALLOCATE";
+ 			break;
+ 
  		default:
  			elog(LOG, "CreateCommandTag: unknown parse node type %d",
  				 nodeTag(parsetree));
Index: src/backend/tcop/utility.c
===================================================================
RCS file: /var/lib/cvs/pgsql/src/backend/tcop/utility.c,v
retrieving revision 1.159
diff -c -r1.159 utility.c
*** src/backend/tcop/utility.c	20 Jun 2002 20:29:36 -0000	1.159
--- src/backend/tcop/utility.c	28 Jun 2002 17:20:06 -0000
***************
*** 29,34 ****
--- 29,35 ----
  #include "commands/explain.h"
  #include "commands/lockcmds.h"
  #include "commands/portalcmds.h"
+ #include "commands/prepare.h"
  #include "commands/proclang.h"
  #include "commands/schemacmds.h"
  #include "commands/sequence.h"
***************
*** 351,356 ****
--- 352,375 ----
  					SetQuerySnapshot();
  
  				DoCopy(stmt);
+ 			}
+ 			break;
+ 
+ 		case T_PrepareStmt:
+ 			{
+ 				PrepareQuery((PrepareStmt *) parsetree);
+ 			}
+ 			break;
+ 
+ 		case T_ExecuteStmt:
+ 			{
+ 				ExecuteQuery((ExecuteStmt *) parsetree, dest);
+ 			}
+ 			break;
+ 
+ 		case T_DeallocateStmt:
+ 			{
+ 				DeallocateQuery((DeallocateStmt *) parsetree);
  			}
  			break;
  
Index: src/include/commands/prepare.h
===================================================================
RCS file: src/include/commands/prepare.h
diff -N src/include/commands/prepare.h
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- src/include/commands/prepare.h	28 Jun 2002 17:20:06 -0000
***************
*** 0 ****
--- 1,26 ----
+ #ifndef _PREPARE_H
+ #define _PREPARE_H
+ 
+ #include "nodes/parsenodes.h"
+ #include "tcop/dest.h"
+ 
+ /* All the data we need to execute a stored query */
+ typedef struct
+ {
+ 	char			*key;
+ 	List			*plan_list;
+ 	List			*query_list;
+ 	int				 nargs;
+ 	Oid				*argtypes;
+ 	MemoryContext	 context;
+ } QueryData;
+ 
+ extern void PrepareQuery(PrepareStmt *stmt);
+ 
+ extern void ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest);
+ 
+ extern void DeallocateQuery(DeallocateStmt *stmt);
+ 
+ extern QueryData *FetchQuery(const char *plan_name);
+ 
+ #endif
Index: src/include/nodes/nodes.h
===================================================================
RCS file: /var/lib/cvs/pgsql/src/include/nodes/nodes.h,v
retrieving revision 1.109
diff -c -r1.109 nodes.h
*** src/include/nodes/nodes.h	20 Jun 2002 20:29:51 -0000	1.109
--- src/include/nodes/nodes.h	28 Jun 2002 17:20:06 -0000
***************
*** 198,203 ****
--- 198,206 ----
  	T_CreateSchemaStmt,
  	T_AlterDatabaseSetStmt,
  	T_AlterUserSetStmt,
+ 	T_DeallocateStmt,
+ 	T_ExecuteStmt,
+ 	T_PrepareStmt,
  
  	T_A_Expr = 700,
  	T_ColumnRef,
Index: src/include/nodes/parsenodes.h
===================================================================
RCS file: /var/lib/cvs/pgsql/src/include/nodes/parsenodes.h,v
retrieving revision 1.182
diff -c -r1.182 parsenodes.h
*** src/include/nodes/parsenodes.h	20 Jun 2002 20:29:51 -0000	1.182
--- src/include/nodes/parsenodes.h	28 Jun 2002 17:20:06 -0000
***************
*** 1494,1497 ****
--- 1494,1536 ----
  	bool		all;
  } ReindexStmt;
  
+ /* ----------------------
+  *		PREPARE Statement
+  * ----------------------
+  */
+ typedef struct PrepareStmt
+ {
+ 	NodeTag		 type;
+ 	char		*name;			/* Name of plan, arbitrary */
+ 	int			 nargs;			/* # of parameters */
+ 	List		*argtypes;		/* Types of parameters (TypeNames) */
+ 	Oid			*argtoids;		/* The OIDs of the param types */
+ 	Query		*query;			/* The query itself */
+ } PrepareStmt;
+ 
+ 
+ /* ----------------------
+  *		EXECUTE Statement
+  * ----------------------
+  */
+ 
+ typedef struct ExecuteStmt
+ {
+ 	NodeTag		 type;
+ 	char		*name;			/* The name of the plan to execute */
+ 	RangeVar	*into;			/* The relation to store the results in */
+ 	List		*params;		/* Values of parameters */
+ } ExecuteStmt;
+ 
+ 
+ /* ----------------------
+  *		DEALLOCATE Statement
+  * ----------------------
+  */
+ typedef struct DeallocateStmt
+ {
+ 	NodeTag		type;
+ 	char	   *name;			/* The name of the plan to remove */
+ } DeallocateStmt;
+ 
  #endif   /* PARSENODES_H */
Index: src/include/parser/gramparse.h
===================================================================
RCS file: /var/lib/cvs/pgsql/src/include/parser/gramparse.h,v
retrieving revision 1.23
diff -c -r1.23 gramparse.h
*** src/include/parser/gramparse.h	20 Jun 2002 20:29:51 -0000	1.23
--- src/include/parser/gramparse.h	28 Jun 2002 17:20:06 -0000
***************
*** 29,34 ****
--- 29,35 ----
  
  /* from gram.y */
  extern void parser_init(Oid *typev, int nargs);
+ extern void parser_param_init(Oid *typev, int nargs);
  extern Oid	param_type(int t);
  extern int	yyparse(void);
  extern List *SystemFuncName(char *name);
Index: src/include/parser/parse_type.h
===================================================================
RCS file: /var/lib/cvs/pgsql/src/include/parser/parse_type.h,v
retrieving revision 1.23
diff -c -r1.23 parse_type.h
*** src/include/parser/parse_type.h	20 Jun 2002 20:29:52 -0000	1.23
--- src/include/parser/parse_type.h	28 Jun 2002 17:20:06 -0000
***************
*** 27,32 ****
--- 27,33 ----
  
  extern bool typeidIsValid(Oid id);
  extern Type typeidType(Oid id);
+ extern char *typeidToString(Oid id);
  
  extern Oid	typeTypeId(Type tp);
  extern int16 typeLen(Type t);
Index: src/test/regress/parallel_schedule
===================================================================
RCS file: /var/lib/cvs/pgsql/src/test/regress/parallel_schedule,v
retrieving revision 1.10
diff -c -r1.10 parallel_schedule
*** src/test/regress/parallel_schedule	20 Jun 2002 17:09:42 -0000	1.10
--- src/test/regress/parallel_schedule	28 Jun 2002 17:20:07 -0000
***************
*** 74,77 ****
  # The sixth group of parallel test
  # ----------
  # "plpgsql" cannot run concurrently with "rules"
! test: limit plpgsql temp domain rangefuncs
--- 74,77 ----
  # The sixth group of parallel test
  # ----------
  # "plpgsql" cannot run concurrently with "rules"
! test: limit plpgsql temp domain rangefuncs prepare
Index: src/test/regress/serial_schedule
===================================================================
RCS file: /var/lib/cvs/pgsql/src/test/regress/serial_schedule,v
retrieving revision 1.10
diff -c -r1.10 serial_schedule
*** src/test/regress/serial_schedule	20 Jun 2002 17:09:42 -0000	1.10
--- src/test/regress/serial_schedule	28 Jun 2002 17:20:07 -0000
***************
*** 83,85 ****
--- 83,86 ----
  test: temp
  test: domain
  test: rangefuncs
+ test: prepare
Index: src/test/regress/expected/prepare.out
===================================================================
RCS file: src/test/regress/expected/prepare.out
diff -N src/test/regress/expected/prepare.out
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- src/test/regress/expected/prepare.out	28 Jun 2002 17:20:07 -0000
***************
*** 0 ****
--- 1,107 ----
+ -- Regression tests for prepareable statements
+ PREPARE q1 FROM SELECT 1;
+ EXECUTE q1;
+  ?column? 
+ ----------
+         1
+ (1 row)
+ 
+ -- should fail
+ PREPARE q1 FROM SELECT 2;
+ ERROR:  Prepared statement with name "q1" already exists.
+ -- should succeed
+ DEALLOCATE q1;
+ PREPARE q1 FROM SELECT 2;
+ EXECUTE q1;
+  ?column? 
+ ----------
+         2
+ (1 row)
+ 
+ -- sql92 syntax
+ DEALLOCATE PREPARE q1;
+ -- parametized queries
+ PREPARE q2(text) FROM
+ 	SELECT datname, datdba, datistemplate, datallowconn
+ 	FROM pg_database WHERE datname = $1;
+ EXECUTE q2 USING 'regression';
+   datname   | datdba | datistemplate | datallowconn 
+ ------------+--------+---------------+--------------
+  regression |      1 | f             | t
+ (1 row)
+ 
+ PREPARE q3(text, int, float, boolean, oid, smallint) FROM
+ 	SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR
+ 	ten = $3::bigint OR true = $4 OR oid = $5 OR odd = $6::int);
+ EXECUTE q3 USING 'AAAAxx', 5::smallint, 10.5::float, false, 500::oid, 4::bigint;
+  unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 
+ ---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+     4502 |     412 |   0 |    2 |   2 |      2 |       2 |      502 |         502 |      4502 |     4502 |   4 |    5 | ERAAAA   | WPAAAA   | AAAAxx
+      102 |     612 |   0 |    2 |   2 |      2 |       2 |      102 |         102 |       102 |      102 |   4 |    5 | YDAAAA   | OXAAAA   | AAAAxx
+     7602 |    1040 |   0 |    2 |   2 |      2 |       2 |      602 |        1602 |      2602 |     7602 |   4 |    5 | KGAAAA   | AOBAAA   | AAAAxx
+      902 |    1104 |   0 |    2 |   2 |      2 |       2 |      902 |         902 |       902 |      902 |   4 |    5 | SIAAAA   | MQBAAA   | AAAAxx
+     4902 |    1600 |   0 |    2 |   2 |      2 |       2 |      902 |         902 |      4902 |     4902 |   4 |    5 | OGAAAA   | OJCAAA   | AAAAxx
+     9502 |    1812 |   0 |    2 |   2 |      2 |       2 |      502 |        1502 |      4502 |     9502 |   4 |    5 | MBAAAA   | SRCAAA   | AAAAxx
+     4702 |    2520 |   0 |    2 |   2 |      2 |       2 |      702 |         702 |      4702 |     4702 |   4 |    5 | WYAAAA   | YSDAAA   | AAAAxx
+     1002 |    2580 |   0 |    2 |   2 |      2 |       2 |        2 |        1002 |      1002 |     1002 |   4 |    5 | OMAAAA   | GVDAAA   | AAAAxx
+        2 |    2716 |   0 |    2 |   2 |      2 |       2 |        2 |           2 |         2 |        2 |   4 |    5 | CAAAAA   | MAEAAA   | AAAAxx
+      802 |    2908 |   0 |    2 |   2 |      2 |       2 |      802 |         802 |       802 |      802 |   4 |    5 | WEAAAA   | WHEAAA   | AAAAxx
+     6402 |    3808 |   0 |    2 |   2 |      2 |       2 |      402 |         402 |      1402 |     6402 |   4 |    5 | GMAAAA   | MQFAAA   | AAAAxx
+     8602 |    5440 |   0 |    2 |   2 |      2 |       2 |      602 |         602 |      3602 |     8602 |   4 |    5 | WSAAAA   | GBIAAA   | AAAAxx
+     8402 |    5708 |   0 |    2 |   2 |      2 |       2 |      402 |         402 |      3402 |     8402 |   4 |    5 | ELAAAA   | OLIAAA   | AAAAxx
+     2102 |    6184 |   0 |    2 |   2 |      2 |       2 |      102 |         102 |      2102 |     2102 |   4 |    5 | WCAAAA   | WDJAAA   | AAAAxx
+     4202 |    6628 |   0 |    2 |   2 |      2 |       2 |      202 |         202 |      4202 |     4202 |   4 |    5 | QFAAAA   | YUJAAA   | AAAAxx
+     2902 |    6816 |   0 |    2 |   2 |      2 |       2 |      902 |         902 |      2902 |     2902 |   4 |    5 | QHAAAA   | ECKAAA   | AAAAxx
+     2302 |    7112 |   0 |    2 |   2 |      2 |       2 |      302 |         302 |      2302 |     2302 |   4 |    5 | OKAAAA   | ONKAAA   | AAAAxx
+     3202 |    7128 |   0 |    2 |   2 |      2 |       2 |      202 |        1202 |      3202 |     3202 |   4 |    5 | ETAAAA   | EOKAAA   | AAAAxx
+     7802 |    7508 |   0 |    2 |   2 |      2 |       2 |      802 |        1802 |      2802 |     7802 |   4 |    5 | COAAAA   | UCLAAA   | AAAAxx
+     4102 |    7676 |   0 |    2 |   2 |      2 |       2 |      102 |         102 |      4102 |     4102 |   4 |    5 | UBAAAA   | GJLAAA   | AAAAxx
+     8302 |    7800 |   0 |    2 |   2 |      2 |       2 |      302 |         302 |      3302 |     8302 |   4 |    5 | IHAAAA   | AOLAAA   | AAAAxx
+     1702 |    7940 |   0 |    2 |   2 |      2 |       2 |      702 |        1702 |      1702 |     1702 |   4 |    5 | MNAAAA   | KTLAAA   | AAAAxx
+     2202 |    8028 |   0 |    2 |   2 |      2 |       2 |      202 |         202 |      2202 |     2202 |   4 |    5 | SGAAAA   | UWLAAA   | AAAAxx
+     1602 |    8148 |   0 |    2 |   2 |      2 |       2 |      602 |        1602 |      1602 |     1602 |   4 |    5 | QJAAAA   | KBMAAA   | AAAAxx
+     5602 |    8796 |   0 |    2 |   2 |      2 |       2 |      602 |        1602 |       602 |     5602 |   4 |    5 | MHAAAA   | IANAAA   | AAAAxx
+     6002 |    8932 |   0 |    2 |   2 |      2 |       2 |        2 |           2 |      1002 |     6002 |   4 |    5 | WWAAAA   | OFNAAA   | AAAAxx
+     3902 |    9224 |   0 |    2 |   2 |      2 |       2 |      902 |        1902 |      3902 |     3902 |   4 |    5 | CUAAAA   | UQNAAA   | AAAAxx
+     9602 |    9972 |   0 |    2 |   2 |      2 |       2 |      602 |        1602 |      4602 |     9602 |   4 |    5 | IFAAAA   | OTOAAA   | AAAAxx
+     8002 |    9980 |   0 |    2 |   2 |      2 |       2 |        2 |           2 |      3002 |     8002 |   4 |    5 | UVAAAA   | WTOAAA   | AAAAxx
+ (29 rows)
+ 
+ -- too few params
+ EXECUTE q3 USING 'bool';
+ ERROR:  Wrong number of parameters, expected 6 but got 1
+ -- too many params
+ EXECUTE q3 USING 'bytea', 5::smallint, 10.5::float, false, 500::oid, 4::bigint, true;
+ ERROR:  Wrong number of parameters, expected 6 but got 7
+ -- wrong param types
+ EXECUTE q3 USING 5::smallint, 10.5::float, false, 500::oid, 4::bigint, 'bytea';
+ ERROR:  Parameter $2 of type "float8" cannot be coerced into the expected type ("int4").
+ 	You will need to rewrite or cast the expression.
+ -- invalid type
+ PREPARE q4(nonexistenttype) FROM SELECT $1;
+ ERROR:  Type "nonexistenttype" does not exist
+ -- execute into
+ PREPARE q5(int, text) FROM
+ 	SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2;
+ EXECUTE q5 INTO TEMPORARY q5_prep_results USING 200, 'DTAAAA';
+ SELECT * FROM q5_prep_results;
+  unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 
+ ---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+     2525 |      64 |   1 |    1 |   5 |      5 |      25 |      525 |         525 |      2525 |     2525 |  50 |   51 | DTAAAA   | MCAAAA   | AAAAxx
+     7257 |    1895 |   1 |    1 |   7 |     17 |      57 |      257 |        1257 |      2257 |     7257 | 114 |  115 | DTAAAA   | XUCAAA   | VVVVxx
+     9961 |    2058 |   1 |    1 |   1 |      1 |      61 |      961 |        1961 |      4961 |     9961 | 122 |  123 | DTAAAA   | EBDAAA   | OOOOxx
+     3877 |    4060 |   1 |    1 |   7 |     17 |      77 |      877 |        1877 |      3877 |     3877 | 154 |  155 | DTAAAA   | EAGAAA   | AAAAxx
+     4553 |    4113 |   1 |    1 |   3 |     13 |      53 |      553 |         553 |      4553 |     4553 | 106 |  107 | DTAAAA   | FCGAAA   | HHHHxx
+     7933 |    4514 |   1 |    1 |   3 |     13 |      33 |      933 |        1933 |      2933 |     7933 |  66 |   67 | DTAAAA   | QRGAAA   | OOOOxx
+     6581 |    4686 |   1 |    1 |   1 |      1 |      81 |      581 |         581 |      1581 |     6581 | 162 |  163 | DTAAAA   | GYGAAA   | OOOOxx
+     8609 |    5918 |   1 |    1 |   9 |      9 |       9 |      609 |         609 |      3609 |     8609 |  18 |   19 | DTAAAA   | QTIAAA   | OOOOxx
+     5229 |    6407 |   1 |    1 |   9 |      9 |      29 |      229 |        1229 |       229 |     5229 |  58 |   59 | DTAAAA   | LMJAAA   | VVVVxx
+     1173 |    6699 |   1 |    1 |   3 |     13 |      73 |      173 |        1173 |      1173 |     1173 | 146 |  147 | DTAAAA   | RXJAAA   | VVVVxx
+     3201 |    7309 |   1 |    1 |   1 |      1 |       1 |      201 |        1201 |      3201 |     3201 |   2 |    3 | DTAAAA   | DVKAAA   | HHHHxx
+     1849 |    8143 |   1 |    1 |   9 |      9 |      49 |      849 |        1849 |      1849 |     1849 |  98 |   99 | DTAAAA   | FBMAAA   | VVVVxx
+     9285 |    8469 |   1 |    1 |   5 |      5 |      85 |      285 |        1285 |      4285 |     9285 | 170 |  171 | DTAAAA   | TNMAAA   | HHHHxx
+      497 |    9092 |   1 |    1 |   7 |     17 |      97 |      497 |         497 |       497 |      497 | 194 |  195 | DTAAAA   | SLNAAA   | AAAAxx
+      200 |    9441 |   0 |    0 |   0 |      0 |       0 |      200 |         200 |       200 |      200 |   0 |    1 | SHAAAA   | DZNAAA   | HHHHxx
+     5905 |    9537 |   1 |    1 |   5 |      5 |       5 |      905 |        1905 |       905 |     5905 |  10 |   11 | DTAAAA   | VCOAAA   | HHHHxx
+ (16 rows)
+ 
Index: src/test/regress/sql/prepare.sql
===================================================================
RCS file: src/test/regress/sql/prepare.sql
diff -N src/test/regress/sql/prepare.sql
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- src/test/regress/sql/prepare.sql	28 Jun 2002 17:20:07 -0000
***************
*** 0 ****
--- 1,45 ----
+ -- Regression tests for prepareable statements
+ 
+ PREPARE q1 FROM SELECT 1;
+ EXECUTE q1;
+ 
+ -- should fail
+ PREPARE q1 FROM SELECT 2;
+ 
+ -- should succeed
+ DEALLOCATE q1;
+ PREPARE q1 FROM SELECT 2;
+ EXECUTE q1;
+ 
+ -- sql92 syntax
+ DEALLOCATE PREPARE q1;
+ 
+ -- parametized queries
+ PREPARE q2(text) FROM
+ 	SELECT datname, datdba, datistemplate, datallowconn
+ 	FROM pg_database WHERE datname = $1;
+ EXECUTE q2 USING 'regression';
+ 
+ PREPARE q3(text, int, float, boolean, oid, smallint) FROM
+ 	SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR
+ 	ten = $3::bigint OR true = $4 OR oid = $5 OR odd = $6::int);
+ 
+ EXECUTE q3 USING 'AAAAxx', 5::smallint, 10.5::float, false, 500::oid, 4::bigint;
+ 
+ -- too few params
+ EXECUTE q3 USING 'bool';
+ 
+ -- too many params
+ EXECUTE q3 USING 'bytea', 5::smallint, 10.5::float, false, 500::oid, 4::bigint, true;
+ 
+ -- wrong param types
+ EXECUTE q3 USING 5::smallint, 10.5::float, false, 500::oid, 4::bigint, 'bytea';
+ 
+ -- invalid type
+ PREPARE q4(nonexistenttype) FROM SELECT $1;
+ 
+ -- execute into
+ PREPARE q5(int, text) FROM
+ 	SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2;
+ EXECUTE q5 INTO TEMPORARY q5_prep_results USING 200, 'DTAAAA';
+ SELECT * FROM q5_prep_results;